@liveblocks/core 0.18.3-test2 → 0.18.4

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 CHANGED
@@ -367,6 +367,10 @@ declare type BaseUserMeta = {
367
367
  * Additional user information that has been set in the authentication endpoint.
368
368
  */
369
369
  info?: Json;
370
+ /**
371
+ * Permissions that the user has in the room.
372
+ */
373
+ scopes: string[];
370
374
  };
371
375
 
372
376
  declare enum OpCode {
@@ -639,6 +643,10 @@ declare type UserJoinServerMsg<TUserMeta extends BaseUserMeta> = {
639
643
  * endpoint.
640
644
  */
641
645
  info: TUserMeta["info"];
646
+ /**
647
+ * Permissions that the user has in the Room.
648
+ */
649
+ scopes: string[];
642
650
  };
643
651
  /**
644
652
  * Sent by the WebSocket server and broadcasted to all clients to announce that
@@ -858,6 +866,10 @@ declare type User<TPresence extends JsonObject, TUserMeta extends BaseUserMeta>
858
866
  * The user presence.
859
867
  */
860
868
  readonly presence: TPresence;
869
+ /**
870
+ * False if the user can modify the room storage, true otherwise.
871
+ */
872
+ readonly isReadOnly: boolean;
861
873
  };
862
874
  declare type AuthEndpointCallback = (room: string) => Promise<{
863
875
  token: string;
@@ -899,11 +911,13 @@ declare type Connection = {
899
911
  id: number;
900
912
  userId?: string;
901
913
  userInfo?: Json;
914
+ isReadOnly: boolean;
902
915
  } | {
903
916
  state: "open";
904
917
  id: number;
905
918
  userId?: string;
906
919
  userInfo?: Json;
920
+ isReadOnly: boolean;
907
921
  } | {
908
922
  state: "unavailable";
909
923
  } | {
package/dist/index.js CHANGED
@@ -997,9 +997,13 @@ var LiveList = class extends AbstractCrdt {
997
997
  return this._items.length;
998
998
  }
999
999
  push(element) {
1000
+ var _a;
1001
+ (_a = this._pool) == null ? void 0 : _a.assertStorageIsWritable();
1000
1002
  return this.insert(element, this.length);
1001
1003
  }
1002
1004
  insert(element, index) {
1005
+ var _a;
1006
+ (_a = this._pool) == null ? void 0 : _a.assertStorageIsWritable();
1003
1007
  if (index < 0 || index > this._items.length) {
1004
1008
  throw new Error(
1005
1009
  `Cannot insert list item at index "${index}". index should be between 0 and ${this._items.length}`
@@ -1024,6 +1028,8 @@ var LiveList = class extends AbstractCrdt {
1024
1028
  }
1025
1029
  }
1026
1030
  move(index, targetIndex) {
1031
+ var _a;
1032
+ (_a = this._pool) == null ? void 0 : _a.assertStorageIsWritable();
1027
1033
  if (targetIndex < 0) {
1028
1034
  throw new Error("targetIndex cannot be less than 0");
1029
1035
  }
@@ -1077,6 +1083,8 @@ var LiveList = class extends AbstractCrdt {
1077
1083
  }
1078
1084
  }
1079
1085
  delete(index) {
1086
+ var _a;
1087
+ (_a = this._pool) == null ? void 0 : _a.assertStorageIsWritable();
1080
1088
  if (index < 0 || index >= this._items.length) {
1081
1089
  throw new Error(
1082
1090
  `Cannot delete list item at index "${index}". index should be between 0 and ${this._items.length - 1}`
@@ -1109,6 +1117,8 @@ var LiveList = class extends AbstractCrdt {
1109
1117
  }
1110
1118
  }
1111
1119
  clear() {
1120
+ var _a;
1121
+ (_a = this._pool) == null ? void 0 : _a.assertStorageIsWritable();
1112
1122
  if (this._pool) {
1113
1123
  const ops = [];
1114
1124
  const reverseOps = [];
@@ -1142,6 +1152,8 @@ var LiveList = class extends AbstractCrdt {
1142
1152
  }
1143
1153
  }
1144
1154
  set(index, item) {
1155
+ var _a;
1156
+ (_a = this._pool) == null ? void 0 : _a.assertStorageIsWritable();
1145
1157
  if (index < 0 || index >= this._items.length) {
1146
1158
  throw new Error(
1147
1159
  `Cannot set list item at index "${index}". index should be between 0 and ${this._items.length - 1}`
@@ -1446,6 +1458,8 @@ var LiveMap = class extends AbstractCrdt {
1446
1458
  return liveNodeToLson(value);
1447
1459
  }
1448
1460
  set(key, value) {
1461
+ var _a;
1462
+ (_a = this._pool) == null ? void 0 : _a.assertStorageIsWritable();
1449
1463
  const oldValue = this._map.get(key);
1450
1464
  if (oldValue) {
1451
1465
  oldValue._detach();
@@ -1479,6 +1493,8 @@ var LiveMap = class extends AbstractCrdt {
1479
1493
  return this._map.has(key);
1480
1494
  }
1481
1495
  delete(key) {
1496
+ var _a;
1497
+ (_a = this._pool) == null ? void 0 : _a.assertStorageIsWritable();
1482
1498
  const item = this._map.get(key);
1483
1499
  if (item === void 0) {
1484
1500
  return false;
@@ -1849,12 +1865,16 @@ var LiveObject = class extends AbstractCrdt {
1849
1865
  return fromEntries(this._map);
1850
1866
  }
1851
1867
  set(key, value) {
1868
+ var _a;
1869
+ (_a = this._pool) == null ? void 0 : _a.assertStorageIsWritable();
1852
1870
  this.update({ [key]: value });
1853
1871
  }
1854
1872
  get(key) {
1855
1873
  return this._map.get(key);
1856
1874
  }
1857
1875
  delete(key) {
1876
+ var _a;
1877
+ (_a = this._pool) == null ? void 0 : _a.assertStorageIsWritable();
1858
1878
  const keyAsString = key;
1859
1879
  const oldValue = this._map.get(keyAsString);
1860
1880
  if (oldValue === void 0) {
@@ -1903,6 +1923,8 @@ var LiveObject = class extends AbstractCrdt {
1903
1923
  );
1904
1924
  }
1905
1925
  update(patch) {
1926
+ var _a;
1927
+ (_a = this._pool) == null ? void 0 : _a.assertStorageIsWritable();
1906
1928
  if (this._pool === void 0 || this._id === void 0) {
1907
1929
  for (const key in patch) {
1908
1930
  const newValue = patch[key];
@@ -2545,11 +2567,12 @@ var OthersRef = class extends ImmutableRef {
2545
2567
  }
2546
2568
  this.invalidate();
2547
2569
  }
2548
- setConnection(connectionId, metaUserId, metaUserInfo) {
2570
+ setConnection(connectionId, metaUserId, metaUserInfo, metaIsReadonly) {
2549
2571
  this._connections[connectionId] = freeze({
2550
2572
  connectionId,
2551
2573
  id: metaUserId,
2552
- info: metaUserInfo
2574
+ info: metaUserInfo,
2575
+ isReadOnly: metaIsReadonly
2553
2576
  });
2554
2577
  if (this._presences[connectionId] !== void 0) {
2555
2578
  this._invalidateUser(connectionId);
@@ -2666,6 +2689,13 @@ function makeStateMachine(state, config, mockedEffects) {
2666
2689
  notify({ storageUpdates }, doNotBatchUpdates);
2667
2690
  });
2668
2691
  }
2692
+ },
2693
+ assertStorageIsWritable: () => {
2694
+ if (isConnectionSelfAware(state.connection.current) && state.connection.current.isReadOnly) {
2695
+ throw new Error(
2696
+ "Cannot write to storage with a read only user, please ensure the user has write permissions"
2697
+ );
2698
+ }
2669
2699
  }
2670
2700
  };
2671
2701
  const eventHub = {
@@ -2727,7 +2757,8 @@ function makeStateMachine(state, config, mockedEffects) {
2727
2757
  connectionId: conn.id,
2728
2758
  id: conn.userId,
2729
2759
  info: conn.userInfo,
2730
- presence: me
2760
+ presence: me,
2761
+ isReadOnly: conn.isReadOnly
2731
2762
  } : null
2732
2763
  );
2733
2764
  function createOrUpdateRootFromMessage(message, batchedUpdatesWrapper) {
@@ -3060,6 +3091,9 @@ function makeStateMachine(state, config, mockedEffects) {
3060
3091
  });
3061
3092
  }
3062
3093
  }
3094
+ function isReadOnly(scopes) {
3095
+ return scopes.includes("room:read" /* Read */) && scopes.includes("room:presence:write" /* PresenceWrite */) && !scopes.includes("room:write" /* Write */);
3096
+ }
3063
3097
  function authenticationSuccess(token, socket) {
3064
3098
  socket.addEventListener("message", onMessage);
3065
3099
  socket.addEventListener("open", onOpen);
@@ -3070,7 +3104,8 @@ function makeStateMachine(state, config, mockedEffects) {
3070
3104
  state: "connecting",
3071
3105
  id: token.actor,
3072
3106
  userInfo: token.info,
3073
- userId: token.id
3107
+ userId: token.id,
3108
+ isReadOnly: isReadOnly(token.scopes)
3074
3109
  },
3075
3110
  batchUpdates
3076
3111
  );
@@ -3126,7 +3161,12 @@ function makeStateMachine(state, config, mockedEffects) {
3126
3161
  for (const key in message.users) {
3127
3162
  const user = message.users[key];
3128
3163
  const connectionId = Number(key);
3129
- state.others.setConnection(connectionId, user.id, user.info);
3164
+ state.others.setConnection(
3165
+ connectionId,
3166
+ user.id,
3167
+ user.info,
3168
+ isReadOnly(user.scopes)
3169
+ );
3130
3170
  }
3131
3171
  return { type: "reset" };
3132
3172
  }
@@ -3142,7 +3182,12 @@ function makeStateMachine(state, config, mockedEffects) {
3142
3182
  });
3143
3183
  }
3144
3184
  function onUserJoinedMessage(message) {
3145
- state.others.setConnection(message.actor, message.id, message.info);
3185
+ state.others.setConnection(
3186
+ message.actor,
3187
+ message.id,
3188
+ message.info,
3189
+ isReadOnly(message.scopes)
3190
+ );
3146
3191
  state.buffer.messages.push({
3147
3192
  type: 100 /* UPDATE_PRESENCE */,
3148
3193
  data: state.me.current,
@@ -3556,7 +3601,7 @@ function makeStateMachine(state, config, mockedEffects) {
3556
3601
  if (state.activeBatch) {
3557
3602
  return callback();
3558
3603
  }
3559
- let rv = void 0;
3604
+ let returnValue = void 0;
3560
3605
  batchUpdates(() => {
3561
3606
  state.activeBatch = {
3562
3607
  ops: [],
@@ -3568,7 +3613,7 @@ function makeStateMachine(state, config, mockedEffects) {
3568
3613
  reverseOps: []
3569
3614
  };
3570
3615
  try {
3571
- rv = callback();
3616
+ returnValue = callback();
3572
3617
  } finally {
3573
3618
  const currentBatch = state.activeBatch;
3574
3619
  state.activeBatch = null;
@@ -3585,7 +3630,7 @@ function makeStateMachine(state, config, mockedEffects) {
3585
3630
  tryFlushing();
3586
3631
  }
3587
3632
  });
3588
- return rv;
3633
+ return returnValue;
3589
3634
  }
3590
3635
  function pauseHistory() {
3591
3636
  state.pausedHistory = [];
@@ -3748,7 +3793,7 @@ function prepareCreateWebSocket(liveblocksServer, WebSocketPolyfill) {
3748
3793
  const ws = WebSocketPolyfill || WebSocket;
3749
3794
  return (token) => {
3750
3795
  return new ws(
3751
- `${liveblocksServer}/?token=${token}&version=${true ? "0.18.3-test2" : "dev"}`
3796
+ `${liveblocksServer}/?token=${token}&version=${true ? "0.18.4" : "dev"}`
3752
3797
  );
3753
3798
  };
3754
3799
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@liveblocks/core",
3
- "version": "0.18.3-test2",
3
+ "version": "0.18.4",
4
4
  "description": "Shared code and foundational internals for Liveblocks",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",
@@ -19,23 +19,35 @@
19
19
  "url": "https://github.com/liveblocks/liveblocks/issues"
20
20
  },
21
21
  "scripts": {
22
- "dev": "tsup --watch --onSuccess ../../scripts/build.sh",
23
22
  "build": "tsup && ../../scripts/build.sh",
24
23
  "format": "eslint --fix src/ && prettier --write src/",
25
24
  "lint": "eslint src/",
26
- "test": "jest --silent --verbose",
27
- "test:watch": "jest --silent --verbose --watch",
25
+ "test": "jest --watch --silent --verbose --config=./jest.config.js",
26
+ "test-ci": "jest --silent --verbose",
28
27
  "test-e2e": "jest --silent --verbose --config=./jest.config.e2e.js"
29
28
  },
30
29
  "license": "Apache-2.0",
31
30
  "devDependencies": {
32
- "@liveblocks/eslint-config": "*",
33
- "@liveblocks/jest-config": "*",
31
+ "@types/jest": "^29.1.2",
32
+ "@types/node-fetch": "^2.6.1",
34
33
  "@types/ws": "^8.5.3",
34
+ "@typescript-eslint/eslint-plugin": "^5.26.0",
35
+ "@typescript-eslint/parser": "^5.26.0",
35
36
  "dotenv": "^16.0.3",
37
+ "eslint": "^8.12.0",
38
+ "eslint-plugin-import": "^2.26.0",
36
39
  "eslint-plugin-rulesdir": "^0.2.1",
40
+ "eslint-plugin-simple-import-sort": "^7.0.0",
37
41
  "fast-check": "^3.0.1",
38
- "msw": "^0.47.4",
42
+ "jest": "^29.1.2",
43
+ "jest-environment-jsdom": "^29.1.2",
44
+ "msw": "^0.39.1",
45
+ "node-fetch": "2.6.7",
46
+ "prettier": "^2.7.1",
47
+ "ts-jest": "^29.0.3",
48
+ "tsup": "^6.2.2",
49
+ "typescript": "^4.7.2",
50
+ "whatwg-fetch": "^3.6.2",
39
51
  "ws": "^8.5.0"
40
52
  },
41
53
  "repository": {