Skip to content

Latest commit

 

History

History
196 lines (167 loc) · 9.43 KB

File metadata and controls

196 lines (167 loc) · 9.43 KB

Shared client library

clients/lib is a dependency-free ECMAScript module for modern browsers and Node 22+. It speaks both the server's standard IRC-over-WebSocket transport and the bounded M11 native gateway. It is the common protocol/state layer for the reference clients. The M12A reference MCP adapter is likewise dependency-free. There is no bundler or generated copy.

What it handles

  • strict IRC message parsing, IRCv3 tag unescaping, and RFC1459 casefolding;
  • safe command and client-tag formatting with separate byte and line-injection checks;
  • text.ircv3.net and binary.ircv3.net framing;
  • CAP 302 negotiation, IRCv3 identity/presence negotiation, and SASL PLAIN chunking;
  • channel/direct-message state, type-preserving PRIVMSG/NOTICE, stable message-ID deduplication, history batches, extended joins, userhost-bearing NAMES normalization, membership, live topics, KICK reconciliation, and unread counts;
  • bounded helpers for channel role modes, account-ban queries/changes, member limits, invitations, and kicks;
  • bounded authenticated helpers and complete-state events for recipient-owned direct-message policy and allow/block rules;
  • labeled-command helpers for correlating acknowledgments, failures, echoes, and multi-line batches;
  • negotiated draft/multiline limits, all-or-nothing outbound batch construction, and exact live/history batch reassembly into one model event;
  • separate account read markers and per-installation delivery markers;
  • retained account identity tags for current-avatar lookup without persisting avatar bytes in the shared message model;
  • compact revision-tagged IRC automation policy snapshots emitted as automationpolicy state events rather than transcript messages;
  • bounded history helpers for LATEST, BEFORE, AFTER, and installation RESUME; and
  • content-epoch discovery plus a durable-sync coordinator that prepares the exact server/account storage scope before loading, commits complete pages before acknowledging their final message, and requests the next page; and
  • native one-use ticket exchange, optional operation/channel attenuation, UUIDv7 request correlation, structured events, independent pageable automation history, grant-scoped current-roster pages, idempotent publish and visible bounded-invocation operations, validated automation guardrail snapshots, and a native durable coordinator with the same commit-before-ack rule; and
  • bounded provider-hosted catalogue search, validated package descriptors, requester-private one-use capabilities, and provider package upsert/removal helpers, plus live provider-side DCC offer, resume, and completion control. Package byte sizes and resume offsets remain exact decimal strings on the wire, and the library never fetches capability URLs.

It intentionally does not store passwords, reconnect automatically, or claim a message was durably received. Those policies belong to an application with an appropriate credential source and durable storage implementation.

Minimal session

import { IrcSession } from "./lib/index.js";

const session = new IrcSession({
  url: "wss://irc.example.net:8097/irc",
  nickname: "alice-web",
  authenticationId: "alice",
  password: passwordFromAUserPrompt,
  enableDeviceResume: true,
  installationToken: tokenLoadedFromPrivateStorage ?? undefined,
});

session.addEventListener("ready", () => {
  session.join("#lounge");
  const { label } = session.sendLabeledCommand("WHOIS", ["bob"]);
  rememberPendingQuery(label);
});

session.model.addEventListener("change", ({ detail }) => {
  if (detail.kind === "message") {
    render(detail.conversation, detail.message);
  }
});

session.addEventListener("automationpolicy", ({ detail }) => {
  replaceChannelAutomationState(detail.target, detail);
});

session.connect();

The URL must be ws:// or wss://, must use /irc, and cannot contain URL credentials, a query, or a fragment. Credentials are consumed after the SASL response is sent and never included in observable outbound events. Create a new session with freshly obtained credentials when reconnecting.

Cursor contract

Read and delivery state have different APIs because they prove different facts:

// Call only after this server-timestamped message is actually visible to the
// human. This advances the account-wide read marker.
session.markVisible("#lounge", message);

// Enable installation resume only when the application has durable local storage.
// Call only after the transaction containing this msgid has committed.
session.acknowledgeStored("#lounge", message);

enableDeviceResume defaults to false. Turning it on negotiates draft/device-resume-0.2. After account-password authentication, the library binds a supplied dr1... installation token or emits an installation event with a newly issued token for the application to persist. It never acknowledges on socket receipt or on insertion into its in-memory model. An application pages with session.resume(target) and advances the delivery checkpoint after its own durable commit. This preserves the rule that a crash between network receipt and local persistence may cause a duplicate, but never a silent gap.

DurableMessageSync implements that ordering for stores exposing prepareContentEpoch(epoch), loadMessages(conversation), and putMessage(conversation, message). It never advances a delivery checkpoint for an arbitrary LATEST/BEFORE/AFTER page, and a storage failure stops resume for that conversation without acknowledging it.

The server advertises its persisted epoch as CONTENTEPOCH in 005. Before using durable state, the coordinator asks the store to reconcile that value. The reference stores atomically clear only the matching server/account scope if the epoch changed and emit epochprepared with discarded: true when cached messages were removed. This is cooperative local cleanup, not remote erasure.

The model tracks MARKREAD and MARKDELIVERED independently. Direct-message history is keyed by the stable @account target carried by its history batch, not by the replayed nickname prefix.

Native vertical slice

NativeSession connects to an HTTP(S) control origin, exchanges a human account password or automated-account app key for a one-use ticket, and opens /v1/sync with irc.native.v1. Callers may request a ticket restricted to selected v1 operations and canonical channels. NativeDurableSync restores the shared model, pages from the installation checkpoint, commits events through the existing store interface, and acknowledges only after that commit succeeds. It also validates the target-only, zero-to-ten-event invocation context envelope.

Every native channel snapshot also carries the moderator-pause state, fixed window message/byte budgets, repetition and causal-loop thresholds, and active account/channel cooldowns. Policy replacements may keep the same revision when only short-lived cooldown state changes; they may never move the durable policy revision backwards.

The library consumes the secret after the ticket request and redacts the ticket from observable outbound events. Human clients automatically bind a distinct installation token for cursor state. Use HTTPS outside loopback, and remember that a ticket scope attenuates only that ticket: software holding the account password or app key can mint a different one. See the practical guide, the wire contract, and the native terminal.

Tests

cd clients
npm test
npm run check

The tests use only Node's built-in runner. They cover parsing and injection bounds, SASL chunking/redaction, capability negotiation, stable direct-history and live account routing, atomic multiline construction and reassembly, current nickname/account observation, message deduplication, cursor separation, crash-safe resume ordering, failed-storage and epoch-change behavior, attenuated native sessions, UUIDv7 generation, requester-private result correlation, MCP lifecycle/framing, durable invocation context, causal reply proof, private adapter state, and publisher idempotency, roster-aware causal replies, durable asynchronous job recovery, Chatbot scheduling/recovery, and ArtifactProvider HTTP/DCC transfer behavior, plus onboarding/account-control input bounds and authenticated response validation, bounded avatar caching, and the configured-operator account/capability/avatar-clear contract. npm run test:browser also drives two live invitations through the server-hosted page and exercises roster-to-account direct-message navigation when the debug daemon and Chrome are available.

The consumers are documented separately: