@liveblocks/core 1.2.0-internal6 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -1,3 +1,81 @@
1
+ // src/version.ts
2
+ var PKG_NAME = "@liveblocks/core";
3
+ var PKG_VERSION = "1.2.0";
4
+ var PKG_FORMAT = "esm";
5
+
6
+ // src/dupe-detection.ts
7
+ var g = typeof globalThis !== "undefined" ? globalThis : typeof window !== "undefined" ? window : typeof global !== "undefined" ? global : {};
8
+ var crossLinkedDocs = "https://liveblocks.io/docs/errors/cross-linked";
9
+ var dupesDocs = "https://liveblocks.io/docs/errors/dupes";
10
+ var SPACE = " ";
11
+ function error(msg) {
12
+ if (process.env.NODE_ENV === "production") {
13
+ console.error(msg);
14
+ } else {
15
+ throw new Error(msg);
16
+ }
17
+ }
18
+ function detectDupes(pkgName, pkgVersion, pkgFormat) {
19
+ const pkgId = Symbol.for(pkgName);
20
+ const pkgBuildInfo = pkgFormat ? `${pkgVersion || "dev"} (${pkgFormat})` : pkgVersion || "dev";
21
+ if (!g[pkgId]) {
22
+ g[pkgId] = pkgBuildInfo;
23
+ } else if (g[pkgId] === pkgBuildInfo) {
24
+ } else {
25
+ const msg = [
26
+ `Multiple copies of Liveblocks are being loaded in your project. This will cause issues! See ${dupesDocs + SPACE}`,
27
+ "",
28
+ "Conflicts:",
29
+ `- ${pkgName} ${g[pkgId]} (already loaded)`,
30
+ `- ${pkgName} ${pkgBuildInfo} (trying to load this now)`
31
+ ].join("\n");
32
+ error(msg);
33
+ }
34
+ if (pkgVersion && PKG_VERSION && pkgVersion !== PKG_VERSION) {
35
+ error(
36
+ [
37
+ `Cross-linked versions of Liveblocks found, which will cause issues! See ${crossLinkedDocs + SPACE}`,
38
+ "",
39
+ "Conflicts:",
40
+ `- ${PKG_NAME} is at ${PKG_VERSION}`,
41
+ `- ${pkgName} is at ${pkgVersion}`,
42
+ "",
43
+ "Always upgrade all Liveblocks packages to the same version number."
44
+ ].join("\n")
45
+ );
46
+ }
47
+ }
48
+
49
+ // src/lib/assert.ts
50
+ function assertNever(_value, errmsg) {
51
+ throw new Error(errmsg);
52
+ }
53
+ function assert(condition, errmsg) {
54
+ if (process.env.NODE_ENV !== "production") {
55
+ if (!condition) {
56
+ const err = new Error(errmsg);
57
+ err.name = "Assertion failure";
58
+ throw err;
59
+ }
60
+ }
61
+ }
62
+ function nn(value, errmsg = "Expected value to be non-nullable") {
63
+ assert(value !== null && value !== void 0, errmsg);
64
+ return value;
65
+ }
66
+
67
+ // src/lib/controlledPromise.ts
68
+ function controlledPromise() {
69
+ let flagger;
70
+ const promise = new Promise((res) => {
71
+ flagger = res;
72
+ });
73
+ if (!flagger) {
74
+ throw new Error("Should never happen");
75
+ }
76
+ return [promise, flagger];
77
+ }
78
+
1
79
  // src/lib/EventSource.ts
2
80
  function makeEventSource() {
3
81
  const _onetimeObservers = /* @__PURE__ */ new Set();
@@ -71,187 +149,6 @@ function makeEventSource() {
71
149
  };
72
150
  }
73
151
 
74
- // src/devtools/bridge.ts
75
- var _bridgeActive = false;
76
- function activateBridge(allowed) {
77
- _bridgeActive = allowed;
78
- }
79
- function sendToPanel(message, options) {
80
- if (process.env.NODE_ENV === "production" || typeof window === "undefined") {
81
- return;
82
- }
83
- const fullMsg = {
84
- ...message,
85
- source: "liveblocks-devtools-client"
86
- };
87
- if (!(options?.force || _bridgeActive)) {
88
- return;
89
- }
90
- window.postMessage(fullMsg, "*");
91
- }
92
- var eventSource = makeEventSource();
93
- if (process.env.NODE_ENV !== "production" && typeof window !== "undefined") {
94
- window.addEventListener("message", (event) => {
95
- if (event.source === window && event.data?.source === "liveblocks-devtools-panel") {
96
- eventSource.notify(event.data);
97
- } else {
98
- }
99
- });
100
- }
101
- var onMessageFromPanel = eventSource.observable;
102
-
103
- // src/devtools/index.ts
104
- var VERSION = true ? (
105
- /* istanbul ignore next */
106
- "1.2.0-internal6"
107
- ) : "dev";
108
- var _devtoolsSetupHasRun = false;
109
- function setupDevTools(getAllRooms) {
110
- if (process.env.NODE_ENV === "production" || typeof window === "undefined") {
111
- return;
112
- }
113
- if (_devtoolsSetupHasRun) {
114
- return;
115
- }
116
- _devtoolsSetupHasRun = true;
117
- onMessageFromPanel.subscribe((msg) => {
118
- switch (msg.msg) {
119
- case "connect": {
120
- activateBridge(true);
121
- for (const roomId of getAllRooms()) {
122
- sendToPanel({
123
- msg: "room::available",
124
- roomId,
125
- clientVersion: VERSION
126
- });
127
- }
128
- break;
129
- }
130
- }
131
- });
132
- sendToPanel({ msg: "wake-up-devtools" }, { force: true });
133
- }
134
- var unsubsByRoomId = /* @__PURE__ */ new Map();
135
- function stopSyncStream(roomId) {
136
- const unsubs = unsubsByRoomId.get(roomId) ?? [];
137
- unsubsByRoomId.delete(roomId);
138
- for (const unsub of unsubs) {
139
- unsub();
140
- }
141
- }
142
- function startSyncStream(room) {
143
- stopSyncStream(room.id);
144
- fullSync(room);
145
- unsubsByRoomId.set(room.id, [
146
- // When the connection status changes
147
- room.events.status.subscribe(() => partialSyncConnection(room)),
148
- // When storage initializes, send the update
149
- room.events.storageDidLoad.subscribeOnce(() => partialSyncStorage(room)),
150
- // Any time storage updates, send the new storage root
151
- room.events.storage.subscribe(() => partialSyncStorage(room)),
152
- // Any time "me" or "others" updates, send the new values accordingly
153
- room.events.me.subscribe(() => partialSyncMe(room)),
154
- room.events.others.subscribe(() => partialSyncOthers(room))
155
- ]);
156
- }
157
- function partialSyncConnection(room) {
158
- sendToPanel({
159
- msg: "room::sync::partial",
160
- roomId: room.id,
161
- status: room.getStatus()
162
- });
163
- }
164
- function partialSyncStorage(room) {
165
- const root = room.getStorageSnapshot();
166
- if (root) {
167
- sendToPanel({
168
- msg: "room::sync::partial",
169
- roomId: room.id,
170
- storage: root.toTreeNode("root").payload
171
- });
172
- }
173
- }
174
- function partialSyncMe(room) {
175
- const me = room.__internal.getSelf_forDevTools();
176
- if (me) {
177
- sendToPanel({
178
- msg: "room::sync::partial",
179
- roomId: room.id,
180
- me
181
- });
182
- }
183
- }
184
- function partialSyncOthers(room) {
185
- const others = room.__internal.getOthers_forDevTools();
186
- if (others) {
187
- sendToPanel({
188
- msg: "room::sync::partial",
189
- roomId: room.id,
190
- others
191
- });
192
- }
193
- }
194
- function fullSync(room) {
195
- const root = room.getStorageSnapshot();
196
- const me = room.__internal.getSelf_forDevTools();
197
- const others = room.__internal.getOthers_forDevTools();
198
- sendToPanel({
199
- msg: "room::sync::full",
200
- roomId: room.id,
201
- status: room.getStatus(),
202
- storage: root?.toTreeNode("root").payload ?? null,
203
- me,
204
- others
205
- });
206
- }
207
- var roomChannelListeners = /* @__PURE__ */ new Map();
208
- function stopRoomChannelListener(roomId) {
209
- const listener = roomChannelListeners.get(roomId);
210
- roomChannelListeners.delete(roomId);
211
- if (listener) {
212
- listener();
213
- }
214
- }
215
- function linkDevTools(roomId, room) {
216
- if (process.env.NODE_ENV === "production" || typeof window === "undefined") {
217
- return;
218
- }
219
- sendToPanel({ msg: "room::available", roomId, clientVersion: VERSION });
220
- stopRoomChannelListener(roomId);
221
- roomChannelListeners.set(
222
- roomId,
223
- // Returns the unsubscribe callback, that we store in the
224
- // roomChannelListeners registry
225
- onMessageFromPanel.subscribe((msg) => {
226
- switch (msg.msg) {
227
- case "room::subscribe": {
228
- if (msg.roomId === roomId) {
229
- startSyncStream(room);
230
- }
231
- break;
232
- }
233
- case "room::unsubscribe": {
234
- if (msg.roomId === roomId) {
235
- stopSyncStream(roomId);
236
- }
237
- break;
238
- }
239
- }
240
- })
241
- );
242
- }
243
- function unlinkDevTools(roomId) {
244
- if (process.env.NODE_ENV === "production" || typeof window === "undefined") {
245
- return;
246
- }
247
- stopSyncStream(roomId);
248
- stopRoomChannelListener(roomId);
249
- sendToPanel({
250
- msg: "room::unavailable",
251
- roomId
252
- });
253
- }
254
-
255
152
  // src/lib/fancy-console.ts
256
153
  var badge = "background:#0e0d12;border-radius:9999px;color:#fff;padding:3px 7px;font-family:sans-serif;font-weight:600;";
257
154
  var bold = "font-weight:600";
@@ -262,7 +159,7 @@ function wrap(method) {
262
159
  );
263
160
  }
264
161
  var warn = wrap("warn");
