@liveblocks/core 1.2.0-internal2 → 1.2.0-internal3

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
@@ -703,9 +703,16 @@ declare type User<TPresence extends JsonObject, TUserMeta extends BaseUserMeta>
703
703
  */
704
704
  readonly presence: TPresence;
705
705
  /**
706
- * False if the user can modify the room storage, true otherwise.
706
+ * @deprecated Use `!user.canWrite` instead.
707
+ * False if the user can mutate the Room’s Storage and/or YDoc, true if they
708
+ * can only read but not mutate it.
707
709
  */
708
710
  readonly isReadOnly: boolean;
711
+ /**
712
+ * True if the user can mutate the Room’s Storage and/or YDoc, false if they
713
+ * can only read but not mutate it.
714
+ */
715
+ readonly canWrite: boolean;
709
716
  };
710
717
 
711
718
  /**
@@ -1548,6 +1555,33 @@ declare enum ServerMsgCode {
1548
1555
  REJECT_STORAGE_OP = 299,
1549
1556
  UPDATE_YDOC = 300
1550
1557
  }
1558
+ /**
1559
+ * Traits are bitflags that are used at the protocol level to communicate what
1560
+ * traits a given User has. Traits may or may not map to official permissions.
1561
+ * Users cannot see each other's true permissions, but they can see each
1562
+ * other's traits.
1563
+ *
1564
+ * Traits are not a security feature, but should only be used to optimize the
1565
+ * app experience (e.g. to visually indicate that another connected user has no
1566
+ * write access to the document).
1567
+ */
1568
+ declare enum Traits {
1569
+ None = 0,
1570
+ /**
1571
+ * Whether the user has write access to Storage™ + YDoc.
1572
+ * Maps to the public `User.canWrite` property.
1573
+ */
1574
+ CanWriteDocument = 1,
1575
+ /**
1576
+ * Whether the user has access to Comments™.
1577
+ * Maps to the public `User.canComment` property.
1578
+ */
1579
+ CanWriteComments = 2,
1580
+ /**
1581
+ * Convenience accessor only, where all the bitflags are enabled.
1582
+ */
1583
+ All = 3
1584
+ }
1551
1585
  /**
1552
1586
  * Messages that can be sent from the server to the client.
1553
1587
  */
@@ -1622,9 +1656,10 @@ declare type UserJoinServerMsg<TUserMeta extends BaseUserMeta> = {
1622
1656
  */
1623
1657
  readonly info: TUserMeta["info"];
1624
1658
  /**
1625
- * Permissions that the user has in the Room.
1659
+ * Informs the client what Traits this (other) User has.
1660
+ * @since v1.2 (WS API v7)
1626
1661
  */
1627
- readonly scopes: string[];
1662
+ readonly traits: Traits;
1628
1663
  };
1629
1664
  /**
1630
1665
  * Sent by the WebSocket server and broadcasted to all clients to announce that
@@ -1665,9 +1700,19 @@ declare type BroadcastedEventServerMsg<TRoomEvent extends Json> = {
1665
1700
  */
