@liveblocks/client 0.14.0 → 0.14.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
+ }
@@ -0,0 +1,9 @@
1
+ import { LiveList } from "./LiveList";
2
+ import { LiveObject } from "./LiveObject";
3
+ import { StorageUpdate } from "./types";
4
+ export declare function liveObjectToJson(liveObject: LiveObject<any>): any;
5
+ export declare function liveNodeToJson(value: any): any;
6
+ export declare function patchLiveList<T>(liveList: LiveList<T>, prev: Array<T>, next: Array<T>): void;
7
+ export declare function patchLiveObjectKey<T>(liveObject: LiveObject<T>, key: keyof T, prev: any, next: any): void;
8
+ export declare function patchLiveObject<T extends Record<string, any>>(root: LiveObject<T>, prev: T, next: T): void;
9
+ export declare function patchImmutableObject<T>(state: T, updates: StorageUpdate[]): T;
@@ -0,0 +1,291 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.patchImmutableObject = exports.patchLiveObject = exports.patchLiveObjectKey = exports.patchLiveList = exports.liveNodeToJson = exports.liveObjectToJson = void 0;
4
+ const LiveList_1 = require("./LiveList");
5
+ const LiveMap_1 = require("./LiveMap");
6
+ const LiveObject_1 = require("./LiveObject");
7
+ const LiveRegister_1 = require("./LiveRegister");
8
+ function liveObjectToJson(liveObject) {
9
+ const result = {};
10
+ const obj = liveObject.toObject();
11
+ for (const key in obj) {
12
+ result[key] = liveNodeToJson(obj[key]);
13
+ }
14
+ return result;
15
+ }
16
+ exports.liveObjectToJson = liveObjectToJson;
17
+ function liveMapToJson(map) {
18
+ const result = {};
19
+ const obj = Object.fromEntries(map);
20
+ for (const key in obj) {
21
+ result[key] = liveNodeToJson(obj[key]);
22
+ }
23
+ return result;
24
+ }
25
+ function liveListToJson(value) {
26
+ return value.toArray().map(liveNodeToJson);
27
+ }
28
+ function liveNodeToJson(value) {
29
+ if (value instanceof LiveObject_1.LiveObject) {
30
+ return liveObjectToJson(value);
31
+ }
32
+ else if (value instanceof LiveList_1.LiveList) {
33
+ return liveListToJson(value);
34
+ }
35
+ else if (value instanceof LiveMap_1.LiveMap) {
36
+ return liveMapToJson(value);
37
+ }
38
+ else if (value instanceof LiveRegister_1.LiveRegister) {
39
+ return value.data;
40
+ }
41
+ return value;
42
+ }
43
+ exports.liveNodeToJson = liveNodeToJson;
44
+ function isPlainObject(obj) {
45
+ return Object.prototype.toString.call(obj) === "[object Object]";
46
+ }
47
+ function anyToCrdt(obj) {
48
+ if (obj == null) {
49
+ return obj;
50
+ }
51
+ if (Array.isArray(obj)) {
52
+ return new LiveList_1.LiveList(obj.map(anyToCrdt));
53
+ }
54
+ if (isPlainObject(obj)) {
55
+ const init = {};
56
+ for (const key in obj) {
57
+ init[key] = anyToCrdt(obj[key]);
58
+ }
59
+ return new LiveObject_1.LiveObject(init);
60
+ }
61
+ return obj;
62
+ }
63
+ function patchLiveList(liveList, prev, next) {
64
+ let i = 0;
65
+ let prevEnd = prev.length - 1;
66
+ let nextEnd = next.length - 1;
67
+ let prevNode = prev[0];
68
+ let nextNode = next[0];
69
+ /**
70
+ * For A,B,C => A,B,C,D
71
+ * i = 3, prevEnd = 2, nextEnd = 3
72
+ *
73
+ * For A,B,C => B,C
74
+ * i = 2, prevEnd = 2, nextEnd = 1
75
+ *
76
+ * For B,C => A,B,C
77
+ * i = 0, pre
78
+ */
79
+ outer: {
80
+ while (prevNode === nextNode) {
81
+ ++i;
82
+ if (i > prevEnd || i > nextEnd) {
83
+ break outer;
84
+ }
85
+ prevNode = prev[i];
86
+ nextNode = next[i];
87
+ }
88
+ prevNode = prev[prevEnd];
89
+ nextNode = next[nextEnd];
90
+ while (prevNode === nextNode) {
91
+ prevEnd--;
92
+ nextEnd--;
93
+ if (i > prevEnd || i > nextEnd) {
94
+ break outer;
95
+ }
96
+ prevNode = prev[prevEnd];
97
+ nextNode = next[nextEnd];
98
+ }
99
+ }
100
+ if (i > prevEnd) {
101
+ if (i <= nextEnd) {
102
+ while (i <= nextEnd) {
103
+ liveList.insert(anyToCrdt(next[i]), i);
104
+ i++;
105
+ }
106
+ }
107
+ }
108
+ else if (i > nextEnd) {
109
+ let localI = i;
110
+ while (localI <= prevEnd) {
111
+ liveList.delete(i);
112
+ localI++;
113
+ }
114
+ }
115
+ else {
116
+ while (i <= prevEnd && i <= nextEnd) {
117
+ prevNode = prev[i];
118
+ nextNode = next[i];
119
+ const liveListNode = liveList.get(i);
120
+ if (liveListNode instanceof LiveObject_1.LiveObject &&
121
+ isPlainObject(prevNode) &&
122
+ isPlainObject(nextNode)) {
123
+ patchLiveObject(liveListNode, prevNode, nextNode);
124
+ }
125
+ else {
126
+ liveList.delete(i);
127
+ liveList.insert(anyToCrdt(nextNode), i);
128
+ }
129
+ i++;
130
+ }
131
+ while (i <= nextEnd) {
132
+ liveList.insert(anyToCrdt(next[i]), i);
133
+ i++;
134
+ }
135
+ while (i <= prevEnd) {
136
+ liveList.delete(i);
137
+ i++;
138
+ }
139
+ }
140
+ }
141
+ exports.patchLiveList = patchLiveList;
142
+ function patchLiveObjectKey(liveObject, key, prev, next) {
143
+ const value = liveObject.get(key);
144
+ if (next === undefined) {
145
+ liveObject.delete(key);
146
+ }
147
+ else if (value === undefined) {
148
+ liveObject.set(key, anyToCrdt(next));
149
+ }
150
+ else if (prev === next) {
151
+ return;
152
+ }
153
+ else if (value instanceof LiveList_1.LiveList &&
154
+ Array.isArray(prev) &&
155
+ Array.isArray(next)) {
156
+ patchLiveList(value, prev, next);
157
+ }
158
+ else if (value instanceof LiveObject_1.LiveObject &&
159
+ isPlainObject(prev) &&
160
+ isPlainObject(next)) {
161
+ patchLiveObject(value, prev, next);
162
+ }
163
+ else {
164
+ liveObject.set(key, anyToCrdt(next));
165
+ }
166
+ }
167
+ exports.patchLiveObjectKey = patchLiveObjectKey;
168
+ function patchLiveObject(root, prev, next) {
169
+ const updates = {};
170
+ for (const key in next) {
171
+ patchLiveObjectKey(root, key, prev[key], next[key]);
172
+ }
173
+ for (const key in prev) {
174
+ if (next[key] === undefined) {
175
+ root.delete(key);
176
+ }
177
+ }
178
+ if (Object.keys(updates).length > 0) {
179
+ root.update(updates);
180
+ }
181
+ }
182
+ exports.patchLiveObject = patchLiveObject;
183
+ function getParentsPath(node) {
184
+ const path = [];
185
+ while (node._parentKey != null && node._parent != null) {
186
+ if (node._parent instanceof LiveList_1.LiveList) {
187
+ path.push(node._parent._indexOfPosition(node._parentKey));
188
+ }
189
+ else {
190
+ path.push(node._parentKey);
191
+ }
192
+ node = node._parent;
193
+ }
194
+ return path;
195
+ }
196
+ function patchImmutableObject(state, updates) {
197
+ return updates.reduce((state, update) => patchImmutableObjectWithUpdate(state, update), state);
198
+ }
199
+ exports.patchImmutableObject = patchImmutableObject;
200
+ function patchImmutableObjectWithUpdate(state, update) {
201
+ const path = getParentsPath(update.node);
202
+ return patchImmutableNode(state, path, update);
203
+ }
204
+ function patchImmutableNode(state, path, update) {
205
+ var _a, _b, _c, _d;
206
+ const pathItem = path.pop();
207
+ if (pathItem === undefined) {
208
+ switch (update.type) {
209
+ case "LiveObject": {
210
+ if (typeof state !== "object") {
211
+ throw new Error("Internal: received update on LiveObject but state was not an object");
212
+ }
213
+ let newState = Object.assign({}, state);
214
+ for (const key in update.updates) {
215
+ if (((_a = update.updates[key]) === null || _a === void 0 ? void 0 : _a.type) === "update") {
216
+ newState[key] = liveNodeToJson(update.node.get(key));
217
+ }
218
+ else if (((_b = update.updates[key]) === null || _b === void 0 ? void 0 : _b.type) === "delete") {
219
+ delete newState[key];
220
+ }
221
+ }
222
+ return newState;
223
+ }
224
+ case "LiveList": {
225
+ if (Array.isArray(state) === false) {
226
+ throw new Error("Internal: received update on LiveList but state was not an array");
227
+ }
228
+ let newState = state.map((x) => x);
229
+ for (const listUpdate of update.updates) {
230
+ if (listUpdate.type === "insert") {
231
+ if (listUpdate.index === newState.length) {
232
+ newState.push(liveNodeToJson(listUpdate.item));
233
+ }
234
+ else {
235
+ newState = [
236
+ ...newState.slice(0, listUpdate.index),
237
+ liveNodeToJson(listUpdate.item),
238
+ ...newState.slice(listUpdate.index),
239
+ ];
240
+ }
241
+ }
242
+ else if (listUpdate.type === "delete") {
243
+ newState.splice(listUpdate.index, 1);
244
+ }
245
+ else if (listUpdate.type === "move") {
246
+ if (listUpdate.previousIndex > listUpdate.index) {
247
+ newState = [
248
+ ...newState.slice(0, listUpdate.index),
249
+ liveNodeToJson(listUpdate.item),
250
+ ...newState.slice(listUpdate.index, listUpdate.previousIndex),
251
+ ...newState.slice(listUpdate.previousIndex + 1),
252
+ ];
253
+ }
254
+ else {
255
+ newState = [
256
+ ...newState.slice(0, listUpdate.previousIndex),
257
+ ...newState.slice(listUpdate.previousIndex + 1, listUpdate.index + 1),
258
+ liveNodeToJson(listUpdate.item),
259
+ ...newState.slice(listUpdate.index + 1),
260
+ ];
261
+ }
262
+ }
263
+ }
264
+ return newState;
265
+ }
266
+ case "LiveMap": {
267
+ if (typeof state !== "object") {
268
+ throw new Error("Internal: received update on LiveMap but state was not an object");
269
+ }
270
+ let newState = Object.assign({}, state);
271
+ for (const key in update.updates) {
272
+ if (((_c = update.updates[key]) === null || _c === void 0 ? void 0 : _c.type) === "update") {
273
+ newState[key] = liveNodeToJson(update.node.get(key));
274
+ }
275
+ else if (((_d = update.updates[key]) === null || _d === void 0 ? void 0 : _d.type) === "delete") {
276
+ delete newState[key];
277
+ }
278
+ }
279
+ return newState;
280
+ }
281
+ }
282
+ }
283
+ if (Array.isArray(state)) {
284
+ const newArray = [...state];
285
+ newArray[pathItem] = patchImmutableNode(state[pathItem], path, update);
286
+ return newArray;
287
+ }
288
+ else {
289
+ return Object.assign(Object.assign({}, state), { [pathItem]: patchImmutableNode(state[pathItem], path, update) });
290
+ }
291
+ }
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) {
@@ -433,15 +409,14 @@ See v0.13 release notes for more information.
433
409
  : null;
434
410
  }
