@docukit/docsync 0.0.1-alpha.1 → 0.2.0-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (268) hide show
  1. package/dist/src/bindings/docnode.d.ts +3 -0
  2. package/dist/src/bindings/docnode.d.ts.map +1 -0
  3. package/dist/src/{shared/docBinding.js → bindings/docnode.js} +2 -5
  4. package/dist/src/bindings/docnode.js.map +1 -0
  5. package/dist/src/bindings/index.d.ts +3 -0
  6. package/dist/src/bindings/index.d.ts.map +1 -0
  7. package/dist/src/bindings/index.js +5 -0
  8. package/dist/src/bindings/index.js.map +1 -0
  9. package/dist/src/client/handlers/clientInitiated/deleteDoc.d.ts +4 -0
  10. package/dist/src/client/handlers/clientInitiated/deleteDoc.d.ts.map +1 -0
  11. package/dist/src/client/handlers/clientInitiated/deleteDoc.js +6 -0
  12. package/dist/src/client/handlers/clientInitiated/deleteDoc.js.map +1 -0
  13. package/dist/src/client/handlers/clientInitiated/presence.d.ts +10 -0
  14. package/dist/src/client/handlers/clientInitiated/presence.d.ts.map +1 -0
  15. package/dist/src/client/handlers/clientInitiated/presence.js +45 -0
  16. package/dist/src/client/handlers/clientInitiated/presence.js.map +1 -0
  17. package/dist/src/client/handlers/clientInitiated/sync/buildSyncPayload.d.ts +20 -0
  18. package/dist/src/client/handlers/clientInitiated/sync/buildSyncPayload.d.ts.map +1 -0
  19. package/dist/src/client/handlers/clientInitiated/sync/buildSyncPayload.js +38 -0
  20. package/dist/src/client/handlers/clientInitiated/sync/buildSyncPayload.js.map +1 -0
  21. package/dist/src/client/handlers/clientInitiated/sync/handleSyncResponse/applyAndBroadcastServerOps.d.ts +12 -0
  22. package/dist/src/client/handlers/clientInitiated/sync/handleSyncResponse/applyAndBroadcastServerOps.d.ts.map +1 -0
  23. package/dist/src/client/handlers/clientInitiated/sync/handleSyncResponse/applyAndBroadcastServerOps.js +39 -0
  24. package/dist/src/client/handlers/clientInitiated/sync/handleSyncResponse/applyAndBroadcastServerOps.js.map +1 -0
  25. package/dist/src/client/handlers/clientInitiated/sync/handleSyncResponse/handleSyncResponse.d.ts +11 -0
  26. package/dist/src/client/handlers/clientInitiated/sync/handleSyncResponse/handleSyncResponse.d.ts.map +1 -0
  27. package/dist/src/client/handlers/clientInitiated/sync/handleSyncResponse/handleSyncResponse.js +37 -0
  28. package/dist/src/client/handlers/clientInitiated/sync/handleSyncResponse/handleSyncResponse.js.map +1 -0
  29. package/dist/src/client/handlers/clientInitiated/sync/handleSyncResponse/persistDocDeleted.d.ts +8 -0
  30. package/dist/src/client/handlers/clientInitiated/sync/handleSyncResponse/persistDocDeleted.d.ts.map +1 -0
  31. package/dist/src/client/handlers/clientInitiated/sync/handleSyncResponse/persistDocDeleted.js +13 -0
  32. package/dist/src/client/handlers/clientInitiated/sync/handleSyncResponse/persistDocDeleted.js.map +1 -0
  33. package/dist/src/client/handlers/clientInitiated/sync/handleSyncResponse/persistSyncResult.d.ts +14 -0
  34. package/dist/src/client/handlers/clientInitiated/sync/handleSyncResponse/persistSyncResult.d.ts.map +1 -0
  35. package/dist/src/client/handlers/clientInitiated/sync/handleSyncResponse/persistSyncResult.js +38 -0
  36. package/dist/src/client/handlers/clientInitiated/sync/handleSyncResponse/persistSyncResult.js.map +1 -0
  37. package/dist/src/client/handlers/clientInitiated/sync/sync.d.ts +7 -0
  38. package/dist/src/client/handlers/clientInitiated/sync/sync.d.ts.map +1 -0
  39. package/dist/src/client/handlers/clientInitiated/sync/sync.js +41 -0
  40. package/dist/src/client/handlers/clientInitiated/sync/sync.js.map +1 -0
  41. package/dist/src/client/handlers/clientInitiated/sync.d.ts +21 -0
  42. package/dist/src/client/handlers/clientInitiated/sync.d.ts.map +1 -0
  43. package/dist/src/client/handlers/clientInitiated/sync.js +174 -0
  44. package/dist/src/client/handlers/clientInitiated/sync.js.map +1 -0
  45. package/dist/src/client/handlers/clientInitiated/unsubscribe.d.ts +4 -0
  46. package/dist/src/client/handlers/clientInitiated/unsubscribe.d.ts.map +1 -0
  47. package/dist/src/client/handlers/clientInitiated/unsubscribe.js +12 -0
  48. package/dist/src/client/handlers/clientInitiated/unsubscribe.js.map +1 -0
  49. package/dist/src/client/handlers/connection/connect.d.ts +5 -0
  50. package/dist/src/client/handlers/connection/connect.d.ts.map +1 -0
  51. package/dist/src/client/handlers/connection/connect.js +10 -0
  52. package/dist/src/client/handlers/connection/connect.js.map +1 -0
  53. package/dist/src/client/handlers/connection/disconnect.d.ts +5 -0
  54. package/dist/src/client/handlers/connection/disconnect.d.ts.map +1 -0
  55. package/dist/src/client/handlers/connection/disconnect.js +21 -0
  56. package/dist/src/client/handlers/connection/disconnect.js.map +1 -0
  57. package/dist/src/client/handlers/serverInitiated/dirty.d.ts +5 -0
  58. package/dist/src/client/handlers/serverInitiated/dirty.d.ts.map +1 -0
  59. package/dist/src/client/handlers/serverInitiated/dirty.js +7 -0
  60. package/dist/src/client/handlers/serverInitiated/dirty.js.map +1 -0
  61. package/dist/src/client/handlers/serverInitiated/presence.d.ts +6 -0
  62. package/dist/src/client/handlers/serverInitiated/presence.d.ts.map +1 -0
  63. package/dist/src/client/handlers/serverInitiated/presence.js +11 -0
  64. package/dist/src/client/handlers/serverInitiated/presence.js.map +1 -0
  65. package/dist/src/client/index.d.ts +20 -73
  66. package/dist/src/client/index.d.ts.map +1 -1
  67. package/dist/src/client/index.js +54 -522
  68. package/dist/src/client/index.js.map +1 -1
  69. package/dist/src/client/methods/deleteDoc.d.ts +5 -0
  70. package/dist/src/client/methods/deleteDoc.d.ts.map +1 -0
  71. package/dist/src/client/methods/deleteDoc.js +22 -0
  72. package/dist/src/client/methods/deleteDoc.js.map +1 -0
  73. package/dist/src/client/methods/getDoc/getDoc.d.ts +6 -0
  74. package/dist/src/client/methods/getDoc/getDoc.d.ts.map +1 -0
  75. package/dist/src/client/methods/getDoc/getDoc.js +107 -0
  76. package/dist/src/client/methods/getDoc/getDoc.js.map +1 -0
  77. package/dist/src/client/methods/getDoc/loadOrCreateDoc.d.ts +3 -0
  78. package/dist/src/client/methods/getDoc/loadOrCreateDoc.d.ts.map +1 -0
  79. package/dist/src/client/methods/getDoc/loadOrCreateDoc.js +42 -0
  80. package/dist/src/client/methods/getDoc/loadOrCreateDoc.js.map +1 -0
  81. package/dist/src/client/methods/getDoc/setupChangeListener/onLocalOperations.d.ts +6 -0
  82. package/dist/src/client/methods/getDoc/setupChangeListener/onLocalOperations.d.ts.map +1 -0
  83. package/dist/src/client/methods/getDoc/setupChangeListener/onLocalOperations.js +36 -0
  84. package/dist/src/client/methods/getDoc/setupChangeListener/onLocalOperations.js.map +1 -0
  85. package/dist/src/client/methods/getDoc/setupChangeListener/setupChangeListener.d.ts +3 -0
  86. package/dist/src/client/methods/getDoc/setupChangeListener/setupChangeListener.d.ts.map +1 -0
  87. package/dist/src/client/methods/getDoc/setupChangeListener/setupChangeListener.js +30 -0
  88. package/dist/src/client/methods/getDoc/setupChangeListener/setupChangeListener.js.map +1 -0
  89. package/dist/src/client/methods/getDoc/unloadDoc.d.ts +3 -0
  90. package/dist/src/client/methods/getDoc/unloadDoc.d.ts.map +1 -0
  91. package/dist/src/client/methods/getDoc/unloadDoc.js +32 -0
  92. package/dist/src/client/methods/getDoc/unloadDoc.js.map +1 -0
  93. package/dist/src/client/methods/getPresence.d.ts +6 -0
  94. package/dist/src/client/methods/getPresence.d.ts.map +1 -0
  95. package/dist/src/client/methods/getPresence.js +23 -0
  96. package/dist/src/client/methods/getPresence.js.map +1 -0
  97. package/dist/src/client/providers/indexeddb.d.ts +3 -3
  98. package/dist/src/client/providers/indexeddb.d.ts.map +1 -1
  99. package/dist/src/client/providers/indexeddb.js.map +1 -1
  100. package/dist/src/client/types.d.ts +81 -0
  101. package/dist/src/client/types.d.ts.map +1 -0
  102. package/dist/src/client/types.js +2 -0
  103. package/dist/src/client/types.js.map +1 -0
  104. package/dist/src/client/utils/BCHelper.d.ts +21 -0
  105. package/dist/src/client/utils/BCHelper.d.ts.map +1 -0
  106. package/dist/src/client/utils/BCHelper.js +57 -0
  107. package/dist/src/client/utils/BCHelper.js.map +1 -0
  108. package/dist/src/client/utils/applyPresencePatch.d.ts +11 -0
  109. package/dist/src/client/utils/applyPresencePatch.d.ts.map +1 -0
  110. package/dist/src/client/utils/applyPresencePatch.js +21 -0
  111. package/dist/src/client/utils/applyPresencePatch.js.map +1 -0
  112. package/dist/src/client/utils/createSocket.d.ts +4 -0
  113. package/dist/src/client/utils/createSocket.d.ts.map +1 -0
  114. package/dist/src/client/utils/createSocket.js +29 -0
  115. package/dist/src/client/utils/createSocket.js.map +1 -0
  116. package/dist/src/client/utils/events.d.ts +46 -0
  117. package/dist/src/client/utils/events.d.ts.map +1 -0
  118. package/dist/src/client/utils/events.js +25 -0
  119. package/dist/src/client/utils/events.js.map +1 -0
  120. package/dist/src/client/utils/getDeviceId.d.ts +6 -0
  121. package/dist/src/client/utils/getDeviceId.d.ts.map +1 -0
  122. package/dist/src/client/utils/getDeviceId.js +14 -0
  123. package/dist/src/client/utils/getDeviceId.js.map +1 -0
  124. package/dist/src/client/utils/getOwnPresencePatch.d.ts +3 -0
  125. package/dist/src/client/utils/getOwnPresencePatch.d.ts.map +1 -0
  126. package/dist/src/client/utils/getOwnPresencePatch.js +10 -0
  127. package/dist/src/client/utils/getOwnPresencePatch.js.map +1 -0
  128. package/dist/src/client/utils/request.d.ts +10 -0
  129. package/dist/src/client/utils/request.d.ts.map +1 -0
  130. package/dist/src/client/utils/request.js +15 -0
  131. package/dist/src/client/utils/request.js.map +1 -0
  132. package/dist/src/exports/client.d.ts +4 -1
  133. package/dist/src/exports/client.d.ts.map +1 -1
  134. package/dist/src/exports/client.js +1 -0
  135. package/dist/src/exports/client.js.map +1 -1
  136. package/dist/src/exports/docnode.d.ts +1 -1
  137. package/dist/src/exports/docnode.d.ts.map +1 -1
  138. package/dist/src/exports/docnode.js +1 -1
  139. package/dist/src/exports/docnode.js.map +1 -1
  140. package/dist/src/exports/server.d.ts +1 -1
  141. package/dist/src/exports/server.d.ts.map +1 -1
  142. package/dist/src/exports/server.js.map +1 -1
  143. package/dist/src/exports/shared.d.ts +2 -0
  144. package/dist/src/exports/shared.d.ts.map +1 -0
  145. package/dist/src/exports/shared.js +2 -0
  146. package/dist/src/exports/shared.js.map +1 -0
  147. package/dist/src/server/cli.js +1 -1
  148. package/dist/src/server/cli.js.map +1 -1
  149. package/dist/src/server/handlers/connection/authenticationAndConnection.d.ts +7 -0
  150. package/dist/src/server/handlers/connection/authenticationAndConnection.d.ts.map +1 -0
  151. package/dist/src/server/handlers/connection/authenticationAndConnection.js +57 -0
  152. package/dist/src/server/handlers/connection/authenticationAndConnection.js.map +1 -0
  153. package/dist/src/server/handlers/connection/disconnect.d.ts +5 -0
  154. package/dist/src/server/handlers/connection/disconnect.d.ts.map +1 -0
  155. package/dist/src/server/handlers/connection/disconnect.js +25 -0
  156. package/dist/src/server/handlers/connection/disconnect.js.map +1 -0
  157. package/dist/src/server/handlers/deleteDoc.d.ts +11 -0
  158. package/dist/src/server/handlers/deleteDoc.d.ts.map +1 -0
  159. package/dist/src/server/handlers/deleteDoc.js +13 -0
  160. package/dist/src/server/handlers/deleteDoc.js.map +1 -0
  161. package/dist/src/server/handlers/disconnect.d.ts +10 -0
  162. package/dist/src/server/handlers/disconnect.d.ts.map +1 -0
  163. package/dist/src/server/handlers/disconnect.js +24 -0
  164. package/dist/src/server/handlers/disconnect.js.map +1 -0
  165. package/dist/src/server/handlers/presence.d.ts +12 -0
  166. package/dist/src/server/handlers/presence.d.ts.map +1 -0
  167. package/dist/src/server/handlers/presence.js +19 -0
  168. package/dist/src/server/handlers/presence.js.map +1 -0
  169. package/dist/src/server/handlers/sync/handleError.d.ts +13 -0
  170. package/dist/src/server/handlers/sync/handleError.d.ts.map +1 -0
  171. package/dist/src/server/handlers/sync/handleError.js +23 -0
  172. package/dist/src/server/handlers/sync/handleError.js.map +1 -0
  173. package/dist/src/server/handlers/sync/handleSync.d.ts +10 -0
  174. package/dist/src/server/handlers/sync/handleSync.d.ts.map +1 -0
  175. package/dist/src/server/handlers/sync/handleSync.js +68 -0
  176. package/dist/src/server/handlers/sync/handleSync.js.map +1 -0
  177. package/dist/src/server/handlers/sync/notifyClients.d.ts +12 -0
  178. package/dist/src/server/handlers/sync/notifyClients.d.ts.map +1 -0
  179. package/dist/src/server/handlers/sync/notifyClients.js +27 -0
  180. package/dist/src/server/handlers/sync/notifyClients.js.map +1 -0
  181. package/dist/src/server/handlers/sync/runSyncTransaction.d.ts +16 -0
  182. package/dist/src/server/handlers/sync/runSyncTransaction.d.ts.map +1 -0
  183. package/dist/src/server/handlers/sync/runSyncTransaction.js +43 -0
  184. package/dist/src/server/handlers/sync/runSyncTransaction.js.map +1 -0
  185. package/dist/src/server/handlers/sync/squashIfNeeded.d.ts +9 -0
  186. package/dist/src/server/handlers/sync/squashIfNeeded.d.ts.map +1 -0
  187. package/dist/src/server/handlers/sync/squashIfNeeded.js +32 -0
  188. package/dist/src/server/handlers/sync/squashIfNeeded.js.map +1 -0
  189. package/dist/src/server/handlers/sync/subscribeToRoom.d.ts +8 -0
  190. package/dist/src/server/handlers/sync/subscribeToRoom.d.ts.map +1 -0
  191. package/dist/src/server/handlers/sync/subscribeToRoom.js +21 -0
  192. package/dist/src/server/handlers/sync/subscribeToRoom.js.map +1 -0
  193. package/dist/src/server/handlers/sync.d.ts +13 -0
  194. package/dist/src/server/handlers/sync.d.ts.map +1 -0
  195. package/dist/src/server/handlers/sync.js +161 -0
  196. package/dist/src/server/handlers/sync.js.map +1 -0
  197. package/dist/src/server/handlers/unsubscribe.d.ts +10 -0
  198. package/dist/src/server/handlers/unsubscribe.d.ts.map +1 -0
  199. package/dist/src/server/handlers/unsubscribe.js +21 -0
  200. package/dist/src/server/handlers/unsubscribe.js.map +1 -0
  201. package/dist/src/server/index.d.ts +11 -11
  202. package/dist/src/server/index.d.ts.map +1 -1
  203. package/dist/src/server/index.js +37 -337
  204. package/dist/src/server/index.js.map +1 -1
  205. package/dist/src/server/providers/memory.d.ts +3 -3
  206. package/dist/src/server/providers/memory.d.ts.map +1 -1
  207. package/dist/src/server/providers/memory.js +5 -0
  208. package/dist/src/server/providers/memory.js.map +1 -1
  209. package/dist/src/server/providers/postgres/drizzle.config.d.ts.map +1 -1
  210. package/dist/src/server/providers/postgres/drizzle.config.js +2 -1
  211. package/dist/src/server/providers/postgres/drizzle.config.js.map +1 -1
  212. package/dist/src/server/providers/postgres/index.d.ts +4 -3
  213. package/dist/src/server/providers/postgres/index.d.ts.map +1 -1
  214. package/dist/src/server/providers/postgres/index.js +7 -9
  215. package/dist/src/server/providers/postgres/index.js.map +1 -1
  216. package/dist/src/server/providers/postgres/schema.d.ts +1 -0
  217. package/dist/src/server/providers/postgres/schema.d.ts.map +1 -1
  218. package/dist/src/server/providers/postgres/schema.js +18 -1
  219. package/dist/src/server/providers/postgres/schema.js.map +1 -1
  220. package/dist/src/server/types.d.ts +107 -0
  221. package/dist/src/server/types.d.ts.map +1 -0
  222. package/dist/src/server/types.js +2 -0
  223. package/dist/src/server/types.js.map +1 -0
  224. package/dist/src/server/utils/applyPresenceUpdate.d.ts +11 -0
  225. package/dist/src/server/utils/applyPresenceUpdate.d.ts.map +1 -0
  226. package/dist/src/server/utils/applyPresenceUpdate.js +25 -0
  227. package/dist/src/server/utils/applyPresenceUpdate.js.map +1 -0
  228. package/dist/src/server/utils/authorizeMiddleware.d.ts +4 -0
  229. package/dist/src/server/utils/authorizeMiddleware.d.ts.map +1 -0
  230. package/dist/src/server/utils/authorizeMiddleware.js +73 -0
  231. package/dist/src/server/utils/authorizeMiddleware.js.map +1 -0
  232. package/dist/src/server/utils/events.d.ts +16 -0
  233. package/dist/src/server/utils/events.d.ts.map +1 -0
  234. package/dist/src/server/utils/events.js +22 -0
  235. package/dist/src/server/utils/events.js.map +1 -0
  236. package/dist/src/server/utils/rateLimitMiddleware.d.ts +6 -0
  237. package/dist/src/server/utils/rateLimitMiddleware.d.ts.map +1 -0
  238. package/dist/src/server/utils/rateLimitMiddleware.js +65 -0
  239. package/dist/src/server/utils/rateLimitMiddleware.js.map +1 -0
  240. package/dist/src/shared/types.d.ts +52 -338
  241. package/dist/src/shared/types.d.ts.map +1 -1
  242. package/dist/src/shared/types.js +0 -4
  243. package/dist/src/shared/types.js.map +1 -1
  244. package/dist/tsconfig.build.tsbuildinfo +1 -1
  245. package/package.json +3 -5
  246. package/dist/src/exports/index.d.ts +0 -3
  247. package/dist/src/exports/index.d.ts.map +0 -1
  248. package/dist/src/exports/index.js +0 -3
  249. package/dist/src/exports/index.js.map +0 -1
  250. package/dist/src/exports/testing.d.ts +0 -7
  251. package/dist/src/exports/testing.d.ts.map +0 -1
  252. package/dist/src/exports/testing.js +0 -6
  253. package/dist/src/exports/testing.js.map +0 -1
  254. package/dist/src/shared/debounce.d.ts +0 -2
  255. package/dist/src/shared/debounce.d.ts.map +0 -1
  256. package/dist/src/shared/debounce.js +0 -10
  257. package/dist/src/shared/debounce.js.map +0 -1
  258. package/dist/src/shared/docBinding.d.ts +0 -17
  259. package/dist/src/shared/docBinding.d.ts.map +0 -1
  260. package/dist/src/shared/docBinding.js.map +0 -1
  261. package/dist/src/shared/throttle.d.ts +0 -30
  262. package/dist/src/shared/throttle.d.ts.map +0 -1
  263. package/dist/src/shared/throttle.js +0 -51
  264. package/dist/src/shared/throttle.js.map +0 -1
  265. package/dist/src/shared/utils.d.ts +0 -2
  266. package/dist/src/shared/utils.d.ts.map +0 -1
  267. package/dist/src/shared/utils.js +0 -11
  268. package/dist/src/shared/utils.js.map +0 -1
