@fieldnotes/sync-server 0.11.0 → 0.13.0

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/dist/index.cjs CHANGED
@@ -35,6 +35,7 @@ var import_sync2 = require("@fieldnotes/sync");
35
35
  var import_sync = require("@fieldnotes/sync");
36
36
  var MemoryHubBackend = class {
37
37
  rooms = /* @__PURE__ */ new Map();
38
+ roomLayers = /* @__PURE__ */ new Map();
38
39
  room(id) {
39
40
  let r = this.rooms.get(id);
40
41
  if (!r) {
@@ -43,6 +44,14 @@ var MemoryHubBackend = class {
43
44
  }
44
45
  return r;
45
46
  }
47
+ layers(room) {
48
+ let r = this.roomLayers.get(room);
49
+ if (!r) {
50
+ r = /* @__PURE__ */ new Map();
51
+ this.roomLayers.set(room, r);
52
+ }
53
+ return r;
54
+ }
46
55
  async snapshot(room) {
47
56
  return [...this.room(room).values()];
48
57
  }
@@ -56,6 +65,15 @@ var MemoryHubBackend = class {
56
65
  }
57
66
  (0, import_sync.applyOpToMap)(this.room(room), op);
58
67
  }
68
+ async layerRecords(room) {
69
+ return [...this.layers(room).values()];
70
+ }
71
+ async getLayerRecord(room, id) {
72
+ return this.layers(room).get(id);
73
+ }
74
+ async applyLayerRecord(room, record) {
75
+ this.layers(room).set(record.id, record);
76
+ }
59
77
  };
60
78
 
61
79
  // src/hub-fanout.ts
@@ -83,6 +101,7 @@ var DEFAULT_MAX_PENDING_AUTH_BYTES = 2 * 1024 * 1024;
83
101
  var DEFAULT_MESSAGES_PER_SECOND = 120;
84
102
  var DEFAULT_MESSAGE_BURST = 240;
85
103
  var DEFAULT_PRESENCE_THROTTLE_MS = 50;
