stream-chat-react-native-core 9.0.2-beta.2 → 9.1.0

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 (212) hide show
  1. package/lib/commonjs/components/Attachment/Attachment.js +38 -10
  2. package/lib/commonjs/components/Attachment/Attachment.js.map +1 -1
  3. package/lib/commonjs/components/Attachment/AttachmentFileUploadProgressIndicator.js +97 -0
  4. package/lib/commonjs/components/Attachment/AttachmentFileUploadProgressIndicator.js.map +1 -0
  5. package/lib/commonjs/components/Attachment/AttachmentUploadIndicator.js +97 -0
  6. package/lib/commonjs/components/Attachment/AttachmentUploadIndicator.js.map +1 -0
  7. package/lib/commonjs/components/Attachment/CircularProgressIndicator.js +141 -0
  8. package/lib/commonjs/components/Attachment/CircularProgressIndicator.js.map +1 -0
  9. package/lib/commonjs/components/Attachment/FileAttachment.js +18 -4
  10. package/lib/commonjs/components/Attachment/FileAttachment.js.map +1 -1
  11. package/lib/commonjs/components/Attachment/Gallery.js +7 -1
  12. package/lib/commonjs/components/Attachment/Gallery.js.map +1 -1
  13. package/lib/commonjs/components/Attachment/MediaUploadProgressOverlay.js +59 -0
  14. package/lib/commonjs/components/Attachment/MediaUploadProgressOverlay.js.map +1 -0
  15. package/lib/commonjs/components/Attachment/VideoThumbnail.js +18 -8
  16. package/lib/commonjs/components/Attachment/VideoThumbnail.js.map +1 -1
  17. package/lib/commonjs/components/Attachment/utils/buildGallery/buildThumbnail.js +2 -0
  18. package/lib/commonjs/components/Attachment/utils/buildGallery/buildThumbnail.js.map +1 -1
  19. package/lib/commonjs/components/Channel/Channel.js +94 -92
  20. package/lib/commonjs/components/Channel/Channel.js.map +1 -1
  21. package/lib/commonjs/components/Chat/Chat.js +10 -1
  22. package/lib/commonjs/components/Chat/Chat.js.map +1 -1
  23. package/lib/commonjs/components/MessageInput/components/AttachmentPreview/AttachmentUploadProgressIndicator.js +42 -54
  24. package/lib/commonjs/components/MessageInput/components/AttachmentPreview/AttachmentUploadProgressIndicator.js.map +1 -1
  25. package/lib/commonjs/components/MessageInput/components/AttachmentPreview/AudioAttachmentUploadPreview.js +10 -6
  26. package/lib/commonjs/components/MessageInput/components/AttachmentPreview/AudioAttachmentUploadPreview.js.map +1 -1
  27. package/lib/commonjs/components/MessageInput/components/AttachmentPreview/FileAttachmentUploadPreview.js +12 -6
  28. package/lib/commonjs/components/MessageInput/components/AttachmentPreview/FileAttachmentUploadPreview.js.map +1 -1
  29. package/lib/commonjs/components/MessageInput/components/AttachmentPreview/ImageAttachmentUploadPreview.js +13 -7
  30. package/lib/commonjs/components/MessageInput/components/AttachmentPreview/ImageAttachmentUploadPreview.js.map +1 -1
  31. package/lib/commonjs/components/MessageInput/components/AttachmentPreview/VideoAttachmentUploadPreview.js +6 -2
  32. package/lib/commonjs/components/MessageInput/components/AttachmentPreview/VideoAttachmentUploadPreview.js.map +1 -1
  33. package/lib/commonjs/components/index.js +33 -0
  34. package/lib/commonjs/components/index.js.map +1 -1
  35. package/lib/commonjs/contexts/componentsContext/defaultComponents.js +6 -0
  36. package/lib/commonjs/contexts/componentsContext/defaultComponents.js.map +1 -1
  37. package/lib/commonjs/contexts/messageInputContext/hooks/useCreateMessageInputContext.js +3 -1
  38. package/lib/commonjs/contexts/messageInputContext/hooks/useCreateMessageInputContext.js.map +1 -1
  39. package/lib/commonjs/contexts/messageInputContext/hooks/useMessageComposerHasSendableData.js +4 -1
  40. package/lib/commonjs/contexts/messageInputContext/hooks/useMessageComposerHasSendableData.js.map +1 -1
  41. package/lib/commonjs/contexts/themeContext/utils/theme.js +5 -0
  42. package/lib/commonjs/contexts/themeContext/utils/theme.js.map +1 -1
  43. package/lib/commonjs/hooks/index.js +11 -0
  44. package/lib/commonjs/hooks/index.js.map +1 -1
  45. package/lib/commonjs/hooks/usePendingAttachmentUpload.js +106 -0
  46. package/lib/commonjs/hooks/usePendingAttachmentUpload.js.map +1 -0
  47. package/lib/commonjs/index.js +12 -0
  48. package/lib/commonjs/index.js.map +1 -1
  49. package/lib/commonjs/middlewares/attachments.js +2 -0
  50. package/lib/commonjs/middlewares/attachments.js.map +1 -1
  51. package/lib/commonjs/native.js +8 -1
  52. package/lib/commonjs/native.js.map +1 -1
  53. package/lib/commonjs/nativeMultipartUpload.js +237 -0
  54. package/lib/commonjs/nativeMultipartUpload.js.map +1 -0
  55. package/lib/commonjs/types/types.js.map +1 -1
  56. package/lib/commonjs/utils/installNativeMultipartAdapter.js +223 -0
  57. package/lib/commonjs/utils/installNativeMultipartAdapter.js.map +1 -0
  58. package/lib/commonjs/utils/utils.js +2 -2
  59. package/lib/commonjs/utils/utils.js.map +1 -1
  60. package/lib/commonjs/version.json +1 -1
  61. package/lib/module/components/Attachment/Attachment.js +38 -10
  62. package/lib/module/components/Attachment/Attachment.js.map +1 -1
  63. package/lib/module/components/Attachment/AttachmentFileUploadProgressIndicator.js +97 -0
  64. package/lib/module/components/Attachment/AttachmentFileUploadProgressIndicator.js.map +1 -0
  65. package/lib/module/components/Attachment/AttachmentUploadIndicator.js +97 -0
  66. package/lib/module/components/Attachment/AttachmentUploadIndicator.js.map +1 -0
  67. package/lib/module/components/Attachment/CircularProgressIndicator.js +141 -0
  68. package/lib/module/components/Attachment/CircularProgressIndicator.js.map +1 -0
  69. package/lib/module/components/Attachment/FileAttachment.js +18 -4
  70. package/lib/module/components/Attachment/FileAttachment.js.map +1 -1
  71. package/lib/module/components/Attachment/Gallery.js +7 -1
  72. package/lib/module/components/Attachment/Gallery.js.map +1 -1
  73. package/lib/module/components/Attachment/MediaUploadProgressOverlay.js +59 -0
  74. package/lib/module/components/Attachment/MediaUploadProgressOverlay.js.map +1 -0
  75. package/lib/module/components/Attachment/VideoThumbnail.js +18 -8
  76. package/lib/module/components/Attachment/VideoThumbnail.js.map +1 -1
  77. package/lib/module/components/Attachment/utils/buildGallery/buildThumbnail.js +2 -0
  78. package/lib/module/components/Attachment/utils/buildGallery/buildThumbnail.js.map +1 -1
  79. package/lib/module/components/Channel/Channel.js +94 -92
  80. package/lib/module/components/Channel/Channel.js.map +1 -1
  81. package/lib/module/components/Chat/Chat.js +10 -1
  82. package/lib/module/components/Chat/Chat.js.map +1 -1
  83. package/lib/module/components/MessageInput/components/AttachmentPreview/AttachmentUploadProgressIndicator.js +42 -54
  84. package/lib/module/components/MessageInput/components/AttachmentPreview/AttachmentUploadProgressIndicator.js.map +1 -1
  85. package/lib/module/components/MessageInput/components/AttachmentPreview/AudioAttachmentUploadPreview.js +10 -6
  86. package/lib/module/components/MessageInput/components/AttachmentPreview/AudioAttachmentUploadPreview.js.map +1 -1
  87. package/lib/module/components/MessageInput/components/AttachmentPreview/FileAttachmentUploadPreview.js +12 -6
  88. package/lib/module/components/MessageInput/components/AttachmentPreview/FileAttachmentUploadPreview.js.map +1 -1
  89. package/lib/module/components/MessageInput/components/AttachmentPreview/ImageAttachmentUploadPreview.js +13 -7
  90. package/lib/module/components/MessageInput/components/AttachmentPreview/ImageAttachmentUploadPreview.js.map +1 -1
  91. package/lib/module/components/MessageInput/components/AttachmentPreview/VideoAttachmentUploadPreview.js +6 -2
  92. package/lib/module/components/MessageInput/components/AttachmentPreview/VideoAttachmentUploadPreview.js.map +1 -1
  93. package/lib/module/components/index.js +33 -0
  94. package/lib/module/components/index.js.map +1 -1
  95. package/lib/module/contexts/componentsContext/defaultComponents.js +6 -0
  96. package/lib/module/contexts/componentsContext/defaultComponents.js.map +1 -1
  97. package/lib/module/contexts/messageInputContext/hooks/useCreateMessageInputContext.js +3 -1
  98. package/lib/module/contexts/messageInputContext/hooks/useCreateMessageInputContext.js.map +1 -1
  99. package/lib/module/contexts/messageInputContext/hooks/useMessageComposerHasSendableData.js +4 -1
  100. package/lib/module/contexts/messageInputContext/hooks/useMessageComposerHasSendableData.js.map +1 -1
  101. package/lib/module/contexts/themeContext/utils/theme.js +5 -0
  102. package/lib/module/contexts/themeContext/utils/theme.js.map +1 -1
  103. package/lib/module/hooks/index.js +11 -0
  104. package/lib/module/hooks/index.js.map +1 -1
  105. package/lib/module/hooks/usePendingAttachmentUpload.js +106 -0
  106. package/lib/module/hooks/usePendingAttachmentUpload.js.map +1 -0
  107. package/lib/module/index.js +12 -0
  108. package/lib/module/index.js.map +1 -1
  109. package/lib/module/middlewares/attachments.js +2 -0
  110. package/lib/module/middlewares/attachments.js.map +1 -1
  111. package/lib/module/native.js +8 -1
  112. package/lib/module/native.js.map +1 -1
  113. package/lib/module/nativeMultipartUpload.js +237 -0
  114. package/lib/module/nativeMultipartUpload.js.map +1 -0
  115. package/lib/module/types/types.js.map +1 -1
  116. package/lib/module/utils/installNativeMultipartAdapter.js +223 -0
  117. package/lib/module/utils/installNativeMultipartAdapter.js.map +1 -0
  118. package/lib/module/utils/utils.js +2 -2
  119. package/lib/module/utils/utils.js.map +1 -1
  120. package/lib/module/version.json +1 -1
  121. package/lib/typescript/components/Attachment/Attachment.d.ts.map +1 -1
  122. package/lib/typescript/components/Attachment/AttachmentFileUploadProgressIndicator.d.ts +14 -0
  123. package/lib/typescript/components/Attachment/AttachmentFileUploadProgressIndicator.d.ts.map +1 -0
  124. package/lib/typescript/components/Attachment/AttachmentUploadIndicator.d.ts +18 -0
  125. package/lib/typescript/components/Attachment/AttachmentUploadIndicator.d.ts.map +1 -0
  126. package/lib/typescript/components/Attachment/CircularProgressIndicator.d.ts +18 -0
  127. package/lib/typescript/components/Attachment/CircularProgressIndicator.d.ts.map +1 -0
  128. package/lib/typescript/components/Attachment/FileAttachment.d.ts.map +1 -1
  129. package/lib/typescript/components/Attachment/Gallery.d.ts.map +1 -1
  130. package/lib/typescript/components/Attachment/MediaUploadProgressOverlay.d.ts +12 -0
  131. package/lib/typescript/components/Attachment/MediaUploadProgressOverlay.d.ts.map +1 -0
  132. package/lib/typescript/components/Attachment/VideoThumbnail.d.ts +4 -0
  133. package/lib/typescript/components/Attachment/VideoThumbnail.d.ts.map +1 -1
  134. package/lib/typescript/components/Attachment/utils/buildGallery/buildThumbnail.d.ts.map +1 -1
  135. package/lib/typescript/components/Attachment/utils/buildGallery/types.d.ts +2 -0
  136. package/lib/typescript/components/Attachment/utils/buildGallery/types.d.ts.map +1 -1
  137. package/lib/typescript/components/Channel/Channel.d.ts.map +1 -1
  138. package/lib/typescript/components/Chat/Chat.d.ts +10 -0
  139. package/lib/typescript/components/Chat/Chat.d.ts.map +1 -1
  140. package/lib/typescript/components/MessageInput/components/AttachmentPreview/AttachmentUploadProgressIndicator.d.ts +7 -2
  141. package/lib/typescript/components/MessageInput/components/AttachmentPreview/AttachmentUploadProgressIndicator.d.ts.map +1 -1
  142. package/lib/typescript/components/MessageInput/components/AttachmentPreview/AudioAttachmentUploadPreview.d.ts.map +1 -1
  143. package/lib/typescript/components/MessageInput/components/AttachmentPreview/FileAttachmentUploadPreview.d.ts.map +1 -1
  144. package/lib/typescript/components/MessageInput/components/AttachmentPreview/ImageAttachmentUploadPreview.d.ts.map +1 -1
  145. package/lib/typescript/components/MessageInput/components/AttachmentPreview/VideoAttachmentUploadPreview.d.ts.map +1 -1
  146. package/lib/typescript/components/index.d.ts +3 -0
  147. package/lib/typescript/components/index.d.ts.map +1 -1
  148. package/lib/typescript/contexts/componentsContext/defaultComponents.d.ts +5 -2
  149. package/lib/typescript/contexts/componentsContext/defaultComponents.d.ts.map +1 -1
  150. package/lib/typescript/contexts/messageInputContext/hooks/useCreateMessageInputContext.d.ts +1 -1
  151. package/lib/typescript/contexts/messageInputContext/hooks/useCreateMessageInputContext.d.ts.map +1 -1
  152. package/lib/typescript/contexts/messageInputContext/hooks/useMessageComposerHasSendableData.d.ts.map +1 -1
  153. package/lib/typescript/contexts/themeContext/ThemeContext.d.ts +5 -0
  154. package/lib/typescript/contexts/themeContext/ThemeContext.d.ts.map +1 -1
  155. package/lib/typescript/contexts/themeContext/utils/theme.d.ts +5 -0
  156. package/lib/typescript/contexts/themeContext/utils/theme.d.ts.map +1 -1
  157. package/lib/typescript/hooks/index.d.ts +1 -0
  158. package/lib/typescript/hooks/index.d.ts.map +1 -1
  159. package/lib/typescript/hooks/usePendingAttachmentUpload.d.ts +14 -0
  160. package/lib/typescript/hooks/usePendingAttachmentUpload.d.ts.map +1 -0
  161. package/lib/typescript/index.d.ts +1 -0
  162. package/lib/typescript/index.d.ts.map +1 -1
  163. package/lib/typescript/middlewares/attachments.d.ts.map +1 -1
  164. package/lib/typescript/native.d.ts +5 -2
  165. package/lib/typescript/native.d.ts.map +1 -1
  166. package/lib/typescript/nativeMultipartUpload.d.ts +98 -0
  167. package/lib/typescript/nativeMultipartUpload.d.ts.map +1 -0
  168. package/lib/typescript/types/types.d.ts +2 -0
  169. package/lib/typescript/types/types.d.ts.map +1 -1
  170. package/lib/typescript/utils/installNativeMultipartAdapter.d.ts +8 -0
  171. package/lib/typescript/utils/installNativeMultipartAdapter.d.ts.map +1 -0
  172. package/lib/typescript/utils/utils.d.ts +1 -1
  173. package/lib/typescript/utils/utils.d.ts.map +1 -1
  174. package/package.json +2 -2
  175. package/src/__tests__/nativeMultipartUpload.test.ts +267 -0
  176. package/src/components/Attachment/Attachment.tsx +54 -5
  177. package/src/components/Attachment/AttachmentFileUploadProgressIndicator.tsx +108 -0
  178. package/src/components/Attachment/AttachmentUploadIndicator.tsx +112 -0
  179. package/src/components/Attachment/CircularProgressIndicator.tsx +161 -0
  180. package/src/components/Attachment/FileAttachment.tsx +22 -6
  181. package/src/components/Attachment/Gallery.tsx +9 -1
  182. package/src/components/Attachment/MediaUploadProgressOverlay.tsx +77 -0
  183. package/src/components/Attachment/VideoThumbnail.tsx +17 -9
  184. package/src/components/Attachment/__tests__/Attachment.test.tsx +63 -16
  185. package/src/components/Attachment/utils/buildGallery/buildThumbnail.ts +3 -0
  186. package/src/components/Attachment/utils/buildGallery/types.ts +2 -0
  187. package/src/components/Channel/Channel.tsx +65 -61
  188. package/src/components/Chat/Chat.tsx +20 -0
  189. package/src/components/MessageInput/__tests__/AttachmentUploadPreviewList.test.tsx +51 -6
  190. package/src/components/MessageInput/__tests__/AudioAttachmentUploadPreview.test.tsx +31 -1
  191. package/src/components/MessageInput/components/AttachmentPreview/AttachmentUploadProgressIndicator.tsx +30 -44
  192. package/src/components/MessageInput/components/AttachmentPreview/AudioAttachmentUploadPreview.tsx +11 -5
  193. package/src/components/MessageInput/components/AttachmentPreview/FileAttachmentUploadPreview.tsx +20 -5
  194. package/src/components/MessageInput/components/AttachmentPreview/ImageAttachmentUploadPreview.tsx +20 -9
  195. package/src/components/MessageInput/components/AttachmentPreview/VideoAttachmentUploadPreview.tsx +7 -1
  196. package/src/components/index.ts +3 -0
  197. package/src/contexts/componentsContext/defaultComponents.ts +6 -0
  198. package/src/contexts/messageInputContext/hooks/useCreateMessageInputContext.ts +3 -1
  199. package/src/contexts/messageInputContext/hooks/useMessageComposerHasSendableData.ts +5 -1
  200. package/src/contexts/themeContext/utils/theme.ts +10 -0
  201. package/src/hooks/__tests__/usePendingAttachmentUpload.test.tsx +106 -0
  202. package/src/hooks/index.ts +1 -0
  203. package/src/hooks/usePendingAttachmentUpload.ts +131 -0
  204. package/src/index.ts +1 -0
  205. package/src/middlewares/attachments.ts +2 -0
  206. package/src/native.ts +28 -0
  207. package/src/nativeMultipartUpload.ts +384 -0
  208. package/src/types/types.ts +2 -0
  209. package/src/utils/__tests__/installNativeMultipartAdapter.test.ts +437 -0
  210. package/src/utils/installNativeMultipartAdapter.ts +302 -0
  211. package/src/utils/utils.ts +3 -3
  212. package/src/version.json +1 -1
