@liveblocks/client 0.15.0-alpha.2 → 0.15.0-alpha.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.
package/lib/cjs/utils.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.mergeStorageUpdates = exports.getTreesDiffOperations = exports.selfOrRegister = exports.selfOrRegisterValue = exports.isCrdt = exports.deserialize = exports.isSameNodeOrChildOf = exports.remove = void 0;
3
+ exports.findNonSerializableValue = exports.mergeStorageUpdates = exports.getTreesDiffOperations = exports.selfOrRegister = exports.selfOrRegisterValue = exports.isCrdt = exports.deserialize = exports.isSameNodeOrChildOf = exports.remove = void 0;
4
4
  const live_1 = require("./live");
5
5
  const LiveList_1 = require("./LiveList");
6
6
  const LiveMap_1 = require("./LiveMap");
@@ -173,3 +173,53 @@ function mergeStorageUpdates(first, second) {
173
173
  return second;
174
174
  }
175
175
  exports.mergeStorageUpdates = mergeStorageUpdates;
176
+ function isPlain(value) {
177
+ const type = typeof value;
178
+ return (type === "undefined" ||
179
+ value === null ||
180
+ type === "string" ||
181
+ type === "boolean" ||
182
+ type === "number" ||
183
+ Array.isArray(value) ||
184
+ isPlainObject(value));
185
+ }
186
+ function isPlainObject(value) {
187
+ if (typeof value !== "object" || value === null)
188
+ return false;
189
+ let proto = Object.getPrototypeOf(value);
190
+ if (proto === null)
191
+ return true;
192
+ let baseProto = proto;
193
+ while (Object.getPrototypeOf(baseProto) !== null) {
194
+ baseProto = Object.getPrototypeOf(baseProto);
195
+ }
196
+ return proto === baseProto;
197
+ }
198
+ function findNonSerializableValue(value, path = "") {
199
+ if (!isPlain) {
200
+ return {
201
+ path: path || "root",
202
+ value: value,
203
+ };
204
+ }
205
+ if (typeof value !== "object" || value === null) {
206
+ return false;
207
+ }
208
+ for (const [key, nestedValue] of Object.entries(value)) {
209
+ const nestedPath = path ? path + "." + key : key;
210
+ if (!isPlain(nestedValue)) {
211
+ return {
212
+ path: nestedPath,
213
+ value: nestedValue,
214
+ };
215
+ }
216
+ if (typeof nestedValue === "object") {
217
+ const nonSerializableNestedValue = findNonSerializableValue(nestedValue, nestedPath);
218
+ if (nonSerializableNestedValue) {
219
+ return nonSerializableNestedValue;
220
+ }
221
+ }
222
+ }
223
+ return false;
224
+ }
225
+ exports.findNonSerializableValue = findNonSerializableValue;
@@ -9,6 +9,7 @@ export declare type ApplyResult = {
9
9
  export interface Doc {
10
10
  generateId: () => string;
11
11
  generateOpId: () => string;
12
+ getItem: (id: string) => AbstractCrdt | undefined;
12
13
  addItem: (id: string, item: AbstractCrdt) => void;
13
14
  deleteItem: (id: string) => void;
14
15
  dispatch: (ops: Op[], reverseOps: Op[], storageUpdates: Map<string, StorageUpdate>) => void;
@@ -46,7 +47,7 @@ export declare abstract class AbstractCrdt {
46
47
  /**
47
48
  * INTERNAL
48
49
  */
49
- abstract _attachChild(id: string, key: string, crdt: AbstractCrdt, isLocal: boolean): ApplyResult;
50
+ abstract _attachChild(id: string, key: string, crdt: AbstractCrdt, opId: string, isLocal: boolean): ApplyResult;
50
51
  /**
51
52
  * INTERNAL
52
53
  */
@@ -26,7 +26,7 @@ export declare class LiveList<T> extends AbstractCrdt {
26
26
  /**
27
27
  * INTERNAL
28
28
  */
29
- _attachChild(id: string, key: string, child: AbstractCrdt, isLocal: boolean): ApplyResult;
29
+ _attachChild(id: string, key: string, child: AbstractCrdt, opId: string, isLocal: boolean): ApplyResult;
30
30
  /**
31
31
  * INTERNAL
32
32
  */
@@ -97,11 +97,14 @@ export class LiveList extends AbstractCrdt {
97
97
  /**
98
98
  * INTERNAL
99
99
  */
100
- _attachChild(id, key, child, isLocal) {
100
+ _attachChild(id, key, child, opId, isLocal) {
101
101
  var _a;
102
102
  if (this._doc == null) {
103
103
  throw new Error("Can't attach child if doc is not present");
104
104
  }
105
+ if (this._doc.getItem(id) !== undefined) {
106
+ return { modified: false };
107
+ }
105
108
  child._attach(id, this._doc);
106
109
  child._setParentLink(this, key);
107
110
  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) {
@@ -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
  */
@@ -103,10 +103,32 @@ export class LiveObject extends AbstractCrdt {
103
103
  /**
104
104
  * INTERNAL
105
105
  */
106
- _attachChild(id, key, child, isLocal) {
106
+ _attachChild(id, key, child, opId, isLocal) {
107
107
  if (this._doc == null) {
108
108
  throw new Error("Can't attach child if doc is not present");
109
109
  }
110
+ if (this._doc.getItem(id) !== undefined) {
111
+ if (__classPrivateFieldGet(this, _LiveObject_propToLastUpdate, "f").get(key) === opId) {
112
+ // Acknowlegment from local operation
113
+ __classPrivateFieldGet(this, _LiveObject_propToLastUpdate, "f").delete(key);
114
+ }
115
+ return { modified: false };
116
+ }
117
+ if (isLocal) {
118
+ __classPrivateFieldGet(this, _LiveObject_propToLastUpdate, "f").set(key, opId);
119
+ }
120
+ else if (__classPrivateFieldGet(this, _LiveObject_propToLastUpdate, "f").get(key) === undefined) {
121
+ // Remote operation with no local change => apply operation
122
+ }
123
+ else if (__classPrivateFieldGet(this, _LiveObject_propToLastUpdate, "f").get(key) === opId) {
124
+ // Acknowlegment from local operation
125
+ __classPrivateFieldGet(this, _LiveObject_propToLastUpdate, "f").delete(key);
126
+ return { modified: false };
127
+ }
128
+ else {
129
+ // Conflict, ignore remote operation
130
+ return { modified: false };
131
+ }
110
132
  const previousValue = __classPrivateFieldGet(this, _LiveObject_map, "f").get(key);
111
133
  let reverse;
112
134
  if (isCrdt(previousValue)) {
@@ -311,7 +333,6 @@ export class LiveObject extends AbstractCrdt {
311
333
  };
312
334
  const updateDelta = {};
313
335
  for (const key in overrides) {
314
- __classPrivateFieldGet(this, _LiveObject_propToLastUpdate, "f").set(key, opId);
315
336
  const oldValue = __classPrivateFieldGet(this, _LiveObject_map, "f").get(key);
316
337
  if (oldValue instanceof AbstractCrdt) {
317
338
  reverseOps.push(...oldValue._serialize(this._id, key));
@@ -327,10 +348,16 @@ export class LiveObject extends AbstractCrdt {
327
348
  if (newValue instanceof AbstractCrdt) {
328
349
  newValue._setParentLink(this, key);
329
350
  newValue._attach(this._doc.generateId(), this._doc);
330
- ops.push(...newValue._serialize(this._id, key, this._doc));
351
+ const newAttachChildOps = newValue._serialize(this._id, key, this._doc);
352
+ const createCrdtOp = newAttachChildOps.find((op) => op.parentId === this._id);
353
+ if (createCrdtOp) {
354
+ __classPrivateFieldGet(this, _LiveObject_propToLastUpdate, "f").set(key, createCrdtOp.opId);
355
+ }
356
+ ops.push(...newAttachChildOps);
331
357
  }
332
358
  else {
333
359
  updatedProps[key] = newValue;
360
+ __classPrivateFieldGet(this, _LiveObject_propToLastUpdate, "f").set(key, opId);
334
361
  }
335
362
  __classPrivateFieldGet(this, _LiveObject_map, "f").set(key, newValue);
336
363
  updateDelta[key] = { type: "update" };
@@ -422,6 +449,11 @@ _LiveObject_map = new WeakMap(), _LiveObject_propToLastUpdate = new WeakMap(), _
422
449
  if (__classPrivateFieldGet(this, _LiveObject_map, "f").has(key) === false) {
423
450
  return { modified: false };
424
451
  }
452
+ // If a local operation exists on the same key
453
+ // prevent flickering by not applying delete op.
454
+ if (__classPrivateFieldGet(this, _LiveObject_propToLastUpdate, "f").get(key) !== undefined) {
455
+ return { modified: false };
456
+ }
425
457
  const oldValue = __classPrivateFieldGet(this, _LiveObject_map, "f").get(key);
426
458
  let reverse = [];
427
459
  if (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): ApplyResult;
24
24
  _apply(op: Op, isLocal: boolean): ApplyResult;
25
25
  /**
@@ -65,7 +65,7 @@ export class LiveRegister extends AbstractCrdt {
65
65
  data: this.data,
66
66
  };
67
67
  }
68
- _attachChild(id, key, crdt, isLocal) {
68
+ _attachChild(id, key, crdt, opId, isLocal) {
69
69
  throw new Error("Method not implemented.");
70
70
  }
71
71
  _detachChild(crdt) {
package/lib/esm/client.js CHANGED
@@ -26,11 +26,7 @@ import { createRoom } from "./room";
26
26
  */
27
27
  export function createClient(options) {
28
28
  const clientOptions = options;
29
- if (typeof clientOptions.throttle === "number") {
30
- if (clientOptions.throttle < 80 || clientOptions.throttle > 1000) {
31
- throw new Error("Liveblocks client throttle should be between 80 and 1000 ms");
32
- }
33
- }
29
+ const throttleDelay = getThrottleDelayFromOptions(options);
34
30
  const rooms = new Map();
35
31
  function getRoom(roomId) {
36
32
  const internalRoom = rooms.get(roomId);
@@ -41,9 +37,21 @@ export function createClient(options) {
41
37
  if (internalRoom) {
42
38
  return internalRoom.room;
43
39
  }
44
- internalRoom = createRoom(roomId, Object.assign(Object.assign({}, clientOptions), options));
40
+ internalRoom = createRoom({
41
+ defaultPresence: options.defaultPresence,
42
+ defaultStorageRoot: options.defaultStorageRoot,
43
+ }, {
44
+ room: roomId,
45
+ throttleDelay,
46
+ WebSocketPolyfill: clientOptions.WebSocketPolyfill,
47
+ fetchPolyfill: clientOptions.fetchPolyfill,
48
+ liveblocksServer: clientOptions.liveblocksServer || "wss://liveblocks.net/v5",
49
+ authentication: prepareAuthentication(clientOptions),
50
+ });
45
51
  rooms.set(roomId, internalRoom);
46
- internalRoom.connect();
52
+ if (!options.DO_NOT_USE_withoutConnecting) {
53
+ internalRoom.connect();
54
+ }
47
55
  return internalRoom.room;
48
56
  }
49
57
  function leave(roomId) {
@@ -74,3 +82,38 @@ export function createClient(options) {
74
82
  leave,
75
83
  };
76
84
  }
85
+ function getThrottleDelayFromOptions(options) {
86
+ if (options.throttle === undefined) {
87
+ return 100;
88
+ }
89
+ if (typeof options.throttle !== "number" ||
90
+ options.throttle < 80 ||
91
+ options.throttle > 1000) {
92
+ throw new Error("throttle should be a number between 80 and 1000.");
93
+ }
94
+ return options.throttle;
95
+ }
96
+ function prepareAuthentication(clientOptions) {
97
+ // TODO: throw descriptive errors for invalid options
98
+ if (typeof clientOptions.publicApiKey === "string") {
99
+ return {
100
+ type: "public",
101
+ publicApiKey: clientOptions.publicApiKey,
102
+ url: clientOptions.publicAuthorizeEndpoint ||
103
+ "https://liveblocks.io/api/public/authorize",
104
+ };
105
+ }
106
+ else if (typeof clientOptions.authEndpoint === "string") {
107
+ return {
108
+ type: "private",
109
+ url: clientOptions.authEndpoint,
110
+ };
111
+ }
112
+ else if (typeof clientOptions.authEndpoint === "function") {
113
+ return {
114
+ type: "custom",
115
+ callback: clientOptions.authEndpoint,
116
+ };
117
+ }
118
+ throw new Error("Invalid Liveblocks client options. For more information: https://liveblocks.io/docs/api-reference/liveblocks-client#createClient");
119
+ }
@@ -2,6 +2,7 @@ import { LiveList } from "./LiveList";
2
2
  import { LiveMap } from "./LiveMap";
3
3
  import { LiveObject } from "./LiveObject";
4
4
  import { LiveRegister } from "./LiveRegister";
5
+ import { findNonSerializableValue } from "./utils";
5
6
  export function liveObjectToJson(liveObject) {
6
7
  const result = {};
7
8
  const obj = liveObject.toObject();
@@ -134,6 +135,13 @@ export function patchLiveList(liveList, prev, next) {
134
135
  }
135
136
  }
136
137
  export function patchLiveObjectKey(liveObject, key, prev, next) {
138
+ if (process.env.NODE_ENV !== "production") {
139
+ const nonSerializableValue = findNonSerializableValue(next);
140
+ if (nonSerializableValue) {
141
+ console.error(`New state path: '${nonSerializableValue.path}' value: '${nonSerializableValue.value}' is not serializable.\nOnly serializable value can be synced with Liveblocks.`);
142
+ return;
143
+ }
144
+ }
137
145
  const value = liveObject.get(key);
138
146
  if (next === undefined) {
139
147
  liveObject.delete(key);
package/lib/esm/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/esm/room.js CHANGED
@@ -8,7 +8,6 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
8
8
  });
9
9
  };
10
10
  import { getTreesDiffOperations, isSameNodeOrChildOf, remove, mergeStorageUpdates, } from "./utils";
11
- import auth, { parseToken } from "./authentication";
12
11
  import { ClientMessageType, ServerMessageType, OpType, } from "./live";
13
12
  import { LiveMap } from "./LiveMap";
14
13
  import { LiveObject } from "./LiveObject";
@@ -53,16 +52,12 @@ function log(...params) {
53
52
  }
54
53
  export function makeStateMachine(state, context, mockedEffects) {
55
54
  const effects = mockedEffects || {
56
- authenticate() {
55
+ authenticate(auth, createWebSocket) {
57
56
  return __awaiter(this, void 0, void 0, function* () {
58
57
  try {
59
- const token = yield auth(context.authEndpoint, context.room, context.publicApiKey);
58
+ const { token } = yield auth(context.room);
60
59
  const parsedToken = parseToken(token);
61
- const socket = new WebSocket(`${context.liveblocksServer}/?token=${token}`);
62
- socket.addEventListener("message", onMessage);
63
- socket.addEventListener("open", onOpen);
64
- socket.addEventListener("close", onClose);
65
- socket.addEventListener("error", onError);
60
+ const socket = createWebSocket(token);
66
61
  authenticationSuccess(parsedToken, socket);
67
62
  }
68
63
  catch (er) {
@@ -165,6 +160,7 @@ export function makeStateMachine(state, context, mockedEffects) {
165
160
  function load(items) {
166
161
  const [root, parentToChildren] = buildRootAndParentToChildren(items);
167
162
  return LiveObject._deserialize(root, parentToChildren, {
163
+ getItem,
168
164
  addItem,
169
165
  deleteItem,
170
166
  generateId,
@@ -319,31 +315,31 @@ export function makeStateMachine(state, context, mockedEffects) {
319
315
  }
320
316
  case OpType.CreateObject: {
321
317
  const parent = state.items.get(op.parentId);
322
- if (parent == null || getItem(op.id) != null) {
318
+ if (parent == null) {
323
319
  return { modified: false };
324
320
  }
325
- return parent._attachChild(op.id, op.parentKey, new LiveObject(op.data), isLocal);
321
+ return parent._attachChild(op.id, op.parentKey, new LiveObject(op.data), op.opId, isLocal);
326
322
  }
327
323
  case OpType.CreateList: {
328
324
  const parent = state.items.get(op.parentId);
329
- if (parent == null || getItem(op.id) != null) {
325
+ if (parent == null) {
330
326
  return { modified: false };
331
327
  }
332
- return parent._attachChild(op.id, op.parentKey, new LiveList(), isLocal);
328
+ return parent._attachChild(op.id, op.parentKey, new LiveList(), op.opId, isLocal);
333
329
  }
334
330
  case OpType.CreateRegister: {
335
331
  const parent = state.items.get(op.parentId);
336
- if (parent == null || getItem(op.id) != null) {
332
+ if (parent == null) {
337
333
  return { modified: false };
338
334
  }
339
- return parent._attachChild(op.id, op.parentKey, new LiveRegister(op.data), isLocal);
335
+ return parent._attachChild(op.id, op.parentKey, new LiveRegister(op.data), op.opId, isLocal);
340
336
  }
341
337
  case OpType.CreateMap: {
342
338
  const parent = state.items.get(op.parentId);
343
- if (parent == null || getItem(op.id) != null) {
339
+ if (parent == null) {
344
340
  return { modified: false };
345
341
  }
346
- return parent._attachChild(op.id, op.parentKey, new LiveMap(), isLocal);
342
+ return parent._attachChild(op.id, op.parentKey, new LiveMap(), op.opId, isLocal);
347
343
  }
348
344
  }
349
345
  return { modified: false };
@@ -390,15 +386,14 @@ See v0.13 release notes for more information.
390
386
  : null;
391
387
  }
392
388
  function connect() {
393
- if (typeof window === "undefined") {
394
- return;
395
- }
396
389
  if (state.connection.state !== "closed" &&
397
390
  state.connection.state !== "unavailable") {
398
391
  return null;
399
392
  }
393
+ const auth = prepareAuthEndpoint(context.authentication, context.fetchPolyfill);
394
+ const createWebSocket = prepareCreateWebSocket(context.liveblocksServer, context.WebSocketPolyfill);
400
395
  updateConnection({ state: "authenticating" });
401
- effects.authenticate();
396
+ effects.authenticate(auth, createWebSocket);
402
397
  }
403
398
  function updatePresence(overrides, options) {
404
399
  const oldValues = {};
@@ -425,6 +420,10 @@ See v0.13 release notes for more information.
425
420
  }
426
421
  }
427
422
  function authenticationSuccess(token, socket) {
423
+ socket.addEventListener("message", onMessage);
424
+ socket.addEventListener("open", onOpen);
425
+ socket.addEventListener("close", onClose);
426
+ socket.addEventListener("error", onError);
428
427
  updateConnection({
429
428
  state: "connecting",
430
429
  id: token.actor,
@@ -658,7 +657,7 @@ See v0.13 release notes for more information.
658
657
  }
659
658
  clearTimeout(state.timeoutHandles.pongTimeout);
660
659
  state.timeoutHandles.pongTimeout = effects.schedulePongTimeout();
661
- if (state.socket.readyState === WebSocket.OPEN) {
660
+ if (state.socket.readyState === state.socket.OPEN) {
662
661
  state.socket.send("ping");
663
662
  }
664
663
  }
@@ -705,7 +704,7 @@ See v0.13 release notes for more information.
705
704
  state.offlineOperations.set(op.opId, op);
706
705
  });
707
706
  }
708
- if (state.socket == null || state.socket.readyState !== WebSocket.OPEN) {
707
+ if (state.socket == null || state.socket.readyState !== state.socket.OPEN) {
709
708
  state.buffer.storageOperations = [];
710
709
  return;
711
710
  }
@@ -912,7 +911,6 @@ See v0.13 release notes for more information.
912
911
  }
913
912
  return {
914
913
  // Internal
915
- onOpen,
916
914
  onClose,
917
915
  onMessage,
918
916
  authenticationSuccess,
@@ -1004,26 +1002,9 @@ export function defaultState(me, defaultStorageRoot) {
1004
1002
  offlineOperations: new Map(),
1005
1003
  };
1006
1004
  }
1007
- export function createRoom(name, options) {
1008
- const throttleDelay = options.throttle || 100;
1009
- const liveblocksServer = options.liveblocksServer || "wss://liveblocks.net/v5";
1010
- let authEndpoint;
1011
- if (options.authEndpoint) {
1012
- authEndpoint = options.authEndpoint;
1013
- }
1014
- else {
1015
- const publicAuthorizeEndpoint = options.publicAuthorizeEndpoint ||
1016
- "https://liveblocks.io/api/public/authorize";
1017
- authEndpoint = publicAuthorizeEndpoint;
1018
- }
1005
+ export function createRoom(options, context) {
1019
1006
  const state = defaultState(options.defaultPresence, options.defaultStorageRoot);
1020
- const machine = makeStateMachine(state, {
1021
- throttleDelay,
1022
- liveblocksServer,
1023
- authEndpoint,
1024
- room: name,
1025
- publicApiKey: options.publicApiKey,
1026
- });
1007
+ const machine = makeStateMachine(state, context);
1027
1008
  const room = {
1028
1009
  /////////////
1029
1010
  // Core //
@@ -1067,3 +1048,76 @@ class LiveblocksError extends Error {
1067
1048
  this.code = code;
1068
1049
  }
1069
1050
  }
1051
+ function parseToken(token) {
1052
+ const tokenParts = token.split(".");
1053
+ if (tokenParts.length !== 3) {
1054
+ throw new Error(`Authentication error. Liveblocks could not parse the response of your authentication endpoint`);
1055
+ }
1056
+ const data = JSON.parse(atob(tokenParts[1]));
1057
+ if (typeof data.actor !== "number") {
1058
+ throw new Error(`Authentication error. Liveblocks could not parse the response of your authentication endpoint`);
1059
+ }
1060
+ return data;
1061
+ }
1062
+ function prepareCreateWebSocket(liveblocksServer, WebSocketPolyfill) {
1063
+ if (typeof window === "undefined" && WebSocketPolyfill == null) {
1064
+ throw new Error("To use Liveblocks client in a non-dom environment, you need to provide a WebSocket polyfill.");
1065
+ }
1066
+ const ws = WebSocketPolyfill || WebSocket;
1067
+ return (token) => {
1068
+ return new ws(`${liveblocksServer}/?token=${token}`);
1069
+ };
1070
+ }
1071
+ function prepareAuthEndpoint(authentication, fetchPolyfill) {
1072
+ if (authentication.type === "public") {
1073
+ if (typeof window === "undefined" && fetchPolyfill == null) {
1074
+ throw new Error("To use Liveblocks client in a non-dom environment with a publicApiKey, you need to provide a fetch polyfill.");
1075
+ }
1076
+ return (room) => fetchAuthEndpoint(fetchPolyfill || fetch, authentication.url, {
1077
+ room,
1078
+ publicApiKey: authentication.publicApiKey,
1079
+ });
1080
+ }
1081
+ if (authentication.type === "private") {
1082
+ if (typeof window === "undefined" && fetchPolyfill == null) {
1083
+ 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.");
1084
+ }
1085
+ return (room) => fetchAuthEndpoint(fetchPolyfill || fetch, authentication.url, {
1086
+ room,
1087
+ });
1088
+ }
1089
+ if (authentication.type === "custom") {
1090
+ return authentication.callback;
1091
+ }
1092
+ throw new Error("Internal error. Unexpected authentication type");
1093
+ }
1094
+ function fetchAuthEndpoint(fetch, endpoint, body) {
1095
+ return __awaiter(this, void 0, void 0, function* () {
1096
+ const res = yield fetch(endpoint, {
1097
+ method: "POST",
1098
+ headers: {
1099
+ "Content-Type": "application/json",
1100
+ },
1101
+ body: JSON.stringify(body),
1102
+ });
1103
+ if (!res.ok) {
1104
+ throw new AuthenticationError(`Authentication error. Liveblocks could not parse the response of your authentication "${endpoint}"`);
1105
+ }
1106
+ let authResponse = null;
1107
+ try {
1108
+ authResponse = yield res.json();
1109
+ }
1110
+ catch (er) {
1111
+ throw new AuthenticationError(`Authentication error. Liveblocks could not parse the response of your authentication "${endpoint}"`);
1112
+ }
1113
+ if (typeof authResponse.token !== "string") {
1114
+ throw new AuthenticationError(`Authentication error. Liveblocks could not parse the response of your authentication "${endpoint}"`);
1115
+ }
1116
+ return authResponse;
1117
+ });
1118
+ }
1119
+ class AuthenticationError extends Error {
1120
+ constructor(message) {
1121
+ super(message);
1122
+ }
1123
+ }
@@ -146,6 +146,8 @@ export declare type AuthEndpoint = string | AuthEndpointCallback;
146
146
  */
147
147
  export declare type ClientOptions = {
148
148
  throttle?: number;
149
+ fetchPolyfill?: any;
150
+ WebSocketPolyfill?: any;
149
151
  } & ({
150
152
  publicApiKey: string;
151
153
  authEndpoint?: never;
@@ -156,6 +158,17 @@ export declare type ClientOptions = {
156
158
  export declare type AuthorizeResponse = {
157
159
  token: string;
158
160
  };
161
+ export declare type Authentication = {
162
+ type: "public";
163
+ publicApiKey: string;
164
+ url: string;
165
+ } | {
166
+ type: "private";
167
+ url: string;
168
+ } | {
169
+ type: "custom";
170
+ callback: (room: string) => Promise<AuthorizeResponse>;
171
+ };
159
172
  declare type ConnectionState = "closed" | "authenticating" | "unavailable" | "failed" | "open" | "connecting";
160
173
  export declare type Connection = {
161
174
  state: "closed" | "authenticating" | "unavailable" | "failed";
@@ -9,3 +9,7 @@ export declare function selfOrRegisterValue(obj: AbstractCrdt): any;
9
9
  export declare function selfOrRegister(obj: any): AbstractCrdt;
10
10
  export declare function getTreesDiffOperations(currentItems: Map<string, SerializedCrdt>, newItems: Map<string, SerializedCrdt>): Op[];
11
11
  export declare function mergeStorageUpdates(first: StorageUpdate | undefined, second: StorageUpdate): StorageUpdate;
12
+ export declare function findNonSerializableValue(value: any, path?: string): {
13
+ path: string;
14
+ value: any;
15
+ } | false;