yrby-client 0.4.0 → 0.4.2

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.
@@ -18,13 +18,22 @@ export interface CableSubscription {
18
18
  send(data: unknown): unknown;
19
19
  /** AnyCable client-to-client broadcast; absent on plain ActionCable. */
20
20
  whisper?(data: unknown): unknown;
21
+ /** Teardown. Present on both @rails/actioncable and @anycable/web. */
21
22
  unsubscribe?(): void;
22
23
  }
23
- /** The minimal slice of an ActionCable/AnyCable consumer this provider uses. */
24
+ /**
25
+ * The minimal slice of an ActionCable/AnyCable consumer this provider uses.
26
+ *
27
+ * Deliberately loose so the consumers from both `@rails/actioncable` and
28
+ * `@anycable/web` are directly assignable -- no adapter or casts. `create` is
29
+ * widened to the channel/params shapes both libs accept, with an optional mixin
30
+ * (the handlers object). There's no `subscriptions.remove`: the provider tears
31
+ * down via `subscription.unsubscribe()` (universal), and @anycable has no such
32
+ * method anyway.
33
+ */
24
34
  export interface CableConsumer {
25
35
  subscriptions: {
26
- create(params: object, mixin: object): CableSubscription;
27
- remove(subscription: CableSubscription): void;
36
+ create(channel: string | object, mixin?: object): CableSubscription;
28
37
  };
29
38
  }
30
39
  export type ActionCableProviderOptions = Pick<YProtocolSessionOptions, "resendInterval" | "onError">;
@@ -1 +1 @@
1
- {"version":3,"file":"actioncable_provider.d.ts","sourceRoot":"","sources":["../src/actioncable_provider.ts"],"names":[],"mappings":"AAeA,OAAO,EAAE,gBAAgB,EAAe,KAAK,uBAAuB,EAAE,MAAM,yBAAyB,CAAC;AAEtG,OAAO,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAClD,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,KAAK,CAAC;AAE/B;;;;;;GAMG;AACH,MAAM,MAAM,cAAc,GAAG,YAAY,GAAG,WAAW,GAAG,QAAQ,GAAG,cAAc,CAAC;AAEpF,kDAAkD;AAClD,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,cAAc,CAAC;CACxB;AAED,oFAAoF;AACpF,MAAM,WAAW,iBAAiB;IAChC,IAAI,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC;IAC7B,wEAAwE;IACxE,OAAO,CAAC,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC;IACjC,WAAW,CAAC,IAAI,IAAI,CAAC;CACtB;AAED,gFAAgF;AAChF,MAAM,WAAW,aAAa;IAC5B,aAAa,EAAE;QACb,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,iBAAiB,CAAC;QACzD,MAAM,CAAC,YAAY,EAAE,iBAAiB,GAAG,IAAI,CAAC;KAC/C,CAAC;CACH;AAED,MAAM,MAAM,0BAA0B,GAAG,IAAI,CAAC,uBAAuB,EAAE,gBAAgB,GAAG,SAAS,CAAC,CAAC;AAQrG,qBAAa,mBAAmB;;IAC9B,QAAQ,CAAC,GAAG,EAAE,GAAG,CAAC;IAClB,QAAQ,CAAC,QAAQ,EAAE,aAAa,CAAC;IACjC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,SAAS,EAAE,SAAS,CAAC;IAC9B,QAAQ,CAAC,OAAO,EAAE,gBAAgB,CAAC;gBASjC,GAAG,EAAE,GAAG,EACR,QAAQ,EAAE,aAAa,EACvB,WAAW,EAAE,MAAM,EACnB,aAAa,GAAE,MAAW,EAC1B,IAAI,GAAE,0BAA+B;IAiBvC,mFAAmF;IACnF,IAAI,MAAM,IAAI,OAAO,CAEpB;IAED,4EAA4E;IAC5E,IAAI,UAAU,IAAI,OAAO,CAExB;IAED;;;;;;;;;;;OAWG;IACH,iBAAiB,CAAC,MAAM,EAAE,UAAU,GAAG,IAAI;IAI3C,6DAA6D;IAC7D,IAAI,MAAM,IAAI,cAAc,CAE3B;IAED,oEAAoE;IACpE,cAAc,CAAC,QAAQ,EAAE,CAAC,KAAK,EAAE,WAAW,KAAK,IAAI,GAAG,MAAM,IAAI;IAKlE,OAAO,IAAI,IAAI;IAgDf,UAAU,IAAI,IAAI;IAelB,OAAO,IAAI,IAAI;CAqDhB"}
1
+ {"version":3,"file":"actioncable_provider.d.ts","sourceRoot":"","sources":["../src/actioncable_provider.ts"],"names":[],"mappings":"AAeA,OAAO,EAAE,gBAAgB,EAAe,KAAK,uBAAuB,EAAE,MAAM,yBAAyB,CAAC;AAEtG,OAAO,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAClD,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,KAAK,CAAC;AAE/B;;;;;;GAMG;AACH,MAAM,MAAM,cAAc,GAAG,YAAY,GAAG,WAAW,GAAG,QAAQ,GAAG,cAAc,CAAC;AAEpF,kDAAkD;AAClD,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,cAAc,CAAC;CACxB;AAED,oFAAoF;AACpF,MAAM,WAAW,iBAAiB;IAChC,IAAI,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC;IAC7B,wEAAwE;IACxE,OAAO,CAAC,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC;IACjC,sEAAsE;IACtE,WAAW,CAAC,IAAI,IAAI,CAAC;CACtB;AAED;;;;;;;;;GASG;AACH,MAAM,WAAW,aAAa;IAC5B,aAAa,EAAE;QACb,MAAM,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,iBAAiB,CAAC;KACrE,CAAC;CACH;AAED,MAAM,MAAM,0BAA0B,GAAG,IAAI,CAAC,uBAAuB,EAAE,gBAAgB,GAAG,SAAS,CAAC,CAAC;AAQrG,qBAAa,mBAAmB;;IAC9B,QAAQ,CAAC,GAAG,EAAE,GAAG,CAAC;IAClB,QAAQ,CAAC,QAAQ,EAAE,aAAa,CAAC;IACjC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,SAAS,EAAE,SAAS,CAAC;IAC9B,QAAQ,CAAC,OAAO,EAAE,gBAAgB,CAAC;gBASjC,GAAG,EAAE,GAAG,EACR,QAAQ,EAAE,aAAa,EACvB,WAAW,EAAE,MAAM,EACnB,aAAa,GAAE,MAAW,EAC1B,IAAI,GAAE,0BAA+B;IAiBvC,mFAAmF;IACnF,IAAI,MAAM,IAAI,OAAO,CAEpB;IAED,4EAA4E;IAC5E,IAAI,UAAU,IAAI,OAAO,CAExB;IAED;;;;;;;;;;;OAWG;IACH,iBAAiB,CAAC,MAAM,EAAE,UAAU,GAAG,IAAI;IAI3C,6DAA6D;IAC7D,IAAI,MAAM,IAAI,cAAc,CAE3B;IAED,oEAAoE;IACpE,cAAc,CAAC,QAAQ,EAAE,CAAC,KAAK,EAAE,WAAW,KAAK,IAAI,GAAG,MAAM,IAAI;IAKlE,OAAO,IAAI,IAAI;IAgDf,UAAU,IAAI,IAAI;IAmBlB,OAAO,IAAI,IAAI;CAqDhB"}
@@ -135,7 +135,11 @@ export class ActionCableProvider {
135
135
  this.#connected = false;
136
136
  this.#subscription = null;
137
137
  this.#removeUnloadHandler();
138
- queueMicrotask(() => this.consumer.subscriptions.remove(sub));
138
+ // Universal teardown: both @rails/actioncable and @anycable/web subscriptions
139
+ // expose unsubscribe() (Rails' just calls consumer.subscriptions.remove(this)
140
+ // internally). @anycable has NO consumer.subscriptions.remove, so calling that
141
+ // would throw there.
142
+ queueMicrotask(() => sub.unsubscribe?.());
139
143
  this.#refreshStatus(); // -> "disconnected"
140
144
  }