@@ -0,0 +1,131 @@
1
+ import { useCallback, useEffect, useRef, useState } from 'react';
2
+
3
+ import type { UploadManagerState } from 'stream-chat';
4
+
5
+ import { useStateStore } from './useStateStore';
6
+
7
+ import { useChatContext } from '../contexts/chatContext/ChatContext';
8
+
9
+ export type PendingAttachmentUpload = {
10
+ /** True when `client.uploadManager` has an in-flight upload for this attachment local id. */
11
+ isUploading: boolean;
12
+ /**
13
+ * Upload percent **0–100** from `client.uploadManager` (same scale as `attachmentManager`
14
+ * `onProgress` / `localMetadata.uploadProgress`). `undefined` when not computable or not uploading.
15
+ */
16
+ uploadProgress: number | undefined;
17
+ };
18
+
19
+ const idle: PendingAttachmentUpload = {
20
+ isUploading: false,
21
+ uploadProgress: undefined,
22
+ };
23
+
24
+ const completed: PendingAttachmentUpload = {
25
+ isUploading: true,
26
+ uploadProgress: 100,
27
+ };
28
+
29
+ const COMPLETION_HOLD_MS = 350;
30
+ const COMPLETION_READY_PROGRESS = 90;
31
+
32
+ const now = () => Date.now();
33
+
34
+ /**
35
+ * Subscribes to `client.uploadManager` for the pending attachment identified by `localId`.
36
+ */
37
+ export function usePendingAttachmentUpload(localId: string | undefined): PendingAttachmentUpload {
38
+ const { client } = useChatContext();
39
+ const [, setRenderTick] = useState(0);
40
+ const completedHoldUntilRef = useRef(0);
41
+ const holdTimeoutRef = useRef<ReturnType<typeof setTimeout> | undefined>(undefined);
42
+ const lastUploadProgressRef = useRef<number | undefined>(undefined);
43
+ const previousLocalIdRef = useRef<string | undefined>(localId);
44
+ const wasUploadingRef = useRef(false);
45
+
46
+ const selector = useCallback(
47
+ (state: UploadManagerState): PendingAttachmentUpload => {
48
+ if (!localId) {
49
+ return idle;
50
+ }
51
+ const record = state.uploads[localId];
52
+ if (!record) {
53
+ return idle;
54
+ }
55
+ return {
56
+ isUploading: true,
57
+ uploadProgress: record.uploadProgress,
58
+ };
59
+ },
60
+ [localId],
61
+ );
62
+
63
+ const result = useStateStore(localId ? client.uploadManager.state : undefined, selector);
64
+ const isUploading = result?.isUploading ?? false;
65
+ const uploadProgress = result?.uploadProgress;
66
+
67
+ if (previousLocalIdRef.current !== localId) {
68
+ previousLocalIdRef.current = localId;
69
+ completedHoldUntilRef.current = 0;
70
+ wasUploadingRef.current = false;
71
+ lastUploadProgressRef.current = undefined;
72
+ }
73
+
74
+ let pendingAttachmentUpload = result ?? idle;
75
+ if (localId && isUploading) {
76
+ completedHoldUntilRef.current = 0;
77
+ wasUploadingRef.current = true;
78
+ if (typeof uploadProgress === 'number') {
79
+ lastUploadProgressRef.current = uploadProgress;
80
+ }
81
+ } else if (localId && completedHoldUntilRef.current > now()) {
82
+ pendingAttachmentUpload = completed;
83
+ } else if (localId) {
84
+ const shouldStartCompletionHold =
85
+ wasUploadingRef.current &&
86
+ typeof lastUploadProgressRef.current === 'number' &&
87
+ lastUploadProgressRef.current >= COMPLETION_READY_PROGRESS;
88
+
89
+ wasUploadingRef.current = false;
90
+ lastUploadProgressRef.current = undefined;
91
+
92
+ if (shouldStartCompletionHold) {
93
+ completedHoldUntilRef.current = now() + COMPLETION_HOLD_MS;
94
+ pendingAttachmentUpload = completed;
95
+ } else {
96
+ completedHoldUntilRef.current = 0;
97
+ }
98
+ } else {
99
+ completedHoldUntilRef.current = 0;
100
+ wasUploadingRef.current = false;
101
+ lastUploadProgressRef.current = undefined;
102
+ }
103
+
104
+ useEffect(() => {
105
+ if (holdTimeoutRef.current) {
106
+ clearTimeout(holdTimeoutRef.current);
107
+ holdTimeoutRef.current = undefined;
108
+ }
109
+
110
+ const holdForMs = completedHoldUntilRef.current - now();
111
+ if (holdForMs <= 0) {
112
+ return;
113
+ }
114
+
115
+ holdTimeoutRef.current = setTimeout(() => {
116
+ holdTimeoutRef.current = undefined;
117
+ setRenderTick((tick) => tick + 1);
118
+ }, holdForMs);
119
+ }, [localId, pendingAttachmentUpload]);
120
+
121
+ useEffect(
122
+ () => () => {
123
+ if (holdTimeoutRef.current) {
124
+ clearTimeout(holdTimeoutRef.current);
125
+ }
126
+ },
127
+ [],
128
+ );
129
+
130
+ return pendingAttachmentUpload;
131
+ }
package/src/index.ts CHANGED
@@ -18,6 +18,7 @@ export * from './utils/i18n/Streami18n';
18
18
  export * from './utils/setupCommandUIMiddlewares';