435
411
  function connect() {
436
- if (typeof window === "undefined") {
437
- return;
438
- }
439
412
  if (state.connection.state !== "closed" &&
440
413
  state.connection.state !== "unavailable") {
441
414
  return null;
442
415
  }
416
+ const auth = prepareAuthEndpoint(context.authentication, context.fetchPolyfill);
417
+ const createWebSocket = prepareCreateWebSocket(context.liveblocksServer, context.WebSocketPolyfill);
443
418
  updateConnection({ state: "authenticating" });
444
- effects.authenticate();
419
+ effects.authenticate(auth, createWebSocket);
445
420
  }
446
421
  function updatePresence(overrides, options) {
447
422
  const oldValues = {};
@@ -468,6 +443,10 @@ See v0.13 release notes for more information.
468
443
  }
469
444
  }
470
445
  function authenticationSuccess(token, socket) {
446
+ socket.addEventListener("message", onMessage);
447
+ socket.addEventListener("open", onOpen);
448
+ socket.addEventListener("close", onClose);
449
+ socket.addEventListener("error", onError);
471
450
  updateConnection({
472
451
  state: "connecting",
473
452
  id: token.actor,
@@ -701,7 +680,7 @@ See v0.13 release notes for more information.
701
680
  }
702
681
  clearTimeout(state.timeoutHandles.pongTimeout);
703
682
  state.timeoutHandles.pongTimeout = effects.schedulePongTimeout();
704
- if (state.socket.readyState === WebSocket.OPEN) {
683
+ if (state.socket.readyState === state.socket.OPEN) {
705
684
  state.socket.send("ping");
706
685
  }
707
686
  }
@@ -748,7 +727,7 @@ See v0.13 release notes for more information.
748
727
  state.offlineOperations.set(op.opId, op);
749
728
  });
