@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.
- package/CHANGELOG.md +88 -0
- package/README.md +43 -33
- package/android/build.gradle +22 -0
- package/android/src/main/java/expo/modules/repzowidget/RepzoVoicePlayer.kt +222 -0
- package/android/src/main/java/expo/modules/repzowidget/RepzoWidgetAudioModule.kt +68 -0
- package/expo-module.config.json +9 -0
- package/ios/RepzoVoicePlayer.swift +175 -0
- package/ios/RepzoWidgetAudio.podspec +26 -0
- package/ios/RepzoWidgetAudioModule.swift +65 -0
- package/lib/commonjs/api/client.js +40 -0
- package/lib/commonjs/api/client.js.map +1 -1
- package/lib/commonjs/attachments/pickers.js +80 -23
- package/lib/commonjs/attachments/pickers.js.map +1 -1
- package/lib/commonjs/audio/native-player.js +97 -0
- package/lib/commonjs/audio/native-player.js.map +1 -0
- package/lib/commonjs/audio/voice.js +34 -7
- package/lib/commonjs/audio/voice.js.map +1 -1
- package/lib/commonjs/i18n.js +2 -0
- package/lib/commonjs/i18n.js.map +1 -1
- package/lib/commonjs/locales.js +1 -0
- package/lib/commonjs/locales.js.map +1 -1
- package/lib/commonjs/state/widget-store.js +238 -40
- package/lib/commonjs/state/widget-store.js.map +1 -1
- package/lib/commonjs/types.js.map +1 -1
- package/lib/commonjs/ui/WidgetProvider.js +8 -6
- package/lib/commonjs/ui/WidgetProvider.js.map +1 -1
- package/lib/commonjs/ui/components/ImageViewer.js +20 -37
- package/lib/commonjs/ui/components/ImageViewer.js.map +1 -1
- package/lib/commonjs/ui/components/MessagesSkeleton.js +126 -0
- package/lib/commonjs/ui/components/MessagesSkeleton.js.map +1 -0
- package/lib/commonjs/ui/components/VoiceNote.js +8 -9
- package/lib/commonjs/ui/components/VoiceNote.js.map +1 -1
- package/lib/commonjs/ui/components/ZoomableImage.js +299 -0
- package/lib/commonjs/ui/components/ZoomableImage.js.map +1 -0
- package/lib/commonjs/ui/components/icons.js +22 -16
- package/lib/commonjs/ui/components/icons.js.map +1 -1
- package/lib/commonjs/ui/screens/chat/ChatView.js +42 -18
- package/lib/commonjs/ui/screens/chat/ChatView.js.map +1 -1
- package/lib/commonjs/ui/screens/chat/ChatView.styles.js +17 -9
- package/lib/commonjs/ui/screens/chat/ChatView.styles.js.map +1 -1
- package/lib/commonjs/ui/styles/theme.js +1 -0
- package/lib/commonjs/ui/styles/theme.js.map +1 -1
- package/lib/commonjs/ui/utils/conversations.js +17 -1
- package/lib/commonjs/ui/utils/conversations.js.map +1 -1
- package/lib/module/api/client.js +40 -0
- package/lib/module/api/client.js.map +1 -1
- package/lib/module/attachments/pickers.js +80 -23
- package/lib/module/attachments/pickers.js.map +1 -1
- package/lib/module/audio/native-player.js +93 -0
- package/lib/module/audio/native-player.js.map +1 -0
- package/lib/module/audio/voice.js +32 -6
- package/lib/module/audio/voice.js.map +1 -1
- package/lib/module/i18n.js +2 -0
- package/lib/module/i18n.js.map +1 -1
- package/lib/module/locales.js +1 -0
- package/lib/module/locales.js.map +1 -1
- package/lib/module/state/widget-store.js +238 -40
- package/lib/module/state/widget-store.js.map +1 -1
- package/lib/module/types.js.map +1 -1
- package/lib/module/ui/WidgetProvider.js +8 -6
- package/lib/module/ui/WidgetProvider.js.map +1 -1
- package/lib/module/ui/components/ImageViewer.js +21 -38
- package/lib/module/ui/components/ImageViewer.js.map +1 -1
- package/lib/module/ui/components/MessagesSkeleton.js +124 -0
- package/lib/module/ui/components/MessagesSkeleton.js.map +1 -0
- package/lib/module/ui/components/VoiceNote.js +9 -10
- package/lib/module/ui/components/VoiceNote.js.map +1 -1
- package/lib/module/ui/components/ZoomableImage.js +291 -0
- package/lib/module/ui/components/ZoomableImage.js.map +1 -0
- package/lib/module/ui/components/icons.js +21 -15
- package/lib/module/ui/components/icons.js.map +1 -1
- package/lib/module/ui/screens/chat/ChatView.js +44 -20
- package/lib/module/ui/screens/chat/ChatView.js.map +1 -1
- package/lib/module/ui/screens/chat/ChatView.styles.js +17 -9
- package/lib/module/ui/screens/chat/ChatView.styles.js.map +1 -1
- package/lib/module/ui/styles/theme.js +1 -0
- package/lib/module/ui/styles/theme.js.map +1 -1
- package/lib/module/ui/utils/conversations.js +15 -1
- package/lib/module/ui/utils/conversations.js.map +1 -1
- package/lib/typescript/commonjs/api/client.d.ts +27 -1
- package/lib/typescript/commonjs/api/client.d.ts.map +1 -1
- package/lib/typescript/commonjs/attachments/pickers.d.ts +22 -10
- package/lib/typescript/commonjs/attachments/pickers.d.ts.map +1 -1
- package/lib/typescript/commonjs/audio/native-player.d.ts +36 -0
- package/lib/typescript/commonjs/audio/native-player.d.ts.map +1 -0
- package/lib/typescript/commonjs/audio/voice.d.ts +17 -4
- package/lib/typescript/commonjs/audio/voice.d.ts.map +1 -1
- package/lib/typescript/commonjs/i18n.d.ts +1 -0
- package/lib/typescript/commonjs/i18n.d.ts.map +1 -1
- package/lib/typescript/commonjs/locales.d.ts.map +1 -1
- package/lib/typescript/commonjs/state/widget-store.d.ts +38 -3
- package/lib/typescript/commonjs/state/widget-store.d.ts.map +1 -1
- package/lib/typescript/commonjs/types.d.ts +17 -0
- package/lib/typescript/commonjs/types.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/WidgetProvider.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/components/ImageViewer.d.ts +2 -2
- package/lib/typescript/commonjs/ui/components/ImageViewer.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/components/MessagesSkeleton.d.ts +10 -0
- package/lib/typescript/commonjs/ui/components/MessagesSkeleton.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/components/VoiceNote.d.ts +2 -2
- package/lib/typescript/commonjs/ui/components/VoiceNote.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/components/ZoomableImage.d.ts +45 -0
- package/lib/typescript/commonjs/ui/components/ZoomableImage.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/components/icons.d.ts +2 -1
- package/lib/typescript/commonjs/ui/components/icons.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/screens/chat/ChatView.d.ts +1 -1
- package/lib/typescript/commonjs/ui/screens/chat/ChatView.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/screens/chat/ChatView.styles.d.ts +14 -9
- package/lib/typescript/commonjs/ui/screens/chat/ChatView.styles.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/screens/chat/ChatView.types.d.ts +4 -0
- package/lib/typescript/commonjs/ui/screens/chat/ChatView.types.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/styles/theme.d.ts +2 -0
- package/lib/typescript/commonjs/ui/styles/theme.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/utils/conversations.d.ts +10 -2
- package/lib/typescript/commonjs/ui/utils/conversations.d.ts.map +1 -1
- package/lib/typescript/module/api/client.d.ts +27 -1
- package/lib/typescript/module/api/client.d.ts.map +1 -1
- package/lib/typescript/module/attachments/pickers.d.ts +22 -10
- package/lib/typescript/module/attachments/pickers.d.ts.map +1 -1
- package/lib/typescript/module/audio/native-player.d.ts +36 -0
- package/lib/typescript/module/audio/native-player.d.ts.map +1 -0
- package/lib/typescript/module/audio/voice.d.ts +17 -4
- package/lib/typescript/module/audio/voice.d.ts.map +1 -1
- package/lib/typescript/module/i18n.d.ts +1 -0
- package/lib/typescript/module/i18n.d.ts.map +1 -1
- package/lib/typescript/module/locales.d.ts.map +1 -1
- package/lib/typescript/module/state/widget-store.d.ts +38 -3
- package/lib/typescript/module/state/widget-store.d.ts.map +1 -1
- package/lib/typescript/module/types.d.ts +17 -0
- package/lib/typescript/module/types.d.ts.map +1 -1
- package/lib/typescript/module/ui/WidgetProvider.d.ts.map +1 -1
- package/lib/typescript/module/ui/components/ImageViewer.d.ts +2 -2
- package/lib/typescript/module/ui/components/ImageViewer.d.ts.map +1 -1
- package/lib/typescript/module/ui/components/MessagesSkeleton.d.ts +10 -0
- package/lib/typescript/module/ui/components/MessagesSkeleton.d.ts.map +1 -0
- package/lib/typescript/module/ui/components/VoiceNote.d.ts +2 -2
- package/lib/typescript/module/ui/components/VoiceNote.d.ts.map +1 -1
- package/lib/typescript/module/ui/components/ZoomableImage.d.ts +45 -0
- package/lib/typescript/module/ui/components/ZoomableImage.d.ts.map +1 -0
- package/lib/typescript/module/ui/components/icons.d.ts +2 -1
- package/lib/typescript/module/ui/components/icons.d.ts.map +1 -1
- package/lib/typescript/module/ui/screens/chat/ChatView.d.ts +1 -1
- package/lib/typescript/module/ui/screens/chat/ChatView.d.ts.map +1 -1
- package/lib/typescript/module/ui/screens/chat/ChatView.styles.d.ts +14 -9
- package/lib/typescript/module/ui/screens/chat/ChatView.styles.d.ts.map +1 -1
- package/lib/typescript/module/ui/screens/chat/ChatView.types.d.ts +4 -0
- package/lib/typescript/module/ui/screens/chat/ChatView.types.d.ts.map +1 -1
- package/lib/typescript/module/ui/styles/theme.d.ts +2 -0
- package/lib/typescript/module/ui/styles/theme.d.ts.map +1 -1
- package/lib/typescript/module/ui/utils/conversations.d.ts +10 -2
- package/lib/typescript/module/ui/utils/conversations.d.ts.map +1 -1
- package/package.json +10 -3
- package/react-native.config.js +12 -0
- package/src/api/client.ts +66 -2
- package/src/attachments/pickers.ts +87 -25
- package/src/audio/native-player.ts +106 -0
- package/src/audio/voice.ts +30 -6
- package/src/i18n.ts +2 -0
- package/src/locales.ts +1 -0
- package/src/state/widget-store.ts +233 -33
- package/src/types.ts +17 -0
- package/src/ui/WidgetProvider.tsx +16 -7
- package/src/ui/components/ImageViewer.tsx +21 -36
- package/src/ui/components/MessagesSkeleton.tsx +92 -0
- package/src/ui/components/VoiceNote.tsx +6 -14
- package/src/ui/components/ZoomableImage.tsx +250 -0
- package/src/ui/components/icons.tsx +10 -12
- package/src/ui/screens/chat/ChatView.styles.ts +17 -9
- package/src/ui/screens/chat/ChatView.tsx +49 -25
- package/src/ui/screens/chat/ChatView.types.ts +4 -0
- package/src/ui/styles/theme.ts +6 -0
- 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;
|
|
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:
|
|
212
|
-
|
|
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
|
|
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":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AACjD,kDAAkD;AAElD,OAAO,KAAK,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,8BAA8B,CAAC;AAChF,OAAO,KAAK,EACV,aAAa,EACb,gBAAgB,EAChB,wBAAwB,EACxB,OAAO,EACP,kBAAkB,EACnB,MAAM,gBAAgB,CAAC;AACxB,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,gCAAgC,CAAC;AACzE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;AACtE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,6BAA6B,CAAC;AACpE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAC;AAElE,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,kBAAkB,EAAE,MAAM,CAAC;IAC3B,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,mBAAmB,EAAE,MAAM,CAAC;IAC5B,cAAc,EAAE,MAAM,CAAC;IACvB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,kBAAkB,EAAE,MAAM,CAAC;IAC3B,aAAa,EAAE,MAAM,CAAC;IACtB,aAAa,EAAE,MAAM,CAAC;IACtB,qBAAqB,EAAE,MAAM,CAAC;IAC9B,oBAAoB,EAAE,MAAM,CAAC;IAC7B,aAAa,EAAE,MAAM,CAAC;IACtB,gDAAgD;IAChD,mBAAmB,EAAE,MAAM,CAAC;IAC5B,sBAAsB,EAAE,MAAM,CAAC;IAC/B,4CAA4C;IAC5C,cAAc,EAAE,MAAM,CAAC;IACvB,yBAAyB,EAAE,MAAM,CAAC;IAClC,yBAAyB,EAAE,MAAM,CAAC;IAClC,MAAM,EAAE,kBAAkB,CAAC;IAC3B,MAAM,EAAE,gBAAgB,CAAC;IACzB,MAAM,EAAE,YAAY,CAAC;IACrB,MAAM,EAAE,iBAAiB,CAAC;IAC1B,KAAK,EAAE,eAAe,CAAC;IACvB,2DAA2D;IAC3D,cAAc,EAAE,MAAM,CAAC;CACxB;AAED,uFAAuF;AACvF,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,MAAM,CAAC;IACf,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,gBAAgB,EAAE,MAAM,CAAC;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,OAAO,EAAE,CAAC;IACpB,OAAO,EAAE,OAAO,CAAC;IACjB,eAAe,EAAE,OAAO,CAAC;IACzB,WAAW,EAAE,OAAO,CAAC;IACrB,KAAK,EAAE,MAAM,CAAC;IACd,yEAAyE;IACzE,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,kFAAkF;IAClF,WAAW,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;IAC7B,QAAQ,EAAE,OAAO,CAAC;IAClB,wFAAwF;IACxF,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,WAAW,CAAC,EAAE,UAAU,EAAE,CAAC;IAC3B,yEAAyE;IACzE,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,UAAU,CAAC;IACnB;;;OAGG;IACH,iBAAiB,CAAC,EAAE,gBAAgB,EAAE,CAAC;IACvC,4EAA4E;IAC5E,iBAAiB,CAAC,EAAE,CAAC,MAAM,EAAE,gBAAgB,EAAE,OAAO,EAAE,eAAe,EAAE,KAAK,OAAO,CAAC,UAAU,CAAC,CAAC;IAClG,6EAA6E;IAC7E,aAAa,EAAE,CACb,IAAI,EAAE,MAAM,EACZ,WAAW,CAAC,EAAE,kBAAkB,EAAE,EAClC,MAAM,CAAC,EAAE,aAAa,KACnB,OAAO,CAAC,IAAI,CAAC,CAAC;IACnB,iBAAiB,CAAC,EAAE,CAAC,IAAI,EAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/F,sFAAsF;IACtF,kBAAkB,CAAC,EAAE,CACnB,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,KACxD,OAAO,CAAC,wBAAwB,CAAC,CAAC;IACvC,sEAAsE;IACtE,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,UAAU,EAAE,MAAM,IAAI,CAAC;IACvB,QAAQ,EAAE,CAAC,QAAQ,EAAE,OAAO,KAAK,IAAI,CAAC;IACtC,MAAM,EAAE,MAAM,IAAI,CAAC;IACnB,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB"}
|
|
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,
|
|
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
|
|
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
|
|
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.
|
|
4
|
-
"description": "Repzo chat widget for React Native
|
|
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
|
-
/**
|
|
341
|
-
|
|
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
|
|
3
|
-
*
|
|
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,
|
|
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,<…>`
|
|
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
|
|
174
|
-
const
|
|
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 =
|
|
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,
|
|
228
|
-
// (full-resolution)
|
|
229
|
-
base64
|
|
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
|
|
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(
|
|
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:
|
|
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
|
-
/**
|
|
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
|
+
}
|