@repzo/widget-react-native 1.2.2 → 1.3.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 (172) hide show
  1. package/CHANGELOG.md +88 -0
  2. package/README.md +43 -33
  3. package/android/build.gradle +22 -0
  4. package/android/src/main/java/expo/modules/repzowidget/RepzoVoicePlayer.kt +222 -0
  5. package/android/src/main/java/expo/modules/repzowidget/RepzoWidgetAudioModule.kt +68 -0
  6. package/expo-module.config.json +9 -0
  7. package/ios/RepzoVoicePlayer.swift +175 -0
  8. package/ios/RepzoWidgetAudio.podspec +26 -0
  9. package/ios/RepzoWidgetAudioModule.swift +65 -0
  10. package/lib/commonjs/api/client.js +40 -0
  11. package/lib/commonjs/api/client.js.map +1 -1
  12. package/lib/commonjs/attachments/pickers.js +80 -23
  13. package/lib/commonjs/attachments/pickers.js.map +1 -1
  14. package/lib/commonjs/audio/native-player.js +97 -0
  15. package/lib/commonjs/audio/native-player.js.map +1 -0
  16. package/lib/commonjs/audio/voice.js +34 -7
  17. package/lib/commonjs/audio/voice.js.map +1 -1
  18. package/lib/commonjs/i18n.js +2 -0
  19. package/lib/commonjs/i18n.js.map +1 -1
  20. package/lib/commonjs/locales.js +1 -0
  21. package/lib/commonjs/locales.js.map +1 -1
  22. package/lib/commonjs/state/widget-store.js +238 -40
  23. package/lib/commonjs/state/widget-store.js.map +1 -1
  24. package/lib/commonjs/types.js.map +1 -1
  25. package/lib/commonjs/ui/WidgetProvider.js +8 -6
  26. package/lib/commonjs/ui/WidgetProvider.js.map +1 -1
  27. package/lib/commonjs/ui/components/ImageViewer.js +20 -37
  28. package/lib/commonjs/ui/components/ImageViewer.js.map +1 -1
  29. package/lib/commonjs/ui/components/MessagesSkeleton.js +126 -0
  30. package/lib/commonjs/ui/components/MessagesSkeleton.js.map +1 -0
  31. package/lib/commonjs/ui/components/VoiceNote.js +8 -9
  32. package/lib/commonjs/ui/components/VoiceNote.js.map +1 -1
  33. package/lib/commonjs/ui/components/ZoomableImage.js +299 -0
  34. package/lib/commonjs/ui/components/ZoomableImage.js.map +1 -0
  35. package/lib/commonjs/ui/components/icons.js +22 -16
  36. package/lib/commonjs/ui/components/icons.js.map +1 -1
  37. package/lib/commonjs/ui/screens/chat/ChatView.js +42 -18
  38. package/lib/commonjs/ui/screens/chat/ChatView.js.map +1 -1
  39. package/lib/commonjs/ui/screens/chat/ChatView.styles.js +17 -9
  40. package/lib/commonjs/ui/screens/chat/ChatView.styles.js.map +1 -1
  41. package/lib/commonjs/ui/styles/theme.js +1 -0
  42. package/lib/commonjs/ui/styles/theme.js.map +1 -1
  43. package/lib/commonjs/ui/utils/conversations.js +17 -1
  44. package/lib/commonjs/ui/utils/conversations.js.map +1 -1
  45. package/lib/module/api/client.js +40 -0
  46. package/lib/module/api/client.js.map +1 -1
  47. package/lib/module/attachments/pickers.js +80 -23
  48. package/lib/module/attachments/pickers.js.map +1 -1
  49. package/lib/module/audio/native-player.js +93 -0
  50. package/lib/module/audio/native-player.js.map +1 -0
  51. package/lib/module/audio/voice.js +32 -6
  52. package/lib/module/audio/voice.js.map +1 -1
  53. package/lib/module/i18n.js +2 -0
  54. package/lib/module/i18n.js.map +1 -1
  55. package/lib/module/locales.js +1 -0
  56. package/lib/module/locales.js.map +1 -1
  57. package/lib/module/state/widget-store.js +238 -40
  58. package/lib/module/state/widget-store.js.map +1 -1
  59. package/lib/module/types.js.map +1 -1
  60. package/lib/module/ui/WidgetProvider.js +8 -6
  61. package/lib/module/ui/WidgetProvider.js.map +1 -1
  62. package/lib/module/ui/components/ImageViewer.js +21 -38
  63. package/lib/module/ui/components/ImageViewer.js.map +1 -1
  64. package/lib/module/ui/components/MessagesSkeleton.js +124 -0
  65. package/lib/module/ui/components/MessagesSkeleton.js.map +1 -0
  66. package/lib/module/ui/components/VoiceNote.js +9 -10
  67. package/lib/module/ui/components/VoiceNote.js.map +1 -1
  68. package/lib/module/ui/components/ZoomableImage.js +291 -0
  69. package/lib/module/ui/components/ZoomableImage.js.map +1 -0
  70. package/lib/module/ui/components/icons.js +21 -15
  71. package/lib/module/ui/components/icons.js.map +1 -1
  72. package/lib/module/ui/screens/chat/ChatView.js +44 -20
  73. package/lib/module/ui/screens/chat/ChatView.js.map +1 -1
  74. package/lib/module/ui/screens/chat/ChatView.styles.js +17 -9
  75. package/lib/module/ui/screens/chat/ChatView.styles.js.map +1 -1
  76. package/lib/module/ui/styles/theme.js +1 -0
  77. package/lib/module/ui/styles/theme.js.map +1 -1
  78. package/lib/module/ui/utils/conversations.js +15 -1
  79. package/lib/module/ui/utils/conversations.js.map +1 -1
  80. package/lib/typescript/commonjs/api/client.d.ts +27 -1
  81. package/lib/typescript/commonjs/api/client.d.ts.map +1 -1
  82. package/lib/typescript/commonjs/attachments/pickers.d.ts +22 -10
  83. package/lib/typescript/commonjs/attachments/pickers.d.ts.map +1 -1
  84. package/lib/typescript/commonjs/audio/native-player.d.ts +36 -0
  85. package/lib/typescript/commonjs/audio/native-player.d.ts.map +1 -0
  86. package/lib/typescript/commonjs/audio/voice.d.ts +17 -4
  87. package/lib/typescript/commonjs/audio/voice.d.ts.map +1 -1
  88. package/lib/typescript/commonjs/i18n.d.ts +1 -0
  89. package/lib/typescript/commonjs/i18n.d.ts.map +1 -1
  90. package/lib/typescript/commonjs/locales.d.ts.map +1 -1
  91. package/lib/typescript/commonjs/state/widget-store.d.ts +38 -3
  92. package/lib/typescript/commonjs/state/widget-store.d.ts.map +1 -1
  93. package/lib/typescript/commonjs/types.d.ts +17 -0
  94. package/lib/typescript/commonjs/types.d.ts.map +1 -1
  95. package/lib/typescript/commonjs/ui/WidgetProvider.d.ts.map +1 -1
  96. package/lib/typescript/commonjs/ui/components/ImageViewer.d.ts +2 -2
  97. package/lib/typescript/commonjs/ui/components/ImageViewer.d.ts.map +1 -1
  98. package/lib/typescript/commonjs/ui/components/MessagesSkeleton.d.ts +10 -0
  99. package/lib/typescript/commonjs/ui/components/MessagesSkeleton.d.ts.map +1 -0
  100. package/lib/typescript/commonjs/ui/components/VoiceNote.d.ts +2 -2
  101. package/lib/typescript/commonjs/ui/components/VoiceNote.d.ts.map +1 -1
  102. package/lib/typescript/commonjs/ui/components/ZoomableImage.d.ts +45 -0
  103. package/lib/typescript/commonjs/ui/components/ZoomableImage.d.ts.map +1 -0
  104. package/lib/typescript/commonjs/ui/components/icons.d.ts +2 -1
  105. package/lib/typescript/commonjs/ui/components/icons.d.ts.map +1 -1
  106. package/lib/typescript/commonjs/ui/screens/chat/ChatView.d.ts +1 -1
  107. package/lib/typescript/commonjs/ui/screens/chat/ChatView.d.ts.map +1 -1
  108. package/lib/typescript/commonjs/ui/screens/chat/ChatView.styles.d.ts +14 -9
  109. package/lib/typescript/commonjs/ui/screens/chat/ChatView.styles.d.ts.map +1 -1
  110. package/lib/typescript/commonjs/ui/screens/chat/ChatView.types.d.ts +4 -0
  111. package/lib/typescript/commonjs/ui/screens/chat/ChatView.types.d.ts.map +1 -1
  112. package/lib/typescript/commonjs/ui/styles/theme.d.ts +2 -0
  113. package/lib/typescript/commonjs/ui/styles/theme.d.ts.map +1 -1
  114. package/lib/typescript/commonjs/ui/utils/conversations.d.ts +10 -2
  115. package/lib/typescript/commonjs/ui/utils/conversations.d.ts.map +1 -1
  116. package/lib/typescript/module/api/client.d.ts +27 -1
  117. package/lib/typescript/module/api/client.d.ts.map +1 -1
  118. package/lib/typescript/module/attachments/pickers.d.ts +22 -10
  119. package/lib/typescript/module/attachments/pickers.d.ts.map +1 -1
  120. package/lib/typescript/module/audio/native-player.d.ts +36 -0
  121. package/lib/typescript/module/audio/native-player.d.ts.map +1 -0
  122. package/lib/typescript/module/audio/voice.d.ts +17 -4
  123. package/lib/typescript/module/audio/voice.d.ts.map +1 -1
  124. package/lib/typescript/module/i18n.d.ts +1 -0
  125. package/lib/typescript/module/i18n.d.ts.map +1 -1
  126. package/lib/typescript/module/locales.d.ts.map +1 -1
  127. package/lib/typescript/module/state/widget-store.d.ts +38 -3
  128. package/lib/typescript/module/state/widget-store.d.ts.map +1 -1
  129. package/lib/typescript/module/types.d.ts +17 -0
  130. package/lib/typescript/module/types.d.ts.map +1 -1
  131. package/lib/typescript/module/ui/WidgetProvider.d.ts.map +1 -1
  132. package/lib/typescript/module/ui/components/ImageViewer.d.ts +2 -2
  133. package/lib/typescript/module/ui/components/ImageViewer.d.ts.map +1 -1
  134. package/lib/typescript/module/ui/components/MessagesSkeleton.d.ts +10 -0
  135. package/lib/typescript/module/ui/components/MessagesSkeleton.d.ts.map +1 -0
  136. package/lib/typescript/module/ui/components/VoiceNote.d.ts +2 -2
  137. package/lib/typescript/module/ui/components/VoiceNote.d.ts.map +1 -1
  138. package/lib/typescript/module/ui/components/ZoomableImage.d.ts +45 -0
  139. package/lib/typescript/module/ui/components/ZoomableImage.d.ts.map +1 -0
  140. package/lib/typescript/module/ui/components/icons.d.ts +2 -1
  141. package/lib/typescript/module/ui/components/icons.d.ts.map +1 -1
  142. package/lib/typescript/module/ui/screens/chat/ChatView.d.ts +1 -1
  143. package/lib/typescript/module/ui/screens/chat/ChatView.d.ts.map +1 -1
  144. package/lib/typescript/module/ui/screens/chat/ChatView.styles.d.ts +14 -9
  145. package/lib/typescript/module/ui/screens/chat/ChatView.styles.d.ts.map +1 -1
  146. package/lib/typescript/module/ui/screens/chat/ChatView.types.d.ts +4 -0
  147. package/lib/typescript/module/ui/screens/chat/ChatView.types.d.ts.map +1 -1
  148. package/lib/typescript/module/ui/styles/theme.d.ts +2 -0
  149. package/lib/typescript/module/ui/styles/theme.d.ts.map +1 -1
  150. package/lib/typescript/module/ui/utils/conversations.d.ts +10 -2
  151. package/lib/typescript/module/ui/utils/conversations.d.ts.map +1 -1
  152. package/package.json +10 -3
  153. package/react-native.config.js +12 -0
  154. package/src/api/client.ts +66 -2
  155. package/src/attachments/pickers.ts +87 -25
  156. package/src/audio/native-player.ts +106 -0
  157. package/src/audio/voice.ts +30 -6
  158. package/src/i18n.ts +2 -0
  159. package/src/locales.ts +1 -0
  160. package/src/state/widget-store.ts +233 -33
  161. package/src/types.ts +17 -0
  162. package/src/ui/WidgetProvider.tsx +16 -7
  163. package/src/ui/components/ImageViewer.tsx +21 -36
  164. package/src/ui/components/MessagesSkeleton.tsx +92 -0
  165. package/src/ui/components/VoiceNote.tsx +6 -14
  166. package/src/ui/components/ZoomableImage.tsx +250 -0
  167. package/src/ui/components/icons.tsx +10 -12
  168. package/src/ui/screens/chat/ChatView.styles.ts +17 -9
  169. package/src/ui/screens/chat/ChatView.tsx +49 -25
  170. package/src/ui/screens/chat/ChatView.types.ts +4 -0
  171. package/src/ui/styles/theme.ts +6 -0
  172. package/src/ui/utils/conversations.ts +16 -2