750
729
  }
751
- if (state.socket == null || state.socket.readyState !== WebSocket.OPEN) {
730
+ if (state.socket == null || state.socket.readyState !== state.socket.OPEN) {
752
731
  state.buffer.storageOperations = [];
753
732
  return;
754
733
  }
@@ -952,7 +931,6 @@ See v0.13 release notes for more information.
952
931
  }
953
932
  return {
954
933
  // Internal
955
- onOpen,
956
934
  onClose,
957
935
  onMessage,
958
936
  authenticationSuccess,
@@ -1042,26 +1020,9 @@ function defaultState(me, defaultStorageRoot) {
1042
1020
  };
1043
1021
  }
1044
1022
  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
- }
1023
+ function createRoom(options, context) {
1057
1024
  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
- });
1025
+ const machine = makeStateMachine(state, context);
1065
1026
  const room = {
1066
1027
  /////////////
1067
1028
  // Core //
@@ -1106,3 +1067,76 @@ class LiveblocksError extends Error {
1106
1067
  this.code = code;
1107
1068
  }
1108
1069
  }
1070
+ function parseToken(token) {
1071
+ const tokenParts = token.split(".");
1072
+ if (tokenParts.length !== 3) {
1073
+ throw new Error(`Authentication error. Liveblocks could not parse the response of your authentication endpoint`);
1074
+ }
1075
+ const data = JSON.parse(atob(tokenParts[1]));
1076
+ if (typeof data.actor !== "number") {
1077
+ throw new Error(`Authentication error. Liveblocks could not parse the response of your authentication endpoint`);
1078
+ }
1079
+ return data;
1080
+ }
1081
+ function prepareCreateWebSocket(liveblocksServer, WebSocketPolyfill) {
1082
+ if (typeof window === "undefined" && WebSocketPolyfill == null) {
1083
+ throw new Error("To use Liveblocks client in a non-dom environment, you need to provide a WebSocket polyfill.");
1084
+ }
1085
+ const ws = WebSocketPolyfill || WebSocket;
1086
+ return (token) => {
1087
+ return new ws(`${liveblocksServer}/?token=${token}`);
1088
+ };
1089
+ }
1090
+ function prepareAuthEndpoint(authentication, fetchPolyfill) {
1091
+ if (authentication.type === "public") {
1092
+ if (typeof window === "undefined" && fetchPolyfill == null) {
1093
+ throw new Error("To use Liveblocks client in a non-dom environment with a publicApiKey, you need to provide a fetch polyfill.");
1094
+ }
1095
+ return (room) => fetchAuthEndpoint(fetchPolyfill || fetch, authentication.url, {
1096
+ room,
1097
+ publicApiKey: authentication.publicApiKey,
1098
+ });
1099
+ }
1100
+ if (authentication.type === "private") {
1101
+ if (typeof window === "undefined" && fetchPolyfill == null) {
1102
+ 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.");
1103
+ }
1104
+ return (room) => fetchAuthEndpoint(fetchPolyfill || fetch, authentication.url, {
1105
+ room,
1106
+ });
1107
+ }
1108
+ if (authentication.type === "custom") {
1109
+ return authentication.callback;
1110
+ }
1111
+ throw new Error("Internal error. Unexpected authentication type");
1112
+ }
1113
+ function fetchAuthEndpoint(fetch, endpoint, body) {
1114
+ return __awaiter(this, void 0, void 0, function* () {
1115
+ const res = yield fetch(endpoint, {
1116
+ method: "POST",
1117
+ headers: {
1118
+ "Content-Type": "application/json",
1119
+ },
1120
+ body: JSON.stringify(body),
1121
+ });
1122
+ if (!res.ok) {
1123
+ throw new AuthenticationError(`Authentication error. Liveblocks could not parse the response of your authentication "${endpoint}"`);
1124
+ }
1125
+ let authResponse = null;
1126
+ try {
1127
+ authResponse = yield res.json();
1128
+ }
1129
+ catch (er) {
1130
+ throw new AuthenticationError(`Authentication error. Liveblocks could not parse the response of your authentication "${endpoint}"`);
1131
+ }
1132
+ if (typeof authResponse.token !== "string") {
1133
+ throw new AuthenticationError(`Authentication error. Liveblocks could not parse the response of your authentication "${endpoint}"`);
1134
+ }
1135
+ return authResponse;
1136
+ });
1137
+ }
1138
+ class AuthenticationError extends Error {
1139
+ constructor(message) {
1140
+ super(message);
1141
+ }
1142
+ }
@@ -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";
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
+ }
@@ -0,0 +1,9 @@
1
+ import { LiveList } from "./LiveList";
2
+ import { LiveObject } from "./LiveObject";
3
+ import { StorageUpdate } from "./types";
4
+ export declare function liveObjectToJson(liveObject: LiveObject<any>): any;
5
+ export declare function liveNodeToJson(value: any): any;
6
+ export declare function patchLiveList<T>(liveList: LiveList<T>, prev: Array<T>, next: Array<T>): void;
7
+ export declare function patchLiveObjectKey<T>(liveObject: LiveObject<T>, key: keyof T, prev: any, next: any): void;
8
+ export declare function patchLiveObject<T extends Record<string, any>>(root: LiveObject<T>, prev: T, next: T): void;
9
+ export declare function patchImmutableObject<T>(state: T, updates: StorageUpdate[]): T;
@@ -0,0 +1,282 @@
1
+ import { LiveList } from "./LiveList";
2
+ import { LiveMap } from "./LiveMap";
3
+ import { LiveObject } from "./LiveObject";
4
+ import { LiveRegister } from "./LiveRegister";
5
+ export function liveObjectToJson(liveObject) {
6
+ const result = {};
7
+ const obj = liveObject.toObject();
8
+ for (const key in obj) {
9
+ result[key] = liveNodeToJson(obj[key]);
10
+ }
11
+ return result;
12
+ }
13
+ function liveMapToJson(map) {
14
+ const result = {};
15
+ const obj = Object.fromEntries(map);
16
+ for (const key in obj) {
17
+ result[key] = liveNodeToJson(obj[key]);
18
+ }
19
+ return result;
20
+ }
21
+ function liveListToJson(value) {
22
+ return value.toArray().map(liveNodeToJson);
23
+ }
24
+ export function liveNodeToJson(value) {
25
+ if (value instanceof LiveObject) {
26
+ return liveObjectToJson(value);
27
+ }
28
+ else if (value instanceof LiveList) {
29
+ return liveListToJson(value);
30
+ }
31
+ else if (value instanceof LiveMap) {
32
+ return liveMapToJson(value);
33
+ }
34
+ else if (value instanceof LiveRegister) {
35
+ return value.data;
36
+ }
37
+ return value;
38
+ }
39
+ function isPlainObject(obj) {
40
+ return Object.prototype.toString.call(obj) === "[object Object]";
41
+ }
42
+ function anyToCrdt(obj) {
43
+ if (obj == null) {
44
+ return obj;
45
+ }
46
+ if (Array.isArray(obj)) {
47
+ return new LiveList(obj.map(anyToCrdt));
48
+ }
49
+ if (isPlainObject(obj)) {
50
+ const init = {};
51
+ for (const key in obj) {
52
+ init[key] = anyToCrdt(obj[key]);
53
+ }
54
+ return new LiveObject(init);
55
+ }
56
+ return obj;
57
+ }
58
+ export function patchLiveList(liveList, prev, next) {
59
+ let i = 0;
60
+ let prevEnd = prev.length - 1;
61
+ let nextEnd = next.length - 1;
62
+ let prevNode = prev[0];
63
+ let nextNode = next[0];
64
+ /**
65
+ * For A,B,C => A,B,C,D
66
+ * i = 3, prevEnd = 2, nextEnd = 3
67
+ *
68
+ * For A,B,C => B,C
69
+ * i = 2, prevEnd = 2, nextEnd = 1
70
+ *
71
+ * For B,C => A,B,C
72
+ * i = 0, pre
73
+ */
74
+ outer: {
75
+ while (prevNode === nextNode) {
76
+ ++i;
77
+ if (i > prevEnd || i > nextEnd) {
78
+ break outer;
79
+ }
80
+ prevNode = prev[i];
81
+ nextNode = next[i];
82
+ }
83
+ prevNode = prev[prevEnd];
84
+ nextNode = next[nextEnd];
85
+ while (prevNode === nextNode) {
86
+ prevEnd--;
87
+ nextEnd--;
88
+ if (i > prevEnd || i > nextEnd) {
89
+ break outer;
90
+ }
91
+ prevNode = prev[prevEnd];
92
+ nextNode = next[nextEnd];
93
+ }
94
+ }
95
+ if (i > prevEnd) {
96
+ if (i <= nextEnd) {
97
+ while (i <= nextEnd) {
98
+ liveList.insert(anyToCrdt(next[i]), i);
99
+ i++;
100
+ }
101
+ }
102
+ }
103
+ else if (i > nextEnd) {
104
+ let localI = i;
105
+ while (localI <= prevEnd) {
106
+ liveList.delete(i);
107
+ localI++;
108
+ }
109
+ }
110
+ else {
111
+ while (i <= prevEnd && i <= nextEnd) {
112
+ prevNode = prev[i];
113
+ nextNode = next[i];
114
+ const liveListNode = liveList.get(i);
115
+ if (liveListNode instanceof LiveObject &&
116
+ isPlainObject(prevNode) &&
117
+ isPlainObject(nextNode)) {
118
+ patchLiveObject(liveListNode, prevNode, nextNode);
119
+ }
120
+ else {
121
+ liveList.delete(i);
122
+ liveList.insert(anyToCrdt(nextNode), i);
123
+ }
124
+ i++;
125
+ }
126
+ while (i <= nextEnd) {
127
+ liveList.insert(anyToCrdt(next[i]), i);
128
+ i++;
129
+ }
130
+ while (i <= prevEnd) {
131
+ liveList.delete(i);
132
+ i++;
133
+ }
134
+ }
135
+ }
136
+ export function patchLiveObjectKey(liveObject, key, prev, next) {
137
+ const value = liveObject.get(key);
138
+ if (next === undefined) {
139
+ liveObject.delete(key);
140
+ }
141
+ else if (value === undefined) {
142
+ liveObject.set(key, anyToCrdt(next));
143
+ }
144
+ else if (prev === next) {
145
+ return;
146
+ }
147
+ else if (value instanceof LiveList &&
148
+ Array.isArray(prev) &&
149
+ Array.isArray(next)) {
150
+ patchLiveList(value, prev, next);
151
+ }
152
+ else if (value instanceof LiveObject &&
153
+ isPlainObject(prev) &&
154
+ isPlainObject(next)) {
155
+ patchLiveObject(value, prev, next);
156
+ }
157
+ else {
158
+ liveObject.set(key, anyToCrdt(next));
159
+ }
160
+ }
161
+ export function patchLiveObject(root, prev, next) {
162
+ const updates = {};
163
+ for (const key in next) {
164
+ patchLiveObjectKey(root, key, prev[key], next[key]);
165
+ }
166
+ for (const key in prev) {
167
+ if (next[key] === undefined) {
168
+ root.delete(key);
169
+ }
170
+ }
171
+ if (Object.keys(updates).length > 0) {
172
+ root.update(updates);
173
+ }
174
+ }
175
+ function getParentsPath(node) {
176
+ const path = [];
177
+ while (node._parentKey != null && node._parent != null) {
178
+ if (node._parent instanceof LiveList) {
179
+ path.push(node._parent._indexOfPosition(node._parentKey));
180
+ }
181
+ else {
182
+ path.push(node._parentKey);
183
+ }
184
+ node = node._parent;
185
+ }
186
+ return path;
187
+ }
188
+ export function patchImmutableObject(state, updates) {
189
+ return updates.reduce((state, update) => patchImmutableObjectWithUpdate(state, update), state);
190
+ }
191
+ function patchImmutableObjectWithUpdate(state, update) {
192
+ const path = getParentsPath(update.node);
193
+ return patchImmutableNode(state, path, update);
194
+ }
195
+ function patchImmutableNode(state, path, update) {
196
+ var _a, _b, _c, _d;
197
+ const pathItem = path.pop();
198
+ if (pathItem === undefined) {
199
+ switch (update.type) {
200
+ case "LiveObject": {
201
+ if (typeof state !== "object") {
202
+ throw new Error("Internal: received update on LiveObject but state was not an object");
203
+ }
204
+ let newState = Object.assign({}, state);
205
+ for (const key in update.updates) {
206
+ if (((_a = update.updates[key]) === null || _a === void 0 ? void 0 : _a.type) === "update") {
207
+ newState[key] = liveNodeToJson(update.node.get(key));
208
+ }
209
+ else if (((_b = update.updates[key]) === null || _b === void 0 ? void 0 : _b.type) === "delete") {
210
+ delete newState[key];
211
+ }
212
+ }
213
+ return newState;
214
+ }
215
+ case "LiveList": {
216
+ if (Array.isArray(state) === false) {
217
+ throw new Error("Internal: received update on LiveList but state was not an array");
218
+ }
219
+ let newState = state.map((x) => x);
220
+ for (const listUpdate of update.updates) {
221
+ if (listUpdate.type === "insert") {
222
+ if (listUpdate.index === newState.length) {
223
+ newState.push(liveNodeToJson(listUpdate.item));
224
+ }
225
+ else {
226
+ newState = [
227
+ ...newState.slice(0, listUpdate.index),
228
+ liveNodeToJson(listUpdate.item),
229
+ ...newState.slice(listUpdate.index),
230
+ ];
231
+ }
232
+ }
233
+ else if (listUpdate.type === "delete") {
234
+ newState.splice(listUpdate.index, 1);
235
+ }
236
+ else if (listUpdate.type === "move") {
237
+ if (listUpdate.previousIndex > listUpdate.index) {
238
+ newState = [
239
+ ...newState.slice(0, listUpdate.index),
240
+ liveNodeToJson(listUpdate.item),
241
+ ...newState.slice(listUpdate.index, listUpdate.previousIndex),
242
+ ...newState.slice(listUpdate.previousIndex + 1),
243
+ ];
244
+ }
245
+ else {
246
+ newState = [
247
+ ...newState.slice(0, listUpdate.previousIndex),
248
+ ...newState.slice(listUpdate.previousIndex + 1, listUpdate.index + 1),
249
+ liveNodeToJson(listUpdate.item),
250
+ ...newState.slice(listUpdate.index + 1),
251
+ ];
252
+ }
253
+ }
254
+ }
255
+ return newState;
256
+ }
257
+ case "LiveMap": {
258
+ if (typeof state !== "object") {
259
+ throw new Error("Internal: received update on LiveMap but state was not an object");
260
+ }
261
+ let newState = Object.assign({}, state);
262
+ for (const key in update.updates) {
263
+ if (((_c = update.updates[key]) === null || _c === void 0 ? void 0 : _c.type) === "update") {
264
+ newState[key] = liveNodeToJson(update.node.get(key));
265
+ }
266
+ else if (((_d = update.updates[key]) === null || _d === void 0 ? void 0 : _d.type) === "delete") {
267
+ delete newState[key];
268
+ }
269
+ }
270
+ return newState;
271
+ }
272
+ }
273
+ }
274
+ if (Array.isArray(state)) {
275
+ const newArray = [...state];
276
+ newArray[pathItem] = patchImmutableNode(state[pathItem], path, update);
277
+ return newArray;
278
+ }
279
+ else {
280
+ return Object.assign(Object.assign({}, state), { [pathItem]: patchImmutableNode(state[pathItem], path, update) });
281
+ }
282
+ }
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 } 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) {
@@ -411,15 +406,14 @@ See v0.13 release notes for more information.
411
406
  : null;
412
407
  }