141
145
  destroy() {
@@ -1 +1 @@
1
- {"version":3,"file":"actioncable_provider.js","sourceRoot":"","sources":["../src/actioncable_provider.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"actioncable_provider.js","sourceRoot":"","sources":["../src/actioncable_provider.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,204 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ActionCableProvider = void 0;
4
+ // Yjs provider for the yrby y-websocket protocol over ActionCable / AnyCable.
5
+ // It owns the cable subscription and translates between the cable's JSON envelope
6
+ // (`{ update, id }` / `{ ack }`, base64) and raw protocol frames. Everything else
7
+ // (sync steps, encode/decode, awareness, reliable delivery) lives in
8
+ // YProtocolSession; this is the transport glue.
9
+ //
10
+ // Awareness frames use AnyCable's `whisper` when available, under a separate
11
+ // awareness-only envelope. Document frames always use `send` so they go through
12
+ // the server's persistence/ack path.
13
+ //
14
+ // The constructor does not auto-connect: wire up your editor binding first, then
15
+ // call `connect()`. Watch the connection with `onStatusChange(({ status }) => ...)`
16
+ // or the `status` getter. On `disconnect()`/`destroy()`, and on browser
17
+ // `pagehide`, the provider broadcasts a presence removal so peers drop our cursor
18
+ // right away instead of waiting for the awareness timeout.
19
+ const y_protocol_session_js_1 = require("./y_protocol_session.js");
20
+ const base64_js_1 = require("./base64.js");
21
+ const awareness_1 = require("y-protocols/awareness");
22
+ class ActionCableProvider {
23
+ doc;
24
+ consumer;
25
+ channelName;
26
+ channelParams;
27
+ awareness;
28
+ session;
29
+ #subscription = null;
30
+ #onError;
31
+ #connected = false;
32
+ #status = "disconnected";
33
+ #statusListeners = new Set();
34
+ #onUnload = null;
35
+ constructor(doc, consumer, channelName, channelParams = {}, opts = {}) {
36
+ this.doc = doc;
37
+ this.consumer = consumer;
38
+ this.channelName = channelName;
39
+ this.channelParams = channelParams;
40
+ this.awareness = new awareness_1.Awareness(doc);
41
+ this.#onError = opts.onError ?? ((error, context) => console.warn(`[yrby] ${context}:`, error));
42
+ this.session = new y_protocol_session_js_1.YProtocolSession(doc, {
43
+ awareness: this.awareness,
44
+ resendInterval: opts.resendInterval,
45
+ onError: this.#onError,
46
+ send: (frame, id) => this.#send(frame, id),
47
+ });
48
+ }
49
+ /** True once the document has caught up with the server (received a SyncStep2). */
50
+ get synced() {
51
+ return this.session.synced;
52
+ }
53
+ /** True while there are unacknowledged local document updates in flight. */
54
+ get hasPending() {
55
+ return this.session.hasPending;
56
+ }
57
+ /**
58
+ * Apply a bootstrap/restore update (initial HTTP state, a server snapshot, an
59
+ * import) without re-sending it to the server as a local edit. Call it once per
60
+ * chunk of already-durable state when seeding the doc, before `connect()`:
61
+ *
62
+ * provider.applyRemoteUpdate(fromBase64(initialState));
63
+ * priorUpdates.forEach((u) => provider.applyRemoteUpdate(fromBase64(u)));
64
+ * provider.connect();
65
+ *
66
+ * See {@link YProtocolSession.applyRemoteUpdate} for why a bare `Y.applyUpdate`
67
+ * would be re-broadcast as a pending change instead.
68
+ */
69
+ applyRemoteUpdate(update) {
70
+ this.session.applyRemoteUpdate(update);
71
+ }
72
+ /** Current connection status. See {@link ProviderStatus}. */
73
+ get status() {
74
+ return this.#status;
75
+ }
76
+ /** Subscribe to status changes. Returns an unsubscribe function. */
77
+ onStatusChange(listener) {
78
+ this.#statusListeners.add(listener);
79
+ return () => this.#statusListeners.delete(listener);
80
+ }
81
+ connect() {
82
+ if (this.#subscription)
83
+ return;
84
+ const provider = this;
85
+ this.#subscription = this.consumer.subscriptions.create({ channel: this.channelName, ...this.channelParams }, {
86
+ received(message) {
87
+ // Reliable-delivery ack: confirm + prune the local queue.
88
+ if (message && message.ack !== undefined) {
89
+ provider.session.ack(message.ack);
90
+ return;
91
+ }
92
+ const awarenessPayload = message && message.awareness;
93
+ const payload = message && (awarenessPayload ?? message.update);
94
+ if (typeof payload !== "string")
95
+ return;
96
+ // Guard base64 decode too: a malformed envelope must not throw into
97
+ // the cable callback (session.receive is itself defensive).
98
+ let frame;
99
+ try {
100
+ frame = (0, base64_js_1.fromBase64)(payload);
101
+ }
102
+ catch (error) {
103
+ provider.#onError(error, "received");
104
+ return;
105
+ }
106
+ if (awarenessPayload !== undefined && frame[0] !== y_protocol_session_js_1.MessageType.Awareness) {
107
+ provider.#onError(new Error("awareness envelope carried a non-awareness frame"), "received");
108
+ return;
109
+ }
110
+ const reply = provider.session.receive(frame);
111
+ if (reply)
112
+ provider.#send(reply, undefined); // e.g. SyncStep2 answering a SyncStep1
113
+ provider.#refreshStatus(); // a SyncStep2 may have just flipped us to "synced"
114
+ },
115
+ connected() {
116
+ provider.#connected = true;
117
+ provider.session.onConnect(); // handshake + replay the unacked tail
118
+ provider.#refreshStatus();
119
+ },
120
+ disconnected() {
121
+ provider.#connected = false;
122
+ provider.session.onDisconnect(); // pause retransmits, clear remote presence
123
+ provider.#refreshStatus(); // subscription still set -> "connecting" (retrying)
124
+ },
125
+ });
126
+ this.#installUnloadHandler();
127
+ this.#refreshStatus(); // -> "connecting"
128
+ }
129
+ disconnect() {
130
+ if (!this.#subscription)
131
+ return;
132
+ const sub = this.#subscription;
133
+ // Tell peers we're gone while the transport is still live, then pause and
134
+ // detach. Defer the unsubscribe one microtask so the removal frame flushes
135
+ // before the channel tears down.
136
+ this.session.removeLocalAwareness();
137
+ this.session.onDisconnect();
138
+ this.#connected = false;
139
+ this.#subscription = null;
140
+ this.#removeUnloadHandler();
141
+ // Universal teardown: both @rails/actioncable and @anycable/web subscriptions
142
+ // expose unsubscribe() (Rails' just calls consumer.subscriptions.remove(this)
143
+ // internally). @anycable has NO consumer.subscriptions.remove, so calling that
144
+ // would throw there.
145
+ queueMicrotask(() => sub.unsubscribe?.());
146
+ this.#refreshStatus(); // -> "disconnected"
147
+ }
148
+ destroy() {
149
+ this.disconnect();
150
+ this.session.destroy();
151
+ this.awareness.destroy(); // stops its reaper timer
152
+ this.#statusListeners.clear();
153
+ }
154
+ #computeStatus() {
155
+ if (!this.#subscription)
156
+ return "disconnected";
157
+ if (!this.#connected)
158
+ return "connecting";
159
+ return this.session.synced ? "synced" : "connected";
160
+ }
161
+ #refreshStatus() {
162
+ const next = this.#computeStatus();
163
+ if (next === this.#status)
164
+ return;
165
+ this.#status = next;
166
+ for (const listener of this.#statusListeners)
167
+ listener({ status: next });
168
+ }
169
+ // Best-effort presence removal when the tab/page goes away (close, navigation,
170
+ // bfcache). `pagehide` fires while the socket is still live and is bfcache-safe
171
+ // (unlike `beforeunload`, which can block it). Sends are not guaranteed to
172
+ // flush on unload, so the server-side awareness timeout remains the backstop.
173
+ #installUnloadHandler() {
174
+ if (typeof window === "undefined" || this.#onUnload)
175
+ return;
176
+ this.#onUnload = () => this.session.removeLocalAwareness();
177
+ window.addEventListener("pagehide", this.#onUnload);
178
+ }
179
+ #removeUnloadHandler() {
180
+ if (typeof window === "undefined" || !this.#onUnload)
181
+ return;
182
+ window.removeEventListener("pagehide", this.#onUnload);
183
+ this.#onUnload = null;
184
+ }
185
+ // Send one raw protocol frame over the cable. Awareness frames are whispered
186
+ // when AnyCable exposes `subscription.whisper`; otherwise they fall back to a
187
+ // normal send. `id` (reliable doc updates) is tagged onto the envelope so the
188
+ // server can ack. A no-op while disconnected: reliable frames stay queued in
189
+ // the session and flush on the next connect().
190
+ #send(frame, id) {
191
+ const sub = this.#subscription;
192
+ if (!sub)
193
+ return;
194
+ const update = (0, base64_js_1.toBase64)(frame);
195
+ const isAwareness = frame[0] === y_protocol_session_js_1.MessageType.Awareness;
196
+ if (isAwareness && typeof sub.whisper === "function") {
197
+ sub.whisper({ awareness: update });
198
+ return;
199
+ }
200
+ const payload = id === undefined ? { update } : { update, id };
201
+ sub.send(payload);
202
+ }
203
+ }
204
+ exports.ActionCableProvider = ActionCableProvider;
@@ -0,0 +1,10 @@
1
+ "use strict";
2
+ // base64 codecs for transports that carry binary frames as strings (e.g.
3
+ // ActionCable's JSON envelope). Optional; a binary WebSocket transport sends the
4
+ // raw frames directly and never needs these.
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.fromBase64 = exports.toBase64 = void 0;
7
+ const toBase64 = (bytes) => btoa(Array.from(bytes, (b) => String.fromCharCode(b)).join(""));
8
+ exports.toBase64 = toBase64;
9
+ const fromBase64 = (str) => Uint8Array.from(atob(str), (c) => c.charCodeAt(0));
10
+ exports.fromBase64 = fromBase64;
@@ -0,0 +1,19 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.fromBase64 = exports.toBase64 = exports.ActionCableProvider = exports.MessageType = exports.YProtocolSession = exports.ReliableSync = void 0;
4
+ // Zero-dependency reliable-delivery core. Safe to import on its own.
5
+ var reliable_sync_js_1 = require("./reliable_sync.js");
6
+ Object.defineProperty(exports, "ReliableSync", { enumerable: true, get: function () { return reliable_sync_js_1.ReliableSync; } });
7
+ // Protocol session (sync steps + encode/decode + awareness).
8
+ // Requires `yjs` and `y-protocols` as peers.
9
+ var y_protocol_session_js_1 = require("./y_protocol_session.js");
10
+ Object.defineProperty(exports, "YProtocolSession", { enumerable: true, get: function () { return y_protocol_session_js_1.YProtocolSession; } });
11
+ Object.defineProperty(exports, "MessageType", { enumerable: true, get: function () { return y_protocol_session_js_1.MessageType; } });
12
+ // ActionCable / AnyCable provider built on YProtocolSession.
13
+ // Bring your own provider instead by composing YProtocolSession.
14
+ var actioncable_provider_js_1 = require("./actioncable_provider.js");
15
+ Object.defineProperty(exports, "ActionCableProvider", { enumerable: true, get: function () { return actioncable_provider_js_1.ActionCableProvider; } });
16
+ // Optional base64 helpers for transports that carry frames as strings.
17
+ var base64_js_1 = require("./base64.js");
18
+ Object.defineProperty(exports, "toBase64", { enumerable: true, get: function () { return base64_js_1.toBase64; } });
19
+ Object.defineProperty(exports, "fromBase64", { enumerable: true, get: function () { return base64_js_1.fromBase64; } });
@@ -0,0 +1 @@
1
+ {"type":"commonjs"}
@@ -0,0 +1,142 @@
1
+ "use strict";
2
+ // Transport-agnostic reliable-delivery core for the yrby y-websocket
3
+ // protocol: an ack-tracked queue of unacknowledged local updates,
4
+ // sync-since-last-ack (the unacked tail goes out as one merged, causally-complete
5
+ // delta so the server never sees an internal gap), cumulative acks, periodic
6
+ // retransmit, and reconnect replay.
7
+ //
8
+ // It doesn't touch the transport, the Yjs binding, or wire encoding. Inject two
9
+ // functions:
10
+ // send(update, id) transmits one update (raw merged bytes plus a cumulative
11
+ // sequence id; you frame, base64, and put it on the socket), and merge(updates)
12
+ // merges update byte-arrays into one (usually Y.mergeUpdates). Drive it from the
13
+ // provider lifecycle: enqueue(update) on each local edit, onAck(id) when an
14
+ // { ack: id } frame arrives, and onConnect()/onDisconnect() on transport changes.
15
+ //
16
+ // Awareness/presence stays out of scope; it's fire-and-forget in the provider.
17
+ Object.defineProperty(exports, "__esModule", { value: true });
18
+ exports.ReliableSync = void 0;
19
+ const DEFAULTS = { resendInterval: 1000 };
20
+ class ReliableSync {
21
+ /** Unacked local updates, in order. */
22
+ pending = [];
23
+ #send;
24
+ #merge;
25
+ #resendInterval;
26
+ #setInterval;
27
+ #clearInterval;
28
+ #nextSeq = 1;
29
+ #connected = false;
30
+ #timer = undefined;
31
+ // Memoized merge of the unacked tail. The tail only changes on enqueue/ack, so
32
+ // retransmit ticks reuse this instead of re-merging the whole queue each time.
33
+ #tailCache = undefined;
34
+ constructor(opts) {
35
+ const { send, merge, resendInterval } = opts ?? {};
36
+ if (typeof send !== "function")
37
+ throw new TypeError("ReliableSync requires a send(update, id) function");
38
+ if (typeof merge !== "function")
39
+ throw new TypeError("ReliableSync requires a merge(updates) function");
40
+ this.#send = send;
41
+ this.#merge = merge;
42
+ const interval = resendInterval ?? DEFAULTS.resendInterval;
43
+ if (!Number.isFinite(interval) || interval <= 0) {
44
+ throw new TypeError("ReliableSync resendInterval must be a positive number");
45
+ }
46
+ this.#resendInterval = interval;
47
+ // Injectable timer hooks make the resend loop testable; default to globals.
48
+ this.#setInterval = opts.setInterval ?? ((fn, ms) => setInterval(fn, ms));
49
+ this.#clearInterval = opts.clearInterval ?? ((h) => clearInterval(h));
50
+ }
51
+ /** True while there are unacknowledged local updates. */
52
+ get hasPending() {
53
+ return this.pending.length > 0;
54
+ }
55
+ /**
56
+ * Record a local document update. It is queued and the unacked tail is
57
+ * flushed; the update remains retained until the server acknowledges it.
58
+ */
59
+ enqueue(update) {
60
+ this.pending.push({ seq: this.#nextSeq++, update });
61
+ this.#tailCache = undefined; // tail changed
62
+ if (this.#connected)
63
+ this.#startTimer();
64
+ this.flush();
65
+ }
66
+ /**
67
+ * Send the whole unacked tail as one merged delta. The id is the highest seq
68
+ * in the batch, so a single { ack } cumulatively confirms everything up to it.
69
+ * No-op while disconnected (the tail is replayed on the next onConnect).
70
+ */
71
+ flush() {
72
+ if (!this.#connected || this.pending.length === 0)
73
+ return;
74
+ this.#send(this.#mergedTail(), this.pending[this.pending.length - 1].seq);
75
+ }
76
+ /**
77
+ * Confirm delivery up to `id`: prune every queued update with seq <= id.
78
+ * Acks arrive over the wire, so validate before pruning. A malformed value
79
+ * (NaN/string/negative) or an impossible future id must not silently drop the
80
+ * queue; invalid acks are ignored.
81
+ */
82
+ onAck(id) {
83
+ if (!Number.isSafeInteger(id) || id < 0)
84
+ return; // malformed / impossible
85
+ if (this.pending.length > 0 && id > this.pending[this.pending.length - 1].seq)
86
+ return; // future ack
87
+ this.pending = this.pending.filter((p) => p.seq > id);
88
+ this.#tailCache = undefined; // tail changed
89
+ if (this.pending.length === 0)
90
+ this.#stopTimer();
91
+ }
92
+ /** Transport (re)connected: replay the unacked tail and resume retransmits. */
93
+ onConnect() {
94
+ this.#connected = true;
95
+ this.flush();
96
+ if (this.pending.length > 0)
97
+ this.#startTimer();
98
+ }
99
+ /** Transport dropped: keep the queue (for reconnect replay), pause the timer. */
100
+ onDisconnect() {
101
+ this.#connected = false;
102
+ this.#stopTimer();
103
+ }
104
+ /**
105
+ * One retransmit tick. Exposed for deterministic testing; normally driven by
106
+ * the internal timer.
107
+ */
108
+ onTick() {
109
+ if (!this.#connected || this.pending.length === 0)
110
+ return;
111
+ this.flush();
112
+ }
113
+ /** Stop timers and drop references. Call when the provider is destroyed. */
114
+ destroy() {
115
+ this.#connected = false;
116
+ this.#stopTimer();
117
+ this.pending = [];
118
+ this.#tailCache = undefined;
119
+ }
120
+ /** The unacked tail merged into one delta (memoized between tail changes). */
121
+ #mergedTail() {
122
+ if (this.#tailCache === undefined) {
123
+ const updates = this.pending.map((p) => p.update);
124
+ this.#tailCache = updates.length === 1 ? updates[0] : this.#merge(updates);
125
+ }
126
+ return this.#tailCache;
127
+ }
128
+ #startTimer() {
129
+ if (this.#timer !== undefined)
130
+ return;
131
+ this.#timer = this.#setInterval(() => this.onTick(), this.#resendInterval);
132
+ const t = this.#timer;
133
+ if (t && typeof t.unref === "function")
134
+ t.unref();
135
+ }
136
+ #stopTimer() {
137
+ if (this.#timer !== undefined)
138
+ this.#clearInterval(this.#timer);
139
+ this.#timer = undefined;
140
+ }
141
+ }
142
+ exports.ReliableSync = ReliableSync;
@@ -0,0 +1,252 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.YProtocolSession = exports.MessageType = void 0;
37
+ // Transport-agnostic session for the yrby y-websocket protocol. Handles the
38
+ // y-protocols framing, the sync handshake (SyncStep1/Step2/Update), and awareness
39
+ // encode/apply on top of ReliableSync. Bind it to a Y.Doc (and an optional
40
+ // Awareness); it works in raw Uint8Array frames and leaves the transport to the
41
+ // caller: base64, the { update, id } / { ack } envelope, and a socket.
42
+ //
43
+ // Call onConnect() when the transport connects, onDisconnect() when it drops,
44
+ // ack(id) on an { ack } envelope, and receive(frame) for an inbound frame (it
45
+ // returns a reply to send, or null). Local doc and awareness edits send
46
+ // themselves via the "update" events.
47
+ const yjs_1 = require("yjs");
48
+ const encoding = __importStar(require("lib0/encoding"));
49
+ const decoding = __importStar(require("lib0/decoding"));
50
+ const sync_1 = require("y-protocols/sync");
51
+ const awareness_1 = require("y-protocols/awareness");
52
+ const reliable_sync_js_1 = require("./reliable_sync.js");
53
+ // The y-protocols frame types yrby speaks, as the leading byte of a frame.
54
+ // Other y-protocols types (auth = 2, query-awareness = 3) are not handled.
55
+ exports.MessageType = { Sync: 0, Awareness: 1 };
56
+ class YProtocolSession {
57
+ doc;
58
+ awareness;
59
+ #send;
60
+ #onError;
61
+ #synced = false;
62
+ #delivery;
63
+ #onDocUpdate;
64
+ #onAwarenessUpdate;
65
+ constructor(doc, opts) {
66
+ const { send, awareness = null, resendInterval, onError, setInterval: setIntervalFn, clearInterval: clearIntervalFn, } = opts ?? {};
67
+ if (!doc)
68
+ throw new TypeError("YProtocolSession requires a Y.Doc");
69
+ if (typeof send !== "function")
70
+ throw new TypeError("YProtocolSession requires a send(frame, id) function");
71
+ this.doc = doc;
72
+ this.awareness = awareness;
73
+ this.#send = send;
74
+ this.#onError = onError ?? ((error, context) => console.warn(`[yrby] ${context}:`, error));
75
+ this.#delivery = new reliable_sync_js_1.ReliableSync({
76
+ merge: yjs_1.mergeUpdates,
77
+ send: (update, id) => this.#send(this.#frameUpdate(update), id),
78
+ resendInterval,
79
+ setInterval: setIntervalFn,
80
+ clearInterval: clearIntervalFn,
81
+ });
82
+ this.#onDocUpdate = (update, origin) => {
83
+ if (origin === this)
84
+ return; // applied from the server; don't echo it back
85
+ this.#delivery.enqueue(update);
86
+ };
87
+ this.doc.on("update", this.#onDocUpdate);
88
+ if (this.awareness) {
89
+ this.#onAwarenessUpdate = ({ added, updated, removed }, origin) => {
90
+ // Only broadcast our own presence changes. Updates applied from a peer,
91
+ // and our own remote-cleanup in onDisconnect, carry origin === this;
92
+ // re-sending those would echo presence and broadcast tombstones for
93
+ // other clients' cursors.
94
+ if (origin === this)
95
+ return;
96
+ const changed = added.concat(updated, removed);
97
+ this.#send(this.#frameAwareness(changed), undefined); // fire-and-forget
98
+ };
99
+ this.awareness.on("update", this.#onAwarenessUpdate);
100
+ }
101
+ }
102
+ /** True once we've received the server's SyncStep2 (the document is caught up). */
103
+ get synced() {
104
+ return this.#synced;
105
+ }
106
+ /** True while there are unacknowledged local document updates in flight. */
107
+ get hasPending() {
108
+ return this.#delivery.hasPending;
109
+ }
110
+ /** Transport connected: send the opening handshake and replay the unacked tail. */
111
+ onConnect() {
112
+ this.#send(this.#frameSyncStep1(), undefined);
113
+ if (this.awareness && this.awareness.getLocalState() !== null) {
114
+ this.#send(this.#frameAwareness([this.doc.clientID]), undefined);
115
+ }
116
+ this.#delivery.onConnect();
117
+ }
118
+ /** Transport dropped: pause retransmits (queue kept) and clear remote presence. */
119
+ onDisconnect() {
120
+ this.#synced = false;
121
+ this.#delivery.onDisconnect();
122
+ if (this.awareness) {
123
+ const remote = [...this.awareness.getStates().keys()].filter((c) => c !== this.doc.clientID);
124
+ if (remote.length)
125
+ (0, awareness_1.removeAwarenessStates)(this.awareness, remote, this);
126
+ }
127
+ }
128
+ /**
129
+ * Broadcast that our local presence is gone (sets local state to null, which
130
+ * emits a removal awareness frame through `send`). Call this while the
131
+ * transport is still live so peers drop our cursor immediately instead of
132
+ * waiting for the awareness timeout. A no-op when there's no local state.
133
+ */
134
+ removeLocalAwareness() {
135
+ if (this.awareness && this.awareness.getLocalState() !== null) {
136
+ this.awareness.setLocalState(null); // fires "update" -> sends the removal frame
137
+ }
138
+ }
139
+ /** A reliable-delivery `{ ack: id }` envelope arrived. */
140
+ ack(id) {
141
+ this.#delivery.onAck(id);
142
+ }
143
+ /**
144
+ * Apply an update without treating it as a local edit, so it isn't queued for
145
+ * re-delivery to the server. Use it for bootstrap/restore: initial state loaded
146
+ * over HTTP, a server snapshot, an import. These are bytes the server already
147
+ * has.
148
+ *
149
+ * The session re-sends any doc update whose origin isn't itself (that's how a
150
+ * keystroke becomes an outbound frame), so a bare `Y.applyUpdate(doc, update)`
151
+ * would look like a local edit and get echoed back on the next connect. Going
152
+ * through here applies under the session's own origin, which the outbound
153
+ * filter skips. Safe to call before `onConnect()`: the state folds into the
154
+ * SyncStep1 handshake instead of being re-sent.
155
+ */
156
+ applyRemoteUpdate(update) {
157
+ (0, yjs_1.applyUpdate)(this.doc, update, this);
158
+ }
159
+ /**
160
+ * Decode and apply one incoming binary protocol frame (document sync or
161
+ * awareness). Returns a reply frame to transmit (e.g. SyncStep2 answering a
162
+ * SyncStep1), or null if there's nothing to send.
163
+ */
164
+ receive(frame) {
165
+ // A malformed/truncated frame must never take down the transport callback:
166
+ // decode + apply defensively, drop the frame on error, keep the session live.
167
+ try {
168
+ const validatedType = this.#validateFrame(frame);
169
+ if (validatedType === null)
170
+ return null;
171
+ const decoder = decoding.createDecoder(frame);
172
+ const encoder = encoding.createEncoder();
173
+ const type = decoding.readVarUint(decoder);
174
+ switch (type) {
175
+ case exports.MessageType.Sync: {
176
+ encoding.writeVarUint(encoder, exports.MessageType.Sync);
177
+ const syncType = (0, sync_1.readSyncMessage)(decoder, encoder, this.doc, this);
178
+ if (!this.#synced && syncType === sync_1.messageYjsSyncStep2)
179
+ this.#synced = true;
180
+ break;
181
+ }
182
+ case exports.MessageType.Awareness:
183
+ if (this.awareness)
184
+ (0, awareness_1.applyAwarenessUpdate)(this.awareness, decoding.readVarUint8Array(decoder), this);
185
+ break;
186
+ default:
187
+ return null; // a y-protocols type yrby doesn't speak (auth, query-awareness): ignore
188
+ }
189
+ return encoding.length(encoder) > 1 ? encoding.toUint8Array(encoder) : null;
190
+ }
191
+ catch (error) {
192
+ this.#onError(error, "receive");
193
+ return null;
194
+ }
195
+ }
196
+ /** Detach doc/awareness listeners and stop retransmits. */
197
+ destroy() {
198
+ this.doc.off("update", this.#onDocUpdate);
199
+ if (this.awareness && this.#onAwarenessUpdate)
200
+ this.awareness.off("update", this.#onAwarenessUpdate);
201
+ this.#delivery.destroy();
202
+ }
203
+ #frameSyncStep1() {
204
+ const e = encoding.createEncoder();
205
+ encoding.writeVarUint(e, exports.MessageType.Sync);
206
+ (0, sync_1.writeSyncStep1)(e, this.doc);
207
+ return encoding.toUint8Array(e);
208
+ }
209
+ #frameUpdate(update) {
210
+ const e = encoding.createEncoder();
211
+ encoding.writeVarUint(e, exports.MessageType.Sync);
212
+ (0, sync_1.writeUpdate)(e, update);
213
+ return encoding.toUint8Array(e);
214
+ }
215
+ #frameAwareness(clients) {
216
+ const e = encoding.createEncoder();
217
+ encoding.writeVarUint(e, exports.MessageType.Awareness);
218
+ encoding.writeVarUint8Array(e, (0, awareness_1.encodeAwarenessUpdate)(this.awareness, clients));
219
+ return encoding.toUint8Array(e);
220
+ }
221
+ #validateFrame(frame) {
222
+ const decoder = decoding.createDecoder(frame);
223
+ const type = decoding.readVarUint(decoder);
224
+ switch (type) {
225
+ case exports.MessageType.Sync: {
226
+ const scratchDoc = new yjs_1.Doc();
227
+ try {
228
+ const scratchEncoder = encoding.createEncoder();
229
+ encoding.writeVarUint(scratchEncoder, exports.MessageType.Sync);
230
+ (0, sync_1.readSyncMessage)(decoder, scratchEncoder, scratchDoc, this);
231
+ }
232
+ finally {
233
+ scratchDoc.destroy();
234
+ }
235
+ break;
236
+ }
237
+ case exports.MessageType.Awareness:
238
+ decoding.readVarUint8Array(decoder);
239
+ break;
240
+ default:
241
+ return null; // a y-protocols type yrby doesn't speak: ignore
242
+ }
243
+ // This protocol is one message per frame. Anything left after a complete
244
+ // message is malformed (trailing garbage, or low-level packed messages whose
245
+ // tail we'd silently drop), so reject it before mutating local state.
246
+ if (decoding.hasContent(decoder)) {
247
+ throw new Error("frame has trailing bytes after a complete message");
248
+ }
249
+ return type;
250
+ }
251
+ }
252
+ exports.YProtocolSession = YProtocolSession;
package/package.json CHANGED
@@ -1,23 +1,26 @@
1
1
  {
2
2
  "name": "yrby-client",
3
- "version": "0.4.0",
4
- "description": "JavaScript client for the yrby y-websocket protocol: a ready-made ActionCable/AnyCable provider, a transport-agnostic protocol session (sync steps, encode/decode, awareness), and a reliable-delivery core (ack-tracked queue, sync-since-last-ack, retransmit + reconnect replay). Written in TypeScript with bundled types; usable from plain JS.",
3
+ "version": "0.4.2",
4
+ "description": "JavaScript client for the yrby y-websocket protocol: a ready-made ActionCable/AnyCable provider, a transport-agnostic protocol session (sync steps, encode/decode, awareness), and a reliable-delivery core (ack-tracked queue, sync-since-last-ack, retransmit + reconnect replay). Written in TypeScript with bundled types; ESM + CommonJS, usable from plain JS.",
5
5
  "type": "module",
6
- "main": "./dist/index.js",
6
+ "main": "./dist/cjs/index.js",
7
7
  "module": "./dist/index.js",
8
8
  "types": "./dist/index.d.ts",
9
9
  "exports": {
10
10
  ".": {
11
11
  "types": "./dist/index.d.ts",
12
- "default": "./dist/index.js"
12
+ "import": "./dist/index.js",
13
+ "require": "./dist/cjs/index.js"
13
14
  },
14
15
  "./reliable": {
15
16
  "types": "./dist/reliable_sync.d.ts",
16
- "default": "./dist/reliable_sync.js"
17
+ "import": "./dist/reliable_sync.js",
18
+ "require": "./dist/cjs/reliable_sync.js"
17
19
  },
18
20
  "./base64": {
19
21
  "types": "./dist/base64.d.ts",
20
- "default": "./dist/base64.js"
22
+ "import": "./dist/base64.js",
23
+ "require": "./dist/cjs/base64.js"
21
24
  }
22
25
  },
23
26
  "files": [
@@ -26,7 +29,7 @@
26
29
  ],
27
30
  "scripts": {
28
31
  "clean": "rm -rf dist",
29
- "build": "npm run clean && tsc",
32
+ "build": "npm run clean && tsc && tsc -p tsconfig.cjs.json && node -e \"require('fs').writeFileSync('dist/cjs/package.json', JSON.stringify({ type: 'commonjs' }) + '\\n')\"",
30
33
  "typecheck": "tsc --noEmit",
31
34
  "prepack": "npm run build",
32
35
  "test": "npm run build && node --test"