265
- var error = wrap("error");
162
+ var error2 = wrap("error");
266
163
  function wrapWithTitle(method) {
267
164
  return typeof window === "undefined" || process.env.NODE_ENV === "test" ? console[method] : (
268
165
  /* istanbul ignore next */
@@ -278,83 +175,32 @@ function wrapWithTitle(method) {
278
175
  var warnWithTitle = wrapWithTitle("warn");
279
176
  var errorWithTitle = wrapWithTitle("error");
280
177
 
281
- // src/lib/deprecation.ts
282
- var _emittedDeprecationWarnings = /* @__PURE__ */ new Set();
283
- function deprecate(message, key = message) {
284
- if (process.env.NODE_ENV !== "production") {
285
- if (!_emittedDeprecationWarnings.has(key)) {
286
- _emittedDeprecationWarnings.add(key);
287
- errorWithTitle("Deprecation warning", message);
288
- }
178
+ // src/lib/fsm.ts
179
+ function distance(state1, state2) {
180
+ if (state1 === state2) {
181
+ return [0, 0];
289
182
  }
290
- }
291
- function deprecateIf(condition, message, key = message) {
292
- if (process.env.NODE_ENV !== "production") {
293
- if (condition) {
294
- deprecate(message, key);
183
+ const chunks1 = state1.split(".");
184
+ const chunks2 = state2.split(".");
185
+ const minLen = Math.min(chunks1.length, chunks2.length);
186
+ let shared = 0;
187
+ for (; shared < minLen; shared++) {
188
+ if (chunks1[shared] !== chunks2[shared]) {
189
+ break;
295
190
  }
296
191
  }
192
+ const up = chunks1.length - shared;
193
+ const down = chunks2.length - shared;
194
+ return [up, down];
297
195
  }
298
- function throwUsageError(message) {
299
- if (process.env.NODE_ENV !== "production") {
300
- const usageError = new Error(message);
301
- usageError.name = "Usage error";
302
- errorWithTitle("Usage error", message);
303
- throw usageError;
196
+ function patterns(targetState, levels) {
197
+ const parts = targetState.split(".");
198
+ if (levels < 1 || levels > parts.length + 1) {
199
+ throw new Error("Invalid number of levels");
304
200
  }
305
- }
306
- function errorIf(condition, message) {
307
- if (process.env.NODE_ENV !== "production") {
308
- if (condition) {
309
- throwUsageError(message);
310
- }
311
- }
312
- }
313
-
314
- // src/lib/assert.ts
315
- function assertNever(_value, errmsg) {
316
- throw new Error(errmsg);
317
- }
318
- function assert(condition, errmsg) {
319
- if (process.env.NODE_ENV !== "production") {
320
- if (!condition) {
321
- const err = new Error(errmsg);
322
- err.name = "Assertion failure";
323
- throw err;
324
- }
325
- }
326
- }
327
- function nn(value, errmsg = "Expected value to be non-nullable") {
328
- assert(value !== null && value !== void 0, errmsg);
329
- return value;
330
- }
331
-
332
- // src/lib/fsm.ts
333
- function distance(state1, state2) {
334
- if (state1 === state2) {
335
- return [0, 0];
336
- }
337
- const chunks1 = state1.split(".");
338
- const chunks2 = state2.split(".");
339
- const minLen = Math.min(chunks1.length, chunks2.length);
340
- let shared = 0;
341
- for (; shared < minLen; shared++) {
342
- if (chunks1[shared] !== chunks2[shared]) {
343
- break;
344
- }
345
- }
346
- const up = chunks1.length - shared;
347
- const down = chunks2.length - shared;
348
- return [up, down];
349
- }
350
- function patterns(targetState, levels) {
351
- const parts = targetState.split(".");
352
- if (levels < 1 || levels > parts.length + 1) {
353
- throw new Error("Invalid number of levels");
354
- }
355
- const result = [];
356
- if (levels > parts.length) {
357
- result.push("*");
201
+ const result = [];
202
+ if (levels > parts.length) {
203
+ result.push("*");
358
204
  }
359
205
  for (let i = parts.length - levels + 1; i < parts.length; i++) {
360
206
  const slice = parts.slice(0, i);
@@ -726,6 +572,9 @@ function tryParseJson(rawMessage) {
726
572
  return void 0;
727
573
  }
728
574
  }
575
+ function deepClone(items) {
576
+ return JSON.parse(JSON.stringify(items));
577
+ }
729
578
  function b64decode(b64value) {
730
579
  try {
731
580
  const formattedValue = b64value.replace(/-/g, "+").replace(/_/g, "/");
@@ -764,6 +613,45 @@ async function withTimeout(promise, millis, errmsg = "Timed out") {
764
613
  return Promise.race([promise, timer$]).finally(() => clearTimeout(timerID));
765
614
  }
766
615
 
616
+ // src/protocol/ServerMsg.ts
617
+ var ServerMsgCode = /* @__PURE__ */ ((ServerMsgCode2) => {
618
+ ServerMsgCode2[ServerMsgCode2["UPDATE_PRESENCE"] = 100] = "UPDATE_PRESENCE";
619
+ ServerMsgCode2[ServerMsgCode2["USER_JOINED"] = 101] = "USER_JOINED";
620
+ ServerMsgCode2[ServerMsgCode2["USER_LEFT"] = 102] = "USER_LEFT";
621
+ ServerMsgCode2[ServerMsgCode2["BROADCASTED_EVENT"] = 103] = "BROADCASTED_EVENT";
622
+ ServerMsgCode2[ServerMsgCode2["ROOM_STATE"] = 104] = "ROOM_STATE";
623
+ ServerMsgCode2[ServerMsgCode2["INITIAL_STORAGE_STATE"] = 200] = "INITIAL_STORAGE_STATE";
624
+ ServerMsgCode2[ServerMsgCode2["UPDATE_STORAGE"] = 201] = "UPDATE_STORAGE";
625
+ ServerMsgCode2[ServerMsgCode2["REJECT_STORAGE_OP"] = 299] = "REJECT_STORAGE_OP";
626
+ ServerMsgCode2[ServerMsgCode2["UPDATE_YDOC"] = 300] = "UPDATE_YDOC";
627
+ return ServerMsgCode2;
628
+ })(ServerMsgCode || {});
629
+
630
+ // src/types/IWebSocket.ts
631
+ var WebsocketCloseCodes = /* @__PURE__ */ ((WebsocketCloseCodes2) => {
632
+ WebsocketCloseCodes2[WebsocketCloseCodes2["CLOSE_ABNORMAL"] = 1006] = "CLOSE_ABNORMAL";
633
+ WebsocketCloseCodes2[WebsocketCloseCodes2["UNEXPECTED_CONDITION"] = 1011] = "UNEXPECTED_CONDITION";
634
+ WebsocketCloseCodes2[WebsocketCloseCodes2["TRY_AGAIN_LATER"] = 1013] = "TRY_AGAIN_LATER";
635
+ WebsocketCloseCodes2[WebsocketCloseCodes2["INVALID_MESSAGE_FORMAT"] = 4e3] = "INVALID_MESSAGE_FORMAT";
636
+ WebsocketCloseCodes2[WebsocketCloseCodes2["NOT_ALLOWED"] = 4001] = "NOT_ALLOWED";
637
+ WebsocketCloseCodes2[WebsocketCloseCodes2["MAX_NUMBER_OF_MESSAGES_PER_SECONDS"] = 4002] = "MAX_NUMBER_OF_MESSAGES_PER_SECONDS";
638
+ WebsocketCloseCodes2[WebsocketCloseCodes2["MAX_NUMBER_OF_CONCURRENT_CONNECTIONS"] = 4003] = "MAX_NUMBER_OF_CONCURRENT_CONNECTIONS";
639
+ WebsocketCloseCodes2[WebsocketCloseCodes2["MAX_NUMBER_OF_MESSAGES_PER_DAY_PER_APP"] = 4004] = "MAX_NUMBER_OF_MESSAGES_PER_DAY_PER_APP";
640
+ WebsocketCloseCodes2[WebsocketCloseCodes2["MAX_NUMBER_OF_CONCURRENT_CONNECTIONS_PER_ROOM"] = 4005] = "MAX_NUMBER_OF_CONCURRENT_CONNECTIONS_PER_ROOM";
641
+ WebsocketCloseCodes2[WebsocketCloseCodes2["TOKEN_EXPIRED"] = 4109] = "TOKEN_EXPIRED";
642
+ WebsocketCloseCodes2[WebsocketCloseCodes2["CLOSE_WITHOUT_RETRY"] = 4999] = "CLOSE_WITHOUT_RETRY";
643
+ return WebsocketCloseCodes2;
644
+ })(WebsocketCloseCodes || {});
645
+ function shouldDisconnect(code) {
646
+ return code === 4999 /* CLOSE_WITHOUT_RETRY */ || code >= 4e3 && code < 4100;
647
+ }
648
+ function shouldReauth(code) {
649
+ return code >= 4100 && code < 4200;
650
+ }
651
+ function shouldRetryWithoutReauth(code) {
652
+ return code === 1013 /* TRY_AGAIN_LATER */ || code >= 4200 && code < 4300;
653
+ }
654
+
767
655
  // src/connection.ts
768
656
  function newToLegacyStatus(status) {
769
657
  switch (status) {
@@ -833,7 +721,7 @@ function resetSuccessCount(context) {
833
721
  context.patch({ successCount: 0 });
834
722
  }
835
723
  function log(level, message) {
836
- const logger = level === 2 /* ERROR */ ? error : level === 1 /* WARN */ ? warn : (
724
+ const logger = level === 2 /* ERROR */ ? error2 : level === 1 /* WARN */ ? warn : (
837
725
  /* black hole */
838
726
  () => {
839
727
  }
@@ -865,11 +753,8 @@ var logPermanentClose = log(
865
753
  1 /* WARN */,
866
754
  "Connection to WebSocket closed permanently. Won't retry."
867
755
  );
868
- function isCloseEvent(error2) {
869
- return !(error2 instanceof Error) && error2.type === "close";
870
- }
871
- function isCustomCloseEvent(error2) {
872
- return isCloseEvent(error2) && error2.code >= 4e3 && error2.code < 4100;
756
+ function isCloseEvent(error3) {
757
+ return !(error3 instanceof Error) && error3.type === "close";
873
758
  }
874
759
  function enableTracing(machine) {
875
760
  const start = (/* @__PURE__ */ new Date()).getTime();
@@ -921,13 +806,19 @@ function defineConnectivityEvents(machine) {
921
806
  };
922
807
  }
923
808
  var assign = (patch) => (ctx) => ctx.patch(patch);
924
- function createConnectionStateMachine(delegates, enableDebugLogging) {
809
+ function createConnectionStateMachine(delegates, options) {
925
810
  const onMessage = makeEventSource();
926
811
  onMessage.pause();
927
812
  const onLiveblocksError = makeEventSource();
813
+ function fireErrorEvent(errmsg, errcode) {
814
+ return () => {
815
+ const err = new LiveblocksError(errmsg, errcode);
816
+ onLiveblocksError.notify(err);
817
+ };
818
+ }
928
819
  const initialContext = {
929
820
  successCount: 0,
930
- token: null,
821
+ authValue: null,
931
822
  socket: null,
932
823
  backoffDelay: RESET_DELAY
933
824
  };
@@ -941,9 +832,9 @@ function createConnectionStateMachine(delegates, enableDebugLogging) {
941
832
  });
942
833
  machine.onEnter("@idle.*", resetSuccessCount).addTransitions("@idle.*", {
943
834
  CONNECT: (_, ctx) => (
944
- // If we still have a known token, try to reconnect to the socket directly,
945
- // otherwise, try to obtain a new token
946
- ctx.token !== null ? "@connecting.busy" : "@auth.busy"
835
+ // If we still have a known authValue, try to reconnect to the socket directly,
836
+ // otherwise, try to obtain a new authValue
837
+ ctx.authValue !== null ? "@connecting.busy" : "@auth.busy"
947
838
  )
948
839
  });
949
840
  machine.addTransitions("@auth.backoff", {
@@ -962,7 +853,7 @@ function createConnectionStateMachine(delegates, enableDebugLogging) {
962
853
  (okEvent) => ({
963
854
  target: "@connecting.busy",
964
855
  effect: assign({
965
- token: okEvent.data,
856
+ authValue: okEvent.data,
966
857
  backoffDelay: RESET_DELAY
967
858
  })
968
859
  }),
@@ -971,7 +862,10 @@ function createConnectionStateMachine(delegates, enableDebugLogging) {
971
862
  if (failedEvent.reason instanceof StopRetrying) {
972
863
  return {
973
864
  target: "@idle.failed",
974
- effect: log(2 /* ERROR */, failedEvent.reason.message)
865
+ effect: [
866
+ log(2 /* ERROR */, failedEvent.reason.message),
867
+ fireErrorEvent(failedEvent.reason.message, -1)
868
+ ]
975
869
  };
976
870
  }
977
871
  return {
@@ -1021,16 +915,29 @@ function createConnectionStateMachine(delegates, enableDebugLogging) {
1021
915
  let capturedPrematureEvent = null;
1022
916
  const connect$ = new Promise(
1023
917
  (resolve, rej) => {
1024
- if (ctx.token === null) {
1025
- throw new Error("No auth token");
918
+ if (ctx.authValue === null) {
919
+ throw new Error("No auth authValue");
1026
920
  }
1027
- const socket = delegates.createSocket(ctx.token);
921
+ const socket = delegates.createSocket(ctx.authValue);
1028
922
  function reject(event) {
1029
923
  capturedPrematureEvent = event;
1030
924
  socket.removeEventListener("message", onSocketMessage);
1031
925
  rej(event);
1032
926
  }
927
+ const [actor$, didReceiveActor] = controlledPromise();
928
+ if (!options.waitForActorId) {
929
+ didReceiveActor();
930
+ }
931
+ function waitForActorId(event) {
932
+ const serverMsg = tryParseJson(event.data);
933
+ if (serverMsg?.type === 104 /* ROOM_STATE */) {
934
+ didReceiveActor();
935
+ }
936
+ }
1033
937
  socket.addEventListener("message", onSocketMessage);
938
+ if (options.waitForActorId) {
939
+ socket.addEventListener("message", waitForActorId);
940
+ }
1034
941
  socket.addEventListener("error", reject);
1035
942
  socket.addEventListener("close", reject);
1036
943
  socket.addEventListener("open", () => {
@@ -1039,8 +946,11 @@ function createConnectionStateMachine(delegates, enableDebugLogging) {
1039
946
  const unsub = () => {
1040
947
  socket.removeEventListener("error", reject);
1041
948
  socket.removeEventListener("close", reject);
949
+ socket.removeEventListener("message", waitForActorId);
1042
950
  };
1043
- resolve([socket, unsub]);
951
+ void actor$.then(() => {
952
+ resolve([socket, unsub]);
953
+ });
1044
954
  });
1045
955
  }
1046
956
  );
@@ -1081,24 +991,35 @@ function createConnectionStateMachine(delegates, enableDebugLogging) {
1081
991
  if (err instanceof StopRetrying) {
1082
992
  return {
1083
993
  target: "@idle.failed",
1084
- effect: log(2 /* ERROR */, err.message)
1085
- };
1086
- }
1087
- if (isCloseEvent(err) && err.code === 4999) {
1088
- return {
1089
- target: "@idle.failed",
1090
- effect: log(2 /* ERROR */, err.reason)
1091
- };
1092
- }
1093
- if (isCustomCloseEvent(err) && err.code !== 4001) {
1094
- return {
1095
- target: "@connecting.backoff",
1096
994
  effect: [
1097
- increaseBackoffDelayAggressively,
1098
- logPrematureErrorOrCloseEvent(err)
995
+ log(2 /* ERROR */, err.message),
996
+ fireErrorEvent(err.message, -1)
1099
997
  ]
1100
998
  };
1101
999
  }
1000
+ if (isCloseEvent(err)) {
1001
+ if (err.code === 4109 /* TOKEN_EXPIRED */) {
1002
+ return "@auth.busy";
1003
+ }
1004
+ if (shouldRetryWithoutReauth(err.code)) {
1005
+ return {
1006
+ target: "@connecting.backoff",
1007
+ effect: [
1008
+ increaseBackoffDelayAggressively,
1009
+ logPrematureErrorOrCloseEvent(err)
1010
+ ]
1011
+ };
1012
+ }
1013
+ if (shouldDisconnect(err.code)) {
1014
+ return {
1015
+ target: "@idle.failed",
1016
+ effect: [
1017
+ log(2 /* ERROR */, err.reason),
1018
+ fireErrorEvent(err.reason, err.code)
1019
+ ]
1020
+ };
1021
+ }
1022
+ }
1102
1023
  return {
1103
1024
  target: "@auth.backoff",
1104
1025
  effect: [increaseBackoffDelay, logPrematureErrorOrCloseEvent(err)]
@@ -1152,29 +1073,29 @@ function createConnectionStateMachine(delegates, enableDebugLogging) {
1152
1073
  };
1153
1074
  },
1154
1075
  EXPLICIT_SOCKET_CLOSE: (e) => {
1155
- if (e.event.code === 4999) {
1076
+ if (shouldDisconnect(e.event.code)) {
1156
1077
  return {
1157
1078
  target: "@idle.failed",
1158
- effect: logPermanentClose
1079
+ effect: [
1080
+ logPermanentClose,
1081
+ fireErrorEvent(e.event.reason, e.event.code)
1082
+ ]
1159
1083
  };
1160
1084
  }
1161
- if (e.event.code === 4001) {
1162
- return {
1163
- target: "@auth.backoff",
1164
- effect: [increaseBackoffDelay, logCloseEvent(e.event)]
1165
- };
1085
+ if (shouldReauth(e.event.code)) {
1086
+ if (e.event.code === 4109 /* TOKEN_EXPIRED */) {
1087
+ return "@auth.busy";
1088
+ } else {
1089
+ return {
1090
+ target: "@auth.backoff",
1091
+ effect: [increaseBackoffDelay, logCloseEvent(e.event)]
1092
+ };
1093
+ }
1166
1094
  }
1167
- if (isCustomCloseEvent(e.event)) {
1095
+ if (shouldRetryWithoutReauth(e.event.code)) {
1168
1096
  return {
1169
1097
  target: "@connecting.backoff",
1170
- effect: [
1171
- increaseBackoffDelayAggressively,
1172
- logCloseEvent(e.event),
1173
- () => {
1174
- const err = new LiveblocksError(e.event.reason, e.event.code);
1175
- onLiveblocksError.notify(err);
1176
- }
1177
- ]
1098
+ effect: [increaseBackoffDelayAggressively, logCloseEvent(e.event)]
1178
1099
  };
1179
1100
  }
1180
1101
  return {
@@ -1213,7 +1134,7 @@ function createConnectionStateMachine(delegates, enableDebugLogging) {
1213
1134
  const cleanups = [];
1214
1135
  const { statusDidChange, didConnect, didDisconnect, unsubscribe } = defineConnectivityEvents(machine);
1215
1136
  cleanups.push(unsubscribe);
1216
- if (enableDebugLogging) {
1137
+ if (options.enableDebugLogging) {
1217
1138
  cleanups.push(enableTracing(machine));
1218
1139
  }
1219
1140
  machine.start();
@@ -1234,7 +1155,7 @@ var ManagedSocket = class {
1234
1155
  constructor(delegates, enableDebugLogging = false) {
1235
1156
  const { machine, events, cleanups } = createConnectionStateMachine(
1236
1157
  delegates,
1237
- enableDebugLogging
1158
+ { waitForActorId: true, enableDebugLogging }
1238
1159
  );
1239
1160
  this.machine = machine;
1240
1161
  this.events = events;
@@ -1251,10 +1172,10 @@ var ManagedSocket = class {
1251
1172
  }
1252
1173
  }
1253
1174
  /**
1254
- * Returns the current auth token.
1175
+ * Returns the current auth authValue.
1255
1176
  */
1256
- get token() {
1257
- return this.machine.context.token;
1177
+ get authValue() {
1178
+ return this.machine.context.authValue;
1258
1179
  }
1259
1180
  /**
1260
1181
  * Call this method to try to connect to a WebSocket. This only has an effect
@@ -1265,7 +1186,7 @@ var ManagedSocket = class {
1265
1186
  }
1266
1187
  /**
1267
1188
  * If idle, will try to connect. Otherwise, it will attempt to reconnect to
1268
- * the socket, potentially obtaining a new token first, if needed.
1189
+ * the socket, potentially obtaining a new authValue first, if needed.
1269
1190
  */
1270
1191
  reconnect() {
1271
1192
  this.machine.send({ type: "RECONNECT" });
@@ -1289,28 +1210,444 @@ var ManagedSocket = class {
1289
1210
  cleanup();
1290
1211
  }
1291
1212
  }
1292
- /**
1293
- * Safely send a message to the current WebSocket connection. Will emit a log
1294
- * message if this is somehow impossible.
1295
- */
1296
- send(data) {
1297
- const socket = this.machine.context?.socket;
1298
- if (socket === null) {
1299
- warn("Cannot send: not connected yet", data);
1300
- } else if (socket.readyState !== 1) {
1301
- warn("Cannot send: WebSocket no longer open", data);
1302
- } else {
1303
- socket.send(data);
1213
+ /**
1214
+ * Safely send a message to the current WebSocket connection. Will emit a log
1215
+ * message if this is somehow impossible.
1216
+ */
1217
+ send(data) {
1218
+ const socket = this.machine.context?.socket;
1219
+ if (socket === null) {
1220
+ warn("Cannot send: not connected yet", data);
1221
+ } else if (socket.readyState !== 1) {
1222
+ warn("Cannot send: WebSocket no longer open", data);
1223
+ } else {
1224
+ socket.send(data);
1225
+ }
1226
+ }
1227
+ /**
1228
+ * NOTE: Used by the E2E app only, to simulate explicit events.
1229
+ * Not ideal to keep exposed :(
1230
+ */
1231
+ _privateSendMachineEvent(event) {
1232
+ this.machine.send(event);
1233
+ }
1234
+ };
1235
+
1236
+ // src/protocol/AuthToken.ts
1237
+ function canWriteStorage(scopes) {
1238
+ return scopes.includes("room:write" /* Write */);
1239
+ }
1240
+ function isValidAuthTokenPayload(data) {
1241
+ return isPlainObject(data) && (data.k === "acc" /* ACCESS_TOKEN */ || data.k === "id" /* ID_TOKEN */ || data.k === "sec-legacy" /* SECRET_LEGACY */);
1242
+ }
1243
+ function parseAuthToken(rawTokenString) {
1244
+ const tokenParts = rawTokenString.split(".");
1245
+ if (tokenParts.length !== 3) {
1246
+ throw new Error("Authentication error: invalid JWT token");
1247
+ }
1248
+ const payload = tryParseJson(b64decode(tokenParts[1]));
1249
+ if (!(payload && isValidAuthTokenPayload(payload))) {
1250
+ throw new Error(
1251
+ "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"
1252
+ );
1253
+ }
1254
+ return {
1255
+ raw: rawTokenString,
1256
+ parsed: payload
1257
+ };
1258
+ }
1259
+
1260
+ // src/auth-manager.ts
1261
+ function createAuthManager(authOptions) {
1262
+ const authentication = prepareAuthentication(authOptions);
1263
+ const tokens = [];
1264
+ const expiryTimes = [];
1265
+ const requestPromises = /* @__PURE__ */ new Map();
1266
+ function hasCorrespondingScopes(requestedScope, scopes) {
1267
+ if (requestedScope === "comments:read") {
1268
+ return scopes.includes("comments:read" /* CommentsRead */) || scopes.includes("comments:write" /* CommentsWrite */) || scopes.includes("room:read" /* Read */) || scopes.includes("room:write" /* Write */);
1269
+ } else if (requestedScope === "room:read") {
1270
+ return scopes.includes("room:read" /* Read */) || scopes.includes("room:write" /* Write */);
1271
+ }
1272
+ return false;
1273
+ }
1274
+ function getCachedToken(requestedScope, roomId) {
1275
+ const now = Math.ceil(Date.now() / 1e3);
1276
+ for (let i = tokens.length - 1; i >= 0; i--) {
1277
+ const token = tokens[i];
1278
+ const expiresAt = expiryTimes[i];
1279
+ if (expiresAt <= now) {
1280
+ tokens.splice(i, 1);
1281
+ expiryTimes.splice(i, 1);
1282
+ continue;
1283
+ }
1284
+ if (token.parsed.k === "id" /* ID_TOKEN */) {
1285
+ return token;
1286
+ } else if (token.parsed.k === "sec-legacy" /* SECRET_LEGACY */) {
1287
+ return void 0;
1288
+ } else if (token.parsed.k === "acc" /* ACCESS_TOKEN */) {
1289
+ for (const [resource, scopes] of Object.entries(token.parsed.perms)) {
1290
+ if (resource.includes("*") && roomId.startsWith(resource.replace("*", "")) || roomId === resource && hasCorrespondingScopes(requestedScope, scopes)) {
1291
+ return token;
1292
+ }
1293
+ }
1294
+ }
1295
+ }
1296
+ return void 0;
1297
+ }
1298
+ async function makeAuthRequest(roomId) {
1299
+ const fetcher = authOptions.polyfills?.fetch ?? (typeof window === "undefined" ? void 0 : window.fetch);
1300
+ if (authentication.type === "private") {
1301
+ if (fetcher === void 0) {
1302
+ throw new StopRetrying(
1303
+ "To use Liveblocks client in a non-dom environment with a url as auth endpoint, you need to provide a fetch polyfill."
1304
+ );
1305
+ }
1306
+ const response = await fetchAuthEndpoint(fetcher, authentication.url, {
1307
+ room: roomId
1308
+ });
1309
+ return parseAuthToken(response.token);
1310
+ }
1311
+ if (authentication.type === "custom") {
1312
+ const response = await authentication.callback(roomId);
1313
+ if (!response || typeof response !== "object") {
1314
+ throw new Error(
1315
+ 'We expect the authentication callback to return a token, but it does not. Hint: the return value should look like: { token: "..." }'
1316
+ );
1317
+ }
1318
+ if (typeof response.token === "string") {
1319
+ return parseAuthToken(response.token);
1320
+ } else if (typeof response.error === "string") {
1321
+ const reason = `Authentication failed: ${"reason" in response && typeof response.reason === "string" ? response.reason : "Forbidden"}`;
1322
+ if (response.error === "forbidden") {
1323
+ throw new StopRetrying(reason);
1324
+ } else {
1325
+ throw new Error(reason);
1326
+ }
1327
+ } else {
1328
+ throw new Error(
1329
+ 'We expect the authentication callback to return a token, but it does not. Hint: the return value should look like: { token: "..." }'
1330
+ );
1331
+ }
1332
+ }
1333
+ throw new Error("Invalid Liveblocks client options");
1334
+ }
1335
+ async function getAuthValue(requestedScope, roomId) {
1336
+ if (authentication.type === "public") {
1337
+ return { type: "public", publicApiKey: authentication.publicApiKey };
1338
+ }
1339
+ const cachedToken = getCachedToken(requestedScope, roomId);
1340
+ if (cachedToken !== void 0) {
1341
+ return { type: "secret", token: cachedToken };
1342
+ }
1343
+ let currentPromise = requestPromises.get(roomId);
1344
+ if (currentPromise === void 0) {
1345
+ currentPromise = makeAuthRequest(roomId);
1346
+ requestPromises.set(roomId, currentPromise);
1347
+ }
1348
+ try {
1349
+ const token = await currentPromise;
1350
+ const BUFFER = 30;
1351
+ const expiresAt = Math.floor(Date.now() / 1e3) + (token.parsed.exp - token.parsed.iat) - BUFFER;
1352
+ tokens.push(token);
1353
+ expiryTimes.push(expiresAt);
1354
+ return { type: "secret", token };
1355
+ } finally {
1356
+ requestPromises.delete(roomId);
1357
+ }
1358
+ }
1359
+ return {
1360
+ getAuthValue
1361
+ };
1362
+ }
1363
+ function prepareAuthentication(authOptions) {
1364
+ const { publicApiKey, authEndpoint } = authOptions;
1365
+ if (authEndpoint !== void 0 && publicApiKey !== void 0) {
1366
+ throw new Error(
1367
+ "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"
1368
+ );
1369
+ }
1370
+ if (typeof publicApiKey === "string") {
1371
+ if (publicApiKey.startsWith("sk_")) {
1372
+ throw new Error(
1373
+ "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"
1374
+ );
1375
+ } else if (!publicApiKey.startsWith("pk_")) {
1376
+ throw new Error(
1377
+ "Invalid key. Please use the public key format: pk_<public key>. For more information: https://liveblocks.io/docs/api-reference/liveblocks-client#createClientPublicKey"
1378
+ );
1379
+ }
1380
+ return {
1381
+ type: "public",
1382
+ publicApiKey
1383
+ };
1384
+ }
1385
+ if (typeof authEndpoint === "string") {
1386
+ return {
1387
+ type: "private",
1388
+ url: authEndpoint
1389
+ };
1390
+ } else if (typeof authEndpoint === "function") {
1391
+ return {
1392
+ type: "custom",
1393
+ callback: authEndpoint
1394
+ };
1395
+ } else if (authEndpoint !== void 0) {
1396
+ throw new Error(
1397
+ "authEndpoint must be a string or a function. For more information: https://liveblocks.io/docs/api-reference/liveblocks-client#createClientAuthEndpoint"
1398
+ );
1399
+ }
1400
+ throw new Error(
1401
+ "Invalid Liveblocks client options. For more information: https://liveblocks.io/docs/api-reference/liveblocks-client#createClient"
1402
+ );
1403
+ }
1404
+ async function fetchAuthEndpoint(fetch2, endpoint, body) {
1405
+ const res = await fetch2(endpoint, {
1406
+ method: "POST",
1407
+ headers: {
1408
+ "Content-Type": "application/json"
1409
+ },
1410
+ body: JSON.stringify(body)
1411
+ });
1412
+ if (!res.ok) {
1413
+ const reason = `${(await res.text()).trim() || "reason not provided in auth response"} (${res.status} returned by POST ${endpoint})`;
1414
+ if (res.status === 401 || res.status === 403) {
1415
+ throw new StopRetrying(`Unauthorized: ${reason}`);
1416
+ } else {
1417
+ throw new Error(`Failed to authenticate: ${reason}`);
1418
+ }
1419
+ }
1420
+ let data;
1421
+ try {
1422
+ data = await res.json();
1423
+ } catch (er) {
1424
+ throw new Error(
1425
+ `Expected a JSON response when doing a POST request on "${endpoint}". ${String(
1426
+ er
1427
+ )}`
1428
+ );
1429
+ }
1430
+ if (!isPlainObject(data) || typeof data.token !== "string") {
1431
+ throw new Error(
1432
+ `Expected a JSON response of the form \`{ token: "..." }\` when doing a POST request on "${endpoint}", but got ${JSON.stringify(
1433
+ data
1434
+ )}`
1435
+ );
1436
+ }
1437
+ const { token } = data;
1438
+ return { token };
1439
+ }
1440
+
1441
+ // src/devtools/bridge.ts
1442
+ var _bridgeActive = false;
1443
+ function activateBridge(allowed) {
1444
+ _bridgeActive = allowed;
1445
+ }
1446
+ function sendToPanel(message, options) {
1447
+ if (process.env.NODE_ENV === "production" || typeof window === "undefined") {
1448
+ return;
1449
+ }
1450
+ const fullMsg = {
1451
+ ...message,
1452
+ source: "liveblocks-devtools-client"
1453
+ };
1454
+ if (!(options?.force || _bridgeActive)) {
1455
+ return;
1456
+ }
1457
+ window.postMessage(fullMsg, "*");
1458
+ }
1459
+ var eventSource = makeEventSource();
1460
+ if (process.env.NODE_ENV !== "production" && typeof window !== "undefined") {
1461
+ window.addEventListener("message", (event) => {
1462
+ if (event.source === window && event.data?.source === "liveblocks-devtools-panel") {
1463
+ eventSource.notify(event.data);
1464
+ } else {
1465
+ }
1466
+ });
1467
+ }
1468
+ var onMessageFromPanel = eventSource.observable;
1469
+
1470
+ // src/devtools/index.ts
1471
+ var VERSION = PKG_VERSION || "dev";
1472
+ var _devtoolsSetupHasRun = false;
1473
+ function setupDevTools(getAllRooms) {
1474
+ if (process.env.NODE_ENV === "production" || typeof window === "undefined") {
1475
+ return;
1476
+ }
1477
+ if (_devtoolsSetupHasRun) {
1478
+ return;
1479
+ }
1480
+ _devtoolsSetupHasRun = true;
1481
+ onMessageFromPanel.subscribe((msg) => {
1482
+ switch (msg.msg) {
1483
+ case "connect": {
1484
+ activateBridge(true);
1485
+ for (const roomId of getAllRooms()) {
1486
+ sendToPanel({
1487
+ msg: "room::available",
1488
+ roomId,
1489
+ clientVersion: VERSION
1490
+ });
1491
+ }
1492
+ break;
1493
+ }
1494
+ }
1495
+ });
1496
+ sendToPanel({ msg: "wake-up-devtools" }, { force: true });
1497
+ }
1498
+ var unsubsByRoomId = /* @__PURE__ */ new Map();
1499
+ function stopSyncStream(roomId) {
1500
+ const unsubs = unsubsByRoomId.get(roomId) ?? [];
1501
+ unsubsByRoomId.delete(roomId);
1502
+ for (const unsub of unsubs) {
1503
+ unsub();
1504
+ }
1505
+ }
1506
+ function startSyncStream(room) {
1507
+ stopSyncStream(room.id);
1508
+ fullSync(room);
1509
+ unsubsByRoomId.set(room.id, [
1510
+ // When the connection status changes
1511
+ room.events.status.subscribe(() => partialSyncConnection(room)),
1512
+ // When storage initializes, send the update
1513
+ room.events.storageDidLoad.subscribeOnce(() => partialSyncStorage(room)),
1514
+ // Any time storage updates, send the new storage root
1515
+ room.events.storage.subscribe(() => partialSyncStorage(room)),
1516
+ // Any time "me" or "others" updates, send the new values accordingly
1517
+ room.events.self.subscribe(() => partialSyncMe(room)),
1518
+ room.events.others.subscribe(() => partialSyncOthers(room))
1519
+ ]);
1520
+ }
1521
+ function partialSyncConnection(room) {
1522
+ sendToPanel({
1523
+ msg: "room::sync::partial",
1524
+ roomId: room.id,
1525
+ status: room.getStatus()
1526
+ });
1527
+ }
1528
+ function partialSyncStorage(room) {
1529
+ const root = room.getStorageSnapshot();
1530
+ if (root) {
1531
+ sendToPanel({
1532
+ msg: "room::sync::partial",
1533
+ roomId: room.id,
1534
+ storage: root.toTreeNode("root").payload
1535
+ });
1536
+ }
1537
+ }
1538
+ function partialSyncMe(room) {
1539
+ const me = room.__internal.getSelf_forDevTools();
1540
+ if (me) {
1541
+ sendToPanel({
1542
+ msg: "room::sync::partial",
1543
+ roomId: room.id,
1544
+ me
1545
+ });
1546
+ }
1547
+ }
1548
+ function partialSyncOthers(room) {
1549
+ const others = room.__internal.getOthers_forDevTools();
1550
+ if (others) {
1551
+ sendToPanel({
1552
+ msg: "room::sync::partial",
1553
+ roomId: room.id,
1554
+ others
1555
+ });
1556
+ }
1557
+ }
1558
+ function fullSync(room) {
1559
+ const root = room.getStorageSnapshot();
1560
+ const me = room.__internal.getSelf_forDevTools();
1561
+ const others = room.__internal.getOthers_forDevTools();
1562
+ sendToPanel({
1563
+ msg: "room::sync::full",
1564
+ roomId: room.id,
1565
+ status: room.getStatus(),
1566
+ storage: root?.toTreeNode("root").payload ?? null,
1567
+ me,
1568
+ others
1569
+ });
1570
+ }
1571
+ var roomChannelListeners = /* @__PURE__ */ new Map();
1572
+ function stopRoomChannelListener(roomId) {
1573
+ const listener = roomChannelListeners.get(roomId);
1574
+ roomChannelListeners.delete(roomId);
1575
+ if (listener) {
1576
+ listener();
1577
+ }
1578
+ }
1579
+ function linkDevTools(roomId, room) {
1580
+ if (process.env.NODE_ENV === "production" || typeof window === "undefined") {
1581
+ return;
1582
+ }
1583
+ sendToPanel({ msg: "room::available", roomId, clientVersion: VERSION });
1584
+ stopRoomChannelListener(roomId);
1585
+ roomChannelListeners.set(
1586
+ roomId,
1587
+ // Returns the unsubscribe callback, that we store in the
1588
+ // roomChannelListeners registry
1589
+ onMessageFromPanel.subscribe((msg) => {
1590
+ switch (msg.msg) {
1591
+ case "room::subscribe": {
1592
+ if (msg.roomId === roomId) {
1593
+ startSyncStream(room);
1594
+ }
1595
+ break;
1596
+ }
1597
+ case "room::unsubscribe": {
1598
+ if (msg.roomId === roomId) {
1599
+ stopSyncStream(roomId);
1600
+ }
1601
+ break;
1602
+ }
1603
+ }
1604
+ })
1605
+ );
1606
+ }
1607
+ function unlinkDevTools(roomId) {
1608
+ if (process.env.NODE_ENV === "production" || typeof window === "undefined") {
1609
+ return;
1610
+ }
1611
+ stopSyncStream(roomId);
1612
+ stopRoomChannelListener(roomId);
1613
+ sendToPanel({
1614
+ msg: "room::unavailable",
1615
+ roomId
1616
+ });
1617
+ }
1618
+
1619
+ // src/lib/deprecation.ts
1620
+ var _emittedDeprecationWarnings = /* @__PURE__ */ new Set();
1621
+ function deprecate(message, key = message) {
1622
+ if (process.env.NODE_ENV !== "production") {
1623
+ if (!_emittedDeprecationWarnings.has(key)) {
1624
+ _emittedDeprecationWarnings.add(key);
1625
+ errorWithTitle("Deprecation warning", message);
1626
+ }
1627
+ }
1628
+ }
1629
+ function deprecateIf(condition, message, key = message) {
1630
+ if (process.env.NODE_ENV !== "production") {
1631
+ if (condition) {
1632
+ deprecate(message, key);
1304
1633
  }
1305
1634
  }
1306
- /**
1307
- * NOTE: Used by the E2E app only, to simulate explicit events.
1308
- * Not ideal to keep exposed :(
1309
- */
1310
- _privateSendMachineEvent(event) {
1311
- this.machine.send(event);
1635
+ }
1636
+ function throwUsageError(message) {
1637
+ if (process.env.NODE_ENV !== "production") {
1638
+ const usageError = new Error(message);
1639
+ usageError.name = "Usage error";
1640
+ errorWithTitle("Usage error", message);
1641
+ throw usageError;
1312
1642
  }
1313
- };
1643
+ }
1644
+ function errorIf(condition, message) {
1645
+ if (process.env.NODE_ENV !== "production") {
1646
+ if (condition) {
1647
+ throwUsageError(message);
1648
+ }
1649
+ }
1650
+ }
1314
1651
 
1315
1652
  // src/lib/position.ts
1316
1653
  var MIN_CODE = 32;
@@ -3844,35 +4181,6 @@ function isJsonObject(data) {
3844
4181
  return !isJsonScalar(data) && !isJsonArray(data);
3845
4182
  }
3846
4183
 
3847
- // src/protocol/AuthToken.ts
3848
- function isTokenExpired(token) {
3849
- const now = Date.now() / 1e3;
3850
- const valid = now <= token.exp - 300 && now >= token.iat - 300;
3851
- return !valid;
3852
- }
3853
- function isStringList(value) {
3854
- return Array.isArray(value) && value.every((i) => typeof i === "string");
3855
- }
3856
- function isMinimalTokenPayload(data) {
3857
- return isPlainObject(data) && typeof data.iat === "number" && typeof data.exp === "number" && typeof data.actor === "number" && (data.id === void 0 || typeof data.id === "string") && isStringList(data.scopes);
3858
- }
3859
- function parseAuthToken(rawTokenString) {
3860
- const tokenParts = rawTokenString.split(".");
3861
- if (tokenParts.length !== 3) {
3862
- throw new Error("Authentication error: invalid JWT token");
3863
- }
3864
- const payload = tryParseJson(b64decode(tokenParts[1]));
3865
- if (!(payload && isMinimalTokenPayload(payload))) {
3866
- throw new Error(
3867
- "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"
3868
- );
3869
- }
3870
- return {
3871
- raw: rawTokenString,
3872
- parsed: payload
3873
- };
3874
- }
3875
-
3876
4184
  // src/protocol/ClientMsg.ts
3877
4185
  var ClientMsgCode = /* @__PURE__ */ ((ClientMsgCode2) => {
3878
4186
  ClientMsgCode2[ClientMsgCode2["UPDATE_PRESENCE"] = 100] = "UPDATE_PRESENCE";
@@ -3884,20 +4192,6 @@ var ClientMsgCode = /* @__PURE__ */ ((ClientMsgCode2) => {
3884
4192
  return ClientMsgCode2;
3885
4193
  })(ClientMsgCode || {});
3886
4194
 
3887
- // src/protocol/ServerMsg.ts
3888
- var ServerMsgCode = /* @__PURE__ */ ((ServerMsgCode2) => {
3889
- ServerMsgCode2[ServerMsgCode2["UPDATE_PRESENCE"] = 100] = "UPDATE_PRESENCE";
3890
- ServerMsgCode2[ServerMsgCode2["USER_JOINED"] = 101] = "USER_JOINED";
3891
- ServerMsgCode2[ServerMsgCode2["USER_LEFT"] = 102] = "USER_LEFT";
3892
- ServerMsgCode2[ServerMsgCode2["BROADCASTED_EVENT"] = 103] = "BROADCASTED_EVENT";
3893
- ServerMsgCode2[ServerMsgCode2["ROOM_STATE"] = 104] = "ROOM_STATE";
3894
- ServerMsgCode2[ServerMsgCode2["INITIAL_STORAGE_STATE"] = 200] = "INITIAL_STORAGE_STATE";
3895
- ServerMsgCode2[ServerMsgCode2["UPDATE_STORAGE"] = 201] = "UPDATE_STORAGE";
3896
- ServerMsgCode2[ServerMsgCode2["REJECT_STORAGE_OP"] = 299] = "REJECT_STORAGE_OP";
3897
- ServerMsgCode2[ServerMsgCode2["UPDATE_YDOC"] = 300] = "UPDATE_YDOC";
3898
- return ServerMsgCode2;
3899
- })(ServerMsgCode || {});
3900
-
3901
4195
  // src/lib/LegacyArray.ts
3902
4196
  function asArrayWithLegacyMethods(arr) {
3903
4197
  Object.defineProperty(arr, "count", {
@@ -3949,7 +4243,19 @@ var ImmutableRef = class {
3949
4243
 
3950
4244
  // src/refs/OthersRef.ts
3951
4245
  function makeUser(conn, presence) {
3952
- return freeze(compactObject({ ...conn, presence }));
4246
+ const { connectionId, id, info } = conn;
4247
+ const canWrite = canWriteStorage(conn.scopes);
4248
+ return freeze(
4249
+ compactObject({
4250
+ connectionId,
4251
+ id,
4252
+ info,
4253
+ canWrite,
4254
+ isReadOnly: !canWrite,
4255
+ // Deprecated, kept for backward-compatibility
4256
+ presence
4257
+ })
4258
+ );
3953
4259
  }
3954
4260
  var OthersRef = class extends ImmutableRef {
3955
4261
  //
@@ -3957,50 +4263,53 @@ var OthersRef = class extends ImmutableRef {
3957
4263
  //
3958
4264
  constructor() {
3959
4265
  super();
3960
- this._connections = {};
3961
- this._presences = {};
3962
- this._users = {};
4266
+ this._connections = /* @__PURE__ */ new Map();
4267
+ this._presences = /* @__PURE__ */ new Map();
4268
+ this._users = /* @__PURE__ */ new Map();
4269
+ }
4270
+ connectionIds() {
4271
+ return this._connections.keys();
3963
4272
  }
3964
4273
  /** @internal */
3965
4274
  _toImmutable() {
3966
4275
  const users = compact(
3967
- Object.keys(this._presences).map(
4276
+ Array.from(this._presences.keys()).map(
3968
4277
  (connectionId) => this.getUser(Number(connectionId))
3969
4278
  )
3970
4279
  );
3971
4280
  return asArrayWithLegacyMethods(users);
3972
4281
  }
3973
4282
  clearOthers() {
3974
- this._connections = {};
3975
- this._presences = {};
3976
- this._users = {};
4283
+ this._connections = /* @__PURE__ */ new Map();
4284
+ this._presences = /* @__PURE__ */ new Map();
4285
+ this._users = /* @__PURE__ */ new Map();
3977
4286
  this.invalidate();
3978
4287
  }
3979
4288
  /** @internal */
3980
4289
  _getUser(connectionId) {
3981
- const conn = this._connections[connectionId];
3982
- const presence = this._presences[connectionId];
4290
+ const conn = this._connections.get(connectionId);
4291
+ const presence = this._presences.get(connectionId);
3983
4292
  if (conn !== void 0 && presence !== void 0) {
3984
4293
  return makeUser(conn, presence);
3985
4294
  }
3986
4295
  return void 0;
3987
4296
  }
3988
4297
  getUser(connectionId) {
3989
- const cachedUser = this._users[connectionId];
4298
+ const cachedUser = this._users.get(connectionId);
3990
4299
  if (cachedUser) {
3991
4300
  return cachedUser;
3992
4301
  }
3993
4302
  const computedUser = this._getUser(connectionId);
3994
4303
  if (computedUser) {
3995
- this._users[connectionId] = computedUser;
4304
+ this._users.set(connectionId, computedUser);
3996
4305
  return computedUser;
3997
4306
  }
3998
4307
  return void 0;
3999
4308
  }
4000
4309
  /** @internal */
4001
4310
  _invalidateUser(connectionId) {
4002
- if (this._users[connectionId] !== void 0) {
4003
- delete this._users[connectionId];
4311
+ if (this._users.has(connectionId)) {
4312
+ this._users.delete(connectionId);
4004
4313
  }
4005
4314
  this.invalidate();
4006
4315
  }
@@ -4008,14 +4317,17 @@ var OthersRef = class extends ImmutableRef {
4008
4317
  * Records a known connection. This records the connection ID and the
4009
4318
  * associated metadata.
4010
4319
  */
4011
- setConnection(connectionId, metaUserId, metaUserInfo, metaIsReadonly) {
4012
- this._connections[connectionId] = freeze({
4320
+ setConnection(connectionId, metaUserId, metaUserInfo, scopes) {
4321
+ this._connections.set(
4013
4322
  connectionId,
4014
- id: metaUserId,
4015
- info: metaUserInfo,
4016
- isReadOnly: metaIsReadonly
4017
- });
4018
- if (this._presences[connectionId] !== void 0) {
4323
+ freeze({
4324
+ connectionId,
4325
+ id: metaUserId,
4326
+ info: metaUserInfo,
4327
+ scopes
4328
+ })
4329
+ );
4330
+ if (this._presences.has(connectionId)) {
4019
4331
  this._invalidateUser(connectionId);
4020
4332
  }
4021
4333
  }
@@ -4024,8 +4336,8 @@ var OthersRef = class extends ImmutableRef {
4024
4336
  * the presence information.
4025
4337
  */
4026
4338
  removeConnection(connectionId) {
4027
- delete this._connections[connectionId];
4028
- delete this._presences[connectionId];
4339
+ this._connections.delete(connectionId);
4340
+ this._presences.delete(connectionId);
4029
4341
  this._invalidateUser(connectionId);
4030
4342
  }
4031
4343
  /**
@@ -4033,8 +4345,8 @@ var OthersRef = class extends ImmutableRef {
4033
4345
  * its known presence data is overwritten.
4034
4346
  */
4035
4347
  setOther(connectionId, presence) {
4036
- this._presences[connectionId] = freeze(compactObject(presence));
4037
- if (this._connections[connectionId] !== void 0) {
4348
+ this._presences.set(connectionId, freeze(compactObject(presence)));
4349
+ if (this._connections.has(connectionId)) {
4038
4350
  this._invalidateUser(connectionId);
4039
4351
  }
4040
4352
  }
@@ -4044,13 +4356,13 @@ var OthersRef = class extends ImmutableRef {
4044
4356
  * full .setOther() call first.
4045
4357
  */
4046
4358
  patchOther(connectionId, patch) {
4047
- const oldPresence = this._presences[connectionId];
4359
+ const oldPresence = this._presences.get(connectionId);
4048
4360
  if (oldPresence === void 0) {
4049
4361
  return;
4050
4362
  }
4051
4363
  const newPresence = merge(oldPresence, patch);
4052
4364
  if (oldPresence !== newPresence) {
4053
- this._presences[connectionId] = freeze(newPresence);
4365
+ this._presences.set(connectionId, freeze(newPresence));
4054
4366
  this._invalidateUser(connectionId);
4055
4367
  }
4056
4368
  }
@@ -4070,10 +4382,10 @@ var PatchableRef = class extends ImmutableRef {
4070
4382
  * Patches the current object.
4071
4383
  */
4072
4384
  patch(patch) {
4073
- const oldMe = this._data;
4074
- const newMe = merge(oldMe, patch);
4075
- if (oldMe !== newMe) {
4076
- this._data = freeze(newMe);
4385
+ const oldData = this._data;
4386
+ const newData = merge(oldData, patch);
4387
+ if (oldData !== newData) {
4388
+ this._data = freeze(newData);
4077
4389
  this.invalidate();
4078
4390
  }
4079
4391
  }
@@ -4130,17 +4442,7 @@ function userToTreeNode(key, user) {
4130
4442
  function createRoom(options, config) {
4131
4443
  const initialPresence = typeof options.initialPresence === "function" ? options.initialPresence(config.roomId) : options.initialPresence;
4132
4444
  const initialStorage = typeof options.initialStorage === "function" ? options.initialStorage(config.roomId) : options.initialStorage;
4133
- const delegates = config.delegates ?? {
4134
- authenticate: makeAuthDelegateForRoom(
4135
- config.roomId,
4136
- config.authentication,
4137
- config.polyfills?.fetch
4138
- ),
4139
- createSocket: makeCreateSocketDelegateForRoom(
4140
- config.liveblocksServer,
4141
- config.polyfills?.WebSocket
4142
- )
4143
- };
4445
+ const delegates = config.delegates;
4144
4446
  const managedSocket = new ManagedSocket(
4145
4447
  delegates,
4146
4448
  config.enableDebugLogging
@@ -4149,7 +4451,7 @@ function createRoom(options, config) {
4149
4451
  buffer: {
4150
4452
  flushTimerID: void 0,
4151
4453
  lastFlushedAt: 0,
4152
- me: (
4454
+ presenceUpdates: (
4153
4455
  // Queue up the initial presence message as a Full Presence™ update
4154
4456
  {
4155
4457
  type: "full",
@@ -4159,8 +4461,9 @@ function createRoom(options, config) {
4159
4461
  messages: [],
4160
4462
  storageOperations: []
4161
4463
  },
4162
- sessionInfo: new ValueRef(null),
4163
- me: new PatchableRef(initialPresence),
4464
+ staticSessionInfo: new ValueRef(null),
4465
+ dynamicSessionInfo: new ValueRef(null),
4466
+ myPresence: new PatchableRef(initialPresence),
4164
4467
  others: new OthersRef(),
4165
4468
  initialStorage,
4166
4469
  idFactory: null,
@@ -4179,22 +4482,31 @@ function createRoom(options, config) {
4179
4482
  };
4180
4483
  const doNotBatchUpdates = (cb) => cb();
4181
4484
  const batchUpdates = config.unstable_batchedUpdates ?? doNotBatchUpdates;
4182
- let lastToken;
4485
+ let lastTokenKey;
4183
4486
  function onStatusDidChange(newStatus) {
4184
- const token = managedSocket.token?.parsed;
4185
- if (token !== void 0 && token !== lastToken) {
4186
- context.sessionInfo.set({
4187
- userInfo: token.info,
4188
- userId: token.id,
4189
- // NOTE: In the future, these fields will get assigned in the connection phase
4190
- actor: token.actor,
4191
- isReadOnly: isStorageReadOnly(token.scopes)
4192
- });
4193
- lastToken = token;
4487
+ const authValue = managedSocket.authValue;
4488
+ if (authValue !== null) {
4489
+ const tokenKey = authValue.type === "secret" ? authValue.token.raw : authValue.publicApiKey;
4490
+ if (tokenKey !== lastTokenKey) {
4491
+ lastTokenKey = tokenKey;
4492
+ if (authValue.type === "secret") {
4493
+ const token = authValue.token.parsed;
4494
+ context.staticSessionInfo.set({
4495
+ userId: token.k === "sec-legacy" /* SECRET_LEGACY */ ? token.id : token.uid,
4496
+ userInfo: token.k === "sec-legacy" /* SECRET_LEGACY */ ? token.info : token.ui
4497
+ });
4498
+ } else {
4499
+ context.staticSessionInfo.set({
4500
+ userId: void 0,
4501
+ userInfo: void 0
4502
+ });
4503
+ }
4504
+ }
4194
4505
  }
4195
4506
  batchUpdates(() => {
4196
4507
  eventHub.status.notify(newStatus);
4197
4508
  eventHub.connection.notify(newToLegacyStatus(newStatus));
4509
+ notifySelfChanged(doNotBatchUpdates);
4198
4510
  });
4199
4511
  }
4200
4512
  let _connectionLossTimerId;
@@ -4226,20 +4538,15 @@ function createRoom(options, config) {
4226
4538
  }
4227
4539
  }
4228
4540
  function onDidConnect() {
4229
- const sessionInfo = context.sessionInfo.current;
4230
- if (sessionInfo === null) {
4231
- throw new Error("Unexpected missing session info");
4232
- }
4233
- context.buffer.me = {
4541
+ context.buffer.presenceUpdates = {
4234
4542
  type: "full",
4235
4543
  data: (
4236
4544
  // Because context.me.current is a readonly object, we'll have to
4237
4545
  // make a copy here. Otherwise, type errors happen later when
4238
4546
  // "patching" my presence.
4239
- { ...context.me.current }
4547
+ { ...context.myPresence.current }
4240
4548
  )
4241
4549
  };
4242
- context.idFactory = makeIdFactory(sessionInfo.actor);
4243
4550
  if (_getStorage$ !== null) {
4244
4551
  refreshStorage({ flush: false });
4245
4552
  }
@@ -4256,7 +4563,7 @@ function createRoom(options, config) {
4256
4563
  managedSocket.events.onLiveblocksError.subscribe((err) => {
4257
4564
  batchUpdates(() => {
4258
4565
  if (process.env.NODE_ENV !== "production") {
4259
- error(
4566
+ error2(
4260
4567
  `Connection to websocket server closed. Reason: ${err.message} (code: ${err.code}).`
4261
4568
  );
4262
4569
  }
@@ -4304,7 +4611,12 @@ function createRoom(options, config) {
4304
4611
  }
4305
4612
  },
4306
4613
  assertStorageIsWritable: () => {
4307
- if (context.sessionInfo.current?.isReadOnly) {
4614
+ const scopes = context.dynamicSessionInfo.current?.scopes;
4615
+ if (scopes === void 0) {
4616
+ return;
4617
+ }
4618
+ const canWrite = canWriteStorage(scopes);
4619
+ if (!canWrite) {
4308
4620
  throw new Error(
4309
4621
  "Cannot write to storage with a read only user, please ensure the user has write permissions"
4310
4622
  );
@@ -4318,7 +4630,8 @@ function createRoom(options, config) {
4318
4630
  // New/recommended API
4319
4631
  lostConnection: makeEventSource(),
4320
4632
  customEvent: makeEventSource(),
4321
- me: makeEventSource(),
4633
+ self: makeEventSource(),
4634
+ myPresence: makeEventSource(),
4322
4635
  others: makeEventSource(),
4323
4636
  error: makeEventSource(),
4324
4637
  storage: makeEventSource(),
@@ -4331,16 +4644,18 @@ function createRoom(options, config) {
4331
4644
  const message = JSON.stringify(messageOrMessages);
4332
4645
  if (config.unstable_fallbackToHTTP) {
4333
4646
  const size = new TextEncoder().encode(message).length;
4334
- if (size > MAX_MESSAGE_SIZE && managedSocket.token?.raw && config.httpSendEndpoint) {
4335
- if (isTokenExpired(managedSocket.token.parsed)) {
4336
- return managedSocket.reconnect();
4337
- }
4647
+ if (size > MAX_MESSAGE_SIZE && // TODO: support public api key auth in REST API
4648
+ managedSocket.authValue?.type === "secret" && config.httpSendEndpoint) {
4338
4649
  void httpSend(
4339
4650
  message,
4340
- managedSocket.token.raw,
4651
+ managedSocket.authValue.token.raw,
4341
4652
  config.httpSendEndpoint,
4342
4653
  config.polyfills?.fetch
4343
- );
4654
+ ).then((resp) => {
4655
+ if (!resp.ok && resp.status === 403) {
4656
+ managedSocket.reconnect();
4657
+ }
4658
+ });
4344
4659
  warn(
4345
4660
  "Message was too large for websockets and sent over HTTP instead"
4346
4661
  );
@@ -4350,18 +4665,36 @@ function createRoom(options, config) {
4350
4665
  managedSocket.send(message);
4351
4666
  }
4352
4667
  const self = new DerivedRef(
4353
- context.sessionInfo,
4354
- context.me,
4355
- (info, me) => {
4356
- return info !== null ? {
4357
- connectionId: info.actor,
4358
- id: info.userId,
4359
- info: info.userInfo,
4360
- presence: me,
4361
- isReadOnly: info.isReadOnly
4362
- } : null;
4668
+ context.staticSessionInfo,
4669
+ context.dynamicSessionInfo,
4670
+ context.myPresence,
4671
+ (staticSession, dynamicSession, myPresence) => {
4672
+ if (staticSession === null || dynamicSession === null) {
4673
+ return null;
4674
+ } else {
4675
+ const canWrite = canWriteStorage(dynamicSession.scopes);
4676
+ return {
4677
+ connectionId: dynamicSession.actor,
4678
+ id: staticSession.userId,
4679
+ info: staticSession.userInfo,
4680
+ presence: myPresence,
4681
+ canWrite,
4682
+ isReadOnly: !canWrite
4683
+ // Deprecated, kept for backward-compatibility
4684
+ };
4685
+ }
4363
4686
  }
4364
4687
  );
4688
+ let _lastSelf;
4689
+ function notifySelfChanged(batchedUpdatesWrapper) {
4690
+ const currSelf = self.current;
4691
+ if (currSelf !== null && currSelf !== _lastSelf) {
4692
+ batchedUpdatesWrapper(() => {
4693
+ eventHub.self.notify(currSelf);
4694
+ });
4695
+ _lastSelf = currSelf;
4696
+ }
4697
+ }
4365
4698
  const selfAsTreeNode = new DerivedRef(
4366
4699
  self,
4367
4700
  (me) => me !== null ? userToTreeNode("Me", me) : null
@@ -4420,16 +4753,18 @@ function createRoom(options, config) {
4420
4753
  }
4421
4754
  }
4422
4755
  if (presence) {
4423
- eventHub.me.notify(context.me.current);
4756
+ notifySelfChanged(doNotBatchUpdates);
4757
+ eventHub.myPresence.notify(context.myPresence.current);
4424
4758
  }
4425
4759
  if (storageUpdates.size > 0) {
4426
4760
  const updates = Array.from(storageUpdates.values());
4427
4761
  eventHub.storage.notify(updates);
4428
4762
  }
4763
+ notifyStorageStatus();
4429
4764
  });
4430
4765
  }
4431
4766
  function getConnectionId() {
4432
- const info = context.sessionInfo.current;
4767
+ const info = context.dynamicSessionInfo.current;
4433
4768
  if (info) {
4434
4769
  return info.actor;
4435
4770
  }
@@ -4458,14 +4793,14 @@ function createRoom(options, config) {
4458
4793
  data: {}
4459
4794
  };
4460
4795
  for (const key in op.data) {
4461
- reverse.data[key] = context.me.current[key];
4796
+ reverse.data[key] = context.myPresence.current[key];
4462
4797
  }
4463
- context.me.patch(op.data);
4464
- if (context.buffer.me === null) {
4465
- context.buffer.me = { type: "partial", data: op.data };
4798
+ context.myPresence.patch(op.data);
4799
+ if (context.buffer.presenceUpdates === null) {
4800
+ context.buffer.presenceUpdates = { type: "partial", data: op.data };
4466
4801
  } else {
4467
4802
  for (const key in op.data) {
4468
- context.buffer.me.data[key] = op.data[key];
4803
+ context.buffer.presenceUpdates.data[key] = op.data[key];
4469
4804
  }
4470
4805
  }
4471
4806
  output.reverse.unshift(reverse);
@@ -4501,7 +4836,6 @@ function createRoom(options, config) {
4501
4836
  }
4502
4837
  }
4503
4838
  }
4504
- notifyStorageStatus();
4505
4839
  return {
4506
4840
  ops,
4507
4841
  reverse: output.reverse,
@@ -4556,8 +4890,8 @@ function createRoom(options, config) {
4556
4890
  }
4557
4891
  function updatePresence(patch, options2) {
4558
4892
  const oldValues = {};
4559
- if (context.buffer.me === null) {
4560
- context.buffer.me = {
4893
+ if (context.buffer.presenceUpdates === null) {
4894
+ context.buffer.presenceUpdates = {
4561
4895
  type: "partial",
4562
4896
  data: {}
4563
4897
  };
@@ -4567,10 +4901,10 @@ function createRoom(options, config) {
4567
4901
  if (overrideValue === void 0) {
4568
4902
  continue;
4569
4903
  }
4570
- context.buffer.me.data[key] = overrideValue;
4571
- oldValues[key] = context.me.current[key];
4904
+ context.buffer.presenceUpdates.data[key] = overrideValue;
4905
+ oldValues[key] = context.myPresence.current[key];
4572
4906
  }
4573
- context.me.patch(patch);
4907
+ context.myPresence.patch(patch);
4574
4908
  if (context.activeBatch) {
4575
4909
  if (options2?.addToHistory) {
4576
4910
  context.activeBatch.reverseOps.unshift({
@@ -4592,9 +4926,6 @@ function createRoom(options, config) {
4592
4926
  });
4593
4927
  }
4594
4928
  }
4595
- function isStorageReadOnly(scopes) {
4596
- return scopes.includes("room:read" /* Read */) && scopes.includes("room:presence:write" /* PresenceWrite */) && !scopes.includes("room:write" /* Write */);
4597
- }
4598
4929
  function onUpdatePresenceMessage(message) {
4599
4930
  if (message.targetActor !== void 0) {
4600
4931
  const oldUser = context.others.getUser(message.actor);
@@ -4625,11 +4956,17 @@ function createRoom(options, config) {
4625
4956
  }
4626
4957
  return null;
4627
4958
  }
4628
- function onRoomStateMessage(message) {
4629
- for (const connectionId in context.others._connections) {
4959
+ function onRoomStateMessage(message, batchedUpdatesWrapper) {
4960
+ context.dynamicSessionInfo.set({
4961
+ actor: message.actor,
4962
+ scopes: message.scopes
4963
+ });
4964
+ context.idFactory = makeIdFactory(message.actor);
4965
+ notifySelfChanged(batchedUpdatesWrapper);
4966
+ for (const connectionId of context.others.connectionIds()) {
4630
4967
  const user = message.users[connectionId];
4631
4968
  if (user === void 0) {
4632
- context.others.removeConnection(Number(connectionId));
4969
+ context.others.removeConnection(connectionId);
4633
4970
  }
4634
4971
  }
4635
4972
  for (const key in message.users) {
@@ -4639,7 +4976,7 @@ function createRoom(options, config) {
4639
4976
  connectionId,
4640
4977
  user.id,
4641
4978
  user.info,
4642
- isStorageReadOnly(user.scopes)
4979
+ user.scopes
4643
4980
  );
4644
4981
  }
4645
4982
  return { type: "reset" };
@@ -4660,11 +4997,11 @@ function createRoom(options, config) {
4660
4997
  message.actor,
4661
4998
  message.id,
4662
4999
  message.info,
4663
- isStorageReadOnly(message.scopes)
5000
+ message.scopes
4664
5001
  );
4665
5002
  context.buffer.messages.push({
4666
5003
  type: 100 /* UPDATE_PRESENCE */,
4667
- data: context.me.current,
5004
+ data: context.myPresence.current,
4668
5005
  targetActor: message.actor
4669
5006
  });
4670
5007
  flushNowOrSoon();
@@ -4749,7 +5086,7 @@ function createRoom(options, config) {
4749
5086
  break;
4750
5087
  }
4751
5088
  case 104 /* ROOM_STATE */: {
4752
- updates.others.push(onRoomStateMessage(message));
5089
+ updates.others.push(onRoomStateMessage(message, doNotBatchUpdates));
4753
5090
  break;
4754
5091
  }
4755
5092
  case 200 /* INITIAL_STORAGE_STATE */: {
@@ -4828,7 +5165,7 @@ ${Array.from(traces).join("\n\n")}`
4828
5165
  lastFlushedAt: now,
4829
5166
  messages: [],
4830
5167
  storageOperations: [],
4831
- me: null
5168
+ presenceUpdates: null
4832
5169
  };
4833
5170
  } else {
4834
5171
  clearTimeout(context.buffer.flushTimerID);
@@ -4840,18 +5177,18 @@ ${Array.from(traces).join("\n\n")}`
4840
5177
  }
4841
5178
  function serializeBuffer() {
4842
5179
  const messages = [];
4843
- if (context.buffer.me) {
5180
+ if (context.buffer.presenceUpdates) {
4844
5181
  messages.push(
4845
- context.buffer.me.type === "full" ? {
5182
+ context.buffer.presenceUpdates.type === "full" ? {
4846
5183
  type: 100 /* UPDATE_PRESENCE */,
4847
5184
  // Populating the `targetActor` field turns this message into
4848
5185
  // a Full Presence™ update message (not a patch), which will get
4849
5186
  // interpreted by other clients as such.
4850
5187
  targetActor: -1,
4851
- data: context.buffer.me.data
5188
+ data: context.buffer.presenceUpdates.data
4852
5189
  } : {
4853
5190
  type: 100 /* UPDATE_PRESENCE */,
4854
- data: context.buffer.me.data
5191
+ data: context.buffer.presenceUpdates.data
4855
5192
  }
4856
5193
  );
4857
5194
  }
@@ -5053,7 +5390,8 @@ ${Array.from(traces).join("\n\n")}`
5053
5390
  lostConnection: eventHub.lostConnection.observable,
5054
5391
  customEvent: eventHub.customEvent.observable,
5055
5392
  others: eventHub.others.observable,
5056
- me: eventHub.me.observable,
5393
+ self: eventHub.self.observable,
5394
+ myPresence: eventHub.myPresence.observable,
5057
5395
  error: eventHub.error.observable,
5058
5396
  storage: eventHub.storage.observable,
5059
5397
  history: eventHub.history.observable,
@@ -5061,65 +5399,70 @@ ${Array.from(traces).join("\n\n")}`
5061
5399
  storageStatus: eventHub.storageStatus.observable,
5062
5400
  ydoc: eventHub.ydoc.observable
5063
5401
  };
5064
- return {
5065
- /* NOTE: Exposing __internal here only to allow testing implementation details in unit tests */
5066
- __internal: {
5067
- get buffer() {
5068
- return context.buffer;
5069
- },
5070
- // prettier-ignore
5071
- get undoStack() {
5072
- return context.undoStack;
5402
+ return Object.defineProperty(
5403
+ {
5404
+ /* NOTE: Exposing __internal here only to allow testing implementation details in unit tests */
5405
+ __internal: {
5406
+ get presenceBuffer() {
5407
+ return deepClone(context.buffer.presenceUpdates?.data ?? null);
5408
+ },
5409
+ // prettier-ignore
5410
+ get undoStack() {
5411
+ return deepClone(context.undoStack);
5412
+ },
5413
+ // prettier-ignore
5414
+ get nodeCount() {
5415
+ return context.nodes.size;
5416
+ },
5417
+ // prettier-ignore
5418
+ // Support for the Liveblocks browser extension
5419
+ getSelf_forDevTools: () => selfAsTreeNode.current,
5420
+ getOthers_forDevTools: () => others_forDevTools.current,
5421
+ // prettier-ignore
5422
+ send: {
5423
+ // These exist only for our E2E testing app
5424
+ explicitClose: (event) => managedSocket._privateSendMachineEvent({ type: "EXPLICIT_SOCKET_CLOSE", event }),
5425
+ implicitClose: () => managedSocket._privateSendMachineEvent({ type: "NAVIGATOR_OFFLINE" })
5426
+ }
5073
5427
  },
5074
- // prettier-ignore
5075
- get nodeCount() {
5076
- return context.nodes.size;
5428
+ id: config.roomId,
5429
+ subscribe: makeClassicSubscribeFn(events),
5430
+ connect: () => managedSocket.connect(),
5431
+ reconnect: () => managedSocket.reconnect(),
5432
+ disconnect: () => managedSocket.disconnect(),
5433
+ destroy: () => managedSocket.destroy(),
5434
+ // Presence
5435
+ updatePresence,
5436
+ updateYDoc,
5437
+ broadcastEvent,
5438
+ // Storage
5439
+ batch,
5440
+ history: {
5441
+ undo,
5442
+ redo,
5443
+ canUndo,
5444
+ canRedo,
5445
+ pause: pauseHistory,
5446
+ resume: resumeHistory
5077
5447
  },
5078
- // prettier-ignore
5079
- // Support for the Liveblocks browser extension
5080
- getSelf_forDevTools: () => selfAsTreeNode.current,
5081
- getOthers_forDevTools: () => others_forDevTools.current,
5082
- // prettier-ignore
5083
- send: {
5084
- // These exist only for our E2E testing app
5085
- explicitClose: (event) => managedSocket._privateSendMachineEvent({ type: "EXPLICIT_SOCKET_CLOSE", event }),
5086
- implicitClose: () => managedSocket._privateSendMachineEvent({ type: "NAVIGATOR_OFFLINE" })
5087
- }
5088
- },
5089
- id: config.roomId,
5090
- subscribe: makeClassicSubscribeFn(events),
5091
- connect: () => managedSocket.connect(),
5092
- reconnect: () => managedSocket.reconnect(),
5093
- disconnect: () => managedSocket.disconnect(),
5094
- destroy: () => managedSocket.destroy(),
5095
- // Presence
5096
- updatePresence,
5097
- updateYDoc,
5098
- broadcastEvent,
5099
- // Storage
5100
- batch,
5101
- history: {
5102
- undo,
5103
- redo,
5104
- canUndo,
5105
- canRedo,
5106
- pause: pauseHistory,
5107
- resume: resumeHistory
5448
+ fetchYDoc,
5449
+ getStorage,
5450
+ getStorageSnapshot,
5451
+ getStorageStatus,
5452
+ events,
5453
+ // Core
5454
+ getStatus: () => managedSocket.getStatus(),
5455
+ getConnectionState: () => managedSocket.getLegacyStatus(),
5456
+ getSelf: () => self.current,
5457
+ // Presence
5458
+ getPresence: () => context.myPresence.current,
5459
+ getOthers: () => context.others.current
5108
5460
  },
5109
- fetchYDoc,
5110
- getStorage,
5111
- getStorageSnapshot,
5112
- getStorageStatus,
5113
- events,
5114
- // Core
5115
- getStatus: () => managedSocket.getStatus(),
5116
- getConnectionState: () => managedSocket.getLegacyStatus(),
5117
- isSelfAware: () => context.sessionInfo.current !== null,
5118
- getSelf: () => self.current,
5119
- // Presence
5120
- getPresence: () => context.me.current,
5121
- getOthers: () => context.others.current
5122
- };
5461
+ // Explictly make the __internal field non-enumerable, to avoid aggressive
5462
+ // freezing when used with Immer
5463
+ "__internal",
5464
+ { enumerable: false }
5465
+ );
5123
5466
  }
5124
5467
  function makeClassicSubscribeFn(events) {
5125
5468
  function subscribeToLiveStructureDeeply(node, callback) {
@@ -5153,7 +5496,7 @@ function makeClassicSubscribeFn(events) {
5153
5496
  callback
5154
5497
  );
5155
5498
  case "my-presence":
5156
- return events.me.subscribe(callback);
5499
+ return events.myPresence.subscribe(callback);
5157
5500
  case "others": {
5158
5501
  const cb = callback;
5159
5502
  return events.others.subscribe(
@@ -5207,24 +5550,30 @@ function makeClassicSubscribeFn(events) {
5207
5550
  function isRoomEventName(value) {
5208
5551
  return value === "my-presence" || value === "others" || value === "event" || value === "error" || value === "history" || value === "status" || value === "storage-status" || value === "lost-connection" || value === "connection";
5209
5552
  }
5210
- function makeCreateSocketDelegateForRoom(liveblocksServer, WebSocketPolyfill) {
5211
- return (richToken) => {
5553
+ function makeAuthDelegateForRoom(roomId, authManager) {
5554
+ return async () => {
5555
+ return authManager.getAuthValue("room:read", roomId);
5556
+ };
5557
+ }
5558
+ function makeCreateSocketDelegateForRoom(roomId, liveblocksServer, WebSocketPolyfill) {
5559
+ return (authValue) => {
5212
5560
  const ws = WebSocketPolyfill ?? (typeof WebSocket === "undefined" ? void 0 : WebSocket);
5213
5561
  if (ws === void 0) {
5214
5562
  throw new StopRetrying(
5215
5563
  "To use Liveblocks client in a non-dom environment, you need to provide a WebSocket polyfill."
5216
5564
  );
5217
5565
  }
5218
- const token = richToken.raw;
5219
- return new ws(
5220
- `${liveblocksServer}/?token=${token}&version=${// prettier-ignore
5221
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
5222
- // @ts-ignore (__PACKAGE_VERSION__ will be injected by the build script)
5223
- true ? (
5224
- /* istanbul ignore next */
5225
- "1.2.0-internal6"
5226
- ) : "dev"}`
5227
- );
5566
+ const url = new URL(liveblocksServer);
5567
+ url.searchParams.set("roomId", roomId);
5568
+ if (authValue.type === "secret") {
5569
+ url.searchParams.set("tok", authValue.token.raw);
5570
+ } else if (authValue.type === "public") {
5571
+ url.searchParams.set("pubkey", authValue.publicApiKey);
5572
+ } else {
5573
+ return assertNever(authValue, "Unhandled case");
5574
+ }
5575
+ url.searchParams.set("version", PKG_VERSION || "dev");
5576
+ return new ws(url.toString());
5228
5577
  };
5229
5578
  }
5230
5579
  async function httpSend(message, token, endpoint, fetchPolyfill) {
@@ -5239,83 +5588,6 @@ async function httpSend(message, token, endpoint, fetchPolyfill) {
5239
5588
  body: message
5240
5589
  });
5241
5590
  }
5242
- function makeAuthDelegateForRoom(roomId, authentication, fetchPolyfill) {
5243
- const fetcher = fetchPolyfill ?? (typeof window === "undefined" ? void 0 : window.fetch);
5244
- if (authentication.type === "public") {
5245
- return async () => {
5246
- if (fetcher === void 0) {
5247
- throw new StopRetrying(
5248
- "To use Liveblocks client in a non-dom environment with a publicApiKey, you need to provide a fetch polyfill."
5249
- );
5250
- }
5251
- return fetchAuthEndpoint(fetcher, authentication.url, {
5252
- room: roomId,
5253
- publicApiKey: authentication.publicApiKey
5254
- }).then(({ token }) => parseAuthToken(token));
5255
- };
5256
- } else if (authentication.type === "private") {
5257
- return async () => {
5258
- if (fetcher === void 0) {
5259
- throw new StopRetrying(
5260
- "To use Liveblocks client in a non-dom environment with a url as auth endpoint, you need to provide a fetch polyfill."
5261
- );
5262
- }
5263
- return fetchAuthEndpoint(fetcher, authentication.url, {
5264
- room: roomId
5265
- }).then(({ token }) => parseAuthToken(token));
5266
- };
5267
- } else if (authentication.type === "custom") {
5268
- return async () => {
5269
- const response = await authentication.callback(roomId);
5270
- if (!response || !response.token) {
5271
- throw new Error(
5272
- 'We expect the authentication callback to return a token, but it does not. Hint: the return value should look like: { token: "..." }'
5273
- );
5274
- }
5275
- return parseAuthToken(response.token);
5276
- };
5277
- } else {
5278
- throw new Error("Internal error. Unexpected authentication type");
5279
- }
5280
- }
5281
- async function fetchAuthEndpoint(fetch2, endpoint, body) {
5282
- const res = await fetch2(endpoint, {
5283
- method: "POST",
5284
- headers: {
5285
- "Content-Type": "application/json"
5286
- },
5287
- // Credentials are needed to support authentication with cookies
5288
- credentials: "include",
5289
- body: JSON.stringify(body)
5290
- });
5291
- if (!res.ok) {
5292
- const reason = `${(await res.text()).trim() || "reason not provided in auth response"} (${res.status} returned by POST ${endpoint})`;
5293
- if (res.status === 401 || res.status === 403) {
5294
- throw new StopRetrying(`Unauthorized: ${reason}`);
5295
- } else {
5296
- throw new Error(`Failed to authenticate: ${reason}`);
5297
- }
5298
- }
5299
- let data;
5300
- try {
5301
- data = await res.json();
5302
- } catch (er) {
5303
- throw new Error(
5304
- `Expected a JSON response when doing a POST request on "${endpoint}". ${String(
5305
- er
5306
- )}`
5307
- );
5308
- }
5309
- if (!isPlainObject(data) || typeof data.token !== "string") {
5310
- throw new Error(
5311
- `Expected a JSON response of the form \`{ token: "..." }\` when doing a POST request on "${endpoint}", but got ${JSON.stringify(
5312
- data
5313
- )}`
5314
- );
5315
- }
5316
- const { token } = data;
5317
- return { token };
5318
- }
5319
5591
 
5320
5592
  // src/client.ts
5321
5593
  var MIN_THROTTLE = 16;
@@ -5327,7 +5599,7 @@ var MAX_LOST_CONNECTION_TIMEOUT = 3e4;
5327
5599
  var DEFAULT_LOST_CONNECTION_TIMEOUT = 5e3;
5328
5600
  function getServerFromClientOptions(clientOptions) {
5329
5601
  const rawOptions = clientOptions;
5330
- return typeof rawOptions.liveblocksServer === "string" ? rawOptions.liveblocksServer : "wss://api.liveblocks.io/v6";
5602
+ return typeof rawOptions.liveblocksServer === "string" ? rawOptions.liveblocksServer : "wss://api.liveblocks.io/v7";
5331
5603
  }
5332
5604
  function createClient(options) {
5333
5605
  const clientOptions = options;
@@ -5335,6 +5607,7 @@ function createClient(options) {
5335
5607
  const lostConnectionTimeout = getLostConnectionTimeout(
5336
5608
  clientOptions.lostConnectionTimeout ?? DEFAULT_LOST_CONNECTION_TIMEOUT
5337
5609
  );
5610
+ const authManager = createAuthManager(options);
5338
5611
  const rooms = /* @__PURE__ */ new Map();
5339
5612
  function getRoom(roomId) {
5340
5613
  const room = rooms.get(roomId);
@@ -5359,11 +5632,17 @@ function createClient(options) {
5359
5632
  throttleDelay,
5360
5633
  lostConnectionTimeout,
5361
5634
  polyfills: clientOptions.polyfills,
5362
- delegates: clientOptions.mockedDelegates,
5635
+ delegates: clientOptions.mockedDelegates ?? {
5636
+ createSocket: makeCreateSocketDelegateForRoom(
5637
+ roomId,
5638
+ getServerFromClientOptions(clientOptions),
5639
+ clientOptions.polyfills?.WebSocket
5640
+ ),
5641
+ authenticate: makeAuthDelegateForRoom(roomId, authManager)
5642
+ },
5363
5643
  enableDebugLogging: clientOptions.enableDebugLogging,
5364
5644
  unstable_batchedUpdates: options2?.unstable_batchedUpdates,
5365
5645
  liveblocksServer: getServerFromClientOptions(clientOptions),
5366
- authentication: prepareAuthentication(clientOptions, roomId),
5367
5646
  httpSendEndpoint: buildLiveblocksHttpSendEndpoint(
5368
5647
  clientOptions,
5369
5648
  roomId
@@ -5422,48 +5701,6 @@ function getLostConnectionTimeout(value) {
5422
5701
  RECOMMENDED_MIN_LOST_CONNECTION_TIMEOUT
5423
5702
  );
5424
5703
  }
5425
- function prepareAuthentication(clientOptions, roomId) {
5426
- const { publicApiKey, authEndpoint } = clientOptions;
5427
- if (authEndpoint !== void 0 && publicApiKey !== void 0) {
5428
- throw new Error(
5429
- "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"
5430
- );
5431
- }
5432
- if (typeof publicApiKey === "string") {
5433
- if (publicApiKey.startsWith("sk_")) {
5434
- throw new Error(
5435
- "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"
5436
- );
5437
- } else if (!publicApiKey.startsWith("pk_")) {
5438
- throw new Error(
5439
- "Invalid key. Please use the public key format: pk_<public key>. For more information: https://liveblocks.io/docs/api-reference/liveblocks-client#createClientPublicKey"
5440
- );
5441
- }
5442
- return {
5443
- type: "public",
5444
- publicApiKey,
5445
- url: buildLiveblocksPublicAuthorizeEndpoint(clientOptions, roomId)
5446
- };
5447
- }
5448
- if (typeof authEndpoint === "string") {
5449
- return {
5450
- type: "private",
5451
- url: authEndpoint
5452
- };
5453
- } else if (typeof authEndpoint === "function") {
5454
- return {
5455
- type: "custom",
5456
- callback: authEndpoint
5457
- };
5458
- } else if (authEndpoint !== void 0) {
5459
- throw new Error(
5460
- "authEndpoint must be a string or a function. For more information: https://liveblocks.io/docs/api-reference/liveblocks-client#createClientAuthEndpoint"
5461
- );
5462
- }
5463
- throw new Error(
5464
- "Invalid Liveblocks client options. For more information: https://liveblocks.io/docs/api-reference/liveblocks-client#createClient"
5465
- );
5466
- }
5467
5704
  function buildLiveblocksHttpSendEndpoint(options, roomId) {
5468
5705
  if (options.httpSendEndpoint) {
5469
5706
  return options.httpSendEndpoint.replace("{roomId}", roomId);
@@ -5472,14 +5709,6 @@ function buildLiveblocksHttpSendEndpoint(options, roomId) {
5472
5709
  roomId
5473
5710
  )}/send-message`;
5474
5711
  }
5475
- function buildLiveblocksPublicAuthorizeEndpoint(options, roomId) {
5476
- if (options.publicAuthorizeEndpoint) {
5477
- return options.publicAuthorizeEndpoint.replace("{roomId}", roomId);
5478
- }
5479
- return `https://api.liveblocks.io/v2/rooms/${encodeURIComponent(
5480
- roomId
5481
- )}/public/authorize`;
5482
- }
5483
5712
 
5484
5713
  // src/crdts/utils.ts
5485
5714
  function toPlainLson(lson) {
@@ -5638,7 +5867,7 @@ function patchLiveObjectKey(liveObject, key, prev, next) {
5638
5867
  if (process.env.NODE_ENV !== "production") {
5639
5868
  const nonSerializableValue = findNonSerializableValue(next);
5640
5869
  if (nonSerializableValue) {
5641
- error(
5870
+ error2(
5642
5871
  `New state path: '${nonSerializableValue.path}' value: '${String(
5643
5872
  nonSerializableValue.value
5644
5873
  )}' is not serializable.
@@ -5852,18 +6081,8 @@ function shallow(a, b) {
5852
6081
  return shallowObj(a, b);
5853
6082
  }
5854
6083
 
5855
- // src/types/IWebSocket.ts
5856
- var WebsocketCloseCodes = /* @__PURE__ */ ((WebsocketCloseCodes2) => {
5857
- WebsocketCloseCodes2[WebsocketCloseCodes2["CLOSE_ABNORMAL"] = 1006] = "CLOSE_ABNORMAL";
5858
- WebsocketCloseCodes2[WebsocketCloseCodes2["INVALID_MESSAGE_FORMAT"] = 4e3] = "INVALID_MESSAGE_FORMAT";
5859
- WebsocketCloseCodes2[WebsocketCloseCodes2["NOT_ALLOWED"] = 4001] = "NOT_ALLOWED";
5860
- WebsocketCloseCodes2[WebsocketCloseCodes2["MAX_NUMBER_OF_MESSAGES_PER_SECONDS"] = 4002] = "MAX_NUMBER_OF_MESSAGES_PER_SECONDS";
5861
- WebsocketCloseCodes2[WebsocketCloseCodes2["MAX_NUMBER_OF_CONCURRENT_CONNECTIONS"] = 4003] = "MAX_NUMBER_OF_CONCURRENT_CONNECTIONS";
5862
- WebsocketCloseCodes2[WebsocketCloseCodes2["MAX_NUMBER_OF_MESSAGES_PER_DAY_PER_APP"] = 4004] = "MAX_NUMBER_OF_MESSAGES_PER_DAY_PER_APP";
5863
- WebsocketCloseCodes2[WebsocketCloseCodes2["MAX_NUMBER_OF_CONCURRENT_CONNECTIONS_PER_ROOM"] = 4005] = "MAX_NUMBER_OF_CONCURRENT_CONNECTIONS_PER_ROOM";
5864
- WebsocketCloseCodes2[WebsocketCloseCodes2["CLOSE_WITHOUT_RETRY"] = 4999] = "CLOSE_WITHOUT_RETRY";
5865
- return WebsocketCloseCodes2;
5866
- })(WebsocketCloseCodes || {});
6084
+ // src/index.ts
6085
+ detectDupes(PKG_NAME, PKG_VERSION, PKG_FORMAT);
5867
6086
  export {
5868
6087
  ClientMsgCode,
5869
6088
  CrdtType,
@@ -5881,6 +6100,7 @@ export {
5881
6100
  createClient,
5882
6101
  deprecate,
5883
6102
  deprecateIf,
6103
+ detectDupes,
5884
6104
  errorIf,
5885
6105
  freeze,
5886
6106
  isChildCrdt,