@automerge/automerge-repo-react-hooks 2.5.2-alpha.0 → 2.5.2-alpha.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/dist/index.js CHANGED
@@ -6681,9 +6681,228 @@ requireSha256();
6681
6681
 
6682
6682
  debug("automerge-repo:collectionsync");
6683
6683
 
6684
- const PRESENCE_MESSAGE_MARKER = "__presence";
6685
6684
  const DEFAULT_HEARTBEAT_INTERVAL_MS = 15_000;
6686
6685
  const DEFAULT_PEER_TTL_MS = 3 * DEFAULT_HEARTBEAT_INTERVAL_MS;
6686
+ const PRESENCE_MESSAGE_MARKER = "__presence";
6687
+
6688
+ function unique(items) {
6689
+ return Array.from(new Set(items));
6690
+ }
6691
+
6692
+ class PeerStateView {
6693
+ value;
6694
+ constructor(value) {
6695
+ this.value = value;
6696
+ }
6697
+ /**
6698
+ * Get all users.
6699
+ *
6700
+ * @returns Array of user presence {@link State}s
6701
+ */
6702
+ get users() {
6703
+ const userIds = unique(Object.values(this.value).map(peerState => peerState.userId));
6704
+ return userIds.map(u => this.getUserState(u));
6705
+ }
6706
+ /**
6707
+ * Get all devices.
6708
+ *
6709
+ * @returns Array of device presence {@link State}s
6710
+ */
6711
+ get devices() {
6712
+ const deviceIds = unique(Object.values(this.value).map(peerState => peerState.deviceId));
6713
+ return deviceIds.map(d => this.getDeviceState(d));
6714
+ }
6715
+ /**
6716
+ * Get all peers.
6717
+ *
6718
+ * @returns Array of peer presence {@link State}s
6719
+ */
6720
+ get peers() {
6721
+ return Object.values(this.value);
6722
+ }
6723
+ /**
6724
+ * Get all peer ids for this user.
6725
+ *
6726
+ * @param userId
6727
+ * @returns Array of peer ids for this user
6728
+ */
6729
+ getUserPeers(userId) {
6730
+ return Object.values(this.value)
6731
+ .filter(peerState => peerState.userId === userId)
6732
+ .map(peerState => peerState.peerId);
6733
+ }
6734
+ /**
6735
+ * Get all peers for this device.
6736
+ *
6737
+ * @param deviceId
6738
+ * @returns Array of peer ids for this device
6739
+ */
6740
+ getDevicePeers(deviceId) {
6741
+ return Object.values(this.value)
6742
+ .filter(peerState => peerState.deviceId === deviceId)
6743
+ .map(peerState => peerState.peerId);
6744
+ }
6745
+ /**
6746
+ * Return the most-recently-seen peer from this group.
6747
+ *
6748
+ * @param peers
6749
+ * @returns id of most recently seen peer
6750
+ */
6751
+ getLastSeenPeer(peers) {
6752
+ let freshestLastSeenAt;
6753
+ return peers.reduce((freshest, curr) => {
6754
+ const lastSeenAt = this.value[curr]?.lastActiveAt;
6755
+ if (!lastSeenAt) {
6756
+ return freshest;
6757
+ }
6758
+ if (!freshest || lastSeenAt > freshestLastSeenAt) {
6759
+ freshestLastSeenAt = lastSeenAt;
6760
+ return curr;
6761
+ }
6762
+ return freshest;
6763
+ }, undefined);
6764
+ }
6765
+ /**
6766
+ * Return the peer from this group that sent a state update most recently
6767
+ *
6768
+ * @param peers
6769
+ * @returns id of most recently seen peer
6770
+ */
6771
+ getLastActivePeer(peers) {
6772
+ let freshestLastActiveAt;
6773
+ return peers.reduce((freshest, curr) => {
6774
+ const lastActiveAt = this.value[curr]?.lastActiveAt;
6775
+ if (!lastActiveAt) {
6776
+ return freshest;
6777
+ }
6778
+ if (!freshest || lastActiveAt > freshestLastActiveAt) {
6779
+ freshestLastActiveAt = lastActiveAt;
6780
+ return curr;
6781
+ }
6782
+ return freshest;
6783
+ }, undefined);
6784
+ }
6785
+ /**
6786
+ * Get current ephemeral state value for this user's most-recently-active
6787
+ * peer.
6788
+ *
6789
+ * @param userId
6790
+ * @returns user's {@link State}
6791
+ */
6792
+ getUserState(userId) {
6793
+ const peers = this.getUserPeers(userId);
6794
+ if (!peers) {
6795
+ return undefined;
6796
+ }
6797
+ const peer = this.getLastActivePeer(peers);
6798
+ if (!peer) {
6799
+ return undefined;
6800
+ }
6801
+ return this.value[peer];
6802
+ }
6803
+ /**
6804
+ * Get current ephemeral state value for this device's most-recently-active
6805
+ * peer.
6806
+ *
6807
+ * @param deviceId
6808
+ * @returns device's {@link State}
6809
+ */
6810
+ getDeviceState(deviceId) {
6811
+ const peers = this.getDevicePeers(deviceId);
6812
+ if (!peers) {
6813
+ return undefined;
6814
+ }
6815
+ const peer = this.getLastActivePeer(peers);
6816
+ if (!peer) {
6817
+ return undefined;
6818
+ }
6819
+ return this.value[peer];
6820
+ }
6821
+ }
6822
+
6823
+ class PeerPresenceInfo {
6824
+ ttl;
6825
+ #peerStates = new PeerStateView({});
6826
+ /**
6827
+ * Build a new peer presence state.
6828
+ *
6829
+ * @param ttl in milliseconds - peers with no activity within this timeframe
6830
+ * are forgotten when {@link prune} is called.
6831
+ */
6832
+ constructor(ttl) {
6833
+ this.ttl = ttl;
6834
+ }
6835
+ has(peerId) {
6836
+ return peerId in this.#peerStates.value;
6837
+ }
6838
+ /**
6839
+ * Record that we've seen the given peer recently.
6840
+ *
6841
+ * @param peerId
6842
+ */
6843
+ markSeen(peerId) {
6844
+ this.#peerStates = new PeerStateView({
6845
+ ...this.#peerStates.value,
6846
+ [peerId]: {
6847
+ ...this.#peerStates.value[peerId],
6848
+ lastSeen: Date.now(),
6849
+ },
6850
+ });
6851
+ }
6852
+ /**
6853
+ * Record a state update for the given peer. Note that existing state is not
6854
+ * overwritten.
6855
+ *
6856
+ * @param peerId
6857
+ * @param value
6858
+ */
6859
+ update({ peerId, deviceId, userId, value, }) {
6860
+ const peerState = this.#peerStates.value[peerId];
6861
+ const existingState = peerState?.value ?? {};
6862
+ const now = Date.now();
6863
+ this.#peerStates = new PeerStateView({
6864
+ ...this.#peerStates.value,
6865
+ [peerId]: {
6866
+ peerId,
6867
+ deviceId,
6868
+ userId,
6869
+ lastActiveAt: now,
6870
+ lastUpdateAt: now,
6871
+ value: {
6872
+ ...existingState,
6873
+ ...value,
6874
+ },
6875
+ },
6876
+ });
6877
+ }
6878
+ /**
6879
+ * Forget the given peer.
6880
+ *
6881
+ * @param peerId
6882
+ */
6883
+ delete(peerId) {
6884
+ this.#peerStates = new PeerStateView(Object.fromEntries(Object.entries(this.#peerStates.value).filter(([existingId]) => {
6885
+ return existingId != peerId;
6886
+ })));
6887
+ }
6888
+ /**
6889
+ * Prune all peers that have not been seen since the configured ttl has
6890
+ * elapsed.
6891
+ */
6892
+ prune() {
6893
+ const threshold = Date.now() - this.ttl;
6894
+ this.#peerStates = new PeerStateView(Object.fromEntries(Object.entries(this.#peerStates).filter(([, state]) => {
6895
+ return state.lastActiveAt >= threshold;
6896
+ })));
6897
+ }
6898
+ /**
6899
+ * Get a snapshot of the current peer states
6900
+ */
6901
+ get states() {
6902
+ return this.#peerStates;
6903
+ }
6904
+ }
6905
+
6687
6906
  /**
6688
6907
  * Presence encapsulates ephemeral state communication for a specific doc
6689
6908
  * handle. It tracks caller-provided local state and broadcasts that state to
@@ -6717,6 +6936,7 @@ class Presence extends EventEmitter {
6717
6936
  super();
6718
6937
  this.#handle = handle;
6719
6938
  this.#peers = new PeerPresenceInfo(DEFAULT_PEER_TTL_MS);
6939
+ this.#localState = {};
6720
6940
  this.userId = userId;
6721
6941
  this.deviceId = deviceId;
6722
6942
  }
@@ -6743,35 +6963,24 @@ class Presence extends EventEmitter {
6743
6963
  }
6744
6964
  const message = envelope[PRESENCE_MESSAGE_MARKER];
6745
6965
  const { deviceId, userId } = message;
6746
- if (!this.#peers.view.has(peerId)) {
6966
+ if (!this.#peers.has(peerId)) {
6747
6967
  this.announce();
6748
6968
  }
6749
6969
  switch (message.type) {
6750
6970
  case "heartbeat":
6751
- this.#peers.markSeen(peerId, deviceId, userId);
6752
- this.emit("heartbeat", {
6753
- type: "heartbeat",
6754
- peerId,
6755
- deviceId,
6756
- userId,
6757
- });
6971
+ this.#peers.markSeen(peerId);
6972
+ this.emit("heartbeat", { type: "heartbeat", peerId });
6758
6973
  break;
6759
6974
  case "goodbye":
6760
6975
  this.#peers.delete(peerId);
6761
- this.emit("goodbye", {
6762
- type: "goodbye",
6763
- peerId,
6764
- deviceId,
6765
- userId,
6766
- });
6976
+ this.emit("goodbye", { type: "goodbye", peerId });
6767
6977
  break;
6768
6978
  case "update":
6769
6979
  this.#peers.update({
6770
6980
  peerId,
6771
6981
  deviceId,
6772
6982
  userId,
6773
- channel: message.channel,
6774
- value: message.value,
6983
+ value: { [message.channel]: message.value },
6775
6984
  });
6776
6985
  this.emit("update", {
6777
6986
  type: "update",
@@ -6783,14 +6992,11 @@ class Presence extends EventEmitter {
6783
6992
  });
6784
6993
  break;
6785
6994
  case "snapshot":
6786
- Object.entries(message.state).forEach(([channel, value]) => {
6787
- this.#peers.update({
6788
- peerId,
6789
- deviceId,
6790
- userId,
6791
- channel: channel,
6792
- value,
6793
- });
6995
+ this.#peers.update({
6996
+ peerId,
6997
+ deviceId,
6998
+ userId,
6999
+ value: message.state,
6794
7000
  });
6795
7001
  this.emit("snapshot", {
6796
7002
  type: "snapshot",
@@ -6810,7 +7016,7 @@ class Presence extends EventEmitter {
6810
7016
  * Return a view of current peer states.
6811
7017
  */
