@developer_tribe/react-native-comnyx 0.15.0 → 0.16.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/Comnyx.podspec +10 -2
- package/README.md +50 -0
- package/android/build.gradle +1 -0
- package/android/consumer-rules.pro +23 -0
- package/android/generated/java/com/comnyx/NativeComnyxSpec.java +46 -0
- package/android/generated/jni/RNComnyxSpec-generated.cpp +23 -1
- package/android/generated/jni/RNComnyxSpec.h +7 -0
- package/android/generated/jni/react/renderer/components/RNComnyxSpec/RNComnyxSpecJSI-generated.cpp +21 -0
- package/android/generated/jni/react/renderer/components/RNComnyxSpec/RNComnyxSpecJSI.h +70 -0
- package/android/src/main/AndroidManifest.xml +11 -1
- package/android/src/main/AndroidManifestNew.xml +11 -1
- package/android/src/main/java/com/comnyx/ComnyxMediaPickerModule.kt +91 -51
- package/android/src/main/java/com/comnyx/ComnyxModule.kt +7 -0
- package/android/src/main/java/com/comnyx/src/messaging/firebase/FirebaseMessagingService.kt +4 -6
- package/android/src/main/res/xml/comnyx_file_paths.xml +12 -0
- package/ios/APNService.swift +9 -9
- package/ios/Comnyx.swift +17 -8
- package/ios/ComnyxMediaPicker.swift +47 -26
- package/ios/ComnyxMessaging.swift +2 -0
- package/ios/PrivacyInfo.xcprivacy +32 -0
- package/ios/comnyx_post_install.rb +25 -0
- package/ios/generated/RCTAppDependencyProvider.h +25 -0
- package/ios/generated/RCTAppDependencyProvider.mm +55 -0
- package/ios/generated/RCTModulesConformingToProtocolsProvider.h +18 -0
- package/ios/generated/RCTModulesConformingToProtocolsProvider.mm +33 -0
- package/ios/generated/RCTThirdPartyComponentsProvider.h +16 -0
- package/ios/generated/RCTThirdPartyComponentsProvider.mm +23 -0
- package/ios/generated/RNComnyxSpec/RNComnyxSpec-generated.mm +53 -0
- package/ios/generated/RNComnyxSpec/RNComnyxSpec.h +67 -0
- package/ios/generated/RNComnyxSpecJSI-generated.cpp +38 -0
- package/ios/generated/RNComnyxSpecJSI.h +89 -0
- package/ios/generated/ReactAppDependencyProvider.podspec +34 -0
- package/lib/commonjs/NativeComnyxMediaPicker.js +19 -0
- package/lib/commonjs/NativeComnyxMediaPicker.js.map +1 -1
- package/lib/commonjs/api/conversations.js +6 -6
- package/lib/commonjs/api/conversations.js.map +1 -1
- package/lib/commonjs/api/customers.js +3 -2
- package/lib/commonjs/api/customers.js.map +1 -1
- package/lib/commonjs/api/media.js +20 -6
- package/lib/commonjs/api/media.js.map +1 -1
- package/lib/commonjs/api/messages.js +3 -2
- package/lib/commonjs/api/messages.js.map +1 -1
- package/lib/commonjs/components/ChatList.js +132 -90
- package/lib/commonjs/components/ChatList.js.map +1 -1
- package/lib/commonjs/components/ComnyxErrorBoundary.js +92 -0
- package/lib/commonjs/components/ComnyxErrorBoundary.js.map +1 -0
- package/lib/commonjs/components/CustomerForm.js +7 -3
- package/lib/commonjs/components/CustomerForm.js.map +1 -1
- package/lib/commonjs/components/InitFailed.js +77 -21
- package/lib/commonjs/components/InitFailed.js.map +1 -1
- package/lib/commonjs/components/MediaMessageItem.js +37 -10
- package/lib/commonjs/components/MediaMessageItem.js.map +1 -1
- package/lib/commonjs/components/MediaViewerModal.js +16 -3
- package/lib/commonjs/components/MediaViewerModal.js.map +1 -1
- package/lib/commonjs/components/MessageInput.js +83 -15
- package/lib/commonjs/components/MessageInput.js.map +1 -1
- package/lib/commonjs/components/MessageItem.js +1 -1
- package/lib/commonjs/components/MessageItem.js.map +1 -1
- package/lib/commonjs/hooks/usePolling.js +30 -21
- package/lib/commonjs/hooks/usePolling.js.map +1 -1
- package/lib/commonjs/hooks/useThemeColors.js +12 -1
- package/lib/commonjs/hooks/useThemeColors.js.map +1 -1
- package/lib/commonjs/index.js +6 -0
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/notifications/initializeNotifications.js +19 -16
- package/lib/commonjs/notifications/initializeNotifications.js.map +1 -1
- package/lib/commonjs/register/Accumulator.js +19 -6
- package/lib/commonjs/register/Accumulator.js.map +1 -1
- package/lib/commonjs/register/collectData.js +1 -1
- package/lib/commonjs/register/collectData.js.map +1 -1
- package/lib/commonjs/register/login.js +5 -0
- package/lib/commonjs/register/login.js.map +1 -1
- package/lib/commonjs/store/store.js +6 -0
- package/lib/commonjs/store/store.js.map +1 -1
- package/lib/commonjs/support/ComnyxSupport.js +77 -16
- package/lib/commonjs/support/ComnyxSupport.js.map +1 -1
- package/lib/commonjs/support/SupportConfigContext.js +66 -0
- package/lib/commonjs/support/SupportConfigContext.js.map +1 -0
- package/lib/commonjs/support/index.js +7 -0
- package/lib/commonjs/support/index.js.map +1 -1
- package/lib/commonjs/types/Theme.js +30 -2
- package/lib/commonjs/types/Theme.js.map +1 -1
- package/lib/commonjs/version.js +1 -1
- package/lib/module/NativeComnyxMediaPicker.js +18 -0
- package/lib/module/NativeComnyxMediaPicker.js.map +1 -1
- package/lib/module/api/conversations.js +6 -6
- package/lib/module/api/conversations.js.map +1 -1
- package/lib/module/api/customers.js +3 -2
- package/lib/module/api/customers.js.map +1 -1
- package/lib/module/api/media.js +21 -6
- package/lib/module/api/media.js.map +1 -1
- package/lib/module/api/messages.js +3 -2
- package/lib/module/api/messages.js.map +1 -1
- package/lib/module/components/ChatList.js +133 -91
- package/lib/module/components/ChatList.js.map +1 -1
- package/lib/module/components/ComnyxErrorBoundary.js +87 -0
- package/lib/module/components/ComnyxErrorBoundary.js.map +1 -0
- package/lib/module/components/CustomerForm.js +7 -3
- package/lib/module/components/CustomerForm.js.map +1 -1
- package/lib/module/components/InitFailed.js +79 -23
- package/lib/module/components/InitFailed.js.map +1 -1
- package/lib/module/components/MediaMessageItem.js +37 -11
- package/lib/module/components/MediaMessageItem.js.map +1 -1
- package/lib/module/components/MediaViewerModal.js +15 -3
- package/lib/module/components/MediaViewerModal.js.map +1 -1
- package/lib/module/components/MessageInput.js +84 -16
- package/lib/module/components/MessageInput.js.map +1 -1
- package/lib/module/components/MessageItem.js +1 -1
- package/lib/module/components/MessageItem.js.map +1 -1
- package/lib/module/hooks/usePolling.js +30 -21
- package/lib/module/hooks/usePolling.js.map +1 -1
- package/lib/module/hooks/useThemeColors.js +13 -2
- package/lib/module/hooks/useThemeColors.js.map +1 -1
- package/lib/module/index.js +1 -0
- package/lib/module/index.js.map +1 -1
- package/lib/module/notifications/initializeNotifications.js +19 -16
- package/lib/module/notifications/initializeNotifications.js.map +1 -1
- package/lib/module/register/Accumulator.js +19 -6
- package/lib/module/register/Accumulator.js.map +1 -1
- package/lib/module/register/collectData.js +1 -1
- package/lib/module/register/collectData.js.map +1 -1
- package/lib/module/register/login.js +5 -0
- package/lib/module/register/login.js.map +1 -1
- package/lib/module/store/store.js +6 -0
- package/lib/module/store/store.js.map +1 -1
- package/lib/module/support/ComnyxSupport.js +79 -18
- package/lib/module/support/ComnyxSupport.js.map +1 -1
- package/lib/module/support/SupportConfigContext.js +59 -0
- package/lib/module/support/SupportConfigContext.js.map +1 -0
- package/lib/module/support/index.js +1 -0
- package/lib/module/support/index.js.map +1 -1
- package/lib/module/types/Theme.js +30 -2
- package/lib/module/types/Theme.js.map +1 -1
- package/lib/module/version.js +1 -1
- package/lib/typescript/src/NativeComnyxMediaPicker.d.ts +9 -0
- package/lib/typescript/src/NativeComnyxMediaPicker.d.ts.map +1 -1
- package/lib/typescript/src/api/conversations.d.ts +2 -2
- package/lib/typescript/src/api/conversations.d.ts.map +1 -1
- package/lib/typescript/src/api/customers.d.ts +1 -1
- package/lib/typescript/src/api/customers.d.ts.map +1 -1
- package/lib/typescript/src/api/media.d.ts +3 -3
- package/lib/typescript/src/api/media.d.ts.map +1 -1
- package/lib/typescript/src/api/messages.d.ts +1 -1
- package/lib/typescript/src/api/messages.d.ts.map +1 -1
- package/lib/typescript/src/components/ChatList.d.ts.map +1 -1
- package/lib/typescript/src/components/ComnyxErrorBoundary.d.ts +18 -0
- package/lib/typescript/src/components/ComnyxErrorBoundary.d.ts.map +1 -0
- package/lib/typescript/src/components/CustomerForm.d.ts.map +1 -1
- package/lib/typescript/src/components/InitFailed.d.ts +5 -2
- package/lib/typescript/src/components/InitFailed.d.ts.map +1 -1
- package/lib/typescript/src/components/MediaMessageItem.d.ts.map +1 -1
- package/lib/typescript/src/components/MediaViewerModal.d.ts.map +1 -1
- package/lib/typescript/src/components/MessageInput.d.ts.map +1 -1
- package/lib/typescript/src/components/MessageItem.d.ts.map +1 -1
- package/lib/typescript/src/hooks/usePolling.d.ts.map +1 -1
- package/lib/typescript/src/hooks/useThemeColors.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +3 -0
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/notifications/initializeNotifications.d.ts.map +1 -1
- package/lib/typescript/src/register/Accumulator.d.ts.map +1 -1
- package/lib/typescript/src/register/collectData.d.ts +4 -1
- package/lib/typescript/src/register/collectData.d.ts.map +1 -1
- package/lib/typescript/src/register/login.d.ts.map +1 -1
- package/lib/typescript/src/store/store.d.ts +6 -2
- package/lib/typescript/src/store/store.d.ts.map +1 -1
- package/lib/typescript/src/support/ComnyxSupport.d.ts +80 -3
- package/lib/typescript/src/support/ComnyxSupport.d.ts.map +1 -1
- package/lib/typescript/src/support/SupportConfigContext.d.ts +98 -0
- package/lib/typescript/src/support/SupportConfigContext.d.ts.map +1 -0
- package/lib/typescript/src/support/index.d.ts +2 -0
- package/lib/typescript/src/support/index.d.ts.map +1 -1
- package/lib/typescript/src/types/Conversation.d.ts +2 -2
- package/lib/typescript/src/types/Conversation.d.ts.map +1 -1
- package/lib/typescript/src/types/Customer.d.ts +1 -1
- package/lib/typescript/src/types/Customer.d.ts.map +1 -1
- package/lib/typescript/src/types/MessageResponse.d.ts +7 -4
- package/lib/typescript/src/types/MessageResponse.d.ts.map +1 -1
- package/lib/typescript/src/types/Theme.d.ts +26 -0
- package/lib/typescript/src/types/Theme.d.ts.map +1 -1
- package/lib/typescript/src/version.d.ts +1 -1
- package/package.json +19 -30
- package/src/NativeComnyxMediaPicker.ts +18 -0
- package/src/api/conversations.ts +6 -4
- package/src/api/customers.ts +3 -1
- package/src/api/media.ts +32 -10
- package/src/api/messages.ts +3 -1
- package/src/components/ChatList.tsx +147 -99
- package/src/components/ComnyxErrorBoundary.tsx +91 -0
- package/src/components/CustomerForm.tsx +7 -3
- package/src/components/InitFailed.tsx +80 -16
- package/src/components/MediaMessageItem.tsx +48 -11
- package/src/components/MediaViewerModal.tsx +21 -8
- package/src/components/MessageInput.tsx +108 -17
- package/src/components/MessageItem.tsx +12 -13
- package/src/hooks/usePolling.ts +26 -11
- package/src/hooks/useThemeColors.ts +11 -2
- package/src/index.ts +16 -0
- package/src/notifications/initializeNotifications.ts +22 -20
- package/src/register/Accumulator.ts +26 -9
- package/src/register/collectData.ts +10 -2
- package/src/register/login.ts +5 -0
- package/src/store/store.ts +11 -3
- package/src/support/ComnyxSupport.tsx +172 -23
- package/src/support/SupportConfigContext.tsx +157 -0
- package/src/support/index.ts +11 -0
- package/src/types/Conversation.ts +2 -2
- package/src/types/Customer.ts +1 -2
- package/src/types/MessageResponse.ts +4 -4
- package/src/types/Theme.ts +38 -0
- package/src/version.ts +1 -1
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { View, Image, TouchableOpacity } from 'react-native';
|
|
1
|
+
import { View, Image, TouchableOpacity, ActivityIndicator } from 'react-native';
|
|
2
|
+
import FastImage from '@d11/react-native-fast-image';
|
|
2
3
|
import { useState, useEffect } from 'react';
|
|
3
4
|
import type { AppConversationMessage } from '../types/Conversation';
|
|
4
5
|
import { useThemeColors } from '../hooks/useThemeColors';
|
|
@@ -30,6 +31,7 @@ function MediaThumbnail({
|
|
|
30
31
|
const [generatedThumb, setGeneratedThumb] = useState<string | null>(null);
|
|
31
32
|
const displayUri = file.local_uri || file.url;
|
|
32
33
|
const isVideo = file.type === 'video';
|
|
34
|
+
const themeColors = useThemeColors();
|
|
33
35
|
|
|
34
36
|
useEffect(() => {
|
|
35
37
|
if (isVideo && !file.thumbnail_uri && !isUploading && displayUri) {
|
|
@@ -44,11 +46,8 @@ function MediaThumbnail({
|
|
|
44
46
|
}, [isVideo, file.thumbnail_uri, isUploading, displayUri]);
|
|
45
47
|
|
|
46
48
|
const effectiveThumb = file.thumbnail_uri || generatedThumb;
|
|
47
|
-
const
|
|
48
|
-
|
|
49
|
-
? { uri: effectiveThumb }
|
|
50
|
-
: undefined
|
|
51
|
-
: { uri: displayUri };
|
|
49
|
+
const thumbUri = isVideo ? effectiveThumb : displayUri;
|
|
50
|
+
const isRemote = !!thumbUri && /^https?:\/\//i.test(thumbUri);
|
|
52
51
|
|
|
53
52
|
const handlePress = () => {
|
|
54
53
|
if (isUploading) return;
|
|
@@ -62,8 +61,14 @@ function MediaThumbnail({
|
|
|
62
61
|
const wrapperStyle = single ? styles.singleMediaWrapper : styles.gridItem;
|
|
63
62
|
const imageStyle = single ? styles.singleMediaImage : styles.gridImage;
|
|
64
63
|
const placeholderStyle = single
|
|
65
|
-
? [
|
|
66
|
-
|
|
64
|
+
? [
|
|
65
|
+
styles.singleMediaImage,
|
|
66
|
+
{ backgroundColor: themeColors.videoPlaceholder },
|
|
67
|
+
]
|
|
68
|
+
: [
|
|
69
|
+
styles.videoPlaceholder,
|
|
70
|
+
{ backgroundColor: themeColors.videoPlaceholder },
|
|
71
|
+
];
|
|
67
72
|
|
|
68
73
|
return (
|
|
69
74
|
<TouchableOpacity
|
|
@@ -72,8 +77,23 @@ function MediaThumbnail({
|
|
|
72
77
|
activeOpacity={activeOpacity}
|
|
73
78
|
disabled={isUploading}
|
|
74
79
|
>
|
|
75
|
-
{
|
|
76
|
-
|
|
80
|
+
{thumbUri ? (
|
|
81
|
+
isRemote ? (
|
|
82
|
+
<FastImage
|
|
83
|
+
source={{
|
|
84
|
+
uri: thumbUri,
|
|
85
|
+
cache: FastImage.cacheControl.immutable,
|
|
86
|
+
}}
|
|
87
|
+
style={imageStyle}
|
|
88
|
+
resizeMode={FastImage.resizeMode.cover}
|
|
89
|
+
/>
|
|
90
|
+
) : (
|
|
91
|
+
<Image
|
|
92
|
+
source={{ uri: thumbUri }}
|
|
93
|
+
style={imageStyle}
|
|
94
|
+
resizeMode="cover"
|
|
95
|
+
/>
|
|
96
|
+
)
|
|
77
97
|
) : (
|
|
78
98
|
<View style={placeholderStyle} />
|
|
79
99
|
)}
|
|
@@ -82,6 +102,11 @@ function MediaThumbnail({
|
|
|
82
102
|
<AppText style={styles.playIcon}>▶</AppText>
|
|
83
103
|
</View>
|
|
84
104
|
)}
|
|
105
|
+
{isUploading && (
|
|
106
|
+
<View style={styles.uploadOverlay}>
|
|
107
|
+
<ActivityIndicator size="small" color="#FFFFFF" />
|
|
108
|
+
</View>
|
|
109
|
+
)}
|
|
85
110
|
</TouchableOpacity>
|
|
86
111
|
);
|
|
87
112
|
}
|
|
@@ -234,7 +259,7 @@ export function MediaMessageItem({
|
|
|
234
259
|
{
|
|
235
260
|
regex:
|
|
236
261
|
/https?:\/\/(?:[-\w.])+(?::[0-9]+)?(?:\/(?:[\w/_.])*(?:\?(?:[\w&=%.])*)?(?:#(?:[\w.])*)?)?/g,
|
|
237
|
-
color:
|
|
262
|
+
color: themeColors.link,
|
|
238
263
|
navigate: (url) => {
|
|
239
264
|
if (url) {
|
|
240
265
|
Linking.openURL(url);
|
|
@@ -329,6 +354,18 @@ const styles = ScaledSheet.create({
|
|
|
329
354
|
alignItems: 'center',
|
|
330
355
|
} as any),
|
|
331
356
|
},
|
|
357
|
+
uploadOverlay: {
|
|
358
|
+
...({
|
|
359
|
+
position: 'absolute',
|
|
360
|
+
top: 0,
|
|
361
|
+
left: 0,
|
|
362
|
+
right: 0,
|
|
363
|
+
bottom: 0,
|
|
364
|
+
justifyContent: 'center',
|
|
365
|
+
alignItems: 'center',
|
|
366
|
+
backgroundColor: 'rgba(0,0,0,0.35)',
|
|
367
|
+
} as any),
|
|
368
|
+
},
|
|
332
369
|
playIcon: {
|
|
333
370
|
fontSize: '20@vs',
|
|
334
371
|
color: '#E0E0E0',
|
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
StatusBar,
|
|
7
7
|
useWindowDimensions,
|
|
8
8
|
} from 'react-native';
|
|
9
|
+
import FastImage from '@d11/react-native-fast-image';
|
|
9
10
|
import { useState, useEffect } from 'react';
|
|
10
11
|
import { AppText } from './AppText';
|
|
11
12
|
import { ScaledSheet } from './ScaledSheet';
|
|
@@ -61,15 +62,27 @@ export function MediaViewerModal({
|
|
|
61
62
|
>
|
|
62
63
|
<AppText style={styles.closeIcon}>✕</AppText>
|
|
63
64
|
</TouchableOpacity>
|
|
64
|
-
<View style={[styles.mediaContainer, { width, height
|
|
65
|
+
<View style={[styles.mediaContainer, { width, height }]}>
|
|
65
66
|
{displayUri && !imageError ? (
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
67
|
+
/^https?:\/\//i.test(displayUri) ? (
|
|
68
|
+
<FastImage
|
|
69
|
+
source={{
|
|
70
|
+
uri: displayUri,
|
|
71
|
+
cache: FastImage.cacheControl.immutable,
|
|
72
|
+
}}
|
|
73
|
+
style={{ width, height }}
|
|
74
|
+
resizeMode={FastImage.resizeMode.contain}
|
|
75
|
+
onError={() => setImageError(true)}
|
|
76
|
+
/>
|
|
77
|
+
) : (
|
|
78
|
+
<Image
|
|
79
|
+
source={{ uri: displayUri }}
|
|
80
|
+
style={{ width, height }}
|
|
81
|
+
resizeMode="contain"
|
|
82
|
+
resizeMethod="resize"
|
|
83
|
+
onError={() => setImageError(true)}
|
|
84
|
+
/>
|
|
85
|
+
)
|
|
73
86
|
) : (
|
|
74
87
|
<View
|
|
75
88
|
style={[
|
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
TouchableOpacity,
|
|
6
6
|
ScrollView,
|
|
7
7
|
} from 'react-native';
|
|
8
|
-
import { useState, useCallback } from 'react';
|
|
8
|
+
import { useState, useCallback, useEffect, useRef } from 'react';
|
|
9
9
|
import { sendCustomerMessage } from '../api';
|
|
10
10
|
import { getUploadUrl, uploadFileToS3, sendMediaMessage } from '../api';
|
|
11
11
|
import { deleteTempFile } from '../NativeComnyxMediaPicker';
|
|
@@ -17,6 +17,10 @@ import { useIsRtl } from '../hooks/isRtl';
|
|
|
17
17
|
import { useAppStore } from '../store/store';
|
|
18
18
|
import { MediaPickerButton } from './MediaPickerButton';
|
|
19
19
|
import type { MediaAsset } from '../types/MediaTypes';
|
|
20
|
+
import {
|
|
21
|
+
useSupportConfig,
|
|
22
|
+
reportSupportError,
|
|
23
|
+
} from '../support/SupportConfigContext';
|
|
20
24
|
|
|
21
25
|
const sendDark = require('../assets/arrow-right.png');
|
|
22
26
|
const circleXIcon = require('../assets/x-circle.png');
|
|
@@ -34,6 +38,20 @@ export function MessageInput({
|
|
|
34
38
|
const themeColors = useThemeColors();
|
|
35
39
|
const localize = useLocalize();
|
|
36
40
|
const isRtl = useIsRtl();
|
|
41
|
+
const { onBeforeSend } = useSupportConfig();
|
|
42
|
+
|
|
43
|
+
const uploadAbortRef = useRef<AbortController | null>(null);
|
|
44
|
+
const lifecycleAbortRef = useRef<AbortController | null>(null);
|
|
45
|
+
if (lifecycleAbortRef.current === null) {
|
|
46
|
+
lifecycleAbortRef.current = new AbortController();
|
|
47
|
+
}
|
|
48
|
+
useEffect(() => {
|
|
49
|
+
const controller = lifecycleAbortRef.current;
|
|
50
|
+
return () => {
|
|
51
|
+
uploadAbortRef.current?.abort();
|
|
52
|
+
controller?.abort();
|
|
53
|
+
};
|
|
54
|
+
}, []);
|
|
37
55
|
|
|
38
56
|
const sendTextOnlyMessage = useCallback(() => {
|
|
39
57
|
if (!value.trim()) return;
|
|
@@ -53,10 +71,14 @@ export function MessageInput({
|
|
|
53
71
|
},
|
|
54
72
|
...(data ?? []),
|
|
55
73
|
]);
|
|
56
|
-
sendCustomerMessage(
|
|
57
|
-
|
|
58
|
-
|
|
74
|
+
sendCustomerMessage(
|
|
75
|
+
customer.external_id as string,
|
|
76
|
+
value,
|
|
77
|
+
{ fake: useAppStore.getState().fake },
|
|
78
|
+
lifecycleAbortRef.current?.signal
|
|
79
|
+
)
|
|
59
80
|
.then((res) => {
|
|
81
|
+
if (lifecycleAbortRef.current?.signal.aborted) return;
|
|
60
82
|
const data = useAppStore.getState().data;
|
|
61
83
|
if (data) {
|
|
62
84
|
const itemIndex = data.findIndex((item) => item.local_id === localId);
|
|
@@ -81,7 +103,7 @@ export function MessageInput({
|
|
|
81
103
|
//TODO: ??
|
|
82
104
|
}
|
|
83
105
|
})
|
|
84
|
-
.catch(() => {
|
|
106
|
+
.catch((err) => {
|
|
85
107
|
const data = useAppStore.getState().data;
|
|
86
108
|
if (data) {
|
|
87
109
|
const itemIndex = data.findIndex((item) => item.local_id === localId);
|
|
@@ -106,6 +128,11 @@ export function MessageInput({
|
|
|
106
128
|
} else {
|
|
107
129
|
//TODO: ??
|
|
108
130
|
}
|
|
131
|
+
reportSupportError(err, {
|
|
132
|
+
section: 'send',
|
|
133
|
+
recoverable: true,
|
|
134
|
+
extras: { contentLength: value.length },
|
|
135
|
+
});
|
|
109
136
|
});
|
|
110
137
|
setValue('');
|
|
111
138
|
}, [value, customer, scrollToBottom]);
|
|
@@ -114,6 +141,7 @@ export function MessageInput({
|
|
|
114
141
|
async (
|
|
115
142
|
asset: MediaAsset,
|
|
116
143
|
isFake: boolean,
|
|
144
|
+
signal: AbortSignal,
|
|
117
145
|
onProgress?: (percentage: number) => void
|
|
118
146
|
): Promise<string> => {
|
|
119
147
|
const presignResponse = await getUploadUrl(
|
|
@@ -124,9 +152,14 @@ export function MessageInput({
|
|
|
124
152
|
const uploadUrl = presignResponse.data.url;
|
|
125
153
|
const filePath = presignResponse.data.path;
|
|
126
154
|
|
|
127
|
-
await uploadFileToS3(
|
|
128
|
-
|
|
129
|
-
|
|
155
|
+
await uploadFileToS3(
|
|
156
|
+
uploadUrl,
|
|
157
|
+
asset.uri,
|
|
158
|
+
asset.mimeType,
|
|
159
|
+
onProgress,
|
|
160
|
+
{ fake: isFake },
|
|
161
|
+
signal
|
|
162
|
+
);
|
|
130
163
|
|
|
131
164
|
return isFake ? asset.uri : filePath;
|
|
132
165
|
},
|
|
@@ -166,6 +199,10 @@ export function MessageInput({
|
|
|
166
199
|
]);
|
|
167
200
|
scrollToBottom(false);
|
|
168
201
|
|
|
202
|
+
uploadAbortRef.current?.abort();
|
|
203
|
+
const controller = new AbortController();
|
|
204
|
+
uploadAbortRef.current = controller;
|
|
205
|
+
|
|
169
206
|
try {
|
|
170
207
|
const fileProgresses = new Array(assets.length).fill(0);
|
|
171
208
|
let lastProgressUpdate = 0;
|
|
@@ -198,6 +235,7 @@ export function MessageInput({
|
|
|
198
235
|
const path = await uploadSingleMedia(
|
|
199
236
|
assets[i]!,
|
|
200
237
|
isFake,
|
|
238
|
+
controller.signal,
|
|
201
239
|
(percentage) => {
|
|
202
240
|
fileProgresses[i] = percentage;
|
|
203
241
|
updateOverallProgress();
|
|
@@ -211,7 +249,8 @@ export function MessageInput({
|
|
|
211
249
|
uploadedPaths,
|
|
212
250
|
assets[0]!.type,
|
|
213
251
|
content,
|
|
214
|
-
{ fake: isFake }
|
|
252
|
+
{ fake: isFake },
|
|
253
|
+
controller.signal
|
|
215
254
|
);
|
|
216
255
|
|
|
217
256
|
const currentData = useAppStore.getState().data;
|
|
@@ -265,6 +304,19 @@ export function MessageInput({
|
|
|
265
304
|
}
|
|
266
305
|
}
|
|
267
306
|
console.error('[Comnyx] Media upload failed:', error);
|
|
307
|
+
reportSupportError(error, {
|
|
308
|
+
section: 'upload',
|
|
309
|
+
recoverable: true,
|
|
310
|
+
extras: {
|
|
311
|
+
assetCount: assets.length,
|
|
312
|
+
mediaTypes: assets.map((a) => a.type),
|
|
313
|
+
aborted: controller.signal.aborted,
|
|
314
|
+
},
|
|
315
|
+
});
|
|
316
|
+
} finally {
|
|
317
|
+
if (uploadAbortRef.current === controller) {
|
|
318
|
+
uploadAbortRef.current = null;
|
|
319
|
+
}
|
|
268
320
|
}
|
|
269
321
|
},
|
|
270
322
|
[customer, scrollToBottom, uploadSingleMedia]
|
|
@@ -294,18 +346,45 @@ export function MessageInput({
|
|
|
294
346
|
const handleSend = useCallback(async () => {
|
|
295
347
|
if (isSending) return;
|
|
296
348
|
|
|
297
|
-
|
|
298
|
-
|
|
349
|
+
const hasMedia = pendingMedia.length > 0;
|
|
350
|
+
const hasText = !!value.trim();
|
|
351
|
+
if (!hasMedia && !hasText) return;
|
|
352
|
+
|
|
353
|
+
if (onBeforeSend) {
|
|
354
|
+
try {
|
|
355
|
+
setIsSending(true);
|
|
356
|
+
await onBeforeSend({
|
|
357
|
+
content: value.trim(),
|
|
358
|
+
mediaCount: pendingMedia.length,
|
|
359
|
+
mediaTypes: pendingMedia.map((m) => m.type),
|
|
360
|
+
});
|
|
361
|
+
} catch (err) {
|
|
362
|
+
console.warn('[Comnyx] onBeforeSend cancelled the message:', err);
|
|
363
|
+
setIsSending(false);
|
|
364
|
+
return;
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
if (hasMedia) {
|
|
369
|
+
if (!onBeforeSend) setIsSending(true);
|
|
299
370
|
const content = value.trim();
|
|
300
371
|
const assets = [...pendingMedia];
|
|
301
372
|
setPendingMedia([]);
|
|
302
373
|
setValue('');
|
|
303
374
|
await sendMediaMessages(assets, content);
|
|
304
375
|
setIsSending(false);
|
|
305
|
-
} else if (
|
|
376
|
+
} else if (hasText) {
|
|
306
377
|
sendTextOnlyMessage();
|
|
378
|
+
if (onBeforeSend) setIsSending(false);
|
|
307
379
|
}
|
|
308
|
-
}, [
|
|
380
|
+
}, [
|
|
381
|
+
isSending,
|
|
382
|
+
pendingMedia,
|
|
383
|
+
value,
|
|
384
|
+
sendMediaMessages,
|
|
385
|
+
sendTextOnlyMessage,
|
|
386
|
+
onBeforeSend,
|
|
387
|
+
]);
|
|
309
388
|
|
|
310
389
|
const hasPendingMedia = pendingMedia.length > 0;
|
|
311
390
|
|
|
@@ -335,8 +414,18 @@ export function MessageInput({
|
|
|
335
414
|
/>
|
|
336
415
|
{asset.type === 'video' && (
|
|
337
416
|
<View style={styles.playIconOverlay}>
|
|
338
|
-
<View
|
|
339
|
-
|
|
417
|
+
<View
|
|
418
|
+
style={[
|
|
419
|
+
styles.playIcon,
|
|
420
|
+
{ borderColor: themeColors.playIcon },
|
|
421
|
+
]}
|
|
422
|
+
>
|
|
423
|
+
<View
|
|
424
|
+
style={[
|
|
425
|
+
styles.playTriangle,
|
|
426
|
+
{ borderLeftColor: themeColors.playIcon },
|
|
427
|
+
]}
|
|
428
|
+
/>
|
|
340
429
|
</View>
|
|
341
430
|
</View>
|
|
342
431
|
)}
|
|
@@ -375,10 +464,14 @@ export function MessageInput({
|
|
|
375
464
|
onPress={handleSend}
|
|
376
465
|
activeOpacity={activeOpacity}
|
|
377
466
|
disabled={isSending}
|
|
467
|
+
accessibilityRole="button"
|
|
468
|
+
accessibilityLabel={localize('chat.messageInput.placeholder')}
|
|
469
|
+
accessibilityState={{ disabled: isSending, busy: isSending }}
|
|
378
470
|
>
|
|
379
471
|
<Image
|
|
380
472
|
style={[
|
|
381
473
|
styles.sendIcon,
|
|
474
|
+
{ tintColor: themeColors.text },
|
|
382
475
|
isRtl && { transform: [{ rotate: '180deg' }] },
|
|
383
476
|
isSending && { opacity: 0.4 },
|
|
384
477
|
]}
|
|
@@ -441,7 +534,6 @@ const styles = ScaledSheet.create({
|
|
|
441
534
|
height: '22@vs',
|
|
442
535
|
borderRadius: '11@vs',
|
|
443
536
|
borderWidth: 1.5,
|
|
444
|
-
borderColor: '#fff',
|
|
445
537
|
justifyContent: 'center',
|
|
446
538
|
alignItems: 'center',
|
|
447
539
|
},
|
|
@@ -451,7 +543,6 @@ const styles = ScaledSheet.create({
|
|
|
451
543
|
borderLeftWidth: '7@vs',
|
|
452
544
|
borderTopWidth: '5@vs',
|
|
453
545
|
borderBottomWidth: '5@vs',
|
|
454
|
-
borderLeftColor: '#fff',
|
|
455
546
|
borderTopColor: 'transparent',
|
|
456
547
|
borderBottomColor: 'transparent',
|
|
457
548
|
marginLeft: '2@s',
|
|
@@ -1,4 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
View,
|
|
3
|
+
Image,
|
|
4
|
+
TouchableOpacity,
|
|
5
|
+
Linking,
|
|
6
|
+
type NativeSyntheticEvent,
|
|
7
|
+
type TextLayoutEventData,
|
|
8
|
+
} from 'react-native';
|
|
2
9
|
import { AppText } from './AppText';
|
|
3
10
|
import type { AppConversationMessage } from '../types/Conversation';
|
|
4
11
|
import { useThemeColors } from '../hooks/useThemeColors';
|
|
@@ -11,14 +18,6 @@ import { MediaMessageItem } from './MediaMessageItem';
|
|
|
11
18
|
const clockIcon = require('../assets/iconamoon_clock-fill.png');
|
|
12
19
|
const infoIcon = require('../assets/info-circle.png');
|
|
13
20
|
|
|
14
|
-
interface TextLine {
|
|
15
|
-
width: number;
|
|
16
|
-
height: number;
|
|
17
|
-
x: number;
|
|
18
|
-
y: number;
|
|
19
|
-
text: string;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
21
|
interface MessageLayout {
|
|
23
22
|
lines: number;
|
|
24
23
|
lastLineWidth: number;
|
|
@@ -90,7 +89,7 @@ function TextMessageItem({
|
|
|
90
89
|
}
|
|
91
90
|
};
|
|
92
91
|
|
|
93
|
-
const onTextLayout = (event:
|
|
92
|
+
const onTextLayout = (event: NativeSyntheticEvent<TextLayoutEventData>) => {
|
|
94
93
|
const { lines } = event.nativeEvent;
|
|
95
94
|
|
|
96
95
|
const numLines = lines.length;
|
|
@@ -99,9 +98,9 @@ function TextMessageItem({
|
|
|
99
98
|
|
|
100
99
|
if (numLines >= 1) {
|
|
101
100
|
// Get the width of the last line
|
|
102
|
-
lastLineWidth = lines[numLines - 1]
|
|
101
|
+
lastLineWidth = lines[numLines - 1]!.width;
|
|
103
102
|
// Use the widest line as the total width
|
|
104
|
-
totalWidth = Math.max(...lines.map((line
|
|
103
|
+
totalWidth = Math.max(...lines.map((line) => line.width));
|
|
105
104
|
}
|
|
106
105
|
|
|
107
106
|
layoutRef.current = {
|
|
@@ -183,7 +182,7 @@ function TextMessageItem({
|
|
|
183
182
|
{
|
|
184
183
|
regex:
|
|
185
184
|
/https?:\/\/(?:[-\w.])+(?::[0-9]+)?(?:\/(?:[\w/_.])*(?:\?(?:[\w&=%.])*)?(?:#(?:[\w.])*)?)?/g,
|
|
186
|
-
color:
|
|
185
|
+
color: themeColors.link,
|
|
187
186
|
navigate: (url) => {
|
|
188
187
|
if (url) {
|
|
189
188
|
Linking.openURL(url);
|
package/src/hooks/usePolling.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { useEffect } from 'react';
|
|
2
2
|
import { getNewCustomerConversation } from '../api';
|
|
3
3
|
import { useAppStore } from '../store/store';
|
|
4
|
+
import { reportSupportError } from '../support/SupportConfigContext';
|
|
4
5
|
|
|
5
6
|
const NEW_MESSAGES_CHECK_INTERVAL = 10000;
|
|
6
7
|
|
|
@@ -13,12 +14,19 @@ export function usePolling() {
|
|
|
13
14
|
const lastMessage = data ? data[data.length - 1] : null;
|
|
14
15
|
useEffect(() => {
|
|
15
16
|
const created_at = lastMessage?.created_at;
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
17
|
+
if (!customer || !created_at) {
|
|
18
|
+
return undefined;
|
|
19
|
+
}
|
|
20
|
+
const controller = new AbortController();
|
|
21
|
+
const interval = setInterval(() => {
|
|
22
|
+
getNewCustomerConversation(
|
|
23
|
+
customer.external_id,
|
|
24
|
+
created_at,
|
|
25
|
+
{ fake: useAppStore.getState().fake },
|
|
26
|
+
controller.signal
|
|
27
|
+
)
|
|
28
|
+
.then((newData) => {
|
|
29
|
+
if (controller.signal.aborted) return;
|
|
22
30
|
setData((prevData) => {
|
|
23
31
|
const newMessages = newData.page.data;
|
|
24
32
|
const existingIds = new Set(prevData?.map((msg) => msg.id));
|
|
@@ -34,13 +42,20 @@ export function usePolling() {
|
|
|
34
42
|
...(prevData ?? []),
|
|
35
43
|
];
|
|
36
44
|
});
|
|
45
|
+
})
|
|
46
|
+
.catch((err) => {
|
|
47
|
+
if (controller.signal.aborted) return;
|
|
48
|
+
console.warn('[Comnyx] Polling failed:', err);
|
|
49
|
+
reportSupportError(err, {
|
|
50
|
+
section: 'polling',
|
|
51
|
+
recoverable: true,
|
|
52
|
+
});
|
|
37
53
|
});
|
|
38
|
-
|
|
39
|
-
|
|
54
|
+
}, NEW_MESSAGES_CHECK_INTERVAL);
|
|
55
|
+
|
|
40
56
|
return () => {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
}
|
|
57
|
+
clearInterval(interval);
|
|
58
|
+
controller.abort();
|
|
44
59
|
};
|
|
45
60
|
}, [customer, lastMessage?.created_at, setData]);
|
|
46
61
|
}
|
|
@@ -1,5 +1,14 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { useMemo } from 'react';
|
|
2
|
+
import { darkTheme, lightTheme, type ThemeColors } from '../types/Theme';
|
|
3
|
+
import { useAppStore } from '../store/store';
|
|
2
4
|
|
|
3
5
|
export function useThemeColors(): ThemeColors {
|
|
4
|
-
|
|
6
|
+
const theme = useAppStore((s) => s.theme);
|
|
7
|
+
const override = useAppStore((s) => s.themeOverride);
|
|
8
|
+
|
|
9
|
+
return useMemo(() => {
|
|
10
|
+
const base = theme === 'dark' ? darkTheme : lightTheme;
|
|
11
|
+
const patch = override?.[theme];
|
|
12
|
+
return patch ? { ...base, ...patch } : base;
|
|
13
|
+
}, [theme, override]);
|
|
5
14
|
}
|
package/src/index.ts
CHANGED
|
@@ -5,6 +5,22 @@ export { ComnyxSupport } from './support';
|
|
|
5
5
|
export { ComnyxNotifications } from './notifications';
|
|
6
6
|
//types
|
|
7
7
|
export { NotificationPermissionStatus } from './NativeComnyx';
|
|
8
|
+
export type {
|
|
9
|
+
ThemeColors,
|
|
10
|
+
ThemeColorsOverride,
|
|
11
|
+
ThemeOverrideConfig,
|
|
12
|
+
} from './types/Theme';
|
|
13
|
+
export type {
|
|
14
|
+
SupportConfig,
|
|
15
|
+
SupportHeaderRenderProps,
|
|
16
|
+
SupportErrorRenderProps,
|
|
17
|
+
SupportMessageRenderProps,
|
|
18
|
+
SupportSendPayload,
|
|
19
|
+
SupportErrorSection,
|
|
20
|
+
SupportErrorContext,
|
|
21
|
+
SupportErrorReporter,
|
|
22
|
+
} from './support';
|
|
23
|
+
export { ComnyxErrorBoundary } from './support';
|
|
8
24
|
|
|
9
25
|
//deprecated
|
|
10
26
|
export { registerOneSignalForComnyx } from './register/collectData';
|
|
@@ -81,10 +81,11 @@ function changePermissionStatus(status: NotificationPermissionStatus) {
|
|
|
81
81
|
|
|
82
82
|
async function initializeNativeNotifications(options: InitializeOptions) {
|
|
83
83
|
const state = useAppStore.getState();
|
|
84
|
-
//
|
|
84
|
+
// Listeners are attached BEFORE nativeComnyx.initialize so that a
|
|
85
|
+
// synchronous TOKEN_INIT emission (e.g. cached APN token on iOS) is not
|
|
86
|
+
// lost between native resolve and JS subscription.
|
|
85
87
|
globalSubscriptions.subscriptionsForNotification = [
|
|
86
88
|
ComnyxNotifications.addEventListener('TOKEN_INIT', (data) => {
|
|
87
|
-
console.log('TOKEN_INIT', data);
|
|
88
89
|
useAppStore.getState().setToken(data.token);
|
|
89
90
|
if (Platform.OS === 'ios') {
|
|
90
91
|
accumulator.add({
|
|
@@ -168,7 +169,11 @@ export async function initializeNotifications(
|
|
|
168
169
|
globalSubscriptions.subscriptionsForOptIn = AppState.addEventListener(
|
|
169
170
|
'change',
|
|
170
171
|
async (nextAppState) => {
|
|
171
|
-
|
|
172
|
+
// AppState callbacks are fire-and-forget — any throw here becomes an
|
|
173
|
+
// unhandled rejection that can surface as a red-screen in the host app.
|
|
174
|
+
// Keep the whole body inside a guard.
|
|
175
|
+
try {
|
|
176
|
+
if (nextAppState !== 'active') return;
|
|
172
177
|
const permissionGivenInForeground =
|
|
173
178
|
await ComnyxNotifications.checkOptIn();
|
|
174
179
|
changePermissionStatus(permissionGivenInForeground);
|
|
@@ -179,24 +184,21 @@ export async function initializeNotifications(
|
|
|
179
184
|
if (!state.notificationInitialized) {
|
|
180
185
|
await initializeNativeNotifications(options);
|
|
181
186
|
}
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
showAlertDialog();
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
});
|
|
198
|
-
}
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
if (!params.showOptInOnForeground) return;
|
|
190
|
+
const permissionResultInner = await ComnyxNotifications.optIn();
|
|
191
|
+
changePermissionStatus(permissionResultInner);
|
|
192
|
+
if (permissionResultInner === NotificationPermissionStatus.GRANTED) {
|
|
193
|
+
await initializeNativeNotifications(options);
|
|
194
|
+
} else if (
|
|
195
|
+
permissionResultInner === NotificationPermissionStatus.BLOCKED &&
|
|
196
|
+
params.linkToSettings
|
|
197
|
+
) {
|
|
198
|
+
showAlertDialog();
|
|
199
199
|
}
|
|
200
|
+
} catch (err) {
|
|
201
|
+
console.warn('[Comnyx] AppState handler failed', err);
|
|
200
202
|
}
|
|
201
203
|
}
|
|
202
204
|
);
|