19
19
  export * from './utils/createGenerateVideoThumbnails';
20
20
  export * from './utils/utils';
21
+ export * from './nativeMultipartUpload';
21
22
 
22
23
  export { default as enTranslations } from './i18n/en.json';
23
24
  export { default as esTranslations } from './i18n/es.json';
@@ -26,6 +26,7 @@ export const localAttachmentToAttachment = (localAttachment: LocalAttachment) =>
26
26
  return {
27
27
  ...attachment,
28
28
  image_url: localMetadata?.previewUri,
29
+ localId: localMetadata?.id,
29
30
  originalFile: localMetadata.file,
30
31
  } as Attachment;
31
32
  } else {
@@ -35,6 +36,7 @@ export const localAttachmentToAttachment = (localAttachment: LocalAttachment) =>
35
36
  return {
36
37
  ...attachment,
37
38
  asset_url: (localMetadata.file as FileReference).uri,
39
+ localId: localMetadata?.id,
38
40
  originalFile: localMetadata.file,
39
41
  } as Attachment;
40
42
  }
package/src/native.ts CHANGED
@@ -7,7 +7,27 @@ import {
7
7
  ViewStyle,
8
8
  } from 'react-native';
9
9
 
10
+ import type { NativeMultipartUpload } from './nativeMultipartUpload';
10
11
  import type { File } from './types/types';
