stream-chat 9.51.0 → 9.52.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/dist/cjs/index.browser.js +418 -215
  2. package/dist/cjs/index.browser.js.map +4 -4
  3. package/dist/cjs/index.node.js +427 -216
  4. package/dist/cjs/index.node.js.map +4 -4
  5. package/dist/esm/index.mjs +416 -213
  6. package/dist/esm/index.mjs.map +4 -4
  7. package/dist/types/channel.d.ts +5 -3
  8. package/dist/types/client.d.ts +10 -0
  9. package/dist/types/index.d.ts +7 -0
  10. package/dist/types/messageComposer/attachmentIdentity.d.ts +4 -0
  11. package/dist/types/messageComposer/attachmentManager.d.ts +8 -0
  12. package/dist/types/messageComposer/messageComposer.d.ts +13 -0
  13. package/dist/types/messageComposer/middleware/attachmentManager/postUpload/attachmentEnrichment.d.ts +20 -1
  14. package/dist/types/messageComposer/middleware/messageComposer/MessageComposerMiddlewareExecutor.d.ts +6 -1
  15. package/dist/types/messageComposer/middleware/messageComposer/attachments.d.ts +24 -0
  16. package/dist/types/messageComposer/middleware/messageComposer/types.d.ts +22 -1
  17. package/dist/types/messageComposer/types.d.ts +5 -0
  18. package/dist/types/middleware.d.ts +8 -0
  19. package/dist/types/uploadManager.d.ts +30 -0
  20. package/dist/types/utils.d.ts +29 -6
  21. package/package.json +1 -1
  22. package/src/channel.ts +42 -26
  23. package/src/client.ts +27 -14
  24. package/src/index.ts +12 -0
  25. package/src/messageComposer/attachmentIdentity.ts +27 -0
  26. package/src/messageComposer/attachmentManager.ts +61 -12
  27. package/src/messageComposer/messageComposer.ts +43 -8
  28. package/src/messageComposer/middleware/attachmentManager/postUpload/AttachmentPostUploadMiddlewareExecutor.ts +1 -1
  29. package/src/messageComposer/middleware/attachmentManager/postUpload/attachmentEnrichment.ts +58 -33
  30. package/src/messageComposer/middleware/attachmentManager/postUpload/uploadErrorHandler.ts +4 -0
  31. package/src/messageComposer/middleware/messageComposer/MessageComposerMiddlewareExecutor.ts +9 -0
  32. package/src/messageComposer/middleware/messageComposer/attachments.ts +109 -0
  33. package/src/messageComposer/middleware/messageComposer/types.ts +22 -1
  34. package/src/messageComposer/types.ts +5 -0
  35. package/src/messageDelivery/MessageDeliveryReporter.ts +5 -4
  36. package/src/middleware.ts +11 -0
  37. package/src/offline-support/offline_support_api.ts +4 -0
  38. package/src/uploadManager.ts +45 -0
  39. package/src/utils.ts +102 -7
package/src/utils.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import FormData from 'form-data';
2
2
  import type {
3
3
  AscDesc,
4
+ Attachment,
4
5
  ChannelFilters,
5
6
  ChannelQueryOptions,
6
7
  ChannelSort,
@@ -40,6 +41,36 @@ export function logChatPromiseExecution<T>(promise: Promise<T>, name: string) {
40
41
  });
41
42
  }
42
43
 
44
+ /**
45
+ * The default no-op client logger.
46
+ */
47
+ export const noopLogger: Logger = () => null;
48
+
49
+ const logError = <T>(logger: Logger, error: unknown, event: T) => {
50
+ const message = 'Unhandled error in event listener';
51
+ logger('error', message, { error, event });
52
+ if (logger === noopLogger) console.error(message, error, event);
53
+ };
54
+
55
+ /**
56
+ * Invokes a single event listener in isolation, so that a faulty listener cannot
57
+ * abort the dispatch loop or the bookkeeping that follows it.
58
+ */
59
+ export const invokeEventListener = <T>(
60
+ listener: (event: T) => unknown,
61
+ event: T,
62
+ logger: Logger,
63
+ ) => {
64
+ try {
65
+ const result = listener(event);
66
+ if (result && typeof (result as PromiseLike<unknown>).then === 'function') {
67
+ Promise.resolve(result).catch((error) => logError(logger, error, event));
68
+ }
69
+ } catch (error) {
70
+ logError(logger, error, event);
71
+ }
72
+ };
73
+
43
74
  export const sleep = (m: number): Promise<void> => new Promise((r) => setTimeout(r, m));
