@liveblocks/core 1.1.8 → 1.2.0-comments

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.mjs CHANGED
@@ -1,6 +1,12 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __export = (target, all) => {
3
+ for (var name in all)
4
+ __defProp(target, name, { get: all[name], enumerable: true });
5
+ };
6
+
1
7
  // src/version.ts
2
8
  var PKG_NAME = "@liveblocks/core";
3
- var PKG_VERSION = "1.1.8";
9
+ var PKG_VERSION = "1.2.0-comments";
4
10
  var PKG_FORMAT = "esm";
5
11
 
6
12
  // src/dupe-detection.ts
@@ -46,6 +52,36 @@ function detectDupes(pkgName, pkgVersion, pkgFormat) {
46
52
  }
47
53
  }
48
54
 
55
+ // src/lib/assert.ts
56
+ function assertNever(_value, errmsg) {
57
+ throw new Error(errmsg);
58
+ }
59
+ function assert(condition, errmsg) {
60
+ if (process.env.NODE_ENV !== "production") {
61
+ if (!condition) {
62
+ const err = new Error(errmsg);
63
+ err.name = "Assertion failure";
64
+ throw err;
65
+ }
66
+ }
67
+ }
68
+ function nn(value, errmsg = "Expected value to be non-nullable") {
69
+ assert(value !== null && value !== void 0, errmsg);
70
+ return value;
71
+ }
72
+
73
+ // src/lib/controlledPromise.ts
74
+ function controlledPromise() {
75
+ let flagger;
76
+ const promise = new Promise((res) => {
77
+ flagger = res;
78
+ });
79
+ if (!flagger) {
80
+ throw new Error("Should never happen");
81
+ }
82
+ return [promise, flagger];
83
+ }
84
+
49
85
  // src/lib/EventSource.ts
50
86
  function makeEventSource() {
51
87
  const _onetimeObservers = /* @__PURE__ */ new Set();
@@ -119,185 +155,14 @@ function makeEventSource() {
119
155
  };
120
156
  }
121
157
 
122
- // src/devtools/bridge.ts
123
- var _bridgeActive = false;
124
- function activateBridge(allowed) {
125
- _bridgeActive = allowed;
126
- }
127
- function sendToPanel(message, options) {
128
- if (process.env.NODE_ENV === "production" || typeof window === "undefined") {
129
- return;
130
- }
131
- const fullMsg = {
132
- ...message,
133
- source: "liveblocks-devtools-client"
134
- };
135
- if (!(options?.force || _bridgeActive)) {
136
- return;
137
- }
138
- window.postMessage(fullMsg, "*");
139
- }
140
- var eventSource = makeEventSource();
141
- if (process.env.NODE_ENV !== "production" && typeof window !== "undefined") {
142
- window.addEventListener("message", (event) => {
143
- if (event.source === window && event.data?.source === "liveblocks-devtools-panel") {
144
- eventSource.notify(event.data);
145
- } else {
146
- }
147
- });
148
- }
149
- var onMessageFromPanel = eventSource.observable;
150
-
151
- // src/devtools/index.ts
152
- var VERSION = PKG_VERSION || "dev";
153
- var _devtoolsSetupHasRun = false;
154
- function setupDevTools(getAllRooms) {
155
- if (process.env.NODE_ENV === "production" || typeof window === "undefined") {
156
- return;
157
- }
158
- if (_devtoolsSetupHasRun) {
159
- return;
160
- }
161
- _devtoolsSetupHasRun = true;
162
- onMessageFromPanel.subscribe((msg) => {
163
- switch (msg.msg) {
164
- case "connect": {
165
- activateBridge(true);
166
- for (const roomId of getAllRooms()) {
167
- sendToPanel({
168
- msg: "room::available",
169
- roomId,
170
- clientVersion: VERSION
171
- });
172
- }
173
- break;
174
- }
175
- }
176
- });
177
- sendToPanel({ msg: "wake-up-devtools" }, { force: true });
178
- }
179
- var unsubsByRoomId = /* @__PURE__ */ new Map();
180
- function stopSyncStream(roomId) {
181
- const unsubs = unsubsByRoomId.get(roomId) ?? [];
182
- unsubsByRoomId.delete(roomId);
183
- for (const unsub of unsubs) {
184
- unsub();
185
- }
186
- }
187
- function startSyncStream(room) {
188
- stopSyncStream(room.id);
189
- fullSync(room);
190
- unsubsByRoomId.set(room.id, [
191
- // When the connection status changes
192
- room.events.status.subscribe(() => partialSyncConnection(room)),
193
- // When storage initializes, send the update
194
- room.events.storageDidLoad.subscribeOnce(() => partialSyncStorage(room)),
195
- // Any time storage updates, send the new storage root
196
- room.events.storage.subscribe(() => partialSyncStorage(room)),
197
- // Any time "me" or "others" updates, send the new values accordingly
198
- room.events.self.subscribe(() => partialSyncMe(room)),
199
- room.events.others.subscribe(() => partialSyncOthers(room))
200
- ]);
201
- }
202
- function partialSyncConnection(room) {
203
- sendToPanel({
204
- msg: "room::sync::partial",
205
- roomId: room.id,
206
- status: room.getStatus()
207
- });
208
- }
209
- function partialSyncStorage(room) {
210
- const root = room.getStorageSnapshot();
211
- if (root) {
212
- sendToPanel({
213
- msg: "room::sync::partial",
214
- roomId: room.id,
215
- storage: root.toTreeNode("root").payload
216
- });
217
- }
218
- }
219
- function partialSyncMe(room) {
220
- const me = room.__internal.getSelf_forDevTools();
221
- if (me) {
222
- sendToPanel({
223
- msg: "room::sync::partial",
224
- roomId: room.id,
225
- me
226
- });
227
- }
228
- }
229
- function partialSyncOthers(room) {
230
- const others = room.__internal.getOthers_forDevTools();
231
- if (others) {
232
- sendToPanel({
233
- msg: "room::sync::partial",
234
- roomId: room.id,
235
- others
236
- });
237
- }
238
- }
239
- function fullSync(room) {
240
- const root = room.getStorageSnapshot();
241
- const me = room.__internal.getSelf_forDevTools();
242
- const others = room.__internal.getOthers_forDevTools();
243
- sendToPanel({
244
- msg: "room::sync::full",
245
- roomId: room.id,
246
- status: room.getStatus(),
247
- storage: root?.toTreeNode("root").payload ?? null,
248
- me,
249
- others
250
- });
251
- }
252
- var roomChannelListeners = /* @__PURE__ */ new Map();
253
- function stopRoomChannelListener(roomId) {
254
- const listener = roomChannelListeners.get(roomId);
255
- roomChannelListeners.delete(roomId);
256
- if (listener) {
257
- listener();
258
- }
259
- }
260
- function linkDevTools(roomId, room) {
261
- if (process.env.NODE_ENV === "production" || typeof window === "undefined") {
262
- return;
263
- }
264
- sendToPanel({ msg: "room::available", roomId, clientVersion: VERSION });
265
- stopRoomChannelListener(roomId);
266
- roomChannelListeners.set(
267
- roomId,
268
- // Returns the unsubscribe callback, that we store in the
269
- // roomChannelListeners registry
270
- onMessageFromPanel.subscribe((msg) => {
271
- switch (msg.msg) {
272
- case "room::subscribe": {
273
- if (msg.roomId === roomId) {
274
- startSyncStream(room);
275
- }
276
- break;
277
- }
278
- case "room::unsubscribe": {
279
- if (msg.roomId === roomId) {
280
- stopSyncStream(roomId);
281
- }
282
- break;
283
- }
284
- }
285
- })
286
- );
287
- }
288
- function unlinkDevTools(roomId) {
289
- if (process.env.NODE_ENV === "production" || typeof window === "undefined") {
290
- return;
291
- }
292
- stopSyncStream(roomId);
293
- stopRoomChannelListener(roomId);
294
- sendToPanel({
295
- msg: "room::unavailable",
296
- roomId
297
- });
298
- }
299
-
300
158
  // src/lib/fancy-console.ts
159
+ var fancy_console_exports = {};
160
+ __export(fancy_console_exports, {
161
+ error: () => error2,
162
+ errorWithTitle: () => errorWithTitle,
163
+ warn: () => warn,
164
+ warnWithTitle: () => warnWithTitle
165
+ });
301
166
  var badge = "background:#0e0d12;border-radius:9999px;color:#fff;padding:3px 7px;font-family:sans-serif;font-weight:600;";
302
167
  var bold = "font-weight:600";