6812
7018
  getPeerStates() {
6813
- return this.#peers.view;
7019
+ return this.#peers.states;
6814
7020
  }
6815
7021
  /**
6816
7022
  * Return a view of current local state.
@@ -6829,7 +7035,7 @@ class Presence extends EventEmitter {
6829
7035
  this.#localState = Object.assign({}, this.#localState, {
6830
7036
  [channel]: value,
6831
7037
  });
6832
- this.broadcastChannelState(channel, value);
7038
+ this.broadcastChannelState(channel);
6833
7039
  }
6834
7040
  /**
6835
7041
  * Whether this Presence is currently active. See
@@ -6860,7 +7066,7 @@ class Presence extends EventEmitter {
6860
7066
  this.#handle.off("ephemeral-message", this.#handleEphemeralMessage);
6861
7067
  this.stopHeartbeats();
6862
7068
  this.stopPruningPeers();
6863
- this.doBroadcast("goodbye");
7069
+ this.send({ type: "goodbye" });
6864
7070
  this.#running = false;
6865
7071
  }
6866
7072
  announce() {
@@ -6877,7 +7083,8 @@ class Presence extends EventEmitter {
6877
7083
  this.doBroadcast("snapshot", { state: this.#localState });
6878
7084
  this.resetHeartbeats();
6879
7085
  }
6880
- broadcastChannelState(channel, value) {
7086
+ broadcastChannelState(channel) {
7087
+ const value = this.#localState[channel];
6881
7088
  this.doBroadcast("update", { channel, value });
6882
7089
  this.resetHeartbeats();
6883
7090
  }
@@ -6888,20 +7095,20 @@ class Presence extends EventEmitter {
6888
7095
  this.stopHeartbeats();
6889
7096
  this.startHeartbeats();
6890
7097
  }
6891
- sendHeartbeat() {
6892
- this.doBroadcast("heartbeat");
6893
- }
6894
7098
  doBroadcast(type, extra) {
7099
+ this.send({
7100
+ userId: this.userId,
7101
+ deviceId: this.deviceId,
7102
+ type,
7103
+ ...extra,
7104
+ });
7105
+ }
7106
+ send(message) {
6895
7107
  if (!this.#running) {
6896
7108
  return;
6897
7109
  }
6898
7110
  this.#handle.broadcast({
6899
- [PRESENCE_MESSAGE_MARKER]: {
6900
- userId: this.userId,
6901
- deviceId: this.deviceId,
6902
- type,
6903
- ...extra,
6904
- },
7111
+ [PRESENCE_MESSAGE_MARKER]: message,
6905
7112
  });
6906
7113
  }
6907
7114
  startHeartbeats() {
@@ -6909,7 +7116,7 @@ class Presence extends EventEmitter {
6909
7116
  return;
6910
7117
  }
6911
7118
  this.#heartbeatInterval = setInterval(() => {
6912
- this.sendHeartbeat();
7119
+ this.send({ type: "heartbeat" });
6913
7120
  }, this.#heartbeatMs);
6914
7121
  }
6915
7122
  stopHeartbeats() {
@@ -6938,280 +7145,6 @@ class Presence extends EventEmitter {
6938
7145
  this.#pruningInterval = undefined;
6939
7146
  }
6940
7147
  }
6941
- /**
6942
- * A summary of the latest Presence information for the set of peers who have
6943
- * reported a Presence status to us.
6944
- */
6945
- class PeerPresenceView {
6946
- #peersLastSeen = new Map();
6947
- #peerStates = new Map();
6948
- #userPeers = new Map();
6949
- #devicePeers = new Map();
6950
- /** @hidden */
6951
- constructor(peersLastSeen, peerStates, userPeers, devicePeers) {
6952
- this.#peersLastSeen = peersLastSeen;
6953
- this.#peerStates = peerStates;
6954
- this.#userPeers = userPeers;
6955
- this.#devicePeers = devicePeers;
6956
- }
6957
- /**
6958
- * Check if peer is currently present.
6959
- *
6960
- * @param peerId
6961
- * @returns true if the peer has been seen recently
6962
- */
6963
- has(peerId) {
6964
- return this.#peerStates.has(peerId);
6965
- }
6966
- /**
6967
- * Check when the peer was last seen.
6968
- *
6969
- * @param peerId
6970
- * @returns last seen UNIX timestamp, or undefined for unknown peers
6971
- */
6972
- getLastSeen(peerId) {
6973
- return this.#peersLastSeen.get(peerId);
6974
- }
6975
- /**
6976
- * Get all recently-seen peers.
6977
- *
6978
- * @returns Array of peer ids
6979
- */
6980
- getPeers() {
6981
- return Array.from(this.#peerStates.keys());
6982
- }
6983
- /**
6984
- * Get all recently-seen users.
6985
- *
6986
- * @returns Array of user ids
6987
- */
6988
- getUsers() {
6989
- return Array.from(this.#userPeers.keys());
6990
- }
6991
- /**
6992
- * Get all recently-seen devices.
6993
- *
6994
- * @returns Array of device ids
6995
- */
6996
- getDevices() {
6997
- return Array.from(this.#devicePeers.keys());
6998
- }
6999
- /**
7000
- * Get all recently-seen peers for this user.
7001
- *
7002
- * @param userId
7003
- * @returns Array of peer ids for this user
7004
- */
7005
- getUserPeers(userId) {
7006
- const peers = this.#userPeers.get(userId);
7007
- if (!peers) {
7008
- return;
7009
- }
7010
- return Array.from(peers);
7011
- }
7012
- /**
7013
- * Get all recently-seen peers for this device.
7014
- *
7015
- * @param deviceId
7016
- * @returns Array of peer ids for this device
7017
- */
7018
- getDevicePeers(deviceId) {
7019
- const peers = this.#devicePeers.get(deviceId);
7020
- if (!peers) {
7021
- return;
7022
- }
7023
- return Array.from(peers);
7024
- }
7025
- /**
7026
- * Get most-recently-seen peer from this group.
7027
- *
7028
- * @param peers
7029
- * @returns id of most recently seen peer
7030
- */
7031
- getFreshestPeer(peers) {
7032
- let freshestLastSeen;
7033
- return Array.from(peers).reduce((freshest, curr) => {
7034
- const lastSeen = this.#peersLastSeen.get(curr);
7035
- if (!lastSeen) {
7036
- return freshest;
7037
- }
7038
- if (!freshest || lastSeen > freshestLastSeen) {
7039
- freshestLastSeen = lastSeen;
7040
- return curr;
7041
- }
7042
- return freshest;
7043
- }, undefined);
7044
- }
7045
- /**
7046
- * Get current @type PeerState for given peer.
7047
- *
7048
- * @param peerId
7049
- * @returns details for the peer
7050
- */
7051
- getPeerInfo(peerId) {
7052
- return this.#peerStates.get(peerId);
7053
- }
7054
- /**
7055
- * Get current ephemeral state value for this peer. If a channel is specified,
7056
- * only returns the ephemeral state for that specific channel. Otherwise,
7057
- * returns the full ephemeral state.
7058
- *
7059
- * @param peerId
7060
- * @param channel
7061
- * @returns latest ephemeral state received
7062
- */
7063
- getPeerState(peerId, channel) {
7064
- const fullState = this.#peerStates.get(peerId)?.value;
7065
- if (!channel) {
7066
- return fullState;
7067
- }
7068
- return fullState?.[channel];
7069
- }
7070
- /**
7071
- * Get current ephemeral state value for this user's most-recently-active
7072
- * peer. See {@link getPeerState}.
7073
- *
7074
- * @param userId
7075
- * @param channel
7076
- * @returns
7077
- */
7078
- getUserState(userId, channel) {
7079
- const peers = this.#userPeers.get(userId);
7080
- if (!peers) {
7081
- return undefined;
7082
- }
7083
- const peer = this.getFreshestPeer(peers);
7084
- if (!peer) {
7085
- return undefined;
7086
- }
7087
- return this.getPeerState(peer, channel);
7088
- }
7089
- /**
7090
- * Get current ephemeral state value for this device's most-recently-active
7091
- * peer. See {@link getPeerState}.
7092
- *
7093
- * @param userId
7094
- * @param channel
7095
- * @returns
7096
- */
7097
- getDeviceState(deviceId, channel) {
7098
- const peers = this.#devicePeers.get(deviceId);
7099
- if (!peers) {
7100
- return undefined;
7101
- }
7102
- const peer = this.getFreshestPeer(peers);
7103
- if (!peer) {
7104
- return undefined;
7105
- }
7106
- return this.getPeerState(peer, channel);
7107
- }
7108
- }
7109
- class PeerPresenceInfo extends EventEmitter {
7110
- ttl;
7111
- #peersLastSeen = new Map();
7112
- #peerStates = new Map();
7113
- #userPeers = new Map();
7114
- #devicePeers = new Map();
7115
- view;
7116
- /**
7117
- * Build a new peer presence state.
7118
- *
7119
- * @param ttl in milliseconds - peers with no activity within this timeframe
7120
- * are forgotten when {@link prune} is called.
7121
- */
7122
- constructor(ttl) {
7123
- super();
7124
- this.ttl = ttl;
7125
- this.view = new PeerPresenceView(this.#peersLastSeen, this.#peerStates, this.#userPeers, this.#devicePeers);
7126
- }
7127
- /**
7128
- * Record that we've seen the given peer recently.
7129
- *
7130
- * @param peerId
7131
- * @param deviceId
7132
- * @param userId
7133
- */
7134
- markSeen(peerId, deviceId, userId) {
7135
- const devicePeers = this.#devicePeers.get(deviceId) ?? new Set();
7136
- devicePeers.add(peerId);
7137
- this.#devicePeers.set(deviceId, devicePeers);
7138
- const userPeers = this.#userPeers.get(userId) ?? new Set();
7139
- userPeers.add(peerId);
7140
- this.#userPeers.set(userId, userPeers);
7141
- this.#peersLastSeen.set(peerId, Date.now());
7142
- }
7143
- /**
7144
- * Record a state update for the given peer. It is also automatically updated with {@link markSeen}.
7145
- *
7146
- * @param peerId
7147
- * @param deviceId
7148
- * @param userId
7149
- * @param value
7150
- */
7151
- update({ peerId, deviceId, userId, channel, value, }) {
7152
- this.markSeen(peerId, deviceId, userId);
7153
- const peerState = this.#peerStates.get(peerId);
7154
- const existingState = peerState?.value ?? {};
7155
- this.#peerStates.set(peerId, {
7156
- peerId,
7157
- deviceId,
7158
- userId,
7159
- value: {
7160
- ...existingState,
7161
- [channel]: value,
7162
- },
7163
- });
7164
- }
7165
- /**
7166
- * Forget the given peer.
7167
- *
7168
- * @param peerId
7169
- */
7170
- delete(peerId) {
7171
- this.#peersLastSeen.delete(peerId);
7172
- this.#peerStates.delete(peerId);
7173
- Array.from(this.#devicePeers.entries()).forEach(([deviceId, peerIds]) => {
7174
- if (peerIds.has(peerId)) {
7175
- peerIds.delete(peerId);
7176
- }
7177
- if (peerIds.size === 0) {
7178
- this.#devicePeers.delete(deviceId);
7179
- }
7180
- });
7181
- Array.from(this.#userPeers.entries()).forEach(([userId, peerIds]) => {
7182
- if (peerIds.has(peerId)) {
7183
- peerIds.delete(peerId);
7184
- }
7185
- if (peerIds.size === 0) {
7186
- this.#userPeers.delete(userId);
7187
- }
7188
- });
7189
- }
7190
- /**
7191
- * Prune all peers that have not been seen since the configured ttl has
7192
- * elapsed.
7193
- */
7194
- prune() {
7195
- const threshold = Date.now() - this.ttl;
7196
- const stalePeers = new Set(Array.from(this.#peersLastSeen.entries())
7197
- .filter(([, lastSeen]) => {
7198
- return lastSeen < threshold;
7199
- })
7200
- .map(([peerId]) => peerId));
7201
- if (stalePeers.size === 0) {
7202
- return;
7203
- }
7204
- stalePeers.forEach(stalePeer => {
7205
- this.delete(stalePeer);
7206
- });
7207
- }
7208
- }
7209
-
7210
- function useInvalidate() {
7211
- const [, setState] = useState(0);
7212
- const increment = useCallback(() => setState((value) => value + 1), [setState]);
7213
- return increment;
7214
- }
7215
7148
 
7216
7149
  function usePresence({
7217
7150
  handle,
@@ -7221,7 +7154,8 @@ function usePresence({
7221
7154
  heartbeatMs,
7222
7155
  peerTtlMs
7223
7156
  }) {
7224
- const invalidate = useInvalidate();
7157
+ const [localState, setLocalState] = useState(initialState);
7158
+ const [peerStates, setPeerStates] = useState(new PeerStateView({}));
7225
7159
  const firstOpts = useRef({
7226
7160
  heartbeatMs,
7227
7161
  peerTtlMs
@@ -7235,25 +7169,26 @@ function usePresence({
7235
7169
  initialState: firstInitialState.current,
7236
7170
  ...firstOpts.current
7237
7171
  });
7238
- presence.on("heartbeat", invalidate);
7239
- presence.on("snapshot", invalidate);
7240
- presence.on("update", invalidate);
7241
- presence.on("goodbye", invalidate);
7172
+ presence.on("heartbeat", () => setPeerStates(presence.getPeerStates()));
7173
+ presence.on("snapshot", () => setPeerStates(presence.getPeerStates()));
7174
+ presence.on("update", () => setPeerStates(presence.getPeerStates()));
7175
+ presence.on("goodbye", () => setPeerStates(presence.getPeerStates()));
7242
7176
  return () => {
7243
7177
  presence.stop();
7244
7178
  };
7245
7179
  }, [presence, userId, deviceId, firstInitialState, firstOpts]);
7246
- const updateLocalState = useCallback(
7180
+ const update = useCallback(
7247
7181
  (channel, msg) => {
7248
- invalidate();
7249
7182
  presence.broadcast(channel, msg);
7183
+ const updated = presence.getLocalState();
7184
+ setLocalState(updated);
7250
7185
  },
7251
7186
  [presence]
7252
7187
  );
7253
7188
  return {
7254
- peerStates: presence.getPeerStates(),
7255
- localState: presence.getLocalState(),
7256
- update: updateLocalState
7189
+ peerStates,
7190
+ localState,
7191
+ update
7257
7192
  };
7258
7193
  }
7259
7194