Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b90c2647be | |||
| b9f278b0b0 |
@@ -78,31 +78,34 @@ Every defect listed in the AGENTS.md table has a regression test naming the beha
|
|||||||
`gitea.larvit.se/larvit/smpp-js` is the repository. `github.com/larvit/smpp-js` mirrors it and is the
|
`gitea.larvit.se/larvit/smpp-js` is the repository. `github.com/larvit/smpp-js` mirrors it and is the
|
||||||
place issues are filed. Maintainer's calls, 2026-09-13 and 2026-09-14.
|
place issues are filed. Maintainer's calls, 2026-09-13 and 2026-09-14.
|
||||||
|
|
||||||
- [x] `larvit/smpp-js` holds `main`, from `typescript`, and `v0.4.0`, from `master`. `rewrite-base`
|
- [x] **Move `main` and `v0.4.0` to `larvit/smpp-js`.** It holds `main`, from `typescript`, and
|
||||||
and the `renovate/*` branches stayed behind.
|
`v0.4.0`, from `master`. `rewrite-base` and the `renovate/*` branches stayed behind.
|
||||||
- [x] Fast-forward is the only merge style. `main` takes no pushes, requires `Test / lint
|
- [x] **Protect `main` with fast-forward merges and required checks.** Fast-forward is the only
|
||||||
(pull_request)` and `Test / test (*) (pull_request)`, blocks an outdated branch, and gives
|
merge style. `main` takes no pushes, requires `Test / lint (pull_request)` and `Test / test
|
||||||
admins no override. Every other branch takes force pushes.
|
(*) (pull_request)`, blocks an outdated branch, and gives admins no override. Every other
|
||||||
- [x] The workflows are in `.gitea/workflows/`. Tests run on pull requests only, the event the gate
|
branch takes force pushes.
|
||||||
reads; Renovate runs as a scheduled workflow, as on adf-codec.
|
- [x] **Run the workflows from `.gitea/workflows/`.** Tests run on pull requests only, the event the
|
||||||
- [x] The release publishes without provenance, which npm generates only on GitHub Actions and
|
gate reads; Renovate runs as a scheduled workflow, as on adf-codec.
|
||||||
GitLab CI/CD.
|
- [x] **Publish the release without provenance.** npm generates it only on GitHub Actions and GitLab
|
||||||
- [x] `package.json` names Gitea, and the GitHub mirror's issues as `bugs`. The README links
|
CI/CD.
|
||||||
|
- [x] **Name Gitea in `package.json`, and the GitHub mirror's issues as `bugs`.** The README links
|
||||||
absolutely: npmjs.com resolves a relative link against itself when the `repository` is not on
|
absolutely: npmjs.com resolves a relative link against itself when the `repository` is not on
|
||||||
GitHub. Its test badge is gone, since Gitea reports a workflow's status per branch and no
|
GitHub. Its test badge is gone, since Gitea reports a workflow's status per branch and no
|
||||||
workflow runs on `main`.
|
workflow runs on `main`.
|
||||||
- `RENOVATE_GITHUB_TOKEN` exists nowhere, so Renovate queries github.com unauthenticated, as
|
- **Add a github.com token for Renovate only if its lookups hit the rate limit.**
|
||||||
|
`RENOVATE_GITHUB_TOKEN` exists nowhere, so Renovate queries github.com unauthenticated, as
|
||||||
adf-codec's nightly run already does without a warning. `RENOVATE_TOKEN` is the Gitea token and
|
adf-codec's nightly run already does without a warning. `RENOVATE_TOKEN` is the Gitea token and
|
||||||
cannot stand in for it. Add a github.com token only if lookups hit the rate limit.
|
cannot stand in for it.
|
||||||
|
|
||||||
## Before publishing 0.5.0
|
## Before publishing 0.5.0
|
||||||
|
|
||||||
- [x] 0.5.0 rather than 1.0.0, while usage is this low. Maintainer's call, 2026-09-14.
|
- [x] **Publish the rewrite as 0.5.0, not 1.0.0, while usage is this low.** Maintainer's call,
|
||||||
- [x] `NPM_TOKEN`, which `.gitea/workflows/release.yaml` needs, is a Gitea organization secret.
|
2026-09-14.
|
||||||
- [x] Tag `v0.5.0` on Gitea to publish. The first publish creates `@larvit/smpp` on npm, provided the
|
- [x] **Hold `NPM_TOKEN` as a Gitea organization secret.** `.gitea/workflows/release.yaml` needs it.
|
||||||
token can publish under `@larvit`.
|
- [x] **Tag `v0.5.0` on Gitea to publish.** The first publish creates `@larvit/smpp` on npm,
|
||||||
- [x] `npm deprecate larvitsmpp` pointing at `@larvit/smpp`. Maintainer's call to run it; not
|
provided the token can publish under `@larvit`.
|
||||||
something CI should do.
|
- [x] **Deprecate `larvitsmpp` on npm, pointing at `@larvit/smpp`.** `npm deprecate larvitsmpp` is
|
||||||
|
the maintainer's call to run; not something CI should do.
|
||||||
|
|
||||||
## Retire the GitHub repository
|
## Retire the GitHub repository
|
||||||
|
|
||||||
@@ -110,52 +113,54 @@ Nothing here starts before 0.5.0 is published. Maintainer's call, 2026-09-14. Th
|
|||||||
deleting GitHub's old branches closes every pull request based on them without a reply, and GitHub
|
deleting GitHub's old branches closes every pull request based on them without a reply, and GitHub
|
||||||
refuses to delete its default branch.
|
refuses to delete its default branch.
|
||||||
|
|
||||||
- [x] Close the backlog below.
|
- [x] **Close the backlog below.**
|
||||||
- [x] Close [#71](https://github.com/larvit/larvitsmpp/pull/71), pointing at Gitea.
|
- [x] **Close [#71](https://github.com/larvit/larvitsmpp/pull/71), pointing at Gitea.**
|
||||||
- [x] Rename `larvit/larvitsmpp` to `larvit/smpp-js`. GitHub redirects the old URLs, and the `bugs`
|
- [x] **Rename `larvit/larvitsmpp` to `larvit/smpp-js`.** GitHub redirects the old URLs, and the
|
||||||
URL in `package.json` resolves from then on.
|
`bugs` URL in `package.json` resolves from then on.
|
||||||
- [x] Push `main` and make it GitHub's default branch.
|
- [x] **Push `main` and make it GitHub's default branch.**
|
||||||
- [x] Renovate is Silent for this repository in the Mend Developer Portal, so it opens nothing on
|
- [x] **Set Renovate to Silent for this repository in the Mend Developer Portal.** It then opens
|
||||||
GitHub while the organization-wide installation stays. CodeRabbit stays installed.
|
nothing on GitHub while the organization-wide installation stays. CodeRabbit stays installed.
|
||||||
Maintainer's call, 2026-09-14.
|
Maintainer's call, 2026-09-14.
|
||||||
- [x] Mirror to GitHub from `.gitea/workflows/mirror.yaml` and `mirror-delete.yaml`, with
|
- [x] **Mirror to GitHub from `.gitea/workflows/mirror.yaml` and `mirror-delete.yaml`, with
|
||||||
`MIRROR_GITHUB_TOKEN`. A push of a commit carrying the workflow, and the nightly run, send all of
|
`MIRROR_GITHUB_TOKEN`.** A push of a commit carrying the workflow, and the nightly run, send
|
||||||
Gitea's branches and tags, overwriting a same-named ref; a branch or tag deleted on Gitea is
|
all of Gitea's branches and tags, overwriting a same-named ref; a branch or tag deleted on
|
||||||
deleted there too. Refs only GitHub has stay. Maintainer's call, 2026-09-14.
|
Gitea is deleted there too. Refs only GitHub has stay. Maintainer's call, 2026-09-14.
|
||||||
- [x] GitHub's wiki, projects and Actions are off, the Travis app and webhook are gone, and its About
|
- [x] **Strip GitHub's repository to a mirror, and give both forges the package's About.** GitHub's
|
||||||
matches the package. Gitea carries the same description, website and topics, and sends issues
|
wiki, projects and Actions are off, the Travis app and webhook are gone, and its About matches
|
||||||
to GitHub as its external tracker.
|
the package. Gitea carries the same description, website and topics, and sends issues to
|
||||||
|
GitHub as its external tracker.
|
||||||
|
|
||||||
## Close the GitHub backlog
|
## Close the GitHub backlog
|
||||||
|
|
||||||
**Answer and close as fixed by 0.5.0**, the reply naming what fixed it:
|
**Answer and close as fixed by 0.5.0**, the reply naming what fixed it:
|
||||||
|
|
||||||
- [x] [#2](https://github.com/larvit/larvitsmpp/issues/2) Tests for the README examples:
|
- [x] **Close [#2](https://github.com/larvit/larvitsmpp/issues/2), tests for the README examples.**
|
||||||
`test/readme.test.ts`.
|
Fixed by `test/readme.test.ts`.
|
||||||
- [x] [#3](https://github.com/larvit/larvitsmpp/issues/3) Tests for flash messages:
|
- [x] **Close [#3](https://github.com/larvit/larvitsmpp/issues/3), tests for flash messages.** Fixed
|
||||||
`test/session.test.ts`.
|
by `test/session.test.ts`.
|
||||||
- [x] [#4](https://github.com/larvit/larvitsmpp/issues/4) DLR errors with `message_state` missing:
|
- [x] **Close [#4](https://github.com/larvit/larvitsmpp/issues/4), DLR errors with `message_state`
|
||||||
`dlrFromPdu()` parses the `stat:` receipt text when the TLVs are absent.
|
missing.** `dlrFromPdu()` parses the `stat:` receipt text when the TLVs are absent.
|
||||||
- [x] [#13](https://github.com/larvit/larvitsmpp/issues/13) Limit a long SMS to fewer segments: the
|
- [x] **Close [#13](https://github.com/larvit/larvitsmpp/issues/13), limit a long SMS to fewer
|
||||||
`maxSegments` send option.
|
segments.** Fixed by the `maxSegments` send option.
|
||||||
- [x] [#16](https://github.com/larvit/larvitsmpp/issues/16) Support all three bind types: bound and
|
- [x] **Close [#16](https://github.com/larvit/larvitsmpp/issues/16), support all three bind types.**
|
||||||
enforced in both directions.
|
Bound and enforced in both directions.
|
||||||
- [x] [#17](https://github.com/larvit/larvitsmpp/issues/17) `addr_ton`/`addr_npi` should be
|
- [x] **Close [#17](https://github.com/larvit/larvitsmpp/issues/17), `addr_ton`/`addr_npi` should be
|
||||||
settable: `sendSms()` takes all four, documented and tested.
|
settable.** `sendSms()` takes all four, documented and tested.
|
||||||
- [x] [#20](https://github.com/larvit/larvitsmpp/issues/20) Tests fail on current dependency
|
- [x] **Close [#20](https://github.com/larvit/larvitsmpp/issues/20), tests fail on current
|
||||||
versions: the mocha suite is gone; `node:test` on Node 18 to 26.
|
dependency versions.** The mocha suite is gone; `node:test` on Node 18 to 26.
|
||||||
- [x] [#33](https://github.com/larvit/larvitsmpp/issues/33) Large inbound text arrives as raw
|
- [x] **Close [#33](https://github.com/larvit/larvitsmpp/issues/33), large inbound text arrives as
|
||||||
`Buffer` segments: `IncomingRequests` reassembles a UDH-carrying `deliver_sm` into one `sms`
|
raw `Buffer` segments.** `IncomingRequests` reassembles a UDH-carrying `deliver_sm` into one
|
||||||
event.
|
`sms` event.
|
||||||
- [x] [#68](https://github.com/larvit/larvitsmpp/pull/68), a pull request: `message_id` in
|
- [x] **Close [#68](https://github.com/larvit/larvitsmpp/pull/68), a pull request for `message_id`
|
||||||
`submit_sm_resp`, spec DLR codes. All four hold: `sendResp()` always answers a `message_id`,
|
in `submit_sm_resp` and spec DLR codes.** All four hold: `sendResp()` always answers a
|
||||||
per segment; `stat:UNDELIV` is the 7-character code. Credit the reporter — the fork found real
|
`message_id`, per segment; `stat:UNDELIV` is the 7-character code. Credit the reporter — the
|
||||||
defects.
|
fork found real defects.
|
||||||
|
|
||||||
**Close as superseded**, all against 0.4.0 dependencies the rewrite does not have — `async`,
|
**Close as superseded**, all against 0.4.0 dependencies the rewrite does not have — `async`,
|
||||||
`coveralls`, `eslint`, `iconv-lite`, `larvitutils`, `mocha`, `mocha-eslint`, `portfinder`, `uuid`:
|
`coveralls`, `eslint`, `iconv-lite`, `larvitutils`, `mocha`, `mocha-eslint`, `portfinder`, `uuid`:
|
||||||
|
|
||||||
- [x] [#40](https://github.com/larvit/larvitsmpp/pull/40),
|
- [x] **Close the twelve dependency pull requests as superseded.**
|
||||||
|
[#40](https://github.com/larvit/larvitsmpp/pull/40),
|
||||||
[#41](https://github.com/larvit/larvitsmpp/pull/41),
|
[#41](https://github.com/larvit/larvitsmpp/pull/41),
|
||||||
[#42](https://github.com/larvit/larvitsmpp/pull/42),
|
[#42](https://github.com/larvit/larvitsmpp/pull/42),
|
||||||
[#45](https://github.com/larvit/larvitsmpp/pull/45),
|
[#45](https://github.com/larvit/larvitsmpp/pull/45),
|
||||||
@@ -175,8 +180,9 @@ the rewrite, for a dependency added later. Maintainer's call, 2026-09-14.
|
|||||||
|
|
||||||
**Close as tracked here**, the reply saying it will be implemented on Gitea:
|
**Close as tracked here**, the reply saying it will be implemented on Gitea:
|
||||||
|
|
||||||
- [x] [#8](https://github.com/larvit/larvitsmpp/issues/8) The socket's remote host and port on log
|
- [x] **Close [#8](https://github.com/larvit/larvitsmpp/issues/8), the socket's remote host and port
|
||||||
messages: under Worth doing, not blocking. Maintainer's call, 2026-09-14.
|
on log messages, as tracked here.** It sits under Worth doing, not blocking. Maintainer's
|
||||||
|
call, 2026-09-14.
|
||||||
|
|
||||||
## 0.6.0
|
## 0.6.0
|
||||||
|
|
||||||
@@ -446,78 +452,80 @@ next work ([decision](docs/decisions.md#internals-and-tests)).
|
|||||||
as `true`. One fix closes all three, and `valueText()` in `defs/types.ts` is the quoted
|
as `true`. One fix closes all three, and `valueText()` in `defs/types.ts` is the quoted
|
||||||
spelling to take it from. Raised by review, 2026-09-20.
|
spelling to take it from. Raised by review, 2026-09-20.
|
||||||
|
|
||||||
- [ ] **A send the codec will refuse waits for a link and a window slot first.** `refuse()` in
|
- [ ] **Refuse a send the codec cannot build before it waits for a link and a window slot.** Today
|
||||||
`outgoing-requests.ts` runs `misuse()` and the abort check before the wait, precisely so a call
|
it waits first. `refuse()` in `outgoing-requests.ts` runs `misuse()` and the abort check
|
||||||
that can never go out does not queue for what it will never use; a body `objToPdu()` refuses on
|
before the wait, precisely so a call that can never go out does not queue for what it will
|
||||||
every attempt is the same case, and #98 made it a common one. On a down link the caller waits
|
never use; a body `objToPdu()` refuses on every attempt is the same case, and #98 made it a
|
||||||
`responseTimeout` and is told the link failed rather than that the body could not be built —
|
common one. On a down link the caller waits `responseTimeout` and is told the link failed
|
||||||
goal 2's wrong answer about what happened. The cheap fix builds the PDU twice, so the shape is
|
rather than that the body could not be built — goal 2's wrong answer about what happened. The
|
||||||
the open half. Raised by the architecture review of
|
cheap fix builds the PDU twice, so the shape is the open half. Raised by the architecture
|
||||||
|
review of [#98](https://github.com/larvit/larvitsmpp/pull/98), 2026-09-09.
|
||||||
|
|
||||||
|
- [ ] **Split the SMPP time format out of `message.ts` once the file has to move anyway.** It
|
||||||
|
answers two questions: message coding and the SMPP time format (`smppDate`, `smppTime`) share
|
||||||
|
the file, which the architecture map in AGENTS.md already spells out as four concerns. Nothing
|
||||||
|
is wrong today; if the file has to move for another reason, `smpp-time.ts` is the split.
|
||||||
|
Raised by the architecture review of
|
||||||
[#98](https://github.com/larvit/larvitsmpp/pull/98), 2026-09-09.
|
[#98](https://github.com/larvit/larvitsmpp/pull/98), 2026-09-09.
|
||||||
|
|
||||||
- [ ] **`message.ts` answers two questions.** Message coding and the SMPP time format (`smppDate`,
|
- [ ] **Add a gate that refuses a floating version anywhere in the repo.** Maintainer's ask on
|
||||||
`smppTime`) share the file, which the architecture map in AGENTS.md already spells out as four
|
[#71](https://github.com/larvit/larvitsmpp/pull/71), 2026-09-06, on the `release.yaml` pinning
|
||||||
concerns. Nothing is wrong today; if the file has to move for another reason, `smpp-time.ts` is
|
thread. Pinning every action and runner by hand is what the ask followed; the gate is what
|
||||||
the split. Raised by the architecture review of
|
keeps them pinned. It has to cover workflow `uses:` and `runs-on:`, compose `image:`, and
|
||||||
[#98](https://github.com/larvit/larvitsmpp/pull/98), 2026-09-09.
|
|
||||||
|
|
||||||
- [ ] **A gate that refuses a floating version anywhere in the repo.** Maintainer's ask on
|
|
||||||
[#71](https://github.com/larvit/larvitsmpp/pull/71), 2026-09-06, on the `release.yaml`
|
|
||||||
pinning thread. Pinning every action and runner by hand is what the ask followed; the gate is
|
|
||||||
what keeps them pinned. It has to cover workflow `uses:` and `runs-on:`, compose `image:`, and
|
|
||||||
Dockerfile `FROM`, and the conventions differ per kind — actions take a semver tag, images the
|
Dockerfile `FROM`, and the conventions differ per kind — actions take a semver tag, images the
|
||||||
full patch version — so one grep for `latest` is not it.
|
full patch version — so one grep for `latest` is not it.
|
||||||
|
|
||||||
- [ ] **A gate that fails when the test matrix misses the current Node.** Maintainer's ask on
|
- [ ] **Add a gate that catches the test matrix missing the current Node.** Maintainer's ask on
|
||||||
[#71](https://github.com/larvit/larvitsmpp/pull/71), 2026-09-06, on the Node 26 thread. Node
|
[#71](https://github.com/larvit/larvitsmpp/pull/71), 2026-09-06, on the Node 26 thread. Node
|
||||||
26 was added by hand; nothing notices when 27 ships. Needs a source for what Current is — the
|
26 was added by hand; nothing notices when 27 ships. Needs a source for what Current is — the
|
||||||
Node release schedule is published as JSON — and a decision on whether a new Current fails the
|
Node release schedule is published as JSON — and a decision on whether a new Current fails the
|
||||||
build or opens a PR, which is what Renovate already does for everything else here.
|
build or opens a PR, which is what Renovate already does for everything else here.
|
||||||
|
|
||||||
- [ ] **CodeRabbit reviews through the GitHub mirror.** CodeRabbit does not support Gitea, so mirror
|
- [ ] **Have CodeRabbit review Gitea pull requests through the GitHub mirror.** CodeRabbit does not
|
||||||
each Gitea pull request to GitHub for it to review there. Maintainer's ask, 2026-09-14; not
|
support Gitea, so mirror each Gitea pull request to GitHub for it to review there.
|
||||||
started until asked.
|
Maintainer's ask, 2026-09-14; not started until asked.
|
||||||
|
|
||||||
- [ ] **`leftOf()` and the link gate's own budget are one concept counted twice.**
|
- [ ] **Count what is left of a budget one way in `leftOf()` and the link gate.** Today they are one
|
||||||
`idle-waiters.ts` reads what is left of a budget as `Math.max(1, deadline - now)`, because 0
|
concept counted twice. `idle-waiters.ts` reads what is left of a budget as `Math.max(1,
|
||||||
means "forever" there; `link-gate.ts` runs the same subtraction and calls `<= 0` expired.
|
deadline - now)`, because 0 means "forever" there; `link-gate.ts` runs the same subtraction
|
||||||
Neither is reachable from the other, so nothing can disagree today, but a reader who learns one
|
and calls `<= 0` expired. Neither is reachable from the other, so nothing can disagree today,
|
||||||
and applies it to the other is wrong. A budget type both take would close it. Raised by review,
|
but a reader who learns one and applies it to the other is wrong. A budget type both take
|
||||||
2026-09-01.
|
would close it. Raised by review, 2026-09-01.
|
||||||
|
|
||||||
- [ ] **`err:` on a receipt for a state that neither delivered nor failed.** `receiptText()` now
|
- [ ] **Write a non-zero `err:` on a receipt only for a state that failed.** `receiptText()` now
|
||||||
writes `err:000` for `DELIVERED` and for the two transient states, and `err:001` for every
|
writes `err:000` for `DELIVERED` and for the two transient states, and `err:001` for every
|
||||||
other — so `ACCEPTED`, `SKIPPED`, `UNKNOWN` and `DELETED` still announce an error code the SMSC
|
other — so `ACCEPTED`, `SKIPPED`, `UNKNOWN` and `DELETED` still announce an error code the
|
||||||
never had. Which of those are failures is the open half. Raised by review, 2026-09-03; needs a
|
SMSC never had. Which of those are failures is the open half. Raised by review, 2026-09-03;
|
||||||
decision.
|
needs a decision.
|
||||||
|
|
||||||
- [ ] **`once()` is copied into four test files, and two copies never give up.**
|
- [ ] **Share one `once()` across the test files, one that gives up.** Today it is copied into four
|
||||||
`session-extras.test.ts` and `readme.test.ts` reject after 5000 ms; `session.test.ts` and
|
test files, and two copies never give up. `session-extras.test.ts` and `readme.test.ts` reject
|
||||||
`tls.test.ts` wait forever, so an event that never fires still hangs the run the way an
|
after 5000 ms; `session.test.ts` and `tls.test.ts` wait forever, so an event that never fires
|
||||||
unclosed listener used to. One shared, guarded copy closes the rest of that class.
|
still hangs the run the way an unclosed listener used to. One shared, guarded copy closes the
|
||||||
|
rest of that class.
|
||||||
|
|
||||||
- [ ] **The peer's address and bind on every session log message.** `remoteAddress` and `remotePort`
|
- [ ] **Carry the peer's address and bind on every session log message.** `remoteAddress` and
|
||||||
reach only `server - incoming connection`, and `systemId` only the bind messages, so with
|
`remotePort` reach only `server - incoming connection`, and `systemId` only the bind messages,
|
||||||
several peers connected one session's lines cannot be told apart, and a reconnect leaves nothing
|
so with several peers connected one session's lines cannot be told apart, and a reconnect
|
||||||
stable to filter on. Carrying them in every session message's metadata is a change to every
|
leaves nothing stable to filter on. Carrying them in every session message's metadata is a
|
||||||
call site. From [#8](https://github.com/larvit/larvitsmpp/issues/8), closed there as tracked
|
change to every call site. From [#8](https://github.com/larvit/larvitsmpp/issues/8), closed
|
||||||
here; maintainer's call, 2026-09-14.
|
there as tracked here; maintainer's call, 2026-09-14.
|
||||||
|
|
||||||
- [ ] **A peer whose message ids share one base logs a refused merge on every send.** `smsc01-000123`
|
- [ ] **Settle how a peer whose message ids share one base logs its refused merges.** Today it logs
|
||||||
and `smsc01-000124` carry the same base, so `DlrMerger` merges the first message and refuses
|
one on every send. `smsc01-000123` and `smsc01-000124` carry the same base, so `DlrMerger`
|
||||||
every one after it, one log line per send. Left at `info` — nothing the operator can fix is
|
merges the first message and refuses every one after it, one log line per send. Left at `info`
|
||||||
wrong — but a rate guard or silence may suit it better. Raised by review, 2026-08-30.
|
— nothing the operator can fix is wrong — but a rate guard or silence may suit it better.
|
||||||
|
Raised by review, 2026-08-30.
|
||||||
|
|
||||||
- [ ] **`submit_multi` and the broadcast commands** encode and decode, but nothing exercises them
|
- [ ] **Exercise `submit_multi` and the broadcast commands end to end.** They encode and decode, but
|
||||||
end to end. The interop suite is the natural place.
|
nothing exercises them end to end. The interop suite is the natural place.
|
||||||
- [ ] **Move to TypeScript 7** once `typescript-eslint` supports it; `renovate.json` pins TypeScript
|
- [ ] **Move to TypeScript 7 once `typescript-eslint` supports it.** `renovate.json` pins TypeScript
|
||||||
below 6.1 for exactly that reason.
|
below 6.1 for exactly that reason.
|
||||||
- [ ] **An `onReceipt` hook.** Receipt text is only loosely specified and operators disagree on it,
|
- [ ] **Add an `onReceipt` hook.** Receipt text is only loosely specified and operators disagree on
|
||||||
but `dlrFromPdu()` is wired into `IncomingRequests` with no seam of its own: an application
|
it, but `dlrFromPdu()` is wired into `IncomingRequests` with no seam of its own: an
|
||||||
facing a format we do not parse has to take the whole PDU on `onRequest` and reimplement the
|
application facing a format we do not parse has to take the whole PDU on `onRequest` and
|
||||||
dispatch, which owns the response as well.
|
reimplement the dispatch, which owns the response as well. Mirror the `onRequest` seam —
|
||||||
Mirror the `onRequest` seam — return a `Dlr` to own the receipt, `undefined` to fall through
|
return a `Dlr` to own the receipt, `undefined` to fall through to the built-in parser.
|
||||||
to the built-in parser.
|
|
||||||
|
|
||||||
## Gaps against other SMPP libraries
|
## Gaps against other SMPP libraries
|
||||||
|
|
||||||
@@ -527,38 +535,40 @@ Each lands under goal 7: an option or a hook, with the call that passes none unc
|
|||||||
|
|
||||||
### Sending
|
### Sending
|
||||||
|
|
||||||
- [ ] **A limiter hook, with a messages-per-second cap built on it.** Maintainer's call, 2026-09-14,
|
- [ ] **Add a limiter hook, with a messages-per-second cap built on it.** Maintainer's call,
|
||||||
reversing the earlier decline: Kannel, Jasmin, go-smpp and `smpp-js-sdk` all limit throughput.
|
2026-09-14, reversing the earlier decline: Kannel, Jasmin, go-smpp and `smpp-js-sdk` all limit
|
||||||
Count PDUs, not `sendSms()` calls — a long message is one `submit_sm` per segment and the
|
throughput. Count PDUs, not `sendSms()` calls — a long message is one `submit_sm` per segment
|
||||||
operator counts those, which an application wrapping `sendSms()` cannot see. The hook takes a
|
and the operator counts those, which an application wrapping `sendSms()` cannot see. The hook
|
||||||
limiter the application already runs; the built-in cap counts per session until the store at the
|
takes a limiter the application already runs; the built-in cap counts per session until the
|
||||||
bottom lets it span sessions and processes. The default stays uncapped. Open: the hook's shape (a
|
store at the bottom lets it span sessions and processes. The default stays uncapped. Open: the
|
||||||
wait that resolves when a PDU may go, cut short by the send's `signal`), which requests it gates
|
hook's shape (a wait that resolves when a PDU may go, cut short by the send's `signal`), which
|
||||||
— messages, never `enquire_link`, `unbind` or a response — and whether its wait counts against
|
requests it gates — messages, never `enquire_link`, `unbind` or a response — and whether its
|
||||||
`responseTimeout`.
|
wait counts against `responseTimeout`.
|
||||||
|
|
||||||
- [ ] **Back off and resend on `ESME_RTHROTTLED`.** The SMSC refused the PDU, so resending cannot
|
- [ ] **Back off and resend on `ESME_RTHROTTLED`.** The SMSC refused the PDU, so resending cannot
|
||||||
duplicate it and goal 2 holds, and the retry needs nothing wider than the session. Needs a
|
duplicate it and goal 2 holds, and the retry needs nothing wider than the session. Needs a
|
||||||
decision: on by default with a bounded budget, as goal 5 suggests, and whether `ESME_RMSGQFUL`
|
decision: on by default with a bounded budget, as goal 5 suggests, and whether `ESME_RMSGQFUL`
|
||||||
counts too. A throttled answer is also what a limiter hook wants to hear about.
|
counts too. A throttled answer is also what a limiter hook wants to hear about.
|
||||||
|
|
||||||
- [ ] **`sendSms()` takes the rest of `submit_sm`.** `service_type`, `priority_flag`, `protocol_id`,
|
- [ ] **Let `sendSms()` take the rest of `submit_sm`.** `service_type`, `priority_flag`,
|
||||||
`replace_if_present_flag` and TLVs on every segment, and `registered_delivery` beyond final
|
`protocol_id`, `replace_if_present_flag` and TLVs on every segment, and `registered_delivery`
|
||||||
receipts: on failure only, and intermediate notifications. Today each needs `send()`, which gives
|
beyond final receipts: on failure only, and intermediate notifications. Today each needs
|
||||||
up splitting, the alphabet checks and receipt merging. TLVs are the common case: India's DLT
|
`send()`, which gives up splitting, the alphabet checks and receipt merging. TLVs are the
|
||||||
rules put `PE_ID` (0x1400) and `TEMPLATE_ID` (0x1401) on every `submit_sm`, and USSD rides on
|
common case: India's DLT rules put `PE_ID` (0x1400) and `TEMPLATE_ID` (0x1401) on every
|
||||||
`ussd_service_op`. Refuse a TLV the send composes itself (`sar_*`, `message_payload`). Open: one
|
`submit_sm`, and USSD rides on `ussd_service_op`. Refuse a TLV the send composes itself
|
||||||
spelling for receipts, since `dlr: true` and a raw `registered_delivery` could disagree, and
|
(`sar_*`, `message_payload`). Open: one spelling for receipts, since `dlr: true` and a raw
|
||||||
whether goal 4's rule on optional parameters binds a TLV the caller named.
|
`registered_delivery` could disagree, and whether goal 4's rule on optional parameters binds a
|
||||||
|
TLV the caller named.
|
||||||
|
|
||||||
- [ ] **Choose how a long message is spelled on the wire.** Only an 8-bit UDH reference goes out
|
- [ ] **Choose how a long message is spelled on the wire.** Only an 8-bit UDH reference goes out
|
||||||
(`message.ts`), though the reader takes a 16-bit UDH, `sar_*` and `message_payload` alike. Some
|
(`message.ts`), though the reader takes a 16-bit UDH, `sar_*` and `message_payload` alike. Some
|
||||||
SMSCs take only `sar_*` or `message_payload`; php-smpp offers all three. A 16-bit reference also
|
SMSCs take only `sar_*` or `message_payload`; php-smpp offers all three. A 16-bit reference also
|
||||||
makes a collision rarer: the 8-bit one wraps every 255 sends on a session.
|
makes a collision rarer: the 8-bit one wraps every 255 sends on a session.
|
||||||
|
|
||||||
- [ ] **Failover across SMSC hosts.** Maintainer's call, 2026-09-14. `client()` takes one `host` and
|
- [ ] **Fail over across SMSC hosts.** Maintainer's call, 2026-09-14. `client()` takes one `host`
|
||||||
`port`; Kannel, Jasmin and php-smpp take several. A list the reconnect loop walks holds only
|
and `port`; Kannel, Jasmin and php-smpp take several. A list the reconnect loop walks holds
|
||||||
which host the one session is on, so it needs no store. Two options, both maintainer's calls:
|
only which host the one session is on, so it needs no store. Two options, both maintainer's
|
||||||
|
calls:
|
||||||
- **Order**, `fixed` or round robin, default `fixed`. Fixed starts every reconnect at the first
|
- **Order**, `fixed` or round robin, default `fixed`. Fixed starts every reconnect at the first
|
||||||
host; round robin at the host after the one the link was last on.
|
host; round robin at the host after the one the link was last on.
|
||||||
- **Starting over**, a boolean, default on: once the last host has been tried, go back to the
|
- **Starting over**, a boolean, default on: once the last host has been tried, go back to the
|
||||||
@@ -569,26 +579,29 @@ Each lands under goal 7: an option or a hook, with the call that passes none unc
|
|||||||
|
|
||||||
### The server
|
### The server
|
||||||
|
|
||||||
- [ ] **`sendSms()` on a `server()` session sends `submit_sm` toward the ESME.** Kannel answers
|
- [ ] **Send `deliver_sm` from `sendSms()` on a `server()` session.** Today it sends `submit_sm`
|
||||||
`ESME_RINVCMDID` (`interop-tests/kannel.test.ts`, "MO to Kannel"), and goal 1 says that PDU never
|
toward the ESME. Kannel answers `ESME_RINVCMDID` (`interop-tests/kannel.test.ts`, "MO to
|
||||||
goes out. A server has no other way to send an MO message either: `sendMo()` in that test builds
|
Kannel"), and goal 1 says that PDU never goes out. A server has no other way to send an MO
|
||||||
one from `submitSmParams()` and `ConcatReference`, neither exported. Choosing `deliver_sm` by
|
message either: `sendMo()` in that test builds one from `submitSmParams()` and
|
||||||
`linkEnd` gives MO messages the splitting and checks, keeps one method for one goal, and refuses
|
`ConcatReference`, neither exported. Choosing `deliver_sm` by `linkEnd` gives MO messages the
|
||||||
the options 3.4 has `deliver_sm` leave empty (`scheduleDeliveryTime`, `validityPeriod`).
|
splitting and checks, keeps one method for one goal, and refuses the options 3.4 has
|
||||||
|
`deliver_sm` leave empty (`scheduleDeliveryTime`, `validityPeriod`).
|
||||||
|
|
||||||
- [ ] **Error TLVs on a response this library builds.** `buildBody()` in `pdu.ts` writes no body for
|
- [ ] **Let a response this library builds carry error TLVs.** `buildBody()` in `pdu.ts` writes no
|
||||||
any non-zero status, so a server cannot answer a `data_sm` with `delivery_failure_reason`,
|
body for any non-zero status, so a server cannot answer a `data_sm` with
|
||||||
`network_error_code` or `additional_status_info_text`, and a 5.0 peer gets none of its error TLVs.
|
`delivery_failure_reason`, `network_error_code` or `additional_status_info_text`, and a 5.0
|
||||||
3.4 omits the body on error for `submit_sm_resp` by name; read each response's section before
|
peer gets none of its error TLVs. 3.4 omits the body on error for `submit_sm_resp` by name;
|
||||||
widening it. Reading needs nothing: an error response carrying a body already parses.
|
read each response's section before widening it. Reading needs nothing: an error response
|
||||||
|
carrying a body already parses.
|
||||||
|
|
||||||
- [ ] **PROXY protocol on `server()`.** Behind HAProxy or an AWS NLB every session's remote address is
|
- [ ] **Accept the PROXY protocol on `server()`.** Behind HAProxy or an AWS NLB every session's
|
||||||
the balancer's, so `authenticate` cannot allow-list by IP and logs name the wrong peer. v1 is
|
remote address is the balancer's, so `authenticate` cannot allow-list by IP and logs name the
|
||||||
text; v2 is binary and the only one an NLB sends. `smpp` accepts v1 from anyone; accept either
|
wrong peer. v1 is text; v2 is binary and the only one an NLB sends. `smpp` accepts v1 from
|
||||||
only from addresses the option names.
|
anyone; accept either only from addresses the option names.
|
||||||
|
|
||||||
- [ ] **`outbind`.** In the command table, handled nowhere: a client cannot take an SMSC's `outbind`
|
- [ ] **Handle `outbind`.** It is in the command table, handled nowhere: a client cannot take an
|
||||||
and bind back, and `server()` cannot send one. Rare; take it on with a peer that uses it.
|
SMSC's `outbind` and bind back, and `server()` cannot send one. Rare; take it on with a peer
|
||||||
|
that uses it.
|
||||||
|
|
||||||
- [ ] **Register vendor-specific commands.** 3.4 reserves `command_id` `0x00010200`–`0x000102FF` for
|
- [ ] **Register vendor-specific commands.** 3.4 reserves `command_id` `0x00010200`–`0x000102FF` for
|
||||||
SMSC vendors; today one arrives as a `PduRefusedError`. `smpp` has `addCommand()`. The same
|
SMSC vendors; today one arrives as a `PduRefusedError`. `smpp` has `addCommand()`. The same
|
||||||
@@ -610,9 +623,9 @@ Each lands under goal 7: an option or a hook, with the call that passes none unc
|
|||||||
on one coding are an `err`. Settle whether a claim on 0x00 reaches the class groups
|
on one coding are an `err`. Settle whether a claim on 0x00 reaches the class groups
|
||||||
`messageClassEncoding()` reads GSM 7-bit from, which `flash` writes under.
|
`messageClassEncoding()` reads GSM 7-bit from, which `flash` writes under.
|
||||||
|
|
||||||
- [ ] **The alphabets SMPP 3.4 names that no encoding carries.** `consts.ENCODING` lists the
|
- [ ] **Carry the alphabets SMPP 3.4 names that no encoding carries.** `consts.ENCODING` lists the
|
||||||
`data_coding` ids (5.2.19); only `ASCII`, `LATIN1` and `UCS2` can be sent. Those with a published
|
`data_coding` ids (5.2.19); only `ASCII`, `LATIN1` and `UCS2` can be sent. Those with a
|
||||||
definition, and what each costs:
|
published definition, and what each costs:
|
||||||
- 0x01 IA5 (ITU-T T.50, ASCII in practice): trivial.
|
- 0x01 IA5 (ITU-T T.50, ASCII in practice): trivial.
|
||||||
- 0x06 ISO-8859-5 (Cyrillic) and 0x07 ISO-8859-8 (Hebrew): 96-entry tables.
|
- 0x06 ISO-8859-5 (Cyrillic) and 0x07 ISO-8859-8 (Hebrew): 96-entry tables.
|
||||||
- 0x05 JIS X 0208, 0x0D JIS X 0212, 0x0A ISO-2022-JP and 0x0E KS C 5601: two-octet sets.
|
- 0x05 JIS X 0208, 0x0D JIS X 0212, 0x0A ISO-2022-JP and 0x0E KS C 5601: two-octet sets.
|
||||||
@@ -622,30 +635,34 @@ Each lands under goal 7: an option or a hook, with the call that passes none unc
|
|||||||
A Node without full ICU throws from `new TextDecoder()`, which hard rule 1 wraps into an `err`.
|
A Node without full ICU throws from `new TextDecoder()`, which hard rule 1 wraps into an `err`.
|
||||||
- 0x09 pictogram has no published definition; leave it out.
|
- 0x09 pictogram has no published definition; leave it out.
|
||||||
|
|
||||||
- [ ] **GSM 7-bit national language shift tables.** 3GPP TS 23.038 defines them for Turkish, Spanish
|
- [ ] **Read and send the GSM 7-bit national language shift tables.** 3GPP TS 23.038 defines them
|
||||||
(single shift only), Portuguese and ten Indian languages — Bengali, Gujarati, Hindi, Kannada,
|
for Turkish, Spanish (single shift only), Portuguese and ten Indian languages — Bengali,
|
||||||
Malayalam, Oriya, Punjabi, Tamil, Telugu and Urdu — selected per message by UDH elements 0x25
|
Gujarati, Hindi, Kannada, Malayalam, Oriya, Punjabi, Tamil, Telugu and Urdu — selected per
|
||||||
(locking) and 0x24 (single). They keep that text near GSM's segment size instead of UCS2's 67
|
message by UDH elements 0x25 (locking) and 0x24 (single). They keep that text near GSM's
|
||||||
characters. Reading means honouring those elements in `decodeMessage()`; sending means `detect()`
|
segment size instead of UCS2's 67 characters. Reading means honouring those elements in
|
||||||
picking a table, with each element's 3 octets off the segment budget. `smpp` has Turkish, Spanish
|
`decodeMessage()`; sending means `detect()` picking a table, with each element's 3 octets off
|
||||||
and Portuguese, used only when the caller writes the UDH.
|
the segment budget. `smpp` has Turkish, Spanish and Portuguese, used only when the caller
|
||||||
|
writes the UDH.
|
||||||
|
|
||||||
- [ ] **Packed GSM 7-bit, opt-in.** Everything goes out unpacked, SMPP's convention (AGENTS.md, "GSM
|
- [ ] **Offer packed GSM 7-bit, opt-in.** Everything goes out unpacked, SMPP's convention
|
||||||
7-bit is sent unpacked"); go-smpp carries a packed codec for SMSCs that want septets. Find an SMSC
|
(AGENTS.md, "GSM 7-bit is sent unpacked"); go-smpp carries a packed codec for SMSCs that want
|
||||||
that needs it before building it.
|
septets. Find an SMSC that needs it before building it.
|
||||||
|
|
||||||
- [ ] **`consts.ENCODING` spells five alphabets twice.** `CYRILLIC`/`ISO_8859_5`,
|
- [ ] **Give each alphabet in `consts.ENCODING` one name.** Five are spelled twice:
|
||||||
`HEBREW`/`ISO_8859_8`, `JIS`/`X_0208_1990`, `EXTENDED_KANJI_JIS`/`X_0212_1990` and
|
`CYRILLIC`/`ISO_8859_5`, `HEBREW`/`ISO_8859_8`, `JIS`/`X_0208_1990`,
|
||||||
`LATIN1`/`ISO_8859_1`; `FLASH` is a message class, not an alphabet. One name each before
|
`EXTENDED_KANJI_JIS`/`X_0212_1990` and `LATIN1`/`ISO_8859_1`; `FLASH` is a message class, not
|
||||||
registration starts taking names. A breaking change to an export.
|
an alphabet. One name each before registration starts taking names. A breaking change to an
|
||||||
|
export.
|
||||||
|
|
||||||
### Observability
|
### Observability
|
||||||
|
|
||||||
- [ ] **Metrics.** Inbound traffic has `data`, `incomingPdu` and `incomingPduObj`; outbound has no
|
- [ ] **Add metrics: outbound PDU events and counts of requests waiting.** Inbound traffic has
|
||||||
event, and nothing counts requests in flight, queued for a window slot, waiting for a link, or
|
`data`, `incomingPdu` and `incomingPduObj`; outbound has no event, and nothing counts requests
|
||||||
unanswered. `smpp` and `@semyonf/smpp` emit `metrics`; cloudhopper keeps per-session counters. An
|
in flight, queued for a window slot, waiting for a link, or unanswered. `smpp` and
|
||||||
`outgoingPdu`/`outgoingPduObj` pair mirrors the inbound events; the counters can be one read-only
|
`@semyonf/smpp` emit `metrics`; cloudhopper keeps per-session counters. An
|
||||||
snapshot, read from the owner of each count rather than a second tally that can drift.
|
`outgoingPdu`/`outgoingPduObj` pair mirrors the inbound events; the counters can be one
|
||||||
|
read-only snapshot, read from the owner of each count rather than a second tally that can
|
||||||
|
drift.
|
||||||
|
|
||||||
### Packaging, tests and CI
|
### Packaging, tests and CI
|
||||||
|
|
||||||
@@ -655,21 +672,21 @@ Each lands under goal 7: an option or a hook, with the call that passes none unc
|
|||||||
`src` (41 files, 209 KB) makes go to definition land in the TypeScript, and lets a debugger or
|
`src` (41 files, 209 KB) makes go to definition land in the TypeScript, and lets a debugger or
|
||||||
`--enable-source-maps` show it.
|
`--enable-source-maps` show it.
|
||||||
|
|
||||||
- [ ] **A coverage report and a floor in the gate.** `node --test --experimental-test-coverage
|
- [ ] **Add a coverage report and a floor to the gate.** `node --test --experimental-test-coverage
|
||||||
--test-coverage-include='src/**' test/*.test.ts` on Node 24.18.0, 2026-09-14: 98.77% lines,
|
--test-coverage-include='src/**' test/*.test.ts` on Node 24.18.0, 2026-09-14: 98.77% lines,
|
||||||
93.85% branches, 98.28% functions. Gate at 98, 93 and 98 with `--test-coverage-lines`,
|
93.85% branches, 98.28% functions. Gate at 98, 93 and 98 with `--test-coverage-lines`,
|
||||||
`--test-coverage-branches` and `--test-coverage-functions`, which Node 22 and later take — a job
|
`--test-coverage-branches` and `--test-coverage-functions`, which Node 22 and later take — a
|
||||||
of its own on 24, since the matrix runs compiled JavaScript — and add it to `main`'s required
|
job of its own on 24, since the matrix runs compiled JavaScript — and add it to `main`'s
|
||||||
checks. Raise the floor as coverage rises; never lower it.
|
required checks. Raise the floor as coverage rises; never lower it.
|
||||||
|
|
||||||
- [ ] **Mutation testing.** `@stryker-mutator/tap-runner` runs `node:test` suites and measures whether
|
- [ ] **Add mutation testing.** `@stryker-mutator/tap-runner` runs `node:test` suites and measures
|
||||||
a test notices a change, which coverage cannot; `@semyonf/smpp` runs Stryker in CI. The session
|
whether a test notices a change, which coverage cannot; `@semyonf/smpp` runs Stryker in CI.
|
||||||
suites are timer-heavy, so start with the codec and the encodings.
|
The session suites are timer-heavy, so start with the codec and the encodings.
|
||||||
|
|
||||||
- [ ] **A Node-RED node, as a package of its own.** `@leissner/node-red-smpp` is the only SMPP node in
|
- [ ] **Build a Node-RED node, as a package of its own.** `@leissner/node-red-smpp` is the only SMPP
|
||||||
the Node-RED library, and by a read of its source it never parses a receipt and never answers the
|
node in the Node-RED library, and by a read of its source it never parses a receipt and never
|
||||||
SMSC's `enquire_link`. Its UI is a fair list of what operators set. It builds on this package,
|
answers the SMSC's `enquire_link`. Its UI is a fair list of what operators set. It builds on
|
||||||
never inside it.
|
this package, never inside it.
|
||||||
|
|
||||||
## Declined
|
## Declined
|
||||||
|
|
||||||
@@ -683,9 +700,9 @@ Each lands under goal 7: an option or a hook, with the call that passes none unc
|
|||||||
|
|
||||||
## An optional store
|
## An optional store
|
||||||
|
|
||||||
- [ ] **Pooling, and state that survives a restart, through an optional store.** Maintainer's call,
|
- [ ] **Add pooling, and state that survives a restart, through an optional store.** Maintainer's
|
||||||
2026-09-14. It replaces two declines — merge state surviving a restart, and a pool of sessions —
|
call, 2026-09-14. It replaces two declines — merge state surviving a restart, and a pool of
|
||||||
and goal 9 was rewritten for it. Big: design before code.
|
sessions — and goal 9 was rewritten for it. Big: design before code.
|
||||||
- **What it holds.** Receipts still awaited and the groups `DlrMerger` collects. Segments of a
|
- **What it holds.** Receipts still awaited and the groups `DlrMerger` collects. Segments of a
|
||||||
message already answered but not yet whole, which the peer will not send again (goal 2). The
|
message already answered but not yet whole, which the peer will not send again (goal 2). The
|
||||||
concatenation reference, so a restart does not reuse one. For a pool, the ids every session
|
concatenation reference, so a restart does not reuse one. For a pool, the ids every session
|
||||||
|
|||||||
Reference in New Issue
Block a user