303
168
  function wrap(method) {
@@ -323,57 +188,6 @@ function wrapWithTitle(method) {
323
188
  var warnWithTitle = wrapWithTitle("warn");
324
189
  var errorWithTitle = wrapWithTitle("error");
325
190
 
326
- // src/lib/deprecation.ts
327
- var _emittedDeprecationWarnings = /* @__PURE__ */ new Set();
328
- function deprecate(message, key = message) {
329
- if (process.env.NODE_ENV !== "production") {
330
- if (!_emittedDeprecationWarnings.has(key)) {
331
- _emittedDeprecationWarnings.add(key);
332
- errorWithTitle("Deprecation warning", message);
333
- }
334
- }
335
- }
336
- function deprecateIf(condition, message, key = message) {
337
- if (process.env.NODE_ENV !== "production") {
338
- if (condition) {
339
- deprecate(message, key);
340
- }
341
- }
342
- }
343
- function throwUsageError(message) {
344
- if (process.env.NODE_ENV !== "production") {
345
- const usageError = new Error(message);
346
- usageError.name = "Usage error";
347
- errorWithTitle("Usage error", message);
348
- throw usageError;
349
- }
350
- }
351
- function errorIf(condition, message) {
352
- if (process.env.NODE_ENV !== "production") {
353
- if (condition) {
354
- throwUsageError(message);
355
- }
356
- }
357
- }
358
-
359
- // src/lib/assert.ts
360
- function assertNever(_value, errmsg) {
361
- throw new Error(errmsg);
362
- }
363
- function assert(condition, errmsg) {
364
- if (process.env.NODE_ENV !== "production") {
365
- if (!condition) {
366
- const err = new Error(errmsg);
367
- err.name = "Assertion failure";
368
- throw err;
369
- }
370
- }
371
- }
372
- function nn(value, errmsg = "Expected value to be non-nullable") {
373
- assert(value !== null && value !== void 0, errmsg);
374
- return value;
375
- }
376
-
377
191
  // src/lib/fsm.ts
378
192
  function distance(state1, state2) {
379
193
  if (state1 === state2) {
@@ -812,9 +626,48 @@ async function withTimeout(promise, millis, errmsg = "Timed out") {
812
626
  return Promise.race([promise, timer$]).finally(() => clearTimeout(timerID));
813
627
  }
814
628
 
815
- // src/connection.ts
816
- function newToLegacyStatus(status) {
817
- switch (status) {
629
+ // src/protocol/ServerMsg.ts
630
+ var ServerMsgCode = /* @__PURE__ */ ((ServerMsgCode2) => {
631
+ ServerMsgCode2[ServerMsgCode2["UPDATE_PRESENCE"] = 100] = "UPDATE_PRESENCE";
632
+ ServerMsgCode2[ServerMsgCode2["USER_JOINED"] = 101] = "USER_JOINED";
633
+ ServerMsgCode2[ServerMsgCode2["USER_LEFT"] = 102] = "USER_LEFT";
634
+ ServerMsgCode2[ServerMsgCode2["BROADCASTED_EVENT"] = 103] = "BROADCASTED_EVENT";
635
+ ServerMsgCode2[ServerMsgCode2["ROOM_STATE"] = 104] = "ROOM_STATE";
636
+ ServerMsgCode2[ServerMsgCode2["INITIAL_STORAGE_STATE"] = 200] = "INITIAL_STORAGE_STATE";
637
+ ServerMsgCode2[ServerMsgCode2["UPDATE_STORAGE"] = 201] = "UPDATE_STORAGE";
638
+ ServerMsgCode2[ServerMsgCode2["REJECT_STORAGE_OP"] = 299] = "REJECT_STORAGE_OP";
639
+ ServerMsgCode2[ServerMsgCode2["UPDATE_YDOC"] = 300] = "UPDATE_YDOC";
640
+ return ServerMsgCode2;
641
+ })(ServerMsgCode || {});
642
+
643
+ // src/types/IWebSocket.ts
644
+ var WebsocketCloseCodes = /* @__PURE__ */ ((WebsocketCloseCodes2) => {
645
+ WebsocketCloseCodes2[WebsocketCloseCodes2["CLOSE_ABNORMAL"] = 1006] = "CLOSE_ABNORMAL";
646
+ WebsocketCloseCodes2[WebsocketCloseCodes2["UNEXPECTED_CONDITION"] = 1011] = "UNEXPECTED_CONDITION";
647
+ WebsocketCloseCodes2[WebsocketCloseCodes2["TRY_AGAIN_LATER"] = 1013] = "TRY_AGAIN_LATER";
648
+ WebsocketCloseCodes2[WebsocketCloseCodes2["INVALID_MESSAGE_FORMAT"] = 4e3] = "INVALID_MESSAGE_FORMAT";
649
+ WebsocketCloseCodes2[WebsocketCloseCodes2["NOT_ALLOWED"] = 4001] = "NOT_ALLOWED";
650
+ WebsocketCloseCodes2[WebsocketCloseCodes2["MAX_NUMBER_OF_MESSAGES_PER_SECONDS"] = 4002] = "MAX_NUMBER_OF_MESSAGES_PER_SECONDS";
651
+ WebsocketCloseCodes2[WebsocketCloseCodes2["MAX_NUMBER_OF_CONCURRENT_CONNECTIONS"] = 4003] = "MAX_NUMBER_OF_CONCURRENT_CONNECTIONS";
652
+ WebsocketCloseCodes2[WebsocketCloseCodes2["MAX_NUMBER_OF_MESSAGES_PER_DAY_PER_APP"] = 4004] = "MAX_NUMBER_OF_MESSAGES_PER_DAY_PER_APP";
653
+ WebsocketCloseCodes2[WebsocketCloseCodes2["MAX_NUMBER_OF_CONCURRENT_CONNECTIONS_PER_ROOM"] = 4005] = "MAX_NUMBER_OF_CONCURRENT_CONNECTIONS_PER_ROOM";
654
+ WebsocketCloseCodes2[WebsocketCloseCodes2["TOKEN_EXPIRED"] = 4109] = "TOKEN_EXPIRED";
655
+ WebsocketCloseCodes2[WebsocketCloseCodes2["CLOSE_WITHOUT_RETRY"] = 4999] = "CLOSE_WITHOUT_RETRY";
656
+ return WebsocketCloseCodes2;
657
+ })(WebsocketCloseCodes || {});
658
+ function shouldDisconnect(code) {
659
+ return code === 4999 /* CLOSE_WITHOUT_RETRY */ || code >= 4e3 && code < 4100;
660
+ }
661
+ function shouldReauth(code) {
662
+ return code >= 4100 && code < 4200;
663
+ }
664
+ function shouldRetryWithoutReauth(code) {
665
+ return code === 1013 /* TRY_AGAIN_LATER */ || code >= 4200 && code < 4300;
666
+ }
667
+
668
+ // src/connection.ts
669
+ function newToLegacyStatus(status) {
670
+ switch (status) {
818
671
  case "connecting":
819
672
  return "connecting";
820
673
  case "connected":
@@ -916,9 +769,6 @@ var logPermanentClose = log(
916
769
  function isCloseEvent(error3) {
917
770
  return !(error3 instanceof Error) && error3.type === "close";
918
771
  }
919
- function isCustomCloseEvent(error3) {
920
- return isCloseEvent(error3) && error3.code >= 4e3 && error3.code < 4100;
921
- }
922
772
  function enableTracing(machine) {
923
773
  const start = (/* @__PURE__ */ new Date()).getTime();
924
774
  function log2(...args) {
@@ -969,13 +819,19 @@ function defineConnectivityEvents(machine) {
969
819
  };
970
820
  }
971
821
  var assign = (patch) => (ctx) => ctx.patch(patch);
972
- function createConnectionStateMachine(delegates, enableDebugLogging) {
822
+ function createConnectionStateMachine(delegates, options) {
973
823
  const onMessage = makeEventSource();
974
824
  onMessage.pause();
975
825
  const onLiveblocksError = makeEventSource();
826
+ function fireErrorEvent(errmsg, errcode) {
827
+ return () => {
828
+ const err = new LiveblocksError(errmsg, errcode);
829
+ onLiveblocksError.notify(err);
830
+ };
831
+ }
976
832
  const initialContext = {
977
833
  successCount: 0,
978
- token: null,
834
+ authValue: null,
979
835
  socket: null,
980
836
  backoffDelay: RESET_DELAY
981
837
  };
@@ -989,9 +845,9 @@ function createConnectionStateMachine(delegates, enableDebugLogging) {
989
845
  });
990
846
  machine.onEnter("@idle.*", resetSuccessCount).addTransitions("@idle.*", {
991
847
  CONNECT: (_, ctx) => (
992
- // If we still have a known token, try to reconnect to the socket directly,
993
- // otherwise, try to obtain a new token
994
- ctx.token !== null ? "@connecting.busy" : "@auth.busy"
848
+ // If we still have a known authValue, try to reconnect to the socket directly,
849
+ // otherwise, try to obtain a new authValue
850
+ ctx.authValue !== null ? "@connecting.busy" : "@auth.busy"
995
851
  )
996
852
  });
997
853
  machine.addTransitions("@auth.backoff", {
@@ -1010,7 +866,7 @@ function createConnectionStateMachine(delegates, enableDebugLogging) {
1010
866
  (okEvent) => ({
1011
867
  target: "@connecting.busy",
1012
868
  effect: assign({
1013
- token: okEvent.data,
869
+ authValue: okEvent.data,
1014
870
  backoffDelay: RESET_DELAY
1015
871
  })
1016
872
  }),
@@ -1019,7 +875,10 @@ function createConnectionStateMachine(delegates, enableDebugLogging) {
1019
875
  if (failedEvent.reason instanceof StopRetrying) {
1020
876
  return {
1021
877
  target: "@idle.failed",
1022
- effect: log(2 /* ERROR */, failedEvent.reason.message)
878
+ effect: [
879
+ log(2 /* ERROR */, failedEvent.reason.message),
880
+ fireErrorEvent(failedEvent.reason.message, -1)
881
+ ]
1023
882
  };
1024
883
  }
1025
884
  return {
@@ -1069,16 +928,29 @@ function createConnectionStateMachine(delegates, enableDebugLogging) {
1069
928
  let capturedPrematureEvent = null;
1070
929
  const connect$ = new Promise(
1071
930
  (resolve, rej) => {
1072
- if (ctx.token === null) {
1073
- throw new Error("No auth token");
931
+ if (ctx.authValue === null) {
932
+ throw new Error("No auth authValue");
1074
933
  }
1075
- const socket = delegates.createSocket(ctx.token);
934
+ const socket = delegates.createSocket(ctx.authValue);
1076
935
  function reject(event) {
1077
936
  capturedPrematureEvent = event;
1078
937
  socket.removeEventListener("message", onSocketMessage);
1079
938
  rej(event);
1080
939
  }
940
+ const [actor$, didReceiveActor] = controlledPromise();
941
+ if (!options.waitForActorId) {
942
+ didReceiveActor();
943
+ }
944
+ function waitForActorId(event) {
945
+ const serverMsg = tryParseJson(event.data);
946
+ if (serverMsg?.type === 104 /* ROOM_STATE */) {
947
+ didReceiveActor();
948
+ }
949
+ }
1081
950
  socket.addEventListener("message", onSocketMessage);
951
+ if (options.waitForActorId) {
952
+ socket.addEventListener("message", waitForActorId);
953
+ }
1082
954
  socket.addEventListener("error", reject);
1083
955
  socket.addEventListener("close", reject);
1084
956
  socket.addEventListener("open", () => {
@@ -1087,8 +959,11 @@ function createConnectionStateMachine(delegates, enableDebugLogging) {
1087
959
  const unsub = () => {
1088
960
  socket.removeEventListener("error", reject);
1089
961
  socket.removeEventListener("close", reject);
962
+ socket.removeEventListener("message", waitForActorId);
1090
963
  };
1091
- resolve([socket, unsub]);
964
+ void actor$.then(() => {
965
+ resolve([socket, unsub]);
966
+ });
1092
967
  });
1093
968
  }
1094
969
  );
@@ -1129,24 +1004,35 @@ function createConnectionStateMachine(delegates, enableDebugLogging) {
1129
1004
  if (err instanceof StopRetrying) {
1130
1005
  return {
1131
1006
  target: "@idle.failed",
1132
- effect: log(2 /* ERROR */, err.message)
1133
- };
1134
- }
1135
- if (isCloseEvent(err) && err.code === 4999) {
1136
- return {
1137
- target: "@idle.failed",
1138
- effect: log(2 /* ERROR */, err.reason)
1139
- };
1140
- }
1141
- if (isCustomCloseEvent(err) && err.code !== 4001) {
1142
- return {
1143
- target: "@connecting.backoff",
1144
1007
  effect: [
1145
- increaseBackoffDelayAggressively,
1146
- logPrematureErrorOrCloseEvent(err)
1008
+ log(2 /* ERROR */, err.message),
1009
+ fireErrorEvent(err.message, -1)
1147
1010
  ]
1148
1011
  };
1149
1012
  }
1013
+ if (isCloseEvent(err)) {
1014
+ if (err.code === 4109 /* TOKEN_EXPIRED */) {
1015
+ return "@auth.busy";
1016
+ }
1017
+ if (shouldRetryWithoutReauth(err.code)) {
1018
+ return {
1019
+ target: "@connecting.backoff",
1020
+ effect: [
1021
+ increaseBackoffDelayAggressively,
1022
+ logPrematureErrorOrCloseEvent(err)
1023
+ ]
1024
+ };
1025
+ }
1026
+ if (shouldDisconnect(err.code)) {
1027
+ return {
1028
+ target: "@idle.failed",
1029
+ effect: [
1030
+ log(2 /* ERROR */, err.reason),
1031
+ fireErrorEvent(err.reason, err.code)
1032
+ ]
1033
+ };
1034
+ }
1035
+ }
1150
1036
  return {
1151
1037
  target: "@auth.backoff",
1152
1038
  effect: [increaseBackoffDelay, logPrematureErrorOrCloseEvent(err)]
@@ -1200,29 +1086,29 @@ function createConnectionStateMachine(delegates, enableDebugLogging) {
1200
1086
  };
1201
1087
  },
1202
1088
  EXPLICIT_SOCKET_CLOSE: (e) => {
1203
- if (e.event.code === 4999) {
1089
+ if (shouldDisconnect(e.event.code)) {
1204
1090
  return {
1205
1091
  target: "@idle.failed",
1206
- effect: logPermanentClose
1092
+ effect: [
1093
+ logPermanentClose,
1094
+ fireErrorEvent(e.event.reason, e.event.code)
1095
+ ]
1207
1096
  };
1208
1097
  }
1209
- if (e.event.code === 4001) {
1210
- return {
1211
- target: "@auth.backoff",
1212
- effect: [increaseBackoffDelay, logCloseEvent(e.event)]
1213
- };
1098
+ if (shouldReauth(e.event.code)) {
1099
+ if (e.event.code === 4109 /* TOKEN_EXPIRED */) {
1100
+ return "@auth.busy";
1101
+ } else {
1102
+ return {
1103
+ target: "@auth.backoff",
1104
+ effect: [increaseBackoffDelay, logCloseEvent(e.event)]
1105
+ };
1106
+ }
1214
1107
  }
1215
- if (isCustomCloseEvent(e.event)) {
1108
+ if (shouldRetryWithoutReauth(e.event.code)) {
1216
1109
  return {
1217
1110
  target: "@connecting.backoff",
1218
- effect: [
1219
- increaseBackoffDelayAggressively,
1220
- logCloseEvent(e.event),
1221
- () => {
1222
- const err = new LiveblocksError(e.event.reason, e.event.code);
1223
- onLiveblocksError.notify(err);
1224
- }
1225
- ]
1111
+ effect: [increaseBackoffDelayAggressively, logCloseEvent(e.event)]
1226
1112
  };
1227
1113
  }
1228
1114
  return {
@@ -1261,7 +1147,7 @@ function createConnectionStateMachine(delegates, enableDebugLogging) {
1261
1147
  const cleanups = [];
1262
1148
  const { statusDidChange, didConnect, didDisconnect, unsubscribe } = defineConnectivityEvents(machine);
1263
1149
  cleanups.push(unsubscribe);
1264
- if (enableDebugLogging) {
1150
+ if (options.enableDebugLogging) {
1265
1151
  cleanups.push(enableTracing(machine));
1266
1152
  }
1267
1153
  machine.start();
@@ -1279,10 +1165,10 @@ function createConnectionStateMachine(delegates, enableDebugLogging) {
1279
1165
  };
1280
1166
  }
1281
1167
  var ManagedSocket = class {
1282
- constructor(delegates, enableDebugLogging = false) {
1168
+ constructor(delegates, enableDebugLogging = false, waitForActorId = true) {
1283
1169
  const { machine, events, cleanups } = createConnectionStateMachine(
1284
1170
  delegates,
1285
- enableDebugLogging
1171
+ { waitForActorId, enableDebugLogging }
1286
1172
  );
1287
1173
  this.machine = machine;
1288
1174
  this.events = events;
@@ -1299,10 +1185,10 @@ var ManagedSocket = class {
1299
1185
  }
1300
1186
  }
1301
1187
  /**
1302
- * Returns the current auth token.
1188
+ * Returns the current auth authValue.
1303
1189
  */
1304
- get token() {
1305
- return this.machine.context.token;
1190
+ get authValue() {
1191
+ return this.machine.context.authValue;
1306
1192
  }
1307
1193
  /**
1308
1194
  * Call this method to try to connect to a WebSocket. This only has an effect
@@ -1313,7 +1199,7 @@ var ManagedSocket = class {
1313
1199
  }
1314
1200
  /**
1315
1201
  * If idle, will try to connect. Otherwise, it will attempt to reconnect to
1316
- * the socket, potentially obtaining a new token first, if needed.
1202
+ * the socket, potentially obtaining a new authValue first, if needed.
1317
1203
  */
1318
1204
  reconnect() {
1319
1205
  this.machine.send({ type: "RECONNECT" });
@@ -1360,124 +1246,635 @@ var ManagedSocket = class {
1360
1246
  }
1361
1247
  };
1362
1248
 
1363
- // src/lib/position.ts
1364
- var MIN_CODE = 32;
1365
- var MAX_CODE = 126;
1366
- var NUM_DIGITS = MAX_CODE - MIN_CODE + 1;
1367
- var ZERO = nthDigit(0);
1368
- var ONE = nthDigit(1);
1369
- var ZERO_NINE = ZERO + nthDigit(-1);
1370
- function nthDigit(n) {
1371
- const code = MIN_CODE + (n < 0 ? NUM_DIGITS + n : n);
1372
- if (code < MIN_CODE || code > MAX_CODE) {
1373
- throw new Error(`Invalid n value: ${n}`);
1374
- }
1375
- return String.fromCharCode(code);
1249
+ // src/protocol/AuthToken.ts
1250
+ function canWriteStorage(scopes) {
1251
+ return scopes.includes("room:write" /* Write */);
1376
1252
  }
1377
- function makePosition(x, y) {
1378
- if (x !== void 0 && y !== void 0) {
1379
- return between(x, y);
1380
- } else if (x !== void 0) {
1381
- return after(x);
1382
- } else if (y !== void 0) {
1383
- return before(y);
1384
- } else {
1385
- return ONE;
1253
+ function isValidAuthTokenPayload(data) {
1254
+ return isPlainObject(data) && (data.k === "acc" /* ACCESS_TOKEN */ || data.k === "id" /* ID_TOKEN */ || data.k === "sec-legacy" /* SECRET_LEGACY */);
1255
+ }
1256
+ function parseAuthToken(rawTokenString) {
1257
+ const tokenParts = rawTokenString.split(".");
1258
+ if (tokenParts.length !== 3) {
1259
+ throw new Error("Authentication error: invalid JWT token");
1260
+ }
1261
+ const payload = tryParseJson(b64decode(tokenParts[1]));
1262
+ if (!(payload && isValidAuthTokenPayload(payload))) {
1263
+ throw new Error(
1264
+ "Authentication error: expected a valid token but did not get one. Hint: if you are using a callback, ensure the room is passed when creating the token. For more information: https://liveblocks.io/docs/api-reference/liveblocks-client#createClientCallback"
1265
+ );
1386
1266
  }
1267
+ return {
1268
+ raw: rawTokenString,
1269
+ parsed: payload
1270
+ };
1387
1271
  }
1388
- function before(pos) {
1389
- const lastIndex = pos.length - 1;
1390
- for (let i = 0; i <= lastIndex; i++) {
1391
- const code = pos.charCodeAt(i);
1392
- if (code <= MIN_CODE) {
1393
- continue;
1272
+
1273
+ // src/auth-manager.ts
1274
+ function createAuthManager(authOptions) {
1275
+ const authentication = prepareAuthentication(authOptions);
1276
+ const tokens = [];
1277
+ const expiryTimes = [];
1278
+ const requestPromises = /* @__PURE__ */ new Map();
1279
+ function hasCorrespondingScopes(requestedScope, scopes) {
1280
+ if (requestedScope === "comments:read") {
1281
+ return scopes.includes("comments:read" /* CommentsRead */) || scopes.includes("comments:write" /* CommentsWrite */) || scopes.includes("room:read" /* Read */) || scopes.includes("room:write" /* Write */);
1282
+ } else if (requestedScope === "room:read") {
1283
+ return scopes.includes("room:read" /* Read */) || scopes.includes("room:write" /* Write */);
1394
1284
  }
1395
- if (i === lastIndex) {
1396
- if (code === MIN_CODE + 1) {
1397
- return pos.substring(0, i) + ZERO_NINE;
1398
- } else {
1399
- return pos.substring(0, i) + String.fromCharCode(code - 1);
1285
+ return false;
1286
+ }
1287
+ function getCachedToken(requestedScope, roomId) {
1288
+ const now = Math.ceil(Date.now() / 1e3);
1289
+ for (let i = tokens.length - 1; i >= 0; i--) {
1290
+ const token = tokens[i];
1291
+ const expiresAt = expiryTimes[i];
1292
+ if (expiresAt <= now) {
1293
+ tokens.splice(i, 1);
1294
+ expiryTimes.splice(i, 1);
1295
+ continue;
1296
+ }
1297
+ if (token.parsed.k === "id" /* ID_TOKEN */) {
1298
+ return token;
1299
+ } else if (token.parsed.k === "sec-legacy" /* SECRET_LEGACY */) {
1300
+ return void 0;
1301
+ } else if (token.parsed.k === "acc" /* ACCESS_TOKEN */) {
1302
+ for (const [resource, scopes] of Object.entries(token.parsed.perms)) {
1303
+ if (resource.includes("*") && roomId.startsWith(resource.replace("*", "")) || roomId === resource && hasCorrespondingScopes(requestedScope, scopes)) {
1304
+ return token;
1305
+ }
1306
+ }
1400
1307
  }
1401
- } else {
1402
- return pos.substring(0, i + 1);
1403
1308
  }
1309
+ return void 0;
1404
1310
  }
1405
- return ONE;
1406
- }
1407
- function after(pos) {
1408
- for (let i = 0; i <= pos.length - 1; i++) {
1409
- const code = pos.charCodeAt(i);
1410
- if (code >= MAX_CODE) {
1411
- continue;
1311
+ async function makeAuthRequest(roomId) {
1312
+ const fetcher = authOptions.polyfills?.fetch ?? (typeof window === "undefined" ? void 0 : window.fetch);
1313
+ if (authentication.type === "private") {
1314
+ if (fetcher === void 0) {
1315
+ throw new StopRetrying(
1316
+ "To use Liveblocks client in a non-dom environment with a url as auth endpoint, you need to provide a fetch polyfill."
1317
+ );
1318
+ }
1319
+ const response = await fetchAuthEndpoint(fetcher, authentication.url, {
1320
+ room: roomId
1321
+ });
1322
+ return parseAuthToken(response.token);
1412
1323
  }
1413
- return pos.substring(0, i) + String.fromCharCode(code + 1);
1414
- }
1415
- return pos + ONE;
1416
- }
1417
- function between(lo, hi) {
1418
- if (lo < hi) {
1419
- return _between(lo, hi);
1420
- } else if (lo > hi) {
1421
- return _between(hi, lo);
1422
- } else {
1423
- throw new Error("Cannot compute value between two equal positions");
1324
+ if (authentication.type === "custom") {
1325
+ const response = await authentication.callback(roomId);
1326
+ if (!response || typeof response !== "object") {
1327
+ throw new Error(
1328
+ 'We expect the authentication callback to return a token, but it does not. Hint: the return value should look like: { token: "..." }'
1329
+ );
1330
+ }
1331
+ if (typeof response.token === "string") {
1332
+ return parseAuthToken(response.token);
1333
+ } else if (typeof response.error === "string") {
1334
+ const reason = `Authentication failed: ${"reason" in response && typeof response.reason === "string" ? response.reason : "Forbidden"}`;
1335
+ if (response.error === "forbidden") {
1336
+ throw new StopRetrying(reason);
1337
+ } else {
1338
+ throw new Error(reason);
1339
+ }
1340
+ } else {
1341
+ throw new Error(
1342
+ 'We expect the authentication callback to return a token, but it does not. Hint: the return value should look like: { token: "..." }'
1343
+ );
1344
+ }
1345
+ }
1346
+ throw new Error("Invalid Liveblocks client options");
1424
1347
  }
1425
- }
1426
- function _between(lo, hi) {
1427
- let index = 0;
1428
- const loLen = lo.length;
1429
- const hiLen = hi.length;
1430
- while (true) {
1431
- const loCode = index < loLen ? lo.charCodeAt(index) : MIN_CODE;
1432
- const hiCode = index < hiLen ? hi.charCodeAt(index) : MAX_CODE;
1433
- if (loCode === hiCode) {
1434
- index++;
1435
- continue;
1348
+ async function getAuthValue(requestedScope, roomId) {
1349
+ if (authentication.type === "public") {
1350
+ return { type: "public", publicApiKey: authentication.publicApiKey };
1436
1351
  }
1437
- if (hiCode - loCode === 1) {
1438
- const prefix = lo.substring(0, index + 1);
1439
- const suffix = lo.substring(index + 1);
1440
- const nines = "";
1441
- return prefix + _between(suffix, nines);
1442
- } else {
1443
- return takeN(lo, index) + String.fromCharCode(hiCode + loCode >> 1);
1352
+ const cachedToken = getCachedToken(requestedScope, roomId);
1353
+ if (cachedToken !== void 0) {
1354
+ return { type: "secret", token: cachedToken };
1355
+ }
1356
+ let currentPromise = requestPromises.get(roomId);
1357
+ if (currentPromise === void 0) {
1358
+ currentPromise = makeAuthRequest(roomId);
1359
+ requestPromises.set(roomId, currentPromise);
1360
+ }
1361
+ try {
1362
+ const token = await currentPromise;
1363
+ const BUFFER = 30;
1364
+ const expiresAt = Math.floor(Date.now() / 1e3) + (token.parsed.exp - token.parsed.iat) - BUFFER;
1365
+ tokens.push(token);
1366
+ expiryTimes.push(expiresAt);
1367
+ return { type: "secret", token };
1368
+ } finally {
1369
+ requestPromises.delete(roomId);
1444
1370
  }
1445
1371
  }
1372
+ return {
1373
+ getAuthValue
1374
+ };
1446
1375
  }
1447
- function takeN(pos, n) {
1448
- return n < pos.length ? pos.substring(0, n) : pos + ZERO.repeat(n - pos.length);
1449
- }
1450
- var MIN_NON_ZERO_CODE = MIN_CODE + 1;
1451
- function isPos(str) {
1452
- if (str === "") {
1453
- return false;
1454
- }
1455
- const lastIdx = str.length - 1;
1456
- const last = str.charCodeAt(lastIdx);
1457
- if (last < MIN_NON_ZERO_CODE || last > MAX_CODE) {
1458
- return false;
1376
+ function prepareAuthentication(authOptions) {
1377
+ const { publicApiKey, authEndpoint } = authOptions;
1378
+ if (authEndpoint !== void 0 && publicApiKey !== void 0) {
1379
+ throw new Error(
1380
+ "You cannot use both publicApiKey and authEndpoint. Please use either publicApiKey or authEndpoint, but not both. For more information: https://liveblocks.io/docs/api-reference/liveblocks-client#createClient"
1381
+ );
1459
1382
  }
1460
- for (let i = 0; i < lastIdx; i++) {
1461
- const code = str.charCodeAt(i);
1462
- if (code < MIN_CODE || code > MAX_CODE) {
1463
- return false;
1383
+ if (typeof publicApiKey === "string") {
1384
+ if (publicApiKey.startsWith("sk_")) {
1385
+ throw new Error(
1386
+ "Invalid publicApiKey. You are using the secret key which is not supported. Please use the public key instead. For more information: https://liveblocks.io/docs/api-reference/liveblocks-client#createClientPublicKey"
1387
+ );
1388
+ } else if (!publicApiKey.startsWith("pk_")) {
1389
+ throw new Error(
1390
+ "Invalid key. Please use the public key format: pk_<public key>. For more information: https://liveblocks.io/docs/api-reference/liveblocks-client#createClientPublicKey"
1391
+ );
1464
1392
  }
1393
+ return {
1394
+ type: "public",
1395
+ publicApiKey
1396
+ };
1465
1397
  }
1466
- return true;
1467
- }
1468
- function convertToPos(str) {
1469
- const codes = [];
1470
- for (let i = 0; i < str.length; i++) {
1471
- const code = str.charCodeAt(i);
1472
- codes.push(code < MIN_CODE ? MIN_CODE : code > MAX_CODE ? MAX_CODE : code);
1473
- }
1474
- while (codes.length > 0 && codes[codes.length - 1] === MIN_CODE) {
1475
- codes.length--;
1398
+ if (typeof authEndpoint === "string") {
1399
+ return {
1400
+ type: "private",
1401
+ url: authEndpoint
1402
+ };
1403
+ } else if (typeof authEndpoint === "function") {
1404
+ return {
1405
+ type: "custom",
1406
+ callback: authEndpoint
1407
+ };
1408
+ } else if (authEndpoint !== void 0) {
1409
+ throw new Error(
1410
+ "authEndpoint must be a string or a function. For more information: https://liveblocks.io/docs/api-reference/liveblocks-client#createClientAuthEndpoint"
1411
+ );
1476
1412
  }
1477
- return codes.length > 0 ? String.fromCharCode(...codes) : (
1478
- // Edge case: the str was a 0-only string, which is invalid. Default back to .1
1479
- ONE
1480
- );
1413
+ throw new Error(
1414
+ "Invalid Liveblocks client options. For more information: https://liveblocks.io/docs/api-reference/liveblocks-client#createClient"
1415
+ );
1416
+ }
1417
+ async function fetchAuthEndpoint(fetch2, endpoint, body) {
1418
+ const res = await fetch2(endpoint, {
1419
+ method: "POST",
1420
+ headers: {
1421
+ "Content-Type": "application/json"
1422
+ },
1423
+ body: JSON.stringify(body)
1424
+ });
1425
+ if (!res.ok) {
1426
+ const reason = `${(await res.text()).trim() || "reason not provided in auth response"} (${res.status} returned by POST ${endpoint})`;
1427
+ if (res.status === 401 || res.status === 403) {
1428
+ throw new StopRetrying(`Unauthorized: ${reason}`);
1429
+ } else {
1430
+ throw new Error(`Failed to authenticate: ${reason}`);
1431
+ }
1432
+ }
1433
+ let data;
1434
+ try {
1435
+ data = await res.json();
1436
+ } catch (er) {
1437
+ throw new Error(
1438
+ `Expected a JSON response when doing a POST request on "${endpoint}". ${String(
1439
+ er
1440
+ )}`
1441
+ );
1442
+ }
1443
+ if (!isPlainObject(data) || typeof data.token !== "string") {
1444
+ throw new Error(
1445
+ `Expected a JSON response of the form \`{ token: "..." }\` when doing a POST request on "${endpoint}", but got ${JSON.stringify(
1446
+ data
1447
+ )}`
1448
+ );
1449
+ }
1450
+ const { token } = data;
1451
+ return { token };
1452
+ }
1453
+
1454
+ // src/devtools/bridge.ts
1455
+ var _bridgeActive = false;
1456
+ function activateBridge(allowed) {
1457
+ _bridgeActive = allowed;
1458
+ }
1459
+ function sendToPanel(message, options) {
1460
+ if (process.env.NODE_ENV === "production" || typeof window === "undefined") {
1461
+ return;
1462
+ }
1463
+ const fullMsg = {
1464
+ ...message,
1465
+ source: "liveblocks-devtools-client"
1466
+ };
1467
+ if (!(options?.force || _bridgeActive)) {
1468
+ return;
1469
+ }
1470
+ window.postMessage(fullMsg, "*");
1471
+ }
1472
+ var eventSource = makeEventSource();
1473
+ if (process.env.NODE_ENV !== "production" && typeof window !== "undefined") {
1474
+ window.addEventListener("message", (event) => {
1475
+ if (event.source === window && event.data?.source === "liveblocks-devtools-panel") {
1476
+ eventSource.notify(event.data);
1477
+ } else {
1478
+ }
1479
+ });
1480
+ }
1481
+ var onMessageFromPanel = eventSource.observable;
1482
+
1483
+ // src/devtools/index.ts
1484
+ var VERSION = PKG_VERSION || "dev";
1485
+ var _devtoolsSetupHasRun = false;
1486
+ function setupDevTools(getAllRooms) {
1487
+ if (process.env.NODE_ENV === "production" || typeof window === "undefined") {
1488
+ return;
1489
+ }
1490
+ if (_devtoolsSetupHasRun) {
1491
+ return;
1492
+ }
1493
+ _devtoolsSetupHasRun = true;
1494
+ onMessageFromPanel.subscribe((msg) => {
1495
+ switch (msg.msg) {
1496
+ case "connect": {
1497
+ activateBridge(true);
1498
+ for (const roomId of getAllRooms()) {
1499
+ sendToPanel({
1500
+ msg: "room::available",
1501
+ roomId,
1502
+ clientVersion: VERSION
1503
+ });
1504
+ }
1505
+ break;
1506
+ }
1507
+ }
1508
+ });
1509
+ sendToPanel({ msg: "wake-up-devtools" }, { force: true });
1510
+ }
1511
+ var unsubsByRoomId = /* @__PURE__ */ new Map();
1512
+ function stopSyncStream(roomId) {
1513
+ const unsubs = unsubsByRoomId.get(roomId) ?? [];
1514
+ unsubsByRoomId.delete(roomId);
1515
+ for (const unsub of unsubs) {
1516
+ unsub();
1517
+ }
1518
+ }
1519
+ function startSyncStream(room) {
1520
+ stopSyncStream(room.id);
1521
+ fullSync(room);
1522
+ unsubsByRoomId.set(room.id, [
1523
+ // When the connection status changes
1524
+ room.events.status.subscribe(() => partialSyncConnection(room)),
1525
+ // When storage initializes, send the update
1526
+ room.events.storageDidLoad.subscribeOnce(() => partialSyncStorage(room)),
1527
+ // Any time storage updates, send the new storage root
1528
+ room.events.storage.subscribe(() => partialSyncStorage(room)),
1529
+ // Any time "me" or "others" updates, send the new values accordingly
1530
+ room.events.self.subscribe(() => partialSyncMe(room)),
1531
+ room.events.others.subscribe(() => partialSyncOthers(room))
1532
+ ]);
1533
+ }
1534
+ function partialSyncConnection(room) {
1535
+ sendToPanel({
1536
+ msg: "room::sync::partial",
1537
+ roomId: room.id,
1538
+ status: room.getStatus()
1539
+ });
1540
+ }
1541
+ function partialSyncStorage(room) {
1542
+ const root = room.getStorageSnapshot();
1543
+ if (root) {
1544
+ sendToPanel({
1545
+ msg: "room::sync::partial",
1546
+ roomId: room.id,
1547
+ storage: root.toTreeNode("root").payload
1548
+ });
1549
+ }
1550
+ }
1551
+ function partialSyncMe(room) {
1552
+ const me = room.__internal.getSelf_forDevTools();
1553
+ if (me) {
1554
+ sendToPanel({
1555
+ msg: "room::sync::partial",
1556
+ roomId: room.id,
1557
+ me
1558
+ });
1559
+ }
1560
+ }
1561
+ function partialSyncOthers(room) {
1562
+ const others = room.__internal.getOthers_forDevTools();
1563
+ if (others) {
1564
+ sendToPanel({
1565
+ msg: "room::sync::partial",
1566
+ roomId: room.id,
1567
+ others
1568
+ });
1569
+ }
1570
+ }
1571
+ function fullSync(room) {
1572
+ const root = room.getStorageSnapshot();
1573
+ const me = room.__internal.getSelf_forDevTools();
1574
+ const others = room.__internal.getOthers_forDevTools();
1575
+ sendToPanel({
1576
+ msg: "room::sync::full",
1577
+ roomId: room.id,
1578
+ status: room.getStatus(),
1579
+ storage: root?.toTreeNode("root").payload ?? null,
1580
+ me,
1581
+ others
1582
+ });
1583
+ }
1584
+ var roomChannelListeners = /* @__PURE__ */ new Map();
1585
+ function stopRoomChannelListener(roomId) {
1586
+ const listener = roomChannelListeners.get(roomId);
1587
+ roomChannelListeners.delete(roomId);
1588
+ if (listener) {
1589
+ listener();
1590
+ }
1591
+ }
1592
+ function linkDevTools(roomId, room) {
1593
+ if (process.env.NODE_ENV === "production" || typeof window === "undefined") {
1594
+ return;
1595
+ }
1596
+ sendToPanel({ msg: "room::available", roomId, clientVersion: VERSION });
1597
+ stopRoomChannelListener(roomId);
1598
+ roomChannelListeners.set(
1599
+ roomId,
1600
+ // Returns the unsubscribe callback, that we store in the
1601
+ // roomChannelListeners registry
1602
+ onMessageFromPanel.subscribe((msg) => {
1603
+ switch (msg.msg) {
1604
+ case "room::subscribe": {
1605
+ if (msg.roomId === roomId) {
1606
+ startSyncStream(room);
1607
+ }
1608
+ break;
1609
+ }
1610
+ case "room::unsubscribe": {
1611
+ if (msg.roomId === roomId) {
1612
+ stopSyncStream(roomId);
1613
+ }
1614
+ break;
1615
+ }
1616
+ }
1617
+ })
1618
+ );
1619
+ }
1620
+ function unlinkDevTools(roomId) {
1621
+ if (process.env.NODE_ENV === "production" || typeof window === "undefined") {
1622
+ return;
1623
+ }
1624
+ stopSyncStream(roomId);
1625
+ stopRoomChannelListener(roomId);
1626
+ sendToPanel({
1627
+ msg: "room::unavailable",
1628
+ roomId
1629
+ });
1630
+ }
1631
+
1632
+ // src/lib/deprecation.ts
1633
+ var _emittedDeprecationWarnings = /* @__PURE__ */ new Set();
1634
+ function deprecate(message, key = message) {
1635
+ if (process.env.NODE_ENV !== "production") {
1636
+ if (!_emittedDeprecationWarnings.has(key)) {
1637
+ _emittedDeprecationWarnings.add(key);
1638
+ errorWithTitle("Deprecation warning", message);
1639
+ }
1640
+ }
1641
+ }
1642
+ function deprecateIf(condition, message, key = message) {
1643
+ if (process.env.NODE_ENV !== "production") {
1644
+ if (condition) {
1645
+ deprecate(message, key);
1646
+ }
1647
+ }
1648
+ }
1649
+ function throwUsageError(message) {
1650
+ if (process.env.NODE_ENV !== "production") {
1651
+ const usageError = new Error(message);
1652
+ usageError.name = "Usage error";
1653
+ errorWithTitle("Usage error", message);
1654
+ throw usageError;
1655
+ }
1656
+ }
1657
+ function errorIf(condition, message) {
1658
+ if (process.env.NODE_ENV !== "production") {
1659
+ if (condition) {
1660
+ throwUsageError(message);
1661
+ }
1662
+ }
1663
+ }
1664
+
1665
+ // src/lib/Json.ts
1666
+ function isJsonScalar(data) {
1667
+ return data === null || typeof data === "string" || typeof data === "number" || typeof data === "boolean";
1668
+ }
1669
+ function isJsonArray(data) {
1670
+ return Array.isArray(data);
1671
+ }
1672
+ function isJsonObject(data) {
1673
+ return !isJsonScalar(data) && !isJsonArray(data);
1674
+ }
1675
+
1676
+ // src/realtime-client.ts
1677
+ function authValueToString(authValue) {
1678
+ return authValue.type === "secret" ? authValue.token.raw : authValue.publicApiKey;
1679
+ }
1680
+ function createRealtimeClient(authManager, serverEndpoint) {
1681
+ const eventHub = {
1682
+ error: makeEventSource(),
1683
+ connection: makeEventSource(),
1684
+ events: {}
1685
+ };
1686
+ let managedSocket = null;
1687
+ function createManagedSocket(roomId) {
1688
+ managedSocket = new ManagedSocket(
1689
+ {
1690
+ // TODO: We're trying to (re)connect based on the first roomId that the user is asking for
1691
+ // This is bad because the user might now have access to this room (anymore)
1692
+ // This prevent any future reconnection to the websocket server
1693
+ // We need to find a better way to handle the first (re)connection
1694
+ // (Could it be based on the current listeners)
1695
+ authenticate: () => authManager.getAuthValue("room:read", roomId),
1696
+ createSocket: (authValue) => new WebSocket(
1697
+ `${serverEndpoint}?token=${authValue.type === "secret" ? authValue.token.raw : authValue.publicApiKey}`
1698
+ )
1699
+ },
1700
+ true,
1701
+ false
1702
+ );
1703
+ managedSocket.events.statusDidChange.subscribe((status) => {
1704
+ if (status === "connected") {
1705
+ for (const roomId2 in eventHub.events) {
1706
+ const eventSource2 = eventHub.events[roomId2];
1707
+ if (eventSource2.count() > 0) {
1708
+ subscribeToRoomEvents(roomId2);
1709
+ }
1710
+ }
1711
+ }
1712
+ eventHub.connection.notify(status);
1713
+ });
1714
+ managedSocket.events.onLiveblocksError.subscribe(eventHub.error.notify);
1715
+ managedSocket.events.onMessage.subscribe((event) => {
1716
+ if (typeof event.data !== "string") {
1717
+ return;
1718
+ }
1719
+ const jsonEvent = tryParseJson(event.data);
1720
+ if (jsonEvent !== void 0 && isJsonObject(jsonEvent) && typeof jsonEvent.roomId === "string") {
1721
+ eventHub.events[jsonEvent.roomId].notify(jsonEvent);
1722
+ }
1723
+ });
1724
+ managedSocket.connect();
1725
+ }
1726
+ function getOrCreateEventSource(roomId) {
1727
+ let eventSource2 = eventHub.events[roomId];
1728
+ if (eventSource2 === void 0) {
1729
+ eventSource2 = makeEventSource();
1730
+ eventHub.events[roomId] = eventSource2;
1731
+ }
1732
+ return eventSource2;
1733
+ }
1734
+ async function subscribeToRoomEvents(roomId) {
1735
+ const authValue = await authManager.getAuthValue("room:read", roomId);
1736
+ if (managedSocket === null || managedSocket.getStatus() !== "connected") {
1737
+ return;
1738
+ }
1739
+ managedSocket.send(
1740
+ JSON.stringify({
1741
+ type: "subscribeToRooms",
1742
+ rooms: [roomId],
1743
+ token: authValueToString(authValue)
1744
+ })
1745
+ );
1746
+ }
1747
+ return {
1748
+ subscribeToEvents: (roomId, callback) => {
1749
+ if (!managedSocket) {
1750
+ createManagedSocket(roomId);
1751
+ }
1752
+ subscribeToRoomEvents(roomId);
1753
+ return getOrCreateEventSource(roomId).subscribe(callback);
1754
+ },
1755
+ error: eventHub.error.observable,
1756
+ connection: eventHub.connection.observable
1757
+ };
1758
+ }
1759
+
1760
+ // src/lib/position.ts
1761
+ var MIN_CODE = 32;
1762
+ var MAX_CODE = 126;
1763
+ var NUM_DIGITS = MAX_CODE - MIN_CODE + 1;
1764
+ var ZERO = nthDigit(0);
1765
+ var ONE = nthDigit(1);
1766
+ var ZERO_NINE = ZERO + nthDigit(-1);
1767
+ function nthDigit(n) {
1768
+ const code = MIN_CODE + (n < 0 ? NUM_DIGITS + n : n);
1769
+ if (code < MIN_CODE || code > MAX_CODE) {
1770
+ throw new Error(`Invalid n value: ${n}`);
1771
+ }
1772
+ return String.fromCharCode(code);
1773
+ }
1774
+ function makePosition(x, y) {
1775
+ if (x !== void 0 && y !== void 0) {
1776
+ return between(x, y);
1777
+ } else if (x !== void 0) {
1778
+ return after(x);
1779
+ } else if (y !== void 0) {
1780
+ return before(y);
1781
+ } else {
1782
+ return ONE;
1783
+ }
1784
+ }
1785
+ function before(pos) {
1786
+ const lastIndex = pos.length - 1;
1787
+ for (let i = 0; i <= lastIndex; i++) {
1788
+ const code = pos.charCodeAt(i);
1789
+ if (code <= MIN_CODE) {
1790
+ continue;
1791
+ }
1792
+ if (i === lastIndex) {
1793
+ if (code === MIN_CODE + 1) {
1794
+ return pos.substring(0, i) + ZERO_NINE;
1795
+ } else {
1796
+ return pos.substring(0, i) + String.fromCharCode(code - 1);
1797
+ }
1798
+ } else {
1799
+ return pos.substring(0, i + 1);
1800
+ }
1801
+ }
1802
+ return ONE;
1803
+ }
1804
+ function after(pos) {
1805
+ for (let i = 0; i <= pos.length - 1; i++) {
1806
+ const code = pos.charCodeAt(i);
1807
+ if (code >= MAX_CODE) {
1808
+ continue;
1809
+ }
1810
+ return pos.substring(0, i) + String.fromCharCode(code + 1);
1811
+ }
1812
+ return pos + ONE;
1813
+ }
1814
+ function between(lo, hi) {
1815
+ if (lo < hi) {
1816
+ return _between(lo, hi);
1817
+ } else if (lo > hi) {
1818
+ return _between(hi, lo);
1819
+ } else {
1820
+ throw new Error("Cannot compute value between two equal positions");
1821
+ }
1822
+ }
1823
+ function _between(lo, hi) {
1824
+ let index = 0;
1825
+ const loLen = lo.length;
1826
+ const hiLen = hi.length;
1827
+ while (true) {
1828
+ const loCode = index < loLen ? lo.charCodeAt(index) : MIN_CODE;
1829
+ const hiCode = index < hiLen ? hi.charCodeAt(index) : MAX_CODE;
1830
+ if (loCode === hiCode) {
1831
+ index++;
1832
+ continue;
1833
+ }
1834
+ if (hiCode - loCode === 1) {
1835
+ const prefix = lo.substring(0, index + 1);
1836
+ const suffix = lo.substring(index + 1);
1837
+ const nines = "";
1838
+ return prefix + _between(suffix, nines);
1839
+ } else {
1840
+ return takeN(lo, index) + String.fromCharCode(hiCode + loCode >> 1);
1841
+ }
1842
+ }
1843
+ }
1844
+ function takeN(pos, n) {
1845
+ return n < pos.length ? pos.substring(0, n) : pos + ZERO.repeat(n - pos.length);
1846
+ }
1847
+ var MIN_NON_ZERO_CODE = MIN_CODE + 1;
1848
+ function isPos(str) {
1849
+ if (str === "") {
1850
+ return false;
1851
+ }
1852
+ const lastIdx = str.length - 1;
1853
+ const last = str.charCodeAt(lastIdx);
1854
+ if (last < MIN_NON_ZERO_CODE || last > MAX_CODE) {
1855
+ return false;
1856
+ }
1857
+ for (let i = 0; i < lastIdx; i++) {
1858
+ const code = str.charCodeAt(i);
1859
+ if (code < MIN_CODE || code > MAX_CODE) {
1860
+ return false;
1861
+ }
1862
+ }
1863
+ return true;
1864
+ }
1865
+ function convertToPos(str) {
1866
+ const codes = [];
1867
+ for (let i = 0; i < str.length; i++) {
1868
+ const code = str.charCodeAt(i);
1869
+ codes.push(code < MIN_CODE ? MIN_CODE : code > MAX_CODE ? MAX_CODE : code);
1870
+ }
1871
+ while (codes.length > 0 && codes[codes.length - 1] === MIN_CODE) {
1872
+ codes.length--;
1873
+ }
1874
+ return codes.length > 0 ? String.fromCharCode(...codes) : (
1875
+ // Edge case: the str was a 0-only string, which is invalid. Default back to .1
1876
+ ONE
1877
+ );
1481
1878
  }
1482
1879
  function asPos(str) {
1483
1880
  return isPos(str) ? str : convertToPos(str);
@@ -3881,41 +4278,6 @@ function captureStackTrace(msg, traceRoot) {
3881
4278
  return errorLike.stack;
3882
4279
  }
3883
4280
 
3884
- // src/lib/Json.ts
3885
- function isJsonScalar(data) {
3886
- return data === null || typeof data === "string" || typeof data === "number" || typeof data === "boolean";
3887
- }
3888
- function isJsonArray(data) {
3889
- return Array.isArray(data);
3890
- }
3891
- function isJsonObject(data) {
3892
- return !isJsonScalar(data) && !isJsonArray(data);
3893
- }
3894
-
3895
- // src/protocol/AuthToken.ts
3896
- function isStringList(value) {
3897
- return Array.isArray(value) && value.every((i) => typeof i === "string");
3898
- }
3899
- function isMinimalTokenPayload(data) {
3900
- return isPlainObject(data) && typeof data.actor === "number" && (data.id === void 0 || typeof data.id === "string") && isStringList(data.scopes);
3901
- }
3902
- function parseAuthToken(rawTokenString) {
3903
- const tokenParts = rawTokenString.split(".");
3904
- if (tokenParts.length !== 3) {
3905
- throw new Error("Authentication error: invalid JWT token");
3906
- }
3907
- const payload = tryParseJson(b64decode(tokenParts[1]));
3908
- if (!(payload && isMinimalTokenPayload(payload))) {
3909
- throw new Error(
3910
- "Authentication error: we expected a room token but did not get one. Hint: if you are using a callback, ensure the room is passed when creating the token. For more information: https://liveblocks.io/docs/api-reference/liveblocks-client#createClientCallback"
3911
- );
3912
- }
3913
- return {
3914
- raw: rawTokenString,
3915
- parsed: payload
3916
- };
3917
- }
3918
-
3919
4281
  // src/protocol/ClientMsg.ts
3920
4282
  var ClientMsgCode = /* @__PURE__ */ ((ClientMsgCode2) => {
3921
4283
  ClientMsgCode2[ClientMsgCode2["UPDATE_PRESENCE"] = 100] = "UPDATE_PRESENCE";
@@ -3927,20 +4289,6 @@ var ClientMsgCode = /* @__PURE__ */ ((ClientMsgCode2) => {
3927
4289
  return ClientMsgCode2;
3928
4290
  })(ClientMsgCode || {});
3929
4291
 
3930
- // src/protocol/ServerMsg.ts
3931
- var ServerMsgCode = /* @__PURE__ */ ((ServerMsgCode2) => {
3932
- ServerMsgCode2[ServerMsgCode2["UPDATE_PRESENCE"] = 100] = "UPDATE_PRESENCE";
3933
- ServerMsgCode2[ServerMsgCode2["USER_JOINED"] = 101] = "USER_JOINED";
3934
- ServerMsgCode2[ServerMsgCode2["USER_LEFT"] = 102] = "USER_LEFT";
3935
- ServerMsgCode2[ServerMsgCode2["BROADCASTED_EVENT"] = 103] = "BROADCASTED_EVENT";
3936
- ServerMsgCode2[ServerMsgCode2["ROOM_STATE"] = 104] = "ROOM_STATE";
3937
- ServerMsgCode2[ServerMsgCode2["INITIAL_STORAGE_STATE"] = 200] = "INITIAL_STORAGE_STATE";
3938
- ServerMsgCode2[ServerMsgCode2["UPDATE_STORAGE"] = 201] = "UPDATE_STORAGE";
3939
- ServerMsgCode2[ServerMsgCode2["REJECT_STORAGE_OP"] = 299] = "REJECT_STORAGE_OP";
3940
- ServerMsgCode2[ServerMsgCode2["UPDATE_YDOC"] = 300] = "UPDATE_YDOC";
3941
- return ServerMsgCode2;
3942
- })(ServerMsgCode || {});
3943
-
3944
4292
  // src/lib/LegacyArray.ts
3945
4293
  function asArrayWithLegacyMethods(arr) {
3946
4294
  Object.defineProperty(arr, "count", {
@@ -3992,7 +4340,19 @@ var ImmutableRef = class {
3992
4340
 
3993
4341
  // src/refs/OthersRef.ts
3994
4342
  function makeUser(conn, presence) {
3995
- return freeze(compactObject({ ...conn, presence }));
4343
+ const { connectionId, id, info } = conn;
4344
+ const canWrite = canWriteStorage(conn.scopes);
4345
+ return freeze(
4346
+ compactObject({
4347
+ connectionId,
4348
+ id,
4349
+ info,
4350
+ canWrite,
4351
+ isReadOnly: !canWrite,
4352
+ // Deprecated, kept for backward-compatibility
4353
+ presence
4354
+ })
4355
+ );
3996
4356
  }
3997
4357
  var OthersRef = class extends ImmutableRef {
3998
4358
  //
@@ -4000,50 +4360,53 @@ var OthersRef = class extends ImmutableRef {
4000
4360
  //
4001
4361
  constructor() {
4002
4362
  super();
4003
- this._connections = {};
4004
- this._presences = {};
4005
- this._users = {};
4363
+ this._connections = /* @__PURE__ */ new Map();
4364
+ this._presences = /* @__PURE__ */ new Map();
4365
+ this._users = /* @__PURE__ */ new Map();
4366
+ }
4367
+ connectionIds() {
4368
+ return this._connections.keys();
4006
4369
  }
4007
4370
  /** @internal */
4008
4371
  _toImmutable() {
4009
4372
  const users = compact(
4010
- Object.keys(this._presences).map(
4373
+ Array.from(this._presences.keys()).map(
4011
4374
  (connectionId) => this.getUser(Number(connectionId))
4012
4375
  )
4013
4376
  );
4014
4377
  return asArrayWithLegacyMethods(users);
4015
4378
  }
4016
4379
  clearOthers() {
4017
- this._connections = {};
4018
- this._presences = {};
4019
- this._users = {};
4380
+ this._connections = /* @__PURE__ */ new Map();
4381
+ this._presences = /* @__PURE__ */ new Map();
4382
+ this._users = /* @__PURE__ */ new Map();
4020
4383
  this.invalidate();
4021
4384
  }
4022
4385
  /** @internal */
4023
4386
  _getUser(connectionId) {
4024
- const conn = this._connections[connectionId];
4025
- const presence = this._presences[connectionId];
4387
+ const conn = this._connections.get(connectionId);
4388
+ const presence = this._presences.get(connectionId);
4026
4389
  if (conn !== void 0 && presence !== void 0) {
4027
4390
  return makeUser(conn, presence);
4028
4391
  }
4029
4392
  return void 0;
4030
4393
  }
4031
4394
  getUser(connectionId) {
4032
- const cachedUser = this._users[connectionId];
4395
+ const cachedUser = this._users.get(connectionId);
4033
4396
  if (cachedUser) {
4034
4397
  return cachedUser;
4035
4398
  }
4036
4399
  const computedUser = this._getUser(connectionId);
4037
4400
  if (computedUser) {
4038
- this._users[connectionId] = computedUser;
4401
+ this._users.set(connectionId, computedUser);
4039
4402
  return computedUser;
4040
4403
  }
4041
4404
  return void 0;
4042
4405
  }
4043
4406
  /** @internal */
4044
4407
  _invalidateUser(connectionId) {
4045
- if (this._users[connectionId] !== void 0) {
4046
- delete this._users[connectionId];
4408
+ if (this._users.has(connectionId)) {
4409
+ this._users.delete(connectionId);
4047
4410
  }
4048
4411
  this.invalidate();
4049
4412
  }
@@ -4051,14 +4414,17 @@ var OthersRef = class extends ImmutableRef {
4051
4414
  * Records a known connection. This records the connection ID and the
4052
4415
  * associated metadata.
4053
4416
  */
4054
- setConnection(connectionId, metaUserId, metaUserInfo, metaIsReadonly) {
4055
- this._connections[connectionId] = freeze({
4417
+ setConnection(connectionId, metaUserId, metaUserInfo, scopes) {
4418
+ this._connections.set(
4056
4419
  connectionId,
4057
- id: metaUserId,
4058
- info: metaUserInfo,
4059
- isReadOnly: metaIsReadonly
4060
- });
4061
- if (this._presences[connectionId] !== void 0) {
4420
+ freeze({
4421
+ connectionId,
4422
+ id: metaUserId,
4423
+ info: metaUserInfo,
4424
+ scopes
4425
+ })
4426
+ );
4427
+ if (this._presences.has(connectionId)) {
4062
4428
  this._invalidateUser(connectionId);
4063
4429
  }
4064
4430
  }
@@ -4067,8 +4433,8 @@ var OthersRef = class extends ImmutableRef {
4067
4433
  * the presence information.
4068
4434
  */
4069
4435
  removeConnection(connectionId) {
4070
- delete this._connections[connectionId];
4071
- delete this._presences[connectionId];
4436
+ this._connections.delete(connectionId);
4437
+ this._presences.delete(connectionId);
4072
4438
  this._invalidateUser(connectionId);
4073
4439
  }
4074
4440
  /**
@@ -4076,8 +4442,8 @@ var OthersRef = class extends ImmutableRef {
4076
4442
  * its known presence data is overwritten.
4077
4443
  */
4078
4444
  setOther(connectionId, presence) {
4079
- this._presences[connectionId] = freeze(compactObject(presence));
4080
- if (this._connections[connectionId] !== void 0) {
4445
+ this._presences.set(connectionId, freeze(compactObject(presence)));
4446
+ if (this._connections.has(connectionId)) {
4081
4447
  this._invalidateUser(connectionId);
4082
4448
  }
4083
4449
  }
@@ -4087,13 +4453,13 @@ var OthersRef = class extends ImmutableRef {
4087
4453
  * full .setOther() call first.
4088
4454
  */
4089
4455
  patchOther(connectionId, patch) {
4090
- const oldPresence = this._presences[connectionId];
4456
+ const oldPresence = this._presences.get(connectionId);
4091
4457
  if (oldPresence === void 0) {
4092
4458
  return;
4093
4459
  }
4094
4460
  const newPresence = merge(oldPresence, patch);
4095
4461
  if (oldPresence !== newPresence) {
4096
- this._presences[connectionId] = freeze(newPresence);
4462
+ this._presences.set(connectionId, freeze(newPresence));
4097
4463
  this._invalidateUser(connectionId);
4098
4464
  }
4099
4465
  }
@@ -4173,17 +4539,7 @@ function userToTreeNode(key, user) {
4173
4539
  function createRoom(options, config) {
4174
4540
  const initialPresence = typeof options.initialPresence === "function" ? options.initialPresence(config.roomId) : options.initialPresence;
4175
4541
  const initialStorage = typeof options.initialStorage === "function" ? options.initialStorage(config.roomId) : options.initialStorage;
4176
- const delegates = config.delegates ?? {
4177
- authenticate: makeAuthDelegateForRoom(
4178
- config.roomId,
4179
- config.authentication,
4180
- config.polyfills?.fetch
4181
- ),
4182
- createSocket: makeCreateSocketDelegateForRoom(
4183
- config.liveblocksServer,
4184
- config.polyfills?.WebSocket
4185
- )
4186
- };
4542
+ const delegates = config.delegates;
4187
4543
  const managedSocket = new ManagedSocket(
4188
4544
  delegates,
4189
4545
  config.enableDebugLogging
@@ -4202,8 +4558,9 @@ function createRoom(options, config) {
4202
4558
  messages: [],
4203
4559
  storageOperations: []
4204
4560
  },
4205
- sessionInfo: new ValueRef(null),
4206
- me: new PatchableRef(initialPresence),
4561
+ staticSessionInfo: new ValueRef(null),
4562
+ dynamicSessionInfo: new ValueRef(null),
4563
+ myPresence: new PatchableRef(initialPresence),
4207
4564
  others: new OthersRef(),
4208
4565
  initialStorage,
4209
4566
  idFactory: null,
@@ -4222,18 +4579,26 @@ function createRoom(options, config) {
4222
4579
  };
4223
4580
  const doNotBatchUpdates = (cb) => cb();
4224
4581
  const batchUpdates = config.unstable_batchedUpdates ?? doNotBatchUpdates;
4225
- let lastToken;
4582
+ let lastTokenKey;
4226
4583
  function onStatusDidChange(newStatus) {
4227
- const token = managedSocket.token?.parsed;
4228
- if (token !== void 0 && token !== lastToken) {
4229
- context.sessionInfo.set({
4230
- userInfo: token.info,
4231
- userId: token.id,
4232
- // NOTE: In the future, these fields will get assigned in the connection phase
4233
- actor: token.actor,
4234
- isReadOnly: isStorageReadOnly(token.scopes)
4235
- });
4236
- lastToken = token;
4584
+ const authValue = managedSocket.authValue;
4585
+ if (authValue !== null) {
4586
+ const tokenKey = authValue.type === "secret" ? authValue.token.raw : authValue.publicApiKey;
4587
+ if (tokenKey !== lastTokenKey) {
4588
+ lastTokenKey = tokenKey;
4589
+ if (authValue.type === "secret") {
4590
+ const token = authValue.token.parsed;
4591
+ context.staticSessionInfo.set({
4592
+ userId: token.k === "sec-legacy" /* SECRET_LEGACY */ ? token.id : token.uid,
4593
+ userInfo: token.k === "sec-legacy" /* SECRET_LEGACY */ ? token.info : token.ui
4594
+ });
4595
+ } else {
4596
+ context.staticSessionInfo.set({
4597
+ userId: void 0,
4598
+ userInfo: void 0
4599
+ });
4600
+ }
4601
+ }
4237
4602
  }
4238
4603
  batchUpdates(() => {
4239
4604
  eventHub.status.notify(newStatus);
@@ -4270,20 +4635,15 @@ function createRoom(options, config) {
4270
4635
  }
4271
4636
  }
4272
4637
  function onDidConnect() {
4273
- const sessionInfo = context.sessionInfo.current;
4274
- if (sessionInfo === null) {
4275
- throw new Error("Unexpected missing session info");
4276
- }
4277
4638
  context.buffer.presenceUpdates = {
4278
4639
  type: "full",
4279
4640
  data: (
4280
4641
  // Because context.me.current is a readonly object, we'll have to
4281
4642
  // make a copy here. Otherwise, type errors happen later when
4282
4643
  // "patching" my presence.
4283
- { ...context.me.current }
4644
+ { ...context.myPresence.current }
4284
4645
  )
4285
4646
  };
4286
- context.idFactory = makeIdFactory(sessionInfo.actor);
4287
4647
  if (_getStorage$ !== null) {
4288
4648
  refreshStorage({ flush: false });
4289
4649
  }
@@ -4348,7 +4708,12 @@ function createRoom(options, config) {
4348
4708
  }
4349
4709
  },
4350
4710
  assertStorageIsWritable: () => {
4351
- if (context.sessionInfo.current?.isReadOnly) {
4711
+ const scopes = context.dynamicSessionInfo.current?.scopes;
4712
+ if (scopes === void 0) {
4713
+ return;
4714
+ }
4715
+ const canWrite = canWriteStorage(scopes);
4716
+ if (!canWrite) {
4352
4717
  throw new Error(
4353
4718
  "Cannot write to storage with a read only user, please ensure the user has write permissions"
4354
4719
  );
@@ -4376,10 +4741,11 @@ function createRoom(options, config) {
4376
4741
  const message = JSON.stringify(messageOrMessages);
4377
4742
  if (config.unstable_fallbackToHTTP) {
4378
4743
  const size = new TextEncoder().encode(message).length;
4379
- if (size > MAX_MESSAGE_SIZE && managedSocket.token?.raw && config.httpSendEndpoint) {
4744
+ if (size > MAX_MESSAGE_SIZE && // TODO: support public api key auth in REST API
4745
+ managedSocket.authValue?.type === "secret" && config.httpSendEndpoint) {
4380
4746
  void httpSend(
4381
4747
  message,
4382
- managedSocket.token.raw,
4748
+ managedSocket.authValue.token.raw,
4383
4749
  config.httpSendEndpoint,
4384
4750
  config.polyfills?.fetch
4385
4751
  ).then((resp) => {
@@ -4396,16 +4762,24 @@ function createRoom(options, config) {
4396
4762
  managedSocket.send(message);
4397
4763
  }
4398
4764
  const self = new DerivedRef(
4399
- context.sessionInfo,
4400
- context.me,
4401
- (info, me) => {
4402
- return info !== null ? {
4403
- connectionId: info.actor,
4404
- id: info.userId,
4405
- info: info.userInfo,
4406
- presence: me,
4407
- isReadOnly: info.isReadOnly
4408
- } : null;
4765
+ context.staticSessionInfo,
4766
+ context.dynamicSessionInfo,
4767
+ context.myPresence,
4768
+ (staticSession, dynamicSession, myPresence) => {
4769
+ if (staticSession === null || dynamicSession === null) {
4770
+ return null;
4771
+ } else {
4772
+ const canWrite = canWriteStorage(dynamicSession.scopes);
4773
+ return {
4774
+ connectionId: dynamicSession.actor,
4775
+ id: staticSession.userId,
4776
+ info: staticSession.userInfo,
4777
+ presence: myPresence,
4778
+ canWrite,
4779
+ isReadOnly: !canWrite
4780
+ // Deprecated, kept for backward-compatibility
4781
+ };
4782
+ }
4409
4783
  }
4410
4784
  );
4411
4785
  let _lastSelf;
@@ -4477,7 +4851,7 @@ function createRoom(options, config) {
4477
4851
  }
4478
4852
  if (presence) {
4479
4853
  notifySelfChanged(doNotBatchUpdates);
4480
- eventHub.myPresence.notify(context.me.current);
4854
+ eventHub.myPresence.notify(context.myPresence.current);
4481
4855
  }
4482
4856
  if (storageUpdates.size > 0) {
4483
4857
  const updates = Array.from(storageUpdates.values());
@@ -4487,7 +4861,7 @@ function createRoom(options, config) {
4487
4861
  });
4488
4862
  }
4489
4863
  function getConnectionId() {
4490
- const info = context.sessionInfo.current;
4864
+ const info = context.dynamicSessionInfo.current;
4491
4865
  if (info) {
4492
4866
  return info.actor;
4493
4867
  }
@@ -4516,9 +4890,9 @@ function createRoom(options, config) {
4516
4890
  data: {}
4517
4891
  };
4518
4892
  for (const key in op.data) {
4519
- reverse.data[key] = context.me.current[key];
4893
+ reverse.data[key] = context.myPresence.current[key];
4520
4894
  }
4521
- context.me.patch(op.data);
4895
+ context.myPresence.patch(op.data);
4522
4896
  if (context.buffer.presenceUpdates === null) {
4523
4897
  context.buffer.presenceUpdates = { type: "partial", data: op.data };
4524
4898
  } else {
@@ -4625,9 +4999,9 @@ function createRoom(options, config) {
4625
4999
  continue;
4626
5000
  }
4627
5001
  context.buffer.presenceUpdates.data[key] = overrideValue;
4628
- oldValues[key] = context.me.current[key];
5002
+ oldValues[key] = context.myPresence.current[key];
4629
5003
  }
4630
- context.me.patch(patch);
5004
+ context.myPresence.patch(patch);
4631
5005
  if (context.activeBatch) {
4632
5006
  if (options2?.addToHistory) {
4633
5007
  context.activeBatch.reverseOps.unshift({
@@ -4649,9 +5023,6 @@ function createRoom(options, config) {
4649
5023
  });
4650
5024
  }
4651
5025
  }
4652
- function isStorageReadOnly(scopes) {
4653
- return scopes.includes("room:read" /* Read */) && scopes.includes("room:presence:write" /* PresenceWrite */) && !scopes.includes("room:write" /* Write */);
4654
- }
4655
5026
  function onUpdatePresenceMessage(message) {
4656
5027
  if (message.targetActor !== void 0) {
4657
5028
  const oldUser = context.others.getUser(message.actor);
@@ -4682,11 +5053,17 @@ function createRoom(options, config) {
4682
5053
  }
4683
5054
  return null;
4684
5055
  }
4685
- function onRoomStateMessage(message) {
4686
- for (const connectionId in context.others._connections) {
5056
+ function onRoomStateMessage(message, batchedUpdatesWrapper) {
5057
+ context.dynamicSessionInfo.set({
5058
+ actor: message.actor,
5059
+ scopes: message.scopes
5060
+ });
5061
+ context.idFactory = makeIdFactory(message.actor);
5062
+ notifySelfChanged(batchedUpdatesWrapper);
5063
+ for (const connectionId of context.others.connectionIds()) {
4687
5064
  const user = message.users[connectionId];
4688
5065
  if (user === void 0) {
4689
- context.others.removeConnection(Number(connectionId));
5066
+ context.others.removeConnection(connectionId);
4690
5067
  }
4691
5068
  }
4692
5069
  for (const key in message.users) {
@@ -4696,7 +5073,7 @@ function createRoom(options, config) {
4696
5073
  connectionId,
4697
5074
  user.id,
4698
5075
  user.info,
4699
- isStorageReadOnly(user.scopes)
5076
+ user.scopes
4700
5077
  );
4701
5078
  }
4702
5079
  return { type: "reset" };
@@ -4717,11 +5094,11 @@ function createRoom(options, config) {
4717
5094
  message.actor,
4718
5095
  message.id,
4719
5096
  message.info,
4720
- isStorageReadOnly(message.scopes)
5097
+ message.scopes
4721
5098
  );
4722
5099
  context.buffer.messages.push({
4723
5100
  type: 100 /* UPDATE_PRESENCE */,
4724
- data: context.me.current,
5101
+ data: context.myPresence.current,
4725
5102
  targetActor: message.actor
4726
5103
  });
4727
5104
  flushNowOrSoon();
@@ -4806,7 +5183,7 @@ function createRoom(options, config) {
4806
5183
  break;
4807
5184
  }
4808
5185
  case 104 /* ROOM_STATE */: {
4809
- updates.others.push(onRoomStateMessage(message));
5186
+ updates.others.push(onRoomStateMessage(message, doNotBatchUpdates));
4810
5187
  break;
4811
5188
  }
4812
5189
  case 200 /* INITIAL_STORAGE_STATE */: {
@@ -5173,10 +5550,9 @@ ${Array.from(traces).join("\n\n")}`
5173
5550
  // Core
5174
5551
  getStatus: () => managedSocket.getStatus(),
5175
5552
  getConnectionState: () => managedSocket.getLegacyStatus(),
5176
- isSelfAware: () => context.sessionInfo.current !== null,
5177
5553
  getSelf: () => self.current,
5178
5554
  // Presence
5179
- getPresence: () => context.me.current,
5555
+ getPresence: () => context.myPresence.current,
5180
5556
  getOthers: () => context.others.current
5181
5557
  },
5182
5558
  // Explictly make the __internal field non-enumerable, to avoid aggressive
@@ -5271,18 +5647,30 @@ function makeClassicSubscribeFn(events) {
5271
5647
  function isRoomEventName(value) {
5272
5648
  return value === "my-presence" || value === "others" || value === "event" || value === "error" || value === "history" || value === "status" || value === "storage-status" || value === "lost-connection" || value === "connection";
5273
5649
  }
5274
- function makeCreateSocketDelegateForRoom(liveblocksServer, WebSocketPolyfill) {
5275
- return (richToken) => {
5650
+ function makeAuthDelegateForRoom(roomId, authManager) {
5651
+ return async () => {
5652
+ return authManager.getAuthValue("room:read", roomId);
5653
+ };
5654
+ }
5655
+ function makeCreateSocketDelegateForRoom(roomId, liveblocksServer, WebSocketPolyfill) {
5656
+ return (authValue) => {
5276
5657
  const ws = WebSocketPolyfill ?? (typeof WebSocket === "undefined" ? void 0 : WebSocket);
5277
5658
  if (ws === void 0) {
5278
5659
  throw new StopRetrying(
5279
5660
  "To use Liveblocks client in a non-dom environment, you need to provide a WebSocket polyfill."
5280
5661
  );
5281
5662
  }
5282
- const token = richToken.raw;
5283
- return new ws(
5284
- `${liveblocksServer}/?token=${token}&version=${PKG_VERSION || "dev"}`
5285
- );
5663
+ const url = new URL(liveblocksServer);
5664
+ url.searchParams.set("roomId", roomId);
5665
+ if (authValue.type === "secret") {
5666
+ url.searchParams.set("tok", authValue.token.raw);
5667
+ } else if (authValue.type === "public") {
5668
+ url.searchParams.set("pubkey", authValue.publicApiKey);
5669
+ } else {
5670
+ return assertNever(authValue, "Unhandled case");
5671
+ }
5672
+ url.searchParams.set("version", PKG_VERSION || "dev");
5673
+ return new ws(url.toString());
5286
5674
  };
5287
5675
  }
5288
5676
  async function httpSend(message, token, endpoint, fetchPolyfill) {
@@ -5297,96 +5685,6 @@ async function httpSend(message, token, endpoint, fetchPolyfill) {
5297
5685
  body: message
5298
5686
  });
5299
5687
  }
5300
- function makeAuthDelegateForRoom(roomId, authentication, fetchPolyfill) {
5301
- const fetcher = fetchPolyfill ?? (typeof window === "undefined" ? void 0 : window.fetch);
5302
- if (authentication.type === "public") {
5303
- return async () => {
5304
- if (fetcher === void 0) {
5305
- throw new StopRetrying(
5306
- "To use Liveblocks client in a non-dom environment with a publicApiKey, you need to provide a fetch polyfill."
5307
- );
5308
- }
5309
- return fetchAuthEndpoint(fetcher, authentication.url, {
5310
- room: roomId,
5311
- publicApiKey: authentication.publicApiKey
5312
- }).then(({ token }) => parseAuthToken(token));
5313
- };
5314
- } else if (authentication.type === "private") {
5315
- return async () => {
5316
- if (fetcher === void 0) {
5317
- throw new StopRetrying(
5318
- "To use Liveblocks client in a non-dom environment with a url as auth endpoint, you need to provide a fetch polyfill."
5319
- );
5320
- }
5321
- return fetchAuthEndpoint(fetcher, authentication.url, {
5322
- room: roomId
5323
- }).then(({ token }) => parseAuthToken(token));
5324
- };
5325
- } else if (authentication.type === "custom") {
5326
- return async () => {
5327
- const response = await authentication.callback(roomId);
5328
- if (!response || typeof response !== "object") {
5329
- throw new Error(
5330
- 'We expect the authentication callback to return a token, but it does not. Hint: the return value should look like: { token: "..." }'
5331
- );
5332
- }
5333
- if (typeof response.token === "string") {
5334
- return parseAuthToken(response.token);
5335
- } else if (typeof response.error === "string") {
5336
- const reason = `Authentication failed: ${"reason" in response && typeof response.reason === "string" ? response.reason : "Forbidden"}`;
5337
- if (response.error === "forbidden") {
5338
- throw new StopRetrying(reason);
5339
- } else {
5340
- throw new Error(reason);
5341
- }
5342
- } else {
5343
- throw new Error(
5344
- 'We expect the authentication callback to return a token, but it does not. Hint: the return value should look like: { token: "..." }'
5345
- );
5346
- }
5347
- };
5348
- } else {
5349
- throw new Error("Internal error. Unexpected authentication type");
5350
- }
5351
- }
5352
- async function fetchAuthEndpoint(fetch2, endpoint, body) {
5353
- const res = await fetch2(endpoint, {
5354
- method: "POST",
5355
- headers: {
5356
- "Content-Type": "application/json"
5357
- },
5358
- // Credentials are needed to support authentication with cookies
5359
- credentials: "include",
5360
- body: JSON.stringify(body)
5361
- });
5362
- if (!res.ok) {
5363
- const reason = `${(await res.text()).trim() || "reason not provided in auth response"} (${res.status} returned by POST ${endpoint})`;
5364
- if (res.status === 401 || res.status === 403) {
5365
- throw new StopRetrying(`Unauthorized: ${reason}`);
5366
- } else {
5367
- throw new Error(`Failed to authenticate: ${reason}`);
5368
- }
5369
- }
5370
- let data;
5371
- try {
5372
- data = await res.json();
5373
- } catch (er) {
5374
- throw new Error(
5375
- `Expected a JSON response when doing a POST request on "${endpoint}". ${String(
5376
- er
5377
- )}`
5378
- );
5379
- }
5380
- if (!isPlainObject(data) || typeof data.token !== "string") {
5381
- throw new Error(
5382
- `Expected a JSON response of the form \`{ token: "..." }\` when doing a POST request on "${endpoint}", but got ${JSON.stringify(
5383
- data
5384
- )}`
5385
- );
5386
- }
5387
- const { token } = data;
5388
- return { token };
5389
- }
5390
5688
 
5391
5689
  // src/client.ts
5392
5690
  var MIN_THROTTLE = 16;
@@ -5398,7 +5696,7 @@ var MAX_LOST_CONNECTION_TIMEOUT = 3e4;
5398
5696
  var DEFAULT_LOST_CONNECTION_TIMEOUT = 5e3;
5399
5697
  function getServerFromClientOptions(clientOptions) {
5400
5698
  const rawOptions = clientOptions;
5401
- return typeof rawOptions.liveblocksServer === "string" ? rawOptions.liveblocksServer : "wss://api.liveblocks.io/v6";
5699
+ return typeof rawOptions.liveblocksServer === "string" ? rawOptions.liveblocksServer : "wss://api.liveblocks.io/v7";
5402
5700
  }
5403
5701
  function createClient(options) {
5404
5702
  const clientOptions = options;
@@ -5406,6 +5704,7 @@ function createClient(options) {
5406
5704
  const lostConnectionTimeout = getLostConnectionTimeout(
5407
5705
  clientOptions.lostConnectionTimeout ?? DEFAULT_LOST_CONNECTION_TIMEOUT
5408
5706
  );
5707
+ const authManager = createAuthManager(options);
5409
5708
  const rooms = /* @__PURE__ */ new Map();
5410
5709
  function getRoom(roomId) {
5411
5710
  const room = rooms.get(roomId);
@@ -5430,11 +5729,17 @@ function createClient(options) {
5430
5729
  throttleDelay,
5431
5730
  lostConnectionTimeout,
5432
5731
  polyfills: clientOptions.polyfills,
5433
- delegates: clientOptions.mockedDelegates,
5732
+ delegates: clientOptions.mockedDelegates ?? {
5733
+ createSocket: makeCreateSocketDelegateForRoom(
5734
+ roomId,
5735
+ getServerFromClientOptions(clientOptions),
5736
+ clientOptions.polyfills?.WebSocket
5737
+ ),
5738
+ authenticate: makeAuthDelegateForRoom(roomId, authManager)
5739
+ },
5434
5740
  enableDebugLogging: clientOptions.enableDebugLogging,
5435
5741
  unstable_batchedUpdates: options2?.unstable_batchedUpdates,
5436
5742
  liveblocksServer: getServerFromClientOptions(clientOptions),
5437
- authentication: prepareAuthentication(clientOptions, roomId),
5438
5743
  httpSendEndpoint: buildLiveblocksHttpSendEndpoint(
5439
5744
  clientOptions,
5440
5745
  roomId
@@ -5468,6 +5773,13 @@ function createClient(options) {
5468
5773
  }
5469
5774
  }
5470
5775
  return {
5776
+ __internal: {
5777
+ getAuthValue: authManager.getAuthValue,
5778
+ realtimeClient: createRealtimeClient(
5779
+ authManager,
5780
+ getWsEventServerEndpoint(options)
5781
+ )
5782
+ },
5471
5783
  getRoom,
5472
5784
  enter,
5473
5785
  leave
@@ -5493,48 +5805,6 @@ function getLostConnectionTimeout(value) {
5493
5805
  RECOMMENDED_MIN_LOST_CONNECTION_TIMEOUT
5494
5806
  );
5495
5807
  }
5496
- function prepareAuthentication(clientOptions, roomId) {
5497
- const { publicApiKey, authEndpoint } = clientOptions;
5498
- if (authEndpoint !== void 0 && publicApiKey !== void 0) {
5499
- throw new Error(
5500
- "You cannot use both publicApiKey and authEndpoint. Please use either publicApiKey or authEndpoint, but not both. For more information: https://liveblocks.io/docs/api-reference/liveblocks-client#createClient"
5501
- );
5502
- }
5503
- if (typeof publicApiKey === "string") {
5504
- if (publicApiKey.startsWith("sk_")) {
5505
- throw new Error(
5506
- "Invalid publicApiKey. You are using the secret key which is not supported. Please use the public key instead. For more information: https://liveblocks.io/docs/api-reference/liveblocks-client#createClientPublicKey"
5507
- );
5508
- } else if (!publicApiKey.startsWith("pk_")) {
5509
- throw new Error(
5510
- "Invalid key. Please use the public key format: pk_<public key>. For more information: https://liveblocks.io/docs/api-reference/liveblocks-client#createClientPublicKey"
5511
- );
5512
- }
5513
- return {
5514
- type: "public",
5515
- publicApiKey,
5516
- url: buildLiveblocksPublicAuthorizeEndpoint(clientOptions, roomId)
5517
- };
5518
- }
5519
- if (typeof authEndpoint === "string") {
5520
- return {
5521
- type: "private",
5522
- url: authEndpoint
5523
- };
5524
- } else if (typeof authEndpoint === "function") {
5525
- return {
5526
- type: "custom",
5527
- callback: authEndpoint
5528
- };
5529
- } else if (authEndpoint !== void 0) {
5530
- throw new Error(
5531
- "authEndpoint must be a string or a function. For more information: https://liveblocks.io/docs/api-reference/liveblocks-client#createClientAuthEndpoint"
5532
- );
5533
- }
5534
- throw new Error(
5535
- "Invalid Liveblocks client options. For more information: https://liveblocks.io/docs/api-reference/liveblocks-client#createClient"
5536
- );
5537
- }
5538
5808
  function buildLiveblocksHttpSendEndpoint(options, roomId) {
5539
5809
  if (options.httpSendEndpoint) {
5540
5810
  return options.httpSendEndpoint.replace("{roomId}", roomId);
@@ -5543,13 +5813,11 @@ function buildLiveblocksHttpSendEndpoint(options, roomId) {
5543
5813
  roomId
5544
5814
  )}/send-message`;
5545
5815
  }
5546
- function buildLiveblocksPublicAuthorizeEndpoint(options, roomId) {
5547
- if (options.publicAuthorizeEndpoint) {
5548
- return options.publicAuthorizeEndpoint.replace("{roomId}", roomId);
5816
+ function getWsEventServerEndpoint(options) {
5817
+ if (typeof options.eventsServerEndpoint === "string") {
5818
+ return options.eventsServerEndpoint;
5549
5819
  }
5550
- return `https://api.liveblocks.io/v2/rooms/${encodeURIComponent(
5551
- roomId
5552
- )}/public/authorize`;
5820
+ return `wss://events.liveblocks.io/v1`;
5553
5821
  }
5554
5822
 
5555
5823
  // src/crdts/utils.ts
@@ -5923,18 +6191,378 @@ function shallow(a, b) {
5923
6191
  return shallowObj(a, b);
5924
6192
  }
5925
6193
 
5926
- // src/types/IWebSocket.ts
5927
- var WebsocketCloseCodes = /* @__PURE__ */ ((WebsocketCloseCodes2) => {
5928
- WebsocketCloseCodes2[WebsocketCloseCodes2["CLOSE_ABNORMAL"] = 1006] = "CLOSE_ABNORMAL";
5929
- WebsocketCloseCodes2[WebsocketCloseCodes2["INVALID_MESSAGE_FORMAT"] = 4e3] = "INVALID_MESSAGE_FORMAT";
5930
- WebsocketCloseCodes2[WebsocketCloseCodes2["NOT_ALLOWED"] = 4001] = "NOT_ALLOWED";
5931
- WebsocketCloseCodes2[WebsocketCloseCodes2["MAX_NUMBER_OF_MESSAGES_PER_SECONDS"] = 4002] = "MAX_NUMBER_OF_MESSAGES_PER_SECONDS";
5932
- WebsocketCloseCodes2[WebsocketCloseCodes2["MAX_NUMBER_OF_CONCURRENT_CONNECTIONS"] = 4003] = "MAX_NUMBER_OF_CONCURRENT_CONNECTIONS";
5933
- WebsocketCloseCodes2[WebsocketCloseCodes2["MAX_NUMBER_OF_MESSAGES_PER_DAY_PER_APP"] = 4004] = "MAX_NUMBER_OF_MESSAGES_PER_DAY_PER_APP";
5934
- WebsocketCloseCodes2[WebsocketCloseCodes2["MAX_NUMBER_OF_CONCURRENT_CONNECTIONS_PER_ROOM"] = 4005] = "MAX_NUMBER_OF_CONCURRENT_CONNECTIONS_PER_ROOM";
5935
- WebsocketCloseCodes2[WebsocketCloseCodes2["CLOSE_WITHOUT_RETRY"] = 4999] = "CLOSE_WITHOUT_RETRY";
5936
- return WebsocketCloseCodes2;
5937
- })(WebsocketCloseCodes || {});
6194
+ // src/lib/AsyncCache.ts
6195
+ var noop = () => {
6196
+ };
6197
+ function isShallowEqual(a, b) {
6198
+ if (a.isLoading !== b.isLoading || a.data === void 0 !== (b.data === void 0) || a.error === void 0 !== (b.error === void 0)) {
6199
+ return false;
6200
+ } else {
6201
+ return shallow(a.data, b.data) && shallow(a.error, b.error);
6202
+ }
6203
+ }
6204
+ function createCacheItem(key, defaultAsyncFunction, options) {
6205
+ let asyncFunction = defaultAsyncFunction;
6206
+ const context = {
6207
+ isInvalid: true
6208
+ };
6209
+ let state = { isLoading: false };
6210
+ let previousState = { isLoading: false };
6211
+ const eventSource2 = makeEventSource();
6212
+ function notify() {
6213
+ const isEqual = options?.isStateEqual ?? isShallowEqual;
6214
+ if (!isEqual(previousState, state)) {
6215
+ previousState = state;
6216
+ eventSource2.notify(state);
6217
+ }
6218
+ }
6219
+ async function resolve() {
6220
+ if (!context.promise) {
6221
+ return;
6222
+ }
6223
+ try {
6224
+ const data = await context.promise;
6225
+ context.isInvalid = false;
6226
+ state = {
6227
+ isLoading: false,
6228
+ data
6229
+ };
6230
+ } catch (error3) {
6231
+ state = {
6232
+ isLoading: false,
6233
+ data: state.data,
6234
+ error: error3
6235
+ };
6236
+ }
6237
+ context.promise = void 0;
6238
+ notify();
6239
+ }
6240
+ async function revalidate() {
6241
+ context.isInvalid = true;
6242
+ return get();
6243
+ }
6244
+ async function get() {
6245
+ if (context.isInvalid) {
6246
+ if (!context.promise) {
6247
+ context.isInvalid = true;
6248
+ context.promise = asyncFunction(key);
6249
+ state = { isLoading: true, data: state.data };
6250
+ notify();
6251
+ }
6252
+ await resolve();
6253
+ }
6254
+ return getState();
6255
+ }
6256
+ function getState() {
6257
+ return state;
6258
+ }
6259
+ function setAsyncFunction(overrideAsyncFunction) {
6260
+ asyncFunction = overrideAsyncFunction;
6261
+ }
6262
+ return {
6263
+ ...eventSource2.observable,
6264
+ setAsyncFunction,
6265
+ get,
6266
+ getState,
6267
+ revalidate
6268
+ };
6269
+ }
6270
+ function createAsyncCache(asyncFunction, options) {
6271
+ const cache = /* @__PURE__ */ new Map();
6272
+ function create(key, overrideAsyncFunction) {
6273
+ let cacheItem = cache.get(key);
6274
+ if (cacheItem) {
6275
+ if (overrideAsyncFunction) {
6276
+ cacheItem.setAsyncFunction(overrideAsyncFunction);
6277
+ }
6278
+ return cacheItem;
6279
+ }
6280
+ cacheItem = createCacheItem(
6281
+ key,
6282
+ overrideAsyncFunction ?? asyncFunction,
6283
+ options
6284
+ );
6285
+ cache.set(key, cacheItem);
6286
+ return cacheItem;
6287
+ }
6288
+ function get(key) {
6289
+ return create(key).get();
6290
+ }
6291
+ function getState(key) {
6292
+ return cache.get(key)?.getState();
6293
+ }
6294
+ function revalidate(key) {
6295
+ return create(key).revalidate();
6296
+ }
6297
+ function subscribe(key, callback) {
6298
+ return create(key).subscribe(callback) ?? noop;
6299
+ }
6300
+ function subscribeOnce(key, callback) {
6301
+ return create(key).subscribeOnce(callback) ?? noop;
6302
+ }
6303
+ function has(key) {
6304
+ return cache.has(key);
6305
+ }
6306
+ function clear() {
6307
+ cache.clear();
6308
+ }
6309
+ return {
6310
+ create,
6311
+ get,
6312
+ getState,
6313
+ revalidate,
6314
+ subscribe,
6315
+ subscribeOnce,
6316
+ has,
6317
+ clear
6318
+ };
6319
+ }
6320
+
6321
+ // src/lib/Poller.ts
6322
+ function makePoller(callback) {
6323
+ let context = {
6324
+ state: "stopped",
6325
+ timeoutHandle: null,
6326
+ interval: null,
6327
+ lastScheduledAt: null,
6328
+ remainingInterval: null
6329
+ };
6330
+ function poll() {
6331
+ if (context.state === "running") {
6332
+ schedule(context.interval);
6333
+ }
6334
+ callback();
6335
+ }
6336
+ function schedule(interval) {
6337
+ context = {
6338
+ state: "running",
6339
+ interval: context.state !== "stopped" ? context.interval : interval,
6340
+ lastScheduledAt: performance.now(),
6341
+ timeoutHandle: setTimeout(poll, interval),
6342
+ remainingInterval: null
6343
+ };
6344
+ }
6345
+ function scheduleRemaining(remaining) {
6346
+ if (context.state !== "paused") {
6347
+ return;
6348
+ }
6349
+ context = {
6350
+ state: "running",
6351
+ interval: context.interval,
6352
+ lastScheduledAt: context.lastScheduledAt,
6353
+ timeoutHandle: setTimeout(poll, remaining),
6354
+ remainingInterval: null
6355
+ };
6356
+ }
6357
+ function start(interval) {
6358
+ if (context.state === "running") {
6359
+ return;
6360
+ }
6361
+ schedule(interval);
6362
+ }
6363
+ function restart(interval) {
6364
+ stop();
6365
+ start(interval);
6366
+ }
6367
+ function pause() {
6368
+ if (context.state !== "running") {
6369
+ return;
6370
+ }
6371
+ clearTimeout(context.timeoutHandle);
6372
+ context = {
6373
+ state: "paused",
6374
+ interval: context.interval,
6375
+ lastScheduledAt: context.lastScheduledAt,
6376
+ timeoutHandle: null,
6377
+ remainingInterval: context.interval - (performance.now() - context.lastScheduledAt)
6378
+ };
6379
+ }
6380
+ function resume() {
6381
+ if (context.state !== "paused") {
6382
+ return;
6383
+ }
6384
+ scheduleRemaining(context.remainingInterval);
6385
+ }
6386
+ function stop() {
6387
+ if (context.state === "stopped") {
6388
+ return;
6389
+ }
6390
+ if (context.timeoutHandle) {
6391
+ clearTimeout(context.timeoutHandle);
6392
+ }
6393
+ context = {
6394
+ state: "stopped",
6395
+ interval: null,
6396
+ lastScheduledAt: null,
6397
+ timeoutHandle: null,
6398
+ remainingInterval: null
6399
+ };
6400
+ }
6401
+ return {
6402
+ start,
6403
+ restart,
6404
+ pause,
6405
+ resume,
6406
+ stop
6407
+ };
6408
+ }
6409
+
6410
+ // src/comments/index.ts
6411
+ function createCommentsApi(client, { serverEndpoint }) {
6412
+ async function fetchJson(roomId, endpoint, options) {
6413
+ const response = await fetchApi(roomId, endpoint, options);
6414
+ if (!response.ok) {
6415
+ if (response.status >= 400 && response.status < 600) {
6416
+ let errorMessage = "";
6417
+ try {
6418
+ const errorBody = await response.json();
6419
+ errorMessage = errorBody.message;
6420
+ } catch (error3) {
6421
+ errorMessage = response.statusText;
6422
+ }
6423
+ throw new Error(
6424
+ `Request failed with status ${response.status}: ${errorMessage}`
6425
+ );
6426
+ }
6427
+ }
6428
+ let body;
6429
+ try {
6430
+ body = await response.json();
6431
+ } catch {
6432
+ body = {};
6433
+ }
6434
+ return body;
6435
+ }
6436
+ async function fetchApi(roomId, endpoint, options) {
6437
+ const authValue = await client.__internal.getAuthValue(
6438
+ "comments:read",
6439
+ // TODO: Use the right scope
6440
+ roomId
6441
+ );
6442
+ if (authValue.type !== "secret") {
6443
+ throw new Error("Only secret key are supported for client.");
6444
+ }
6445
+ const url = `${serverEndpoint}/rooms/${roomId}${endpoint}`;
6446
+ return await fetch(url, {
6447
+ ...options,
6448
+ headers: {
6449
+ ...options?.headers,
6450
+ Authorization: `Bearer ${authValue.token.raw}`
6451
+ }
6452
+ });
6453
+ }
6454
+ async function getThreads({
6455
+ roomId
6456
+ }) {
6457
+ const response = await fetchApi(roomId, "/threads");
6458
+ if (response.ok) {
6459
+ const json = await response.json();
6460
+ return json.data;
6461
+ } else if (response.status === 404) {
6462
+ return [];
6463
+ } else {
6464
+ throw new Error("FAIL");
6465
+ }
6466
+ }
6467
+ function createThread({
6468
+ roomId,
6469
+ metadata,
6470
+ body,
6471
+ commentId,
6472
+ threadId
6473
+ }) {
6474
+ return fetchJson(roomId, "/threads", {
6475
+ method: "POST",
6476
+ headers: {
6477
+ "Content-Type": "application/json"
6478
+ },
6479
+ body: JSON.stringify({
6480
+ id: threadId,
6481
+ comment: {
6482
+ id: commentId,
6483
+ body
6484
+ },
6485
+ metadata
6486
+ })
6487
+ });
6488
+ }
6489
+ function editThreadMetadata({
6490
+ roomId,
6491
+ metadata,
6492
+ threadId
6493
+ }) {
6494
+ return fetchJson(
6495
+ roomId,
6496
+ `/threads/${threadId}/metadata`,
6497
+ {
6498
+ method: "POST",
6499
+ headers: {
6500
+ "Content-Type": "application/json"
6501
+ },
6502
+ body: JSON.stringify(metadata)
6503
+ }
6504
+ );
6505
+ }
6506
+ function createComment({
6507
+ roomId,
6508
+ threadId,
6509
+ commentId,
6510
+ body
6511
+ }) {
6512
+ return fetchJson(roomId, `/threads/${threadId}/comments`, {
6513
+ method: "POST",
6514
+ headers: {
6515
+ "Content-Type": "application/json"
6516
+ },
6517
+ body: JSON.stringify({
6518
+ id: commentId,
6519
+ body
6520
+ })
6521
+ });
6522
+ }
6523
+ function editComment({
6524
+ roomId,
6525
+ threadId,
6526
+ commentId,
6527
+ body
6528
+ }) {
6529
+ return fetchJson(
6530
+ roomId,
6531
+ `/threads/${threadId}/comments/${commentId}`,
6532
+ {
6533
+ method: "POST",
6534
+ headers: {
6535
+ "Content-Type": "application/json"
6536
+ },
6537
+ body: JSON.stringify({
6538
+ body
6539
+ })
6540
+ }
6541
+ );
6542
+ }
6543
+ async function deleteComment({
6544
+ roomId,
6545
+ threadId,
6546
+ commentId
6547
+ }) {
6548
+ await fetchJson(roomId, `/threads/${threadId}/comments/${commentId}`, {
6549
+ method: "DELETE"
6550
+ });
6551
+ }
6552
+ return {
6553
+ getThreads,
6554
+ createThread,
6555
+ editThreadMetadata,
6556
+ createComment,
6557
+ editComment,
6558
+ deleteComment
6559
+ };
6560
+ }
6561
+
6562
+ // src/comments/utils.ts
6563
+ function isCommentBodyMention(element) {
6564
+ return "type" in element && element.type === "mention";
6565
+ }
5938
6566
 
5939
6567
  // src/index.ts
5940
6568
  detectDupes(PKG_NAME, PKG_VERSION, PKG_FORMAT);
@@ -5952,13 +6580,17 @@ export {
5952
6580
  assert,
5953
6581
  assertNever,
5954
6582
  b64decode,
6583
+ fancy_console_exports as console,
6584
+ createAsyncCache,
5955
6585
  createClient,
6586
+ createCommentsApi,
5956
6587
  deprecate,
5957
6588
  deprecateIf,
5958
6589
  detectDupes,
5959
6590
  errorIf,
5960
6591
  freeze,
5961
6592
  isChildCrdt,
6593
+ isCommentBodyMention,
5962
6594
  isJsonArray,
5963
6595
  isJsonObject,
5964
6596
  isJsonScalar,
@@ -5966,6 +6598,8 @@ export {
5966
6598
  isRootCrdt,
5967
6599
  legacy_patchImmutableObject,
5968
6600
  lsonToJson,
6601
+ makeEventSource,
6602
+ makePoller,
5969
6603
  makePosition,
5970
6604
  nn,
5971
6605
  patchLiveObjectKey,