@liveblocks/react 1.9.8-pre1 → 1.10.0-beta2

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.mjs CHANGED
@@ -5,7 +5,7 @@ import { detectDupes } from "@liveblocks/core";
5
5
 
6
6
  // src/version.ts
7
7
  var PKG_NAME = "@liveblocks/react";
8
- var PKG_VERSION = "1.9.8-pre1";
8
+ var PKG_VERSION = "1.10.0-beta2";
9
9
  var PKG_FORMAT = "esm";
10
10
 
11
11
  // src/ClientSideSuspense.tsx
@@ -18,29 +18,47 @@ function ClientSideSuspense(props) {
18
18
  return /* @__PURE__ */ React.createElement(React.Suspense, { fallback: props.fallback }, mounted ? props.children() : props.fallback);
19
19
  }
20
20
 
21
- // src/factory.tsx
21
+ // src/liveblocks.tsx
22
+ import { kInternal as kInternal3, makePoller as makePoller2 } from "@liveblocks/core";
23
+ import { nanoid as nanoid3 } from "nanoid";
24
+ import React3, {
25
+ createContext as createContext2,
26
+ useCallback as useCallback3,
27
+ useContext as useContext3,
28
+ useEffect as useEffect5
29
+ } from "react";
30
+ import { useSyncExternalStore as useSyncExternalStore3 } from "use-sync-external-store/shim/index.js";
31
+ import { useSyncExternalStoreWithSelector as useSyncExternalStoreWithSelector2 } from "use-sync-external-store/shim/with-selector.js";
32
+
33
+ // src/comments/lib/selected-inbox-notifications.ts
34
+ import { applyOptimisticUpdates } from "@liveblocks/core";
35
+ function selectedInboxNotifications(state) {
36
+ const result = applyOptimisticUpdates(state);
37
+ return Object.values(result.inboxNotifications);
38
+ }
39
+
40
+ // src/shared.ts
41
+ import { kInternal as kInternal2 } from "@liveblocks/core";
42
+ import { useCallback as useCallback2, useContext as useContext2, useEffect as useEffect4 } from "react";
43
+ import { useSyncExternalStore as useSyncExternalStore2 } from "use-sync-external-store/shim/index.js";
44
+
45
+ // src/room.tsx
22
46
  import { shallow } from "@liveblocks/client";
23
47
  import {
24
- createAsyncCache,
48
+ CommentsApiError,
49
+ console as console2,
25
50
  deprecateIf,
26
51
  errorIf,
27
52
  isLiveNode,
28
- makeEventSource as makeEventSource2,
29
- stringify as stringify2
53
+ kInternal,
54
+ makeEventSource,
55
+ makePoller,
56
+ NotificationsApiError,
57
+ ServerMsgCode,
58
+ stringify
30
59
  } from "@liveblocks/core";
31
- import * as React3 from "react";
32
- import { useSyncExternalStoreWithSelector as useSyncExternalStoreWithSelector2 } from "use-sync-external-store/shim/with-selector.js";
33
-
34
- // src/comments/CommentsRoom.tsx
35
- import { CommentsApiError, makeEventSource, stringify } from "@liveblocks/core";
36
- import { nanoid } from "nanoid";
37
- import React2, {
38
- createContext,
39
- useCallback as useCallback3,
40
- useContext,
41
- useEffect as useEffect3,
42
- useMemo
43
- } from "react";
60
+ import { nanoid as nanoid2 } from "nanoid";
61
+ import * as React2 from "react";
44
62
  import { useSyncExternalStoreWithSelector } from "use-sync-external-store/shim/with-selector.js";
45
63
 
46
64
  // src/comments/errors.ts
@@ -100,1298 +118,333 @@ var RemoveReactionError = class extends Error {
100
118
  this.name = "RemoveReactionError";
101
119
  }
102
120
  };
121
+ var MarkInboxNotificationAsReadError = class extends Error {
122
+ constructor(cause, context) {
123
+ super("Mark inbox notification as read failed.");
124
+ this.cause = cause;
125
+ this.context = context;
126
+ this.name = "MarkInboxNotificationAsReadError";
127
+ }
128
+ };
129
+ var UpdateNotificationSettingsError = class extends Error {
130
+ constructor(cause, context) {
131
+ super("Update notification settings failed.");
132
+ this.cause = cause;
133
+ this.context = context;
134
+ this.name = "UpdateNotificationSettingsError";
135
+ }
136
+ };
103
137
 