44
75
 
45
76
  export function isFunction(value: unknown): value is (...args: unknown[]) => unknown {
@@ -125,13 +156,6 @@ export const channelTracksReadLocally = (channel?: Channel) =>
125
156
  !channelHasReadEvents(channel) &&
126
157
  !!channel?.getClient().options.isLocalUnreadCountEnabled;
127
158
 
128
- /**
129
- * userHasReadReceipts - Whether the current user allows read receipts, per their privacy settings.
130
- * Read receipts are treated as enabled unless the user has explicitly disabled them.
131
- */
132
- export const userHasReadReceipts = (client: StreamChat) =>
133
- client.user?.privacy_settings?.read_receipts?.enabled ?? true;
134
-
135
159
  export function addFileToFormData(
136
160
  uri: string | NodeJS.ReadableStream | Buffer | File,
137
161
  name?: string,
@@ -382,6 +406,77 @@ export function unformatMessage(message: LocalMessage): MessageResponse {
382
406
  } as MessageResponse;
383
407
  }
384
408
 
409
+ /**
410
+ * Whether a URL points at something the API and other devices can actually fetch.
411
+ */
412
+ const isRemoteUrl = (url?: string) => !!url && /^https?:\/\//i.test(url.trim());
413
+
414
+ /**
415
+ * Strips composer-internal state from a message's attachments, and drops any whose upload never
416
+ * resolved.
417
+ *
418
+ * `localMetadata` is how `AttachmentManager` tracks an upload (its id, the `File`, the local
419
+ * preview); it must never reach the API. An attachment that still carries it and has no remote
420
+ * URL was never settled — which is what happens when
421
+ * `createSendWithPendingUploadsAttachmentsMiddleware` is installed by a UI SDK that does not
422
+ * implement the rest of the flow (awaiting those uploads before sending). Sending it would store
423
+ * an attachment pointing at nothing, so it is dropped and logged instead.
424
+ *
425
+ * Returns the same message object when there was nothing to change.
426
+ */
427
+ export const sanitizeOutgoingAttachments = <T extends { attachments?: Attachment[] }>({
428
+ client,
429
+ message,
430
+ }: {
431
+ client: StreamChat;
432
+ message: T;
433
+ }): T => {
434
+ const attachments = message.attachments;
435
+ if (!attachments?.length) return message;
436
+
437
+ const unresolved: Attachment[] = [];
438
+ let changed = false;
439
+
440
+ const sanitized = attachments.reduce<Attachment[]>((acc, attachment) => {
441
+ const { localMetadata, ...rest } = attachment as Attachment & {
442
+ localMetadata?: Record<string, unknown>;
443
+ };
444
+
445
+ if (!localMetadata) {
446
+ acc.push(attachment);
447
+ return acc;
448
+ }
449
+
450
+ changed = true;
451
+
452
+ // Any of these pointing at a remote URL means the attachment resolved to something the API
453
+ // can store and other devices can load.
454
+ const hasRemoteSource =
455
+ isRemoteUrl(rest.asset_url) ||
456
+ isRemoteUrl(rest.image_url) ||
457
+ isRemoteUrl(rest.og_scrape_url) ||
458
+ isRemoteUrl(rest.title_link);
459
+
460
+ if (hasRemoteSource) {
461
+ acc.push(rest as Attachment);
462
+ } else {
463
+ unresolved.push(rest as Attachment);
464
+ }
465
+
466
+ return acc;
467
+ }, []);
468
+
469
+ if (unresolved.length) {
470
+ const text = `Dropped ${unresolved.length} attachment(s) whose upload never completed. Composing with pending uploads requires awaiting them before sending.`;
471
+ const extraData = { attachments: unresolved, message, tags: ['attachments'] };
472
+
473
+ client.logger('warn', text, extraData);
474
+ console.warn(text, extraData);
475
+ }
476
+
477
+ return changed ? { ...message, attachments: sanitized } : message;
478
+ };
479
+
385
480
  export const localMessageToNewMessagePayload = (localMessage: LocalMessage): Message => {
386
481
  /* eslint-disable @typescript-eslint/no-unused-vars */
387
482
  const {