@@ -1 +1 @@
1
- {"version":3,"file":"ChatView.d.ts","sourceRoot":"","sources":["../../../../../../src/ui/screens/chat/ChatView.tsx"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AA0DH,OAAO,KAAK,EAAc,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAElE,wBAAgB,QAAQ,CAAC,EACvB,QAAQ,EACR,OAAO,EACP,eAAe,EACf,WAAW,EACX,KAAK,EACL,SAAS,EACT,WAAW,EACX,WAAW,EACX,QAAQ,EACR,QAAQ,EACR,WAAgB,EAChB,cAAc,EACd,MAAM,EACN,WAAW,EACX,cAAc,EACd,MAAM,EACN,iBAAsB,EACtB,iBAAiB,EACjB,aAAa,EACb,iBAAiB,EACjB,kBAAkB,EAClB,aAAa,EACb,UAAU,EACV,QAAQ,EACR,MAAM,EACN,OAAO,GACR,EAAE,aAAa,+BAuiBf"}
1
+ {"version":3,"file":"ChatView.d.ts","sourceRoot":"","sources":["../../../../../../src/ui/screens/chat/ChatView.tsx"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AA2DH,OAAO,KAAK,EAAc,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAElE,wBAAgB,QAAQ,CAAC,EACvB,QAAQ,EACR,OAAO,EACP,eAAe,EACf,WAAW,EACX,KAAK,EACL,SAAS,EACT,WAAW,EACX,WAAW,EACX,QAAQ,EACR,QAAQ,EACR,WAAgB,EAChB,cAAc,EACd,MAAM,EACN,WAAW,EACX,cAAc,EACd,MAAM,EACN,iBAAsB,EACtB,iBAAiB,EACjB,aAAa,EACb,cAAc,EACd,iBAAiB,EACjB,kBAAkB,EAClB,aAAa,EACb,UAAU,EACV,QAAQ,EACR,MAAM,EACN,OAAO,GACR,EAAE,aAAa,+BAmjBf"}
@@ -78,6 +78,10 @@ export declare const styles: {
78
78
  flexGrow: number;
79
79
  justifyContent: "center";
80
80
  };
81
+ messagesContentLoading: {
82
+ flexGrow: number;
83
+ justifyContent: "flex-end";
84
+ };
81
85
  emptyState: {
82
86
  alignItems: "center";
83
87
  paddingHorizontal: 20;
@@ -111,13 +115,6 @@ export declare const styles: {
111
115
  fontSize: 13;
112
116
  fontWeight: "600";
113
117
  };
114
- stateText: {
115
- textAlign: "center";
116
- paddingVertical: number;
117
- paddingHorizontal: 20;
118
- color: "#9ca3af";
119
- fontSize: 13;
120
- };
121
118
  messageRow: {
122
119
  maxWidth: "100%";
123
120
  };
@@ -207,9 +204,17 @@ export declare const styles: {
207
204
  height: number;
208
205
  borderRadius: 10;
209
206
  };
207
+ failedRow: {
208
+ flexDirection: "row";
209
+ alignItems: "center";
210
+ gap: number;
211
+ marginTop: number;
212
+ marginEnd: 4;
213
+ };
210
214
  failedText: {
211
- fontSize: 12;
212
- marginTop: 4;
215
+ fontSize: number;
216
+ fontWeight: "600";
217
+ color: "#ef4444";
213
218
  };
214
219
  deletedBubble: {
215
220
  maxWidth: "80%";
@@ -1 +1 @@
1
- {"version":3,"file":"ChatView.styles.d.ts","sourceRoot":"","sources":["../../../../../../src/ui/screens/chat/ChatView.styles.ts"],"names":[],"mappings":"AAAA,kFAAkF;AAOlF,eAAO,MAAM,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA0ZjB,CAAC"}
1
+ {"version":3,"file":"ChatView.styles.d.ts","sourceRoot":"","sources":["../../../../../../src/ui/screens/chat/ChatView.styles.ts"],"names":[],"mappings":"AAAA,kFAAkF;AAOlF,eAAO,MAAM,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAkajB,CAAC"}
@@ -13,6 +13,8 @@ export interface ChatLabels {
13
13
  emptyConversation: string;
14
14
  messagePlaceholder: string;
15
15
  failedToSend: string;
16
+ /** After `failedToSend` on a bubble that can be re-sent. */
17
+ tapToRetry: string;
16
18
  thinking: string;
17
19
  team: string;
18
20
  statusOnline: string;
@@ -90,6 +92,8 @@ export interface ChatViewProps {
90
92
  onPickAttachments?: (source: AttachmentSource, current: DraftAttachment[]) => Promise<PickResult>;
91
93
  /** Rejects when the send fails, so the composer can keep its attachments. */
92
94
  onSendMessage: (body: string, attachments?: OutgoingAttachment[], action?: AgentUiAction) => Promise<void>;
95
+ /** Re-sends a failed bubble; rejects when it fails again. Absent: no retry. */
96
+ onRetryMessage?: (messageId: string) => Promise<void>;
93
97
  onIdentifyVisitor?: (data: {
94
98
  email?: string;
95
99
  name?: string;
@@ -1 +1 @@
1
- {"version":3,"file":"ChatView.types.d.ts","sourceRoot":"","sources":["../../../../../../src/ui/screens/chat/ChatView.types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"ChatView.types.d.ts","sourceRoot":"","sources":["../../../../../../src/ui/screens/chat/ChatView.types.ts"],"names":[],"mappings":"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"}
@@ -62,6 +62,8 @@ export interface WidgetTheme {
62
62
  allowNewConversations: boolean;
63
63
  showPreviousConversations: boolean;
64
64
  attachmentsEnabled: boolean;
65
+ /** The server's upload cap; null when it only takes files inline in the message. */
66
+ maxAttachmentBytes: number | null;
65
67
  preChatEnabled: boolean;
66
68
  }
67
69
  export declare const DEFAULT_PRIMARY_COLOR = "#0d9488";
@@ -1 +1 @@
1
- {"version":3,"file":"theme.d.ts","sourceRoot":"","sources":["../../../../../src/ui/styles/theme.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEhD,eAAO,MAAM,OAAO;;;;;;;;;;;;;;;;;;CAkBV,CAAC;AAEX,eAAO,MAAM,MAAM;;;;;;CAMT,CAAC;AAEX,eAAO,MAAM,OAAO;;;;;;;CAOV,CAAC;AAEX,iEAAiE;AACjE,eAAO,MAAM,QAAQ;;;;;;;;;CASX,CAAC;AAEX,MAAM,WAAW,WAAW;IAC1B,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC;IAC3B,QAAQ,EAAE,OAAO,CAAC;IAClB,4EAA4E;IAC5E,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,sBAAsB,EAAE,MAAM,CAAC;IAC/B,cAAc,EAAE,MAAM,CAAC;IACvB,qBAAqB,EAAE,OAAO,CAAC;IAC/B,yBAAyB,EAAE,OAAO,CAAC;IACnC,kBAAkB,EAAE,OAAO,CAAC;IAC5B,cAAc,EAAE,OAAO,CAAC;CACzB;AAED,eAAO,MAAM,qBAAqB,YAAY,CAAC;AAE/C;;;;GAIG;AAGH,wBAAgB,UAAU,CAAC,MAAM,EAAE,YAAY,GAAG,IAAI,GAAG,WAAW,CAgBnE;AAED,kFAAkF;AAClF,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAI9D;AAED,wBAAgB,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI,CAYvE;AAED,wFAAwF;AACxF,wBAAgB,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAMtE;AAED,kEAAkE;AAClE,wBAAgB,oBAAoB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAM/D;AAED,eAAO,MAAM,OAAO;;;;;;;;;;;;;;;;;;;;;CAeV,CAAC"}
1
+ {"version":3,"file":"theme.d.ts","sourceRoot":"","sources":["../../../../../src/ui/styles/theme.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEhD,eAAO,MAAM,OAAO;;;;;;;;;;;;;;;;;;CAkBV,CAAC;AAEX,eAAO,MAAM,MAAM;;;;;;CAMT,CAAC;AAEX,eAAO,MAAM,OAAO;;;;;;;CAOV,CAAC;AAEX,iEAAiE;AACjE,eAAO,MAAM,QAAQ;;;;;;;;;CASX,CAAC;AAEX,MAAM,WAAW,WAAW;IAC1B,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC;IAC3B,QAAQ,EAAE,OAAO,CAAC;IAClB,4EAA4E;IAC5E,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,sBAAsB,EAAE,MAAM,CAAC;IAC/B,cAAc,EAAE,MAAM,CAAC;IACvB,qBAAqB,EAAE,OAAO,CAAC;IAC/B,yBAAyB,EAAE,OAAO,CAAC;IACnC,kBAAkB,EAAE,OAAO,CAAC;IAC5B,oFAAoF;IACpF,kBAAkB,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,cAAc,EAAE,OAAO,CAAC;CACzB;AAED,eAAO,MAAM,qBAAqB,YAAY,CAAC;AAE/C;;;;GAIG;AAGH,wBAAgB,UAAU,CAAC,MAAM,EAAE,YAAY,GAAG,IAAI,GAAG,WAAW,CAoBnE;AAED,kFAAkF;AAClF,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAI9D;AAED,wBAAgB,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI,CAYvE;AAED,wFAAwF;AACxF,wBAAgB,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAMtE;AAED,kEAAkE;AAClE,wBAAgB,oBAAoB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAM/D;AAED,eAAO,MAAM,OAAO;;;;;;;;;;;;;;;;;;;;;CAeV,CAAC"}
@@ -1,9 +1,10 @@
1
1
  /**
2
2
  * Conversation selectors and formatting shared by the Home and Messages
3
- * spaces, so the two can never disagree about ordering or wording.
3
+ * spaces (and the store and chat thread, for message helpers), so they can
4
+ * never disagree about ordering or wording.
4
5
  * Pure and free of React so they stay trivial to test.
5
6
  */
6
- import type { Conversation } from '../../types';
7
+ import type { Conversation, Message } from '../../types';
7
8
  export interface TeamAgent {
8
9
  name: string | null;
9
10
  avatar: string | null;
@@ -38,4 +39,11 @@ export declare function formatDayLabel(dateStr: string, locale: string, labels:
38
39
  today: string;
39
40
  yesterday: string;
40
41
  }): string;
42
+ /** Optimistic visitor bubbles the server hasn't acknowledged (pending/failed). */
43
+ export declare function isLocalMessage(message: Message): boolean;
44
+ /**
45
+ * The visitor's own bubble that never reached the server. A `failed` status
46
+ * the server sets on any other message isn't something this device can re-send.
47
+ */
48
+ export declare function canRetryMessage(message: Message): boolean;
41
49
  //# sourceMappingURL=conversations.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"conversations.d.ts","sourceRoot":"","sources":["../../../../../src/ui/utils/conversations.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEhD,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;CACvB;AAOD;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,aAAa,EAAE,YAAY,EAAE,GAAG,YAAY,EAAE,CAE3E;AAED,wBAAgB,uBAAuB,CAAC,aAAa,EAAE,YAAY,EAAE,EAAE,KAAK,SAAI,GAAG,YAAY,EAAE,CAEhG;AAED,iFAAiF;AACjF,wBAAgB,cAAc,CAAC,aAAa,EAAE,YAAY,EAAE,EAAE,KAAK,SAAI,GAAG,SAAS,EAAE,CAepF;AAED,4EAA4E;AAC5E,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;CACf;AAED,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,kBAAkB,GAAG,MAAM,CActF;AAKD,wBAAgB,SAAS,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,OAAO,CAEvD;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAQvE;AAED,yEAAyE;AACzE,wBAAgB,cAAc,CAC5B,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,MAAM,EACd,MAAM,EAAE;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,GAC3C,MAAM,CAmBR"}
1
+ {"version":3,"file":"conversations.d.ts","sourceRoot":"","sources":["../../../../../src/ui/utils/conversations.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,KAAK,EAAE,YAAY,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAEzD,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;CACvB;AAOD;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,aAAa,EAAE,YAAY,EAAE,GAAG,YAAY,EAAE,CAE3E;AAED,wBAAgB,uBAAuB,CAAC,aAAa,EAAE,YAAY,EAAE,EAAE,KAAK,SAAI,GAAG,YAAY,EAAE,CAEhG;AAED,iFAAiF;AACjF,wBAAgB,cAAc,CAAC,aAAa,EAAE,YAAY,EAAE,EAAE,KAAK,SAAI,GAAG,SAAS,EAAE,CAepF;AAED,4EAA4E;AAC5E,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;CACf;AAED,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,kBAAkB,GAAG,MAAM,CActF;AAKD,wBAAgB,SAAS,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,OAAO,CAEvD;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAQvE;AAED,yEAAyE;AACzE,wBAAgB,cAAc,CAC5B,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,MAAM,EACd,MAAM,EAAE;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,GAC3C,MAAM,CAmBR;AAED,kFAAkF;AAClF,wBAAgB,cAAc,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAExD;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAEzD"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@repzo/widget-react-native",
3
- "version": "1.2.2",
4
- "description": "Repzo chat widget for React Native \u2014 pure JavaScript, no native modules",
3
+ "version": "1.3.1",
4
+ "description": "Repzo chat widget for React Native",
5
5
  "source": "./src/index.tsx",
6
6
  "main": "./lib/commonjs/index.js",
7
7
  "module": "./lib/module/index.js",
@@ -30,7 +30,14 @@
30
30
  "!**/__mocks__",
31
31
  "!**/.*",
32
32
  "metro.js",
33
- "CHANGELOG.md"
33
+ "CHANGELOG.md",
34
+ "android",
35
+ "!android/build",
36
+ "!android/.gradle",
37
+ "!android/.cxx",
38
+ "ios",
39
+ "expo-module.config.json",
40
+ "react-native.config.js"
34
41
  ],
35
42
  "scripts": {
36
43
  "test": "bob build && jest",
@@ -0,0 +1,12 @@
1
+ // The native voice player is an Expo module, linked through
2
+ // expo-module.config.json. This keeps the React Native CLI from also trying
3
+ // to link it as a classic native module, which apps without Expo modules
4
+ // would fail to build. Without Expo modules, voice notes stay a file row.
5
+ module.exports = {
6
+ dependency: {
7
+ platforms: {
8
+ android: null,
9
+ ios: null,
10
+ },
11
+ },
12
+ };
package/src/api/client.ts CHANGED
@@ -337,15 +337,79 @@ export class WidgetApiClient {
337
337
  conversationId?: string;
338
338
  forceNewConversation?: boolean;
339
339
  body: string;
340
- /** `url` is a base64 data URL; max 5, each <= 1.5 MB decoded. */
341
- attachments?: Array<{ name: string; url: string; contentType: string; size: number }>;
340
+ /**
341
+ * Max 5. Either inline (`url`, a base64 data URL, each <= 1.5 MB decoded)
342
+ * or uploaded first (`uploadToken`, from uploadAttachment).
343
+ */
344
+ attachments?: Array<
345
+ | { name: string; url: string; contentType: string; size: number }
346
+ | { name: string; uploadToken: string; contentType: string; size: number }
347
+ >;
342
348
  visitorName?: string;
343
349
  visitorEmail?: string;
344
350
  agentUiAction?: AgentUiAction;
351
+ /**
352
+ * Same on every retry of a message. Newer Workstation deployments store it
353
+ * once and echo it on the stored row; older ones don't read it.
354
+ */
355
+ clientMessageId?: string;
345
356
  }): Promise<{ messageId: string; conversationId: string; isNewConversation: boolean }> {
346
357
  return this.request('/api/widget/messages', { method: 'POST', body: data });
347
358
  }
348
359
 
360
+ /**
361
+ * Puts a local file in storage ahead of the message, which then names it by
362
+ * the returned upload token. The target is presigned storage, or a path on
363
+ * the server where its storage can't presign. The file is read as a native
364
+ * blob, so it never becomes a JS string; its size is the blob's, since the
365
+ * upload URL is signed for exactly the size asked for.
366
+ */
367
+ async uploadAttachment(file: {
368
+ uri: string;
369
+ name: string;
370
+ contentType: string;
371
+ size: number;
372
+ }): Promise<string> {
373
+ let blob: Blob;
374
+ try {
375
+ blob = await (await fetch(file.uri)).blob();
376
+ } catch (error) {
377
+ throw new RepzoWidgetError(
378
+ error instanceof Error ? error.message : 'Could not read the file',
379
+ 0,
380
+ 'network'
381
+ );
382
+ }
383
+ const target = await this.request<{
384
+ uploadUrl: string;
385
+ method?: string;
386
+ headers?: Record<string, string>;
387
+ uploadToken: string;
388
+ }>('/api/widget/uploads', {
389
+ method: 'POST',
390
+ body: { fileName: file.name, contentType: file.contentType, size: blob.size || file.size },
391
+ });
392
+ const uploadUrl = this.resolveUrl(target.uploadUrl);
393
+ if (!uploadUrl) throw new RepzoWidgetError('Invalid upload target', 0, 'http');
394
+
395
+ let response: Response;
396
+ try {
397
+ response = await fetch(uploadUrl, {
398
+ method: target.method ?? 'PUT',
399
+ headers: target.headers ?? { 'Content-Type': file.contentType },
400
+ body: blob,
401
+ });
402
+ } catch (error) {
403
+ throw new RepzoWidgetError(
404
+ error instanceof Error ? error.message : 'Upload failed',
405
+ 0,
406
+ 'network'
407
+ );
408
+ }
409
+ if (!response.ok) throw new RepzoWidgetError('Upload failed', response.status, 'http');
410
+ return target.uploadToken;
411
+ }
412
+
349
413
  listConversations(): Promise<{ data: Conversation[] }> {
350
414
  return this.request('/api/widget/conversations');
351
415
  }
@@ -1,6 +1,10 @@
1
1
  /**
2
- * Visitor attachments: picking photos/files and turning them into the base64
3
- * data URLs `POST /api/widget/messages` accepts.
2
+ * Visitor attachments: picking photos/files for `POST /api/widget/messages`.
3
+ *
4
+ * A server that takes uploads names its cap (`maxAttachmentBytes`): a picked
5
+ * file then stays a local URI and is uploaded when the message is sent. An
6
+ * older server only takes files inline, as base64 data URLs within the
7
+ * 1.5 MB / 6 MB caps below.
4
8
  *
5
9
  * The SDK ships no native code, so every picker is borrowed from the host —
6
10
  * the same approach as `storage.ts` and `native-gradient.ts`:
@@ -8,7 +12,7 @@
8
12
  * - `expo-document-picker` → any file
9
13
  * - `expo-file-system` → reads a picked file as base64 (optional; without it
10
14
  * the file is read through `fetch` + `FileReader`)
11
- * - `expo-image-manipulator` → shrinks photos to fit the 1.5 MB cap (optional;
15
+ * - `expo-image-manipulator` → shrinks photos, to fit the 1.5 MB inline cap (optional;
12
16
  * without it a full-resolution camera photo is usually refused as too large)
13
17
  *
14
18
  * A host without those (or with `react-native-image-picker` etc.) plugs its own
@@ -23,10 +27,10 @@ import type { AttachmentSource, RepzoPickedFile, RepzoWidgetAttachmentOptions }
23
27
 
24
28
  /** The server keeps the first five and silently drops the rest. */
25
29
  export const MAX_ATTACHMENTS = 5;
26
- /** Per file, decoded. The server silently drops anything larger. */
30
+ /** Per inline file, decoded. The server silently drops anything larger. */
27
31
  export const MAX_ATTACHMENT_BYTES = 1_500_000;
28
32
  /**
29
- * All files in one message, decoded. Base64 inside JSON inflates bytes by
33
+ * All inline files in one message, decoded. Base64 inside JSON inflates bytes by
30
34
  * ~1.37x and the server caps a request body at 10 MB, so five files at the
31
35
  * per-file maximum would come back as a 413.
32
36
  */
@@ -35,15 +39,17 @@ export const MAX_TOTAL_ATTACHMENT_BYTES = 6_000_000;
35
39
  /** Photos are re-encoded at this JPEG quality — small enough to fit the cap more often. */
36
40
  const IMAGE_QUALITY = 0.7;
37
41
 
38
- /** A picked file, encoded and ready to send; what the composer shows as a chip. */
42
+ /** A picked file, ready to send; what the composer shows as a chip. */
39
43
  export interface DraftAttachment {
40
44
  id: string;
41
45
  name: string;
42
46
  contentType: string;
43
47
  /** Decoded bytes — what the server measures against its cap. */
44
48
  size: number;
45
- /** `data:<mime>;base64,<…>` — the wire format. */
49
+ /** `data:<mime>;base64,<…>` for an inline file; empty when `fileUri` is uploaded instead. */
46
50
  dataUrl: string;
51
+ /** The local file uploaded when the message is sent. */
52
+ fileUri?: string;
47
53
  /** What `<Image>` renders locally: the picked file's URI, else the data URL. */
48
54
  previewUri: string;
49
55
  }
@@ -139,11 +145,13 @@ const CAMERA_DENIED = 'cameraDenied' as const;
139
145
 
140
146
  /**
141
147
  * Opens the picker for `source` and returns what fits alongside `current`.
148
+ * `uploadLimit` is the server's upload cap; without it files go inline.
142
149
  * Never throws: a cancelled pick is `{ added: [], problem: null }`.
143
150
  */
144
151
  export async function pickAttachments(
145
152
  source: AttachmentSource,
146
- current: DraftAttachment[]
153
+ current: DraftAttachment[],
154
+ uploadLimit: number | null = null
147
155
  ): Promise<PickResult> {
148
156
  const slots = MAX_ATTACHMENTS - current.length;
149
157
  if (slots <= 0) return { added: [], problem: 'tooMany' };
@@ -152,7 +160,7 @@ export async function pickAttachments(
152
160
  try {
153
161
  picked = hostOptions?.pick
154
162
  ? await hostOptions.pick(source)
155
- : await launchBuiltInPicker(source, slots);
163
+ : await launchBuiltInPicker(source, slots, !!uploadLimit);
156
164
  } catch (error) {
157
165
  console.warn('[repzo-widget] attachment picker failed', error);
158
166
  return { added: [], problem: 'failed' };
@@ -170,8 +178,9 @@ export async function pickAttachments(
170
178
  }
171
179
  // A size the picker already knows rules a file out before it's read, so
172
180
  // a 40 MB video never lands in memory as a base64 string.
173
- const declared = file.base64 ? null : validSize(file.size);
174
- const early = declared === null ? null : checkAttachmentFits(accepted, declared);
181
+ const uploads = !!uploadLimit && !!file.uri;
182
+ const declared = file.base64 && !uploads ? null : validSize(file.size);
183
+ const early = declared === null ? null : checkAttachmentFits(accepted, declared, uploads ? uploadLimit : null);
175
184
  if (early) {
176
185
  problem = problem ?? early;
177
186
  continue;
@@ -179,7 +188,7 @@ export async function pickAttachments(
179
188
 
180
189
  let draft: DraftAttachment;
181
190
  try {
182
- draft = await toDraft(file);
191
+ draft = uploads ? await toUploadDraft(file, uploadLimit!) : await toDraft(file);
183
192
  } catch (error) {
184
193
  if (error === TOO_LARGE) {
185
194
  problem = problem ?? 'tooLarge';
@@ -189,7 +198,8 @@ export async function pickAttachments(
189
198
  problem = problem ?? 'failed';
190
199
  continue;
191
200
  }
192
- const fits = draft.size > 0 ? checkAttachmentFits(accepted, draft.size) : 'failed';
201
+ const fits =
202
+ draft.size > 0 ? checkAttachmentFits(accepted, draft.size, uploads ? uploadLimit : null) : 'failed';
193
203
  if (fits) {
194
204
  problem = problem ?? fits;
195
205
  continue;
@@ -201,7 +211,8 @@ export async function pickAttachments(
201
211
 
202
212
  async function launchBuiltInPicker(
203
213
  source: AttachmentSource,
204
- slots: number
214
+ slots: number,
215
+ uploads: boolean
205
216
  ): Promise<RepzoPickedFile[] | null | typeof CAMERA_DENIED> {
206
217
  if (source === 'file') {
207
218
  if (!documentPicker) return null;
@@ -224,9 +235,10 @@ async function launchBuiltInPicker(
224
235
  if (!imagePicker) return null;
225
236
  const options = {
226
237
  quality: IMAGE_QUALITY,
227
- // With the manipulator the photo is re-encoded below, so the picker's own
228
- // (full-resolution) base64 would only waste memory.
229
- base64: !imageManipulator,
238
+ // With the manipulator the photo is re-encoded below, and an uploaded
239
+ // photo is read from its file, so the picker's own (full-resolution)
240
+ // base64 would only waste memory.
241
+ base64: !uploads && !imageManipulator,
230
242
  exif: false,
231
243
  };
232
244
 
@@ -246,7 +258,7 @@ async function launchBuiltInPicker(
246
258
  const assets: any[] = Array.isArray(result?.assets) ? result.assets : [];
247
259
  const files: RepzoPickedFile[] = [];
248
260
  for (const [index, asset] of assets.slice(0, slots).entries()) {
249
- const shrunk = await shrinkPhoto(asset);
261
+ const shrunk = await shrinkPhoto(asset, !uploads);
250
262
  const type = shrunk
251
263
  ? 'image/jpeg'
252
264
  : asset.mimeType || guessContentType(asset.fileName || asset.uri) || 'image/jpeg';
@@ -254,7 +266,7 @@ async function launchBuiltInPicker(
254
266
  files.push({
255
267
  name: `${baseName}.${extensionFor(type)}`,
256
268
  type,
257
- // No `size`: `fileSize` is the original's, not the re-encoded base64's.
269
+ // No `size`: `fileSize` is the original's, not the re-encoded file's.
258
270
  uri: shrunk?.uri ?? asset.uri,
259
271
  base64: shrunk?.base64 ?? asset.base64 ?? undefined,
260
272
  });
@@ -282,7 +294,10 @@ async function launchImagePicker(launch: (options: object) => Promise<any>, opti
282
294
  * MAX_IMAGE_EDGE. Null when the manipulator isn't installed or fails — the
283
295
  * caller then falls back to the picker's own output (and the size check).
284
296
  */
285
- async function shrinkPhoto(asset: any): Promise<{ uri: string; base64: string } | null> {
297
+ async function shrinkPhoto(
298
+ asset: any,
299
+ withBase64: boolean
300
+ ): Promise<{ uri: string; base64?: string } | null> {
286
301
  if (!imageManipulator || !asset?.uri) return null;
287
302
  const width = Number(asset.width) || 0;
288
303
  const height = Number(asset.height) || 0;
@@ -296,7 +311,7 @@ async function shrinkPhoto(asset: any): Promise<{ uri: string; base64: string }
296
311
  const saveOptions = {
297
312
  compress: IMAGE_QUALITY,
298
313
  format: imageManipulator.SaveFormat?.JPEG ?? 'jpeg',
299
- base64: true,
314
+ base64: withBase64,
300
315
  };
301
316
  try {
302
317
  let output: any;
@@ -308,6 +323,7 @@ async function shrinkPhoto(asset: any): Promise<{ uri: string; base64: string }
308
323
  const image = await context.renderAsync();
309
324
  output = await image.saveAsync(saveOptions);
310
325
  }
326
+ if (!withBase64) return typeof output?.uri === 'string' && output.uri ? { uri: output.uri } : null;
311
327
  return typeof output?.base64 === 'string' && output.base64
312
328
  ? { uri: output.uri ?? asset.uri, base64: output.base64 }
313
329
  : null;
@@ -340,6 +356,46 @@ async function toDraft(file: RepzoPickedFile): Promise<DraftAttachment> {
340
356
  };
341
357
  }
342
358
 
359
+ /** A file that's uploaded when the message is sent: only its size is read here. */
360
+ async function toUploadDraft(file: RepzoPickedFile, uploadLimit: number): Promise<DraftAttachment> {
361
+ const uri = file.uri!;
362
+ const size = validSize(file.size) ?? (await readFileSize(uri));
363
+ if (size > uploadLimit) throw TOO_LARGE;
364
+ const name = (file.name || fileNameFromUri(uri) || 'file').slice(0, 255);
365
+ return {
366
+ id: `draft-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
367
+ name,
368
+ contentType: normalizeContentType(file.type) ?? guessContentType(name) ?? 'application/octet-stream',
369
+ size,
370
+ dataUrl: '',
371
+ fileUri: uri,
372
+ previewUri: uri,
373
+ };
374
+ }
375
+
376
+ /** A local file's size in bytes, or 0 when nothing can tell. */
377
+ async function readFileSize(uri: string): Promise<number> {
378
+ if (typeof fileSystem?.File === 'function') {
379
+ try {
380
+ const size = validSize(new fileSystem.File(uri).size);
381
+ if (size !== null) return size;
382
+ } catch {
383
+ // Fall through to the next reader.
384
+ }
385
+ }
386
+ if (typeof fileSystem?.getInfoAsync === 'function') {
387
+ try {
388
+ const size = validSize((await fileSystem.getInfoAsync(uri, { size: true }))?.size);
389
+ if (size !== null) return size;
390
+ } catch {
391
+ // Fall through to fetch.
392
+ }
393
+ }
394
+ // A native blob: its size is known without reading it into JS.
395
+ const blob = await (await fetch(uri)).blob();
396
+ return validSize(blob.size) ?? 0;
397
+ }
398
+
343
399
  /** Thrown (by identity) when a file is over the per-file cap before it's read. */
344
400
  const TOO_LARGE = new Error('File exceeds the attachment cap');
345
401
 
@@ -407,14 +463,20 @@ async function readBase64WithFetch(uri: string): Promise<string> {
407
463
 
408
464
  // ─── Pure helpers ──────────────────────────────────────────────────────────
409
465
 
410
- /** Null when a file of `size` bytes still fits beside `current`; otherwise the limit it breaks. */
466
+ /**
467
+ * Null when a file of `size` bytes still fits beside `current`; otherwise the
468
+ * limit it breaks. With `uploadLimit` the file is uploaded on its own, so
469
+ * only its own size counts; inline files also share one request body.
470
+ */
411
471
  export function checkAttachmentFits(
412
- current: ReadonlyArray<{ size: number }>,
413
- size: number
472
+ current: ReadonlyArray<{ size: number; fileUri?: string }>,
473
+ size: number,
474
+ uploadLimit: number | null = null
414
475
  ): AttachmentProblem | null {
415
476
  if (current.length >= MAX_ATTACHMENTS) return 'tooMany';
477
+ if (uploadLimit) return size > uploadLimit ? 'tooLarge' : null;
416
478
  if (size > MAX_ATTACHMENT_BYTES) return 'tooLarge';
417
- const total = current.reduce((sum, item) => sum + item.size, 0);
479
+ const total = current.filter((item) => !item.fileUri).reduce((sum, item) => sum + item.size, 0);
418
480
  if (total + size > MAX_TOTAL_ATTACHMENT_BYTES) return 'tooLargeTogether';
419
481
  return null;
420
482
  }
@@ -0,0 +1,106 @@
1
+ /**
2
+ * The SDK's own voice-note player (`ios/` and `android/`), on the system
3
+ * players: AVPlayer on iOS, MediaPlayer on Android. It's an Expo module, so it
4
+ * exists in apps with Expo modules after a native build; in Expo Go, bare
5
+ * React Native apps and an over-the-air update to an app built with an older
6
+ * SDK it's missing, and `src/audio/voice.ts` falls back to `expo-audio` or the
7
+ * file row.
8
+ */
9
+
10
+ import { useEffect, useMemo, useState } from 'react';
11
+
12
+ interface NativeAudioModule {
13
+ create(id: string, url: string): Promise<void>;
14
+ play(id: string): Promise<void>;
15
+ pause(id: string): Promise<void>;
16
+ seekTo(id: string, seconds: number): Promise<void>;
17
+ setRate(id: string, rate: number): Promise<void>;
18
+ release(id: string): Promise<void>;
19
+ addListener?(event: 'onStatus', listener: (status: NativeVoiceStatus) => void): { remove(): void };
20
+ }
21
+
22
+ /** What the player reports; the same fields `expo-audio`'s status has. */
23
+ export interface NativeVoiceStatus {
24
+ id: string;
25
+ currentTime: number;
26
+ duration: number;
27
+ playing: boolean;
28
+ isLoaded: boolean;
29
+ isBuffering: boolean;
30
+ didJustFinish: boolean;
31
+ error?: string | null;
32
+ }
33
+
34
+ function loadNativeAudio(): NativeAudioModule | null {
35
+ let mod: any = null;
36
+ try {
37
+ mod = require('expo-modules-core').requireOptionalNativeModule('RepzoWidgetAudio');
38
+ } catch {
39
+ mod = null;
40
+ }
41
+ // Without the JS package (an unusual setup), the module can still be installed.
42
+ mod = mod ?? (globalThis as any).expo?.modules?.RepzoWidgetAudio ?? null;
43
+ return mod && typeof mod.create === 'function' && typeof mod.play === 'function' ? mod : null;
44
+ }
45
+
46
+ const nativeAudio = loadNativeAudio();
47
+
48
+ /** Subscribes to every player's status; null when events can't be received. */
49
+ function addStatusListener(listener: (status: NativeVoiceStatus) => void): { remove(): void } | null {
50
+ if (!nativeAudio) return null;
51
+ try {
52
+ if (typeof nativeAudio.addListener === 'function') return nativeAudio.addListener('onStatus', listener);
53
+ // Expo SDK 51 and older: native modules aren't event emitters themselves.
54
+ const { EventEmitter } = require('expo-modules-core');
55
+ return new EventEmitter(nativeAudio).addListener('onStatus', listener);
56
+ } catch {
57
+ return null;
58
+ }
59
+ }
60
+
61
+ /** Whether the app has the SDK's own player. */
62
+ export function hasNativeVoicePlayer(): boolean {
63
+ return nativeAudio != null;
64
+ }
65
+
66
+ let nextPlayerId = 0;
67
+
68
+ const ignore = () => {};
69
+
70
+ /**
71
+ * A player for one note, released when the component goes away. Shaped like
72
+ * `expo-audio`'s `useAudioPlayer` + `useAudioPlayerStatus`, so the voice note
73
+ * drives either the same way.
74
+ */
75
+ export function useNativeVoicePlayer(url: string) {
76
+ const id = useMemo(() => `repzo-voice-${(nextPlayerId += 1)}`, []);
77
+ const [status, setStatus] = useState<Partial<NativeVoiceStatus>>({ isLoaded: false });
78
+
79
+ useEffect(() => {
80
+ const audio = nativeAudio!;
81
+ const subscription = addStatusListener((next) => {
82
+ if (next?.id === id) setStatus(next);
83
+ });
84
+ if (!subscription) {
85
+ setStatus({ isLoaded: false, error: 'Voice notes need a newer Expo SDK' });
86
+ return undefined;
87
+ }
88
+ audio.create(id, url).catch((error: unknown) => setStatus({ isLoaded: false, error: String(error) }));
89
+ return () => {
90
+ subscription.remove();
91
+ audio.release(id).catch(ignore);
92
+ };
93
+ }, [id, url]);
94
+
95
+ const player = useMemo(
96
+ () => ({
97
+ play: () => void nativeAudio!.play(id).catch(ignore),
98
+ pause: () => void nativeAudio!.pause(id).catch(ignore),
99
+ seekTo: (seconds: number) => nativeAudio!.seekTo(id, seconds).catch(ignore),
100
+ setPlaybackRate: (rate: number) => void nativeAudio!.setRate(id, rate).catch(ignore),
101
+ }),
102
+ [id]
103
+ );
104
+
105
+ return { player, status };
106
+ }