1666
1701
  declare type RoomStateServerMsg<TUserMeta extends BaseUserMeta> = {
1667
1702
  readonly type: ServerMsgCode.ROOM_STATE;
1703
+ /**
1704
+ * Informs the client what their actor ID is going to be.
1705
+ * @since v1.2 (WS API v7)
1706
+ */
1707
+ readonly actor: number;
1708
+ /**
1709
+ * Informs the client what Traits the current User (self) has.
1710
+ * @since v1.2 (WS API v7)
1711
+ */
1712
+ readonly traits: Traits;
1668
1713
  readonly users: {
1669
- readonly [actor: number]: TUserMeta & {
1670
- scopes: string[];
1714
+ readonly [otherActor: number]: TUserMeta & {
1715
+ traits: Traits;
1671
1716
  };
1672
1717
  };
1673
1718
  };
@@ -1890,4 +1935,4 @@ declare type EnsureJson<T> = [
1890
1935
  [K in keyof T]: EnsureJson<T[K]>;
1891
1936
  };
1892
1937
 
1893
- export { AckOp, BaseAuthResult, BaseUserMeta, BroadcastEventClientMsg, BroadcastOptions, BroadcastedEventServerMsg, Client, ClientMsg, ClientMsgCode, CrdtType, CreateChildOp, CreateListOp, CreateMapOp, CreateObjectOp, CreateOp, CreateRegisterOp, CreateRootObjectOp, Delegates, DeleteCrdtOp, DeleteObjectKeyOp, DevToolsTreeNode as DevTools, protocol as DevToolsMsg, EnsureJson, FetchStorageClientMsg, FetchYDocClientMsg, History, IWebSocket, IWebSocketCloseEvent, IWebSocketEvent, IWebSocketInstance, IWebSocketMessageEvent, IdTuple, Immutable, InitialDocumentStateServerMsg, Json, JsonArray, JsonObject, JsonScalar, LegacyConnectionStatus, LiveList, LiveListUpdate, LiveMap, LiveMapUpdate, LiveNode, LiveObject, LiveObjectUpdate, LiveStructure, LostConnectionEvent, Lson, LsonObject, NodeMap, Op, OpCode, Others, ParentToChildNodeMap, PlainLson, PlainLsonFields, PlainLsonList, PlainLsonMap, PlainLsonObject, RejectedStorageOpServerMsg, Resolve, Room, RoomInitializers, RoomStateServerMsg, SerializedChild, SerializedCrdt, SerializedList, SerializedMap, SerializedObject, SerializedRegister, SerializedRootObject, ServerMsg, ServerMsgCode, SetParentKeyOp, Status, StorageStatus, StorageUpdate, ToImmutable, ToJson, UpdateObjectOp, UpdatePresenceClientMsg, UpdatePresenceServerMsg, UpdateStorageClientMsg, UpdateStorageServerMsg, UpdateYDocClientMsg, User, UserJoinServerMsg, UserLeftServerMsg, WebsocketCloseCodes, asArrayWithLegacyMethods, asPos, assert, assertNever, b64decode, createClient, deprecate, deprecateIf, errorIf, freeze, isChildCrdt, isJsonArray, isJsonObject, isJsonScalar, isPlainObject, isRootCrdt, legacy_patchImmutableObject, lsonToJson, makePosition, nn, patchLiveObjectKey, shallow, throwUsageError, toPlainLson, tryParseJson, withTimeout };
1938
+ export { AckOp, BaseAuthResult, BaseUserMeta, BroadcastEventClientMsg, BroadcastOptions, BroadcastedEventServerMsg, Client, ClientMsg, ClientMsgCode, CrdtType, CreateChildOp, CreateListOp, CreateMapOp, CreateObjectOp, CreateOp, CreateRegisterOp, CreateRootObjectOp, Delegates, DeleteCrdtOp, DeleteObjectKeyOp, DevToolsTreeNode as DevTools, protocol as DevToolsMsg, EnsureJson, FetchStorageClientMsg, FetchYDocClientMsg, History, IWebSocket, IWebSocketCloseEvent, IWebSocketEvent, IWebSocketInstance, IWebSocketMessageEvent, IdTuple, Immutable, InitialDocumentStateServerMsg, Json, JsonArray, JsonObject, JsonScalar, LegacyConnectionStatus, LiveList, LiveListUpdate, LiveMap, LiveMapUpdate, LiveNode, LiveObject, LiveObjectUpdate, LiveStructure, LostConnectionEvent, Lson, LsonObject, NodeMap, Op, OpCode, Others, ParentToChildNodeMap, PlainLson, PlainLsonFields, PlainLsonList, PlainLsonMap, PlainLsonObject, RejectedStorageOpServerMsg, Resolve, Room, RoomInitializers, RoomStateServerMsg, SerializedChild, SerializedCrdt, SerializedList, SerializedMap, SerializedObject, SerializedRegister, SerializedRootObject, ServerMsg, ServerMsgCode, SetParentKeyOp, Status, StorageStatus, StorageUpdate, ToImmutable, ToJson, Traits, UpdateObjectOp, UpdatePresenceClientMsg, UpdatePresenceServerMsg, UpdateStorageClientMsg, UpdateStorageServerMsg, UpdateYDocClientMsg, User, UserJoinServerMsg, UserLeftServerMsg, WebsocketCloseCodes, asArrayWithLegacyMethods, asPos, assert, assertNever, b64decode, createClient, deprecate, deprecateIf, errorIf, freeze, isChildCrdt, isJsonArray, isJsonObject, isJsonScalar, isPlainObject, isRootCrdt, legacy_patchImmutableObject, lsonToJson, makePosition, nn, patchLiveObjectKey, shallow, throwUsageError, toPlainLson, tryParseJson, withTimeout };
package/dist/index.js CHANGED
@@ -145,7 +145,7 @@ var onMessageFromPanel = eventSource.observable;
145
145
  // src/devtools/index.ts
146
146
  var VERSION = true ? (
147
147
  /* istanbul ignore next */
148
- "1.2.0-internal2"
148
+ "1.2.0-internal3"
149
149
  ) : "dev";
150
150
  var _devtoolsSetupHasRun = false;
151
151
  function setupDevTools(getAllRooms) {
@@ -4025,7 +4025,19 @@ var ImmutableRef = class {
4025
4025
 
4026
4026
  // src/refs/OthersRef.ts
4027
4027
  function makeUser(conn, presence) {
4028
- return freeze(compactObject(__spreadProps(__spreadValues({}, conn), { presence })));
4028
+ const { connectionId, id, info, traits } = conn;
4029
+ const canWrite = (traits & 1 /* CanWriteDocument */) === 1 /* CanWriteDocument */;
4030
+ return freeze(
4031
+ compactObject({
4032
+ connectionId,
4033
+ id,
4034
+ info,
4035
+ canWrite,
4036
+ isReadOnly: !canWrite,
4037
+ // Deprecated, kept for backward-compatibility
4038
+ presence
4039
+ })
4040
+ );
4029
4041
  }
4030
4042
  var OthersRef = class extends ImmutableRef {
4031
4043
  //
@@ -4084,12 +4096,12 @@ var OthersRef = class extends ImmutableRef {
4084
4096
  * Records a known connection. This records the connection ID and the
4085
4097
  * associated metadata.
4086
4098
  */
4087
- setConnection(connectionId, metaUserId, metaUserInfo, metaIsReadonly) {
4099
+ setConnection(connectionId, traits, metaUserId, metaUserInfo) {
4088
4100
  this._connections[connectionId] = freeze({
4089
- connectionId,
4090
4101
  id: metaUserId,
4091
4102
  info: metaUserInfo,
4092
- isReadOnly: metaIsReadonly
4103
+ connectionId,
4104
+ traits
4093
4105
  });
4094
4106
  if (this._presences[connectionId] !== void 0) {
4095
4107
  this._invalidateUser(connectionId);
@@ -4236,8 +4248,9 @@ function createRoom(options, config) {
4236
4248
  messages: [],
4237
4249
  storageOperations: []
4238
4250
  },
4239
- sessionInfo: new ValueRef(null),
4240
- me: new PatchableRef(initialPresence),
4251
+ staticSessionInfo: new ValueRef(null),
4252
+ dynamicSessionInfo: new ValueRef(null),
4253
+ myPresence: new PatchableRef(initialPresence),
4241
4254
  others: new OthersRef(),
4242
4255
  initialStorage,
4243
4256
  idFactory: null,
@@ -4261,12 +4274,9 @@ function createRoom(options, config) {
4261
4274
  var _a2;
4262
4275
  const token = (_a2 = managedSocket.token) == null ? void 0 : _a2.parsed;
4263
4276
  if (token !== void 0 && token !== lastToken) {
4264
- context.sessionInfo.set({
4277
+ context.staticSessionInfo.set({
4265
4278
  userInfo: token.info,
4266
- userId: token.id,
4267
- // NOTE: In the future, these fields will get assigned in the connection phase
4268
- actor: token.actor,
4269
- isReadOnly: isStorageReadOnly(token.scopes)
4279
+ userId: token.id
4270
4280
  });
4271
4281
  lastToken = token;
4272
4282
  }
@@ -4304,20 +4314,15 @@ function createRoom(options, config) {
4304
4314
  }
4305
4315
  }
4306
4316
  function onDidConnect() {
4307
- const sessionInfo = context.sessionInfo.current;
4308
- if (sessionInfo === null) {
4309
- throw new Error("Unexpected missing session info");
4310
- }
4311
4317
  context.buffer.me = {
4312
4318
  type: "full",
4313
4319
  data: (
4314
4320
  // Because context.me.current is a readonly object, we'll have to
4315
4321
  // make a copy here. Otherwise, type errors happen later when
4316
4322
  // "patching" my presence.
4317
- __spreadValues({}, context.me.current)
4323
+ __spreadValues({}, context.myPresence.current)
4318
4324
  )
4319
4325
  };
4320
- context.idFactory = makeIdFactory(sessionInfo.actor);
4321
4326
  if (_getStorage$ !== null) {
4322
4327
  refreshStorage({ flush: false });
4323
4328
  }
@@ -4382,8 +4387,17 @@ function createRoom(options, config) {
4382
4387
  }
4383
4388
  },
4384
4389
  assertStorageIsWritable: () => {
4385
- var _a2;
4386
- if ((_a2 = context.sessionInfo.current) == null ? void 0 : _a2.isReadOnly) {
4390
+ var _a2, _b2;
4391
+ const traits = (_b2 = (_a2 = context.dynamicSessionInfo.current) == null ? void 0 : _a2.traits) != null ? _b2 : (
4392
+ // XXX Double-check if this is the sane thing to do! Previously this is
4393
+ // how the context.sessionInfo?.isReadOnly check worked too. If the
4394
+ // isReadOnly property wasn't known yet, the client assumed write
4395
+ // access. Not sure if this will break anything if we flip it to
4396
+ // Traits.None.
4397
+ 3 /* All */
4398
+ );
4399
+ const canWrite = (traits & 1 /* CanWriteDocument */) === 1 /* CanWriteDocument */;
4400
+ if (!canWrite) {
4387
4401
  throw new Error(
4388
4402
  "Cannot write to storage with a read only user, please ensure the user has write permissions"
4389
4403
  );
@@ -4430,16 +4444,24 @@ function createRoom(options, config) {
4430
4444
  managedSocket.send(message);
4431
4445
  }
4432
4446
  const self = new DerivedRef(
4433
- context.sessionInfo,
4434
- context.me,
4435
- (info, me) => {
4436
- return info !== null ? {
4437
- connectionId: info.actor,
4438
- id: info.userId,
4439
- info: info.userInfo,
4440
- presence: me,
4441
- isReadOnly: info.isReadOnly
4442
- } : null;
4447
+ context.staticSessionInfo,
4448
+ context.dynamicSessionInfo,
4449
+ context.myPresence,
4450
+ (staticSession, dynamicSession, myPresence) => {
4451
+ if (staticSession === null || dynamicSession === null) {
4452
+ return null;
4453
+ } else {
4454
+ const canWrite = (dynamicSession.traits & 1 /* CanWriteDocument */) === 1 /* CanWriteDocument */;
4455
+ return {
4456
+ connectionId: dynamicSession.actor,
4457
+ id: staticSession.userId,
4458
+ info: staticSession.userInfo,
4459
+ presence: myPresence,
4460
+ canWrite,
4461
+ isReadOnly: !canWrite
4462
+ // Deprecated, kept for backward-compatibility
4463
+ };
4464
+ }
4443
4465
  }
4444
4466
  );
4445
4467
  const selfAsTreeNode = new DerivedRef(
@@ -4500,7 +4522,7 @@ function createRoom(options, config) {
4500
4522
  }
4501
4523
  }
4502
4524
  if (presence) {
4503
- eventHub.me.notify(context.me.current);
4525
+ eventHub.me.notify(context.myPresence.current);
4504
4526
  }
4505
4527
  if (storageUpdates.size > 0) {
4506
4528
  const updates = Array.from(storageUpdates.values());
@@ -4509,7 +4531,7 @@ function createRoom(options, config) {
4509
4531
  });
4510
4532
  }
4511
4533
  function getConnectionId() {
4512
- const info = context.sessionInfo.current;
4534
+ const info = context.dynamicSessionInfo.current;
4513
4535
  if (info) {
4514
4536
  return info.actor;
4515
4537
  }
@@ -4538,9 +4560,9 @@ function createRoom(options, config) {
4538
4560
  data: {}
4539
4561
  };
4540
4562
  for (const key in op.data) {
4541
- reverse.data[key] = context.me.current[key];
4563
+ reverse.data[key] = context.myPresence.current[key];
4542
4564
  }
4543
- context.me.patch(op.data);
4565
+ context.myPresence.patch(op.data);
4544
4566
  if (context.buffer.me === null) {
4545
4567
  context.buffer.me = { type: "partial", data: op.data };
4546
4568
  } else {
@@ -4648,9 +4670,9 @@ function createRoom(options, config) {
4648
4670
  continue;
4649
4671
  }
4650
4672
  context.buffer.me.data[key] = overrideValue;
4651
- oldValues[key] = context.me.current[key];
4673
+ oldValues[key] = context.myPresence.current[key];
4652
4674
  }
4653
- context.me.patch(patch);
4675
+ context.myPresence.patch(patch);
4654
4676
  if (context.activeBatch) {
4655
4677
  if (options2 == null ? void 0 : options2.addToHistory) {
4656
4678
  context.activeBatch.reverseOps.unshift({
@@ -4672,9 +4694,6 @@ function createRoom(options, config) {
4672
4694
  });
4673
4695
  }
4674
4696
  }
4675
- function isStorageReadOnly(scopes) {
4676
- return scopes.includes("room:read" /* Read */) && scopes.includes("room:presence:write" /* PresenceWrite */) && !scopes.includes("room:write" /* Write */);
4677
- }
4678
4697
  function onUpdatePresenceMessage(message) {
4679
4698
  if (message.targetActor !== void 0) {
4680
4699
  const oldUser = context.others.getUser(message.actor);
@@ -4706,6 +4725,11 @@ function createRoom(options, config) {
4706
4725
  return null;
4707
4726
  }
4708
4727
  function onRoomStateMessage(message) {
4728
+ context.dynamicSessionInfo.set({
4729
+ actor: message.actor,
4730
+ traits: message.traits
4731
+ });
4732
+ context.idFactory = makeIdFactory(message.actor);
4709
4733
  for (const connectionId in context.others._connections) {
4710
4734
  const user = message.users[connectionId];
4711
4735
  if (user === void 0) {
@@ -4717,9 +4741,9 @@ function createRoom(options, config) {
4717
4741
  const connectionId = Number(key);
4718
4742
  context.others.setConnection(
4719
4743
  connectionId,
4744
+ user.traits,
4720
4745
  user.id,
4721
- user.info,
4722
- isStorageReadOnly(user.scopes)
4746
+ user.info
4723
4747
  );
4724
4748
  }
4725
4749
  return { type: "reset" };
@@ -4738,13 +4762,13 @@ function createRoom(options, config) {
4738
4762
  function onUserJoinedMessage(message) {
4739
4763
  context.others.setConnection(
4740
4764
  message.actor,
4765
+ message.traits,
4741
4766
  message.id,
4742
- message.info,
4743
- isStorageReadOnly(message.scopes)
4767
+ message.info
4744
4768
  );
4745
4769
  context.buffer.messages.push({
4746
4770
  type: 100 /* UPDATE_PRESENCE */,
4747
- data: context.me.current,
4771
+ data: context.myPresence.current,
4748
4772
  targetActor: message.actor
4749
4773
  });
4750
4774
  flushNowOrSoon();
@@ -5197,10 +5221,10 @@ ${Array.from(traces).join("\n\n")}`
5197
5221
  // Core
5198
5222
  getStatus: () => managedSocket.getStatus(),
5199
5223
  getConnectionState: () => managedSocket.getLegacyStatus(),
5200
- isSelfAware: () => context.sessionInfo.current !== null,
5224
+ isSelfAware: () => context.staticSessionInfo.current !== null,
5201
5225
  getSelf: () => self.current,
5202
5226
  // Presence
5203
- getPresence: () => context.me.current,
5227
+ getPresence: () => context.myPresence.current,
5204
5228
  getOthers: () => context.others.current
5205
5229
  };
5206
5230
  }
@@ -5305,7 +5329,7 @@ function makeCreateSocketDelegateForRoom(liveblocksServer, WebSocketPolyfill) {
5305
5329
  // @ts-ignore (__PACKAGE_VERSION__ will be injected by the build script)
5306
5330
  true ? (
5307
5331
  /* istanbul ignore next */
5308
- "1.2.0-internal2"
5332
+ "1.2.0-internal3"
5309
5333
  ) : "dev"}`
5310
5334
  );
5311
5335
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@liveblocks/core",
3
- "version": "1.2.0-internal2",
3
+ "version": "1.2.0-internal3",
4
4
  "description": "Shared code and foundational internals for Liveblocks",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",