104
+ var DEFAULT_MAX_PRESENCE_LANES = 16;
86
105
  function hasJsonDepthAtMost(message, maxDepth) {
87
106
  let depth = 0;
88
107
  let inString = false;
@@ -138,6 +157,32 @@ function isFanoutOp(op) {
138
157
  if (o.kind === "remove") return typeof o.id === "string";
139
158
  return o.kind === "clear";
140
159
  }
160
+ var FALLBACK_PRESENCE_LANE = "";
161
+ var MAX_PRESENCE_LANE_LENGTH = 64;
162
+ function presenceLaneOf(data) {
163
+ if (typeof data !== "object" || data === null) return FALLBACK_PRESENCE_LANE;
164
+ const kind = data.kind;
165
+ if (typeof kind !== "string" || kind.length === 0 || kind.length > MAX_PRESENCE_LANE_LENGTH) {
166
+ return FALLBACK_PRESENCE_LANE;
167
+ }
168
+ return kind;
169
+ }
170
+ function isLayerOp(op) {
171
+ if (typeof op !== "object" || op === null) return false;
172
+ const k = op.kind;
173
+ return k === "layer-upsert" || k === "layer-remove";
174
+ }
175
+ function layerOpToRecord(op) {
176
+ return op.kind === "layer-upsert" ? { id: op.layer.id, version: op.version, editor: op.editor, definition: op.layer } : { id: op.id, version: op.version, editor: op.editor };
177
+ }
178
+ function layerRecordToOp(record) {
179
+ return record.definition ? {
180
+ kind: "layer-upsert",
181
+ layer: record.definition,
182
+ version: record.version,
183
+ editor: record.editor
184
+ } : { kind: "layer-remove", id: record.id, version: record.version, editor: record.editor };
185
+ }
141
186
  function isPresenceOp(op) {
142
187
  if (typeof op !== "object" || op === null) return false;
143
188
  const k = op.kind;
@@ -155,19 +200,37 @@ var SyncHub = class {
155
200
  fanout;
156
201
  fanoutUnsub;
157
202
  authorize;
203
+ authorizeLayer;
158
204
  canRead;
205
+ /** Fallback layer-record store for backends without layer persistence. */
206
+ memoryLayers = /* @__PURE__ */ new Map();
159
207
  maxJsonDepth;
160
208
  presenceThrottleMs;
161
- lastPresenceAt = /* @__PURE__ */ new Map();
162
- pendingPresence = /* @__PURE__ */ new Map();
209
+ maxPresenceLanes;
210
+ /**
211
+ * Presence throttle state keyed by connection, then by lane. A lane is the
212
+ * payload's `kind` (a non-empty string of at most 64 chars) or the reserved
213
+ * fallback lane `''`, so a rapid stream of one kind (awareness cursors) can
214
+ * never replace a pending frame of another kind (a ping, a path `cleared`).
215
+ * Within a lane the newest payload wins. The lane count per connection is
216
+ * capped by `maxPresenceLanes`, counting the fallback lane, so a client
217
+ * cannot mint timers by varying `kind`.
218
+ */
219
+ presenceLanes = /* @__PURE__ */ new Map();
163
220
  constructor(options = {}) {
164
221
  this.backend = options.backend ?? new MemoryHubBackend();
165
222
  this.instanceId = options.instanceId ?? generateInstanceId();
166
223
  this.fanout = options.fanout ?? new InMemoryHubFanout();
167
224
  this.authorize = options.authorize;
225
+ this.authorizeLayer = options.authorizeLayer;
168
226
  this.canRead = options.canRead;
169
227
  this.maxJsonDepth = options.maxJsonDepth ?? DEFAULT_MAX_JSON_DEPTH;
170
228
  this.presenceThrottleMs = options.presenceThrottleMs ?? DEFAULT_PRESENCE_THROTTLE_MS;
229
+ const maxPresenceLanes = options.maxPresenceLanes ?? DEFAULT_MAX_PRESENCE_LANES;
230
+ if (!Number.isFinite(maxPresenceLanes) || maxPresenceLanes < 1) {
231
+ throw new RangeError("maxPresenceLanes must be a finite number of at least 1");
232
+ }
233
+ this.maxPresenceLanes = Math.floor(maxPresenceLanes);
171
234
  this.fanoutUnsub = this.fanout.subscribe((payload) => this.onFanout(payload));
172
235
  }
173
236
  addConnection(conn) {
@@ -185,10 +248,7 @@ var SyncHub = class {
185
248
  this.conns.delete(connId);
186
249
  const room = conn.room;
187
250
  const hadPresence = this.presenceConnections.delete(connId);
188
- this.lastPresenceAt.delete(connId);
189
- const pendingPresence = this.pendingPresence.get(connId);
190
- if (pendingPresence) clearTimeout(pendingPresence.timer);
191
- this.pendingPresence.delete(connId);
251
+ this.clearPresenceLanes(connId);
192
252
  const members = this.rooms.get(room);
193
253
  if (members) {
194
254
  members.delete(connId);
@@ -238,11 +298,17 @@ var SyncHub = class {
238
298
  if (op.kind === "request-snapshot") {
239
299
  const all = await this.backend.snapshot(conn.room);
240
300
  const elements = this.canRead ? all.filter((el) => this.mayRead(conn, el.audience)) : all;
301
+ const layers = await this.getLayerRecords(conn.room);
241
302
  conn.send(
242
303
  // `to` is a private correlation address for the requesting SyncClient. It is never
243
304
  // broadcast; every public sender identity below comes from the server-owned connection.
244
- JSON.stringify({ from: HUB_FROM, op: { kind: "snapshot", to: env.from, elements } })
305
+ JSON.stringify({
306
+ from: HUB_FROM,
307
+ op: layers.length > 0 ? { kind: "snapshot", to: env.from, elements, layers } : { kind: "snapshot", to: env.from, elements }
308
+ })
245
309
  );
310
+ } else if (op.kind === "layer-upsert" || op.kind === "layer-remove") {
311
+ await this.processLayerOp(conn, op);
246
312
  } else if (op.kind === "upsert" || op.kind === "remove" || op.kind === "clear") {
247
313
  const id = op.kind === "upsert" ? op.element.id : op.kind === "remove" ? op.id : void 0;
248
314
  const needCurrent = (this.authorize || this.canRead) && id !== void 0;
@@ -282,6 +348,72 @@ var SyncHub = class {
282
348
  this.deliverToRoom(conn.room, conn.id, conn.id, outboundOp, prevAudience, prevExisted);
283
349
  }
284
350
  }
351
+ /**
352
+ * Applies a layer-definition edit on the room's serial queue. Convergence is
353
+ * last-writer-wins under the deterministic (version, editor) ordering — the
354
+ * same rule every client applies — so arrival order never decides a race. A
355
+ * stale or denied edit is answered with an authoritative correction to the
356
+ * sender only.
357
+ */
358
+ async processLayerOp(conn, op) {
359
+ const record = layerOpToRecord(op);
360
+ const current = await this.getLayerRecord(conn.room, record.id);
361
+ if (this.authorizeLayer) {
362
+ const allowed = await this.authorizeLayer({
363
+ userId: conn.userId,
364
+ role: conn.role,
365
+ room: conn.room,
366
+ op,
367
+ currentRecord: current
368
+ });
369
+ if (!allowed) {
370
+ const correction = current ?? { id: record.id, version: record.version, editor: HUB_FROM };
371
+ conn.send(JSON.stringify({ from: HUB_FROM, op: layerRecordToOp(correction) }));
372
+ return;
373
+ }
374
+ }
375
+ if (current && !(0, import_sync2.isNewerLayerRecord)(record, current)) {
376
+ conn.send(JSON.stringify({ from: HUB_FROM, op: layerRecordToOp(current) }));
377
+ return;
378
+ }
379
+ await this.applyLayerRecord(conn.room, record);
380
+ await this.fanout.publish(
381
+ JSON.stringify({ o: this.instanceId, room: conn.room, from: conn.id, op })
382
+ );
383
+ this.relayToRoom(conn.room, conn.id, JSON.stringify({ from: conn.id, op }));
384
+ }
385
+ layerBackend() {
386
+ const { layerRecords, getLayerRecord, applyLayerRecord } = this.backend;
387
+ if (!layerRecords || !getLayerRecord || !applyLayerRecord) return null;
388
+ return {
389
+ layerRecords: layerRecords.bind(this.backend),
390
+ getLayerRecord: getLayerRecord.bind(this.backend),
391
+ applyLayerRecord: applyLayerRecord.bind(this.backend)
392
+ };
393
+ }
394
+ async getLayerRecords(room) {
395
+ const backend = this.layerBackend();
396
+ if (backend) return backend.layerRecords(room);
397
+ return [...this.memoryLayers.get(room)?.values() ?? []];
398
+ }
399
+ async getLayerRecord(room, id) {
400
+ const backend = this.layerBackend();
401
+ if (backend) return backend.getLayerRecord(room, id);
402
+ return this.memoryLayers.get(room)?.get(id);
403
+ }
404
+ async applyLayerRecord(room, record) {
405
+ const backend = this.layerBackend();
406
+ if (backend) {
407
+ await backend.applyLayerRecord(room, record);
408
+ return;
409
+ }
410
+ let map = this.memoryLayers.get(room);
411
+ if (!map) {
412
+ map = /* @__PURE__ */ new Map();
413
+ this.memoryLayers.set(room, map);
414
+ }
415
+ map.set(record.id, record);
416
+ }
285
417
  mayRead(conn, audience) {
286
418
  if (!this.canRead) return true;
287
419
  return this.canRead({ userId: conn.userId, role: conn.role, room: conn.room, audience });
@@ -322,34 +454,63 @@ var SyncHub = class {
322
454
  })
323
455
  );
324
456
  }
457
+ clearPresenceLanes(connId) {
458
+ const lanes = this.presenceLanes.get(connId);
459
+ if (!lanes) return;
460
+ for (const lane of lanes.values()) {
461
+ if (lane.pending) clearTimeout(lane.pending.timer);
462
+ }
463
+ this.presenceLanes.delete(connId);
464
+ }
325
465
  schedulePresence(conn, data) {
326
466
  if (this.presenceThrottleMs <= 0) {
327
467
  this.broadcastClientPresence(conn, data);
328
468
  return;
329
469
  }
470
+ const lane = this.presenceLaneFor(conn.id, presenceLaneOf(data));
330
471
  const now = Date.now();
331
- const lastSentAt = this.lastPresenceAt.get(conn.id);
332
- if (lastSentAt === void 0 || now - lastSentAt >= this.presenceThrottleMs) {
333
- this.lastPresenceAt.set(conn.id, now);
472
+ if (lane.lastSentAt === void 0 || now - lane.lastSentAt >= this.presenceThrottleMs) {
473
+ if (lane.pending) {
474
+ clearTimeout(lane.pending.timer);
475
+ lane.pending = null;
476
+ }
477
+ lane.lastSentAt = now;
334
478
  this.broadcastClientPresence(conn, data);
335
479
  return;
336
480
  }
337
- const existing = this.pendingPresence.get(conn.id);
338
- if (existing) {
339
- existing.data = data;
481
+ if (lane.pending) {
482
+ lane.pending.data = data;
340
483
  return;
341
484
  }
342
485
  const timer = setTimeout(
343
486
  () => {
344
- const pending = this.pendingPresence.get(conn.id);
345
- this.pendingPresence.delete(conn.id);
487
+ const pending = lane.pending;
488
+ lane.pending = null;
346
489
  if (!pending || !this.conns.has(conn.id)) return;
347
- this.lastPresenceAt.set(conn.id, Date.now());
490
+ lane.lastSentAt = Date.now();
348
491
  this.broadcastClientPresence(conn, pending.data);
349
492
  },
350
- this.presenceThrottleMs - (now - lastSentAt)
493
+ this.presenceThrottleMs - (now - lane.lastSentAt)
351
494
  );
352
- this.pendingPresence.set(conn.id, { data, timer });
495
+ lane.pending = { data, timer };
496
+ }
497
+ presenceLaneFor(connId, requested) {
498
+ let lanes = this.presenceLanes.get(connId);
499
+ if (!lanes) {
500
+ lanes = /* @__PURE__ */ new Map();
501
+ this.presenceLanes.set(connId, lanes);
502
+ }
503
+ let key = requested;
504
+ if (key !== FALLBACK_PRESENCE_LANE && !lanes.has(key)) {
505
+ const named = lanes.size - (lanes.has(FALLBACK_PRESENCE_LANE) ? 1 : 0);
506
+ if (named >= this.maxPresenceLanes - 1) key = FALLBACK_PRESENCE_LANE;
507
+ }
508
+ let lane = lanes.get(key);
509
+ if (!lane) {
510
+ lane = { lastSentAt: void 0, pending: null };
511
+ lanes.set(key, lane);
512
+ }
513
+ return lane;
353
514
  }
354
515
  broadcastLeave(room, from) {
355
516
  const message = JSON.stringify({ from, op: { kind: "presence-leave" } });
@@ -427,14 +588,25 @@ var SyncHub = class {
427
588
  this.relayToRoom(env.room, void 0, JSON.stringify({ from: env.from, op }));
428
589
  return;
429
590
  }
591
+ if (isLayerOp(op)) {
592
+ void this.applyFanoutLayerOp(env.room, op).catch(() => {
593
+ });
594
+ this.relayToRoom(env.room, void 0, JSON.stringify({ from: env.from, op }));
595
+ return;
596
+ }
430
597
  if (!isFanoutOp(op)) return;
431
598
  const prevAudience = typeof env.prev === "string" ? env.prev : void 0;
432
599
  const prevExisted = env.existed === true;
433
600
  this.deliverToRoom(env.room, void 0, env.from, op, prevAudience, prevExisted);
434
601
  }
602
+ async applyFanoutLayerOp(room, op) {
603
+ const record = layerOpToRecord(op);
604
+ const current = await this.getLayerRecord(room, record.id);
605
+ if (current && !(0, import_sync2.isNewerLayerRecord)(record, current)) return;
606
+ await this.applyLayerRecord(room, record);
607
+ }
435
608
  close() {
436
- for (const pending of this.pendingPresence.values()) clearTimeout(pending.timer);
437
- this.pendingPresence.clear();
609
+ for (const connId of [...this.presenceLanes.keys()]) this.clearPresenceLanes(connId);
438
610
  this.fanoutUnsub();
439
611
  }
440
612
  };
@@ -522,9 +694,11 @@ function createSyncServer(options = {}) {
522
694
  fanout: options.fanout,
523
695
  instanceId: options.instanceId,
524
696
  authorize: options.authorize,
697
+ authorizeLayer: options.authorizeLayer,
525
698
  canRead: options.canRead,
526
699
  maxJsonDepth: options.maxJsonDepth ?? DEFAULT_MAX_JSON_DEPTH,
527
- presenceThrottleMs: options.presenceThrottleMs ?? DEFAULT_PRESENCE_THROTTLE_MS
700
+ presenceThrottleMs: options.presenceThrottleMs ?? DEFAULT_PRESENCE_THROTTLE_MS,
701
+ maxPresenceLanes: options.maxPresenceLanes
528
702
  });
529
703
  const maxMessageBytes = options.maxMessageBytes ?? DEFAULT_MAX_MESSAGE_BYTES;
530
704
  const wss = options.server ? new import_ws.WebSocketServer({ server: options.server, maxPayload: maxMessageBytes }) : new import_ws.WebSocketServer({ port: options.port ?? 0, maxPayload: maxMessageBytes });
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/sync-hub.ts","../src/memory-hub-backend.ts","../src/hub-fanout.ts","../src/resource-limits.ts","../src/create-sync-server.ts","../src/heartbeat.ts","../src/shutdown.ts"],"sourcesContent":["export { SyncHub } from './sync-hub';\r\nexport type { SyncHubOptions, Connection } from './sync-hub';\r\nexport { MemoryHubBackend } from './memory-hub-backend';\r\nexport type { HubBackend } from './hub-backend';\r\nexport { createSyncServer } from './create-sync-server';\r\nexport type { CreateSyncServerOptions } from './create-sync-server';\r\nexport { InMemoryHubFanout } from './hub-fanout';\r\nexport type { HubFanout } from './hub-fanout';\r\nexport type { AuthInfo, AuthResult, Authenticate } from './authenticate';\r\nexport type { Authorize, AuthorizeContext, OwnedElement, ReadContext, CanRead } from './authorize';\r\nexport { startHeartbeat } from './heartbeat';\r\nexport type { Heartbeat, HeartbeatSocket, HeartbeatServer } from './heartbeat';\r\n","import { parseEnvelope, isValidElement, type SyncOp, type SyncEnvelope } from '@fieldnotes/sync';\nimport { MemoryHubBackend } from './memory-hub-backend';\nimport { InMemoryHubFanout, type HubFanout } from './hub-fanout';\nimport type { HubBackend } from './hub-backend';\nimport type { Authorize, CanRead, OwnedElement } from './authorize';\nimport {\n DEFAULT_MAX_JSON_DEPTH,\n DEFAULT_PRESENCE_THROTTLE_MS,\n hasJsonDepthAtMost,\n} from './resource-limits';\n\nexport interface Connection {\n id: string;\n room: string;\n userId?: string;\n role?: string;\n send(message: string): void;\n}\n\nexport interface SyncHubOptions {\n backend?: HubBackend;\n fanout?: HubFanout;\n instanceId?: string;\n authorize?: Authorize;\n canRead?: CanRead;\n maxJsonDepth?: number;\n presenceThrottleMs?: number;\n}\n\nconst HUB_FROM = 'hub';\n\nfunction generateInstanceId(): string {\n if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function')\n return crypto.randomUUID();\n return `i-${Math.random().toString(36).slice(2)}`;\n}\n\nfunction isFanoutOp(op: unknown): op is Extract<SyncOp, { kind: 'upsert' | 'remove' | 'clear' }> {\n if (typeof op !== 'object' || op === null) return false;\n const o = op as { kind?: unknown; element?: unknown; id?: unknown };\n if (o.kind === 'upsert') return isValidElement(o.element);\n if (o.kind === 'remove') return typeof o.id === 'string';\n return o.kind === 'clear';\n}\n\nfunction isPresenceOp(\n op: unknown,\n): op is { kind: 'presence'; data: unknown } | { kind: 'presence-leave' } {\n if (typeof op !== 'object' || op === null) return false;\n const k = (op as { kind?: unknown }).kind;\n return k === 'presence' || k === 'presence-leave';\n}\n\nexport class SyncHub {\n private readonly backend: HubBackend;\n private readonly conns = new Map<string, Connection>();\n private readonly rooms = new Map<string, Set<string>>(); // room → connIds\n private readonly roomQueues = new Map<string, Promise<void>>(); // room → serial tail\n private readonly presenceConnections = new Set<string>();\n private readonly instanceId: string;\n private readonly fanout: HubFanout;\n private readonly fanoutUnsub: () => void;\n private readonly authorize?: Authorize;\n private readonly canRead?: CanRead;\n private readonly maxJsonDepth: number;\n private readonly presenceThrottleMs: number;\n private readonly lastPresenceAt = new Map<string, number>();\n private readonly pendingPresence = new Map<\n string,\n { data: unknown; timer: ReturnType<typeof setTimeout> }\n >();\n\n constructor(options: SyncHubOptions = {}) {\n this.backend = options.backend ?? new MemoryHubBackend();\n this.instanceId = options.instanceId ?? generateInstanceId();\n this.fanout = options.fanout ?? new InMemoryHubFanout();\n this.authorize = options.authorize;\n this.canRead = options.canRead;\n this.maxJsonDepth = options.maxJsonDepth ?? DEFAULT_MAX_JSON_DEPTH;\n this.presenceThrottleMs = options.presenceThrottleMs ?? DEFAULT_PRESENCE_THROTTLE_MS;\n this.fanoutUnsub = this.fanout.subscribe((payload) => this.onFanout(payload));\n }\n\n addConnection(conn: Connection): void {\n this.conns.set(conn.id, conn);\n let set = this.rooms.get(conn.room);\n if (!set) {\n set = new Set();\n this.rooms.set(conn.room, set);\n }\n set.add(conn.id);\n }\n\n removeConnection(connId: string): void {\n const conn = this.conns.get(connId);\n if (!conn) return;\n this.conns.delete(connId);\n const room = conn.room;\n const hadPresence = this.presenceConnections.delete(connId);\n this.lastPresenceAt.delete(connId);\n const pendingPresence = this.pendingPresence.get(connId);\n if (pendingPresence) clearTimeout(pendingPresence.timer);\n this.pendingPresence.delete(connId);\n const members = this.rooms.get(room);\n if (members) {\n members.delete(connId);\n if (members.size === 0) {\n this.rooms.delete(room);\n this.roomQueues.delete(room);\n }\n }\n if (hadPresence) this.broadcastLeave(room, conn.id);\n }\n\n roomCount(): number {\n return this.rooms.size;\n }\n\n /**\n * Broadcasts ephemeral server-owned presence data to every connection in a room.\n * The returned count covers successful delivery on this hub instance only; configured fan-out\n * forwards the same event to other instances on a best-effort basis.\n */\n broadcastPresence<T>(room: string, data: T): number {\n const op = { kind: 'presence' as const, data };\n const sent = this.relayToRoom(room, undefined, JSON.stringify({ from: HUB_FROM, op }));\n this.safePublish(JSON.stringify({ o: this.instanceId, room, from: HUB_FROM, op }));\n return sent;\n }\n\n handleMessage(connId: string, message: string): Promise<void> {\n const conn = this.conns.get(connId);\n if (!conn) return Promise.resolve();\n if (!hasJsonDepthAtMost(message, this.maxJsonDepth)) return Promise.resolve();\n const env = parseEnvelope(message);\n if (!env) return Promise.resolve();\n if (env.op.kind === 'presence') {\n this.schedulePresence(conn, env.op.data); // off-queue, throttled independently\n return Promise.resolve();\n }\n const room = conn.room;\n // The per-room serial queue is the single total-order authority: ops apply in arrival order\n // (arrival-order LWW — no per-element seq; see D3 / TD-12). Different rooms run independently.\n const prev = this.roomQueues.get(room) ?? Promise.resolve();\n const operation = prev.then(() => this.process(conn, env));\n this.roomQueues.set(\n room,\n operation.catch(() => {\n // Recover only the internal tail so one failed message never wedges the room queue.\n // The caller still receives the operation rejection for observability.\n }),\n );\n return operation;\n }\n\n private async process(conn: Connection, env: SyncEnvelope): Promise<void> {\n const op = env.op;\n if (op.kind === 'request-snapshot') {\n const all = (await this.backend.snapshot(conn.room)) as OwnedElement[];\n const elements = this.canRead ? all.filter((el) => this.mayRead(conn, el.audience)) : all;\n conn.send(\n // `to` is a private correlation address for the requesting SyncClient. It is never\n // broadcast; every public sender identity below comes from the server-owned connection.\n JSON.stringify({ from: HUB_FROM, op: { kind: 'snapshot', to: env.from, elements } }),\n );\n } else if (op.kind === 'upsert' || op.kind === 'remove' || op.kind === 'clear') {\n const id = op.kind === 'upsert' ? op.element.id : op.kind === 'remove' ? op.id : undefined;\n const needCurrent = (this.authorize || this.canRead) && id !== undefined;\n const current: OwnedElement | undefined = needCurrent\n ? await this.backend.get(conn.room, id)\n : undefined;\n\n let outboundOp: SyncOp = op;\n if (this.authorize) {\n const allowed = await this.authorize({\n userId: conn.userId,\n role: conn.role,\n room: conn.room,\n op,\n currentElement: current,\n });\n if (!allowed) {\n await this.sendCorrection(conn, env.from, op, current);\n return;\n }\n if (op.kind === 'upsert') {\n const ownerId = current?.ownerId ?? conn.userId;\n const stampedElement: OwnedElement = { ...op.element, ownerId };\n outboundOp = { kind: 'upsert', element: stampedElement };\n }\n }\n\n await this.backend.apply(conn.room, outboundOp);\n\n const prevExisted = current !== undefined;\n const prevAudience = current?.audience;\n\n await this.fanout.publish(\n JSON.stringify({\n o: this.instanceId,\n room: conn.room,\n from: conn.id,\n op: outboundOp,\n prev: prevAudience,\n existed: prevExisted,\n }),\n );\n\n this.deliverToRoom(conn.room, conn.id, conn.id, outboundOp, prevAudience, prevExisted);\n }\n // 'snapshot' from a client → ignored\n }\n\n private mayRead(conn: Connection, audience: string | undefined): boolean {\n if (!this.canRead) return true;\n return this.canRead({ userId: conn.userId, role: conn.role, room: conn.room, audience });\n }\n\n private safePublish(payload: string): void {\n try {\n void Promise.resolve(this.fanout.publish(payload)).catch(() => {\n /* presence is ephemeral; a broken publisher must not create an unhandled rejection */\n });\n } catch {\n /* a broken fanout publisher must not break the un-queued presence relay */\n }\n }\n\n private relayToRoom(room: string, excludeId: string | undefined, message: string): number {\n const members = this.rooms.get(room);\n if (!members) return 0;\n let sent = 0;\n for (const cid of members) {\n if (cid === excludeId) continue;\n const conn = this.conns.get(cid);\n if (!conn) continue;\n try {\n conn.send(message);\n sent += 1;\n } catch {\n /* a throwing socket must not break the relay loop */\n }\n }\n return sent;\n }\n\n private broadcastClientPresence(conn: Connection, data: unknown): void {\n this.presenceConnections.add(conn.id);\n const message = JSON.stringify({ from: conn.id, op: { kind: 'presence', data } });\n this.relayToRoom(conn.room, conn.id, message);\n this.safePublish(\n JSON.stringify({\n o: this.instanceId,\n room: conn.room,\n from: conn.id,\n op: { kind: 'presence', data },\n }),\n );\n }\n\n private schedulePresence(conn: Connection, data: unknown): void {\n if (this.presenceThrottleMs <= 0) {\n this.broadcastClientPresence(conn, data);\n return;\n }\n const now = Date.now();\n const lastSentAt = this.lastPresenceAt.get(conn.id);\n if (lastSentAt === undefined || now - lastSentAt >= this.presenceThrottleMs) {\n this.lastPresenceAt.set(conn.id, now);\n this.broadcastClientPresence(conn, data);\n return;\n }\n\n const existing = this.pendingPresence.get(conn.id);\n if (existing) {\n existing.data = data;\n return;\n }\n const timer = setTimeout(\n () => {\n const pending = this.pendingPresence.get(conn.id);\n this.pendingPresence.delete(conn.id);\n if (!pending || !this.conns.has(conn.id)) return;\n this.lastPresenceAt.set(conn.id, Date.now());\n this.broadcastClientPresence(conn, pending.data);\n },\n this.presenceThrottleMs - (now - lastSentAt),\n );\n this.pendingPresence.set(conn.id, { data, timer });\n }\n\n private broadcastLeave(room: string, from: string): void {\n const message = JSON.stringify({ from, op: { kind: 'presence-leave' } });\n this.relayToRoom(room, undefined, message);\n this.safePublish(\n JSON.stringify({ o: this.instanceId, room, from, op: { kind: 'presence-leave' } }),\n );\n }\n\n private deliverToRoom(\n room: string,\n excludeId: string | undefined,\n from: string,\n op: SyncOp,\n prevAudience: string | undefined,\n prevExisted: boolean,\n ): void {\n const members = this.rooms.get(room);\n if (!members) return;\n const send = (conn: Connection, msg: string): void => {\n try {\n conn.send(msg);\n } catch {\n /* a throwing socket must not break the delivery loop */\n }\n };\n if (op.kind === 'upsert') {\n const audience = (op.element as OwnedElement).audience;\n const upsertMsg = JSON.stringify({ from, op });\n const removeMsg = JSON.stringify({\n from: HUB_FROM,\n op: { kind: 'remove', id: op.element.id },\n });\n for (const cid of members) {\n if (cid === excludeId) continue;\n const conn = this.conns.get(cid);\n if (!conn) continue;\n if (this.mayRead(conn, audience)) send(conn, upsertMsg);\n else if (prevExisted && this.mayRead(conn, prevAudience)) send(conn, removeMsg);\n }\n } else if (op.kind === 'remove') {\n const removeMsg = JSON.stringify({ from, op });\n for (const cid of members) {\n if (cid === excludeId) continue;\n const conn = this.conns.get(cid);\n if (!conn) continue;\n // No read filter → forward to all (today's behavior; current/prevExisted aren't fetched without\n // a hook). With canRead, only recipients who could see the removed element get it.\n const wasVisible = !this.canRead || (prevExisted && this.mayRead(conn, prevAudience));\n if (wasVisible) send(conn, removeMsg);\n }\n } else if (op.kind === 'clear') {\n const clearMsg = JSON.stringify({ from, op });\n for (const cid of members) {\n if (cid === excludeId) continue;\n const conn = this.conns.get(cid);\n if (conn) send(conn, clearMsg);\n }\n }\n }\n\n private async sendCorrection(\n conn: Connection,\n from: string,\n op: SyncOp,\n current: OwnedElement | undefined,\n ): Promise<void> {\n let correction: SyncOp | undefined;\n if (op.kind === 'upsert') {\n correction = current\n ? this.mayRead(conn, current.audience)\n ? { kind: 'upsert', element: current }\n : { kind: 'remove', id: current.id }\n : { kind: 'remove', id: op.element.id };\n } else if (op.kind === 'remove') {\n correction = current\n ? this.mayRead(conn, current.audience)\n ? { kind: 'upsert', element: current }\n : { kind: 'remove', id: current.id }\n : undefined;\n } else if (op.kind === 'clear') {\n const all = (await this.backend.snapshot(conn.room)) as OwnedElement[];\n const elements = this.canRead ? all.filter((el) => this.mayRead(conn, el.audience)) : all;\n correction = { kind: 'snapshot', to: from, elements };\n }\n if (correction) conn.send(JSON.stringify({ from: HUB_FROM, op: correction }));\n }\n\n private onFanout(payload: string): void {\n // Off the serial queue on purpose: forward-only (the origin already applied to the SHARED backend),\n // and delivery is already ordered. Re-filter per local member (canRead runs on EVERY instance).\n let env: {\n o?: unknown;\n room?: unknown;\n from?: unknown;\n op?: unknown;\n prev?: unknown;\n existed?: unknown;\n };\n try {\n env = JSON.parse(payload);\n } catch {\n return;\n }\n if (typeof env.o !== 'string' || typeof env.room !== 'string' || typeof env.from !== 'string')\n return;\n if (env.o === this.instanceId) return; // our own publish — already delivered locally\n const op = env.op;\n if (isPresenceOp(op)) {\n // presence/leave: raw forward to all local members (the sender lives on the origin instance),\n // no backend, no canRead filter.\n this.relayToRoom(env.room, undefined, JSON.stringify({ from: env.from, op }));\n return;\n }\n if (!isFanoutOp(op)) return;\n const prevAudience = typeof env.prev === 'string' ? env.prev : undefined;\n const prevExisted = env.existed === true;\n this.deliverToRoom(env.room, undefined, env.from, op, prevAudience, prevExisted);\n }\n\n close(): void {\n for (const pending of this.pendingPresence.values()) clearTimeout(pending.timer);\n this.pendingPresence.clear();\n this.fanoutUnsub();\n }\n}\n","import { applyOpToMap, type SyncOp } from '@fieldnotes/sync';\r\nimport type { CanvasElement } from '@fieldnotes/core';\r\nimport type { HubBackend } from './hub-backend';\r\n\r\nexport class MemoryHubBackend implements HubBackend {\r\n private rooms = new Map<string, Map<string, CanvasElement>>();\r\n\r\n private room(id: string): Map<string, CanvasElement> {\r\n let r = this.rooms.get(id);\r\n if (!r) {\r\n r = new Map();\r\n this.rooms.set(id, r);\r\n }\r\n return r;\r\n }\r\n\r\n async snapshot(room: string): Promise<CanvasElement[]> {\r\n return [...this.room(room).values()];\r\n }\r\n\r\n async get(room: string, id: string): Promise<CanvasElement | undefined> {\r\n return this.room(room).get(id);\r\n }\r\n\r\n async apply(room: string, op: SyncOp): Promise<void> {\r\n if (op.kind === 'clear') {\r\n this.rooms.delete(room);\r\n return;\r\n }\r\n applyOpToMap(this.room(room), op);\r\n }\r\n}\r\n","export interface HubFanout {\r\n publish(payload: string): void | Promise<void>;\r\n subscribe(handler: (payload: string) => void): () => void;\r\n close?(): void;\r\n}\r\n\r\nexport class InMemoryHubFanout implements HubFanout {\r\n private readonly handlers = new Set<(payload: string) => void>();\r\n\r\n publish(payload: string): void {\r\n for (const h of this.handlers) {\r\n try {\r\n h(payload);\r\n } catch {\r\n /* one throwing subscriber must not break the publish loop */\r\n }\r\n }\r\n }\r\n\r\n subscribe(handler: (payload: string) => void): () => void {\r\n this.handlers.add(handler);\r\n return () => this.handlers.delete(handler);\r\n }\r\n}\r\n","export const DEFAULT_MAX_MESSAGE_BYTES = 1024 * 1024;\r\nexport const DEFAULT_MAX_JSON_DEPTH = 32;\r\nexport const DEFAULT_MAX_PENDING_AUTH_MESSAGES = 100;\r\nexport const DEFAULT_MAX_PENDING_AUTH_BYTES = 2 * 1024 * 1024;\r\nexport const DEFAULT_MESSAGES_PER_SECOND = 120;\r\nexport const DEFAULT_MESSAGE_BURST = 240;\r\nexport const DEFAULT_PRESENCE_THROTTLE_MS = 50;\r\n\r\nexport function hasJsonDepthAtMost(message: string, maxDepth: number): boolean {\r\n let depth = 0;\r\n let inString = false;\r\n let escaped = false;\r\n\r\n for (const char of message) {\r\n if (inString) {\r\n if (escaped) escaped = false;\r\n else if (char === '\\\\') escaped = true;\r\n else if (char === '\"') inString = false;\r\n continue;\r\n }\r\n if (char === '\"') inString = true;\r\n else if (char === '{' || char === '[') {\r\n depth += 1;\r\n if (depth > maxDepth) return false;\r\n } else if (char === '}' || char === ']') {\r\n depth -= 1;\r\n if (depth < 0) return false;\r\n }\r\n }\r\n\r\n return depth === 0 && !inString;\r\n}\r\n\r\nexport class MessageRateLimiter {\r\n private tokens: number;\r\n private updatedAt: number;\r\n\r\n constructor(\r\n private readonly ratePerSecond: number,\r\n private readonly burst: number,\r\n now = Date.now(),\r\n ) {\r\n this.tokens = burst;\r\n this.updatedAt = now;\r\n }\r\n\r\n take(now = Date.now()): boolean {\r\n const elapsedSeconds = Math.max(0, now - this.updatedAt) / 1000;\r\n this.tokens = Math.min(this.burst, this.tokens + elapsedSeconds * this.ratePerSecond);\r\n this.updatedAt = now;\r\n if (this.tokens < 1) return false;\r\n this.tokens -= 1;\r\n return true;\r\n }\r\n}\r\n","import { WebSocketServer, type RawData } from 'ws';\r\nimport type { Server } from 'http';\r\nimport { SyncHub } from './sync-hub';\r\nimport type { HubBackend } from './hub-backend';\r\nimport type { HubFanout } from './hub-fanout';\r\nimport type { Authenticate } from './authenticate';\r\nimport type { Authorize, CanRead } from './authorize';\r\nimport { startHeartbeat } from './heartbeat';\r\nimport {\r\n DEFAULT_MAX_JSON_DEPTH,\r\n DEFAULT_MAX_MESSAGE_BYTES,\r\n DEFAULT_MAX_PENDING_AUTH_BYTES,\r\n DEFAULT_MAX_PENDING_AUTH_MESSAGES,\r\n DEFAULT_MESSAGE_BURST,\r\n DEFAULT_MESSAGES_PER_SECOND,\r\n DEFAULT_PRESENCE_THROTTLE_MS,\r\n MessageRateLimiter,\r\n} from './resource-limits';\r\nimport { DEFAULT_SHUTDOWN_GRACE_MS, drainWebSocketServer } from './shutdown';\r\n\r\nexport interface CreateSyncServerOptions {\r\n port?: number;\r\n server?: Server;\r\n backend?: HubBackend;\r\n fanout?: HubFanout;\r\n instanceId?: string;\r\n authenticate?: Authenticate;\r\n authorize?: Authorize;\r\n canRead?: CanRead;\r\n heartbeatIntervalMs?: number;\r\n maxMessageBytes?: number;\r\n maxJsonDepth?: number;\r\n maxPendingAuthMessages?: number;\r\n maxPendingAuthBytes?: number;\r\n messagesPerSecond?: number;\r\n messageBurst?: number;\r\n presenceThrottleMs?: number;\r\n shutdownGraceMs?: number;\r\n}\r\n\r\nfunction rawDataByteLength(data: RawData): number {\r\n if (Array.isArray(data)) return data.reduce((total, chunk) => total + chunk.byteLength, 0);\r\n return data.byteLength;\r\n}\r\n\r\nexport function createSyncServer(options: CreateSyncServerOptions = {}): {\r\n hub: SyncHub;\r\n wss: WebSocketServer;\r\n close: () => Promise<void>;\r\n} {\r\n const shutdownGraceMs = options.shutdownGraceMs ?? DEFAULT_SHUTDOWN_GRACE_MS;\r\n if (!Number.isFinite(shutdownGraceMs) || shutdownGraceMs < 0) {\r\n throw new RangeError('shutdownGraceMs must be a non-negative finite number');\r\n }\r\n const hub = new SyncHub({\r\n backend: options.backend,\r\n fanout: options.fanout,\r\n instanceId: options.instanceId,\r\n authorize: options.authorize,\r\n canRead: options.canRead,\r\n maxJsonDepth: options.maxJsonDepth ?? DEFAULT_MAX_JSON_DEPTH,\r\n presenceThrottleMs: options.presenceThrottleMs ?? DEFAULT_PRESENCE_THROTTLE_MS,\r\n });\r\n const maxMessageBytes = options.maxMessageBytes ?? DEFAULT_MAX_MESSAGE_BYTES;\r\n const wss = options.server\r\n ? new WebSocketServer({ server: options.server, maxPayload: maxMessageBytes })\r\n : new WebSocketServer({ port: options.port ?? 0, maxPayload: maxMessageBytes });\r\n const heartbeat = startHeartbeat(wss, options.heartbeatIntervalMs ?? 30000);\r\n let shuttingDown = false;\r\n let closePromise: Promise<void> | undefined;\r\n let counter = 0;\r\n wss.on('connection', (ws, req) => {\r\n if (shuttingDown) {\r\n ws.close(1001, 'server shutting down');\r\n return;\r\n }\r\n heartbeat.track(ws);\r\n // Protocol/parser failures (including maxPayload) close the peer. Consuming the socket-level\r\n // error keeps hostile input from becoming an uncaught process error.\r\n ws.on('error', () => undefined);\r\n const url = new URL(req.url ?? '', 'http://localhost');\r\n const room = url.searchParams.get('room');\r\n if (!room) {\r\n ws.close(4400, 'room required');\r\n return;\r\n }\r\n const connId = `c${++counter}-${Math.random().toString(36).slice(2, 8)}`;\r\n\r\n let state: 'pending' | 'ready' | 'rejected' = 'pending';\r\n let closed = false;\r\n let admitted = false;\r\n const queue: string[] = [];\r\n let queuedBytes = 0;\r\n const maxPendingAuthMessages =\r\n options.maxPendingAuthMessages ?? DEFAULT_MAX_PENDING_AUTH_MESSAGES;\r\n const maxPendingAuthBytes = options.maxPendingAuthBytes ?? DEFAULT_MAX_PENDING_AUTH_BYTES;\r\n const limiter = new MessageRateLimiter(\r\n options.messagesPerSecond ?? DEFAULT_MESSAGES_PER_SECOND,\r\n options.messageBurst ?? DEFAULT_MESSAGE_BURST,\r\n );\r\n\r\n const send = (m: string) => {\r\n try {\r\n ws.send(m);\r\n } catch {\r\n /* socket closed mid-send */\r\n }\r\n };\r\n\r\n ws.on('message', (data) => {\r\n if (state === 'rejected') return;\r\n const messageBytes = rawDataByteLength(data);\r\n if (messageBytes > maxMessageBytes) {\r\n state = 'rejected';\r\n ws.close(1009, 'message too large');\r\n return;\r\n }\r\n if (!limiter.take()) {\r\n state = 'rejected';\r\n ws.close(4408, 'rate limit exceeded');\r\n return;\r\n }\r\n const msg = String(data);\r\n if (state === 'pending') {\r\n if (\r\n queue.length >= maxPendingAuthMessages ||\r\n queuedBytes + messageBytes > maxPendingAuthBytes\r\n ) {\r\n state = 'rejected';\r\n ws.close(4408, 'authentication queue limit exceeded');\r\n return;\r\n }\r\n queue.push(msg);\r\n queuedBytes += messageBytes;\r\n return;\r\n }\r\n void hub.handleMessage(connId, msg).catch((err) => console.error('[sync-server]', err));\r\n });\r\n ws.on('close', () => {\r\n closed = true;\r\n if (admitted) hub.removeConnection(connId);\r\n });\r\n\r\n Promise.resolve(options.authenticate ? options.authenticate({ req, room }) : { userId: connId })\r\n .then((result) => {\r\n if (closed || state === 'rejected' || shuttingDown) return;\r\n if (!result) {\r\n state = 'rejected';\r\n ws.close(4401, 'unauthorized');\r\n return;\r\n }\r\n state = 'ready';\r\n admitted = true;\r\n hub.addConnection({ id: connId, room, userId: result.userId, role: result.role, send });\r\n for (const m of queue) {\r\n void hub.handleMessage(connId, m).catch((err) => console.error('[sync-server]', err));\r\n }\r\n queue.length = 0;\r\n queuedBytes = 0;\r\n })\r\n .catch(() => {\r\n if (!closed && !shuttingDown) {\r\n state = 'rejected';\r\n ws.close(4401, 'unauthorized');\r\n }\r\n });\r\n });\r\n const close = (): Promise<void> => {\r\n if (closePromise) return closePromise;\r\n shuttingDown = true;\r\n closePromise = drainWebSocketServer(wss, shutdownGraceMs).then(() => {\r\n heartbeat.stop();\r\n hub.close();\r\n });\r\n return closePromise;\r\n };\r\n\r\n return {\r\n hub,\r\n wss,\r\n close,\r\n };\r\n}\r\n","export interface HeartbeatSocket {\r\n ping(): void;\r\n terminate(): void;\r\n on(event: 'pong', listener: () => void): void;\r\n}\r\nexport interface HeartbeatServer {\r\n clients: Set<HeartbeatSocket>;\r\n}\r\nexport interface Heartbeat {\r\n track(ws: HeartbeatSocket): void;\r\n stop(): void;\r\n}\r\n\r\nexport function startHeartbeat(wss: HeartbeatServer, intervalMs: number): Heartbeat {\r\n if (intervalMs <= 0) {\r\n return {\r\n track: () => {\r\n /* disabled */\r\n },\r\n stop: () => {\r\n /* disabled */\r\n },\r\n };\r\n }\r\n const alive = new WeakMap<HeartbeatSocket, boolean>();\r\n const track = (ws: HeartbeatSocket): void => {\r\n alive.set(ws, true);\r\n ws.on('pong', () => alive.set(ws, true));\r\n };\r\n const interval = setInterval(() => {\r\n for (const ws of wss.clients) {\r\n try {\r\n if (alive.get(ws) === false) {\r\n ws.terminate();\r\n continue;\r\n }\r\n alive.set(ws, false);\r\n ws.ping();\r\n } catch {\r\n /* socket dying mid-tick — skip it */\r\n }\r\n }\r\n }, intervalMs);\r\n return { track, stop: () => clearInterval(interval) };\r\n}\r\n","export const DEFAULT_SHUTDOWN_GRACE_MS = 5000;\r\n\r\nexport interface ShutdownSocket {\r\n close(code?: number, reason?: string): void;\r\n terminate(): void;\r\n}\r\n\r\nexport interface ShutdownServer {\r\n clients: Set<ShutdownSocket>;\r\n close(callback: (error?: Error) => void): void;\r\n}\r\n\r\n/** Stop admission, ask connected peers to leave, then forcefully reap stragglers. */\r\nexport function drainWebSocketServer(wss: ShutdownServer, graceMs: number): Promise<void> {\r\n return new Promise((resolve) => {\r\n let settled = false;\r\n const finish = (): void => {\r\n if (settled) return;\r\n settled = true;\r\n clearTimeout(forceTimer);\r\n resolve();\r\n };\r\n const forceTimer = setTimeout(() => {\r\n for (const ws of wss.clients) {\r\n try {\r\n ws.terminate();\r\n } catch {\r\n /* socket already failed while shutdown was in progress */\r\n }\r\n }\r\n finish();\r\n }, graceMs);\r\n\r\n // Calling close first prevents new connections. Its callback runs once all clients have left.\r\n wss.close(() => finish());\r\n for (const ws of wss.clients) {\r\n try {\r\n ws.close(1001, 'server shutting down');\r\n } catch {\r\n try {\r\n ws.terminate();\r\n } catch {\r\n /* socket already closed */\r\n }\r\n }\r\n }\r\n });\r\n}\r\n"],"mappings":"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","names":["import_sync"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/sync-hub.ts","../src/memory-hub-backend.ts","../src/hub-fanout.ts","../src/resource-limits.ts","../src/create-sync-server.ts","../src/heartbeat.ts","../src/shutdown.ts"],"sourcesContent":["export { SyncHub } from './sync-hub';\r\nexport type { SyncHubOptions, Connection } from './sync-hub';\r\nexport { MemoryHubBackend } from './memory-hub-backend';\r\nexport type { HubBackend } from './hub-backend';\r\nexport { createSyncServer } from './create-sync-server';\r\nexport type { CreateSyncServerOptions } from './create-sync-server';\r\nexport { InMemoryHubFanout } from './hub-fanout';\r\nexport type { HubFanout } from './hub-fanout';\r\nexport type { AuthInfo, AuthResult, Authenticate } from './authenticate';\r\nexport type {\r\n Authorize,\r\n AuthorizeContext,\r\n AuthorizeLayer,\r\n AuthorizeLayerContext,\r\n OwnedElement,\r\n ReadContext,\r\n CanRead,\r\n} from './authorize';\r\nexport { startHeartbeat } from './heartbeat';\r\nexport type { Heartbeat, HeartbeatSocket, HeartbeatServer } from './heartbeat';\r\n","import {\n parseEnvelope,\n isValidElement,\n isNewerLayerRecord,\n type LayerRecord,\n type SyncOp,\n type SyncEnvelope,\n} from '@fieldnotes/sync';\nimport { MemoryHubBackend } from './memory-hub-backend';\nimport { InMemoryHubFanout, type HubFanout } from './hub-fanout';\nimport type { HubBackend } from './hub-backend';\nimport type { Authorize, AuthorizeLayer, CanRead, OwnedElement } from './authorize';\nimport {\n DEFAULT_MAX_JSON_DEPTH,\n DEFAULT_MAX_PRESENCE_LANES,\n DEFAULT_PRESENCE_THROTTLE_MS,\n hasJsonDepthAtMost,\n} from './resource-limits';\n\nexport interface Connection {\n id: string;\n room: string;\n userId?: string;\n role?: string;\n send(message: string): void;\n}\n\nexport interface SyncHubOptions {\n backend?: HubBackend;\n fanout?: HubFanout;\n instanceId?: string;\n authorize?: Authorize;\n authorizeLayer?: AuthorizeLayer;\n canRead?: CanRead;\n maxJsonDepth?: number;\n presenceThrottleMs?: number;\n maxPresenceLanes?: number;\n}\n\nconst HUB_FROM = 'hub';\n\nfunction generateInstanceId(): string {\n if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function')\n return crypto.randomUUID();\n return `i-${Math.random().toString(36).slice(2)}`;\n}\n\nfunction isFanoutOp(op: unknown): op is Extract<SyncOp, { kind: 'upsert' | 'remove' | 'clear' }> {\n if (typeof op !== 'object' || op === null) return false;\n const o = op as { kind?: unknown; element?: unknown; id?: unknown };\n if (o.kind === 'upsert') return isValidElement(o.element);\n if (o.kind === 'remove') return typeof o.id === 'string';\n return o.kind === 'clear';\n}\n\ntype LayerOp = Extract<SyncOp, { kind: 'layer-upsert' | 'layer-remove' }>;\n\ninterface PresenceLane {\n lastSentAt: number | undefined;\n pending: { data: unknown; timer: ReturnType<typeof setTimeout> } | null;\n}\n\nconst FALLBACK_PRESENCE_LANE = '';\nconst MAX_PRESENCE_LANE_LENGTH = 64;\n\nfunction presenceLaneOf(data: unknown): string {\n if (typeof data !== 'object' || data === null) return FALLBACK_PRESENCE_LANE;\n const kind = (data as { kind?: unknown }).kind;\n if (typeof kind !== 'string' || kind.length === 0 || kind.length > MAX_PRESENCE_LANE_LENGTH) {\n return FALLBACK_PRESENCE_LANE;\n }\n return kind;\n}\n\nfunction isLayerOp(op: unknown): op is LayerOp {\n if (typeof op !== 'object' || op === null) return false;\n const k = (op as { kind?: unknown }).kind;\n // Shape is re-validated by parseEnvelope on the serial path; fanout payloads\n // come from a sibling hub that already validated them.\n return k === 'layer-upsert' || k === 'layer-remove';\n}\n\nfunction layerOpToRecord(op: LayerOp): LayerRecord {\n return op.kind === 'layer-upsert'\n ? { id: op.layer.id, version: op.version, editor: op.editor, definition: op.layer }\n : { id: op.id, version: op.version, editor: op.editor };\n}\n\nfunction layerRecordToOp(record: LayerRecord): LayerOp {\n return record.definition\n ? {\n kind: 'layer-upsert',\n layer: record.definition,\n version: record.version,\n editor: record.editor,\n }\n : { kind: 'layer-remove', id: record.id, version: record.version, editor: record.editor };\n}\n\nfunction isPresenceOp(\n op: unknown,\n): op is { kind: 'presence'; data: unknown } | { kind: 'presence-leave' } {\n if (typeof op !== 'object' || op === null) return false;\n const k = (op as { kind?: unknown }).kind;\n return k === 'presence' || k === 'presence-leave';\n}\n\nexport class SyncHub {\n private readonly backend: HubBackend;\n private readonly conns = new Map<string, Connection>();\n private readonly rooms = new Map<string, Set<string>>(); // room → connIds\n private readonly roomQueues = new Map<string, Promise<void>>(); // room → serial tail\n private readonly presenceConnections = new Set<string>();\n private readonly instanceId: string;\n private readonly fanout: HubFanout;\n private readonly fanoutUnsub: () => void;\n private readonly authorize?: Authorize;\n private readonly authorizeLayer?: AuthorizeLayer;\n private readonly canRead?: CanRead;\n /** Fallback layer-record store for backends without layer persistence. */\n private readonly memoryLayers = new Map<string, Map<string, LayerRecord>>();\n private readonly maxJsonDepth: number;\n private readonly presenceThrottleMs: number;\n private readonly maxPresenceLanes: number;\n /**\n * Presence throttle state keyed by connection, then by lane. A lane is the\n * payload's `kind` (a non-empty string of at most 64 chars) or the reserved\n * fallback lane `''`, so a rapid stream of one kind (awareness cursors) can\n * never replace a pending frame of another kind (a ping, a path `cleared`).\n * Within a lane the newest payload wins. The lane count per connection is\n * capped by `maxPresenceLanes`, counting the fallback lane, so a client\n * cannot mint timers by varying `kind`.\n */\n private readonly presenceLanes = new Map<string, Map<string, PresenceLane>>();\n\n constructor(options: SyncHubOptions = {}) {\n this.backend = options.backend ?? new MemoryHubBackend();\n this.instanceId = options.instanceId ?? generateInstanceId();\n this.fanout = options.fanout ?? new InMemoryHubFanout();\n this.authorize = options.authorize;\n this.authorizeLayer = options.authorizeLayer;\n this.canRead = options.canRead;\n this.maxJsonDepth = options.maxJsonDepth ?? DEFAULT_MAX_JSON_DEPTH;\n this.presenceThrottleMs = options.presenceThrottleMs ?? DEFAULT_PRESENCE_THROTTLE_MS;\n const maxPresenceLanes = options.maxPresenceLanes ?? DEFAULT_MAX_PRESENCE_LANES;\n if (!Number.isFinite(maxPresenceLanes) || maxPresenceLanes < 1) {\n throw new RangeError('maxPresenceLanes must be a finite number of at least 1');\n }\n this.maxPresenceLanes = Math.floor(maxPresenceLanes);\n this.fanoutUnsub = this.fanout.subscribe((payload) => this.onFanout(payload));\n }\n\n addConnection(conn: Connection): void {\n this.conns.set(conn.id, conn);\n let set = this.rooms.get(conn.room);\n if (!set) {\n set = new Set();\n this.rooms.set(conn.room, set);\n }\n set.add(conn.id);\n }\n\n removeConnection(connId: string): void {\n const conn = this.conns.get(connId);\n if (!conn) return;\n this.conns.delete(connId);\n const room = conn.room;\n const hadPresence = this.presenceConnections.delete(connId);\n this.clearPresenceLanes(connId);\n const members = this.rooms.get(room);\n if (members) {\n members.delete(connId);\n if (members.size === 0) {\n this.rooms.delete(room);\n this.roomQueues.delete(room);\n }\n }\n if (hadPresence) this.broadcastLeave(room, conn.id);\n }\n\n roomCount(): number {\n return this.rooms.size;\n }\n\n /**\n * Broadcasts ephemeral server-owned presence data to every connection in a room.\n * The returned count covers successful delivery on this hub instance only; configured fan-out\n * forwards the same event to other instances on a best-effort basis.\n */\n broadcastPresence<T>(room: string, data: T): number {\n const op = { kind: 'presence' as const, data };\n const sent = this.relayToRoom(room, undefined, JSON.stringify({ from: HUB_FROM, op }));\n this.safePublish(JSON.stringify({ o: this.instanceId, room, from: HUB_FROM, op }));\n return sent;\n }\n\n handleMessage(connId: string, message: string): Promise<void> {\n const conn = this.conns.get(connId);\n if (!conn) return Promise.resolve();\n if (!hasJsonDepthAtMost(message, this.maxJsonDepth)) return Promise.resolve();\n const env = parseEnvelope(message);\n if (!env) return Promise.resolve();\n if (env.op.kind === 'presence') {\n this.schedulePresence(conn, env.op.data); // off-queue, throttled independently\n return Promise.resolve();\n }\n const room = conn.room;\n // The per-room serial queue is the single total-order authority: ops apply in arrival order\n // (arrival-order LWW — no per-element seq; see D3 / TD-12). Different rooms run independently.\n const prev = this.roomQueues.get(room) ?? Promise.resolve();\n const operation = prev.then(() => this.process(conn, env));\n this.roomQueues.set(\n room,\n operation.catch(() => {\n // Recover only the internal tail so one failed message never wedges the room queue.\n // The caller still receives the operation rejection for observability.\n }),\n );\n return operation;\n }\n\n private async process(conn: Connection, env: SyncEnvelope): Promise<void> {\n const op = env.op;\n if (op.kind === 'request-snapshot') {\n const all = (await this.backend.snapshot(conn.room)) as OwnedElement[];\n const elements = this.canRead ? all.filter((el) => this.mayRead(conn, el.audience)) : all;\n // Layer records are presentation-only and carry no element bytes, so no\n // audience filter applies; the field is omitted while a room has never\n // used layer sync, keeping snapshot frames byte-identical to before.\n const layers = await this.getLayerRecords(conn.room);\n conn.send(\n // `to` is a private correlation address for the requesting SyncClient. It is never\n // broadcast; every public sender identity below comes from the server-owned connection.\n JSON.stringify({\n from: HUB_FROM,\n op:\n layers.length > 0\n ? { kind: 'snapshot', to: env.from, elements, layers }\n : { kind: 'snapshot', to: env.from, elements },\n }),\n );\n } else if (op.kind === 'layer-upsert' || op.kind === 'layer-remove') {\n await this.processLayerOp(conn, op);\n } else if (op.kind === 'upsert' || op.kind === 'remove' || op.kind === 'clear') {\n const id = op.kind === 'upsert' ? op.element.id : op.kind === 'remove' ? op.id : undefined;\n const needCurrent = (this.authorize || this.canRead) && id !== undefined;\n const current: OwnedElement | undefined = needCurrent\n ? await this.backend.get(conn.room, id)\n : undefined;\n\n let outboundOp: SyncOp = op;\n if (this.authorize) {\n const allowed = await this.authorize({\n userId: conn.userId,\n role: conn.role,\n room: conn.room,\n op,\n currentElement: current,\n });\n if (!allowed) {\n await this.sendCorrection(conn, env.from, op, current);\n return;\n }\n if (op.kind === 'upsert') {\n const ownerId = current?.ownerId ?? conn.userId;\n const stampedElement: OwnedElement = { ...op.element, ownerId };\n outboundOp = { kind: 'upsert', element: stampedElement };\n }\n }\n\n await this.backend.apply(conn.room, outboundOp);\n\n const prevExisted = current !== undefined;\n const prevAudience = current?.audience;\n\n await this.fanout.publish(\n JSON.stringify({\n o: this.instanceId,\n room: conn.room,\n from: conn.id,\n op: outboundOp,\n prev: prevAudience,\n existed: prevExisted,\n }),\n );\n\n this.deliverToRoom(conn.room, conn.id, conn.id, outboundOp, prevAudience, prevExisted);\n }\n // 'snapshot' from a client → ignored\n }\n\n /**\n * Applies a layer-definition edit on the room's serial queue. Convergence is\n * last-writer-wins under the deterministic (version, editor) ordering — the\n * same rule every client applies — so arrival order never decides a race. A\n * stale or denied edit is answered with an authoritative correction to the\n * sender only.\n */\n private async processLayerOp(conn: Connection, op: LayerOp): Promise<void> {\n const record = layerOpToRecord(op);\n const current = await this.getLayerRecord(conn.room, record.id);\n if (this.authorizeLayer) {\n const allowed = await this.authorizeLayer({\n userId: conn.userId,\n role: conn.role,\n room: conn.room,\n op,\n currentRecord: current,\n });\n if (!allowed) {\n // Revert the sender to the room's record; a tombstone when there is\n // none, so the denied local edit disappears everywhere consistently.\n const correction = current ?? { id: record.id, version: record.version, editor: HUB_FROM };\n conn.send(JSON.stringify({ from: HUB_FROM, op: layerRecordToOp(correction) }));\n return;\n }\n }\n if (current && !isNewerLayerRecord(record, current)) {\n // Stale under (version, editor): converge the sender, do not broadcast.\n conn.send(JSON.stringify({ from: HUB_FROM, op: layerRecordToOp(current) }));\n return;\n }\n await this.applyLayerRecord(conn.room, record);\n await this.fanout.publish(\n JSON.stringify({ o: this.instanceId, room: conn.room, from: conn.id, op }),\n );\n this.relayToRoom(conn.room, conn.id, JSON.stringify({ from: conn.id, op }));\n }\n\n private layerBackend(): Required<\n Pick<HubBackend, 'layerRecords' | 'getLayerRecord' | 'applyLayerRecord'>\n > | null {\n const { layerRecords, getLayerRecord, applyLayerRecord } = this.backend;\n if (!layerRecords || !getLayerRecord || !applyLayerRecord) return null;\n return {\n layerRecords: layerRecords.bind(this.backend),\n getLayerRecord: getLayerRecord.bind(this.backend),\n applyLayerRecord: applyLayerRecord.bind(this.backend),\n };\n }\n\n private async getLayerRecords(room: string): Promise<LayerRecord[]> {\n const backend = this.layerBackend();\n if (backend) return backend.layerRecords(room);\n return [...(this.memoryLayers.get(room)?.values() ?? [])];\n }\n\n private async getLayerRecord(room: string, id: string): Promise<LayerRecord | undefined> {\n const backend = this.layerBackend();\n if (backend) return backend.getLayerRecord(room, id);\n return this.memoryLayers.get(room)?.get(id);\n }\n\n private async applyLayerRecord(room: string, record: LayerRecord): Promise<void> {\n const backend = this.layerBackend();\n if (backend) {\n await backend.applyLayerRecord(room, record);\n return;\n }\n let map = this.memoryLayers.get(room);\n if (!map) {\n map = new Map();\n this.memoryLayers.set(room, map);\n }\n map.set(record.id, record);\n }\n\n private mayRead(conn: Connection, audience: string | undefined): boolean {\n if (!this.canRead) return true;\n return this.canRead({ userId: conn.userId, role: conn.role, room: conn.room, audience });\n }\n\n private safePublish(payload: string): void {\n try {\n void Promise.resolve(this.fanout.publish(payload)).catch(() => {\n /* presence is ephemeral; a broken publisher must not create an unhandled rejection */\n });\n } catch {\n /* a broken fanout publisher must not break the un-queued presence relay */\n }\n }\n\n private relayToRoom(room: string, excludeId: string | undefined, message: string): number {\n const members = this.rooms.get(room);\n if (!members) return 0;\n let sent = 0;\n for (const cid of members) {\n if (cid === excludeId) continue;\n const conn = this.conns.get(cid);\n if (!conn) continue;\n try {\n conn.send(message);\n sent += 1;\n } catch {\n /* a throwing socket must not break the relay loop */\n }\n }\n return sent;\n }\n\n private broadcastClientPresence(conn: Connection, data: unknown): void {\n this.presenceConnections.add(conn.id);\n const message = JSON.stringify({ from: conn.id, op: { kind: 'presence', data } });\n this.relayToRoom(conn.room, conn.id, message);\n this.safePublish(\n JSON.stringify({\n o: this.instanceId,\n room: conn.room,\n from: conn.id,\n op: { kind: 'presence', data },\n }),\n );\n }\n\n private clearPresenceLanes(connId: string): void {\n const lanes = this.presenceLanes.get(connId);\n if (!lanes) return;\n for (const lane of lanes.values()) {\n if (lane.pending) clearTimeout(lane.pending.timer);\n }\n this.presenceLanes.delete(connId);\n }\n\n private schedulePresence(conn: Connection, data: unknown): void {\n if (this.presenceThrottleMs <= 0) {\n this.broadcastClientPresence(conn, data);\n return;\n }\n const lane = this.presenceLaneFor(conn.id, presenceLaneOf(data));\n const now = Date.now();\n if (lane.lastSentAt === undefined || now - lane.lastSentAt >= this.presenceThrottleMs) {\n if (lane.pending) {\n clearTimeout(lane.pending.timer);\n lane.pending = null;\n }\n lane.lastSentAt = now;\n this.broadcastClientPresence(conn, data);\n return;\n }\n if (lane.pending) {\n lane.pending.data = data;\n return;\n }\n const timer = setTimeout(\n () => {\n const pending = lane.pending;\n lane.pending = null;\n if (!pending || !this.conns.has(conn.id)) return;\n lane.lastSentAt = Date.now();\n this.broadcastClientPresence(conn, pending.data);\n },\n this.presenceThrottleMs - (now - lane.lastSentAt),\n );\n lane.pending = { data, timer };\n }\n\n private presenceLaneFor(connId: string, requested: string): PresenceLane {\n let lanes = this.presenceLanes.get(connId);\n if (!lanes) {\n lanes = new Map();\n this.presenceLanes.set(connId, lanes);\n }\n let key = requested;\n if (key !== FALLBACK_PRESENCE_LANE && !lanes.has(key)) {\n const named = lanes.size - (lanes.has(FALLBACK_PRESENCE_LANE) ? 1 : 0);\n if (named >= this.maxPresenceLanes - 1) key = FALLBACK_PRESENCE_LANE;\n }\n let lane = lanes.get(key);\n if (!lane) {\n lane = { lastSentAt: undefined, pending: null };\n lanes.set(key, lane);\n }\n return lane;\n }\n\n private broadcastLeave(room: string, from: string): void {\n const message = JSON.stringify({ from, op: { kind: 'presence-leave' } });\n this.relayToRoom(room, undefined, message);\n this.safePublish(\n JSON.stringify({ o: this.instanceId, room, from, op: { kind: 'presence-leave' } }),\n );\n }\n\n private deliverToRoom(\n room: string,\n excludeId: string | undefined,\n from: string,\n op: SyncOp,\n prevAudience: string | undefined,\n prevExisted: boolean,\n ): void {\n const members = this.rooms.get(room);\n if (!members) return;\n const send = (conn: Connection, msg: string): void => {\n try {\n conn.send(msg);\n } catch {\n /* a throwing socket must not break the delivery loop */\n }\n };\n if (op.kind === 'upsert') {\n const audience = (op.element as OwnedElement).audience;\n const upsertMsg = JSON.stringify({ from, op });\n const removeMsg = JSON.stringify({\n from: HUB_FROM,\n op: { kind: 'remove', id: op.element.id },\n });\n for (const cid of members) {\n if (cid === excludeId) continue;\n const conn = this.conns.get(cid);\n if (!conn) continue;\n if (this.mayRead(conn, audience)) send(conn, upsertMsg);\n else if (prevExisted && this.mayRead(conn, prevAudience)) send(conn, removeMsg);\n }\n } else if (op.kind === 'remove') {\n const removeMsg = JSON.stringify({ from, op });\n for (const cid of members) {\n if (cid === excludeId) continue;\n const conn = this.conns.get(cid);\n if (!conn) continue;\n // No read filter → forward to all (today's behavior; current/prevExisted aren't fetched without\n // a hook). With canRead, only recipients who could see the removed element get it.\n const wasVisible = !this.canRead || (prevExisted && this.mayRead(conn, prevAudience));\n if (wasVisible) send(conn, removeMsg);\n }\n } else if (op.kind === 'clear') {\n const clearMsg = JSON.stringify({ from, op });\n for (const cid of members) {\n if (cid === excludeId) continue;\n const conn = this.conns.get(cid);\n if (conn) send(conn, clearMsg);\n }\n }\n }\n\n private async sendCorrection(\n conn: Connection,\n from: string,\n op: SyncOp,\n current: OwnedElement | undefined,\n ): Promise<void> {\n let correction: SyncOp | undefined;\n if (op.kind === 'upsert') {\n correction = current\n ? this.mayRead(conn, current.audience)\n ? { kind: 'upsert', element: current }\n : { kind: 'remove', id: current.id }\n : { kind: 'remove', id: op.element.id };\n } else if (op.kind === 'remove') {\n correction = current\n ? this.mayRead(conn, current.audience)\n ? { kind: 'upsert', element: current }\n : { kind: 'remove', id: current.id }\n : undefined;\n } else if (op.kind === 'clear') {\n const all = (await this.backend.snapshot(conn.room)) as OwnedElement[];\n const elements = this.canRead ? all.filter((el) => this.mayRead(conn, el.audience)) : all;\n correction = { kind: 'snapshot', to: from, elements };\n }\n if (correction) conn.send(JSON.stringify({ from: HUB_FROM, op: correction }));\n }\n\n private onFanout(payload: string): void {\n // Off the serial queue on purpose: forward-only (the origin already applied to the SHARED backend),\n // and delivery is already ordered. Re-filter per local member (canRead runs on EVERY instance).\n let env: {\n o?: unknown;\n room?: unknown;\n from?: unknown;\n op?: unknown;\n prev?: unknown;\n existed?: unknown;\n };\n try {\n env = JSON.parse(payload);\n } catch {\n return;\n }\n if (typeof env.o !== 'string' || typeof env.room !== 'string' || typeof env.from !== 'string')\n return;\n if (env.o === this.instanceId) return; // our own publish — already delivered locally\n const op = env.op;\n if (isPresenceOp(op)) {\n // presence/leave: raw forward to all local members (the sender lives on the origin instance),\n // no backend, no canRead filter.\n this.relayToRoom(env.room, undefined, JSON.stringify({ from: env.from, op }));\n return;\n }\n if (isLayerOp(op)) {\n // LWW-guarded local apply keeps memory-fallback instances converged; a\n // shared backend already holds the record (the guarded re-apply is a\n // no-op). Relay is unconditional — receiving clients LWW-drop stale ops.\n void this.applyFanoutLayerOp(env.room, op).catch(() => {\n /* a broken backend must not break the fanout relay */\n });\n this.relayToRoom(env.room, undefined, JSON.stringify({ from: env.from, op }));\n return;\n }\n if (!isFanoutOp(op)) return;\n const prevAudience = typeof env.prev === 'string' ? env.prev : undefined;\n const prevExisted = env.existed === true;\n this.deliverToRoom(env.room, undefined, env.from, op, prevAudience, prevExisted);\n }\n\n private async applyFanoutLayerOp(room: string, op: LayerOp): Promise<void> {\n const record = layerOpToRecord(op);\n const current = await this.getLayerRecord(room, record.id);\n if (current && !isNewerLayerRecord(record, current)) return;\n await this.applyLayerRecord(room, record);\n }\n\n close(): void {\n for (const connId of [...this.presenceLanes.keys()]) this.clearPresenceLanes(connId);\n this.fanoutUnsub();\n }\n}\n","import { applyOpToMap, type SyncOp, type LayerRecord } from '@fieldnotes/sync';\r\nimport type { CanvasElement } from '@fieldnotes/core';\r\nimport type { HubBackend } from './hub-backend';\r\n\r\nexport class MemoryHubBackend implements HubBackend {\r\n private rooms = new Map<string, Map<string, CanvasElement>>();\r\n private roomLayers = new Map<string, Map<string, LayerRecord>>();\r\n\r\n private room(id: string): Map<string, CanvasElement> {\r\n let r = this.rooms.get(id);\r\n if (!r) {\r\n r = new Map();\r\n this.rooms.set(id, r);\r\n }\r\n return r;\r\n }\r\n\r\n private layers(room: string): Map<string, LayerRecord> {\r\n let r = this.roomLayers.get(room);\r\n if (!r) {\r\n r = new Map();\r\n this.roomLayers.set(room, r);\r\n }\r\n return r;\r\n }\r\n\r\n async snapshot(room: string): Promise<CanvasElement[]> {\r\n return [...this.room(room).values()];\r\n }\r\n\r\n async get(room: string, id: string): Promise<CanvasElement | undefined> {\r\n return this.room(room).get(id);\r\n }\r\n\r\n async apply(room: string, op: SyncOp): Promise<void> {\r\n if (op.kind === 'clear') {\r\n // Clears elements only; layer records are a separate, longer-lived ledger.\r\n this.rooms.delete(room);\r\n return;\r\n }\r\n applyOpToMap(this.room(room), op);\r\n }\r\n\r\n async layerRecords(room: string): Promise<LayerRecord[]> {\r\n return [...this.layers(room).values()];\r\n }\r\n\r\n async getLayerRecord(room: string, id: string): Promise<LayerRecord | undefined> {\r\n return this.layers(room).get(id);\r\n }\r\n\r\n async applyLayerRecord(room: string, record: LayerRecord): Promise<void> {\r\n this.layers(room).set(record.id, record);\r\n }\r\n}\r\n","export interface HubFanout {\n publish(payload: string): void | Promise<void>;\n subscribe(handler: (payload: string) => void): () => void;\n close?(): void;\n}\n\nexport class InMemoryHubFanout implements HubFanout {\n private readonly handlers = new Set<(payload: string) => void>();\n\n publish(payload: string): void {\n for (const h of this.handlers) {\n try {\n h(payload);\n } catch {\n /* one throwing subscriber must not break the publish loop */\n }\n }\n }\n\n subscribe(handler: (payload: string) => void): () => void {\n this.handlers.add(handler);\n return () => this.handlers.delete(handler);\n }\n}\n","export const DEFAULT_MAX_MESSAGE_BYTES = 1024 * 1024;\nexport const DEFAULT_MAX_JSON_DEPTH = 32;\nexport const DEFAULT_MAX_PENDING_AUTH_MESSAGES = 100;\nexport const DEFAULT_MAX_PENDING_AUTH_BYTES = 2 * 1024 * 1024;\nexport const DEFAULT_MESSAGES_PER_SECOND = 120;\nexport const DEFAULT_MESSAGE_BURST = 240;\nexport const DEFAULT_PRESENCE_THROTTLE_MS = 50;\n/** Per-connection presence throttle lanes, INCLUDING the reserved fallback lane. */\nexport const DEFAULT_MAX_PRESENCE_LANES = 16;\n\nexport function hasJsonDepthAtMost(message: string, maxDepth: number): boolean {\n let depth = 0;\n let inString = false;\n let escaped = false;\n\n for (const char of message) {\n if (inString) {\n if (escaped) escaped = false;\n else if (char === '\\\\') escaped = true;\n else if (char === '\"') inString = false;\n continue;\n }\n if (char === '\"') inString = true;\n else if (char === '{' || char === '[') {\n depth += 1;\n if (depth > maxDepth) return false;\n } else if (char === '}' || char === ']') {\n depth -= 1;\n if (depth < 0) return false;\n }\n }\n\n return depth === 0 && !inString;\n}\n\nexport class MessageRateLimiter {\n private tokens: number;\n private updatedAt: number;\n\n constructor(\n private readonly ratePerSecond: number,\n private readonly burst: number,\n now = Date.now(),\n ) {\n this.tokens = burst;\n this.updatedAt = now;\n }\n\n take(now = Date.now()): boolean {\n const elapsedSeconds = Math.max(0, now - this.updatedAt) / 1000;\n this.tokens = Math.min(this.burst, this.tokens + elapsedSeconds * this.ratePerSecond);\n this.updatedAt = now;\n if (this.tokens < 1) return false;\n this.tokens -= 1;\n return true;\n }\n}\n","import { WebSocketServer, type RawData } from 'ws';\nimport type { Server } from 'http';\nimport { SyncHub } from './sync-hub';\nimport type { HubBackend } from './hub-backend';\nimport type { HubFanout } from './hub-fanout';\nimport type { Authenticate } from './authenticate';\nimport type { Authorize, AuthorizeLayer, CanRead } from './authorize';\nimport { startHeartbeat } from './heartbeat';\nimport {\n DEFAULT_MAX_JSON_DEPTH,\n DEFAULT_MAX_MESSAGE_BYTES,\n DEFAULT_MAX_PENDING_AUTH_BYTES,\n DEFAULT_MAX_PENDING_AUTH_MESSAGES,\n DEFAULT_MESSAGE_BURST,\n DEFAULT_MESSAGES_PER_SECOND,\n DEFAULT_PRESENCE_THROTTLE_MS,\n MessageRateLimiter,\n} from './resource-limits';\nimport { DEFAULT_SHUTDOWN_GRACE_MS, drainWebSocketServer } from './shutdown';\n\nexport interface CreateSyncServerOptions {\n port?: number;\n server?: Server;\n backend?: HubBackend;\n fanout?: HubFanout;\n instanceId?: string;\n authenticate?: Authenticate;\n authorize?: Authorize;\n authorizeLayer?: AuthorizeLayer;\n canRead?: CanRead;\n heartbeatIntervalMs?: number;\n maxMessageBytes?: number;\n maxJsonDepth?: number;\n maxPendingAuthMessages?: number;\n maxPendingAuthBytes?: number;\n messagesPerSecond?: number;\n messageBurst?: number;\n presenceThrottleMs?: number;\n maxPresenceLanes?: number;\n shutdownGraceMs?: number;\n}\n\nfunction rawDataByteLength(data: RawData): number {\n if (Array.isArray(data)) return data.reduce((total, chunk) => total + chunk.byteLength, 0);\n return data.byteLength;\n}\n\nexport function createSyncServer(options: CreateSyncServerOptions = {}): {\n hub: SyncHub;\n wss: WebSocketServer;\n close: () => Promise<void>;\n} {\n const shutdownGraceMs = options.shutdownGraceMs ?? DEFAULT_SHUTDOWN_GRACE_MS;\n if (!Number.isFinite(shutdownGraceMs) || shutdownGraceMs < 0) {\n throw new RangeError('shutdownGraceMs must be a non-negative finite number');\n }\n const hub = new SyncHub({\n backend: options.backend,\n fanout: options.fanout,\n instanceId: options.instanceId,\n authorize: options.authorize,\n authorizeLayer: options.authorizeLayer,\n canRead: options.canRead,\n maxJsonDepth: options.maxJsonDepth ?? DEFAULT_MAX_JSON_DEPTH,\n presenceThrottleMs: options.presenceThrottleMs ?? DEFAULT_PRESENCE_THROTTLE_MS,\n maxPresenceLanes: options.maxPresenceLanes,\n });\n const maxMessageBytes = options.maxMessageBytes ?? DEFAULT_MAX_MESSAGE_BYTES;\n const wss = options.server\n ? new WebSocketServer({ server: options.server, maxPayload: maxMessageBytes })\n : new WebSocketServer({ port: options.port ?? 0, maxPayload: maxMessageBytes });\n const heartbeat = startHeartbeat(wss, options.heartbeatIntervalMs ?? 30000);\n let shuttingDown = false;\n let closePromise: Promise<void> | undefined;\n let counter = 0;\n wss.on('connection', (ws, req) => {\n if (shuttingDown) {\n ws.close(1001, 'server shutting down');\n return;\n }\n heartbeat.track(ws);\n // Protocol/parser failures (including maxPayload) close the peer. Consuming the socket-level\n // error keeps hostile input from becoming an uncaught process error.\n ws.on('error', () => undefined);\n const url = new URL(req.url ?? '', 'http://localhost');\n const room = url.searchParams.get('room');\n if (!room) {\n ws.close(4400, 'room required');\n return;\n }\n const connId = `c${++counter}-${Math.random().toString(36).slice(2, 8)}`;\n\n let state: 'pending' | 'ready' | 'rejected' = 'pending';\n let closed = false;\n let admitted = false;\n const queue: string[] = [];\n let queuedBytes = 0;\n const maxPendingAuthMessages =\n options.maxPendingAuthMessages ?? DEFAULT_MAX_PENDING_AUTH_MESSAGES;\n const maxPendingAuthBytes = options.maxPendingAuthBytes ?? DEFAULT_MAX_PENDING_AUTH_BYTES;\n const limiter = new MessageRateLimiter(\n options.messagesPerSecond ?? DEFAULT_MESSAGES_PER_SECOND,\n options.messageBurst ?? DEFAULT_MESSAGE_BURST,\n );\n\n const send = (m: string) => {\n try {\n ws.send(m);\n } catch {\n /* socket closed mid-send */\n }\n };\n\n ws.on('message', (data) => {\n if (state === 'rejected') return;\n const messageBytes = rawDataByteLength(data);\n if (messageBytes > maxMessageBytes) {\n state = 'rejected';\n ws.close(1009, 'message too large');\n return;\n }\n if (!limiter.take()) {\n state = 'rejected';\n ws.close(4408, 'rate limit exceeded');\n return;\n }\n const msg = String(data);\n if (state === 'pending') {\n if (\n queue.length >= maxPendingAuthMessages ||\n queuedBytes + messageBytes > maxPendingAuthBytes\n ) {\n state = 'rejected';\n ws.close(4408, 'authentication queue limit exceeded');\n return;\n }\n queue.push(msg);\n queuedBytes += messageBytes;\n return;\n }\n void hub.handleMessage(connId, msg).catch((err) => console.error('[sync-server]', err));\n });\n ws.on('close', () => {\n closed = true;\n if (admitted) hub.removeConnection(connId);\n });\n\n Promise.resolve(options.authenticate ? options.authenticate({ req, room }) : { userId: connId })\n .then((result) => {\n if (closed || state === 'rejected' || shuttingDown) return;\n if (!result) {\n state = 'rejected';\n ws.close(4401, 'unauthorized');\n return;\n }\n state = 'ready';\n admitted = true;\n hub.addConnection({ id: connId, room, userId: result.userId, role: result.role, send });\n for (const m of queue) {\n void hub.handleMessage(connId, m).catch((err) => console.error('[sync-server]', err));\n }\n queue.length = 0;\n queuedBytes = 0;\n })\n .catch(() => {\n if (!closed && !shuttingDown) {\n state = 'rejected';\n ws.close(4401, 'unauthorized');\n }\n });\n });\n const close = (): Promise<void> => {\n if (closePromise) return closePromise;\n shuttingDown = true;\n closePromise = drainWebSocketServer(wss, shutdownGraceMs).then(() => {\n heartbeat.stop();\n hub.close();\n });\n return closePromise;\n };\n\n return {\n hub,\n wss,\n close,\n };\n}\n","export interface HeartbeatSocket {\n ping(): void;\n terminate(): void;\n on(event: 'pong', listener: () => void): void;\n}\nexport interface HeartbeatServer {\n clients: Set<HeartbeatSocket>;\n}\nexport interface Heartbeat {\n track(ws: HeartbeatSocket): void;\n stop(): void;\n}\n\nexport function startHeartbeat(wss: HeartbeatServer, intervalMs: number): Heartbeat {\n if (intervalMs <= 0) {\n return {\n track: () => {\n /* disabled */\n },\n stop: () => {\n /* disabled */\n },\n };\n }\n const alive = new WeakMap<HeartbeatSocket, boolean>();\n const track = (ws: HeartbeatSocket): void => {\n alive.set(ws, true);\n ws.on('pong', () => alive.set(ws, true));\n };\n const interval = setInterval(() => {\n for (const ws of wss.clients) {\n try {\n if (alive.get(ws) === false) {\n ws.terminate();\n continue;\n }\n alive.set(ws, false);\n ws.ping();\n } catch {\n /* socket dying mid-tick — skip it */\n }\n }\n }, intervalMs);\n return { track, stop: () => clearInterval(interval) };\n}\n","export const DEFAULT_SHUTDOWN_GRACE_MS = 5000;\n\nexport interface ShutdownSocket {\n close(code?: number, reason?: string): void;\n terminate(): void;\n}\n\nexport interface ShutdownServer {\n clients: Set<ShutdownSocket>;\n close(callback: (error?: Error) => void): void;\n}\n\n/** Stop admission, ask connected peers to leave, then forcefully reap stragglers. */\nexport function drainWebSocketServer(wss: ShutdownServer, graceMs: number): Promise<void> {\n return new Promise((resolve) => {\n let settled = false;\n const finish = (): void => {\n if (settled) return;\n settled = true;\n clearTimeout(forceTimer);\n resolve();\n };\n const forceTimer = setTimeout(() => {\n for (const ws of wss.clients) {\n try {\n ws.terminate();\n } catch {\n /* socket already failed while shutdown was in progress */\n }\n }\n finish();\n }, graceMs);\n\n // Calling close first prevents new connections. Its callback runs once all clients have left.\n wss.close(() => finish());\n for (const ws of wss.clients) {\n try {\n ws.close(1001, 'server shutting down');\n } catch {\n try {\n ws.terminate();\n } catch {\n /* socket already closed */\n }\n }\n }\n });\n}\n"],"mappings":"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","names":["import_sync"]}