413
408
  function connect() {
414
- if (typeof window === "undefined") {
415
- return;
416
- }
417
409
  if (state.connection.state !== "closed" &&
418
410
  state.connection.state !== "unavailable") {
419
411
  return null;
420
412
  }
413
+ const auth = prepareAuthEndpoint(context.authentication, context.fetchPolyfill);
414
+ const createWebSocket = prepareCreateWebSocket(context.liveblocksServer, context.WebSocketPolyfill);
421
415
  updateConnection({ state: "authenticating" });
422
- effects.authenticate();
416
+ effects.authenticate(auth, createWebSocket);
423
417
  }
424
418
  function updatePresence(overrides, options) {
425
419
  const oldValues = {};
@@ -446,6 +440,10 @@ See v0.13 release notes for more information.
446
440
  }
447
441
  }
448
442
  function authenticationSuccess(token, socket) {
443
+ socket.addEventListener("message", onMessage);
444
+ socket.addEventListener("open", onOpen);
445
+ socket.addEventListener("close", onClose);
446
+ socket.addEventListener("error", onError);
449
447
  updateConnection({
450
448
  state: "connecting",
451
449
  id: token.actor,
@@ -679,7 +677,7 @@ See v0.13 release notes for more information.
679
677
  }
680
678
  clearTimeout(state.timeoutHandles.pongTimeout);
681
679
  state.timeoutHandles.pongTimeout = effects.schedulePongTimeout();
682
- if (state.socket.readyState === WebSocket.OPEN) {
680
+ if (state.socket.readyState === state.socket.OPEN) {
683
681
  state.socket.send("ping");
684
682
  }
685
683
  }
@@ -726,7 +724,7 @@ See v0.13 release notes for more information.
726
724
  state.offlineOperations.set(op.opId, op);
727
725
  });