12
+
13
+ export type {
14
+ NativeMultipartAbortSignal,
15
+ NativeMultipartCanceledError,
16
+ NativeMultipartUpload,
17
+ NativeMultipartUploadEventEmitter,
18
+ NativeMultipartUploadHeader,
19
+ NativeMultipartUploadNativeResponse,
20
+ NativeMultipartUploadPart,
21
+ NativeMultipartUploadProgressConfig,
22
+ NativeMultipartUploadProgressEvent,
23
+ NativeMultipartUploadRequest,
24
+ NativeMultipartUploadResult,
25
+ NativeMultipartUploader,
26
+ NativeMultipartUploaderModule,
27
+ NativeMultipartUploaderProgressConfig,
28
+ NativeMultipartUploaderRequest,
29
+ } from './nativeMultipartUpload';
30
+
11
31
  const fail = () => {
12
32
  throw Error(
13
33
  'Native handler was not registered, you should import stream-chat-expo or stream-chat-react-native',
@@ -308,6 +328,7 @@ type Handlers = {
308
328
  getLocalAssetUri?: GetLocalAssetUri;
309
329
  getPhotos?: GetPhotos;
310
330
  iOS14RefreshGallerySelection?: iOS14RefreshGallerySelection;
331
+ multipartUpload?: NativeMultipartUpload;
311
332
  oniOS14GalleryLibrarySelectionChange?: OniOS14LibrarySelectionChange;
312
333
  overrideAudioRecordingConfiguration?: (
313
334
  audioRecordingConfiguration: AudioRecordingConfiguration,
@@ -338,6 +359,7 @@ export const NativeHandlers: Pick<
338
359
  | 'getLocalAssetUri'
339
360
  | 'getPhotos'
340
361
  | 'iOS14RefreshGallerySelection'
362
+ | 'multipartUpload'
341
363
  | 'oniOS14GalleryLibrarySelectionChange'
342
364
  | 'pickDocument'
343
365
  | 'pickImage'
@@ -355,6 +377,7 @@ export const NativeHandlers: Pick<
355
377
  getLocalAssetUri: fail,
356
378
  getPhotos: fail,
357
379
  iOS14RefreshGallerySelection: fail,
380
+ multipartUpload: fail,
358
381
  oniOS14GalleryLibrarySelectionChange: fail,
359
382
  pickDocument: fail,
360
383
  pickImage: fail,
@@ -404,6 +427,10 @@ export const registerNativeHandlers = (handlers: Handlers) => {
404
427
  NativeHandlers.iOS14RefreshGallerySelection = handlers.iOS14RefreshGallerySelection;
405
428
  }
406
429
 
430
+ if (handlers.multipartUpload !== undefined) {
431
+ NativeHandlers.multipartUpload = handlers.multipartUpload;
432
+ }
433
+
407
434
  if (handlers.oniOS14GalleryLibrarySelectionChange !== undefined) {
408
435
  NativeHandlers.oniOS14GalleryLibrarySelectionChange =
409
436
  handlers.oniOS14GalleryLibrarySelectionChange;
@@ -469,3 +496,4 @@ export const isImageMediaLibraryAvailable = () =>
469
496
  !!NativeHandlers.iOS14RefreshGallerySelection &&
470
497
  !!NativeHandlers.oniOS14GalleryLibrarySelectionChange &&
471
498
  !!NativeHandlers.getLocalAssetUri;
499
+ export const isNativeMultipartUploadAvailable = () => NativeHandlers.multipartUpload !== fail;
@@ -0,0 +1,384 @@
1
+ import { NativeEventEmitter } from 'react-native';
2
+
3
+ const STREAM_MULTIPART_UPLOAD_PROGRESS_EVENT = 'streamMultipartUploadProgress';
4
+ const CANCELED_ERROR_CODE = 'ERR_CANCELED';
5
+
6
+ export type NativeMultipartAbortSignal = {
7
+ aborted: boolean;
8
+ addEventListener?: (...args: unknown[]) => unknown;
9
+ onabort?: ((...args: unknown[]) => unknown) | null;
10
+ removeEventListener?: (...args: unknown[]) => unknown;
11
+ };
12
+
13
+ export type NativeMultipartUploadHeader = {
14
+ name: string;
15
+ value: string;
16
+ };
17
+
18
+ export type NativeMultipartUploadPart =
19
+ | {
20
+ fieldName: string;
21
+ kind: 'file';
22
+ fileName: string;
23
+ mimeType?: string;
24
+ uri: string;
25
+ }
26
+ | {
27
+ fieldName: string;
28
+ kind: 'text';
29
+ value: string;
30
+ };
31
+
32
+ export type NativeMultipartUploaderProgressConfig = {
33
+ count?: number;
34
+ intervalMs?: number;
35
+ };
36
+
37
+ export type NativeMultipartUploadProgressConfig = NativeMultipartUploaderProgressConfig & {
38
+ /**
39
+ * Maximum progress percentage reported while the native request body is still being sent.
40
+ * Completion is represented by the upload request resolving and the upload indicator being removed.
41
+ *
42
+ * @default 90
43
+ */
44
+ completionProgressCap?: number;
45
+ };
46
+
47
+ export type NativeMultipartUploadProgressEvent = {
48
+ loaded: number;
49
+ total?: number | null;
50
+ uploadId: string;
51
+ };
52
+
53
+ export type NativeMultipartUploadNativeResponse = {
54
+ body: string;
55
+ headers?: ReadonlyArray<NativeMultipartUploadHeader> | null;
56
+ status: number;
57
+ statusText?: string | null;
58
+ };
59
+
60
+ export type NativeMultipartUploadResult = {
61
+ body: string;
62
+ headers?: Record<string, string>;
63
+ status: number;
64
+ statusText?: string;
65
+ };
66
+
67
+ export type NativeMultipartUploadRequest = {
68
+ headers: Record<string, string>;
69
+ method: string;
70
+ onProgress?: (progress: { loaded: number; total?: number }) => void;
71
+ parts: NativeMultipartUploadPart[];
72
+ progress?: NativeMultipartUploadProgressConfig;
73
+ signal?: NativeMultipartAbortSignal;
74
+ timeoutMs?: number;
75
+ url: string;
76
+ };
77
+
78
+ export type NativeMultipartUploaderRequest = Omit<NativeMultipartUploadRequest, 'progress'> & {
79
+ progress?: NativeMultipartUploaderProgressConfig;
80
+ uploadId: string;
81
+ };
82
+
83
+ export type NativeMultipartUpload = (
84
+ request: NativeMultipartUploadRequest,
85
+ ) => Promise<NativeMultipartUploadResult | undefined> | never;
86
+
87
+ export type NativeMultipartUploader = (
88
+ request: NativeMultipartUploaderRequest,
89
+ ) => Promise<NativeMultipartUploadResult>;
90
+
91
+ export type NativeMultipartUploaderModule = {
92
+ addListener(eventType: string): void;
93
+ cancelUpload(uploadId: string): Promise<void>;
94
+ removeListeners(count: number): void;
95
+ uploadMultipart(
96
+ uploadId: string,
97
+ url: string,
98
+ method: string,
99
+ headers: ReadonlyArray<NativeMultipartUploadHeader>,
100
+ parts: ReadonlyArray<NativeMultipartUploadPart>,
101
+ progress?: NativeMultipartUploaderProgressConfig,
102
+ timeoutMs?: number | null,
103
+ ): Promise<NativeMultipartUploadNativeResponse>;
104
+ };
105
+
106
+ export type NativeMultipartUploadEventEmitter = {
107
+ addListener(
108
+ eventType: string,
109
+ listener: (event: NativeMultipartUploadProgressEvent) => void,
110
+ ): { remove: () => void };
111
+ };
112
+
113
+ export type NativeMultipartCanceledError = Error & {
114
+ __CANCEL__: true;
115
+ code: typeof CANCELED_ERROR_CODE;
116
+ };
117
+
118
+ type CreateNativeMultipartUploaderOptions = {
119
+ eventEmitter?: NativeMultipartUploadEventEmitter;
120
+ };
121
+
122
+ type CreateNativeMultipartUploadOptions = {
123
+ getLocalAssetUri?: ((uri: string) => Promise<string | null | undefined>) | null;
124
+ uploadIdFactory?: () => string;
125
+ uploadMultipart?: NativeMultipartUploader;
126
+ };
127
+
128
+ const createDefaultUploadId = () =>
129
+ `stream-upload-${Date.now()}-${Math.random().toString(16).slice(2)}`;
130
+
131
+ const createCanceledError = (): NativeMultipartCanceledError => {
132
+ const error = new Error('Request aborted') as NativeMultipartCanceledError;
133
+ error.name = 'CanceledError';
134
+ error.code = CANCELED_ERROR_CODE;
135
+ // eslint-disable-next-line no-underscore-dangle -- Axios marks cancellation with this legacy field, and callers still use axios.isCancel.
136
+ error.__CANCEL__ = true;
137
+ return error;
138
+ };
139
+
140
+ const toUploadHeaders = (headers: Record<string, string>): NativeMultipartUploadHeader[] =>
141
+ Object.entries(headers).map(([name, value]) => ({ name, value }));
142
+
143
+ const fromUploadHeaders = (
144
+ headers?: ReadonlyArray<NativeMultipartUploadHeader> | null,
145
+ ): Record<string, string> | undefined => {
146
+ if (!headers?.length) {
147
+ return undefined;
148
+ }
149
+
150
+ return headers.reduce<Record<string, string>>((acc, header) => {
151
+ acc[header.name] = header.value;
152
+ return acc;
153
+ }, {});
154
+ };
155
+
156
+ const addAbortHandler = (signal: NativeMultipartAbortSignal | undefined, onAbort: () => void) => {
157
+ if (!signal) {
158
+ return () => undefined;
159
+ }
160
+
161
+ let handled = false;
162
+ const handleAbort = () => {
163
+ if (handled) {
164
+ return;
165
+ }
166
+
167
+ handled = true;
168
+ onAbort();
169
+ };
170
+
171
+ if (typeof signal.addEventListener === 'function') {
172
+ signal.addEventListener('abort', handleAbort, { once: true });
173
+ return () => {
174
+ signal.removeEventListener?.('abort', handleAbort);
175
+ };
176
+ }
177
+
178
+ const previousOnAbort = signal.onabort;
179
+ const chainedOnAbort = (...args: unknown[]) => {
180
+ previousOnAbort?.(...args);
181
+ handleAbort();
182
+ };
183
+
184
+ signal.onabort = chainedOnAbort;
185
+
186
+ return () => {
187
+ if (signal.onabort === chainedOnAbort) {
188
+ signal.onabort = previousOnAbort ?? null;
189
+ }
190
+ };
191
+ };
192
+
193
+ const getNativeProgressConfig = (
194
+ progress?: NativeMultipartUploadProgressConfig,
195
+ ): NativeMultipartUploaderProgressConfig | undefined => {
196
+ if (!progress) {
197
+ return undefined;
198
+ }
199
+
200
+ const nativeProgressConfig = { ...progress };
201
+ delete nativeProgressConfig.completionProgressCap;
202
+
203
+ return Object.keys(nativeProgressConfig).length ? nativeProgressConfig : undefined;
204
+ };
205
+
206
+ const isPhotoLibraryUri = (uri: string) => {
207
+ const normalizedUri = uri.toLowerCase();
208
+ return normalizedUri.startsWith('ph://') || normalizedUri.startsWith('assets-library://');
209
+ };
210
+
211
+ const sanitizeResolvedFileUri = (uri: string) => {
212
+ const normalizedUri = uri.startsWith('/') ? `file://${uri}` : uri;
213
+
214
+ if (!normalizedUri.startsWith('file://')) {
215
+ return normalizedUri;
216
+ }
217
+
218
+ return normalizedUri.split('#')[0].split('?')[0];
219
+ };
220
+
221
+ const resolvePartUri = async (
222
+ part: NativeMultipartUploadPart,
223
+ getLocalAssetUri: CreateNativeMultipartUploadOptions['getLocalAssetUri'],
224
+ ): Promise<NativeMultipartUploadPart> => {
225
+ if (
226
+ part.kind !== 'file' ||
227
+ typeof getLocalAssetUri !== 'function' ||
228
+ !isPhotoLibraryUri(part.uri)
229
+ ) {
230
+ return part;
231
+ }
232
+
233
+ try {
234
+ const resolvedUri = await getLocalAssetUri(part.uri);
235
+
236
+ return {
237
+ ...part,
238
+ uri: resolvedUri ? sanitizeResolvedFileUri(resolvedUri) : part.uri,
239
+ };
240
+ } catch {
241
+ return part;
242
+ }
243
+ };
244
+
245
+ export const createNativeMultipartUploader = (
246
+ nativeModule: NativeMultipartUploaderModule | null | undefined,
247
+ options: CreateNativeMultipartUploaderOptions = {},
248
+ ): NativeMultipartUploader | undefined => {
249
+ if (!nativeModule) {
250
+ return undefined;
251
+ }
252
+
253
+ const multipartUploadEventEmitter = options.eventEmitter ?? new NativeEventEmitter(nativeModule);
254
+
255
+ return async ({
256
+ headers,
257
+ method,
258
+ onProgress,
259
+ parts,
260
+ progress,
261
+ signal,
262
+ timeoutMs,
263
+ uploadId,
264
+ url,
265
+ }: NativeMultipartUploaderRequest): Promise<NativeMultipartUploadResult> => {
266
+ let progressSubscription:
267
+ | {
268
+ remove: () => void;
269
+ }
270
+ | undefined;
271
+ let uploadStarted = false;
272
+
273
+ const abortUpload = async () => {
274
+ try {
275
+ await nativeModule.cancelUpload(uploadId);
276
+ } catch {
277
+ // Ignore cancellation races for already-finished uploads.
278
+ }
279
+ };
280
+
281
+ const handleAbort = () => {
282
+ if (uploadStarted) {
283
+ abortUpload().catch(() => undefined);
284
+ }
285
+ };
286
+
287
+ if (signal?.aborted) {
288
+ throw createCanceledError();
289
+ }
290
+
291
+ const removeAbortListener = addAbortHandler(signal, handleAbort);
292
+
293
+ if (signal?.aborted) {
294
+ removeAbortListener();
295
+ throw createCanceledError();
296
+ }
297
+
298
+ if (onProgress) {
299
+ progressSubscription = multipartUploadEventEmitter.addListener(
300
+ STREAM_MULTIPART_UPLOAD_PROGRESS_EVENT,
301
+ (event: NativeMultipartUploadProgressEvent) => {
302
+ if (event.uploadId !== uploadId) {
303
+ return;
304
+ }
305
+
306
+ onProgress({
307
+ loaded: event.loaded,
308
+ total: typeof event.total === 'number' ? event.total : undefined,
309
+ });
310
+ },
311
+ );
312
+ }
313
+
314
+ try {
315
+ uploadStarted = true;
316
+ const response = await nativeModule.uploadMultipart(
317
+ uploadId,
318
+ url,
319
+ method,
320
+ toUploadHeaders(headers),
321
+ parts,
322
+ progress ?? {},
323
+ timeoutMs,
324
+ );
325
+
326
+ if (signal?.aborted) {
327
+ throw createCanceledError();
328
+ }
329
+
330
+ return {
331
+ body: response.body,
332
+ headers: fromUploadHeaders(response.headers),
333
+ status: response.status,
334
+ statusText: typeof response.statusText === 'string' ? response.statusText : undefined,
335
+ };
336
+ } catch (error) {
337
+ if (signal?.aborted) {
338
+ throw createCanceledError();
339
+ }
340
+
341
+ throw error;
342
+ } finally {
343
+ progressSubscription?.remove();
344
+ removeAbortListener();
345
+ }
346
+ };
347
+ };
348
+
349
+ export const createNativeMultipartUpload = ({
350
+ getLocalAssetUri,
351
+ uploadIdFactory = createDefaultUploadId,
352
+ uploadMultipart,
353
+ }: CreateNativeMultipartUploadOptions): NativeMultipartUpload | undefined => {
354
+ if (!uploadMultipart) {
355
+ return undefined;
356
+ }
357
+
358
+ return async ({
359
+ headers,
360
+ method,
361
+ onProgress,
362
+ parts,
363
+ progress,
364
+ signal,
365
+ timeoutMs,
366
+ url,
367
+ }: NativeMultipartUploadRequest) => {
368
+ const resolvedParts = await Promise.all(
369
+ parts.map((part) => resolvePartUri(part, getLocalAssetUri)),
370
+ );
371
+
372
+ return uploadMultipart({
373
+ headers,
374
+ method,
375
+ onProgress,
376
+ parts: resolvedParts,
377
+ progress: getNativeProgressConfig(progress),
378
+ signal,
379
+ timeoutMs,
380
+ uploadId: uploadIdFactory(),
381
+ url,
382
+ });
383
+ };
384
+ };
@@ -43,6 +43,8 @@ export type UploadAttachmentPreviewProps<A extends LocalUploadAttachment> = {
43
43
 
44
44
  export interface DefaultAttachmentData {
45
45
  originalFile?: File;
46
+ /** Matches `LocalAttachment.localMetadata.id` / `uploadManager` record id for pending uploads */
47
+ localId?: string;
46
48
  }
47
49
 
48
50
  export interface DefaultUserData {