@foony/realtime 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,118 @@
1
+ # @foony/realtime
2
+
3
+ TypeScript SDK for the Foony Realtime service. A small client for the
4
+ wire protocol implemented by `services/realtime-saas` — connect, sub /
5
+ unsub, publish, and presence.
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ npm install @foony/realtime
11
+ ```
12
+
13
+ The package ships compiled ESM output and TypeScript declarations.
14
+
15
+ ## Quick start
16
+
17
+ ### Browser / Foony client
18
+
19
+ ```ts
20
+ import { Realtime } from '@foony/realtime';
21
+
22
+ const realtime = new Realtime({
23
+ url: 'wss://realtime.foony.com',
24
+ authCallback: async () => {
25
+ const response = await fetch('/api/realtime/token');
26
+ return await response.text();
27
+ },
28
+ });
29
+
30
+ const channel = realtime.channels.get('chat:room-1');
31
+
32
+ channel.subscribe((message) => {
33
+ console.log('chat message:', message.data);
34
+ });
35
+
36
+ await channel.publish('chat', { text: 'hello world' });
37
+
38
+ channel.presence.subscribe((event) => {
39
+ console.log(event.action, event.clientId, event.data);
40
+ });
41
+ await channel.presence.enter({ name: 'Alice' });
42
+ ```
43
+
44
+ ### Node / server (token minting)
45
+
46
+ ```ts
47
+ import { mintRealtimeToken } from '@foony/realtime/server';
48
+
49
+ app.get('/api/realtime/token', (req, res) => {
50
+ const token = mintRealtimeToken({
51
+ signingKey: process.env.REALTIME_JWT_SIGNING_KEY!,
52
+ appId: 'foony',
53
+ clientId: req.user.id,
54
+ capability: '{"chat:*":["subscribe","publish","presence"]}',
55
+ ttlMs: 15 * 60 * 1000,
56
+ });
57
+ res.type('text/plain').send(token);
58
+ });
59
+ ```
60
+
61
+ The signing key must exactly match the `JWT_SIGNING_KEY` env var the
62
+ realtime edge binary boots with.
63
+
64
+ ## Local development against the realtime backend
65
+
66
+ Start the backend following `services/realtime-saas/README.md`. Then
67
+ mint a dev token:
68
+
69
+ ```bash
70
+ cd services/realtime-saas
71
+ JWT_SIGNING_KEY=local-dev-key go run ./cmd/devtoken -app foony -client alice
72
+ ```
73
+
74
+ Use the printed token in the SDK:
75
+
76
+ ```ts
77
+ const realtime = new Realtime({
78
+ url: 'ws://localhost:3000',
79
+ token: process.env.FOONY_REALTIME_DEV_TOKEN!,
80
+ });
81
+ ```
82
+
83
+ ## Channel names
84
+
85
+ Channel names must match `[A-Za-z0-9._-]{1,255}` and cannot start or
86
+ end with a `.`. Use dots to express hierarchy (`chat.rooms.42`). The
87
+ server rejects invalid names with error code `40001` (`BadFrame`).
88
+
89
+ ## API surface
90
+
91
+ - `Realtime` — top-level client. Owns the WebSocket; channels attach lazily.
92
+ - `client.channels.get(name)` — returns a stable `Channel` for that name.
93
+ - `channel.subscribe(fn)` — message listener; returns an unsubscribe fn.
94
+ - `channel.publish(name, data)` — publish one message; resolves on ack.
95
+ - `channel.presence.subscribe(fn)` — presence listener.
96
+ - `channel.presence.enter|update|leave(data?)` — mutate this connection's membership.
97
+ - `client.onStateChange(fn)` — observe `connecting | connected | disconnected | closed | failed`.
98
+
99
+ ## Reconnect
100
+
101
+ When the connection drops unexpectedly the client retries with
102
+ exponential backoff (1s, 2s, 4s, ..., capped at 30s). All
103
+ subscriptions that were established before the disconnect are
104
+ re-issued automatically; presence membership is NOT automatically
105
+ restored — call `enter()` again on the `disconnected -> connected`
106
+ transition if you need it.
107
+
108
+ Pass `autoReconnect: false` to disable retries entirely (useful in tests).
109
+
110
+ ## Tests
111
+
112
+ ```bash
113
+ npm test
114
+ ```
115
+
116
+ Runs unit tests (wire + token mint) plus an in-process end-to-end test
117
+ that drives the SDK against a fake edge built on `ws`. No external
118
+ services required.
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Channel + Presence public API. Wraps the Connection layer with
3
+ * per-channel state.
4
+ */
5
+ import type { Connection, MessageListener, PresenceEventListener } from './connection.js';
6
+ /** Listener handle returned by `subscribe` — call to remove the listener. */
7
+ export type UnsubscribeFn = () => void;
8
+ /**
9
+ * One subscription handle per (channel, listener) pair. Channels are
10
+ * value-equal by name on a given Realtime client — calling
11
+ * `client.channels.get('chat:1')` twice returns the same instance.
12
+ */
13
+ export declare class Channel {
14
+ readonly name: string;
15
+ readonly presence: Presence;
16
+ private readonly connection;
17
+ private attachPromise;
18
+ private attached;
19
+ constructor(connection: Connection, name: string);
20
+ /**
21
+ * Ensure the server is subscribed to this channel. Called implicitly
22
+ * by `subscribe()` and `presence.subscribe()`; expose it so callers
23
+ * can pre-attach if they want to surface attach errors before the
24
+ * first message arrives.
25
+ */
26
+ attach(): Promise<void>;
27
+ /**
28
+ * Detach from the server (stop receiving messages and presence
29
+ * events). Local listeners are preserved — call `unsubscribe()` to
30
+ * clear them.
31
+ */
32
+ detach(): Promise<void>;
33
+ /**
34
+ * Register a listener for message frames on this channel. Implicitly
35
+ * attaches if needed. Returns an unsubscribe function.
36
+ */
37
+ subscribe(listener: MessageListener): UnsubscribeFn;
38
+ /** Publish one application-level message to the channel. */
39
+ publish(name: string, data: unknown): Promise<void>;
40
+ }
41
+ /**
42
+ * Per-channel presence facade. Wraps the `pres` frame and `presEvt`
43
+ * listener dispatch.
44
+ */
45
+ export declare class Presence {
46
+ private readonly connection;
47
+ private readonly channelName;
48
+ private readonly channel;
49
+ constructor(connection: Connection, channelName: string, channel: Channel);
50
+ /**
51
+ * Register a listener for presence events. Implicitly attaches the
52
+ * underlying channel — presence events arrive on the same WebSocket
53
+ * subscription as message frames.
54
+ */
55
+ subscribe(listener: PresenceEventListener): UnsubscribeFn;
56
+ /** Announce this connection as present in the channel. */
57
+ enter(data?: unknown): Promise<void>;
58
+ /** Update the data attached to this connection's presence entry. */
59
+ update(data?: unknown): Promise<void>;
60
+ /** Remove this connection's presence entry. */
61
+ leave(): Promise<void>;
62
+ private send;
63
+ }
64
+ //# sourceMappingURL=channel.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"channel.d.ts","sourceRoot":"","sources":["../src/channel.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,eAAe,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;AAG1F,6EAA6E;AAC7E,MAAM,MAAM,aAAa,GAAG,MAAM,IAAI,CAAC;AAEvC;;;;GAIG;AACH,qBAAa,OAAO;IAClB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC;IAC5B,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAa;IACxC,OAAO,CAAC,aAAa,CAA8B;IACnD,OAAO,CAAC,QAAQ,CAAS;gBAEb,UAAU,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM;IAMhD;;;;;OAKG;IACG,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC;IAe7B;;;;OAIG;IACG,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC;IAO7B;;;OAGG;IACH,SAAS,CAAC,QAAQ,EAAE,eAAe,GAAG,aAAa;IAWnD,4DAA4D;IACtD,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;CAI1D;AAED;;;GAGG;AACH,qBAAa,QAAQ;IACnB,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAa;IACxC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;IACrC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAU;gBAEtB,UAAU,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO;IAMzE;;;;OAIG;IACH,SAAS,CAAC,QAAQ,EAAE,qBAAqB,GAAG,aAAa;IASzD,0DAA0D;IACpD,KAAK,CAAC,IAAI,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IAI1C,oEAAoE;IAC9D,MAAM,CAAC,IAAI,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IAI3C,+CAA+C;IACzC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;YAId,IAAI;CASnB"}
package/lib/channel.js ADDED
@@ -0,0 +1,123 @@
1
+ /**
2
+ * Channel + Presence public API. Wraps the Connection layer with
3
+ * per-channel state.
4
+ */
5
+ /**
6
+ * One subscription handle per (channel, listener) pair. Channels are
7
+ * value-equal by name on a given Realtime client — calling
8
+ * `client.channels.get('chat:1')` twice returns the same instance.
9
+ */
10
+ export class Channel {
11
+ name;
12
+ presence;
13
+ connection;
14
+ attachPromise = null;
15
+ attached = false;
16
+ constructor(connection, name) {
17
+ this.connection = connection;
18
+ this.name = name;
19
+ this.presence = new Presence(connection, name, this);
20
+ }
21
+ /**
22
+ * Ensure the server is subscribed to this channel. Called implicitly
23
+ * by `subscribe()` and `presence.subscribe()`; expose it so callers
24
+ * can pre-attach if they want to surface attach errors before the
25
+ * first message arrives.
26
+ */
27
+ async attach() {
28
+ if (this.attached)
29
+ return;
30
+ if (this.attachPromise)
31
+ return this.attachPromise;
32
+ this.attachPromise = this.connection
33
+ .request({ t: 'sub', channel: this.name })
34
+ .then(() => {
35
+ this.attached = true;
36
+ this.connection.rememberSubscription(this.name);
37
+ })
38
+ .finally(() => {
39
+ this.attachPromise = null;
40
+ });
41
+ return this.attachPromise;
42
+ }
43
+ /**
44
+ * Detach from the server (stop receiving messages and presence
45
+ * events). Local listeners are preserved — call `unsubscribe()` to
46
+ * clear them.
47
+ */
48
+ async detach() {
49
+ if (!this.attached)
50
+ return;
51
+ await this.connection.request({ t: 'unsub', channel: this.name });
52
+ this.attached = false;
53
+ this.connection.forgetSubscription(this.name);
54
+ }
55
+ /**
56
+ * Register a listener for message frames on this channel. Implicitly
57
+ * attaches if needed. Returns an unsubscribe function.
58
+ */
59
+ subscribe(listener) {
60
+ const listeners = this.connection.addChannelListeners(this.name);
61
+ listeners.messages.add(listener);
62
+ // Fire-and-forget attach; the listener stays registered even if
63
+ // attach fails so a retry-on-reconnect surfaces the right state.
64
+ this.attach().catch(() => { });
65
+ return () => {
66
+ listeners.messages.delete(listener);
67
+ };
68
+ }
69
+ /** Publish one application-level message to the channel. */
70
+ async publish(name, data) {
71
+ await this.attach();
72
+ await this.connection.request({ t: 'pub', channel: this.name, name, data });
73
+ }
74
+ }
75
+ /**
76
+ * Per-channel presence facade. Wraps the `pres` frame and `presEvt`
77
+ * listener dispatch.
78
+ */
79
+ export class Presence {
80
+ connection;
81
+ channelName;
82
+ channel;
83
+ constructor(connection, channelName, channel) {
84
+ this.connection = connection;
85
+ this.channelName = channelName;
86
+ this.channel = channel;
87
+ }
88
+ /**
89
+ * Register a listener for presence events. Implicitly attaches the
90
+ * underlying channel — presence events arrive on the same WebSocket
91
+ * subscription as message frames.
92
+ */
93
+ subscribe(listener) {
94
+ const listeners = this.connection.addChannelListeners(this.channelName);
95
+ listeners.presence.add(listener);
96
+ this.channel.attach().catch(() => { });
97
+ return () => {
98
+ listeners.presence.delete(listener);
99
+ };
100
+ }
101
+ /** Announce this connection as present in the channel. */
102
+ async enter(data) {
103
+ await this.send('enter', data);
104
+ }
105
+ /** Update the data attached to this connection's presence entry. */
106
+ async update(data) {
107
+ await this.send('update', data);
108
+ }
109
+ /** Remove this connection's presence entry. */
110
+ async leave() {
111
+ await this.send('leave', undefined);
112
+ }
113
+ async send(action, data) {
114
+ await this.channel.attach();
115
+ await this.connection.request({
116
+ t: 'pres',
117
+ channel: this.channelName,
118
+ action,
119
+ ...(data === undefined ? {} : { data }),
120
+ });
121
+ }
122
+ }
123
+ //# sourceMappingURL=channel.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"channel.js","sourceRoot":"","sources":["../src/channel.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,139 @@
1
+ /**
2
+ * Low-level WebSocket connection manager. Handles framing, request /
3
+ * response correlation, and dispatch to per-channel listeners.
4
+ *
5
+ * The class is intentionally protocol-aware but channel-agnostic — the
6
+ * Channel and Realtime classes layer the public API on top.
7
+ */
8
+ import type { AckFrame, ClientFrame, MessageFrame, PresenceEventFrame, PresenceFrame, PublishFrame, SubscribeFrame, UnsubscribeFrame } from './wire.js';
9
+ /**
10
+ * Frames the SDK can issue with `request()`. Each carries an `id` the
11
+ * server echoes on the matching ack/err frame; Connection assigns the
12
+ * id so callers can omit it.
13
+ */
14
+ export type AckableFrame = Omit<SubscribeFrame, 'id'> | Omit<UnsubscribeFrame, 'id'> | Omit<PublishFrame, 'id'> | Omit<PresenceFrame, 'id'>;
15
+ /** Options that control how Connection reaches the edge. */
16
+ export type ConnectionOptions = {
17
+ /** ws:// or wss:// URL pointing at the realtime edge binary. */
18
+ readonly url: string;
19
+ /**
20
+ * A Realtime API key in `appSlug.publicKeyId:privateKey` form. Convenient for trusted
21
+ * quick starts and server-side scripts; browser apps should prefer JWTs
22
+ * returned from `authCallback`.
23
+ */
24
+ readonly key?: string;
25
+ /** Optional client id to attach to a direct key-auth connection. */
26
+ readonly clientId?: string;
27
+ /**
28
+ * A static JWT to send in the auth handshake. Mutually exclusive with
29
+ * `authCallback`. Useful for local dev and short scripts.
30
+ */
31
+ readonly token?: string;
32
+ /**
33
+ * Async callback that returns a fresh JWT. Called once on connect and
34
+ * again on every reconnect. Use this when the token is short-lived
35
+ * (the production path).
36
+ */
37
+ readonly authCallback?: () => Promise<string> | string;
38
+ /**
39
+ * Override the global WebSocket constructor. Mostly useful in tests;
40
+ * defaults to `globalThis.WebSocket` which is present in browsers and
41
+ * Node 22+.
42
+ */
43
+ readonly webSocket?: typeof WebSocket;
44
+ /**
45
+ * If true, attempt to reconnect after unexpected disconnects with
46
+ * exponential backoff. Defaults to true.
47
+ */
48
+ readonly autoReconnect?: boolean;
49
+ /**
50
+ * Initial backoff for reconnects (default 1000ms). Doubles each
51
+ * attempt up to maxReconnectDelayMs.
52
+ */
53
+ readonly initialReconnectDelayMs?: number;
54
+ /** Cap on the reconnect backoff (default 30000ms). */
55
+ readonly maxReconnectDelayMs?: number;
56
+ };
57
+ /** Connection lifecycle states. */
58
+ export type ConnectionState = 'initialized' | 'connecting' | 'connected' | 'disconnected' | 'closing' | 'closed' | 'failed';
59
+ /** Listener for state transitions. */
60
+ export type ConnectionStateListener = (state: ConnectionState, reason?: Error) => void;
61
+ /** Listener invoked for every message frame on a channel. */
62
+ export type MessageListener = (message: MessageFrame) => void;
63
+ /** Listener invoked for every presence event frame on a channel. */
64
+ export type PresenceEventListener = (event: PresenceEventFrame) => void;
65
+ /**
66
+ * Internal listener registry, keyed by channel name. Connection owns
67
+ * the maps so reconnect can transparently re-subscribe.
68
+ */
69
+ type ChannelListeners = {
70
+ readonly messages: Set<MessageListener>;
71
+ readonly presence: Set<PresenceEventListener>;
72
+ };
73
+ /**
74
+ * Connection is the transport layer. One Realtime client owns one
75
+ * Connection; channels share it.
76
+ */
77
+ export declare class Connection {
78
+ readonly options: ConnectionOptions;
79
+ private socket;
80
+ private state;
81
+ private connectionId;
82
+ private serverClientId;
83
+ private nextRequestId;
84
+ private readonly pending;
85
+ private readonly channelListeners;
86
+ private readonly stateListeners;
87
+ private connectPromise;
88
+ private reconnectTimer;
89
+ private reconnectAttempt;
90
+ /** Channels the SDK has asked to be subscribed to; re-sent on reconnect. */
91
+ private readonly desiredSubscriptions;
92
+ constructor(options: ConnectionOptions);
93
+ /** Current connection state. */
94
+ getState(): ConnectionState;
95
+ /** The server-issued connection id, populated after a successful auth handshake. */
96
+ getConnectionId(): string | null;
97
+ /** The client id encoded in the token, populated after auth. */
98
+ getClientId(): string | null;
99
+ /** Register a state-change listener. Returns an unsubscribe function. */
100
+ onStateChange(listener: ConnectionStateListener): () => void;
101
+ /**
102
+ * Open the WebSocket and complete the auth handshake. Idempotent —
103
+ * concurrent calls await the same in-flight connect.
104
+ */
105
+ connect(): Promise<void>;
106
+ /** Close the WebSocket and release resources. */
107
+ close(): Promise<void>;
108
+ /**
109
+ * Send a frame that expects an ack. Returns the matching AckFrame, or
110
+ * rejects with the server's ErrorFrame (wrapped in an Error).
111
+ */
112
+ request(frame: AckableFrame): Promise<AckFrame>;
113
+ /** Send a fire-and-forget frame (no ack expected). */
114
+ send(frame: ClientFrame): Promise<void>;
115
+ /**
116
+ * Register listeners for a channel. Connection remembers the
117
+ * registration so it can re-attach across reconnects, but actually
118
+ * issuing the `sub` frame is the caller's job (Channel does that).
119
+ */
120
+ addChannelListeners(channel: string): ChannelListeners;
121
+ /** Forget all listeners for a channel. Called from Channel.detach. */
122
+ removeChannelListeners(channel: string): void;
123
+ /** Add `channel` to the set of subscriptions to restore on reconnect. */
124
+ rememberSubscription(channel: string): void;
125
+ /** Stop restoring this subscription on future reconnects. */
126
+ forgetSubscription(channel: string): void;
127
+ private doConnect;
128
+ private makeSocket;
129
+ private createAuthFrame;
130
+ /** Steady-state message handler; installed after a successful auth. */
131
+ private readonly handleMessage;
132
+ private handleClose;
133
+ private scheduleReconnect;
134
+ private restoreSubscriptionsOnReconnect;
135
+ private sendRaw;
136
+ private setState;
137
+ }
138
+ export {};
139
+ //# sourceMappingURL=connection.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"connection.d.ts","sourceRoot":"","sources":["../src/connection.ts"],"names":[],"mappings":"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"}