728
726
  }
729
- if (state.socket == null || state.socket.readyState !== WebSocket.OPEN) {
727
+ if (state.socket == null || state.socket.readyState !== state.socket.OPEN) {
730
728
  state.buffer.storageOperations = [];
731
729
  return;
732
730
  }
@@ -930,7 +928,6 @@ See v0.13 release notes for more information.
930
928
  }
931
929
  return {
932
930
  // Internal
933
- onOpen,
934
931
  onClose,
935
932
  onMessage,
936
933
  authenticationSuccess,
@@ -1018,26 +1015,9 @@ export function defaultState(me, defaultStorageRoot) {
1018
1015
  offlineOperations: new Map(),
1019
1016
  };
1020
1017
  }
1021
- export function createRoom(name, options) {
1022
- const throttleDelay = options.throttle || 100;
1023
- const liveblocksServer = options.liveblocksServer || "wss://liveblocks.net/v5";
1024
- let authEndpoint;
1025
- if (options.authEndpoint) {
1026
- authEndpoint = options.authEndpoint;
1027
- }
1028
- else {
1029
- const publicAuthorizeEndpoint = options.publicAuthorizeEndpoint ||
1030
- "https://liveblocks.io/api/public/authorize";
1031
- authEndpoint = publicAuthorizeEndpoint;
1032
- }
1018
+ export function createRoom(options, context) {
1033
1019
  const state = defaultState(options.defaultPresence, options.defaultStorageRoot);
1034
- const machine = makeStateMachine(state, {
1035
- throttleDelay,
1036
- liveblocksServer,
1037
- authEndpoint,
1038
- room: name,
1039
- publicApiKey: options.publicApiKey,
1040
- });
1020
+ const machine = makeStateMachine(state, context);
1041
1021
  const room = {
1042
1022
  /////////////
1043
1023
  // Core //
@@ -1081,3 +1061,76 @@ class LiveblocksError extends Error {
1081
1061
  this.code = code;
1082
1062
  }
1083
1063
  }
1064
+ function parseToken(token) {
1065
+ const tokenParts = token.split(".");
1066
+ if (tokenParts.length !== 3) {
1067
+ throw new Error(`Authentication error. Liveblocks could not parse the response of your authentication endpoint`);
1068
+ }
1069
+ const data = JSON.parse(atob(tokenParts[1]));
1070
+ if (typeof data.actor !== "number") {
1071
+ throw new Error(`Authentication error. Liveblocks could not parse the response of your authentication endpoint`);
1072
+ }
1073
+ return data;
1074
+ }
1075
+ function prepareCreateWebSocket(liveblocksServer, WebSocketPolyfill) {
1076
+ if (typeof window === "undefined" && WebSocketPolyfill == null) {
1077
+ throw new Error("To use Liveblocks client in a non-dom environment, you need to provide a WebSocket polyfill.");
1078
+ }
1079
+ const ws = WebSocketPolyfill || WebSocket;
1080
+ return (token) => {
1081
+ return new ws(`${liveblocksServer}/?token=${token}`);
1082
+ };
1083
+ }
1084
+ function prepareAuthEndpoint(authentication, fetchPolyfill) {
1085
+ if (authentication.type === "public") {
1086
+ if (typeof window === "undefined" && fetchPolyfill == null) {
1087
+ throw new Error("To use Liveblocks client in a non-dom environment with a publicApiKey, you need to provide a fetch polyfill.");
1088
+ }
1089
+ return (room) => fetchAuthEndpoint(fetchPolyfill || fetch, authentication.url, {
1090
+ room,
1091
+ publicApiKey: authentication.publicApiKey,
1092
+ });
1093
+ }
1094
+ if (authentication.type === "private") {
1095
+ if (typeof window === "undefined" && fetchPolyfill == null) {
1096
+ 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.");
1097
+ }
1098
+ return (room) => fetchAuthEndpoint(fetchPolyfill || fetch, authentication.url, {
1099
+ room,
1100
+ });
1101
+ }
1102
+ if (authentication.type === "custom") {
1103
+ return authentication.callback;
1104
+ }
1105
+ throw new Error("Internal error. Unexpected authentication type");
1106
+ }
1107
+ function fetchAuthEndpoint(fetch, endpoint, body) {
1108
+ return __awaiter(this, void 0, void 0, function* () {
1109
+ const res = yield fetch(endpoint, {
1110
+ method: "POST",
1111
+ headers: {
1112
+ "Content-Type": "application/json",
1113
+ },
1114
+ body: JSON.stringify(body),
1115
+ });
1116
+ if (!res.ok) {
1117
+ throw new AuthenticationError(`Authentication error. Liveblocks could not parse the response of your authentication "${endpoint}"`);
1118
+ }
1119
+ let authResponse = null;
1120
+ try {
1121
+ authResponse = yield res.json();
1122
+ }
1123
+ catch (er) {
1124
+ throw new AuthenticationError(`Authentication error. Liveblocks could not parse the response of your authentication "${endpoint}"`);
1125
+ }
1126
+ if (typeof authResponse.token !== "string") {
1127
+ throw new AuthenticationError(`Authentication error. Liveblocks could not parse the response of your authentication "${endpoint}"`);
1128
+ }
1129
+ return authResponse;
1130
+ });
1131
+ }
1132
+ class AuthenticationError extends Error {
1133
+ constructor(message) {
1134
+ super(message);
1135
+ }
1136
+ }
@@ -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";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@liveblocks/client",
3
- "version": "0.14.0",
3
+ "version": "0.14.1",
4
4
  "description": "",
5
5
  "main": "./lib/cjs/index.js",
6
6
  "module": "./lib/esm/index.js",
@@ -29,13 +29,17 @@
29
29
  "@babel/preset-env": "^7.12.16",
30
30
  "@babel/preset-typescript": "^7.12.16",
31
31
  "@types/jest": "^26.0.21",
32
+ "@types/node-fetch": "^2.6.1",
33
+ "@types/ws": "^8.2.2",
32
34
  "babel-jest": "^26.6.3",
33
35
  "jest": "^26.6.3",
34
- "typescript": "^4.4.0"
36
+ "node-fetch": "2.6.7",
37
+ "typescript": "^4.4.0",
38
+ "ws": "^8.5.0"
35
39
  },
36
40
  "repository": {
37
41
  "type": "git",
38
42
  "url": "https://github.com/liveblocks/liveblocks.git",
39
43
  "directory": "packages/liveblocks"
40
44
  }
41
- }
45
+ }