104
- // src/comments/lib/revalidation.ts
105
- import { useCallback as useCallback2, useEffect as useEffect2, useRef as useRef2 } from "react";
106
-
107
- // src/comments/lib/use-is-document-visible.ts
108
- import { useSyncExternalStore } from "use-sync-external-store/shim/index.js";
109
- function useIsDocumentVisible() {
110
- const isVisible = useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
111
- return isVisible;
138
+ // src/comments/lib/createIds.ts
139
+ import { nanoid } from "nanoid";
140
+ var THREAD_ID_PREFIX = "th";
141
+ var COMMENT_ID_PREFIX = "cm";
142
+ function createOptimisticId(prefix) {
143
+ return `${prefix}_${nanoid()}`;
112
144
  }
113
- function subscribe(onStoreChange) {
114
- document.addEventListener("visibilitychange", onStoreChange);
115
- return () => {
116
- document.removeEventListener("visibilitychange", onStoreChange);
117
- };
145
+ function createThreadId() {
146
+ return createOptimisticId(THREAD_ID_PREFIX);
118
147
  }
119
- function getSnapshot() {
120
- const isDocumentDefined = typeof document !== "undefined";
121
- return isDocumentDefined ? document.visibilityState === "visible" : true;
148
+ function createCommentId() {
149
+ return createOptimisticId(COMMENT_ID_PREFIX);
122
150
  }
123
151
 
124
- // src/comments/lib/use-is-online.ts
125
- import { useCallback, useRef } from "react";
126
- import { useSyncExternalStore as useSyncExternalStore2 } from "use-sync-external-store/shim/index.js";
127
- function useIsOnline() {
128
- const isOnlineRef = useRef(true);
129
- const subscribe2 = useCallback((onStoreChange) => {
130
- function handleIsOnline() {
131
- isOnlineRef.current = true;
132
- onStoreChange();
133
- }
134
- function handleIsOffline() {
135
- isOnlineRef.current = false;
136
- onStoreChange();
137
- }
138
- window.addEventListener("online", handleIsOnline);
139
- window.addEventListener("offline", handleIsOffline);
140
- return () => {
141
- window.removeEventListener("online", handleIsOnline);
142
- window.removeEventListener("offline", handleIsOffline);
143
- };
144
- }, []);
145
- const getSnapshot2 = useCallback(() => {
146
- return isOnlineRef.current;
147
- }, []);
148
- const isOnline = useSyncExternalStore2(subscribe2, getSnapshot2, getSnapshot2);
149
- return isOnline;
152
+ // src/comments/lib/select-notification-settings.ts
153
+ import {
154
+ applyOptimisticUpdates as applyOptimisticUpdates2
155
+ } from "@liveblocks/core";
156
+ function selectNotificationSettings(roomId, state) {
157
+ const { notificationSettings } = applyOptimisticUpdates2(state);
158
+ return notificationSettings[roomId];
150
159
  }
151
160
 
152
- // src/comments/lib/revalidation.ts
153
- var DEFAULT_ERROR_RETRY_INTERVAL = 5e3;
154
- var DEFAULT_MAX_ERROR_RETRY_COUNT = 5;
155
- var DEFAULT_DEDUPING_INTERVAL = 2e3;
156
- var timestamp = 0;
157
- function useRevalidateCache(manager, fetcher, options = {}) {
158
- const isOnlineRef = useRef2(true);
159
- const {
160
- dedupingInterval = DEFAULT_DEDUPING_INTERVAL,
161
- errorRetryInterval = DEFAULT_ERROR_RETRY_INTERVAL,
162
- errorRetryCount = DEFAULT_MAX_ERROR_RETRY_COUNT
163
- } = options;
164
- const _revalidateCache = useCallback2(
165
- async ({
166
- shouldDedupe,
167
- retryCount = 0
168
- }) => {
169
- let startAt;
170
- const shouldStartRequest = !manager.getRequest() || !shouldDedupe;
171
- function deleteActiveRequest() {
172
- const activeRequest = manager.getRequest();
173
- if (!activeRequest)
174
- return;
175
- if (activeRequest.timestamp !== startAt)
176
- return;
177
- manager.setRequest(void 0);
178
- }
179
- function handleError() {
180
- const timeout = ~~((Math.random() + 0.5) * (1 << (retryCount < 8 ? retryCount : 8))) * errorRetryInterval;
181
- if (retryCount > errorRetryCount)
182
- return;
183
- setTimeout(() => {
184
- void _revalidateCache({
185
- shouldDedupe: false,
186
- retryCount: retryCount + 1
187
- });
188
- }, timeout);
189
- }
190
- if (shouldStartRequest) {
191
- manager.setRequest({
192
- fetcher: fetcher(),
193
- timestamp: ++timestamp
194
- });
195
- }
196
- try {
197
- let activeRequest = manager.getRequest();
198
- if (!activeRequest)
199
- return;
200
- startAt = activeRequest.timestamp;
201
- const newData = await activeRequest.fetcher;
202
- if (shouldStartRequest) {
203
- setTimeout(deleteActiveRequest, dedupingInterval);
204
- }
205
- activeRequest = manager.getRequest();
206
- if (!activeRequest || activeRequest.timestamp !== startAt)
207
- return;
208
- const activeMutation = manager.getMutation();
209
- if (activeMutation && (activeMutation.startTime > startAt || activeMutation.endTime > startAt || activeMutation.endTime === 0)) {
210
- return;
211
- }
212
- manager.setCache(newData);
213
- } catch (err) {
214
- deleteActiveRequest();
215
- const isVisible = document.visibilityState === "visible";
216
- const isOnline = isOnlineRef.current;
217
- if (shouldStartRequest && isVisible && isOnline)
218
- handleError();
219
- manager.setError(err);
161
+ // src/comments/lib/selected-threads.ts
162
+ import {
163
+ applyOptimisticUpdates as applyOptimisticUpdates3
164
+ } from "@liveblocks/core";
165
+ function selectedThreads(roomId, state, options) {
166
+ const result = applyOptimisticUpdates3(state);
167
+ const threads = Object.values(result.threads).filter((thread) => {
168
+ if (thread.roomId !== roomId)
169
+ return false;
170
+ const query = options.query;
171
+ if (!query)
172
+ return true;
173
+ for (const key in query.metadata) {
174
+ if (thread.metadata[key] !== query.metadata[key]) {
175
+ return false;
220
176
  }
221
- return;
222
- },
223
- [manager, fetcher, dedupingInterval, errorRetryInterval, errorRetryCount]
224
- );
225
- useEffect2(() => {
226
- function handleIsOnline() {
227
- isOnlineRef.current = true;
228
177
  }
229
- function handleIsOffline() {
230
- isOnlineRef.current = false;
178
+ return true;
179
+ });
180
+ return threads.sort((a, b) => a.createdAt.getTime() - b.createdAt.getTime());
181
+ }
182
+
183
+ // src/comments/lib/upsert-comment.ts
184
+ function upsertComment(threads, newComment) {
185
+ const thread = threads[newComment.threadId];
186
+ if (thread === void 0) {
187
+ return threads;
188
+ }
189
+ const newComments = [];
190
+ let updated = false;
191
+ for (const comment of thread.comments) {
192
+ if (comment.id === newComment.id) {
193
+ updated = true;
194
+ newComments.push(newComment);
195
+ } else {
196
+ newComments.push(comment);
197
+ }
198
+ }
199
+ if (!updated) {
200
+ newComments.push(newComment);
201
+ }
202
+ return {
203
+ ...threads,
204
+ [thread.id]: {
205
+ ...thread,
206
+ comments: newComments
231
207
  }
232
- window.addEventListener("online", handleIsOnline);
233
- window.addEventListener("offline", handleIsOffline);
234
- return () => {
235
- window.removeEventListener("online", handleIsOnline);
236
- window.removeEventListener("offline", handleIsOffline);
237
- };
238
- }, []);
239
- const revalidateCache = useCallback2(
240
- ({ shouldDedupe }) => {
241
- return _revalidateCache({ shouldDedupe, retryCount: 0 });
242
- },
243
- [_revalidateCache]
208
+ };
209
+ }
210
+
211
+ // src/lib/use-initial.ts
212
+ import { useState as useState2 } from "react";
213
+ function useInitial(value) {
214
+ return useState2(value)[0];
215
+ }
216
+
217
+ // src/lib/use-latest.ts
218
+ import { useEffect as useEffect2, useRef } from "react";
219
+ function useLatest(value) {
220
+ const ref = useRef(value);
221
+ useEffect2(() => {
222
+ ref.current = value;
223
+ }, [value]);
224
+ return ref;
225
+ }
226
+
227
+ // src/lib/use-rerender.ts
228
+ import { useReducer } from "react";
229
+ function useRerender() {
230
+ const [, update] = useReducer(
231
+ // This implementation works by incrementing a hidden counter value that is
232
+ // never consumed. Simply incrementing the counter changes the component's
233
+ // state and, thus, trigger a re-render.
234
+ (x) => x + 1,
235
+ 0
244
236
  );
245
- return revalidateCache;
237
+ return update;
246
238
  }
247
- function useMutate(manager, revalidateCache) {
248
- const mutate = useCallback2(
249
- async (data, options) => {
250
- const beforeMutationTimestamp = ++timestamp;
251
- manager.setMutation({
252
- startTime: beforeMutationTimestamp,
253
- endTime: 0
254
- });
255
- const currentCache = manager.getCache();
256
- manager.setCache(options.optimisticData);
257
- let error;
258
- try {
259
- await data;
260
- } catch (err) {
261
- error = err;
239
+
240
+ // src/room.tsx
241
+ var noop = () => {
242
+ };
243
+ var identity = (x) => x;
244
+ var missing_unstable_batchedUpdates = (reactVersion, roomId) => `We noticed you\u2019re using React ${reactVersion}. Please pass unstable_batchedUpdates at the RoomProvider level until you\u2019re ready to upgrade to React 18:
245
+
246
+ import { unstable_batchedUpdates } from "react-dom"; // or "react-native"
247
+
248
+ <RoomProvider id=${JSON.stringify(
249
+ roomId
250
+ )} ... unstable_batchedUpdates={unstable_batchedUpdates}>
251
+ ...
252
+ </RoomProvider>
253
+
254
+ Why? Please see https://liveblocks.io/docs/platform/troubleshooting#stale-props-zombie-child for more information`;
255
+ var superfluous_unstable_batchedUpdates = "You don\u2019t need to pass unstable_batchedUpdates to RoomProvider anymore, since you\u2019re on React 18+ already.";
256
+ function useSyncExternalStore(s, gs, gss) {
257
+ return useSyncExternalStoreWithSelector(s, gs, gss, identity);
258
+ }
259
+ var STABLE_EMPTY_LIST = Object.freeze([]);
260
+ var POLLING_INTERVAL = 5 * 60 * 1e3;
261
+ var MENTION_SUGGESTIONS_DEBOUNCE = 500;
262
+ function alwaysEmptyList() {
263
+ return STABLE_EMPTY_LIST;
264
+ }
265
+ function alwaysNull() {
266
+ return null;
267
+ }
268
+ function makeMutationContext(room) {
269
+ const errmsg = "This mutation cannot be used until connected to the Liveblocks room";
270
+ return {
271
+ get storage() {
272
+ const mutableRoot = room.getStorageSnapshot();
273
+ if (mutableRoot === null) {
274
+ throw new Error(errmsg);
262
275
  }
263
- const activeMutation = manager.getMutation();
264
- if (activeMutation && beforeMutationTimestamp !== activeMutation.startTime) {
265
- if (error)
266
- throw error;
267
- return;
276
+ return mutableRoot;
277
+ },
278
+ get self() {
279
+ const self = room.getSelf();
280
+ if (self === null) {
281
+ throw new Error(errmsg);
268
282
  }
269
- if (error) {
270
- manager.setCache(currentCache);
283
+ return self;
284
+ },
285
+ get others() {
286
+ const others = room.getOthers();
287
+ if (room.getSelf() === null) {
288
+ throw new Error(errmsg);
271
289
  }
272
- manager.setMutation({
273
- startTime: beforeMutationTimestamp,
274
- endTime: ++timestamp
275
- });
276
- manager.setRequest(void 0);
277
- void revalidateCache({ shouldDedupe: false });
278
- if (error)
279
- throw error;
290
+ return others;
280
291
  },
281
- [manager, revalidateCache]
282
- );
283
- return mutate;
292
+ setMyPresence: room.updatePresence
293
+ };
284
294
  }
285
-
286
- // src/comments/CommentsRoom.tsx
287
- var THREAD_ID_PREFIX = "th";
288
- var COMMENT_ID_PREFIX = "cm";
289
- var POLLING_INTERVAL_REALTIME = 3e4;
290
- var POLLING_INTERVAL = 5e3;
291
- function createCommentsRoom(errorEventSource) {
292
- const store = createClientCacheStore();
293
- const FetcherContext = createContext(null);
294
- const RoomManagerContext = createContext(null);
295
- function getThreads(manager) {
296
- const threads = manager.getCache();
297
- if (!threads) {
298
- throw new Error(
299
- "Cannot update threads or comments before they are loaded."
300
- );
301
- }
302
- return threads;
295
+ var ContextBundle = React2.createContext(null);
296
+ function useRoomContextBundle() {
297
+ const bundle = React2.useContext(ContextBundle);
298
+ if (bundle === null) {
299
+ throw new Error("RoomProvider is missing from the React tree.");
303
300
  }
304
- function CommentsRoomProvider({
305
- room,
306
- children
307
- }) {
308
- const manager = useMemo(() => {
309
- return createRoomRevalidationManager(room.id, {
310
- getCache: store.getThreads,
311
- setCache: store.setThreads
312
- });
313
- }, [room.id]);
314
- const fetcher = React2.useCallback(async () => {
315
- const options = manager.getRevalidationManagers().filter(([key]) => manager.getReferenceCount(key) > 0).map(([_, manager2]) => manager2.getOptions());
316
- const responses = await Promise.all(
317
- options.map(async (option) => {
318
- return await room.getThreads(option);
319
- })
301
+ return bundle;
302
+ }
303
+ function createRoomContext(client, options) {
304
+ if (options?.resolveUsers) {
305
+ throw new Error(
306
+ "The 'resolveUsers' option has moved to 'createClient' from '@liveblocks/client'. Please refer to our Upgrade Guide to learn more, see https://liveblocks.io/docs/platform/upgrading/1.10."
307
+ );
308
+ }
309
+ if (options?.resolveMentionSuggestions) {
310
+ throw new Error(
311
+ "The 'resolveMentionSuggestions' option has moved to 'createClient' from '@liveblocks/client'. Please refer to our Upgrade Guide to learn more, see https://liveblocks.io/docs/platform/upgrading/1.10."
312
+ );
313
+ }
314
+ const RoomContext = React2.createContext(null);
315
+ const commentsErrorEventSource = makeEventSource();
316
+ const shared = createSharedContext(client);
317
+ function RoomProviderOuter(props) {
318
+ const [cache] = React2.useState(
319
+ () => /* @__PURE__ */ new Map()
320
+ );
321
+ const stableEnterRoom = React2.useCallback(
322
+ (roomId, options2) => {
323
+ const cached = cache.get(roomId);
324
+ if (cached)
325
+ return cached;
326
+ const rv = client.enterRoom(
327
+ roomId,
328
+ options2
329
+ );
330
+ const origLeave = rv.leave;
331
+ rv.leave = () => {
332
+ origLeave();
333
+ cache.delete(roomId);
334
+ };
335
+ cache.set(roomId, rv);
336
+ return rv;
337
+ },
338
+ [cache]
339
+ );
340
+ return /* @__PURE__ */ React2.createElement(RoomProviderInner, { ...props, stableEnterRoom });
341
+ }
342
+ function RoomProviderInner(props) {
343
+ const { id: roomId, stableEnterRoom } = props;
344
+ if (process.env.NODE_ENV !== "production") {
345
+ if (!roomId) {
346
+ throw new Error(
347
+ "RoomProvider id property is required. For more information: https://liveblocks.io/docs/errors/liveblocks-react/RoomProvider-id-property-is-required"
348
+ );
349
+ }
350
+ if (typeof roomId !== "string") {
351
+ throw new Error("RoomProvider id property should be a string.");
352
+ }
353
+ const majorReactVersion = parseInt(React2.version) || 1;
354
+ const oldReactVersion = majorReactVersion < 18;
355
+ errorIf(
356
+ oldReactVersion && props.unstable_batchedUpdates === void 0,
357
+ missing_unstable_batchedUpdates(majorReactVersion, roomId)
320
358
  );
321
- const threads = Array.from(
322
- new Map(responses.flat().map((thread) => [thread.id, thread])).values()
359
+ deprecateIf(
360
+ !oldReactVersion && props.unstable_batchedUpdates !== void 0,
361
+ superfluous_unstable_batchedUpdates
323
362
  );
324
- return threads;
325
- }, [room, manager]);
326
- const revalidateCache = useRevalidateCache(manager, fetcher);
327
- const status = useSyncExternalStore3(
328
- room.events.status.subscribe,
329
- room.getStatus,
330
- room.getStatus
331
- );
332
- const isOnline = useIsOnline();
333
- const isDocumentVisible = useIsDocumentVisible();
334
- const refreshInterval = getPollingInterval(
335
- isOnline,
336
- isDocumentVisible,
337
- status === "connected"
363
+ }
364
+ const frozenProps = useInitial({
365
+ initialPresence: props.initialPresence,
366
+ initialStorage: props.initialStorage,
367
+ unstable_batchedUpdates: props.unstable_batchedUpdates,
368
+ autoConnect: props.autoConnect ?? props.shouldInitiallyConnect ?? typeof window !== "undefined"
369
+ });
370
+ const [{ room }, setRoomLeavePair] = React2.useState(
371
+ () => stableEnterRoom(roomId, {
372
+ ...frozenProps,
373
+ autoConnect: false
374
+ // Deliberately using false here on the first render, see below
375
+ })
338
376
  );
339
- useEffect3(() => {
340
- let revalidationTimerId;
341
- function scheduleRevalidation() {
342
- if (refreshInterval === 0)
377
+ React2.useEffect(() => {
378
+ async function handleCommentEvent(message) {
379
+ const info = await room.getThread({ threadId: message.threadId });
380
+ if (!info) {
381
+ store.deleteThread(message.threadId);
343
382
  return;
344
- revalidationTimerId = window.setTimeout(() => {
345
- if (isOnline && isDocumentVisible && !manager.getError() && manager.getTotalReferenceCount() > 0) {
346
- void revalidateCache({ shouldDedupe: true }).then(
347
- scheduleRevalidation
348
- );
349
- return;
350
- }
351
- scheduleRevalidation();
352
- }, refreshInterval);
353
- }
354
- scheduleRevalidation();
355
- return () => {
356
- window.clearTimeout(revalidationTimerId);
357
- };
358
- }, [
359
- revalidateCache,
360
- refreshInterval,
361
- isOnline,
362
- isDocumentVisible,
363
- manager
364
- ]);
365
- useEffect3(() => {
366
- function handleIsOnline() {
367
- if (isDocumentVisible) {
368
- void revalidateCache({ shouldDedupe: true });
369
383
  }
370
- }
371
- window.addEventListener("online", handleIsOnline);
372
- return () => {
373
- window.removeEventListener("online", handleIsOnline);
374
- };
375
- }, [revalidateCache, isDocumentVisible]);
376
- useEffect3(() => {
377
- function handleVisibilityChange() {
378
- const isVisible = document.visibilityState === "visible";
379
- if (isVisible && isOnline) {
380
- void revalidateCache({ shouldDedupe: true });
384
+ const { thread, inboxNotification } = info;
385
+ const existingThread = store.get().threads[message.threadId];
386
+ switch (message.type) {
387
+ case ServerMsgCode.COMMENT_EDITED:
388
+ case ServerMsgCode.THREAD_METADATA_UPDATED:
389
+ case ServerMsgCode.COMMENT_REACTION_ADDED:
390
+ case ServerMsgCode.COMMENT_REACTION_REMOVED:
391
+ case ServerMsgCode.COMMENT_DELETED:
392
+ if (!existingThread)
393
+ break;
394
+ store.updateThreadAndNotification(thread, inboxNotification);
395
+ break;
396
+ case ServerMsgCode.COMMENT_CREATED:
397
+ store.updateThreadAndNotification(thread, inboxNotification);
398
+ break;
399
+ default:
400
+ break;
381
401
  }
382
402
  }
383
- document.addEventListener("visibilitychange", handleVisibilityChange);
384
- return () => {
385
- document.removeEventListener(
386
- "visibilitychange",
387
- handleVisibilityChange
388
- );
389
- };
390
- }, [revalidateCache, isOnline]);
391
- useEffect3(() => {
392
- const unsubscribe = room.events.comments.subscribe(() => {
393
- void revalidateCache({ shouldDedupe: false });
394
- });
403
+ return room.events.comments.subscribe(
404
+ (message) => void handleCommentEvent(message)
405
+ );
406
+ }, [room]);
407
+ React2.useEffect(() => {
408
+ const pair = stableEnterRoom(roomId, frozenProps);
409
+ setRoomLeavePair(pair);
410
+ const { room: room2, leave } = pair;
411
+ if (frozenProps.autoConnect) {
412
+ room2.connect();
413
+ }
395
414
  return () => {
396
- unsubscribe();
415
+ leave();
397
416
  };
398
- }, [room, revalidateCache]);
399
- return /* @__PURE__ */ React2.createElement(FetcherContext.Provider, { value: fetcher }, /* @__PURE__ */ React2.createElement(RoomManagerContext.Provider, { value: manager }, children));
417
+ }, [roomId, frozenProps, stableEnterRoom]);
418
+ return /* @__PURE__ */ React2.createElement(RoomContext.Provider, { value: room }, /* @__PURE__ */ React2.createElement(
419
+ ContextBundle.Provider,
420
+ {
421
+ value: bundle
422
+ },
423
+ props.children
424
+ ));
400
425
  }
401
- function useRoomManager() {
402
- const manager = useContext(RoomManagerContext);
403
- if (manager === null) {
404
- throw new Error("CommentsRoomProvider is missing from the React tree.");
405
- }
406
- return manager;
407
- }
408
- function getUseThreadsRevalidationManager(options, roomManager) {
409
- const key = stringify(options);
410
- const revalidationManager = roomManager.getRevalidationManager(key);
411
- if (!revalidationManager) {
412
- const useThreadsRevalidationManager = createUseThreadsRevalidationManager(
413
- options,
414
- roomManager
415
- );
416
- roomManager.setRevalidationmanager(key, useThreadsRevalidationManager);
417
- return useThreadsRevalidationManager;
418
- }
419
- return revalidationManager;
426
+ function connectionIdSelector(others) {
427
+ return others.map((user) => user.connectionId);
420
428
  }
421
- function useThreadsFetcher() {
422
- const fetcher = useContext(FetcherContext);
423
- if (fetcher === null) {
424
- throw new Error("CommentsRoomProvider is missing from the React tree.");
425
- }
426
- return fetcher;
427
- }
428
- function useThreads(room, options = { query: { metadata: {} } }) {
429
- const key = useMemo(() => stringify(options), [options]);
430
- const manager = useRoomManager();
431
- const useThreadsRevalidationManager = getUseThreadsRevalidationManager(
432
- options,
433
- manager
434
- );
435
- const fetcher = React2.useCallback(
436
- () => {
437
- return room.getThreads(options);
438
- },
439
- // eslint-disable-next-line react-hooks/exhaustive-deps -- The missing dependency is `options` but `key` and `normalized` are analogous, so we only include `key` as dependency. This helps minimize the number of re-renders as `options` can change on each render
440
- [key, room]
441
- );
442
- const revalidateCache = useRevalidateCache(
443
- useThreadsRevalidationManager,
444
- fetcher
445
- );
446
- useEffect3(() => {
447
- void revalidateCache({ shouldDedupe: true });
448
- }, [revalidateCache]);
449
- useEffect3(() => {
450
- manager.incrementReferenceCount(key);
451
- return () => {
452
- manager.decrementReferenceCount(key);
453
- };
454
- }, [manager, key]);
455
- const cache = _useThreads(room, options);
456
- return cache;
457
- }
458
- function useThreadsSuspense(room, options = { query: { metadata: {} } }) {
459
- const key = useMemo(() => stringify(options), [options]);
460
- const manager = useRoomManager();
461
- const useThreadsRevalidationManager = getUseThreadsRevalidationManager(
462
- options,
463
- manager
464
- );
465
- const fetcher = React2.useCallback(
466
- () => {
467
- return room.getThreads(options);
468
- },
469
- // eslint-disable-next-line react-hooks/exhaustive-deps -- The missing dependency is `options` but `key` and `normalized` are analogous, so we only include `key` as dependency. This helps minimize the number of re-renders as `options` can change on each render
470
- [key, room]
471
- );
472
- const revalidateCache = useRevalidateCache(
473
- useThreadsRevalidationManager,
474
- fetcher
475
- );
476
- useEffect3(() => {
477
- void revalidateCache({ shouldDedupe: true });
478
- }, [revalidateCache]);
479
- useEffect3(() => {
480
- manager.incrementReferenceCount(key);
481
- return () => {
482
- manager.decrementReferenceCount(key);
483
- };
484
- }, [manager, key]);
485
- const cache = _useThreads(room, options);
486
- if (cache.error) {
487
- throw cache.error;
488
- }
489
- if (cache.isLoading || !cache.threads) {
490
- throw revalidateCache({
491
- shouldDedupe: true
492
- });
493
- }
494
- return {
495
- isLoading: false,
496
- threads: cache.threads,
497
- error: cache.error
498
- };
499
- }
500
- function _useThreads(room, options) {
501
- const manager = useRoomManager();
502
- const useThreadsRevalidationManager = getUseThreadsRevalidationManager(
503
- options,
504
- manager
505
- );
506
- return useSyncExternalStoreWithSelector(
507
- store.subscribe,
508
- () => store.getThreads(),
509
- () => store.getThreads(),
510
- (state) => {
511
- const isLoading = useThreadsRevalidationManager.getIsLoading();
512
- if (isLoading) {
513
- return {
514
- isLoading: true
515
- };
516
- }
517
- const options2 = useThreadsRevalidationManager.getOptions();
518
- const error = useThreadsRevalidationManager.getError();
519
- const filtered = state.filter((thread) => {
520
- if (thread.roomId !== room.id)
521
- return false;
522
- const query = options2.query ?? {};
523
- for (const key in query.metadata) {
524
- if (thread.metadata[key] !== query.metadata[key]) {
525
- return false;
526
- }
527
- }
528
- return true;
529
- });
530
- return {
531
- isLoading: false,
532
- threads: filtered,
533
- error
534
- };
535
- }
536
- );
537
- }
538
- function useEditThreadMetadata(room) {
539
- const manager = useRoomManager();
540
- const fetcher = useThreadsFetcher();
541
- const revalidate = useRevalidateCache(manager, fetcher);
542
- const mutate = useMutate(manager, revalidate);
543
- const editThreadMetadata = useCallback3(
544
- (options) => {
545
- const threadId = options.threadId;
546
- const metadata = "metadata" in options ? options.metadata : {};
547
- const threads = getThreads(manager);
548
- const optimisticData = threads.map(
549
- (thread) => thread.id === threadId ? {
550
- ...thread,
551
- metadata: {
552
- ...thread.metadata,
553
- ...metadata
554
- }
555
- } : thread
556
- );
557
- mutate(room.editThreadMetadata({ metadata, threadId }), {
558
- optimisticData
559
- }).catch((err) => {
560
- if (!(err instanceof CommentsApiError)) {
561
- throw err;
562
- }
563
- const error = handleCommentsApiError(err);
564
- errorEventSource.notify(
565
- new EditThreadMetadataError(error, {
566
- roomId: room.id,
567
- threadId,
568
- metadata
569
- })
570
- );
571
- });
572
- },
573
- [room, mutate, manager]
574
- );
575
- return editThreadMetadata;
576
- }
577
- function useCreateThread(room) {
578
- const manager = useRoomManager();
579
- const fetcher = useThreadsFetcher();
580
- const revalidate = useRevalidateCache(manager, fetcher);
581
- const mutate = useMutate(manager, revalidate);
582
- const createThread = useCallback3(
583
- (options) => {
584
- const body = options.body;
585
- const metadata = "metadata" in options ? options.metadata : {};
586
- const threads = getThreads(manager);
587
- const threadId = createThreadId();
588
- const commentId = createCommentId();
589
- const now = /* @__PURE__ */ new Date();
590
- const newComment = {
591
- id: commentId,
592
- threadId,
593
- roomId: room.id,
594
- createdAt: now,
595
- type: "comment",
596
- userId: getCurrentUserId(room),
597
- body,
598
- reactions: []
599
- };
600
- const newThread = {
601
- id: threadId,
602
- type: "thread",
603
- createdAt: now,
604
- roomId: room.id,
605
- metadata,
606
- comments: [newComment]
607
- };
608
- mutate(room.createThread({ threadId, commentId, body, metadata }), {
609
- optimisticData: [...threads, newThread]
610
- }).catch((err) => {
611
- if (!(err instanceof CommentsApiError)) {
612
- throw err;
613
- }
614
- const error = handleCommentsApiError(err);
615
- errorEventSource.notify(
616
- new CreateThreadError(error, {
617
- roomId: room.id,
618
- threadId,
619
- commentId,
620
- body,
621
- metadata
622
- })
623
- );
624
- });
625
- return newThread;
626
- },
627
- [room, mutate, manager]
628
- );
629
- return createThread;
630
- }
631
- function useCreateComment(room) {
632
- const manager = useRoomManager();
633
- const fetcher = useThreadsFetcher();
634
- const revalidate = useRevalidateCache(manager, fetcher);
635
- const mutate = useMutate(manager, revalidate);
636
- const createComment = useCallback3(
637
- ({ threadId, body }) => {
638
- const threads = getThreads(manager);
639
- const commentId = createCommentId();
640
- const now = /* @__PURE__ */ new Date();
641
- const comment = {
642
- id: commentId,
643
- threadId,
644
- roomId: room.id,
645
- type: "comment",
646
- createdAt: now,
647
- userId: getCurrentUserId(room),
648
- body,
649
- reactions: []
650
- };
651
- const optimisticData = threads.map(
652
- (thread) => thread.id === threadId ? {
653
- ...thread,
654
- comments: [...thread.comments, comment]
655
- } : thread
656
- );
657
- mutate(room.createComment({ threadId, commentId, body }), {
658
- optimisticData
659
- }).catch((err) => {
660
- if (!(err instanceof CommentsApiError)) {
661
- throw err;
662
- }
663
- const error = handleCommentsApiError(err);
664
- errorEventSource.notify(
665
- new CreateCommentError(error, {
666
- roomId: room.id,
667
- threadId,
668
- commentId,
669
- body
670
- })
671
- );
672
- });
673
- return comment;
674
- },
675
- [room, mutate, manager]
676
- );
677
- return createComment;
678
- }
679
- function useEditComment(room) {
680
- const manager = useRoomManager();
681
- const fetcher = useThreadsFetcher();
682
- const revalidate = useRevalidateCache(manager, fetcher);
683
- const mutate = useMutate(manager, revalidate);
684
- const editComment = useCallback3(
685
- ({ threadId, commentId, body }) => {
686
- const threads = getThreads(manager);
687
- const now = /* @__PURE__ */ new Date();
688
- const optimisticData = threads.map(
689
- (thread) => thread.id === threadId ? {
690
- ...thread,
691
- comments: thread.comments.map(
692
- (comment) => comment.id === commentId ? {
693
- ...comment,
694
- editedAt: now,
695
- body
696
- } : comment
697
- )
698
- } : thread
699
- );
700
- mutate(room.editComment({ threadId, commentId, body }), {
701
- optimisticData
702
- }).catch((err) => {
703
- if (!(err instanceof CommentsApiError)) {
704
- throw err;
705
- }
706
- const error = handleCommentsApiError(err);
707
- errorEventSource.notify(
708
- new EditCommentError(error, {
709
- roomId: room.id,
710
- threadId,
711
- commentId,
712
- body
713
- })
714
- );
715
- });
716
- },
717
- [room, mutate, manager]
718
- );
719
- return editComment;
720
- }
721
- function useDeleteComment(room) {
722
- const manager = useRoomManager();
723
- const fetcher = useThreadsFetcher();
724
- const revalidate = useRevalidateCache(manager, fetcher);
725
- const mutate = useMutate(manager, revalidate);
726
- const deleteComment = useCallback3(
727
- ({ threadId, commentId }) => {
728
- const threads = getThreads(manager);
729
- const now = /* @__PURE__ */ new Date();
730
- const newThreads = [];
731
- for (const thread of threads) {
732
- if (thread.id === threadId) {
733
- const newThread = {
734
- ...thread,
735
- comments: thread.comments.map(
736
- (comment) => comment.id === commentId ? {
737
- ...comment,
738
- deletedAt: now,
739
- body: void 0
740
- } : comment
741
- )
742
- };
743
- if (newThread.comments.some(
744
- (comment) => comment.deletedAt === void 0
745
- )) {
746
- newThreads.push(newThread);
747
- }
748
- } else {
749
- newThreads.push(thread);
750
- }
751
- }
752
- mutate(room.deleteComment({ threadId, commentId }), {
753
- optimisticData: newThreads
754
- }).catch((err) => {
755
- if (!(err instanceof CommentsApiError)) {
756
- throw err;
757
- }
758
- const error = handleCommentsApiError(err);
759
- errorEventSource.notify(
760
- new DeleteCommentError(error, {
761
- roomId: room.id,
762
- threadId,
763
- commentId
764
- })
765
- );
766
- });
767
- },
768
- [room, mutate, manager]
769
- );
770
- return deleteComment;
771
- }
772
- function useAddReaction(room) {
773
- const manager = useRoomManager();
774
- const fetcher = useThreadsFetcher();
775
- const revalidate = useRevalidateCache(manager, fetcher);
776
- const mutate = useMutate(manager, revalidate);
777
- const createComment = useCallback3(
778
- ({ threadId, commentId, emoji }) => {
779
- const threads = getThreads(manager);
780
- const now = /* @__PURE__ */ new Date();
781
- const userId = getCurrentUserId(room);
782
- const optimisticData = threads.map(
783
- (thread) => thread.id === threadId ? {
784
- ...thread,
785
- comments: thread.comments.map((comment) => {
786
- if (comment.id !== commentId) {
787
- return comment;
788
- }
789
- let reactions;
790
- if (comment.reactions.some(
791
- (reaction) => reaction.emoji === emoji
792
- )) {
793
- reactions = comment.reactions.map(
794
- (reaction) => reaction.emoji === emoji ? {
795
- ...reaction,
796
- users: [...reaction.users, { id: userId }]
797
- } : reaction
798
- );
799
- } else {
800
- reactions = [
801
- ...comment.reactions,
802
- {
803
- emoji,
804
- createdAt: now,
805
- users: [{ id: userId }]
806
- }
807
- ];
808
- }
809
- return {
810
- ...comment,
811
- reactions
812
- };
813
- })
814
- } : thread
815
- );
816
- mutate(room.addReaction({ threadId, commentId, emoji }), {
817
- optimisticData
818
- }).catch((err) => {
819
- if (!(err instanceof CommentsApiError)) {
820
- throw err;
821
- }
822
- const error = handleCommentsApiError(err);
823
- errorEventSource.notify(
824
- new AddReactionError(error, {
825
- roomId: room.id,
826
- threadId,
827
- commentId,
828
- emoji
829
- })
830
- );
831
- });
832
- },
833
- [room, mutate, manager]
834
- );
835
- return createComment;
836
- }
837
- function useRemoveReaction(room) {
838
- const manager = useRoomManager();
839
- const fetcher = useThreadsFetcher();
840
- const revalidate = useRevalidateCache(manager, fetcher);
841
- const mutate = useMutate(manager, revalidate);
842
- const createComment = useCallback3(
843
- ({ threadId, commentId, emoji }) => {
844
- const threads = getThreads(manager);
845
- const userId = getCurrentUserId(room);
846
- const optimisticData = threads.map(
847
- (thread) => thread.id === threadId ? {
848
- ...thread,
849
- comments: thread.comments.map((comment) => {
850
- if (comment.id !== commentId) {
851
- return comment;
852
- }
853
- const reactionIndex = comment.reactions.findIndex(
854
- (reaction) => reaction.emoji === emoji
855
- );
856
- let reactions = comment.reactions;
857
- if (reactionIndex >= 0 && comment.reactions[reactionIndex].users.some(
858
- (user) => user.id === userId
859
- )) {
860
- if (comment.reactions[reactionIndex].users.length <= 1) {
861
- reactions = [...comment.reactions];
862
- reactions.splice(reactionIndex, 1);
863
- } else {
864
- reactions[reactionIndex] = {
865
- ...reactions[reactionIndex],
866
- users: reactions[reactionIndex].users.filter(
867
- (user) => user.id !== userId
868
- )
869
- };
870
- }
871
- }
872
- return {
873
- ...comment,
874
- reactions
875
- };
876
- })
877
- } : thread
878
- );
879
- mutate(room.removeReaction({ threadId, commentId, emoji }), {
880
- optimisticData
881
- }).catch((err) => {
882
- if (!(err instanceof CommentsApiError)) {
883
- throw err;
884
- }
885
- const error = handleCommentsApiError(err);
886
- errorEventSource.notify(
887
- new RemoveReactionError(error, {
888
- roomId: room.id,
889
- threadId,
890
- commentId,
891
- emoji
892
- })
893
- );
894
- });
895
- },
896
- [room, mutate, manager]
897
- );
898
- return createComment;
899
- }
900
- return {
901
- CommentsRoomProvider,
902
- useThreads,
903
- useThreadsSuspense,
904
- useEditThreadMetadata,
905
- useCreateThread,
906
- useCreateComment,
907
- useEditComment,
908
- useDeleteComment,
909
- useAddReaction,
910
- useRemoveReaction
911
- };
912
- }
913
- function createOptimisticId(prefix) {
914
- return `${prefix}_${nanoid()}`;
915
- }
916
- function createThreadId() {
917
- return createOptimisticId(THREAD_ID_PREFIX);
918
- }
919
- function createCommentId() {
920
- return createOptimisticId(COMMENT_ID_PREFIX);
921
- }
922
- function getCurrentUserId(room) {
923
- const self = room.getSelf();
924
- if (self === null || self.id === void 0) {
925
- return "anonymous";
926
- } else {
927
- return self.id;
928
- }
929
- }
930
- function handleCommentsApiError(err) {
931
- const message = `Request failed with status ${err.status}: ${err.message}`;
932
- if (err.details?.error === "FORBIDDEN") {
933
- const detailedMessage = [message, err.details.suggestion, err.details.docs].filter(Boolean).join("\n");
934
- console.error(detailedMessage);
935
- }
936
- return new Error(message);
937
- }
938
- function createRoomRevalidationManager(roomId, {
939
- getCache,
940
- setCache
941
- }) {
942
- let request;
943
- let error;
944
- let mutation;
945
- const revalidationManagerByOptions = /* @__PURE__ */ new Map();
946
- const referenceCountByOptions = /* @__PURE__ */ new Map();
947
- return {
948
- // Cache
949
- getCache() {
950
- const threads = getCache();
951
- const filtered = threads.filter((thread) => thread.roomId === roomId);
952
- return filtered;
953
- },
954
- setCache(value) {
955
- for (const key of revalidationManagerByOptions.keys()) {
956
- if (referenceCountByOptions.get(key) === 0) {
957
- revalidationManagerByOptions.delete(key);
958
- referenceCountByOptions.delete(key);
959
- }
960
- }
961
- const sorted = value.sort(
962
- (a, b) => new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime()
963
- );
964
- const threads = getCache();
965
- const newThreads = threads.filter((thread) => thread.roomId !== roomId).concat(sorted);
966
- setCache(newThreads);
967
- },
968
- // Request
969
- getRequest() {
970
- return request;
971
- },
972
- setRequest(value) {
973
- request = value;
974
- },
975
- // Error
976
- getError() {
977
- return error;
978
- },
979
- setError(err) {
980
- error = err;
981
- },
982
- // Mutation
983
- getMutation() {
984
- return mutation;
985
- },
986
- setMutation(info) {
987
- mutation = info;
988
- },
989
- getRevalidationManagers() {
990
- return Array.from(revalidationManagerByOptions.entries());
991
- },
992
- getRevalidationManager(key) {
993
- return revalidationManagerByOptions.get(key);
994
- },
995
- setRevalidationmanager(key, manager) {
996
- revalidationManagerByOptions.set(key, manager);
997
- },
998
- getTotalReferenceCount() {
999
- return Array.from(referenceCountByOptions.values()).reduce(
1000
- (acc, count) => acc + count,
1001
- 0
1002
- );
1003
- },
1004
- incrementReferenceCount(key) {
1005
- const count = referenceCountByOptions.get(key) ?? 0;
1006
- referenceCountByOptions.set(key, count + 1);
1007
- },
1008
- decrementReferenceCount(key) {
1009
- const count = referenceCountByOptions.get(key) ?? 0;
1010
- referenceCountByOptions.set(key, count - 1);
1011
- },
1012
- getReferenceCount(key) {
1013
- return referenceCountByOptions.get(key) ?? 0;
1014
- }
1015
- };
1016
- }
1017
- function createClientCacheStore() {
1018
- let threads = [];
1019
- const threadsEventSource = makeEventSource();
1020
- return {
1021
- getThreads() {
1022
- return threads;
1023
- },
1024
- setThreads(value) {
1025
- threads = value;
1026
- threadsEventSource.notify(threads);
1027
- },
1028
- subscribe(callback) {
1029
- return threadsEventSource.subscribe(callback);
1030
- }
1031
- };
1032
- }
1033
- function createUseThreadsRevalidationManager(options, manager) {
1034
- let isLoading = true;
1035
- let request;
1036
- let error;
1037
- return {
1038
- // Cache
1039
- getCache() {
1040
- return void 0;
1041
- },
1042
- setCache(value) {
1043
- const cache = new Map(
1044
- (manager.getCache() ?? []).map((thread) => [thread.id, thread])
1045
- );
1046
- for (const thread of value) {
1047
- cache.set(thread.id, thread);
1048
- }
1049
- manager.setCache(Array.from(cache.values()));
1050
- isLoading = false;
1051
- },
1052
- // Request
1053
- getRequest() {
1054
- return request;
1055
- },
1056
- setRequest(value) {
1057
- request = value;
1058
- },
1059
- // Error
1060
- getError() {
1061
- return error;
1062
- },
1063
- setError(err) {
1064
- error = err;
1065
- isLoading = false;
1066
- const cache = manager.getCache();
1067
- manager.setCache(cache);
1068
- },
1069
- // Mutation
1070
- getMutation() {
1071
- return void 0;
1072
- },
1073
- setMutation(_) {
1074
- return;
1075
- },
1076
- getOptions() {
1077
- return options;
1078
- },
1079
- getIsLoading() {
1080
- return isLoading;
1081
- },
1082
- setIsLoading(value) {
1083
- isLoading = value;
1084
- }
1085
- };
1086
- }
1087
- function getPollingInterval(isBrowserOnline, isDocumentVisible, isRoomConnected) {
1088
- if (!isBrowserOnline || !isDocumentVisible)
1089
- return;
1090
- if (isRoomConnected)
1091
- return POLLING_INTERVAL_REALTIME;
1092
- return POLLING_INTERVAL;
1093
- }
1094
-
1095
- // src/comments/lib/use-debounce.ts
1096
- import { useEffect as useEffect4, useRef as useRef3, useState as useState2 } from "react";
1097
- var DEFAULT_DELAY = 500;
1098
- function useDebounce(value, delay = DEFAULT_DELAY) {
1099
- const timeout = useRef3();
1100
- const [debouncedValue, setDebouncedValue] = useState2(value);
1101
- useEffect4(() => {
1102
- if (delay === false) {
1103
- return;
1104
- }
1105
- if (timeout.current === void 0) {
1106
- setDebouncedValue(value);
1107
- }
1108
- timeout.current = window.setTimeout(() => {
1109
- setDebouncedValue(value);
1110
- timeout.current = void 0;
1111
- }, delay);
1112
- return () => {
1113
- window.clearTimeout(timeout.current);
1114
- };
1115
- }, [value, delay]);
1116
- return debouncedValue;
1117
- }
1118
-
1119
- // src/lib/use-async-cache.ts
1120
- import { useCallback as useCallback4, useEffect as useEffect5, useMemo as useMemo2, useRef as useRef4 } from "react";
1121
- import { useSyncExternalStore as useSyncExternalStore4 } from "use-sync-external-store/shim/index.js";
1122
-
1123
- // src/lib/use-initial.ts
1124
- import { useState as useState3 } from "react";
1125
- function useInitial(value) {
1126
- return useState3(value)[0];
1127
- }
1128
-
1129
- // src/lib/use-async-cache.ts
1130
- var INITIAL_ASYNC_STATE = {
1131
- isLoading: false,
1132
- data: void 0,
1133
- error: void 0
1134
- };
1135
- var noop = () => {
1136
- };
1137
- function useAsyncCache(cache, key, options) {
1138
- const frozenOptions = useInitial(options);
1139
- const cacheItem = useMemo2(() => {
1140
- if (key === null || !cache) {
1141
- return null;
1142
- }
1143
- const cacheItem2 = cache.create(key);
1144
- void cacheItem2.get();
1145
- return cacheItem2;
1146
- }, [cache, key]);
1147
- const subscribe2 = useCallback4(
1148
- (callback) => cacheItem?.subscribe(callback) ?? noop,
1149
- [cacheItem]
1150
- );
1151
- const getState = useCallback4(
1152
- () => cacheItem?.getState() ?? INITIAL_ASYNC_STATE,
1153
- [cacheItem]
1154
- );
1155
- const revalidate = useCallback4(() => cacheItem?.revalidate(), [cacheItem]);
1156
- const state = useSyncExternalStore4(subscribe2, getState, getState);
1157
- const previousData = useRef4();
1158
- let data = state.data;
1159
- useEffect5(() => {
1160
- previousData.current = { key, data: state.data };
1161
- }, [key, state.data]);
1162
- if (!cacheItem) {
1163
- return {
1164
- isLoading: false,
1165
- data: void 0,
1166
- error: void 0,
1167
- getState,
1168
- revalidate
1169
- };
1170
- }
1171
- if (frozenOptions?.suspense) {
1172
- const error = getState().error;
1173
- if (error) {
1174
- throw error;
1175
- } else if (getState().isLoading) {
1176
- throw new Promise((resolve) => {
1177
- cacheItem.subscribeOnce((state2) => {
1178
- if (!state2.isLoading) {
1179
- resolve();
1180
- }
1181
- });
1182
- });
1183
- }
1184
- }
1185
- if (state.isLoading && frozenOptions?.keepPreviousDataWhileLoading && typeof state.data === "undefined" && previousData.current?.key !== key && typeof previousData.current?.data !== "undefined") {
1186
- data = previousData.current.data;
1187
- }
1188
- return {
1189
- isLoading: state.isLoading,
1190
- data,
1191
- error: state.error,
1192
- getState,
1193
- revalidate
1194
- };
1195
- }
1196
-
1197
- // src/lib/use-latest.ts
1198
- import { useEffect as useEffect6, useRef as useRef5 } from "react";
1199
- function useLatest(value) {
1200
- const ref = useRef5(value);
1201
- useEffect6(() => {
1202
- ref.current = value;
1203
- }, [value]);
1204
- return ref;
1205
- }
1206
-
1207
- // src/lib/use-rerender.ts
1208
- import { useReducer } from "react";
1209
- function useRerender() {
1210
- const [, update] = useReducer(
1211
- // This implementation works by incrementing a hidden counter value that is
1212
- // never consumed. Simply incrementing the counter changes the component's
1213
- // state and, thus, trigger a re-render.
1214
- (x) => x + 1,
1215
- 0
1216
- );
1217
- return update;
1218
- }
1219
-
1220
- // src/factory.tsx
1221
- var noop2 = () => {
1222
- };
1223
- var identity = (x) => x;
1224
- var missing_unstable_batchedUpdates = (reactVersion, roomId) => `We noticed you\u2019re using React ${reactVersion}. Please pass unstable_batchedUpdates at the RoomProvider level until you\u2019re ready to upgrade to React 18:
1225
-
1226
- import { unstable_batchedUpdates } from "react-dom"; // or "react-native"
1227
-
1228
- <RoomProvider id=${JSON.stringify(
1229
- roomId
1230
- )} ... unstable_batchedUpdates={unstable_batchedUpdates}>
1231
- ...
1232
- </RoomProvider>
1233
-
1234
- Why? Please see https://liveblocks.io/docs/platform/troubleshooting#stale-props-zombie-child for more information`;
1235
- var superfluous_unstable_batchedUpdates = "You don\u2019t need to pass unstable_batchedUpdates to RoomProvider anymore, since you\u2019re on React 18+ already.";
1236
- function useSyncExternalStore3(s, gs, gss) {
1237
- return useSyncExternalStoreWithSelector2(s, gs, gss, identity);
1238
- }
1239
- var STABLE_EMPTY_LIST = Object.freeze([]);
1240
- function alwaysEmptyList() {
1241
- return STABLE_EMPTY_LIST;
1242
- }
1243
- function alwaysNull() {
1244
- return null;
1245
- }
1246
- function makeMutationContext(room) {
1247
- const errmsg = "This mutation cannot be used until connected to the Liveblocks room";
1248
- return {
1249
- get storage() {
1250
- const mutableRoot = room.getStorageSnapshot();
1251
- if (mutableRoot === null) {
1252
- throw new Error(errmsg);
1253
- }
1254
- return mutableRoot;
1255
- },
1256
- get self() {
1257
- const self = room.getSelf();
1258
- if (self === null) {
1259
- throw new Error(errmsg);
1260
- }
1261
- return self;
1262
- },
1263
- get others() {
1264
- const others = room.getOthers();
1265
- if (room.getSelf() === null) {
1266
- throw new Error(errmsg);
1267
- }
1268
- return others;
1269
- },
1270
- setMyPresence: room.updatePresence
1271
- };
1272
- }
1273
- var hasWarnedIfNoResolveUsers = false;
1274
- function warnIfNoResolveUsers(usersCache) {
1275
- if (!hasWarnedIfNoResolveUsers && !usersCache && process.env.NODE_ENV !== "production") {
1276
- console.warn(
1277
- "Set the resolveUsers option in createRoomContext to specify user info."
1278
- );
1279
- hasWarnedIfNoResolveUsers = true;
1280
- }
1281
- }
1282
- var ContextBundle = React3.createContext(null);
1283
- function useRoomContextBundle() {
1284
- const bundle = React3.useContext(ContextBundle);
1285
- if (bundle === null) {
1286
- throw new Error("RoomProvider is missing from the React tree.");
1287
- }
1288
- return bundle;
1289
- }
1290
- function createRoomContext(client, options) {
1291
- const RoomContext = React3.createContext(null);
1292
- const commentsErrorEventSource = makeEventSource2();
1293
- const { CommentsRoomProvider, ...commentsRoom } = createCommentsRoom(commentsErrorEventSource);
1294
- function RoomProviderOuter(props) {
1295
- const [cache] = React3.useState(
1296
- () => /* @__PURE__ */ new Map()
1297
- );
1298
- const stableEnterRoom = React3.useCallback(
1299
- (roomId, options2) => {
1300
- const cached = cache.get(roomId);
1301
- if (cached)
1302
- return cached;
1303
- const rv = client.enterRoom(
1304
- roomId,
1305
- options2
1306
- );
1307
- const origLeave = rv.leave;
1308
- rv.leave = () => {
1309
- origLeave();
1310
- cache.delete(roomId);
1311
- };
1312
- cache.set(roomId, rv);
1313
- return rv;
1314
- },
1315
- [cache]
1316
- );
1317
- return /* @__PURE__ */ React3.createElement(RoomProviderInner, { ...props, stableEnterRoom });
1318
- }
1319
- function RoomProviderInner(props) {
1320
- const { id: roomId, stableEnterRoom } = props;
1321
- if (process.env.NODE_ENV !== "production") {
1322
- if (!roomId) {
1323
- throw new Error(
1324
- "RoomProvider id property is required. For more information: https://liveblocks.io/docs/errors/liveblocks-react/RoomProvider-id-property-is-required"
1325
- );
1326
- }
1327
- if (typeof roomId !== "string") {
1328
- throw new Error("RoomProvider id property should be a string.");
1329
- }
1330
- const majorReactVersion = parseInt(React3.version) || 1;
1331
- const oldReactVersion = majorReactVersion < 18;
1332
- errorIf(
1333
- oldReactVersion && props.unstable_batchedUpdates === void 0,
1334
- missing_unstable_batchedUpdates(majorReactVersion, roomId)
1335
- );
1336
- deprecateIf(
1337
- !oldReactVersion && props.unstable_batchedUpdates !== void 0,
1338
- superfluous_unstable_batchedUpdates
1339
- );
1340
- }
1341
- const frozenProps = useInitial({
1342
- initialPresence: props.initialPresence,
1343
- initialStorage: props.initialStorage,
1344
- unstable_batchedUpdates: props.unstable_batchedUpdates,
1345
- autoConnect: props.autoConnect ?? props.shouldInitiallyConnect ?? typeof window !== "undefined"
1346
- });
1347
- const [{ room }, setRoomLeavePair] = React3.useState(
1348
- () => stableEnterRoom(roomId, {
1349
- ...frozenProps,
1350
- autoConnect: false
1351
- // Deliberately using false here on the first render, see below
1352
- })
1353
- );
1354
- React3.useEffect(() => {
1355
- const pair = stableEnterRoom(roomId, frozenProps);
1356
- setRoomLeavePair(pair);
1357
- const { room: room2, leave } = pair;
1358
- if (frozenProps.autoConnect) {
1359
- room2.connect();
1360
- }
1361
- return () => {
1362
- leave();
1363
- };
1364
- }, [roomId, frozenProps, stableEnterRoom]);
1365
- return /* @__PURE__ */ React3.createElement(RoomContext.Provider, { value: room }, /* @__PURE__ */ React3.createElement(CommentsRoomProvider, { room }, /* @__PURE__ */ React3.createElement(
1366
- ContextBundle.Provider,
1367
- {
1368
- value: internalBundle
1369
- },
1370
- props.children
1371
- )));
1372
- }
1373
- function connectionIdSelector(others) {
1374
- return others.map((user) => user.connectionId);
1375
- }
1376
- function useRoom() {
1377
- const room = React3.useContext(RoomContext);
1378
- if (room === null) {
1379
- throw new Error("RoomProvider is missing from the React tree.");
429
+ function useRoom() {
430
+ const room = React2.useContext(RoomContext);
431
+ if (room === null) {
432
+ throw new Error("RoomProvider is missing from the React tree.");
1380
433
  }
1381
434
  return room;
1382
435
  }
1383
436
  function useStatus() {
1384
437
  const room = useRoom();
1385
- const subscribe2 = room.events.status.subscribe;
1386
- const getSnapshot2 = room.getStatus;
438
+ const subscribe = room.events.status.subscribe;
439
+ const getSnapshot = room.getStatus;
1387
440
  const getServerSnapshot = room.getStatus;
1388
- return useSyncExternalStore3(subscribe2, getSnapshot2, getServerSnapshot);
441
+ return useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
1389
442
  }
1390
443
  function useMyPresence() {
1391
444
  const room = useRoom();
1392
- const subscribe2 = room.events.myPresence.subscribe;
1393
- const getSnapshot2 = room.getPresence;
1394
- const presence = useSyncExternalStore3(subscribe2, getSnapshot2, getSnapshot2);
445
+ const subscribe = room.events.myPresence.subscribe;
446
+ const getSnapshot = room.getPresence;
447
+ const presence = useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
1395
448
  const setPresence = room.updatePresence;
1396
449
  return [presence, setPresence];
1397
450
  }
@@ -1400,12 +453,12 @@ function createRoomContext(client, options) {
1400
453
  }
1401
454
  function useOthers(selector, isEqual) {
1402
455
  const room = useRoom();
1403
- const subscribe2 = room.events.others.subscribe;
1404
- const getSnapshot2 = room.getOthers;
456
+ const subscribe = room.events.others.subscribe;
457
+ const getSnapshot = room.getOthers;
1405
458
  const getServerSnapshot = alwaysEmptyList;
1406
- return useSyncExternalStoreWithSelector2(
1407
- subscribe2,
1408
- getSnapshot2,
459
+ return useSyncExternalStoreWithSelector(
460
+ subscribe,
461
+ getSnapshot,
1409
462
  getServerSnapshot,
1410
463
  selector ?? identity,
1411
464
  isEqual
@@ -1415,13 +468,13 @@ function createRoomContext(client, options) {
1415
468
  return useOthers(connectionIdSelector, shallow);
1416
469
  }
1417
470
  function useOthersMapped(itemSelector, itemIsEqual) {
1418
- const wrappedSelector = React3.useCallback(
471
+ const wrappedSelector = React2.useCallback(
1419
472
  (others) => others.map(
1420
473
  (other) => [other.connectionId, itemSelector(other)]
1421
474
  ),
1422
475
  [itemSelector]
1423
476
  );
1424
- const wrappedIsEqual = React3.useCallback(
477
+ const wrappedIsEqual = React2.useCallback(
1425
478
  (a, b) => {
1426
479
  const eq = itemIsEqual ?? Object.is;
1427
480
  return a.length === b.length && a.every((atuple, index) => {
@@ -1435,7 +488,7 @@ function createRoomContext(client, options) {
1435
488
  }
1436
489
  const NOT_FOUND = Symbol();
1437
490
  function useOther(connectionId, selector, isEqual) {
1438
- const wrappedSelector = React3.useCallback(
491
+ const wrappedSelector = React2.useCallback(
1439
492
  (others) => {
1440
493
  const other2 = others.find(
1441
494
  (other3) => other3.connectionId === connectionId
@@ -1444,7 +497,7 @@ function createRoomContext(client, options) {
1444
497
  },
1445
498
  [connectionId, selector]
1446
499
  );
1447
- const wrappedIsEqual = React3.useCallback(
500
+ const wrappedIsEqual = React2.useCallback(
1448
501
  (prev, curr) => {
1449
502
  if (prev === NOT_FOUND || curr === NOT_FOUND) {
1450
503
  return prev === curr;
@@ -1464,7 +517,7 @@ function createRoomContext(client, options) {
1464
517
  }
1465
518
  function useBroadcastEvent() {
1466
519
  const room = useRoom();
1467
- return React3.useCallback(
520
+ return React2.useCallback(
1468
521
  (event, options2 = { shouldQueueEventIfNotReady: false }) => {
1469
522
  room.broadcastEvent(event, options2);
1470
523
  },
@@ -1474,7 +527,7 @@ function createRoomContext(client, options) {
1474
527
  function useOthersListener(callback) {
1475
528
  const room = useRoom();
1476
529
  const savedCallback = useLatest(callback);
1477
- React3.useEffect(
530
+ React2.useEffect(
1478
531
  () => room.events.others.subscribe((event) => savedCallback.current(event)),
1479
532
  [room, savedCallback]
1480
533
  );
@@ -1482,7 +535,7 @@ function createRoomContext(client, options) {
1482
535
  function useLostConnectionListener(callback) {
1483
536
  const room = useRoom();
1484
537
  const savedCallback = useLatest(callback);
1485
- React3.useEffect(
538
+ React2.useEffect(
1486
539
  () => room.events.lostConnection.subscribe(
1487
540
  (event) => savedCallback.current(event)
1488
541
  ),
@@ -1492,7 +545,7 @@ function createRoomContext(client, options) {
1492
545
  function useErrorListener(callback) {
1493
546
  const room = useRoom();
1494
547
  const savedCallback = useLatest(callback);
1495
- React3.useEffect(
548
+ React2.useEffect(
1496
549
  () => room.events.error.subscribe((e) => savedCallback.current(e)),
1497
550
  [room, savedCallback]
1498
551
  );
@@ -1500,7 +553,7 @@ function createRoomContext(client, options) {
1500
553
  function useEventListener(callback) {
1501
554
  const room = useRoom();
1502
555
  const savedCallback = useLatest(callback);
1503
- React3.useEffect(() => {
556
+ React2.useEffect(() => {
1504
557
  const listener = (eventData) => {
1505
558
  savedCallback.current(eventData);
1506
559
  };
@@ -1509,17 +562,17 @@ function createRoomContext(client, options) {
1509
562
  }
1510
563
  function useSelf(maybeSelector, isEqual) {
1511
564
  const room = useRoom();
1512
- const subscribe2 = room.events.self.subscribe;
1513
- const getSnapshot2 = room.getSelf;
565
+ const subscribe = room.events.self.subscribe;
566
+ const getSnapshot = room.getSelf;
1514
567
  const selector = maybeSelector ?? identity;
1515
- const wrappedSelector = React3.useCallback(
568
+ const wrappedSelector = React2.useCallback(
1516
569
  (me) => me !== null ? selector(me) : null,
1517
570
  [selector]
1518
571
  );
1519
572
  const getServerSnapshot = alwaysNull;
1520
- return useSyncExternalStoreWithSelector2(
1521
- subscribe2,
1522
- getSnapshot2,
573
+ return useSyncExternalStoreWithSelector(
574
+ subscribe,
575
+ getSnapshot,
1523
576
  getServerSnapshot,
1524
577
  wrappedSelector,
1525
578
  isEqual
@@ -1527,10 +580,10 @@ function createRoomContext(client, options) {
1527
580
  }
1528
581
  function useMutableStorageRoot() {
1529
582
  const room = useRoom();
1530
- const subscribe2 = room.events.storageDidLoad.subscribeOnce;
1531
- const getSnapshot2 = room.getStorageSnapshot;
583
+ const subscribe = room.events.storageDidLoad.subscribeOnce;
584
+ const getSnapshot = room.getStorageSnapshot;
1532
585
  const getServerSnapshot = alwaysNull;
1533
- return useSyncExternalStore3(subscribe2, getSnapshot2, getServerSnapshot);
586
+ return useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
1534
587
  }
1535
588
  function useStorageRoot() {
1536
589
  return [useMutableStorageRoot()];
@@ -1546,15 +599,15 @@ function createRoomContext(client, options) {
1546
599
  }
1547
600
  function useCanUndo() {
1548
601
  const room = useRoom();
1549
- const subscribe2 = room.events.history.subscribe;
602
+ const subscribe = room.events.history.subscribe;
1550
603
  const canUndo = room.history.canUndo;
1551
- return useSyncExternalStore3(subscribe2, canUndo, canUndo);
604
+ return useSyncExternalStore(subscribe, canUndo, canUndo);
1552
605
  }
1553
606
  function useCanRedo() {
1554
607
  const room = useRoom();
1555
- const subscribe2 = room.events.history.subscribe;
608
+ const subscribe = room.events.history.subscribe;
1556
609
  const canRedo = room.history.canRedo;
1557
- return useSyncExternalStore3(subscribe2, canRedo, canRedo);
610
+ return useSyncExternalStore(subscribe, canRedo, canRedo);
1558
611
  }
1559
612
  function useBatch() {
1560
613
  return useRoom().batch;
@@ -1563,7 +616,7 @@ function createRoomContext(client, options) {
1563
616
  const room = useRoom();
1564
617
  const rootOrNull = useMutableStorageRoot();
1565
618
  const rerender = useRerender();
1566
- React3.useEffect(() => {
619
+ React2.useEffect(() => {
1567
620
  if (rootOrNull === null) {
1568
621
  return;
1569
622
  }
@@ -1599,15 +652,15 @@ function createRoomContext(client, options) {
1599
652
  function useStorage(selector, isEqual) {
1600
653
  const room = useRoom();
1601
654
  const rootOrNull = useMutableStorageRoot();
1602
- const wrappedSelector = React3.useCallback(
655
+ const wrappedSelector = React2.useCallback(
1603
656
  (rootOrNull2) => rootOrNull2 !== null ? selector(rootOrNull2) : null,
1604
657
  [selector]
1605
658
  );
1606
- const subscribe2 = React3.useCallback(
1607
- (onStoreChange) => rootOrNull !== null ? room.subscribe(rootOrNull, onStoreChange, { isDeep: true }) : noop2,
659
+ const subscribe = React2.useCallback(
660
+ (onStoreChange) => rootOrNull !== null ? room.subscribe(rootOrNull, onStoreChange, { isDeep: true }) : noop,
1608
661
  [room, rootOrNull]
1609
662
  );
1610
- const getSnapshot2 = React3.useCallback(() => {
663
+ const getSnapshot = React2.useCallback(() => {
1611
664
  if (rootOrNull === null) {
1612
665
  return null;
1613
666
  } else {
@@ -1617,9 +670,9 @@ function createRoomContext(client, options) {
1617
670
  }
1618
671
  }, [rootOrNull]);
1619
672
  const getServerSnapshot = alwaysNull;
1620
- return useSyncExternalStoreWithSelector2(
1621
- subscribe2,
1622
- getSnapshot2,
673
+ return useSyncExternalStoreWithSelector(
674
+ subscribe,
675
+ getSnapshot,
1623
676
  getServerSnapshot,
1624
677
  wrappedSelector,
1625
678
  isEqual
@@ -1655,7 +708,7 @@ function createRoomContext(client, options) {
1655
708
  }
1656
709
  function useMutation(callback, deps) {
1657
710
  const room = useRoom();
1658
- return React3.useMemo(
711
+ return React2.useMemo(
1659
712
  () => {
1660
713
  return (...args) => (
1661
714
  // eslint-disable-next-line @typescript-eslint/no-unsafe-return
@@ -1711,102 +764,841 @@ function createRoomContext(client, options) {
1711
764
  useSuspendUntilStorageLoaded();
1712
765
  return useLegacyKey(key);
1713
766
  }
1714
- function useThreads(options2) {
767
+ const store = client[kInternal].cacheStore;
768
+ function onMutationFailure(innerError, optimisticUpdateId, createPublicError) {
769
+ store.set((state) => ({
770
+ ...state,
771
+ optimisticUpdates: state.optimisticUpdates.filter(
772
+ (update) => update.id !== optimisticUpdateId
773
+ )
774
+ }));
775
+ if (innerError instanceof CommentsApiError) {
776
+ const error = handleApiError(innerError);
777
+ commentsErrorEventSource.notify(createPublicError(error));
778
+ return;
779
+ }
780
+ if (innerError instanceof NotificationsApiError) {
781
+ handleApiError(innerError);
782
+ return;
783
+ }
784
+ throw innerError;
785
+ }
786
+ const requestsCache = /* @__PURE__ */ new Map();
787
+ const poller = makePoller(refreshThreadsAndNotifications);
788
+ async function refreshThreadsAndNotifications() {
789
+ await Promise.allSettled(
790
+ Array.from(requestsCache.entries()).filter(([_, requestCache]) => requestCache.subscribers > 0).map(async ([_, requestCache]) => {
791
+ return requestCache.requestFactory().then(
792
+ (result) => requestCache.onSuccess(result),
793
+ () => {
794
+ }
795
+ );
796
+ })
797
+ );
798
+ }
799
+ function incrementQuerySubscribers(queryKey) {
800
+ const requestCache = requestsCache.get(queryKey);
801
+ if (requestCache === void 0) {
802
+ console2.warn(
803
+ `Internal unexpected behavior. Cannot increase subscriber count for query "${queryKey}"`
804
+ );
805
+ return;
806
+ }
807
+ requestCache.subscribers++;
808
+ poller.start(POLLING_INTERVAL);
809
+ }
810
+ function decrementQuerySubscribers(queryKey) {
811
+ const requestCache = requestsCache.get(queryKey);
812
+ if (requestCache === void 0 || requestCache.subscribers <= 0) {
813
+ console2.warn(
814
+ `Internal unexpected behavior. Cannot decrease subscriber count for query "${queryKey}"`
815
+ );
816
+ return;
817
+ }
818
+ requestCache.subscribers--;
819
+ let totalSubscribers = 0;
820
+ for (const requestCache2 of requestsCache.values()) {
821
+ totalSubscribers += requestCache2.subscribers;
822
+ }
823
+ if (totalSubscribers <= 0) {
824
+ poller.stop();
825
+ }
826
+ }
827
+ async function getOrInitRequest(queryKey, requestFactory, onSuccess) {
828
+ const requestInfo = requestsCache.get(queryKey);
829
+ if (requestInfo !== void 0) {
830
+ return requestInfo.promise;
831
+ }
832
+ const promise = requestFactory();
833
+ requestsCache.set(queryKey, {
834
+ promise,
835
+ requestFactory,
836
+ onSuccess,
837
+ subscribers: 0
838
+ });
839
+ store.setQueryState(queryKey, {
840
+ isLoading: true
841
+ });
842
+ try {
843
+ const result = await promise;
844
+ onSuccess(result);
845
+ } catch (er) {
846
+ store.setQueryState(queryKey, {
847
+ isLoading: false,
848
+ error: er
849
+ });
850
+ }
851
+ poller.start(POLLING_INTERVAL);
852
+ }
853
+ async function getThreadsAndInboxNotifications(room, queryKey, options2) {
854
+ const roomId = room.id;
855
+ return getOrInitRequest(
856
+ queryKey,
857
+ async () => {
858
+ const room2 = client.getRoom(roomId);
859
+ if (room2 === null) {
860
+ return;
861
+ }
862
+ return room2.getThreads(options2);
863
+ },
864
+ (result) => {
865
+ if (result !== void 0) {
866
+ store.updateThreadsAndNotifications(
867
+ result.threads,
868
+ result.inboxNotifications,
869
+ queryKey
870
+ );
871
+ }
872
+ }
873
+ );
874
+ }
875
+ function useThreads(options2 = { query: { metadata: {} } }) {
1715
876
  const room = useRoom();
1716
- return commentsRoom.useThreads(room, options2);
877
+ const queryKey = React2.useMemo(
878
+ () => generateQueryKey(room.id, options2.query),
879
+ [room, options2]
880
+ );
881
+ React2.useEffect(() => {
882
+ void getThreadsAndInboxNotifications(room, queryKey, options2);
883
+ incrementQuerySubscribers(queryKey);
884
+ return () => decrementQuerySubscribers(queryKey);
885
+ }, [room, queryKey]);
886
+ const selector = React2.useCallback(
887
+ (state) => {
888
+ if (state.queries[queryKey] === void 0 || state.queries[queryKey].isLoading) {
889
+ return {
890
+ isLoading: true
891
+ };
892
+ }
893
+ return {
894
+ threads: selectedThreads(room.id, state, options2),
895
+ isLoading: false,
896
+ error: state.queries[queryKey].error
897
+ };
898
+ },
899
+ [room, queryKey]
900
+ // eslint-disable-line react-hooks/exhaustive-deps
901
+ );
902
+ return useSyncExternalStoreWithSelector(
903
+ store.subscribe,
904
+ store.get,
905
+ store.get,
906
+ selector
907
+ );
1717
908
  }
1718
- function useThreadsSuspense(options2) {
909
+ function useThreadsSuspense(options2 = { query: { metadata: {} } }) {
1719
910
  const room = useRoom();
1720
- return commentsRoom.useThreadsSuspense(room, options2);
911
+ const queryKey = React2.useMemo(
912
+ () => generateQueryKey(room.id, options2?.query),
913
+ [room, options2]
914
+ );
915
+ const query = store.get().queries[queryKey];
916
+ if (query === void 0 || query.isLoading) {
917
+ throw getThreadsAndInboxNotifications(room, queryKey, options2);
918
+ }
919
+ if (query.error) {
920
+ throw query.error;
921
+ }
922
+ const selector = React2.useCallback(
923
+ (state) => {
924
+ return {
925
+ threads: selectedThreads(room.id, state, options2),
926
+ isLoading: false
927
+ };
928
+ },
929
+ [room, queryKey]
930
+ // eslint-disable-line react-hooks/exhaustive-deps
931
+ );
932
+ React2.useEffect(() => {
933
+ incrementQuerySubscribers(queryKey);
934
+ return () => {
935
+ decrementQuerySubscribers(queryKey);
936
+ };
937
+ }, [room, queryKey]);
938
+ return useSyncExternalStoreWithSelector(
939
+ store.subscribe,
940
+ store.get,
941
+ store.get,
942
+ selector
943
+ );
1721
944
  }
1722
945
  function useCreateThread() {
1723
946
  const room = useRoom();
1724
- return commentsRoom.useCreateThread(room);
947
+ return React2.useCallback(
948
+ (options2) => {
949
+ const body = options2.body;
950
+ const metadata = "metadata" in options2 ? options2.metadata : {};
951
+ const threadId = createThreadId();
952
+ const commentId = createCommentId();
953
+ const now = /* @__PURE__ */ new Date();
954
+ const newComment = {
955
+ id: commentId,
956
+ threadId,
957
+ roomId: room.id,
958
+ createdAt: now,
959
+ type: "comment",
960
+ userId: getCurrentUserId(room),
961
+ body,
962
+ reactions: []
963
+ };
964
+ const newThread = {
965
+ id: threadId,
966
+ type: "thread",
967
+ createdAt: now,
968
+ roomId: room.id,
969
+ metadata,
970
+ comments: [newComment]
971
+ };
972
+ const optimisticUpdateId = nanoid2();
973
+ store.pushOptimisticUpdate({
974
+ type: "create-thread",
975
+ thread: newThread,
976
+ id: optimisticUpdateId
977
+ });
978
+ room.createThread({ threadId, commentId, body, metadata }).then(
979
+ (thread) => {
980
+ store.set((state) => ({
981
+ ...state,
982
+ threads: {
983
+ ...state.threads,
984
+ [threadId]: thread
985
+ },
986
+ optimisticUpdates: state.optimisticUpdates.filter(
987
+ (update) => update.id !== optimisticUpdateId
988
+ )
989
+ }));
990
+ },
991
+ (err) => onMutationFailure(
992
+ err,
993
+ optimisticUpdateId,
994
+ (err2) => new CreateThreadError(err2, {
995
+ roomId: room.id,
996
+ threadId,
997
+ commentId,
998
+ body,
999
+ metadata
1000
+ })
1001
+ )
1002
+ );
1003
+ return newThread;
1004
+ },
1005
+ [room]
1006
+ );
1725
1007
  }
1726
1008
  function useEditThreadMetadata() {
1727
1009
  const room = useRoom();
1728
- return commentsRoom.useEditThreadMetadata(room);
1010
+ return React2.useCallback(
1011
+ (options2) => {
1012
+ if (!("metadata" in options2)) {
1013
+ return;
1014
+ }
1015
+ const threadId = options2.threadId;
1016
+ const metadata = options2.metadata;
1017
+ const optimisticUpdateId = nanoid2();
1018
+ store.pushOptimisticUpdate({
1019
+ type: "edit-thread-metadata",
1020
+ metadata,
1021
+ id: optimisticUpdateId,
1022
+ threadId
1023
+ });
1024
+ room.editThreadMetadata({ metadata, threadId }).then(
1025
+ (metadata2) => {
1026
+ store.set((state) => ({
1027
+ ...state,
1028
+ threads: {
1029
+ ...state.threads,
1030
+ [threadId]: {
1031
+ ...state.threads[threadId],
1032
+ metadata: {
1033
+ ...state.threads[threadId].metadata,
1034
+ ...metadata2
1035
+ }
1036
+ }
1037
+ },
1038
+ optimisticUpdates: state.optimisticUpdates.filter(
1039
+ (update) => update.id !== optimisticUpdateId
1040
+ )
1041
+ }));
1042
+ },
1043
+ (err) => onMutationFailure(
1044
+ err,
1045
+ optimisticUpdateId,
1046
+ (error) => new EditThreadMetadataError(error, {
1047
+ roomId: room.id,
1048
+ threadId,
1049
+ metadata
1050
+ })
1051
+ )
1052
+ );
1053
+ },
1054
+ [room]
1055
+ );
1729
1056
  }
1730
1057
  function useAddReaction() {
1731
1058
  const room = useRoom();
1732
- return commentsRoom.useAddReaction(room);
1059
+ return React2.useCallback(
1060
+ ({ threadId, commentId, emoji }) => {
1061
+ const now = /* @__PURE__ */ new Date();
1062
+ const userId = getCurrentUserId(room);
1063
+ const optimisticUpdateId = nanoid2();
1064
+ store.pushOptimisticUpdate({
1065
+ type: "add-reaction",
1066
+ threadId,
1067
+ commentId,
1068
+ emoji,
1069
+ userId,
1070
+ createdAt: now,
1071
+ id: optimisticUpdateId
1072
+ });
1073
+ room.addReaction({ threadId, commentId, emoji }).then(
1074
+ (addedReaction) => {
1075
+ store.set((state) => ({
1076
+ ...state,
1077
+ threads: {
1078
+ ...state.threads,
1079
+ [threadId]: {
1080
+ ...state.threads[threadId],
1081
+ comments: state.threads[threadId].comments.map(
1082
+ (comment) => comment.id === commentId ? {
1083
+ ...comment,
1084
+ reactions: comment.reactions.some(
1085
+ (reaction) => reaction.emoji === addedReaction.emoji
1086
+ ) ? comment.reactions.map(
1087
+ (reaction) => reaction.emoji === addedReaction.emoji ? {
1088
+ ...reaction,
1089
+ users: [
1090
+ ...reaction.users,
1091
+ { id: addedReaction.userId }
1092
+ ]
1093
+ } : reaction
1094
+ ) : [
1095
+ ...comment.reactions,
1096
+ {
1097
+ emoji: addedReaction.emoji,
1098
+ createdAt: addedReaction.createdAt,
1099
+ users: [{ id: addedReaction.userId }]
1100
+ }
1101
+ ]
1102
+ } : comment
1103
+ )
1104
+ }
1105
+ },
1106
+ optimisticUpdates: state.optimisticUpdates.filter(
1107
+ (update) => update.id !== optimisticUpdateId
1108
+ )
1109
+ }));
1110
+ },
1111
+ (err) => onMutationFailure(
1112
+ err,
1113
+ optimisticUpdateId,
1114
+ (error) => new AddReactionError(error, {
1115
+ roomId: room.id,
1116
+ threadId,
1117
+ commentId,
1118
+ emoji
1119
+ })
1120
+ )
1121
+ );
1122
+ },
1123
+ [room]
1124
+ );
1125
+ }
1126
+ function useRemoveReaction() {
1127
+ const room = useRoom();
1128
+ return React2.useCallback(
1129
+ ({ threadId, commentId, emoji }) => {
1130
+ const userId = getCurrentUserId(room);
1131
+ const optimisticUpdateId = nanoid2();
1132
+ store.pushOptimisticUpdate({
1133
+ type: "remove-reaction",
1134
+ threadId,
1135
+ commentId,
1136
+ emoji,
1137
+ userId,
1138
+ id: optimisticUpdateId
1139
+ });
1140
+ room.removeReaction({ threadId, commentId, emoji }).then(
1141
+ () => {
1142
+ store.set((state) => ({
1143
+ ...state,
1144
+ threads: {
1145
+ ...state.threads,
1146
+ [threadId]: {
1147
+ ...state.threads[threadId],
1148
+ comments: state.threads[threadId].comments.map((comment) => {
1149
+ if (comment.id !== commentId) {
1150
+ return comment;
1151
+ }
1152
+ const reactionIndex = comment.reactions.findIndex(
1153
+ (reaction) => reaction.emoji === emoji
1154
+ );
1155
+ let reactions = comment.reactions;
1156
+ if (reactionIndex >= 0 && comment.reactions[reactionIndex].users.some(
1157
+ (user) => user.id === userId
1158
+ )) {
1159
+ if (comment.reactions[reactionIndex].users.length <= 1) {
1160
+ reactions = [...comment.reactions];
1161
+ reactions.splice(reactionIndex, 1);
1162
+ } else {
1163
+ reactions[reactionIndex] = {
1164
+ ...reactions[reactionIndex],
1165
+ users: reactions[reactionIndex].users.filter(
1166
+ (user) => user.id !== userId
1167
+ )
1168
+ };
1169
+ }
1170
+ }
1171
+ return {
1172
+ ...comment,
1173
+ reactions
1174
+ };
1175
+ })
1176
+ }
1177
+ },
1178
+ optimisticUpdates: state.optimisticUpdates.filter(
1179
+ (update) => update.id !== optimisticUpdateId
1180
+ )
1181
+ }));
1182
+ },
1183
+ (err) => onMutationFailure(
1184
+ err,
1185
+ optimisticUpdateId,
1186
+ (error) => new RemoveReactionError(error, {
1187
+ roomId: room.id,
1188
+ threadId,
1189
+ commentId,
1190
+ emoji
1191
+ })
1192
+ )
1193
+ );
1194
+ },
1195
+ [room]
1196
+ );
1197
+ }
1198
+ function useCreateComment() {
1199
+ const room = useRoom();
1200
+ return React2.useCallback(
1201
+ ({ threadId, body }) => {
1202
+ const commentId = createCommentId();
1203
+ const now = /* @__PURE__ */ new Date();
1204
+ const comment = {
1205
+ id: commentId,
1206
+ threadId,
1207
+ roomId: room.id,
1208
+ type: "comment",
1209
+ createdAt: now,
1210
+ userId: getCurrentUserId(room),
1211
+ body,
1212
+ reactions: []
1213
+ };
1214
+ const optimisticUpdateId = nanoid2();
1215
+ const inboxNotification = Object.values(
1216
+ store.get().inboxNotifications
1217
+ ).find((inboxNotification2) => inboxNotification2.threadId === threadId);
1218
+ store.pushOptimisticUpdate({
1219
+ type: "create-comment",
1220
+ comment,
1221
+ id: optimisticUpdateId,
1222
+ inboxNotificationId: inboxNotification?.id
1223
+ });
1224
+ room.createComment({ threadId, commentId, body }).then(
1225
+ (newComment) => {
1226
+ store.set((state) => ({
1227
+ ...state,
1228
+ threads: upsertComment(state.threads, newComment),
1229
+ inboxNotifications: inboxNotification ? {
1230
+ ...state.inboxNotifications,
1231
+ [inboxNotification.id]: {
1232
+ ...inboxNotification,
1233
+ notifiedAt: newComment.createdAt,
1234
+ readAt: newComment.createdAt
1235
+ }
1236
+ } : state.inboxNotifications,
1237
+ optimisticUpdates: state.optimisticUpdates.filter(
1238
+ (update) => update.id !== optimisticUpdateId
1239
+ )
1240
+ }));
1241
+ },
1242
+ (err) => onMutationFailure(
1243
+ err,
1244
+ optimisticUpdateId,
1245
+ (err2) => new CreateCommentError(err2, {
1246
+ roomId: room.id,
1247
+ threadId,
1248
+ commentId,
1249
+ body
1250
+ })
1251
+ )
1252
+ );
1253
+ return comment;
1254
+ },
1255
+ [room]
1256
+ );
1257
+ }
1258
+ function useEditComment() {
1259
+ const room = useRoom();
1260
+ return React2.useCallback(
1261
+ ({ threadId, commentId, body }) => {
1262
+ const now = /* @__PURE__ */ new Date();
1263
+ const optimisticUpdateId = nanoid2();
1264
+ store.pushOptimisticUpdate({
1265
+ type: "edit-comment",
1266
+ threadId,
1267
+ commentId,
1268
+ body,
1269
+ editedAt: now,
1270
+ id: optimisticUpdateId
1271
+ });
1272
+ room.editComment({ threadId, commentId, body }).then(
1273
+ (editedComment) => {
1274
+ store.set((state) => ({
1275
+ ...state,
1276
+ threads: upsertComment(state.threads, editedComment),
1277
+ optimisticUpdates: state.optimisticUpdates.filter(
1278
+ (update) => update.id !== optimisticUpdateId
1279
+ )
1280
+ }));
1281
+ },
1282
+ (err) => onMutationFailure(
1283
+ err,
1284
+ optimisticUpdateId,
1285
+ (error) => new EditCommentError(error, {
1286
+ roomId: room.id,
1287
+ threadId,
1288
+ commentId,
1289
+ body
1290
+ })
1291
+ )
1292
+ );
1293
+ },
1294
+ [room]
1295
+ );
1296
+ }
1297
+ function useDeleteComment() {
1298
+ const room = useRoom();
1299
+ return React2.useCallback(
1300
+ ({ threadId, commentId }) => {
1301
+ const now = /* @__PURE__ */ new Date();
1302
+ const optimisticUpdateId = nanoid2();
1303
+ store.pushOptimisticUpdate({
1304
+ type: "delete-comment",
1305
+ threadId,
1306
+ commentId,
1307
+ deletedAt: now,
1308
+ id: optimisticUpdateId
1309
+ });
1310
+ room.deleteComment({ threadId, commentId }).then(
1311
+ () => {
1312
+ const newThreads = { ...store.get().threads };
1313
+ const thread = newThreads[threadId];
1314
+ if (thread === void 0)
1315
+ return;
1316
+ newThreads[thread.id] = {
1317
+ ...thread,
1318
+ comments: thread.comments.map(
1319
+ (comment) => comment.id === commentId ? {
1320
+ ...comment,
1321
+ deletedAt: now,
1322
+ body: void 0
1323
+ } : comment
1324
+ )
1325
+ };
1326
+ if (!newThreads[thread.id].comments.some(
1327
+ (comment) => comment.deletedAt === void 0
1328
+ )) {
1329
+ delete newThreads[thread.id];
1330
+ }
1331
+ store.set((state) => ({
1332
+ ...state,
1333
+ threads: newThreads,
1334
+ optimisticUpdates: state.optimisticUpdates.filter(
1335
+ (update) => update.id !== optimisticUpdateId
1336
+ )
1337
+ }));
1338
+ },
1339
+ (err) => onMutationFailure(
1340
+ err,
1341
+ optimisticUpdateId,
1342
+ (error) => new DeleteCommentError(error, {
1343
+ roomId: room.id,
1344
+ threadId,
1345
+ commentId
1346
+ })
1347
+ )
1348
+ );
1349
+ },
1350
+ [room]
1351
+ );
1733
1352
  }
1734
- function useRemoveReaction() {
1353
+ const resolveMentionSuggestions = client[kInternal].resolveMentionSuggestions;
1354
+ const mentionSuggestionsCache = /* @__PURE__ */ new Map();
1355
+ function useMentionSuggestions(search) {
1735
1356
  const room = useRoom();
1736
- return commentsRoom.useRemoveReaction(room);
1357
+ const [mentionSuggestions, setMentionSuggestions] = React2.useState();
1358
+ const lastInvokedAt = React2.useRef();
1359
+ React2.useEffect(() => {
1360
+ if (search === void 0 || !resolveMentionSuggestions) {
1361
+ return;
1362
+ }
1363
+ const resolveMentionSuggestionsArgs = { text: search, roomId: room.id };
1364
+ const mentionSuggestionsCacheKey = stringify(
1365
+ resolveMentionSuggestionsArgs
1366
+ );
1367
+ let debounceTimeout;
1368
+ let isCanceled = false;
1369
+ const getMentionSuggestions = async () => {
1370
+ try {
1371
+ lastInvokedAt.current = performance.now();
1372
+ const mentionSuggestions2 = await resolveMentionSuggestions(
1373
+ resolveMentionSuggestionsArgs
1374
+ );
1375
+ if (!isCanceled) {
1376
+ setMentionSuggestions(mentionSuggestions2);
1377
+ mentionSuggestionsCache.set(
1378
+ mentionSuggestionsCacheKey,
1379
+ mentionSuggestions2
1380
+ );
1381
+ }
1382
+ } catch (error) {
1383
+ console2.error(error?.message);
1384
+ }
1385
+ };
1386
+ if (mentionSuggestionsCache.has(mentionSuggestionsCacheKey)) {
1387
+ setMentionSuggestions(
1388
+ mentionSuggestionsCache.get(mentionSuggestionsCacheKey)
1389
+ );
1390
+ } else if (!lastInvokedAt.current || Math.abs(performance.now() - lastInvokedAt.current) > MENTION_SUGGESTIONS_DEBOUNCE) {
1391
+ void getMentionSuggestions();
1392
+ } else {
1393
+ debounceTimeout = window.setTimeout(() => {
1394
+ void getMentionSuggestions();
1395
+ }, MENTION_SUGGESTIONS_DEBOUNCE);
1396
+ }
1397
+ return () => {
1398
+ isCanceled = true;
1399
+ window.clearTimeout(debounceTimeout);
1400
+ };
1401
+ }, [room.id, search]);
1402
+ return mentionSuggestions;
1737
1403
  }
1738
- function useCreateComment() {
1739
- const room = useRoom();
1740
- return commentsRoom.useCreateComment(room);
1404
+ function useThreadSubscription(threadId) {
1405
+ return useSyncExternalStoreWithSelector(
1406
+ store.subscribe,
1407
+ store.get,
1408
+ store.get,
1409
+ (state) => {
1410
+ const inboxNotification = selectedInboxNotifications(state).find(
1411
+ (inboxNotification2) => inboxNotification2.threadId === threadId
1412
+ );
1413
+ const thread = state.threads[threadId];
1414
+ if (inboxNotification === void 0 || thread === void 0) {
1415
+ return {
1416
+ status: "not-subscribed"
1417
+ };
1418
+ }
1419
+ return {
1420
+ status: "subscribed",
1421
+ unreadSince: inboxNotification.readAt
1422
+ };
1423
+ }
1424
+ );
1741
1425
  }
1742
- function useEditComment() {
1743
- const room = useRoom();
1744
- return commentsRoom.useEditComment(room);
1426
+ function useMarkThreadAsRead() {
1427
+ return React2.useCallback((threadId) => {
1428
+ const inboxNotification = Object.values(
1429
+ store.get().inboxNotifications
1430
+ ).find((inboxNotification2) => inboxNotification2.threadId === threadId);
1431
+ if (!inboxNotification)
1432
+ return;
1433
+ const optimisticUpdateId = nanoid2();
1434
+ const now = /* @__PURE__ */ new Date();
1435
+ store.pushOptimisticUpdate({
1436
+ type: "mark-inbox-notification-as-read",
1437
+ id: optimisticUpdateId,
1438
+ inboxNotificationId: inboxNotification.id,
1439
+ readAt: now
1440
+ });
1441
+ client.markInboxNotificationAsRead(inboxNotification.id).then(
1442
+ () => {
1443
+ store.set((state) => ({
1444
+ ...state,
1445
+ inboxNotifications: {
1446
+ ...state.inboxNotifications,
1447
+ [inboxNotification.id]: {
1448
+ ...inboxNotification,
1449
+ readAt: now
1450
+ }
1451
+ },
1452
+ optimisticUpdates: state.optimisticUpdates.filter(
1453
+ (update) => update.id !== optimisticUpdateId
1454
+ )
1455
+ }));
1456
+ },
1457
+ (err) => {
1458
+ onMutationFailure(
1459
+ err,
1460
+ optimisticUpdateId,
1461
+ (error) => new MarkInboxNotificationAsReadError(error, {
1462
+ inboxNotificationId: inboxNotification.id
1463
+ })
1464
+ );
1465
+ return;
1466
+ }
1467
+ );
1468
+ }, []);
1469
+ }
1470
+ function makeNotificationSettingsQueryKey(roomId) {
1471
+ return `${roomId}:NOTIFICATION_SETTINGS`;
1472
+ }
1473
+ async function getInboxNotificationSettings(room, queryKey) {
1474
+ const roomId = room.id;
1475
+ return getOrInitRequest(
1476
+ queryKey,
1477
+ async () => {
1478
+ const room2 = client.getRoom(roomId);
1479
+ if (room2 === null) {
1480
+ return;
1481
+ }
1482
+ return room2.getRoomNotificationSettings();
1483
+ },
1484
+ (settings) => {
1485
+ if (settings !== void 0) {
1486
+ store.set((state) => ({
1487
+ ...state,
1488
+ notificationSettings: {
1489
+ ...state.notificationSettings,
1490
+ [room.id]: settings
1491
+ },
1492
+ queries: {
1493
+ ...state.queries,
1494
+ [queryKey]: {
1495
+ isLoading: false
1496
+ }
1497
+ }
1498
+ }));
1499
+ }
1500
+ }
1501
+ );
1745
1502
  }
1746
- function useDeleteComment() {
1503
+ function useRoomNotificationSettings() {
1747
1504
  const room = useRoom();
1748
- return commentsRoom.useDeleteComment(room);
1505
+ React2.useEffect(() => {
1506
+ const queryKey = makeNotificationSettingsQueryKey(room.id);
1507
+ void getInboxNotificationSettings(room, queryKey);
1508
+ incrementQuerySubscribers(queryKey);
1509
+ return () => decrementQuerySubscribers(queryKey);
1510
+ }, [room]);
1511
+ const updateRoomNotificationSettings = useUpdateRoomNotificationSettings();
1512
+ return [
1513
+ useSyncExternalStoreWithSelector(
1514
+ store.subscribe,
1515
+ store.get,
1516
+ store.get,
1517
+ (state) => {
1518
+ const query = state.queries[makeNotificationSettingsQueryKey(room.id)];
1519
+ if (query === void 0 || query.isLoading) {
1520
+ return { isLoading: true };
1521
+ }
1522
+ if (query.error !== void 0) {
1523
+ return { isLoading: false, error: query.error };
1524
+ }
1525
+ return {
1526
+ isLoading: false,
1527
+ settings: selectNotificationSettings(room.id, state)
1528
+ };
1529
+ }
1530
+ ),
1531
+ updateRoomNotificationSettings
1532
+ ];
1749
1533
  }
1750
- const { resolveUsers, resolveMentionSuggestions } = options ?? {};
1751
- const usersCache = resolveUsers ? createAsyncCache(async (stringifiedOptions) => {
1752
- const users = await resolveUsers(
1753
- JSON.parse(stringifiedOptions)
1754
- );
1755
- return users?.[0];
1756
- }) : void 0;
1757
- function useUser(userId) {
1534
+ function useRoomNotificationSettingsSuspense() {
1535
+ const updateRoomNotificationSettings = useUpdateRoomNotificationSettings();
1758
1536
  const room = useRoom();
1759
- const resolverKey = React3.useMemo(
1760
- () => stringify2({ userIds: [userId], roomId: room.id }),
1761
- [userId, room.id]
1762
- );
1763
- const state = useAsyncCache(usersCache, resolverKey);
1764
- React3.useEffect(() => warnIfNoResolveUsers(usersCache), []);
1765
- if (state.isLoading) {
1766
- return {
1767
- isLoading: true
1768
- };
1769
- } else {
1770
- return {
1771
- user: state.data,
1772
- error: state.error,
1773
- isLoading: false
1774
- };
1537
+ const queryKey = makeNotificationSettingsQueryKey(room.id);
1538
+ const query = store.get().queries[queryKey];
1539
+ if (query === void 0 || query.isLoading) {
1540
+ throw getInboxNotificationSettings(room, queryKey);
1541
+ }
1542
+ if (query.error) {
1543
+ throw query.error;
1775
1544
  }
1545
+ React2.useEffect(() => {
1546
+ const queryKey2 = makeNotificationSettingsQueryKey(room.id);
1547
+ incrementQuerySubscribers(queryKey2);
1548
+ return () => decrementQuerySubscribers(queryKey2);
1549
+ }, [room]);
1550
+ return [
1551
+ useSyncExternalStoreWithSelector(
1552
+ store.subscribe,
1553
+ store.get,
1554
+ store.get,
1555
+ (state) => {
1556
+ return {
1557
+ isLoading: false,
1558
+ settings: selectNotificationSettings(room.id, state)
1559
+ };
1560
+ }
1561
+ ),
1562
+ updateRoomNotificationSettings
1563
+ ];
1776
1564
  }
1777
- function useUserSuspense(userId) {
1565
+ function useUpdateRoomNotificationSettings() {
1778
1566
  const room = useRoom();
1779
- const resolverKey = React3.useMemo(
1780
- () => stringify2({ userIds: [userId], roomId: room.id }),
1781
- [userId, room.id]
1567
+ return React2.useCallback(
1568
+ (settings) => {
1569
+ const optimisticUpdateId = nanoid2();
1570
+ store.pushOptimisticUpdate({
1571
+ id: optimisticUpdateId,
1572
+ type: "update-notification-settings",
1573
+ roomId: room.id,
1574
+ settings
1575
+ });
1576
+ room.updateRoomNotificationSettings(settings).then(
1577
+ (settings2) => {
1578
+ store.set((state) => ({
1579
+ ...state,
1580
+ notificationSettings: {
1581
+ [room.id]: settings2
1582
+ },
1583
+ optimisticUpdates: state.optimisticUpdates.filter(
1584
+ (update) => update.id !== optimisticUpdateId
1585
+ )
1586
+ }));
1587
+ },
1588
+ (err) => onMutationFailure(
1589
+ err,
1590
+ optimisticUpdateId,
1591
+ (error) => new UpdateNotificationSettingsError(error, {
1592
+ roomId: room.id
1593
+ })
1594
+ )
1595
+ );
1596
+ },
1597
+ [room]
1782
1598
  );
1783
- const state = useAsyncCache(usersCache, resolverKey, {
1784
- suspense: true
1785
- });
1786
- React3.useEffect(() => warnIfNoResolveUsers(usersCache), []);
1787
- return {
1788
- user: state.data,
1789
- isLoading: false
1790
- };
1791
1599
  }
1792
- const mentionSuggestionsCache = createAsyncCache(
1793
- resolveMentionSuggestions ? (stringifiedOptions) => {
1794
- return resolveMentionSuggestions(
1795
- JSON.parse(stringifiedOptions)
1796
- );
1797
- } : () => Promise.resolve([])
1798
- );
1799
- function useMentionSuggestions(search) {
1800
- const room = useRoom();
1801
- const debouncedSearch = useDebounce(search, 500);
1802
- const resolverKey = React3.useMemo(
1803
- () => debouncedSearch !== void 0 ? stringify2({ text: debouncedSearch, roomId: room.id }) : null,
1804
- [debouncedSearch, room.id]
1805
- );
1806
- const { data } = useAsyncCache(mentionSuggestionsCache, resolverKey, {
1807
- keepPreviousDataWhileLoading: true
1808
- });
1809
- return data;
1600
+ function useCurrentUserId() {
1601
+ return useSelf((user) => typeof user.id === "string" ? user.id : null);
1810
1602
  }
1811
1603
  const bundle = {
1812
1604
  RoomContext,
@@ -1839,7 +1631,6 @@ function createRoomContext(client, options) {
1839
1631
  useOther,
1840
1632
  useMutation,
1841
1633
  useThreads,
1842
- useUser,
1843
1634
  useCreateThread,
1844
1635
  useEditThreadMetadata,
1845
1636
  useCreateComment,
@@ -1847,6 +1638,11 @@ function createRoomContext(client, options) {
1847
1638
  useDeleteComment,
1848
1639
  useAddReaction,
1849
1640
  useRemoveReaction,
1641
+ useMarkThreadAsRead,
1642
+ useThreadSubscription,
1643
+ useRoomNotificationSettings,
1644
+ useUpdateRoomNotificationSettings,
1645
+ ...shared,
1850
1646
  suspense: {
1851
1647
  RoomContext,
1852
1648
  RoomProvider: RoomProviderOuter,
@@ -1878,31 +1674,483 @@ function createRoomContext(client, options) {
1878
1674
  useOther: useOtherSuspense,
1879
1675
  useMutation,
1880
1676
  useThreads: useThreadsSuspense,
1881
- useUser: useUserSuspense,
1882
1677
  useCreateThread,
1883
1678
  useEditThreadMetadata,
1884
1679
  useCreateComment,
1885
1680
  useEditComment,
1886
1681
  useDeleteComment,
1887
1682
  useAddReaction,
1888
- useRemoveReaction
1683
+ useRemoveReaction,
1684
+ useMarkThreadAsRead,
1685
+ useThreadSubscription,
1686
+ useRoomNotificationSettings: useRoomNotificationSettingsSuspense,
1687
+ useUpdateRoomNotificationSettings,
1688
+ ...shared.suspense
1689
+ },
1690
+ [kInternal]: {
1691
+ useCurrentUserId,
1692
+ hasResolveMentionSuggestions: resolveMentionSuggestions !== void 0,
1693
+ useMentionSuggestions
1889
1694
  }
1890
1695
  };
1891
- const internalBundle = {
1892
- ...bundle,
1893
- hasResolveMentionSuggestions: resolveMentionSuggestions !== void 0,
1894
- useMentionSuggestions
1696
+ return Object.defineProperty(bundle, kInternal, {
1697
+ enumerable: false
1698
+ });
1699
+ }
1700
+ function getCurrentUserId(room) {
1701
+ const self = room.getSelf();
1702
+ if (self === null || self.id === void 0) {
1703
+ return "anonymous";
1704
+ } else {
1705
+ return self.id;
1706
+ }
1707
+ }
1708
+ function handleApiError(err) {
1709
+ const message = `Request failed with status ${err.status}: ${err.message}`;
1710
+ if (err.details?.error === "FORBIDDEN") {
1711
+ const detailedMessage = [message, err.details.suggestion, err.details.docs].filter(Boolean).join("\n");
1712
+ console2.error(detailedMessage);
1713
+ }
1714
+ return new Error(message);
1715
+ }
1716
+ function generateQueryKey(roomId, options) {
1717
+ return `${roomId}-${stringify(options ?? {})}`;
1718
+ }
1719
+
1720
+ // src/shared.ts
1721
+ function useSharedContextBundle() {
1722
+ const roomContextBundle = useContext2(ContextBundle);
1723
+ const liveblocksContextBundle = useContext2(ContextBundle2);
1724
+ if (roomContextBundle !== null) {
1725
+ return roomContextBundle;
1726
+ } else if (liveblocksContextBundle !== null) {
1727
+ return liveblocksContextBundle;
1728
+ } else {
1729
+ throw new Error(
1730
+ "LiveblocksProvider or RoomProvider are missing from the React tree."
1731
+ );
1732
+ }
1733
+ }
1734
+ function createSharedContext(client) {
1735
+ const usersStore = client[kInternal2].usersStore;
1736
+ const roomsInfoStore = client[kInternal2].roomsInfoStore;
1737
+ function useUser(userId) {
1738
+ const getUserState = useCallback2(
1739
+ () => usersStore.getState(userId),
1740
+ [userId]
1741
+ );
1742
+ useEffect4(() => {
1743
+ void usersStore.get(userId);
1744
+ }, [userId]);
1745
+ const state = useSyncExternalStore2(
1746
+ usersStore.subscribe,
1747
+ getUserState,
1748
+ getUserState
1749
+ );
1750
+ return state ? {
1751
+ ...state,
1752
+ user: state.data
1753
+ } : { isLoading: true };
1754
+ }
1755
+ function useUserSuspense(userId) {
1756
+ const getUserState = useCallback2(
1757
+ () => usersStore.getState(userId),
1758
+ [userId]
1759
+ );
1760
+ const userState = getUserState();
1761
+ if (!userState || userState.isLoading) {
1762
+ throw usersStore.get(userId);
1763
+ }
1764
+ if (userState.error) {
1765
+ throw userState.error;
1766
+ }
1767
+ const state = useSyncExternalStore2(
1768
+ usersStore.subscribe,
1769
+ getUserState,
1770
+ getUserState
1771
+ );
1772
+ return {
1773
+ ...state,
1774
+ user: state?.data
1775
+ };
1776
+ }
1777
+ function useRoomInfo(roomId) {
1778
+ const getRoomInfoState = useCallback2(
1779
+ () => roomsInfoStore.getState(roomId),
1780
+ [roomId]
1781
+ );
1782
+ useEffect4(() => {
1783
+ void roomsInfoStore.get(roomId);
1784
+ }, [roomId]);
1785
+ const state = useSyncExternalStore2(
1786
+ roomsInfoStore.subscribe,
1787
+ getRoomInfoState,
1788
+ getRoomInfoState
1789
+ );
1790
+ return state ? {
1791
+ ...state,
1792
+ info: state.data
1793
+ } : { isLoading: true };
1794
+ }
1795
+ function useRoomInfoSuspense(roomId) {
1796
+ const getRoomInfoState = useCallback2(
1797
+ () => roomsInfoStore.getState(roomId),
1798
+ [roomId]
1799
+ );
1800
+ const roomInfoState = getRoomInfoState();
1801
+ if (!roomInfoState || roomInfoState.isLoading) {
1802
+ throw usersStore.get(roomId);
1803
+ }
1804
+ if (roomInfoState.error) {
1805
+ throw roomInfoState.error;
1806
+ }
1807
+ const state = useSyncExternalStore2(
1808
+ usersStore.subscribe,
1809
+ getRoomInfoState,
1810
+ getRoomInfoState
1811
+ );
1812
+ return {
1813
+ ...state,
1814
+ info: state?.data
1815
+ };
1816
+ }
1817
+ const bundle = {
1818
+ useUser,
1819
+ useRoomInfo,
1820
+ suspense: {
1821
+ useUser: useUserSuspense,
1822
+ useRoomInfo: useRoomInfoSuspense
1823
+ }
1895
1824
  };
1896
1825
  return bundle;
1897
1826
  }
1898
1827
 
1828
+ // src/liveblocks.tsx
1829
+ var ContextBundle2 = createContext2(null);
1830
+ function useLiveblocksContextBundle() {
1831
+ const bundle = useContext3(ContextBundle2);
1832
+ if (bundle === null) {
1833
+ throw new Error("LiveblocksProvider is missing from the React tree.");
1834
+ }
1835
+ return bundle;
1836
+ }
1837
+ function createLiveblocksContext(client) {
1838
+ const shared = createSharedContext(client);
1839
+ const store = client[kInternal3].cacheStore;
1840
+ function LiveblocksProvider(props) {
1841
+ return /* @__PURE__ */ React3.createElement(
1842
+ ContextBundle2.Provider,
1843
+ {
1844
+ value: bundle
1845
+ },
1846
+ props.children
1847
+ );
1848
+ }
1849
+ let fetchInboxNotificationsRequest = null;
1850
+ let inboxNotificationsSubscribers = 0;
1851
+ const INBOX_NOTIFICATIONS_QUERY = "INBOX_NOTIFICATIONS";
1852
+ const POLLING_INTERVAL2 = 60 * 1e3;
1853
+ const poller = makePoller2(refreshThreadsAndNotifications);
1854
+ function refreshThreadsAndNotifications() {
1855
+ return client.getInboxNotifications().then(
1856
+ ({ threads, inboxNotifications }) => {
1857
+ store.updateThreadsAndNotifications(
1858
+ threads,
1859
+ inboxNotifications,
1860
+ INBOX_NOTIFICATIONS_QUERY
1861
+ );
1862
+ },
1863
+ () => {
1864
+ }
1865
+ );
1866
+ }
1867
+ function incrementInboxNotificationsSubscribers() {
1868
+ inboxNotificationsSubscribers++;
1869
+ poller.start(POLLING_INTERVAL2);
1870
+ }
1871
+ function decrementInboxNotificationsSubscribers() {
1872
+ if (inboxNotificationsSubscribers <= 0) {
1873
+ console.warn(
1874
+ `Internal unexpected behavior. Cannot decrease subscriber count for query "${INBOX_NOTIFICATIONS_QUERY}"`
1875
+ );
1876
+ return;
1877
+ }
1878
+ inboxNotificationsSubscribers--;
1879
+ if (inboxNotificationsSubscribers <= 0) {
1880
+ poller.stop();
1881
+ }
1882
+ }
1883
+ async function fetchInboxNotifications() {
1884
+ if (fetchInboxNotificationsRequest) {
1885
+ return fetchInboxNotificationsRequest;
1886
+ }
1887
+ store.setQueryState(INBOX_NOTIFICATIONS_QUERY, {
1888
+ isLoading: true
1889
+ });
1890
+ try {
1891
+ fetchInboxNotificationsRequest = client.getInboxNotifications();
1892
+ const { inboxNotifications, threads } = await fetchInboxNotificationsRequest;
1893
+ store.updateThreadsAndNotifications(
1894
+ threads,
1895
+ inboxNotifications,
1896
+ INBOX_NOTIFICATIONS_QUERY
1897
+ );
1898
+ } catch (er) {
1899
+ store.setQueryState(INBOX_NOTIFICATIONS_QUERY, {
1900
+ isLoading: false,
1901
+ error: er
1902
+ });
1903
+ }
1904
+ return;
1905
+ }
1906
+ function useInboxNotifications() {
1907
+ useEffect5(() => {
1908
+ void fetchInboxNotifications();
1909
+ incrementInboxNotificationsSubscribers();
1910
+ return () => decrementInboxNotificationsSubscribers();
1911
+ });
1912
+ const result = useSyncExternalStoreWithSelector2(
1913
+ store.subscribe,
1914
+ store.get,
1915
+ store.get,
1916
+ (state) => {
1917
+ const query = state.queries[INBOX_NOTIFICATIONS_QUERY];
1918
+ if (query === void 0 || query.isLoading) {
1919
+ return {
1920
+ isLoading: true
1921
+ };
1922
+ }
1923
+ if (query.error !== void 0) {
1924
+ return {
1925
+ error: query.error,
1926
+ isLoading: false
1927
+ };
1928
+ }
1929
+ return {
1930
+ inboxNotifications: selectedInboxNotifications(state),
1931
+ isLoading: false
1932
+ };
1933
+ }
1934
+ );
1935
+ return result;
1936
+ }
1937
+ function useInboxNotificationsSuspense() {
1938
+ if (store.get().queries[INBOX_NOTIFICATIONS_QUERY] === void 0 || store.get().queries[INBOX_NOTIFICATIONS_QUERY].isLoading) {
1939
+ throw fetchInboxNotifications();
1940
+ }
1941
+ React3.useEffect(() => {
1942
+ incrementInboxNotificationsSubscribers();
1943
+ return () => {
1944
+ decrementInboxNotificationsSubscribers();
1945
+ };
1946
+ }, []);
1947
+ return useSyncExternalStoreWithSelector2(
1948
+ store.subscribe,
1949
+ store.get,
1950
+ store.get,
1951
+ (state) => {
1952
+ return {
1953
+ inboxNotifications: selectedInboxNotifications(state),
1954
+ isLoading: false
1955
+ };
1956
+ }
1957
+ );
1958
+ }
1959
+ function selectUnreadInboxNotificationsCount(state) {
1960
+ let count = 0;
1961
+ for (const notification of selectedInboxNotifications(state)) {
1962
+ if (notification.readAt === null || notification.readAt < notification.notifiedAt) {
1963
+ count++;
1964
+ }
1965
+ }
1966
+ return count;
1967
+ }
1968
+ function useUnreadInboxNotificationsCount() {
1969
+ useEffect5(() => {
1970
+ void fetchInboxNotifications();
1971
+ incrementInboxNotificationsSubscribers();
1972
+ return () => decrementInboxNotificationsSubscribers();
1973
+ });
1974
+ return useSyncExternalStoreWithSelector2(
1975
+ store.subscribe,
1976
+ store.get,
1977
+ store.get,
1978
+ (state) => {
1979
+ const query = store.get().queries[INBOX_NOTIFICATIONS_QUERY];
1980
+ if (query === void 0 || query.isLoading) {
1981
+ return {
1982
+ isLoading: true
1983
+ };
1984
+ }
1985
+ if (query.error !== void 0) {
1986
+ return {
1987
+ error: query.error,
1988
+ isLoading: false
1989
+ };
1990
+ }
1991
+ return {
1992
+ isLoading: false,
1993
+ count: selectUnreadInboxNotificationsCount(state)
1994
+ };
1995
+ }
1996
+ );
1997
+ }
1998
+ function useUnreadInboxNotificationsCountSuspense() {
1999
+ if (store.get().queries[INBOX_NOTIFICATIONS_QUERY] === void 0 || store.get().queries[INBOX_NOTIFICATIONS_QUERY].isLoading) {
2000
+ throw fetchInboxNotifications();
2001
+ }
2002
+ React3.useEffect(() => {
2003
+ incrementInboxNotificationsSubscribers();
2004
+ return () => {
2005
+ decrementInboxNotificationsSubscribers();
2006
+ };
2007
+ }, []);
2008
+ return useSyncExternalStoreWithSelector2(
2009
+ store.subscribe,
2010
+ store.get,
2011
+ store.get,
2012
+ (state) => {
2013
+ return {
2014
+ isLoading: false,
2015
+ count: selectUnreadInboxNotificationsCount(state)
2016
+ };
2017
+ }
2018
+ );
2019
+ }
2020
+ function useMarkInboxNotificationAsRead() {
2021
+ return useCallback3((inboxNotificationId) => {
2022
+ const optimisticUpdateId = nanoid3();
2023
+ const readAt = /* @__PURE__ */ new Date();
2024
+ store.pushOptimisticUpdate({
2025
+ type: "mark-inbox-notification-as-read",
2026
+ id: optimisticUpdateId,
2027
+ inboxNotificationId,
2028
+ readAt
2029
+ });
2030
+ client.markInboxNotificationAsRead(inboxNotificationId).then(
2031
+ () => {
2032
+ store.set((state) => ({
2033
+ ...state,
2034
+ inboxNotifications: {
2035
+ ...state.inboxNotifications,
2036
+ [inboxNotificationId]: {
2037
+ // TODO: Handle potential deleted inbox notification
2038
+ ...state.inboxNotifications[inboxNotificationId],
2039
+ readAt
2040
+ }
2041
+ },
2042
+ optimisticUpdates: state.optimisticUpdates.filter(
2043
+ (update) => update.id !== optimisticUpdateId
2044
+ )
2045
+ }));
2046
+ },
2047
+ () => {
2048
+ store.set((state) => ({
2049
+ ...state,
2050
+ optimisticUpdates: state.optimisticUpdates.filter(
2051
+ (update) => update.id !== optimisticUpdateId
2052
+ )
2053
+ }));
2054
+ }
2055
+ );
2056
+ }, []);
2057
+ }
2058
+ function useMarkAllInboxNotificationsAsRead() {
2059
+ return useCallback3(() => {
2060
+ const optimisticUpdateId = nanoid3();
2061
+ const readAt = /* @__PURE__ */ new Date();
2062
+ store.pushOptimisticUpdate({
2063
+ type: "mark-inbox-notifications-as-read",
2064
+ id: optimisticUpdateId,
2065
+ readAt
2066
+ });
2067
+ client.markAllInboxNotificationsAsRead().then(
2068
+ () => {
2069
+ store.set((state) => ({
2070
+ ...state,
2071
+ inboxNotifications: Object.fromEntries(
2072
+ Array.from(Object.entries(state.inboxNotifications)).map(
2073
+ ([id, inboxNotification]) => [
2074
+ id,
2075
+ { ...inboxNotification, readAt }
2076
+ ]
2077
+ )
2078
+ ),
2079
+ optimisticUpdates: state.optimisticUpdates.filter(
2080
+ (update) => update.id !== optimisticUpdateId
2081
+ )
2082
+ }));
2083
+ },
2084
+ () => {
2085
+ store.set((state) => ({
2086
+ ...state,
2087
+ optimisticUpdates: state.optimisticUpdates.filter(
2088
+ (update) => update.id !== optimisticUpdateId
2089
+ )
2090
+ }));
2091
+ }
2092
+ );
2093
+ }, []);
2094
+ }
2095
+ function useThreadFromCache(threadId) {
2096
+ return useSyncExternalStoreWithSelector2(
2097
+ store.subscribe,
2098
+ store.get,
2099
+ store.get,
2100
+ (state) => {
2101
+ const thread = state.threads[threadId];
2102
+ if (thread === void 0) {
2103
+ throw new Error(
2104
+ `Internal error: thread with id "${threadId}" not found in cache`
2105
+ );
2106
+ }
2107
+ return thread;
2108
+ }
2109
+ );
2110
+ }
2111
+ const currentUserIdStore = client[kInternal3].currentUserIdStore;
2112
+ function useCurrentUserId() {
2113
+ return useSyncExternalStore3(
2114
+ currentUserIdStore.subscribe,
2115
+ currentUserIdStore.get,
2116
+ currentUserIdStore.get
2117
+ );
2118
+ }
2119
+ const bundle = {
2120
+ LiveblocksProvider,
2121
+ useInboxNotifications,
2122
+ useUnreadInboxNotificationsCount,
2123
+ useMarkInboxNotificationAsRead,
2124
+ useMarkAllInboxNotificationsAsRead,
2125
+ ...shared,
2126
+ suspense: {
2127
+ LiveblocksProvider,
2128
+ useInboxNotifications: useInboxNotificationsSuspense,
2129
+ useUnreadInboxNotificationsCount: useUnreadInboxNotificationsCountSuspense,
2130
+ useMarkInboxNotificationAsRead,
2131
+ useMarkAllInboxNotificationsAsRead,
2132
+ ...shared.suspense
2133
+ },
2134
+ [kInternal3]: {
2135
+ useThreadFromCache,
2136
+ useCurrentUserId
2137
+ }
2138
+ };
2139
+ return Object.defineProperty(bundle, kInternal3, {
2140
+ enumerable: false
2141
+ });
2142
+ }
2143
+
1899
2144
  // src/index.ts
1900
2145
  import { shallow as shallow2 } from "@liveblocks/client";
1901
2146
  detectDupes(PKG_NAME, PKG_VERSION, PKG_FORMAT);
1902
2147
  export {
1903
2148
  ClientSideSuspense,
2149
+ createLiveblocksContext,
1904
2150
  createRoomContext,
1905
2151
  shallow2 as shallow,
1906
- useRoomContextBundle
2152
+ useLiveblocksContextBundle,
2153
+ useRoomContextBundle,
2154
+ useSharedContextBundle
1907
2155
  };
1908
2156
  //# sourceMappingURL=index.mjs.map