@@ -1,5 +1,17 @@
1
1
  /* eslint-disable @typescript-eslint/no-empty-object-type */
2
2
  import { io } from "socket.io-client";
3
+ import { createClientEventEmitter } from "./utils/events.js";
4
+ import { handleConnect } from "./handlers/connection/connect.js";
5
+ import { handleDeleteDoc } from "./handlers/clientInitiated/deleteDoc.js";
6
+ import { handleDisconnect } from "./handlers/connection/disconnect.js";
7
+ import { handleDirty } from "./handlers/serverInitiated/dirty.js";
8
+ import { handlePresence } from "./handlers/clientInitiated/presence.js";
9
+ import { handlePresence as handleServerPresence } from "./handlers/serverInitiated/presence.js";
10
+ import { handleSync } from "./handlers/clientInitiated/sync.js";
11
+ import { handleUnsubscribe } from "./handlers/clientInitiated/unsubscribe.js";
12
+ import { BCHelper } from "./utils/BCHelper.js";
13
+ import { getDeviceId } from "./utils/getDeviceId.js";
14
+ import { getOwnPresencePatch } from "./utils/getOwnPresencePatch.js";
3
15
  export class DocSyncClient {
4
16
  _docBinding;
5
17
  _docsCache = new Map();
@@ -8,7 +20,7 @@ export class DocSyncClient {
8
20
  /** Client-generated id for presence (works offline; sent in auth so server uses same key) */
9
21
  _clientId;
10
22
  _shouldBroadcast = true;
11
- _broadcastChannel;
23
+ _bcHelper;
12
24
  _socket;
13
25
  // Flow control state (batching, debouncing, push queueing)
14
26
  _localOpsBatchState = new Map();
@@ -16,13 +28,8 @@ export class DocSyncClient {
16
28
  _presenceDebounceState = new Map();
17
29
  _presenceDebounce = 200;
18
30
  _pushStatusByDocId = new Map();
19
- // Event handlers - ChangeHandler and SyncHandler use default (unknown) to allow covariance
20
- _connectHandlers = new Set();
21
- _disconnectHandlers = new Set();
22
- _changeHandlers = new Set();
23
- _syncHandlers = new Set();
24
- _docLoadHandlers = new Set();
25
- _docUnloadHandlers = new Set();
31
+ /** Typed as unknown so DocSyncClient remains covariant in O, S (assignable to DocSyncClient base). */
32
+ _events = createClientEventEmitter();
26
33
  constructor(config) {
27
34
  if (typeof window === "undefined")
28
35
  throw new Error("DocSyncClient can only be used in the browser");
@@ -33,88 +40,23 @@ export class DocSyncClient {
33
40
  this._localPromise = (async () => {
34
41
  const identity = await local.getIdentity();
35
42
  const provider = new local.provider(identity);
36
- // Initialize BroadcastChannel with user-specific channel name
37
- // This ensures only tabs of the same user share operations
38
- this._broadcastChannel = new BroadcastChannel(`docsync:${identity.userId}`);
39
- this._broadcastChannel.onmessage = async (ev) => {
40
- // RECEIVED MESSAGES
41
- if (ev.data.type === "OPERATIONS") {
42
- // Another tab is pushing operations - they are responsible for pushing to server
43
- // We just need to coordinate push status to avoid conflicts
44
- const currentStatus = this._pushStatusByDocId.get(ev.data.docId) ?? "idle";
45
- if (currentStatus === "pushing") {
46
- // Mark as busy to avoid concurrent pushes
47
- this._pushStatusByDocId.set(ev.data.docId, "pushing-with-pending");
48
- }
49
- // Note: We don't call saveRemote here - the sender is responsible for pushing
50
- // If the sender is offline, the push will happen when they reconnect
51
- void this._applyOperations(ev.data.operations, ev.data.docId);
52
- // Apply presence after ops so the doc is updated first (avoids cursor lag)
53
- if (ev.data.presence) {
54
- const cacheEntry = this._docsCache.get(ev.data.docId);
55
- if (cacheEntry)
56
- this._applyPresencePatch(cacheEntry, ev.data.presence);
57
- }
58
- return;
59
- }
60
- if (ev.data.type === "PRESENCE") {
61
- const { docId, presence } = ev.data;
62
- const cacheEntry = this._docsCache.get(docId);
63
- if (!cacheEntry)
64
- return;
65
- this._applyPresencePatch(cacheEntry, presence);
66
- }
67
- };
43
+ this._bcHelper = new BCHelper(this, identity.userId);
68
44
  return { provider, identity };
69
45
  })();
70
46
  this._deviceId = getDeviceId();
71
47
  this._socket = io(config.server.url, {
72
48
  auth: (cb) => {
73
- void config.server.auth.getToken().then((token) => {
49
+ void Promise.resolve(config.server.auth.getToken()).then((token) => {
74
50
  cb({ token, deviceId: this._deviceId, clientId: this._clientId });
75
51
  });
76
52
  },
77
53
  // Performance optimizations for testing
78
54
  transports: ["websocket"], // Skip polling, go straight to WebSocket
79
55
  });
80
- this._socket.on("connect", () => {
81
- // Emit connect event
82
- this._emit(this._connectHandlers);
83
- // Push pending operations for all loaded docs
84
- for (const docId of this._docsCache.keys()) {
85
- this.saveRemote({ docId });
86
- }
87
- });
88
- this._socket.on("disconnect", (reason) => {
89
- this._pushStatusByDocId.clear();
90
- // Clear pending presence debounce timers so their callbacks never run after disconnect
91
- for (const state of this._presenceDebounceState.values()) {
92
- clearTimeout(state.timeout);
93
- }
94
- this._presenceDebounceState.clear();
95
- // Tell other tabs to remove this client's presence (clientId works offline)
96
- for (const docId of this._docsCache.keys()) {
97
- this._sendMessage({
98
- type: "PRESENCE",
99
- docId,
100
- presence: { [this._clientId]: null },
101
- });
102
- }
103
- this._emit(this._disconnectHandlers, { reason });
104
- });
105
- this._socket.on("connect_error", (err) => {
106
- this._emit(this._disconnectHandlers, { reason: err.message });
107
- });
108
- // Listen for dirty notifications from server
109
- this._socket.on("dirty", (payload) => {
110
- this.saveRemote({ docId: payload.docId });
111
- });
112
- this._socket.on("presence", (payload) => {
113
- const cacheEntry = this._docsCache.get(payload.docId);
114
- if (!cacheEntry)
115
- return;
116
- this._applyPresencePatch(cacheEntry, payload.presence);
117
- });
56
+ handleConnect({ client: this });
57
+ handleDisconnect({ client: this });
58
+ handleDirty({ client: this });
59
+ handleServerPresence({ client: this });
118
60
  }
119
61
  connect() {
120
62
  this._socket.connect();
@@ -122,88 +64,6 @@ export class DocSyncClient {
122
64
  disconnect() {
123
65
  this._socket.disconnect();
124
66
  }
125
- async _applyOperations(operations, docId) {
126
- const docFromCache = this._docsCache.get(docId);
127
- if (!docFromCache)
128
- return;
129
- const doc = await docFromCache.promisedDoc;
130
- if (!doc)
131
- return;
132
- this._shouldBroadcast = false;
133
- this._docBinding.applyOperations(doc, operations);
134
- this._shouldBroadcast = true;
135
- // Emit change event for broadcast operations
136
- this._emit(this._changeHandlers, {
137
- docId,
138
- origin: "broadcast",
139
- operations: [operations],
140
- });
141
- }
142
- _applyPresencePatch(cacheEntry, patch) {
143
- const newPresence = { ...cacheEntry.presence };
144
- for (const [key, value] of Object.entries(patch)) {
145
- if (key === this._clientId)
146
- continue; // never store own presence in cache; local tab must not render self as remote
147
- if (value === undefined || value === null) {
148
- delete newPresence[key];
149
- }
150
- else {
151
- newPresence[key] = value;
152
- }
153
- }
154
- cacheEntry.presence = newPresence;
155
- cacheEntry.presenceHandlers.forEach((handler) => handler(cacheEntry.presence));
156
- }
157
- /** Current presence for this client (debounce state or cache); does not clear the timer */
158
- _getOwnPresencePatch(docId) {
159
- const debounced = this._presenceDebounceState.get(docId);
160
- if (debounced)
161
- return { [this._clientId]: debounced.data };
162
- const cacheEntry = this._docsCache.get(docId);
163
- if (cacheEntry?.presence[this._clientId] !== undefined)
164
- return { [this._clientId]: cacheEntry.presence[this._clientId] };
165
- return undefined;
166
- }
167
- // TODO: used when server responds with a new doc (squashing)
168
- async _replaceDocInCache({ docId, doc, serializedDoc, }) {
169
- const cacheEntry = this._docsCache.get(docId);
170
- if (!cacheEntry)
171
- return;
172
- // Deserialize if needed
173
- const newDoc = doc ?? this._docBinding.deserialize(serializedDoc);
174
- // Replace the cached document with the new one
175
- // Keep the same refCount
176
- // Note: We don't setup a new change listener here because:
177
- // 1. The doc already has all operations applied from the sync
178
- // 2. A listener will be setup when the doc is loaded via getDoc
179
- // 3. Multiple listeners would cause operations to be applied multiple times
180
- this._docsCache.set(docId, {
181
- promisedDoc: Promise.resolve(newDoc),
182
- refCount: cacheEntry.refCount,
183
- presence: cacheEntry.presence,
184
- presenceHandlers: cacheEntry.presenceHandlers,
185
- });
186
- }
187
- async _applyServerOperations({ docId, operations, }) {
188
- const cacheEntry = this._docsCache.get(docId);
189
- if (!cacheEntry)
190
- return;
191
- // Get the cached document and apply server operations to it
192
- const doc = await cacheEntry.promisedDoc;
193
- if (!doc)
194
- return;
195
- this._shouldBroadcast = false;
196
- for (const op of operations) {
197
- this._docBinding.applyOperations(doc, op);
198
- }
199
- this._shouldBroadcast = true;
200
- // Emit change event for remote operations
201
- this._emit(this._changeHandlers, {
202
- docId,
203
- origin: "remote",
204
- operations,
205
- });
206
- }
207
67
  /**
208
68
  * Subscribe to a document with reactive state updates.
209
69
  *
@@ -248,13 +108,13 @@ export class DocSyncClient {
248
108
  this._docsCache.set(createdDocId, {
249
109
  promisedDoc: Promise.resolve(doc),
250
110
  refCount: 1,
111
+ type,
251
112
  presence: {},
252
- presenceHandlers: new Set(),
113
+ presenceListeners: new Set(),
253
114
  });
254
115
  this._setupChangeListener(doc, createdDocId);
255
116
  emit({ status: "success", data: { doc, docId: createdDocId } });
256
- // Emit doc load event
257
- this._emit(this._docLoadHandlers, {
117
+ this._events.emit("docLoad", {
258
118
  docId: createdDocId,
259
119
  source: "created",
260
120
  refCount: 1,
@@ -269,7 +129,7 @@ export class DocSyncClient {
269
129
  clock: 0,
270
130
  }));
271
131
  })();
272
- // We don't trigger a initial saveRemote here because argId is undefined,
132
+ // We don't trigger an initial sync here because argId is undefined;
273
133
  // so this is truly a new doc. Initial operations will be pushed to server
274
134
  return () => void this._unloadDoc(createdDocId);
275
135
  }
@@ -289,8 +149,9 @@ export class DocSyncClient {
289
149
  this._docsCache.set(docId, {
290
150
  promisedDoc,
291
151
  refCount: 1,
152
+ type,
292
153
  presence: {},
293
- presenceHandlers: new Set(),
154
+ presenceListeners: new Set(),
294
155
  });
295
156
  }
296
157
  void (async () => {
@@ -310,22 +171,14 @@ export class DocSyncClient {
310
171
  source = createIfMissing ? "created" : "local";
311
172
  }
312
173
  }
313
- // Emit doc load event
314
174
  if (doc) {
315
175
  const refCount = this._docsCache.get(docId)?.refCount ?? 1;
316
- this._emit(this._docLoadHandlers, {
317
- docId,
318
- source,
319
- refCount,
320
- });
176
+ this._events.emit("docLoad", { docId, source, refCount });
321
177
  }
322
- emit({
323
- status: "success",
324
- data: doc ? { doc, docId } : undefined,
325
- });
178
+ emit({ status: "success", data: doc ? { doc, docId } : undefined });
326
179
  // Fetch from server to check if document exists there
327
180
  if (doc) {
328
- void this.saveRemote({ docId });
181
+ void handleSync(this, docId);
329
182
  }
330
183
  }
331
184
  catch (e) {
@@ -341,7 +194,7 @@ export class DocSyncClient {
341
194
  }
342
195
  /**
343
196
  * Subscribe to presence updates for a document.
344
- * Multiple handlers can be registered for the same document.
197
+ * Multiple listeners can be registered for the same document.
345
198
  * @param args - The arguments for the getPresence request.
346
199
  * @param onChange - The callback to invoke when the presence changes.
347
200
  * @returns A function to unsubscribe from presence updates.
@@ -354,64 +207,28 @@ export class DocSyncClient {
354
207
  if (!cacheEntry) {
355
208
  throw new Error(`Cannot subscribe to presence for document "${docId}" - document not loaded.`);
356
209
  }
357
- // Add handler to the set
358
- cacheEntry.presenceHandlers.add(onChange);
210
+ // Add listener to the set
211
+ cacheEntry.presenceListeners.add(onChange);
359
212
  // Immediately call with current presence if available
360
213
  if (Object.keys(cacheEntry.presence).length > 0) {
361
214
  onChange(cacheEntry.presence);
362
215
  }
363
- // Return unsubscribe function that removes only this handler
216
+ // Return unsubscribe function that removes only this listener
364
217
  return () => {
365
218
  const entry = this._docsCache.get(docId);
366
219
  if (entry) {
367
- entry.presenceHandlers.delete(onChange);
220
+ entry.presenceListeners.delete(onChange);
368
221
  }
369
222
  };
370
223
  }
371
- async setPresence({ docId, presence }) {
372
- const cacheEntry = this._docsCache.get(docId);
373
- if (!cacheEntry)
374
- throw new Error(`Doc ${docId} is not loaded, cannot set presence`);
375
- // Clear existing timeout if any
376
- const existingState = this._presenceDebounceState.get(docId);
377
- clearTimeout(existingState?.timeout);
378
- // Debounce the presence update
379
- const timeout = setTimeout(() => {
380
- const state = this._presenceDebounceState.get(docId);
381
- if (!state)
382
- return;
383
- this._presenceDebounceState.delete(docId);
384
- const patch = { [this._clientId]: state.data };
385
- // Update local cache and notify handlers (so own cursor shows and UI stays in sync)
386
- this._applyPresencePatch(cacheEntry, patch);
387
- // Same device: broadcast to other tabs (works offline)
388
- this._sendMessage({
389
- type: "PRESENCE",
390
- docId,
391
- presence: patch,
392
- });
393
- // Other devices: send via WebSocket only when connected
394
- if (this._socket.connected) {
395
- void (async () => {
396
- if (!this._socket.connected)
397
- return;
398
- const { error } = await this._request("presence", {
399
- docId,
400
- presence: state.data,
401
- });
402
- if (error) {
403
- console.error(`Error setting presence for doc ${docId}:`, error);
404
- }
405
- })();
406
- }
407
- }, this._presenceDebounce);
408
- this._presenceDebounceState.set(docId, { timeout, data: presence });
224
+ setPresence({ docId, presence }) {
225
+ void handlePresence(this, { docId, presence });
409
226
  }
410
227
  _setupChangeListener(doc, docId) {
411
228
  this._docBinding.onChange(doc, ({ operations }) => {
412
229
  if (this._shouldBroadcast) {
413
230
  void this.onLocalOperations({ docId, operations: [operations] });
414
- this._emit(this._changeHandlers, {
231
+ this._events.emit("change", {
415
232
  docId,
416
233
  origin: "local",
417
234
  operations: [operations],
@@ -420,8 +237,8 @@ export class DocSyncClient {
420
237
  // include is the new cursor. Two frames so setPresence (from selection change) has run.
421
238
  requestAnimationFrame(() => {
422
239
  requestAnimationFrame(() => {
423
- const presencePatch = this._getOwnPresencePatch(docId);
424
- this._sendMessage({
240
+ const presencePatch = getOwnPresencePatch(this, docId);
241
+ this._bcHelper?.broadcast({
425
242
  type: "OPERATIONS",
426
243
  operations,
427
244
  docId,
@@ -480,34 +297,23 @@ export class DocSyncClient {
480
297
  return;
481
298
  if (cacheEntry.refCount > 1) {
482
299
  cacheEntry.refCount -= 1;
483
- this._emit(this._docUnloadHandlers, {
484
- docId,
485
- refCount: cacheEntry.refCount,
486
- });
300
+ this._events.emit("docUnload", { docId, refCount: cacheEntry.refCount });
487
301
  }
488
302
  else {
489
- // Mark refCount as 0 but keep in cache until promise resolves
490
303
  cacheEntry.refCount = 0;
491
- // Emit immediately
492
- this._emit(this._docUnloadHandlers, {
493
- docId,
494
- refCount: 0,
495
- });
304
+ this._events.emit("docUnload", { docId, refCount: 0 });
496
305
  // Dispose when promise resolves
497
306
  const doc = await cacheEntry.promisedDoc;
498
307
  const currentEntry = this._docsCache.get(docId);
499
308
  if (currentEntry?.refCount === 0) {
500
309
  this._docsCache.delete(docId);
501
310
  if (doc) {
502
- await this.unsubscribeDoc(docId);
311
+ await handleUnsubscribe(this._socket, { docId });
503
312
  this._docBinding.dispose(doc);
504
313
  }
505
314
  }
506
315
  }
507
316
  }
508
- _sendMessage(message) {
509
- this._broadcastChannel?.postMessage(message);
510
- }
511
317
  onLocalOperations({ docId, operations }) {
512
318
  // Get or create the batch state for this document
513
319
  let state = this._localOpsBatchState.get(docId);
@@ -535,298 +341,24 @@ export class DocSyncClient {
535
341
  if (opsToSave && opsToSave.length > 0) {
536
342
  const local = await this._localPromise;
537
343
  await local?.provider.transaction("readwrite", (ctx) => ctx.saveOperations({ docId, operations: opsToSave }));
538
- this.saveRemote({ docId });
344
+ void handleSync(this, docId);
539
345
  }
540
346
  })();
541
347
  }, this._batchDelay);
542
348
  }
543
- /**
544
- * Push local operations to the server for a specific document.
545
- * Uses a per-docId queue to prevent concurrent pushes for the same doc.
546
- */
547
- saveRemote({ docId }) {
548
- const status = this._pushStatusByDocId.get(docId) ?? "idle";
549
- if (status !== "idle") {
550
- this._pushStatusByDocId.set(docId, "pushing-with-pending");
551
- return;
552
- }
553
- void this._doPush({ docId });
349
+ async _deleteDoc(docId) {
350
+ return handleDeleteDoc(this._socket, { docId });
554
351
  }
555
352
  /**
556
- * Unsubscribe from real-time updates for a document.
557
- * Should be called when a document is unloaded (refCount 1 → 0).
558
- */
559
- async unsubscribeDoc(docId) {
560
- // Skip if socket is not connected (e.g., in local-only mode or during tests)
561
- if (!this._socket.connected)
562
- return;
563
- try {
564
- await this._request("unsubscribe-doc", { docId });
565
- }
566
- catch {
567
- // Silently ignore errors during cleanup (e.g., socket
568
- // disconnected during request, timeout, or server error)
569
- }
570
- }
571
- async _doPush({ docId }) {
572
- this._pushStatusByDocId.set(docId, "pushing");
573
- const provider = (await this._localPromise).provider;
574
- // Get the current clock value and operations from provider
575
- const [operationsBatches, stored] = await provider.transaction("readonly", async (ctx) => {
576
- return Promise.all([
577
- ctx.getOperations({ docId }),
578
- ctx.getSerializedDoc(docId),
579
- ]);
580
- });
581
- const operations = operationsBatches.flat();
582
- const clientClock = stored?.clock ?? 0;
583
- let response;
584
- try {
585
- const presenceState = this._presenceDebounceState.get(docId);
586
- if (presenceState) {
587
- clearTimeout(presenceState.timeout);
588
- this._presenceDebounceState.delete(docId);
589
- this._sendMessage({
590
- type: "PRESENCE",
591
- docId,
592
- presence: { [this._clientId]: presenceState.data },
593
- });
594
- }
595
- response = await this._request("sync-operations", {
596
- clock: clientClock,
597
- docId,
598
- operations,
599
- ...(presenceState ? { presence: presenceState.data } : {}),
600
- });
601
- }
602
- catch (error) {
603
- // Emit sync event (network error)
604
- this._emit(this._syncHandlers, {
605
- req: {
606
- docId,
607
- operations,
608
- clock: clientClock,
609
- },
610
- error: {
611
- type: "NetworkError",
612
- message: error instanceof Error ? error.message : String(error),
613
- },
614
- });
615
- // Retry on failure
616
- this._pushStatusByDocId.set(docId, "idle");
617
- void this._doPush({ docId });
618
- return;
619
- }
620
- // Check if server returned an error
621
- if ("error" in response && response.error) {
622
- // Emit sync event with server error
623
- this._emit(this._syncHandlers, {
624
- req: {
625
- docId,
626
- operations,
627
- clock: clientClock,
628
- },
629
- error: response.error,
630
- });
631
- // Retry on error
632
- this._pushStatusByDocId.set(docId, "idle");
633
- void this._doPush({ docId });
634
- return;
635
- }
636
- // At this point, response must have data
637
- const { data } = response;
638
- // Emit sync event (success)
639
- this._emit(this._syncHandlers, {
640
- req: {
641
- docId,
642
- operations,
643
- clock: clientClock,
644
- },
645
- data: {
646
- ...(data.operations ? { operations: data.operations } : {}),
647
- ...(data.serializedDoc ? { serializedDoc: data.serializedDoc } : {}),
648
- clock: data.clock,
649
- },
650
- });
651
- // Atomically: delete synced operations + consolidate into serialized doc
652
- let didConsolidate = false; // Track if we actually saved new operations to IDB
653
- await provider.transaction("readwrite", async (ctx) => {
654
- // Delete client operations that were synced (delete batches, not individual ops)
655
- if (operationsBatches.length > 0) {
656
- await ctx.deleteOperations({
657
- docId,
658
- count: operationsBatches.length,
659
- });
660
- }
661
- // Consolidate operations into serialized doc
662
- const stored = await ctx.getSerializedDoc(docId);
663
- if (!stored)
664
- return;
665
- // Skip consolidation if another client (same IDB) already updated to this clock
666
- // This handles the case where another tab/client already wrote this update
667
- if (stored.clock >= data.clock) {
668
- didConsolidate = false;
669
- return;
670
- }
671
- // Collect all operations to apply: server ops first, then client ops
672
- const serverOps = data.operations ?? [];
673
- const allOps = [...serverOps, ...operations];
674
- // Only proceed if there are operations to apply
675
- if (allOps.length > 0) {
676
- const doc = this._docBinding.deserialize(stored.serializedDoc);
677
- // Apply all operations in order (server ops first, then client ops)
678
- for (const op of allOps) {
679
- this._docBinding.applyOperations(doc, op);
680
- }
681
- const serializedDoc = this._docBinding.serialize(doc);
682
- // Before saving, verify clock hasn't changed (another concurrent write)
683
- // This prevents race conditions when multiple tabs/clients share the same IDB
684
- const recheckStored = await ctx.getSerializedDoc(docId);
685
- if (!recheckStored || recheckStored?.clock !== stored.clock) {
686
- // Clock changed during our transaction - another client beat us
687
- // Silently skip to avoid duplicate operations
688
- return;
689
- }
690
- await ctx.saveSerializedDoc({
691
- serializedDoc,
692
- docId,
693
- clock: data.clock, // Use clock from server
694
- });
695
- didConsolidate = true; // Mark that we successfully saved
696
- }
697
- });
698
- // CRITICAL: Only apply serverOps to memory if we actually saved to IDB
699
- // If we skipped (clock already up-to-date), operations are already in memory via BC
700
- if (didConsolidate && data.operations && data.operations.length > 0) {
701
- // Apply to our own memory
702
- void this._applyServerOperations({
703
- docId,
704
- operations: data.operations,
705
- });
706
- // Broadcast server operations to other tabs so they can apply them too
707
- const presencePatch = this._getOwnPresencePatch(docId);
708
- for (const op of data.operations) {
709
- this._sendMessage({
710
- type: "OPERATIONS",
711
- operations: op,
712
- docId,
713
- ...(presencePatch && { presence: presencePatch }),
714
- });
715
- }
716
- }
717
- // Status may have changed to "pushing-with-pending" during async ops
718
- const currentStatus = this._pushStatusByDocId.get(docId);
719
- const shouldRetry = currentStatus === "pushing-with-pending";
720
- if (shouldRetry) {
721
- // Keep status as "pushing" and retry immediately to avoid race window
722
- // where a dirty event could trigger another concurrent _doPush
723
- void this._doPush({ docId });
724
- }
725
- else {
726
- this._pushStatusByDocId.set(docId, "idle");
727
- }
728
- }
729
- async _request(event, payload) {
730
- // TO-DO: should I reject on disconnect?
731
- return new Promise((resolve, reject) => {
732
- // Add a timeout to prevent hanging forever if socket disconnects during request
733
- const timeout = setTimeout(() => {
734
- reject(new Error(`Request timeout: ${event}`));
735
- }, 5000); // 5 second timeout
736
- this._socket.emit(event, payload, (response) => {
737
- clearTimeout(timeout);
738
- resolve(response);
739
- });
740
- });
741
- }
742
- // ============================================================================
743
- // Event Registration Methods
744
- // ============================================================================
745
- /**
746
- * Register a handler for connection events.
747
- * @returns Unsubscribe function
748
- */
749
- onConnect(handler) {
750
- this._connectHandlers.add(handler);
751
- return () => {
752
- this._connectHandlers.delete(handler);
753
- };
754
- }
755
- /**
756
- * Register a handler for disconnection events.
757
- * @returns Unsubscribe function
758
- */
759
- onDisconnect(handler) {
760
- this._disconnectHandlers.add(handler);
761
- return () => {
762
- this._disconnectHandlers.delete(handler);
763
- };
764
- }
765
- /**
766
- * Register a handler for document change events.
767
- * @returns Unsubscribe function
768
- */
769
- onChange(handler) {
770
- const h = handler;
771
- this._changeHandlers.add(h);
772
- return () => {
773
- this._changeHandlers.delete(h);
774
- };
775
- }
776
- /**
777
- * Register a handler for sync events.
778
- * @returns Unsubscribe function
779
- */
780
- onSync(handler) {
781
- const h = handler;
782
- this._syncHandlers.add(h);
783
- return () => {
784
- this._syncHandlers.delete(h);
785
- };
786
- }
787
- /**
788
- * Register a handler for document load events.
789
- * @returns Unsubscribe function
790
- */
791
- onDocLoad(handler) {
792
- this._docLoadHandlers.add(handler);
793
- return () => {
794
- this._docLoadHandlers.delete(handler);
795
- };
796
- }
797
- /**
798
- * Register a handler for document unload events.
799
- * @returns Unsubscribe function
353
+ * Register a listener for an event. Returns an unsubscribe function.
354
+ * Event payload type is inferred from the event name (first argument).
355
+ * @example
356
+ * const off = client.on("connect", () => { ... });
357
+ * client.on("docUnload", (ev) => { ... }); // ev is DocUnloadEvent
358
+ * off(); // unsubscribe
800
359
  */
801
- onDocUnload(handler) {
802
- this._docUnloadHandlers.add(handler);
803
- return () => {
804
- this._docUnloadHandlers.delete(handler);
805
- };
806
- }
807
- _emit(handlers, event) {
808
- for (const handler of handlers) {
809
- if (event !== undefined) {
810
- handler(event);
811
- }
812
- else {
813
- handler();
814
- }
815
- }
816
- }
817
- }
818
- /**
819
- * Get or create a unique device ID stored in localStorage.
820
- * This ID is shared across all tabs/windows on the same device.
821
- */
822
- function getDeviceId() {
823
- const key = "docsync:deviceId";
824
- let deviceId = localStorage.getItem(key);
825
- if (!deviceId) {
826
- // Generate a new device ID using crypto.randomUUID()
827
- deviceId = crypto.randomUUID();
828
- localStorage.setItem(key, deviceId);
360
+ on(event, listener) {
361
+ return this._events.on(event, listener);
829
362
  }
830
- return deviceId;
831
363
  }
832
364
  //# sourceMappingURL=index.js.map