better-smpp
Core

Custom commands & TLVs

Vendor extensions through an instance-scoped registry — no global mutation.

SMSC vendors extend SMPP with proprietary commands and optional parameters. better-smpp supports both through SmppRegistry — an instance you build and pass around, unlike node-smpp's addCommand/addTLV which mutated global tables.

Registering

import { SmppRegistry } from "@better-smpp/core";

const registry = new SmppRegistry()
  .registerCommand("vendor_ping", {
    id: 0x00010001,
    params: [
      { name: "token", type: "cstring" },
      { name: "weight", type: "int16" },
    ],
  })
  .registerCommand("vendor_ping_resp", {
    id: 0x80010001, // response id = request id | 0x80000000
    params: [{ name: "token", type: "cstring" }],
  })
  .registerTlv("vendorTag", { tag: 0x1600, type: "cstring" });

Field types: int8, int16, int32, cstring (null-terminated), string (length-prefixed), buffer, plus the composite dest_address_array / unsuccess_sme_array. TLVs additionally accept multiple: true (repeatable tags) and a filter (value transform, e.g. "time").

Name or id collisions with existing registrations throw immediately.

Using it

Pass the registry to whichever layer speaks the dialect:

// Client side:
const client = new SmppClient({ url: "…", registry });
const pong = await client.request({ command: "vendor_ping", token: "abc", weight: 5 });

// Server side:
const server = new SmppServer({ registry });
server.on("session", (session) => {
  session.connection.on("pdu", (pdu) => {
    if (pdu.command === "vendor_ping") {
      void session.respond(pdu, { token: `pong:${pdu.token}` });
    }
  });
});

// Or the pure codec:
const bytes = encodePdu({ command: "vendor_ping", token: "abc" }, { registry });
const decoded = decodePdu(bytes, { registry });

Custom TLVs work on standard commands too — attach vendorTag: "value" to any PDU and it encodes/decodes wherever the registry is in effect.

Unknown extensions without a registry

Even without registrations, nothing breaks: unknown inbound commands decode to { command: "unknown", commandId, body } (and are nacked automatically by the connection layer), and unknown TLV tags round-trip through the unknownTlvs: Map<number, Buffer> field — enough for pass-through proxying.

On this page