@liveblocks/client 0.14.0 → 0.14.3

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.
@@ -8,6 +8,7 @@ export declare type ApplyResult = {
8
8
  export interface Doc {
9
9
  generateId: () => string;
10
10
  generateOpId: () => string;
11
+ getItem: (id: string) => AbstractCrdt | undefined;
11
12
  addItem: (id: string, item: AbstractCrdt) => void;
12
13
  deleteItem: (id: string) => void;
13
14
  dispatch: (ops: Op[], reverseOps: Op[], modified: AbstractCrdt[]) => void;
@@ -45,7 +46,7 @@ export declare abstract class AbstractCrdt {
45
46
  /**
46
47
  * INTERNAL
47
48
  */
48
- abstract _attachChild(id: string, key: string, crdt: AbstractCrdt, isLocal: boolean): ApplyResult;
49
+ abstract _attachChild(id: string, key: string, crdt: AbstractCrdt, opId: string, isLocal: boolean): ApplyResult;
49
50
  /**
50
51
  * INTERNAL
51
52
  */
@@ -25,7 +25,7 @@ export declare class LiveList<T> extends AbstractCrdt {
25
25
  /**
26
26
  * INTERNAL
27
27
  */
28
- _attachChild(id: string, key: string, child: AbstractCrdt, isLocal: boolean): ApplyResult;
28
+ _attachChild(id: string, key: string, child: AbstractCrdt, opId: string, isLocal: boolean): ApplyResult;
29
29
  /**
30
30
  * INTERNAL
31
31
  */
@@ -96,11 +96,14 @@ class LiveList extends AbstractCrdt_1.AbstractCrdt {
96
96
  /**
97
97
  * INTERNAL
98
98
  */
99
- _attachChild(id, key, child, isLocal) {
99
+ _attachChild(id, key, child, opId, isLocal) {
100
100
  var _a;
101
101
  if (this._doc == null) {
102
102
  throw new Error("Can't attach child if doc is not present");
103
103
  }
104
+ if (this._doc.getItem(id) !== undefined) {
105
+ return { modified: false };
106
+ }
104
107
  child._attach(id, this._doc);
105
108
  child._setParentLink(this, key);
106
109
  const index = __classPrivateFieldGet(this, _LiveList_items, "f").findIndex((entry) => entry[1] === key);
@@ -23,7 +23,7 @@ export declare class LiveMap<TKey extends string, TValue> extends AbstractCrdt {
23
23
  /**
24
24
  * INTERNAL
25
25
  */
26
- _attachChild(id: string, key: TKey, child: AbstractCrdt, isLocal: boolean): ApplyResult;
26
+ _attachChild(id: string, key: TKey, child: AbstractCrdt, opId: string, isLocal: boolean): ApplyResult;
27
27
  /**
28
28
  * INTERNAL
29
29
  */
@@ -100,10 +100,13 @@ class LiveMap extends AbstractCrdt_1.AbstractCrdt {
100
100
  /**
101
101
  * INTERNAL
102
102
  */
103
- _attachChild(id, key, child, isLocal) {
103
+ _attachChild(id, key, child, opId, isLocal) {
104
104
  if (this._doc == null) {
105
105
  throw new Error("Can't attach child if doc is not present");
106
106
  }
107
+ if (this._doc.getItem(id) !== undefined) {
108
+ return { modified: false };
109
+ }
107
110
  const previousValue = __classPrivateFieldGet(this, _LiveMap_map, "f").get(key);
108
111
  let reverse;
109
112
  if (previousValue) {
@@ -27,7 +27,7 @@ export declare class LiveObject<T extends Record<string, any> = Record<string, a
27
27
  /**
28
28
  * INTERNAL
29
29
  */
30
- _attachChild(id: string, key: keyof T, child: AbstractCrdt, isLocal: boolean): ApplyResult;
30
+ _attachChild(id: string, key: keyof T, child: AbstractCrdt, opId: string, isLocal: boolean): ApplyResult;
31
31
  /**
32
32
  * INTERNAL
33
33
  */
@@ -106,10 +106,32 @@ class LiveObject extends AbstractCrdt_1.AbstractCrdt {
106
106
  /**
107
107
  * INTERNAL
108
108
  */
109
- _attachChild(id, key, child, isLocal) {
109
+ _attachChild(id, key, child, opId, isLocal) {
110
110
  if (this._doc == null) {
111
111
  throw new Error("Can't attach child if doc is not present");
112
112
  }
113
+ if (this._doc.getItem(id) !== undefined) {
114
+ if (__classPrivateFieldGet(this, _LiveObject_propToLastUpdate, "f").get(key) === opId) {
115
+ // Acknowlegment from local operation
116
+ __classPrivateFieldGet(this, _LiveObject_propToLastUpdate, "f").delete(key);
117
+ }
118
+ return { modified: false };
119
+ }
120
+ if (isLocal) {
121
+ __classPrivateFieldGet(this, _LiveObject_propToLastUpdate, "f").set(key, opId);
122
+ }
123
+ else if (__classPrivateFieldGet(this, _LiveObject_propToLastUpdate, "f").get(key) === undefined) {
124
+ // Remote operation with no local change => apply operation
125
+ }
126
+ else if (__classPrivateFieldGet(this, _LiveObject_propToLastUpdate, "f").get(key) === opId) {
127
+ // Acknowlegment from local operation
128
+ __classPrivateFieldGet(this, _LiveObject_propToLastUpdate, "f").delete(key);
129
+ return { modified: false };
130
+ }
131
+ else {
132
+ // Conflict, ignore remote operation
133
+ return { modified: false };
134
+ }
113
135
  const previousValue = __classPrivateFieldGet(this, _LiveObject_map, "f").get(key);
114
136
  let reverse;
115
137
  if ((0, utils_1.isCrdt)(previousValue)) {
@@ -284,7 +306,6 @@ class LiveObject extends AbstractCrdt_1.AbstractCrdt {
284
306
  data: {},
285
307
  };
286
308
  for (const key in overrides) {
287
- __classPrivateFieldGet(this, _LiveObject_propToLastUpdate, "f").set(key, opId);
288
309
  const oldValue = __classPrivateFieldGet(this, _LiveObject_map, "f").get(key);
289
310
  if (oldValue instanceof AbstractCrdt_1.AbstractCrdt) {
290
311
  reverseOps.push(...oldValue._serialize(this._id, key));
@@ -300,10 +321,16 @@ class LiveObject extends AbstractCrdt_1.AbstractCrdt {
300
321
  if (newValue instanceof AbstractCrdt_1.AbstractCrdt) {
301
322
  newValue._setParentLink(this, key);
302
323
  newValue._attach(this._doc.generateId(), this._doc);
303
- ops.push(...newValue._serialize(this._id, key, this._doc));
324
+ const newAttachChildOps = newValue._serialize(this._id, key, this._doc);
325
+ const createCrdtOp = newAttachChildOps.find((op) => op.parentId === this._id);
326
+ if (createCrdtOp) {
327
+ __classPrivateFieldGet(this, _LiveObject_propToLastUpdate, "f").set(key, createCrdtOp.opId);
328
+ }
329
+ ops.push(...newAttachChildOps);
304
330
  }
305
331
  else {
306
332
  updatedProps[key] = newValue;
333
+ __classPrivateFieldGet(this, _LiveObject_propToLastUpdate, "f").set(key, opId);
307
334
  }
308
335
  __classPrivateFieldGet(this, _LiveObject_map, "f").set(key, newValue);
309
336
  }
@@ -378,6 +405,11 @@ _LiveObject_map = new WeakMap(), _LiveObject_propToLastUpdate = new WeakMap(), _
378
405
  if (__classPrivateFieldGet(this, _LiveObject_map, "f").has(key) === false) {
379
406
  return { modified: false };
380
407
  }
408
+ // If a local operation exists on the same key
409
+ // prevent flickering by not applying delete op.
410
+ if (__classPrivateFieldGet(this, _LiveObject_propToLastUpdate, "f").get(key) !== undefined) {
411
+ return { modified: false };
412
+ }
381
413
  const oldValue = __classPrivateFieldGet(this, _LiveObject_map, "f").get(key);
382
414
  let reverse = [];
383
415
  if ((0, utils_1.isCrdt)(oldValue)) {
@@ -19,7 +19,7 @@ export declare class LiveRegister<TValue = any> extends AbstractCrdt {
19
19
  * INTERNAL
20
20
  */
21
21
  _toSerializedCrdt(): SerializedCrdt;
22
- _attachChild(id: string, key: string, crdt: AbstractCrdt, isLocal: boolean): ApplyResult;
22
+ _attachChild(id: string, key: string, crdt: AbstractCrdt, opId: string, isLocal: boolean): ApplyResult;
23
23
  _detachChild(crdt: AbstractCrdt): void;
24
24
  _apply(op: Op, isLocal: boolean): ApplyResult;
25
25
  }
@@ -68,7 +68,7 @@ class LiveRegister extends AbstractCrdt_1.AbstractCrdt {
68
68
  data: this.data,
69
69
  };
70
70
  }
71
- _attachChild(id, key, crdt, isLocal) {
71
+ _attachChild(id, key, crdt, opId, isLocal) {
72
72
  throw new Error("Method not implemented.");
73
73
  }
74
74
  _detachChild(crdt) {
package/lib/cjs/client.js CHANGED
@@ -29,11 +29,7 @@ const room_1 = require("./room");
29
29
  */
30
30
  function createClient(options) {
31
31
  const clientOptions = options;
32
- if (typeof clientOptions.throttle === "number") {
33
- if (clientOptions.throttle < 80 || clientOptions.throttle > 1000) {
34
- throw new Error("Liveblocks client throttle should be between 80 and 1000 ms");
35
- }
36
- }
32
+ const throttleDelay = getThrottleDelayFromOptions(options);
37
33
  const rooms = new Map();
38
34
  function getRoom(roomId) {
39
35
  const internalRoom = rooms.get(roomId);
@@ -44,9 +40,21 @@ function createClient(options) {
44
40
  if (internalRoom) {
45
41
  return internalRoom.room;
46
42
  }
47
- internalRoom = (0, room_1.createRoom)(roomId, Object.assign(Object.assign({}, clientOptions), options));
43
+ internalRoom = (0, room_1.createRoom)({
44
+ defaultPresence: options.defaultPresence,
45
+ defaultStorageRoot: options.defaultStorageRoot,
46
+ }, {
47
+ room: roomId,
48
+ throttleDelay,
49
+ WebSocketPolyfill: clientOptions.WebSocketPolyfill,
50
+ fetchPolyfill: clientOptions.fetchPolyfill,
51
+ liveblocksServer: clientOptions.liveblocksServer || "wss://liveblocks.net/v5",
52
+ authentication: prepareAuthentication(clientOptions),
53
+ });
48
54
  rooms.set(roomId, internalRoom);
49
- internalRoom.connect();
55
+ if (!options.DO_NOT_USE_withoutConnecting) {
56
+ internalRoom.connect();
57
+ }
50
58
  return internalRoom.room;
51
59
  }
52
60
  function leave(roomId) {
@@ -78,3 +86,38 @@ function createClient(options) {
78
86
  };
79
87
  }
80
88
  exports.createClient = createClient;
89
+ function getThrottleDelayFromOptions(options) {
90
+ if (options.throttle === undefined) {
91
+ return 100;
92
+ }
93
+ if (typeof options.throttle !== "number" ||
94
+ options.throttle < 80 ||
95
+ options.throttle > 1000) {
96
+ throw new Error("throttle should be a number between 80 and 1000.");
97
+ }
98
+ return options.throttle;
99
+ }
100
+ function prepareAuthentication(clientOptions) {
101
+ // TODO: throw descriptive errors for invalid options
102
+ if (typeof clientOptions.publicApiKey === "string") {
103
+ return {
104
+ type: "public",
105
+ publicApiKey: clientOptions.publicApiKey,
106
+ url: clientOptions.publicAuthorizeEndpoint ||
107
+ "https://liveblocks.io/api/public/authorize",
108
+ };
109
+ }
110
+ else if (typeof clientOptions.authEndpoint === "string") {
111
+ return {
112
+ type: "private",
113
+ url: clientOptions.authEndpoint,
114
+ };
115
+ }
116
+ else if (typeof clientOptions.authEndpoint === "function") {
117
+ return {
118
+ type: "custom",
119
+ callback: clientOptions.authEndpoint,
120
+ };
121
+ }
122
+ throw new Error("Invalid Liveblocks client options. For more information: https://liveblocks.io/docs/api-reference/liveblocks-client#createClient");
123
+ }
package/lib/cjs/room.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { Others, Presence, ClientOptions, Room, MyPresenceCallback, OthersEventCallback, AuthEndpoint, EventCallback, User, Connection, ErrorCallback, AuthenticationToken, ConnectionCallback, StorageCallback, StorageUpdate, BroadcastOptions } from "./types";
1
+ import { Others, Presence, Room, MyPresenceCallback, OthersEventCallback, EventCallback, User, Connection, ErrorCallback, AuthenticationToken, ConnectionCallback, StorageCallback, StorageUpdate, BroadcastOptions, AuthorizeResponse, Authentication } from "./types";
2
2
  import { ClientMessage, Op } from "./live";
3
3
  import { LiveMap } from "./LiveMap";
4
4
  import { LiveObject } from "./LiveObject";
@@ -66,7 +66,7 @@ export declare type State = {
66
66
  offlineOperations: Map<string, Op>;
67
67
  };
68
68
  export declare type Effects = {
69
- authenticate(): void;
69
+ authenticate(auth: (room: string) => Promise<AuthorizeResponse>, createWebSocket: (token: string) => WebSocket): void;
70
70
  send(messages: ClientMessage[]): void;
71
71
  delayFlush(delay: number): number;
72
72
  startHeartbeatInterval(): number;
@@ -75,13 +75,13 @@ export declare type Effects = {
75
75
  };
76
76
  declare type Context = {
77
77
  room: string;
78
- authEndpoint: AuthEndpoint;
79
- liveblocksServer: string;
80
78
  throttleDelay: number;
81
- publicApiKey?: string;
79
+ fetchPolyfill?: typeof fetch;
80
+ WebSocketPolyfill?: typeof WebSocket;
81
+ authentication: Authentication;
82
+ liveblocksServer: string;
82
83
  };
83
84
  export declare function makeStateMachine(state: State, context: Context, mockedEffects?: Effects): {
84
- onOpen: () => void;
85
85
  onClose: (event: {
86
86
  code: number;
87
87
  wasClean: boolean;
@@ -152,8 +152,8 @@ export declare type InternalRoom = {
152
152
  onNavigatorOnline: () => void;
153
153
  onVisibilityChange: (visibilityState: VisibilityState) => void;
154
154
  };
155
- export declare function createRoom(name: string, options: ClientOptions & {
155
+ export declare function createRoom(options: {
156
156
  defaultPresence?: Presence;
157
157
  defaultStorageRoot?: Record<string, any>;
158
- }): InternalRoom;
158
+ }, context: Context): InternalRoom;
159
159
  export {};
package/lib/cjs/room.js CHANGED
@@ -1,23 +1,4 @@
1
1
  "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
5
- }) : (function(o, m, k, k2) {
6
- if (k2 === undefined) k2 = k;
7
- o[k2] = m[k];
8
- }));
9
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
10
- Object.defineProperty(o, "default", { enumerable: true, value: v });
11
- }) : function(o, v) {
12
- o["default"] = v;
13
- });
14
- var __importStar = (this && this.__importStar) || function (mod) {
15
- if (mod && mod.__esModule) return mod;
16
- var result = {};
17
- if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
18
- __setModuleDefault(result, mod);
19
- return result;
20
- };
21
2
  var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
22
3
  function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
23
4
  return new (P || (P = Promise))(function (resolve, reject) {
@@ -30,7 +11,6 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
30
11
  Object.defineProperty(exports, "__esModule", { value: true });
31
12
  exports.createRoom = exports.defaultState = exports.makeStateMachine = void 0;
32
13
  const utils_1 = require("./utils");
33
- const authentication_1 = __importStar(require("./authentication"));
34
14
  const live_1 = require("./live");
35
15
  const LiveMap_1 = require("./LiveMap");
36
16
  const LiveObject_1 = require("./LiveObject");
@@ -75,16 +55,12 @@ function log(...params) {
75
55
  }
76
56
  function makeStateMachine(state, context, mockedEffects) {
77
57
  const effects = mockedEffects || {
78
- authenticate() {
58
+ authenticate(auth, createWebSocket) {
79
59
  return __awaiter(this, void 0, void 0, function* () {
80
60
  try {
81
- const token = yield (0, authentication_1.default)(context.authEndpoint, context.room, context.publicApiKey);
82
- const parsedToken = (0, authentication_1.parseToken)(token);
83
- const socket = new WebSocket(`${context.liveblocksServer}/?token=${token}`);
84
- socket.addEventListener("message", onMessage);
85
- socket.addEventListener("open", onOpen);
86
- socket.addEventListener("close", onClose);
87
- socket.addEventListener("error", onError);
61
+ const { token } = yield auth(context.room);
62
+ const parsedToken = parseToken(token);
63
+ const socket = createWebSocket(token);
88
64
  authenticationSuccess(parsedToken, socket);
89
65
  }
90
66
  catch (er) {
@@ -187,6 +163,7 @@ function makeStateMachine(state, context, mockedEffects) {
187
163
  function load(items) {
188
164
  const [root, parentToChildren] = buildRootAndParentToChildren(items);
189
165
  return LiveObject_1.LiveObject._deserialize(root, parentToChildren, {
166
+ getItem,
190
167
  addItem,
191
168
  deleteItem,
192
169
  generateId,
@@ -362,31 +339,31 @@ function makeStateMachine(state, context, mockedEffects) {
362
339
  }
363
340
  case live_1.OpType.CreateObject: {
364
341
  const parent = state.items.get(op.parentId);
365
- if (parent == null || getItem(op.id) != null) {
342
+ if (parent == null) {
366
343
  return { modified: false };
367
344
  }
368
- return parent._attachChild(op.id, op.parentKey, new LiveObject_1.LiveObject(op.data), isLocal);
345
+ return parent._attachChild(op.id, op.parentKey, new LiveObject_1.LiveObject(op.data), op.opId, isLocal);
369
346
  }
370
347
  case live_1.OpType.CreateList: {
371
348
  const parent = state.items.get(op.parentId);
372
- if (parent == null || getItem(op.id) != null) {
349
+ if (parent == null) {
373
350
  return { modified: false };
374
351
  }
375
- return parent._attachChild(op.id, op.parentKey, new LiveList_1.LiveList(), isLocal);
352
+ return parent._attachChild(op.id, op.parentKey, new LiveList_1.LiveList(), op.opId, isLocal);
376
353
  }
377
354
  case live_1.OpType.CreateRegister: {
378
355
  const parent = state.items.get(op.parentId);
379
- if (parent == null || getItem(op.id) != null) {
356
+ if (parent == null) {
380
357
  return { modified: false };
381
358
  }
382
- return parent._attachChild(op.id, op.parentKey, new LiveRegister_1.LiveRegister(op.data), isLocal);
359
+ return parent._attachChild(op.id, op.parentKey, new LiveRegister_1.LiveRegister(op.data), op.opId, isLocal);
383
360
  }
384
361
  case live_1.OpType.CreateMap: {
385
362
  const parent = state.items.get(op.parentId);
386
- if (parent == null || getItem(op.id) != null) {
363
+ if (parent == null) {
387
364
  return { modified: false };
388
365
  }
389
- return parent._attachChild(op.id, op.parentKey, new LiveMap_1.LiveMap(), isLocal);
366
+ return parent._attachChild(op.id, op.parentKey, new LiveMap_1.LiveMap(), op.opId, isLocal);
390
367
  }
391
368
  }
392
369
  return { modified: false };
@@ -433,15 +410,14 @@ See v0.13 release notes for more information.
433
410
  : null;
434
411
  }
435
412
  function connect() {
436
- if (typeof window === "undefined") {
437
- return;
438
- }
439
413
  if (state.connection.state !== "closed" &&
440
414
  state.connection.state !== "unavailable") {
441
415
  return null;
442
416
  }
417
+ const auth = prepareAuthEndpoint(context.authentication, context.fetchPolyfill);
418
+ const createWebSocket = prepareCreateWebSocket(context.liveblocksServer, context.WebSocketPolyfill);
443
419
  updateConnection({ state: "authenticating" });
444
- effects.authenticate();
420
+ effects.authenticate(auth, createWebSocket);
445
421
  }
446
422
  function updatePresence(overrides, options) {
447
423
  const oldValues = {};
@@ -468,6 +444,10 @@ See v0.13 release notes for more information.
468
444
  }
469
445
  }
470
446
  function authenticationSuccess(token, socket) {
447
+ socket.addEventListener("message", onMessage);
448
+ socket.addEventListener("open", onOpen);
449
+ socket.addEventListener("close", onClose);
450
+ socket.addEventListener("error", onError);
471
451
  updateConnection({
472
452
  state: "connecting",
473
453
  id: token.actor,
@@ -684,6 +664,11 @@ See v0.13 release notes for more information.
684
664
  if (state.connection.state === "connecting") {
685
665
  updateConnection(Object.assign(Object.assign({}, state.connection), { state: "open" }));
686
666
  state.numberOfRetry = 0;
667
+ // Re-broadcast the user presence during a reconnect.
668
+ if (state.lastConnectionId !== undefined) {
669
+ state.buffer.presence = state.me;
670
+ tryFlushing();
671
+ }
687
672
  state.lastConnectionId = state.connection.id;
688
673
  if (state.root) {
689
674
  state.buffer.messages.push({ type: live_1.ClientMessageType.FetchStorage });
@@ -701,7 +686,7 @@ See v0.13 release notes for more information.
701
686
  }
702
687
  clearTimeout(state.timeoutHandles.pongTimeout);
703
688
  state.timeoutHandles.pongTimeout = effects.schedulePongTimeout();
704
- if (state.socket.readyState === WebSocket.OPEN) {
689
+ if (state.socket.readyState === state.socket.OPEN) {
705
690
  state.socket.send("ping");
706
691
  }
707
692
  }
@@ -748,7 +733,7 @@ See v0.13 release notes for more information.
748
733
  state.offlineOperations.set(op.opId, op);
749
734
  });
750
735
  }
751
- if (state.socket == null || state.socket.readyState !== WebSocket.OPEN) {
736
+ if (state.socket == null || state.socket.readyState !== state.socket.OPEN) {
752
737
  state.buffer.storageOperations = [];
753
738
  return;
754
739
  }
@@ -952,7 +937,6 @@ See v0.13 release notes for more information.
952
937
  }
953
938
  return {
954
939
  // Internal
955
- onOpen,
956
940
  onClose,
957
941
  onMessage,
958
942
  authenticationSuccess,
@@ -1042,26 +1026,9 @@ function defaultState(me, defaultStorageRoot) {
1042
1026
  };
1043
1027
  }
1044
1028
  exports.defaultState = defaultState;
1045
- function createRoom(name, options) {
1046
- const throttleDelay = options.throttle || 100;
1047
- const liveblocksServer = options.liveblocksServer || "wss://liveblocks.net/v5";
1048
- let authEndpoint;
1049
- if (options.authEndpoint) {
1050
- authEndpoint = options.authEndpoint;
1051
- }
1052
- else {
1053
- const publicAuthorizeEndpoint = options.publicAuthorizeEndpoint ||
1054
- "https://liveblocks.io/api/public/authorize";
1055
- authEndpoint = publicAuthorizeEndpoint;
1056
- }
1029
+ function createRoom(options, context) {
1057
1030
  const state = defaultState(options.defaultPresence, options.defaultStorageRoot);
1058
- const machine = makeStateMachine(state, {
1059
- throttleDelay,
1060
- liveblocksServer,
1061
- authEndpoint,
1062
- room: name,
1063
- publicApiKey: options.publicApiKey,
1064
- });
1031
+ const machine = makeStateMachine(state, context);
1065
1032
  const room = {
1066
1033
  /////////////
1067
1034
  // Core //
@@ -1106,3 +1073,76 @@ class LiveblocksError extends Error {
1106
1073
  this.code = code;
1107
1074
  }
1108
1075
  }
1076
+ function parseToken(token) {
1077
+ const tokenParts = token.split(".");
1078
+ if (tokenParts.length !== 3) {
1079
+ throw new Error(`Authentication error. Liveblocks could not parse the response of your authentication endpoint`);
1080
+ }
1081
+ const data = JSON.parse(atob(tokenParts[1]));
1082
+ if (typeof data.actor !== "number") {
1083
+ throw new Error(`Authentication error. Liveblocks could not parse the response of your authentication endpoint`);
1084
+ }
1085
+ return data;
1086
+ }
1087
+ function prepareCreateWebSocket(liveblocksServer, WebSocketPolyfill) {
1088
+ if (typeof window === "undefined" && WebSocketPolyfill == null) {
1089
+ throw new Error("To use Liveblocks client in a non-dom environment, you need to provide a WebSocket polyfill.");
1090
+ }
1091
+ const ws = WebSocketPolyfill || WebSocket;
1092
+ return (token) => {
1093
+ return new ws(`${liveblocksServer}/?token=${token}`);
1094
+ };
1095
+ }
1096
+ function prepareAuthEndpoint(authentication, fetchPolyfill) {
1097
+ if (authentication.type === "public") {
1098
+ if (typeof window === "undefined" && fetchPolyfill == null) {
1099
+ throw new Error("To use Liveblocks client in a non-dom environment with a publicApiKey, you need to provide a fetch polyfill.");
1100
+ }
1101
+ return (room) => fetchAuthEndpoint(fetchPolyfill || fetch, authentication.url, {
1102
+ room,
1103
+ publicApiKey: authentication.publicApiKey,
1104
+ });
1105
+ }
1106
+ if (authentication.type === "private") {
1107
+ if (typeof window === "undefined" && fetchPolyfill == null) {
1108
+ throw new Error("To use Liveblocks client in a non-dom environment with a url as auth endpoint, you need to provide a fetch polyfill.");
1109
+ }
1110
+ return (room) => fetchAuthEndpoint(fetchPolyfill || fetch, authentication.url, {
1111
+ room,
1112
+ });
1113
+ }
1114
+ if (authentication.type === "custom") {
1115
+ return authentication.callback;
1116
+ }
1117
+ throw new Error("Internal error. Unexpected authentication type");
1118
+ }
1119
+ function fetchAuthEndpoint(fetch, endpoint, body) {
1120
+ return __awaiter(this, void 0, void 0, function* () {
1121
+ const res = yield fetch(endpoint, {
1122
+ method: "POST",
1123
+ headers: {
1124
+ "Content-Type": "application/json",
1125
+ },
1126
+ body: JSON.stringify(body),
1127
+ });
1128
+ if (!res.ok) {
1129
+ throw new AuthenticationError(`Authentication error. Liveblocks could not parse the response of your authentication "${endpoint}"`);
1130
+ }
1131
+ let authResponse = null;
1132
+ try {
1133
+ authResponse = yield res.json();
1134
+ }
1135
+ catch (er) {
1136
+ throw new AuthenticationError(`Authentication error. Liveblocks could not parse the response of your authentication "${endpoint}"`);
1137
+ }
1138
+ if (typeof authResponse.token !== "string") {
1139
+ throw new AuthenticationError(`Authentication error. Liveblocks could not parse the response of your authentication "${endpoint}"`);
1140
+ }
1141
+ return authResponse;
1142
+ });
1143
+ }
1144
+ class AuthenticationError extends Error {
1145
+ constructor(message) {
1146
+ super(message);
1147
+ }
1148
+ }
@@ -121,6 +121,8 @@ export declare type AuthEndpoint = string | AuthEndpointCallback;
121
121
  */
122
122
  export declare type ClientOptions = {
123
123
  throttle?: number;
124
+ fetchPolyfill?: any;
125
+ WebSocketPolyfill?: any;
124
126
  } & ({
125
127
  publicApiKey: string;
126
128
  authEndpoint?: never;
@@ -131,6 +133,17 @@ export declare type ClientOptions = {
131
133
  export declare type AuthorizeResponse = {
132
134
  token: string;
133
135
  };
136
+ export declare type Authentication = {
137
+ type: "public";
138
+ publicApiKey: string;
139
+ url: string;
140
+ } | {
141
+ type: "private";
142
+ url: string;
143
+ } | {
144
+ type: "custom";
145
+ callback: (room: string) => Promise<AuthorizeResponse>;
146
+ };
134
147
  declare type ConnectionState = "closed" | "authenticating" | "unavailable" | "failed" | "open" | "connecting";
135
148
  export declare type Connection = {
136
149
  state: "closed" | "authenticating" | "unavailable" | "failed";
@@ -8,6 +8,7 @@ export declare type ApplyResult = {
8
8
  export interface Doc {
9
9
  generateId: () => string;
10
10
  generateOpId: () => string;
11
+ getItem: (id: string) => AbstractCrdt | undefined;
11
12
  addItem: (id: string, item: AbstractCrdt) => void;
12
13
  deleteItem: (id: string) => void;
13
14
  dispatch: (ops: Op[], reverseOps: Op[], modified: AbstractCrdt[]) => void;
@@ -45,7 +46,7 @@ export declare abstract class AbstractCrdt {
45
46
  /**
46
47
  * INTERNAL
47
48
  */
48
- abstract _attachChild(id: string, key: string, crdt: AbstractCrdt, isLocal: boolean): ApplyResult;
49
+ abstract _attachChild(id: string, key: string, crdt: AbstractCrdt, opId: string, isLocal: boolean): ApplyResult;
49
50
  /**
50
51
  * INTERNAL
51
52
  */
@@ -25,7 +25,7 @@ export declare class LiveList<T> extends AbstractCrdt {
25
25
  /**
26
26
  * INTERNAL
27
27
  */
28
- _attachChild(id: string, key: string, child: AbstractCrdt, isLocal: boolean): ApplyResult;
28
+ _attachChild(id: string, key: string, child: AbstractCrdt, opId: string, isLocal: boolean): ApplyResult;
29
29
  /**
30
30
  * INTERNAL
31
31
  */
@@ -93,11 +93,14 @@ export class LiveList extends AbstractCrdt {
93
93
  /**
94
94
  * INTERNAL
95
95
  */
96
- _attachChild(id, key, child, isLocal) {
96
+ _attachChild(id, key, child, opId, isLocal) {
97
97
  var _a;
98
98
  if (this._doc == null) {
99
99
  throw new Error("Can't attach child if doc is not present");
100
100
  }
101
+ if (this._doc.getItem(id) !== undefined) {
102
+ return { modified: false };
103
+ }
101
104
  child._attach(id, this._doc);
102
105
  child._setParentLink(this, key);
103
106
  const index = __classPrivateFieldGet(this, _LiveList_items, "f").findIndex((entry) => entry[1] === key);
@@ -23,7 +23,7 @@ export declare class LiveMap<TKey extends string, TValue> extends AbstractCrdt {
23
23
  /**
24
24
  * INTERNAL
25
25
  */
26
- _attachChild(id: string, key: TKey, child: AbstractCrdt, isLocal: boolean): ApplyResult;
26
+ _attachChild(id: string, key: TKey, child: AbstractCrdt, opId: string, isLocal: boolean): ApplyResult;
27
27
  /**
28
28
  * INTERNAL
29
29
  */
@@ -97,10 +97,13 @@ export class LiveMap extends AbstractCrdt {
97
97
  /**
98
98
  * INTERNAL
99
99
  */
100
- _attachChild(id, key, child, isLocal) {
100
+ _attachChild(id, key, child, opId, isLocal) {
101
101
  if (this._doc == null) {
102
102
  throw new Error("Can't attach child if doc is not present");
103
103
  }
104
+ if (this._doc.getItem(id) !== undefined) {
105
+ return { modified: false };
106
+ }
104
107
  const previousValue = __classPrivateFieldGet(this, _LiveMap_map, "f").get(key);
105
108
  let reverse;
106
109
  if (previousValue) {