@liveblocks/core 1.0.8 → 1.0.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +67 -59
- package/dist/index.js +178 -87
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -129,6 +129,63 @@ declare type UpdateDelta = {
|
|
|
129
129
|
type: "delete";
|
|
130
130
|
};
|
|
131
131
|
|
|
132
|
+
/**
|
|
133
|
+
* "Plain LSON" is a JSON-based format that's used when serializing Live structures
|
|
134
|
+
* to send them over HTTP (e.g. in the API endpoint to let users upload their initial
|
|
135
|
+
* Room storage, in the API endpoint to fetch a Room's storage, ...).
|
|
136
|
+
*
|
|
137
|
+
* In the client, you would typically create LSON values using:
|
|
138
|
+
*
|
|
139
|
+
* new LiveObject({ x: 0, y: 0 })
|
|
140
|
+
*
|
|
141
|
+
* But over HTTP, this has to be serialized somehow. The "Plain LSON" format
|
|
142
|
+
* is what's used in the POST /init-storage-new endpoint, to allow users to
|
|
143
|
+
* control which parts of their data structure should be considered "Live"
|
|
144
|
+
* objects, and which parts are "normal" objects.
|
|
145
|
+
*
|
|
146
|
+
* So if they have a structure like:
|
|
147
|
+
*
|
|
148
|
+
* { x: 0, y: 0 }
|
|
149
|
+
*
|
|
150
|
+
* And want to make it a Live object, they can serialize it by wrapping it in
|
|
151
|
+
* a special "annotation":
|
|
152
|
+
*
|
|
153
|
+
* {
|
|
154
|
+
* "liveblocksType": "LiveObject",
|
|
155
|
+
* "data": { x: 0, y: 0 },
|
|
156
|
+
* }
|
|
157
|
+
*
|
|
158
|
+
* This "Plain LSON" data format defines exactly those wrappings.
|
|
159
|
+
*
|
|
160
|
+
* To summarize:
|
|
161
|
+
*
|
|
162
|
+
* LSON value | Plain LSON equivalent
|
|
163
|
+
* ----------------------+----------------------------------------------
|
|
164
|
+
* 42 | 42
|
|
165
|
+
* [1, 2, 3] | [1, 2, 3]
|
|
166
|
+
* { x: 0, y: 0 } | { x: 0, y: 0 }
|
|
167
|
+
* ----------------------+----------------------------------------------
|
|
168
|
+
* new LiveList(...) | { liveblocksType: "LiveList", data: ... }
|
|
169
|
+
* new LiveMap(...) | { liveblocksType: "LiveMap", data: ... }
|
|
170
|
+
* new LiveObject(...) | { liveblocksType: "LiveObject", data: ... }
|
|
171
|
+
*
|
|
172
|
+
*/
|
|
173
|
+
|
|
174
|
+
declare type PlainLsonFields = Record<string, PlainLson>;
|
|
175
|
+
declare type PlainLsonObject = {
|
|
176
|
+
liveblocksType: "LiveObject";
|
|
177
|
+
data: PlainLsonFields;
|
|
178
|
+
};
|
|
179
|
+
declare type PlainLsonMap = {
|
|
180
|
+
liveblocksType: "LiveMap";
|
|
181
|
+
data: PlainLsonFields;
|
|
182
|
+
};
|
|
183
|
+
declare type PlainLsonList = {
|
|
184
|
+
liveblocksType: "LiveList";
|
|
185
|
+
data: PlainLson[];
|
|
186
|
+
};
|
|
187
|
+
declare type PlainLson = PlainLsonObject | PlainLsonMap | PlainLsonList | Json;
|
|
188
|
+
|
|
132
189
|
declare type LiveObjectUpdateDelta<O extends {
|
|
133
190
|
[key: string]: unknown;
|
|
134
191
|
}> = {
|
|
@@ -197,6 +254,10 @@ declare class LiveObject<O extends LsonObject> extends AbstractCrdt {
|
|
|
197
254
|
declare type ToImmutable<L extends Lson | LsonObject> = L extends LiveList<infer I> ? readonly ToImmutable<I>[] : L extends LiveObject<infer O> ? ToImmutable<O> : L extends LiveMap<infer K, infer V> ? ReadonlyMap<K, ToImmutable<V>> : L extends LsonObject ? {
|
|
198
255
|
readonly [K in keyof L]: ToImmutable<Exclude<L[K], undefined>> | (undefined extends L[K] ? undefined : never);
|
|
199
256
|
} : L extends Json ? L : never;
|
|
257
|
+
/**
|
|
258
|
+
* Returns PlainLson for a given Json or LiveStructure, suitable for calling the storage init api
|
|
259
|
+
*/
|
|
260
|
+
declare function toPlainLson(lson: Lson): PlainLson;
|
|
200
261
|
|
|
201
262
|
/**
|
|
202
263
|
* A LiveMap notification that is sent in-client to any subscribers whenever
|
|
@@ -268,12 +329,15 @@ declare class LiveMap<TKey extends string, TValue extends Lson> extends Abstract
|
|
|
268
329
|
}
|
|
269
330
|
|
|
270
331
|
declare type StorageCallback = (updates: StorageUpdate[]) => void;
|
|
332
|
+
declare type LiveMapUpdate = LiveMapUpdates<string, Lson>;
|
|
333
|
+
declare type LiveObjectUpdate = LiveObjectUpdates<LsonObject>;
|
|
334
|
+
declare type LiveListUpdate = LiveListUpdates<Lson>;
|
|
271
335
|
/**
|
|
272
336
|
* The payload of notifications sent (in-client) when LiveStructures change.
|
|
273
337
|
* Messages of this kind are not originating from the network, but are 100%
|
|
274
338
|
* in-client.
|
|
275
339
|
*/
|
|
276
|
-
declare type StorageUpdate =
|
|
340
|
+
declare type StorageUpdate = LiveMapUpdate | LiveObjectUpdate | LiveListUpdate;
|
|
277
341
|
|
|
278
342
|
declare abstract class AbstractCrdt {
|
|
279
343
|
get roomId(): string | null;
|
|
@@ -1051,6 +1115,7 @@ declare type AuthEndpoint = string | ((room: string) => Promise<{
|
|
|
1051
1115
|
declare type ClientOptions = {
|
|
1052
1116
|
throttle?: number;
|
|
1053
1117
|
polyfills?: Polyfills;
|
|
1118
|
+
unstable_fallbackToHTTP?: boolean;
|
|
1054
1119
|
/**
|
|
1055
1120
|
* Backward-compatible way to set `polyfills.fetch`.
|
|
1056
1121
|
*/
|
|
@@ -1546,63 +1611,6 @@ SerializedCrdt>;
|
|
|
1546
1611
|
declare type ParentToChildNodeMap = Map<string, // Parent's node ID
|
|
1547
1612
|
IdTuple<SerializedChild>[]>;
|
|
1548
1613
|
|
|
1549
|
-
/**
|
|
1550
|
-
* "Plain LSON" is a JSON-based format that's used when serializing Live structures
|
|
1551
|
-
* to send them over HTTP (e.g. in the API endpoint to let users upload their initial
|
|
1552
|
-
* Room storage, in the API endpoint to fetch a Room's storage, ...).
|
|
1553
|
-
*
|
|
1554
|
-
* In the client, you would typically create LSON values using:
|
|
1555
|
-
*
|
|
1556
|
-
* new LiveObject({ x: 0, y: 0 })
|
|
1557
|
-
*
|
|
1558
|
-
* But over HTTP, this has to be serialized somehow. The "Plain LSON" format
|
|
1559
|
-
* is what's used in the POST /init-storage-new endpoint, to allow users to
|
|
1560
|
-
* control which parts of their data structure should be considered "Live"
|
|
1561
|
-
* objects, and which parts are "normal" objects.
|
|
1562
|
-
*
|
|
1563
|
-
* So if they have a structure like:
|
|
1564
|
-
*
|
|
1565
|
-
* { x: 0, y: 0 }
|
|
1566
|
-
*
|
|
1567
|
-
* And want to make it a Live object, they can serialize it by wrapping it in
|
|
1568
|
-
* a special "annotation":
|
|
1569
|
-
*
|
|
1570
|
-
* {
|
|
1571
|
-
* "liveblocksType": "LiveObject",
|
|
1572
|
-
* "data": { x: 0, y: 0 },
|
|
1573
|
-
* }
|
|
1574
|
-
*
|
|
1575
|
-
* This "Plain LSON" data format defines exactly those wrappings.
|
|
1576
|
-
*
|
|
1577
|
-
* To summarize:
|
|
1578
|
-
*
|
|
1579
|
-
* LSON value | Plain LSON equivalent
|
|
1580
|
-
* ----------------------+----------------------------------------------
|
|
1581
|
-
* 42 | 42
|
|
1582
|
-
* [1, 2, 3] | [1, 2, 3]
|
|
1583
|
-
* { x: 0, y: 0 } | { x: 0, y: 0 }
|
|
1584
|
-
* ----------------------+----------------------------------------------
|
|
1585
|
-
* new LiveList(...) | { liveblocksType: "LiveList", data: ... }
|
|
1586
|
-
* new LiveMap(...) | { liveblocksType: "LiveMap", data: ... }
|
|
1587
|
-
* new LiveObject(...) | { liveblocksType: "LiveObject", data: ... }
|
|
1588
|
-
*
|
|
1589
|
-
*/
|
|
1590
|
-
|
|
1591
|
-
declare type PlainLsonFields = Record<string, PlainLson>;
|
|
1592
|
-
declare type PlainLsonObject = {
|
|
1593
|
-
liveblocksType: "LiveObject";
|
|
1594
|
-
data: PlainLsonFields;
|
|
1595
|
-
};
|
|
1596
|
-
declare type PlainLsonMap = {
|
|
1597
|
-
liveblocksType: "LiveMap";
|
|
1598
|
-
data: PlainLsonFields;
|
|
1599
|
-
};
|
|
1600
|
-
declare type PlainLsonList = {
|
|
1601
|
-
liveblocksType: "LiveList";
|
|
1602
|
-
data: PlainLson[];
|
|
1603
|
-
};
|
|
1604
|
-
declare type PlainLson = PlainLsonObject | PlainLsonMap | PlainLsonList | Json;
|
|
1605
|
-
|
|
1606
1614
|
declare type JsonTreeNode = {
|
|
1607
1615
|
readonly type: "Json";
|
|
1608
1616
|
readonly id: string;
|
|
@@ -1779,4 +1787,4 @@ declare type EnsureJson<T> = [
|
|
|
1779
1787
|
[K in keyof T]: EnsureJson<T[K]>;
|
|
1780
1788
|
};
|
|
1781
1789
|
|
|
1782
|
-
export { AckOp, AppOnlyAuthToken, AuthToken, BaseUserMeta, BroadcastEventClientMsg, BroadcastOptions, BroadcastedEventServerMsg, Client, ClientMsg, ClientMsgCode, ConnectionStatus, CrdtType, CreateChildOp, CreateListOp, CreateMapOp, CreateObjectOp, CreateOp, CreateRegisterOp, CreateRootObjectOp, DeleteCrdtOp, DeleteObjectKeyOp, DevToolsTreeNode as DevTools, protocol as DevToolsMsg, EnsureJson, FetchStorageClientMsg, History, IWebSocket, IWebSocketCloseEvent, IWebSocketEvent, IWebSocketInstance, IWebSocketMessageEvent, IdTuple, Immutable, InitialDocumentStateServerMsg, Json, JsonArray, JsonObject, JsonScalar, LiveList, LiveMap, LiveNode, LiveObject, LiveStructure, Lson, LsonObject, NodeMap, Op, OpCode, Others, ParentToChildNodeMap, PlainLson, PlainLsonFields, PlainLsonList, PlainLsonMap, PlainLsonObject, RejectedStorageOpServerMsg, Resolve, Room, RoomAuthToken, RoomInitializers, RoomStateServerMsg, SerializedChild, SerializedCrdt, SerializedList, SerializedMap, SerializedObject, SerializedRegister, SerializedRootObject, ServerMsg, ServerMsgCode, SetParentKeyOp, StorageStatus, StorageUpdate, ToImmutable, ToJson, UpdateObjectOp, UpdatePresenceClientMsg, UpdatePresenceServerMsg, UpdateStorageClientMsg, UpdateStorageServerMsg, User, UserJoinServerMsg, UserLeftServerMsg, WebsocketCloseCodes, asArrayWithLegacyMethods, asPos, assert, assertNever, b64decode, createClient, deprecate, deprecateIf, errorIf, freeze, isAppOnlyAuthToken, isAuthToken, isChildCrdt, isJsonArray, isJsonObject, isJsonScalar, isPlainObject, isRoomAuthToken, isRootCrdt, legacy_patchImmutableObject, lsonToJson, makePosition, nn, patchLiveObjectKey, shallow, throwUsageError, tryParseJson };
|
|
1790
|
+
export { AckOp, AppOnlyAuthToken, AuthToken, BaseUserMeta, BroadcastEventClientMsg, BroadcastOptions, BroadcastedEventServerMsg, Client, ClientMsg, ClientMsgCode, ConnectionStatus, CrdtType, CreateChildOp, CreateListOp, CreateMapOp, CreateObjectOp, CreateOp, CreateRegisterOp, CreateRootObjectOp, DeleteCrdtOp, DeleteObjectKeyOp, DevToolsTreeNode as DevTools, protocol as DevToolsMsg, EnsureJson, FetchStorageClientMsg, History, IWebSocket, IWebSocketCloseEvent, IWebSocketEvent, IWebSocketInstance, IWebSocketMessageEvent, IdTuple, Immutable, InitialDocumentStateServerMsg, Json, JsonArray, JsonObject, JsonScalar, LiveList, LiveListUpdate, LiveMap, LiveMapUpdate, LiveNode, LiveObject, LiveObjectUpdate, LiveStructure, Lson, LsonObject, NodeMap, Op, OpCode, Others, ParentToChildNodeMap, PlainLson, PlainLsonFields, PlainLsonList, PlainLsonMap, PlainLsonObject, RejectedStorageOpServerMsg, Resolve, Room, RoomAuthToken, RoomInitializers, RoomStateServerMsg, SerializedChild, SerializedCrdt, SerializedList, SerializedMap, SerializedObject, SerializedRegister, SerializedRootObject, ServerMsg, ServerMsgCode, SetParentKeyOp, StorageStatus, StorageUpdate, ToImmutable, ToJson, UpdateObjectOp, UpdatePresenceClientMsg, UpdatePresenceServerMsg, UpdateStorageClientMsg, UpdateStorageServerMsg, User, UserJoinServerMsg, UserLeftServerMsg, WebsocketCloseCodes, asArrayWithLegacyMethods, asPos, assert, assertNever, b64decode, createClient, deprecate, deprecateIf, errorIf, freeze, isAppOnlyAuthToken, isAuthToken, isChildCrdt, isJsonArray, isJsonObject, isJsonScalar, isPlainObject, isRoomAuthToken, isRootCrdt, legacy_patchImmutableObject, lsonToJson, makePosition, nn, patchLiveObjectKey, shallow, throwUsageError, toPlainLson, tryParseJson };
|
package/dist/index.js
CHANGED
|
@@ -117,7 +117,7 @@ var onMessageFromPanel = eventSource.observable;
|
|
|
117
117
|
// src/devtools/index.ts
|
|
118
118
|
var VERSION = true ? (
|
|
119
119
|
/* istanbul ignore next */
|
|
120
|
-
"1.0.
|
|
120
|
+
"1.0.10"
|
|
121
121
|
) : "dev";
|
|
122
122
|
var _devtoolsSetupHasRun = false;
|
|
123
123
|
function setupDevTools(getAllRooms) {
|
|
@@ -2955,7 +2955,8 @@ function hasJwtMeta(data) {
|
|
|
2955
2955
|
}
|
|
2956
2956
|
function isTokenExpired(token) {
|
|
2957
2957
|
const now = Date.now() / 1e3;
|
|
2958
|
-
|
|
2958
|
+
const valid = now <= token.exp - 300 && now >= token.iat - 300;
|
|
2959
|
+
return !valid;
|
|
2959
2960
|
}
|
|
2960
2961
|
function isStringList(value) {
|
|
2961
2962
|
return Array.isArray(value) && value.every((i) => typeof i === "string");
|
|
@@ -2983,20 +2984,22 @@ function parseJwtToken(token) {
|
|
|
2983
2984
|
}
|
|
2984
2985
|
function parseRoomAuthToken(tokenString) {
|
|
2985
2986
|
const data = parseJwtToken(tokenString);
|
|
2986
|
-
if (data && isRoomAuthToken(data)) {
|
|
2987
|
-
const _a = data, {
|
|
2988
|
-
maxConnections: _legacyField
|
|
2989
|
-
} = _a, token = __objRest(_a, [
|
|
2990
|
-
// If this legacy field is found on the token, pretend it wasn't there,
|
|
2991
|
-
// to make all internally used token payloads uniform
|
|
2992
|
-
"maxConnections"
|
|
2993
|
-
]);
|
|
2994
|
-
return token;
|
|
2995
|
-
} else {
|
|
2987
|
+
if (!(data && isRoomAuthToken(data))) {
|
|
2996
2988
|
throw new Error(
|
|
2997
2989
|
"Authentication error: we expected a room token but did not get one. Hint: if you are using a callback, ensure the room is passed when creating the token. For more information: https://liveblocks.io/docs/api-reference/liveblocks-client#createClientCallback"
|
|
2998
2990
|
);
|
|
2999
2991
|
}
|
|
2992
|
+
const _a = data, {
|
|
2993
|
+
maxConnections: _legacyField
|
|
2994
|
+
} = _a, parsedToken = __objRest(_a, [
|
|
2995
|
+
// If this legacy field is found on the token, pretend it wasn't there,
|
|
2996
|
+
// to make all internally used token payloads uniform
|
|
2997
|
+
"maxConnections"
|
|
2998
|
+
]);
|
|
2999
|
+
return {
|
|
3000
|
+
raw: tokenString,
|
|
3001
|
+
parsed: parsedToken
|
|
3002
|
+
};
|
|
3000
3003
|
}
|
|
3001
3004
|
|
|
3002
3005
|
// src/protocol/ClientMsg.ts
|
|
@@ -3255,6 +3258,7 @@ var BACKOFF_RETRY_DELAYS = [250, 500, 1e3, 2e3, 4e3, 8e3, 1e4];
|
|
|
3255
3258
|
var BACKOFF_RETRY_DELAYS_SLOW = [2e3, 3e4, 6e4, 3e5];
|
|
3256
3259
|
var HEARTBEAT_INTERVAL = 3e4;
|
|
3257
3260
|
var PONG_TIMEOUT = 2e3;
|
|
3261
|
+
var MAX_MESSAGE_SIZE = 1024 * 1024 - 128;
|
|
3258
3262
|
function makeIdFactory(connectionId) {
|
|
3259
3263
|
let count = 0;
|
|
3260
3264
|
return () => `${connectionId}:${count++}`;
|
|
@@ -3380,21 +3384,20 @@ function createRoom(options, config) {
|
|
|
3380
3384
|
storageStatus: makeEventSource()
|
|
3381
3385
|
};
|
|
3382
3386
|
const effects = config.mockedEffects || {
|
|
3383
|
-
|
|
3387
|
+
authenticateAndConnect(auth, createWebSocket) {
|
|
3384
3388
|
const prevToken = context.token;
|
|
3385
3389
|
if (prevToken !== null && !isTokenExpired(prevToken.parsed)) {
|
|
3386
|
-
const socket = createWebSocket(prevToken
|
|
3387
|
-
|
|
3390
|
+
const socket = createWebSocket(prevToken);
|
|
3391
|
+
handleAuthSuccess(prevToken.parsed, socket);
|
|
3388
3392
|
return void 0;
|
|
3389
3393
|
} else {
|
|
3390
|
-
void auth(
|
|
3394
|
+
void auth().then((token) => {
|
|
3391
3395
|
if (context.connection.current.status !== "authenticating") {
|
|
3392
3396
|
return;
|
|
3393
3397
|
}
|
|
3394
|
-
const parsedToken = parseRoomAuthToken(token);
|
|
3395
3398
|
const socket = createWebSocket(token);
|
|
3396
|
-
|
|
3397
|
-
context.token =
|
|
3399
|
+
handleAuthSuccess(token.parsed, socket);
|
|
3400
|
+
context.token = token;
|
|
3398
3401
|
}).catch(
|
|
3399
3402
|
(er) => authenticationFailure(
|
|
3400
3403
|
er instanceof Error ? er : new Error(String(er))
|
|
@@ -3404,15 +3407,34 @@ function createRoom(options, config) {
|
|
|
3404
3407
|
}
|
|
3405
3408
|
},
|
|
3406
3409
|
send(messageOrMessages) {
|
|
3410
|
+
var _a2, _b;
|
|
3407
3411
|
if (context.socket === null) {
|
|
3408
3412
|
throw new Error("Can't send message if socket is null");
|
|
3409
3413
|
}
|
|
3410
3414
|
if (context.socket.readyState === context.socket.OPEN) {
|
|
3411
|
-
|
|
3415
|
+
const message = JSON.stringify(messageOrMessages);
|
|
3416
|
+
if (config.unstable_fallbackToHTTP) {
|
|
3417
|
+
const size = new TextEncoder().encode(message).length;
|
|
3418
|
+
if (size > MAX_MESSAGE_SIZE && ((_a2 = context.token) == null ? void 0 : _a2.raw) && config.httpSendEndpoint) {
|
|
3419
|
+
if (isTokenExpired(context.token.parsed)) {
|
|
3420
|
+
return reconnect();
|
|
3421
|
+
}
|
|
3422
|
+
void httpSend(
|
|
3423
|
+
message,
|
|
3424
|
+
context.token.raw,
|
|
3425
|
+
config.httpSendEndpoint,
|
|
3426
|
+
(_b = config.polyfills) == null ? void 0 : _b.fetch
|
|
3427
|
+
);
|
|
3428
|
+
warn(
|
|
3429
|
+
"Message was too large for websockets and sent over HTTP instead"
|
|
3430
|
+
);
|
|
3431
|
+
return;
|
|
3432
|
+
}
|
|
3433
|
+
}
|
|
3434
|
+
context.socket.send(message);
|
|
3412
3435
|
}
|
|
3413
3436
|
},
|
|
3414
|
-
|
|
3415
|
-
scheduleReconnect: (delay) => setTimeout(connect, delay),
|
|
3437
|
+
scheduleReconnect: (delay) => setTimeout(handleConnect, delay),
|
|
3416
3438
|
startHeartbeatInterval: () => setInterval(heartbeat, HEARTBEAT_INTERVAL),
|
|
3417
3439
|
schedulePongTimeout: () => setTimeout(pongTimeout, PONG_TIMEOUT)
|
|
3418
3440
|
};
|
|
@@ -3621,12 +3643,13 @@ function createRoom(options, config) {
|
|
|
3621
3643
|
}
|
|
3622
3644
|
}
|
|
3623
3645
|
}
|
|
3624
|
-
function
|
|
3646
|
+
function handleConnect() {
|
|
3625
3647
|
var _a2, _b;
|
|
3626
3648
|
if (context.connection.current.status !== "closed" && context.connection.current.status !== "unavailable") {
|
|
3627
3649
|
return;
|
|
3628
3650
|
}
|
|
3629
3651
|
const auth = prepareAuthEndpoint(
|
|
3652
|
+
config.roomId,
|
|
3630
3653
|
config.authentication,
|
|
3631
3654
|
(_a2 = config.polyfills) == null ? void 0 : _a2.fetch
|
|
3632
3655
|
);
|
|
@@ -3635,7 +3658,7 @@ function createRoom(options, config) {
|
|
|
3635
3658
|
(_b = config.polyfills) == null ? void 0 : _b.WebSocket
|
|
3636
3659
|
);
|
|
3637
3660
|
updateConnection({ status: "authenticating" }, batchUpdates);
|
|
3638
|
-
effects.
|
|
3661
|
+
effects.authenticateAndConnect(auth, createWebSocket);
|
|
3639
3662
|
}
|
|
3640
3663
|
function updatePresence(patch, options2) {
|
|
3641
3664
|
const oldValues = {};
|
|
@@ -3678,11 +3701,11 @@ function createRoom(options, config) {
|
|
|
3678
3701
|
function isStorageReadOnly(scopes) {
|
|
3679
3702
|
return scopes.includes("room:read" /* Read */) && scopes.includes("room:presence:write" /* PresenceWrite */) && !scopes.includes("room:write" /* Write */);
|
|
3680
3703
|
}
|
|
3681
|
-
function
|
|
3682
|
-
socket.addEventListener("message",
|
|
3683
|
-
socket.addEventListener("open",
|
|
3684
|
-
socket.addEventListener("close",
|
|
3685
|
-
socket.addEventListener("error",
|
|
3704
|
+
function handleAuthSuccess(token, socket) {
|
|
3705
|
+
socket.addEventListener("message", handleRawSocketMessage);
|
|
3706
|
+
socket.addEventListener("open", handleSocketOpen);
|
|
3707
|
+
socket.addEventListener("close", handleExplicitClose);
|
|
3708
|
+
socket.addEventListener("error", handleSocketError);
|
|
3686
3709
|
updateConnection(
|
|
3687
3710
|
{
|
|
3688
3711
|
status: "connecting",
|
|
@@ -3706,8 +3729,8 @@ function createRoom(options, config) {
|
|
|
3706
3729
|
clearTimeout(context.timers.reconnect);
|
|
3707
3730
|
context.timers.reconnect = effects.scheduleReconnect(getRetryDelay());
|
|
3708
3731
|
}
|
|
3709
|
-
function
|
|
3710
|
-
if (
|
|
3732
|
+
function handleWindowGotFocus() {
|
|
3733
|
+
if (context.connection.current.status === "open") {
|
|
3711
3734
|
log("Heartbeat after visibility change");
|
|
3712
3735
|
heartbeat();
|
|
3713
3736
|
}
|
|
@@ -3761,7 +3784,7 @@ function createRoom(options, config) {
|
|
|
3761
3784
|
}
|
|
3762
3785
|
return { type: "reset" };
|
|
3763
3786
|
}
|
|
3764
|
-
function
|
|
3787
|
+
function handleNavigatorBackOnline() {
|
|
3765
3788
|
if (context.connection.current.status === "unavailable") {
|
|
3766
3789
|
log("Try to reconnect after connectivity change");
|
|
3767
3790
|
reconnect();
|
|
@@ -3824,11 +3847,17 @@ function createRoom(options, config) {
|
|
|
3824
3847
|
notify(result.updates, batchedUpdatesWrapper);
|
|
3825
3848
|
effects.send(messages);
|
|
3826
3849
|
}
|
|
3827
|
-
function
|
|
3850
|
+
function handleRawSocketMessage(event) {
|
|
3828
3851
|
if (event.data === "pong") {
|
|
3829
|
-
|
|
3830
|
-
|
|
3852
|
+
transition({ type: "RECEIVE_PONG" });
|
|
3853
|
+
} else {
|
|
3854
|
+
handleServerMessage(event);
|
|
3831
3855
|
}
|
|
3856
|
+
}
|
|
3857
|
+
function handlePong() {
|
|
3858
|
+
clearTimeout(context.timers.pongTimeout);
|
|
3859
|
+
}
|
|
3860
|
+
function handleServerMessage(event) {
|
|
3832
3861
|
if (typeof event.data !== "string") {
|
|
3833
3862
|
return;
|
|
3834
3863
|
}
|
|
@@ -3929,7 +3958,7 @@ ${Array.from(traces).join("\n\n")}`
|
|
|
3929
3958
|
notify(updates, doNotBatchUpdates);
|
|
3930
3959
|
});
|
|
3931
3960
|
}
|
|
3932
|
-
function
|
|
3961
|
+
function handleExplicitClose(event) {
|
|
3933
3962
|
context.socket = null;
|
|
3934
3963
|
clearTimeout(context.timers.flush);
|
|
3935
3964
|
clearTimeout(context.timers.reconnect);
|
|
@@ -3980,9 +4009,9 @@ ${Array.from(traces).join("\n\n")}`
|
|
|
3980
4009
|
}
|
|
3981
4010
|
return BACKOFF_RETRY_DELAYS[context.numRetries < BACKOFF_RETRY_DELAYS.length ? context.numRetries : BACKOFF_RETRY_DELAYS.length - 1];
|
|
3982
4011
|
}
|
|
3983
|
-
function
|
|
4012
|
+
function handleSocketError() {
|
|
3984
4013
|
}
|
|
3985
|
-
function
|
|
4014
|
+
function handleSocketOpen() {
|
|
3986
4015
|
clearInterval(context.timers.heartbeat);
|
|
3987
4016
|
context.timers.heartbeat = effects.startHeartbeatInterval();
|
|
3988
4017
|
if (context.connection.current.status === "connecting") {
|
|
@@ -4025,12 +4054,12 @@ ${Array.from(traces).join("\n\n")}`
|
|
|
4025
4054
|
log("Pong timeout. Trying to reconnect.");
|
|
4026
4055
|
reconnect();
|
|
4027
4056
|
}
|
|
4028
|
-
function
|
|
4057
|
+
function handleDisconnect() {
|
|
4029
4058
|
if (context.socket) {
|
|
4030
|
-
context.socket.removeEventListener("open",
|
|
4031
|
-
context.socket.removeEventListener("message",
|
|
4032
|
-
context.socket.removeEventListener("close",
|
|
4033
|
-
context.socket.removeEventListener("error",
|
|
4059
|
+
context.socket.removeEventListener("open", handleSocketOpen);
|
|
4060
|
+
context.socket.removeEventListener("message", handleRawSocketMessage);
|
|
4061
|
+
context.socket.removeEventListener("close", handleExplicitClose);
|
|
4062
|
+
context.socket.removeEventListener("error", handleSocketError);
|
|
4034
4063
|
context.socket.close();
|
|
4035
4064
|
context.socket = null;
|
|
4036
4065
|
}
|
|
@@ -4049,10 +4078,10 @@ ${Array.from(traces).join("\n\n")}`
|
|
|
4049
4078
|
}
|
|
4050
4079
|
function reconnect() {
|
|
4051
4080
|
if (context.socket) {
|
|
4052
|
-
context.socket.removeEventListener("open",
|
|
4053
|
-
context.socket.removeEventListener("message",
|
|
4054
|
-
context.socket.removeEventListener("close",
|
|
4055
|
-
context.socket.removeEventListener("error",
|
|
4081
|
+
context.socket.removeEventListener("open", handleSocketOpen);
|
|
4082
|
+
context.socket.removeEventListener("message", handleRawSocketMessage);
|
|
4083
|
+
context.socket.removeEventListener("close", handleExplicitClose);
|
|
4084
|
+
context.socket.removeEventListener("error", handleSocketError);
|
|
4056
4085
|
context.socket.close();
|
|
4057
4086
|
context.socket = null;
|
|
4058
4087
|
}
|
|
@@ -4061,7 +4090,7 @@ ${Array.from(traces).join("\n\n")}`
|
|
|
4061
4090
|
clearInterval(context.timers.heartbeat);
|
|
4062
4091
|
clearTimeout(context.timers.pongTimeout);
|
|
4063
4092
|
updateConnection({ status: "unavailable" }, batchUpdates);
|
|
4064
|
-
|
|
4093
|
+
handleConnect();
|
|
4065
4094
|
}
|
|
4066
4095
|
function tryFlushing() {
|
|
4067
4096
|
const storageOps = context.buffer.storageOperations;
|
|
@@ -4091,7 +4120,8 @@ ${Array.from(traces).join("\n\n")}`
|
|
|
4091
4120
|
};
|
|
4092
4121
|
} else {
|
|
4093
4122
|
clearTimeout(context.timers.flush);
|
|
4094
|
-
context.timers.flush =
|
|
4123
|
+
context.timers.flush = setTimeout(
|
|
4124
|
+
tryFlushing,
|
|
4095
4125
|
config.throttleDelay - elapsedMillis
|
|
4096
4126
|
);
|
|
4097
4127
|
}
|
|
@@ -4264,14 +4294,11 @@ ${Array.from(traces).join("\n\n")}`
|
|
|
4264
4294
|
_addToRealUndoStack(historyOps, batchUpdates);
|
|
4265
4295
|
}
|
|
4266
4296
|
}
|
|
4267
|
-
function
|
|
4297
|
+
function handleImplicitClose() {
|
|
4268
4298
|
if (context.socket) {
|
|
4269
4299
|
context.socket = null;
|
|
4270
4300
|
}
|
|
4271
4301
|
}
|
|
4272
|
-
function simulateSendCloseEvent(event) {
|
|
4273
|
-
onClose(event);
|
|
4274
|
-
}
|
|
4275
4302
|
function getStorageStatus() {
|
|
4276
4303
|
if (_getInitialStatePromise === null) {
|
|
4277
4304
|
return "not-loaded";
|
|
@@ -4304,6 +4331,28 @@ ${Array.from(traces).join("\n\n")}`
|
|
|
4304
4331
|
storageDidLoad: eventHub.storageDidLoad.observable,
|
|
4305
4332
|
storageStatus: eventHub.storageStatus.observable
|
|
4306
4333
|
};
|
|
4334
|
+
function transition(event) {
|
|
4335
|
+
switch (event.type) {
|
|
4336
|
+
case "CONNECT":
|
|
4337
|
+
return handleConnect();
|
|
4338
|
+
case "DISCONNECT":
|
|
4339
|
+
return handleDisconnect();
|
|
4340
|
+
case "RECEIVE_PONG":
|
|
4341
|
+
return handlePong();
|
|
4342
|
+
case "AUTH_SUCCESS":
|
|
4343
|
+
return handleAuthSuccess(event.token, event.socket);
|
|
4344
|
+
case "WINDOW_GOT_FOCUS":
|
|
4345
|
+
return handleWindowGotFocus();
|
|
4346
|
+
case "NAVIGATOR_ONLINE":
|
|
4347
|
+
return handleNavigatorBackOnline();
|
|
4348
|
+
case "IMPLICIT_CLOSE":
|
|
4349
|
+
return handleImplicitClose();
|
|
4350
|
+
case "EXPLICIT_CLOSE":
|
|
4351
|
+
return handleExplicitClose(event.closeEvent);
|
|
4352
|
+
default:
|
|
4353
|
+
return assertNever(event, "Invalid event");
|
|
4354
|
+
}
|
|
4355
|
+
}
|
|
4307
4356
|
return {
|
|
4308
4357
|
/* NOTE: Exposing __internal here only to allow testing implementation details in unit tests */
|
|
4309
4358
|
__internal: {
|
|
@@ -4315,20 +4364,36 @@ ${Array.from(traces).join("\n\n")}`
|
|
|
4315
4364
|
return context.numRetries;
|
|
4316
4365
|
},
|
|
4317
4366
|
// prettier-ignore
|
|
4318
|
-
|
|
4319
|
-
|
|
4320
|
-
|
|
4321
|
-
|
|
4322
|
-
|
|
4323
|
-
|
|
4324
|
-
|
|
4325
|
-
|
|
4326
|
-
getItemsCount: () => context.nodes.size,
|
|
4327
|
-
connect,
|
|
4328
|
-
disconnect,
|
|
4367
|
+
get undoStack() {
|
|
4368
|
+
return context.undoStack;
|
|
4369
|
+
},
|
|
4370
|
+
// prettier-ignore
|
|
4371
|
+
get nodeCount() {
|
|
4372
|
+
return context.nodes.size;
|
|
4373
|
+
},
|
|
4374
|
+
// prettier-ignore
|
|
4329
4375
|
// Support for the Liveblocks browser extension
|
|
4330
4376
|
getSelf_forDevTools: () => selfAsTreeNode.current,
|
|
4331
|
-
getOthers_forDevTools: () => others_forDevTools.current
|
|
4377
|
+
getOthers_forDevTools: () => others_forDevTools.current,
|
|
4378
|
+
// prettier-ignore
|
|
4379
|
+
send: {
|
|
4380
|
+
explicitClose: (closeEvent) => transition({ type: "EXPLICIT_CLOSE", closeEvent }),
|
|
4381
|
+
implicitClose: () => transition({ type: "IMPLICIT_CLOSE" }),
|
|
4382
|
+
authSuccess: (token, socket) => transition({ type: "AUTH_SUCCESS", token, socket }),
|
|
4383
|
+
navigatorOnline: () => transition({ type: "NAVIGATOR_ONLINE" }),
|
|
4384
|
+
windowGotFocus: () => transition({ type: "WINDOW_GOT_FOCUS" }),
|
|
4385
|
+
pong: () => transition({ type: "RECEIVE_PONG" }),
|
|
4386
|
+
connect: () => transition({ type: "CONNECT" }),
|
|
4387
|
+
disconnect: () => transition({ type: "DISCONNECT" }),
|
|
4388
|
+
/**
|
|
4389
|
+
* This one looks differently from the rest, because receiving messages
|
|
4390
|
+
* is handled orthorgonally from all other possible events above,
|
|
4391
|
+
* because it does not matter what the connectivity state of the
|
|
4392
|
+
* machine is, so there won't be an explicit state machine transition
|
|
4393
|
+
* needed for this event.
|
|
4394
|
+
*/
|
|
4395
|
+
incomingMessage: handleServerMessage
|
|
4396
|
+
}
|
|
4332
4397
|
},
|
|
4333
4398
|
id: config.roomId,
|
|
4334
4399
|
subscribe: makeClassicSubscribeFn(events),
|
|
@@ -4404,10 +4469,6 @@ function makeClassicSubscribeFn(events) {
|
|
|
4404
4469
|
return events.connection.subscribe(
|
|
4405
4470
|
callback
|
|
4406
4471
|
);
|
|
4407
|
-
case "storage":
|
|
4408
|
-
return events.storage.subscribe(
|
|
4409
|
-
callback
|
|
4410
|
-
);
|
|
4411
4472
|
case "history":
|
|
4412
4473
|
return events.history.subscribe(callback);
|
|
4413
4474
|
case "storage-status":
|
|
@@ -4456,34 +4517,49 @@ function prepareCreateWebSocket(liveblocksServer, WebSocketPolyfill) {
|
|
|
4456
4517
|
);
|
|
4457
4518
|
}
|
|
4458
4519
|
const ws = WebSocketPolyfill || WebSocket;
|
|
4459
|
-
return (
|
|
4520
|
+
return (richToken) => {
|
|
4521
|
+
const token = richToken.raw;
|
|
4460
4522
|
return new ws(
|
|
4461
4523
|
`${liveblocksServer}/?token=${token}&version=${// prettier-ignore
|
|
4462
4524
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
4463
4525
|
// @ts-ignore (__PACKAGE_VERSION__ will be injected by the build script)
|
|
4464
4526
|
true ? (
|
|
4465
4527
|
/* istanbul ignore next */
|
|
4466
|
-
"1.0.
|
|
4528
|
+
"1.0.10"
|
|
4467
4529
|
) : "dev"}`
|
|
4468
4530
|
);
|
|
4469
4531
|
};
|
|
4470
4532
|
}
|
|
4471
|
-
function
|
|
4533
|
+
function httpSend(message, token, endpoint, fetchPolyfill) {
|
|
4534
|
+
return __async(this, null, function* () {
|
|
4535
|
+
const fetcher = fetchPolyfill || /* istanbul ignore next */
|
|
4536
|
+
fetch;
|
|
4537
|
+
return fetcher(endpoint, {
|
|
4538
|
+
method: "POST",
|
|
4539
|
+
headers: {
|
|
4540
|
+
"Content-Type": "application/json",
|
|
4541
|
+
Authorization: `Bearer ${token}`
|
|
4542
|
+
},
|
|
4543
|
+
body: message
|
|
4544
|
+
});
|
|
4545
|
+
});
|
|
4546
|
+
}
|
|
4547
|
+
function prepareAuthEndpoint(roomId, authentication, fetchPolyfill) {
|
|
4472
4548
|
if (authentication.type === "public") {
|
|
4473
4549
|
if (typeof window === "undefined" && fetchPolyfill === void 0) {
|
|
4474
4550
|
throw new Error(
|
|
4475
4551
|
"To use Liveblocks client in a non-dom environment with a publicApiKey, you need to provide a fetch polyfill."
|
|
4476
4552
|
);
|
|
4477
4553
|
}
|
|
4478
|
-
return (
|
|
4554
|
+
return () => fetchAuthEndpoint(
|
|
4479
4555
|
fetchPolyfill || /* istanbul ignore next */
|
|
4480
4556
|
fetch,
|
|
4481
4557
|
authentication.url,
|
|
4482
4558
|
{
|
|
4483
|
-
room,
|
|
4559
|
+
room: roomId,
|
|
4484
4560
|
publicApiKey: authentication.publicApiKey
|
|
4485
4561
|
}
|
|
4486
|
-
);
|
|
4562
|
+
).then(({ token }) => parseRoomAuthToken(token));
|
|
4487
4563
|
}
|
|
4488
4564
|
if (authentication.type === "private") {
|
|
4489
4565
|
if (typeof window === "undefined" && fetchPolyfill === void 0) {
|
|
@@ -4491,19 +4567,19 @@ function prepareAuthEndpoint(authentication, fetchPolyfill) {
|
|
|
4491
4567
|
"To use Liveblocks client in a non-dom environment with a url as auth endpoint, you need to provide a fetch polyfill."
|
|
4492
4568
|
);
|
|
4493
4569
|
}
|
|
4494
|
-
return (
|
|
4495
|
-
room
|
|
4496
|
-
});
|
|
4570
|
+
return () => fetchAuthEndpoint(fetchPolyfill || fetch, authentication.url, {
|
|
4571
|
+
room: roomId
|
|
4572
|
+
}).then(({ token }) => parseRoomAuthToken(token));
|
|
4497
4573
|
}
|
|
4498
4574
|
if (authentication.type === "custom") {
|
|
4499
|
-
return (
|
|
4500
|
-
const response = yield authentication.callback(
|
|
4575
|
+
return () => __async(this, null, function* () {
|
|
4576
|
+
const response = yield authentication.callback(roomId);
|
|
4501
4577
|
if (!response || !response.token) {
|
|
4502
4578
|
throw new Error(
|
|
4503
4579
|
'Authentication error. We expect the authentication callback to return a token, but it does not. Hint: the return value should look like: { token: "..." }'
|
|
4504
4580
|
);
|
|
4505
4581
|
}
|
|
4506
|
-
return response;
|
|
4582
|
+
return parseRoomAuthToken(response.token);
|
|
4507
4583
|
});
|
|
4508
4584
|
}
|
|
4509
4585
|
throw new Error("Internal error. Unexpected authentication type");
|
|
@@ -4588,7 +4664,12 @@ function createClient(options) {
|
|
|
4588
4664
|
polyfills: clientOptions.polyfills,
|
|
4589
4665
|
unstable_batchedUpdates: options2 == null ? void 0 : options2.unstable_batchedUpdates,
|
|
4590
4666
|
liveblocksServer: getServerFromClientOptions(clientOptions),
|
|
4591
|
-
authentication: prepareAuthentication(clientOptions, roomId)
|
|
4667
|
+
authentication: prepareAuthentication(clientOptions, roomId),
|
|
4668
|
+
httpSendEndpoint: buildLiveblocksHttpSendEndpoint(
|
|
4669
|
+
clientOptions,
|
|
4670
|
+
roomId
|
|
4671
|
+
),
|
|
4672
|
+
unstable_fallbackToHTTP: !!clientOptions.unstable_fallbackToHTTP
|
|
4592
4673
|
}
|
|
4593
4674
|
);
|
|
4594
4675
|
rooms.set(roomId, newRoom);
|
|
@@ -4604,7 +4685,7 @@ function createClient(options) {
|
|
|
4604
4685
|
}
|
|
4605
4686
|
global.atob = clientOptions.polyfills.atob;
|
|
4606
4687
|
}
|
|
4607
|
-
newRoom.__internal.connect();
|
|
4688
|
+
newRoom.__internal.send.connect();
|
|
4608
4689
|
}
|
|
4609
4690
|
return newRoom;
|
|
4610
4691
|
}
|
|
@@ -4612,7 +4693,7 @@ function createClient(options) {
|
|
|
4612
4693
|
unlinkDevTools(roomId);
|
|
4613
4694
|
const room = rooms.get(roomId);
|
|
4614
4695
|
if (room !== void 0) {
|
|
4615
|
-
room.__internal.disconnect();
|
|
4696
|
+
room.__internal.send.disconnect();
|
|
4616
4697
|
rooms.delete(roomId);
|
|
4617
4698
|
}
|
|
4618
4699
|
}
|
|
@@ -4620,14 +4701,16 @@ function createClient(options) {
|
|
|
4620
4701
|
typeof window.addEventListener !== "undefined") {
|
|
4621
4702
|
window.addEventListener("online", () => {
|
|
4622
4703
|
for (const [, room] of rooms) {
|
|
4623
|
-
room.__internal.
|
|
4704
|
+
room.__internal.send.navigatorOnline();
|
|
4624
4705
|
}
|
|
4625
4706
|
});
|
|
4626
4707
|
}
|
|
4627
4708
|
if (typeof document !== "undefined") {
|
|
4628
4709
|
document.addEventListener("visibilitychange", () => {
|
|
4629
|
-
|
|
4630
|
-
room
|
|
4710
|
+
if (document.visibilityState === "visible") {
|
|
4711
|
+
for (const [, room] of rooms) {
|
|
4712
|
+
room.__internal.send.windowGotFocus();
|
|
4713
|
+
}
|
|
4631
4714
|
}
|
|
4632
4715
|
});
|
|
4633
4716
|
}
|
|
@@ -4690,6 +4773,14 @@ function prepareAuthentication(clientOptions, roomId) {
|
|
|
4690
4773
|
"Invalid Liveblocks client options. For more information: https://liveblocks.io/docs/api-reference/liveblocks-client#createClient"
|
|
4691
4774
|
);
|
|
4692
4775
|
}
|
|
4776
|
+
function buildLiveblocksHttpSendEndpoint(options, roomId) {
|
|
4777
|
+
if (options.httpSendEndpoint) {
|
|
4778
|
+
return options.httpSendEndpoint.replace("{roomId}", roomId);
|
|
4779
|
+
}
|
|
4780
|
+
return `https://api.liveblocks.io/v2/rooms/${encodeURIComponent(
|
|
4781
|
+
roomId
|
|
4782
|
+
)}/send-message`;
|
|
4783
|
+
}
|
|
4693
4784
|
function buildLiveblocksPublicAuthorizeEndpoint(options, roomId) {
|
|
4694
4785
|
if (options.publicAuthorizeEndpoint) {
|
|
4695
4786
|
return options.publicAuthorizeEndpoint.replace("{roomId}", roomId);
|