@liveblocks/client 0.14.0-beta.1 → 0.14.2

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.
@@ -28,6 +28,14 @@ export declare type LiveListUpdates<TItem = any> = {
28
28
  type: "LiveList";
29
29
  node: LiveList<TItem>;
30
30
  };
31
+ export declare type BroadcastOptions = {
32
+ /**
33
+ * Whether or not event is queued if the connection is currently closed.
34
+ *
35
+ * ❗ We are not sure if we want to support this option in the future so it might be deprecated to be replaced by something else
36
+ */
37
+ shouldQueueEventIfNotReady: boolean;
38
+ };
31
39
  export declare type StorageUpdate = LiveMapUpdates | LiveObjectUpdates | LiveListUpdates;
32
40
  export declare type StorageCallback = (updates: StorageUpdate[]) => void;
33
41
  export declare type Client = {
@@ -65,6 +73,10 @@ export interface Others<TPresence extends Presence = Presence> {
65
73
  * Number of other users in the room.
66
74
  */
67
75
  readonly count: number;
76
+ /**
77
+ * Returns a new Iterator object that contains the users.
78
+ */
79
+ [Symbol.iterator](): IterableIterator<User<TPresence>>;
68
80
  /**
69
81
  * Returns the array of connected users in room.
70
82
  */
@@ -109,6 +121,8 @@ export declare type AuthEndpoint = string | AuthEndpointCallback;
109
121
  */
110
122
  export declare type ClientOptions = {
111
123
  throttle?: number;
124
+ fetchPolyfill?: any;
125
+ WebSocketPolyfill?: any;
112
126
  } & ({
113
127
  publicApiKey: string;
114
128
  authEndpoint?: never;
@@ -119,6 +133,17 @@ export declare type ClientOptions = {
119
133
  export declare type AuthorizeResponse = {
120
134
  token: string;
121
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
+ };
122
147
  declare type ConnectionState = "closed" | "authenticating" | "unavailable" | "failed" | "open" | "connecting";
123
148
  export declare type Connection = {
124
149
  state: "closed" | "authenticating" | "unavailable" | "failed";
@@ -423,7 +448,7 @@ export declare type Room = {
423
448
  * }
424
449
  * });
425
450
  */
426
- broadcastEvent: (event: any) => void;
451
+ broadcastEvent: (event: any, options?: BroadcastOptions) => void;
427
452
  /**
428
453
  * Get the room's storage asynchronously.
429
454
  * The storage's root is a {@link LiveObject}.
@@ -8,6 +8,7 @@ export declare type ApplyResult = {
8
8
  export interface Doc {
9
9
  generateId: () => string;
10
10
  generateOpId: () => string;
11
+ getItem: (id: string) => AbstractCrdt | undefined;
11
12
  addItem: (id: string, item: AbstractCrdt) => void;
12
13
  deleteItem: (id: string) => void;
13
14
  dispatch: (ops: Op[], reverseOps: Op[], modified: AbstractCrdt[]) => void;
@@ -45,7 +46,7 @@ export declare abstract class AbstractCrdt {
45
46
  /**
46
47
  * INTERNAL
47
48
  */
48
- abstract _attachChild(id: string, key: string, crdt: AbstractCrdt, isLocal: boolean): ApplyResult;
49
+ abstract _attachChild(id: string, key: string, crdt: AbstractCrdt, opId: string, isLocal: boolean): ApplyResult;
49
50
  /**
50
51
  * INTERNAL
51
52
  */
@@ -25,7 +25,7 @@ export declare class LiveList<T> extends AbstractCrdt {
25
25
  /**
26
26
  * INTERNAL
27
27
  */
28
- _attachChild(id: string, key: string, child: AbstractCrdt, isLocal: boolean): ApplyResult;
28
+ _attachChild(id: string, key: string, child: AbstractCrdt, opId: string, isLocal: boolean): ApplyResult;
29
29
  /**
30
30
  * INTERNAL
31
31
  */
@@ -68,6 +68,7 @@ export declare class LiveList<T> extends AbstractCrdt {
68
68
  * @param index The index of the element to delete
69
69
  */
70
70
  delete(index: number): void;
71
+ clear(): void;
71
72
  /**
72
73
  * Returns an Array of all the elements in the LiveList.
73
74
  */
@@ -93,11 +93,14 @@ export class LiveList extends AbstractCrdt {
93
93
  /**
94
94
  * INTERNAL
95
95
  */
96
- _attachChild(id, key, child, isLocal) {
96
+ _attachChild(id, key, child, opId, isLocal) {
97
97
  var _a;
98
98
  if (this._doc == null) {
99
99
  throw new Error("Can't attach child if doc is not present");
100
100
  }
101
+ if (this._doc.getItem(id) !== undefined) {
102
+ return { modified: false };
103
+ }
101
104
  child._attach(id, this._doc);
102
105
  child._setParentLink(this, key);
103
106
  const index = __classPrivateFieldGet(this, _LiveList_items, "f").findIndex((entry) => entry[1] === key);
@@ -280,6 +283,28 @@ export class LiveList extends AbstractCrdt {
280
283
  }
281
284
  }
282
285
  }
286
+ clear() {
287
+ if (this._doc) {
288
+ let ops = [];
289
+ let reverseOps = [];
290
+ for (const item of __classPrivateFieldGet(this, _LiveList_items, "f")) {
291
+ item[0]._detach();
292
+ const childId = item[0]._id;
293
+ if (childId) {
294
+ ops.push({ id: childId, type: OpType.DeleteCrdt });
295
+ reverseOps.push(...item[0]._serialize(this._id, item[1]));
296
+ }
297
+ }
298
+ __classPrivateFieldSet(this, _LiveList_items, [], "f");
299
+ this._doc.dispatch(ops, reverseOps, [this]);
300
+ }
301
+ else {
302
+ for (const item of __classPrivateFieldGet(this, _LiveList_items, "f")) {
303
+ item[0]._detach();
304
+ }
305
+ __classPrivateFieldSet(this, _LiveList_items, [], "f");
306
+ }
307
+ }
283
308
  /**
284
309
  * Returns an Array of all the elements in the LiveList.
285
310
  */
@@ -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)) {
@@ -281,7 +303,6 @@ export class LiveObject extends AbstractCrdt {
281
303
  data: {},
282
304
  };
283
305
  for (const key in overrides) {
284
- __classPrivateFieldGet(this, _LiveObject_propToLastUpdate, "f").set(key, opId);
285
306
  const oldValue = __classPrivateFieldGet(this, _LiveObject_map, "f").get(key);
286
307
  if (oldValue instanceof AbstractCrdt) {
287
308
  reverseOps.push(...oldValue._serialize(this._id, key));
@@ -297,10 +318,16 @@ export class LiveObject extends AbstractCrdt {
297
318
  if (newValue instanceof AbstractCrdt) {
298
319
  newValue._setParentLink(this, key);
299
320
  newValue._attach(this._doc.generateId(), this._doc);
300
- ops.push(...newValue._serialize(this._id, key, this._doc));
321
+ const newAttachChildOps = newValue._serialize(this._id, key, this._doc);
322
+ const createCrdtOp = newAttachChildOps.find((op) => op.parentId === this._id);
323
+ if (createCrdtOp) {
324
+ __classPrivateFieldGet(this, _LiveObject_propToLastUpdate, "f").set(key, createCrdtOp.opId);
325
+ }
326
+ ops.push(...newAttachChildOps);
301
327
  }
302
328
  else {
303
329
  updatedProps[key] = newValue;
330
+ __classPrivateFieldGet(this, _LiveObject_propToLastUpdate, "f").set(key, opId);
304
331
  }
305
332
  __classPrivateFieldGet(this, _LiveObject_map, "f").set(key, newValue);
306
333
  }
@@ -374,6 +401,11 @@ _LiveObject_map = new WeakMap(), _LiveObject_propToLastUpdate = new WeakMap(), _
374
401
  if (__classPrivateFieldGet(this, _LiveObject_map, "f").has(key) === false) {
375
402
  return { modified: false };
376
403
  }
404
+ // If a local operation exists on the same key
405
+ // prevent flickering by not applying delete op.
406
+ if (__classPrivateFieldGet(this, _LiveObject_propToLastUpdate, "f").get(key) !== undefined) {
407
+ return { modified: false };
408
+ }
377
409
  const oldValue = __classPrivateFieldGet(this, _LiveObject_map, "f").get(key);
378
410
  let reverse = [];
379
411
  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): void;
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
+ }
@@ -1,5 +1,5 @@
1
1
  export { LiveObject } from "./LiveObject";
2
2
  export { LiveMap } from "./LiveMap";
3
3
  export { LiveList } from "./LiveList";
4
- export type { Others, Presence, Room, Client, User } from "./types";
4
+ export type { Others, Presence, Room, Client, User, BroadcastOptions, } from "./types";
5
5
  export { createClient } from "./client";
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 } 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;
@@ -126,7 +126,7 @@ export declare function makeStateMachine(state: State, context: Context, mockedE
126
126
  updatePresence: <T_4 extends Presence>(overrides: Partial<T_4>, options?: {
127
127
  addToHistory: boolean;
128
128
  } | undefined) => void;
129
- broadcastEvent: (event: any) => void;
129
+ broadcastEvent: (event: any, options?: BroadcastOptions) => void;
130
130
  batch: (callback: () => void) => void;
131
131
  undo: () => void;
132
132
  redo: () => void;
@@ -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 {};