@developer_tribe/react-native-comnyx 0.15.0 → 0.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (185) hide show
  1. package/Comnyx.podspec +10 -2
  2. package/README.md +50 -0
  3. package/android/build.gradle +1 -0
  4. package/android/consumer-rules.pro +23 -0
  5. package/android/generated/java/com/comnyx/NativeComnyxSpec.java +46 -0
  6. package/android/generated/jni/RNComnyxSpec-generated.cpp +23 -1
  7. package/android/generated/jni/RNComnyxSpec.h +7 -0
  8. package/android/generated/jni/react/renderer/components/RNComnyxSpec/RNComnyxSpecJSI-generated.cpp +21 -0
  9. package/android/generated/jni/react/renderer/components/RNComnyxSpec/RNComnyxSpecJSI.h +70 -0
  10. package/android/src/main/AndroidManifest.xml +11 -1
  11. package/android/src/main/AndroidManifestNew.xml +11 -1
  12. package/android/src/main/java/com/comnyx/ComnyxMediaPickerModule.kt +91 -51
  13. package/android/src/main/java/com/comnyx/ComnyxModule.kt +7 -0
  14. package/android/src/main/java/com/comnyx/src/messaging/firebase/FirebaseMessagingService.kt +4 -6
  15. package/android/src/main/res/xml/comnyx_file_paths.xml +12 -0
  16. package/ios/APNService.swift +9 -9
  17. package/ios/Comnyx.swift +17 -8
  18. package/ios/ComnyxMediaPicker.swift +47 -26
  19. package/ios/ComnyxMessaging.swift +2 -0
  20. package/ios/PrivacyInfo.xcprivacy +32 -0
  21. package/ios/comnyx_post_install.rb +25 -0
  22. package/ios/generated/RCTAppDependencyProvider.h +25 -0
  23. package/ios/generated/RCTAppDependencyProvider.mm +55 -0
  24. package/ios/generated/RCTModulesConformingToProtocolsProvider.h +18 -0
  25. package/ios/generated/RCTModulesConformingToProtocolsProvider.mm +33 -0
  26. package/ios/generated/RCTThirdPartyComponentsProvider.h +16 -0
  27. package/ios/generated/RCTThirdPartyComponentsProvider.mm +23 -0
  28. package/ios/generated/RNComnyxSpec/RNComnyxSpec-generated.mm +53 -0
  29. package/ios/generated/RNComnyxSpec/RNComnyxSpec.h +67 -0
  30. package/ios/generated/RNComnyxSpecJSI-generated.cpp +38 -0
  31. package/ios/generated/RNComnyxSpecJSI.h +89 -0
  32. package/ios/generated/ReactAppDependencyProvider.podspec +34 -0
  33. package/lib/commonjs/NativeComnyxMediaPicker.js +19 -0
  34. package/lib/commonjs/NativeComnyxMediaPicker.js.map +1 -1
  35. package/lib/commonjs/api/conversations.js +6 -6
  36. package/lib/commonjs/api/conversations.js.map +1 -1
  37. package/lib/commonjs/api/customers.js +3 -2
  38. package/lib/commonjs/api/customers.js.map +1 -1
  39. package/lib/commonjs/api/media.js +20 -6
  40. package/lib/commonjs/api/media.js.map +1 -1
  41. package/lib/commonjs/api/messages.js +3 -2
  42. package/lib/commonjs/api/messages.js.map +1 -1
  43. package/lib/commonjs/components/ChatList.js +93 -45
  44. package/lib/commonjs/components/ChatList.js.map +1 -1
  45. package/lib/commonjs/components/ComnyxErrorBoundary.js +92 -0
  46. package/lib/commonjs/components/ComnyxErrorBoundary.js.map +1 -0
  47. package/lib/commonjs/components/CustomerForm.js +2 -2
  48. package/lib/commonjs/components/CustomerForm.js.map +1 -1
  49. package/lib/commonjs/components/MediaMessageItem.js +4 -3
  50. package/lib/commonjs/components/MediaMessageItem.js.map +1 -1
  51. package/lib/commonjs/components/MessageInput.js +63 -13
  52. package/lib/commonjs/components/MessageInput.js.map +1 -1
  53. package/lib/commonjs/components/MessageItem.js +1 -1
  54. package/lib/commonjs/components/MessageItem.js.map +1 -1
  55. package/lib/commonjs/hooks/usePolling.js +25 -21
  56. package/lib/commonjs/hooks/usePolling.js.map +1 -1
  57. package/lib/commonjs/hooks/useThemeColors.js +12 -1
  58. package/lib/commonjs/hooks/useThemeColors.js.map +1 -1
  59. package/lib/commonjs/index.js.map +1 -1
  60. package/lib/commonjs/notifications/initializeNotifications.js +19 -16
  61. package/lib/commonjs/notifications/initializeNotifications.js.map +1 -1
  62. package/lib/commonjs/register/Accumulator.js +19 -6
  63. package/lib/commonjs/register/Accumulator.js.map +1 -1
  64. package/lib/commonjs/register/collectData.js +1 -1
  65. package/lib/commonjs/register/collectData.js.map +1 -1
  66. package/lib/commonjs/store/store.js +6 -0
  67. package/lib/commonjs/store/store.js.map +1 -1
  68. package/lib/commonjs/support/ComnyxSupport.js +60 -15
  69. package/lib/commonjs/support/ComnyxSupport.js.map +1 -1
  70. package/lib/commonjs/support/SupportConfigContext.js +24 -0
  71. package/lib/commonjs/support/SupportConfigContext.js.map +1 -0
  72. package/lib/commonjs/types/Theme.js +30 -2
  73. package/lib/commonjs/types/Theme.js.map +1 -1
  74. package/lib/commonjs/version.js +1 -1
  75. package/lib/module/NativeComnyxMediaPicker.js +18 -0
  76. package/lib/module/NativeComnyxMediaPicker.js.map +1 -1
  77. package/lib/module/api/conversations.js +6 -6
  78. package/lib/module/api/conversations.js.map +1 -1
  79. package/lib/module/api/customers.js +3 -2
  80. package/lib/module/api/customers.js.map +1 -1
  81. package/lib/module/api/media.js +21 -6
  82. package/lib/module/api/media.js.map +1 -1
  83. package/lib/module/api/messages.js +3 -2
  84. package/lib/module/api/messages.js.map +1 -1
  85. package/lib/module/components/ChatList.js +94 -46
  86. package/lib/module/components/ChatList.js.map +1 -1
  87. package/lib/module/components/ComnyxErrorBoundary.js +87 -0
  88. package/lib/module/components/ComnyxErrorBoundary.js.map +1 -0
  89. package/lib/module/components/CustomerForm.js +2 -2
  90. package/lib/module/components/CustomerForm.js.map +1 -1
  91. package/lib/module/components/MediaMessageItem.js +4 -3
  92. package/lib/module/components/MediaMessageItem.js.map +1 -1
  93. package/lib/module/components/MessageInput.js +64 -14
  94. package/lib/module/components/MessageInput.js.map +1 -1
  95. package/lib/module/components/MessageItem.js +1 -1
  96. package/lib/module/components/MessageItem.js.map +1 -1
  97. package/lib/module/hooks/usePolling.js +25 -21
  98. package/lib/module/hooks/usePolling.js.map +1 -1
  99. package/lib/module/hooks/useThemeColors.js +13 -2
  100. package/lib/module/hooks/useThemeColors.js.map +1 -1
  101. package/lib/module/index.js +0 -1
  102. package/lib/module/index.js.map +1 -1
  103. package/lib/module/notifications/initializeNotifications.js +19 -16
  104. package/lib/module/notifications/initializeNotifications.js.map +1 -1
  105. package/lib/module/register/Accumulator.js +19 -6
  106. package/lib/module/register/Accumulator.js.map +1 -1
  107. package/lib/module/register/collectData.js +1 -1
  108. package/lib/module/register/collectData.js.map +1 -1
  109. package/lib/module/store/store.js +6 -0
  110. package/lib/module/store/store.js.map +1 -1
  111. package/lib/module/support/ComnyxSupport.js +61 -16
  112. package/lib/module/support/ComnyxSupport.js.map +1 -1
  113. package/lib/module/support/SupportConfigContext.js +19 -0
  114. package/lib/module/support/SupportConfigContext.js.map +1 -0
  115. package/lib/module/types/Theme.js +30 -2
  116. package/lib/module/types/Theme.js.map +1 -1
  117. package/lib/module/version.js +1 -1
  118. package/lib/typescript/src/NativeComnyxMediaPicker.d.ts +9 -0
  119. package/lib/typescript/src/NativeComnyxMediaPicker.d.ts.map +1 -1
  120. package/lib/typescript/src/api/conversations.d.ts +2 -2
  121. package/lib/typescript/src/api/conversations.d.ts.map +1 -1
  122. package/lib/typescript/src/api/customers.d.ts +1 -1
  123. package/lib/typescript/src/api/customers.d.ts.map +1 -1
  124. package/lib/typescript/src/api/media.d.ts +3 -3
  125. package/lib/typescript/src/api/media.d.ts.map +1 -1
  126. package/lib/typescript/src/api/messages.d.ts +1 -1
  127. package/lib/typescript/src/api/messages.d.ts.map +1 -1
  128. package/lib/typescript/src/components/ChatList.d.ts.map +1 -1
  129. package/lib/typescript/src/components/ComnyxErrorBoundary.d.ts +18 -0
  130. package/lib/typescript/src/components/ComnyxErrorBoundary.d.ts.map +1 -0
  131. package/lib/typescript/src/components/MediaMessageItem.d.ts.map +1 -1
  132. package/lib/typescript/src/components/MessageInput.d.ts.map +1 -1
  133. package/lib/typescript/src/components/MessageItem.d.ts.map +1 -1
  134. package/lib/typescript/src/hooks/usePolling.d.ts.map +1 -1
  135. package/lib/typescript/src/hooks/useThemeColors.d.ts.map +1 -1
  136. package/lib/typescript/src/index.d.ts +2 -0
  137. package/lib/typescript/src/index.d.ts.map +1 -1
  138. package/lib/typescript/src/notifications/initializeNotifications.d.ts.map +1 -1
  139. package/lib/typescript/src/register/Accumulator.d.ts.map +1 -1
  140. package/lib/typescript/src/register/collectData.d.ts +4 -1
  141. package/lib/typescript/src/register/collectData.d.ts.map +1 -1
  142. package/lib/typescript/src/store/store.d.ts +6 -2
  143. package/lib/typescript/src/store/store.d.ts.map +1 -1
  144. package/lib/typescript/src/support/ComnyxSupport.d.ts +56 -2
  145. package/lib/typescript/src/support/ComnyxSupport.d.ts.map +1 -1
  146. package/lib/typescript/src/support/SupportConfigContext.d.ts +58 -0
  147. package/lib/typescript/src/support/SupportConfigContext.d.ts.map +1 -0
  148. package/lib/typescript/src/support/index.d.ts +1 -0
  149. package/lib/typescript/src/support/index.d.ts.map +1 -1
  150. package/lib/typescript/src/types/Conversation.d.ts +2 -2
  151. package/lib/typescript/src/types/Conversation.d.ts.map +1 -1
  152. package/lib/typescript/src/types/Customer.d.ts +1 -1
  153. package/lib/typescript/src/types/Customer.d.ts.map +1 -1
  154. package/lib/typescript/src/types/MessageResponse.d.ts +7 -4
  155. package/lib/typescript/src/types/MessageResponse.d.ts.map +1 -1
  156. package/lib/typescript/src/types/Theme.d.ts +26 -0
  157. package/lib/typescript/src/types/Theme.d.ts.map +1 -1
  158. package/lib/typescript/src/version.d.ts +1 -1
  159. package/package.json +12 -25
  160. package/src/NativeComnyxMediaPicker.ts +18 -0
  161. package/src/api/conversations.ts +6 -4
  162. package/src/api/customers.ts +3 -1
  163. package/src/api/media.ts +32 -10
  164. package/src/api/messages.ts +3 -1
  165. package/src/components/ChatList.tsx +115 -55
  166. package/src/components/ComnyxErrorBoundary.tsx +91 -0
  167. package/src/components/CustomerForm.tsx +2 -2
  168. package/src/components/MediaMessageItem.tsx +10 -3
  169. package/src/components/MessageInput.tsx +89 -16
  170. package/src/components/MessageItem.tsx +12 -13
  171. package/src/hooks/usePolling.ts +21 -11
  172. package/src/hooks/useThemeColors.ts +11 -2
  173. package/src/index.ts +12 -0
  174. package/src/notifications/initializeNotifications.ts +22 -20
  175. package/src/register/Accumulator.ts +26 -9
  176. package/src/register/collectData.ts +10 -2
  177. package/src/store/store.ts +11 -3
  178. package/src/support/ComnyxSupport.tsx +128 -22
  179. package/src/support/SupportConfigContext.tsx +79 -0
  180. package/src/support/index.ts +7 -0
  181. package/src/types/Conversation.ts +2 -2
  182. package/src/types/Customer.ts +1 -2
  183. package/src/types/MessageResponse.ts +4 -4
  184. package/src/types/Theme.ts +38 -0
  185. package/src/version.ts +1 -1
@@ -13,12 +13,19 @@ export function usePolling() {
13
13
  const lastMessage = data ? data[data.length - 1] : null;
14
14
  useEffect(() => {
15
15
  const created_at = lastMessage?.created_at;
16
- let interval: ReturnType<typeof setInterval> | null = null;
17
- if (customer && created_at) {
18
- interval = setInterval(() => {
19
- getNewCustomerConversation(customer?.external_id, created_at, {
20
- fake: useAppStore.getState().fake,
21
- }).then((newData) => {
16
+ if (!customer || !created_at) {
17
+ return undefined;
18
+ }
19
+ const controller = new AbortController();
20
+ const interval = setInterval(() => {
21
+ getNewCustomerConversation(
22
+ customer.external_id,
23
+ created_at,
24
+ { fake: useAppStore.getState().fake },
25
+ controller.signal
26
+ )
27
+ .then((newData) => {
28
+ if (controller.signal.aborted) return;
22
29
  setData((prevData) => {
23
30
  const newMessages = newData.page.data;
24
31
  const existingIds = new Set(prevData?.map((msg) => msg.id));
@@ -34,13 +41,16 @@ export function usePolling() {
34
41
  ...(prevData ?? []),
35
42
  ];
36
43
  });
44
+ })
45
+ .catch((err) => {
46
+ if (controller.signal.aborted) return;
47
+ console.warn('[Comnyx] Polling failed:', err);
37
48
  });
38
- }, NEW_MESSAGES_CHECK_INTERVAL);
39
- }
49
+ }, NEW_MESSAGES_CHECK_INTERVAL);
50
+
40
51
  return () => {
41
- if (interval) {
42
- clearInterval(interval);
43
- }
52
+ clearInterval(interval);
53
+ controller.abort();
44
54
  };
45
55
  }, [customer, lastMessage?.created_at, setData]);
46
56
  }
@@ -1,5 +1,14 @@
1
- import { lightTheme, type ThemeColors } from '../types/Theme';
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
- return lightTheme;
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,18 @@ 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
+ } from './support';
8
20
 
9
21
  //deprecated
10
22
  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
- //TODO: better event listener. Event listener doesn't work if its set after initialize
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
- if (nextAppState === 'active') {
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
- } else {
183
- if (params.showOptInOnForeground) {
184
- ComnyxNotifications.optIn().then(async (permissionResultInner) => {
185
- changePermissionStatus(permissionResultInner);
186
- if (
187
- permissionResultInner === NotificationPermissionStatus.GRANTED
188
- ) {
189
- await initializeNativeNotifications(options);
190
- } else if (
191
- permissionResultInner === NotificationPermissionStatus.BLOCKED
192
- ) {
193
- if (params.linkToSettings) {
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
  );
@@ -1,4 +1,4 @@
1
- import type { CreateCustomerRequest } from '../types/Customer';
1
+ import type { CreateCustomerRequest, CustomParameter } from '../types/Customer';
2
2
 
3
3
  const ACCUMULATOR_DEBOUNCE_TIME_IN_MS = 3000;
4
4
 
@@ -21,11 +21,17 @@ export class Accumulator {
21
21
 
22
22
  add(data: Partial<Omit<CreateCustomerRequest, 'externalId'>>) {
23
23
  if (!this.registerData) {
24
- throw new Error('Register data is not set');
24
+ // Late events (push token callbacks, AppState changes) may arrive
25
+ // after logout() or before login(). Ignore silently instead of
26
+ // throwing so the host app is not crashed by SDK-internal races.
27
+ console.warn(
28
+ '[Comnyx] add() called before register/after logout — ignored'
29
+ );
30
+ return;
25
31
  }
26
32
 
27
33
  // First, create a map of existing custom parameters by name
28
- const customParamsMap = new Map<string, string>();
34
+ const customParamsMap = new Map<string, CustomParameter['value']>();
29
35
 
30
36
  // Add existing parameters to the map
31
37
  (this.registerData.customParameters || []).forEach((param) => {
@@ -59,10 +65,19 @@ export class Accumulator {
59
65
 
60
66
  async flush() {
61
67
  if (!this.registerData) {
62
- throw new Error('Register data is not set');
68
+ console.warn(
69
+ '[Comnyx] flush() called before register/after logout — ignored'
70
+ );
71
+ return;
63
72
  }
64
73
  if (this.listener) {
65
- await this.listener(this.registerData!);
74
+ try {
75
+ await this.listener(this.registerData!);
76
+ } catch (err) {
77
+ console.error('[Comnyx] flush listener error', err);
78
+ this.reset();
79
+ return;
80
+ }
66
81
  this.registerData = {
67
82
  ...this.registerData,
68
83
  customParameters: [],
@@ -74,14 +89,16 @@ export class Accumulator {
74
89
 
75
90
  debounce() {
76
91
  if (!this.registerData) {
77
- throw new Error('Register data is not set');
92
+ return;
78
93
  }
79
94
  this.reset();
80
95
  this.__select_time = setTimeout(() => {
81
- if (this.listener) {
82
- this.listener(this.registerData!);
96
+ if (this.listener && this.registerData) {
97
+ Promise.resolve(this.listener(this.registerData)).catch((err) => {
98
+ console.error('[Comnyx] debounce listener error', err);
99
+ });
83
100
  this.registerData = {
84
- ...this.registerData!,
101
+ ...this.registerData,
85
102
  customParameters: [],
86
103
  };
87
104
  this._isListenerCalledOnce = true;
@@ -1,12 +1,20 @@
1
+ import type { CustomParameter } from '../types/Customer';
1
2
  import { accumulator } from './Accumulator';
2
3
 
3
- export function collectData<T>(key: string | Record<string, any>, value?: T) {
4
+ export function collectData(key: string, value: CustomParameter['value']): void;
5
+ export function collectData(
6
+ data: Partial<Parameters<typeof accumulator.add>[0]>
7
+ ): void;
8
+ export function collectData(
9
+ key: string | Partial<Parameters<typeof accumulator.add>[0]>,
10
+ value?: CustomParameter['value']
11
+ ) {
4
12
  if (typeof key === 'string') {
5
13
  accumulator.add({
6
14
  customParameters: [
7
15
  {
8
16
  name: key,
9
- value,
17
+ value: value ?? null,
10
18
  },
11
19
  ],
12
20
  });
@@ -6,6 +6,8 @@ import type { AppConversationMessage } from '../types/Conversation';
6
6
  import type { LanguageCode } from '../types/Language';
7
7
  import type { Customer } from '../types/Customer';
8
8
  import type { LocalizationKeys } from '../types/LocalizationKeys';
9
+ import type { GlobalTheme } from '../types/GlobalTheme';
10
+ import type { ThemeOverrideConfig } from '../types/Theme';
9
11
  import { mmkvStorage } from '../utils/mmkvStorage';
10
12
 
11
13
  interface AppStoreState {
@@ -16,7 +18,8 @@ interface AppStoreState {
16
18
  language: LanguageCode;
17
19
  theme: 'light' | 'dark';
18
20
  fake: boolean;
19
- themes: any;
21
+ themes: GlobalTheme;
22
+ themeOverride: ThemeOverrideConfig;
20
23
  firstMessage: AppConversationMessage | null;
21
24
  setData: (
22
25
  cb: (
@@ -28,7 +31,8 @@ interface AppStoreState {
28
31
  setLanguage: (language: LanguageCode) => void;
29
32
  setTheme: (theme: 'light' | 'dark') => void;
30
33
  setFake: (fake: boolean) => void;
31
- setThemes: (themes: any) => void;
34
+ setThemes: (themes: GlobalTheme) => void;
35
+ setThemeOverride: (themeOverride: ThemeOverrideConfig) => void;
32
36
  updateBaseDimensions: (config: {
33
37
  baseHeight: number;
34
38
  baseWidth: number;
@@ -57,6 +61,7 @@ export const storeCreator: StateCreator<AppStoreState> = (set, get) => ({
57
61
  theme: 'light',
58
62
  fake: false,
59
63
  themes: {},
64
+ themeOverride: {},
60
65
  firstMessage: null,
61
66
  formSubmitted: false,
62
67
  setData: (cb) => {
@@ -81,9 +86,12 @@ export const storeCreator: StateCreator<AppStoreState> = (set, get) => ({
81
86
  setFake: (fake: boolean) => {
82
87
  set({ fake });
83
88
  },
84
- setThemes: (themes: any) => {
89
+ setThemes: (themes: GlobalTheme) => {
85
90
  set({ themes });
86
91
  },
92
+ setThemeOverride: (themeOverride: ThemeOverrideConfig) => {
93
+ set({ themeOverride });
94
+ },
87
95
  updateBaseDimensions: ({ baseWidth, baseHeight }) => {
88
96
  set({ baseWidth, baseHeight });
89
97
  },
@@ -5,35 +5,100 @@ import {
5
5
  type ViewStyle,
6
6
  type StyleProp,
7
7
  } from 'react-native';
8
+ import { useSafeAreaInsets } from 'react-native-safe-area-context';
8
9
  import { ChatList } from '../components/ChatList';
9
10
  import { CustomerForm } from '../components/CustomerForm';
10
11
  import type { LanguageCode } from '../types/Language';
11
- import { useEffect, useState } from 'react';
12
+ import type { GlobalTheme } from '../types/GlobalTheme';
13
+ import type { ThemeOverrideConfig } from '../types/Theme';
14
+ import { useEffect, useMemo, useState, type ReactElement } from 'react';
12
15
  import { AppText } from '../components/AppText';
13
16
  import { useThemeColors } from '../hooks/useThemeColors';
14
17
  import { usePolling } from '../hooks/usePolling';
15
18
  import { useAppStore } from '../store/store';
16
19
  import { ScaledSheet } from '../components/ScaledSheet';
17
20
  import { accumulator } from '../register/Accumulator';
21
+ import {
22
+ SupportConfigProvider,
23
+ type SupportConfig,
24
+ } from './SupportConfigContext';
25
+ import { ComnyxErrorBoundary } from '../components/ComnyxErrorBoundary';
18
26
  const closeIcon = require('../assets/x-close.png');
19
27
 
20
28
  interface SupportComnyxProps {
29
+ /** Language code used for localised UI strings. Defaults to `'en'`. */
21
30
  language?: LanguageCode;
31
+ /** Colour palette mode. Defaults to `'dark'`. */
22
32
  theme?: 'light' | 'dark';
33
+ /** Render the chat in fake/demo mode (no network). */
23
34
  fake?: boolean;
35
+ /** Called when the user taps the close button. */
24
36
  onBack: () => void;
25
- themes?: any;
37
+ /**
38
+ * Font-family provider. Shape: `{ family: (languageCode) => FamilyWeight }`.
39
+ * Not related to colour theming — use `themeOverride` for colours.
40
+ */
41
+ themes?: GlobalTheme;
42
+ /**
43
+ * Partial override for the built-in colour palette. Only the tokens you
44
+ * specify are replaced; the rest fall back to the default light/dark theme.
45
+ *
46
+ * @example
47
+ * <ComnyxSupport
48
+ * themeOverride={{
49
+ * light: { primary: '#FF6B6B', link: '#FF9999' },
50
+ * dark: { primary: '#FF9999' },
51
+ * }}
52
+ * />
53
+ */
54
+ themeOverride?: ThemeOverrideConfig;
55
+ /**
56
+ * @deprecated No longer consumed. Kept for backward compatibility and will
57
+ * be removed in a future major release.
58
+ */
26
59
  rtlFix?: boolean;
60
+ /** Additional style merged onto the root container. */
27
61
  containerStyle?: StyleProp<ViewStyle>;
62
+ /**
63
+ * Replace the built-in header. Receives `{ onBack }`. Return `null` to hide
64
+ * the header entirely.
65
+ */
66
+ renderHeader?: SupportConfig['renderHeader'];
67
+ /** Replace the built-in empty-state card shown when there are no messages. */
68
+ renderEmptyState?: SupportConfig['renderEmptyState'];
69
+ /**
70
+ * Replace the built-in init-failed screen. Receives `{ retry }` to trigger a
71
+ * first-page refetch.
72
+ */
73
+ renderErrorState?: SupportConfig['renderErrorState'];
74
+ /**
75
+ * Wrap or replace individual message bubbles. Receives
76
+ * `{ message, defaultNode }` — return `defaultNode` as-is for no change,
77
+ * wrap it for decorations (reactions, swipe actions), or render a fully
78
+ * custom bubble.
79
+ */
80
+ renderMessage?: SupportConfig['renderMessage'];
81
+ /**
82
+ * Called immediately before a message is dispatched. Throw or reject to
83
+ * cancel the send — the user's input and any pending media are preserved.
84
+ * Use for analytics, rate limiting, moderation or injecting extra context.
85
+ */
86
+ onBeforeSend?: SupportConfig['onBeforeSend'];
28
87
  }
29
88
 
30
- export function ComnyxSupport({
89
+ function ComnyxSupportInner({
31
90
  language = 'en',
32
91
  theme = 'dark',
33
92
  fake = false,
34
93
  onBack,
35
94
  themes,
95
+ themeOverride,
36
96
  containerStyle,
97
+ renderHeader,
98
+ renderEmptyState,
99
+ renderErrorState,
100
+ renderMessage,
101
+ onBeforeSend,
37
102
  }: SupportComnyxProps) {
38
103
  const { customer, formSubmitted } = useAppStore((s) => ({
39
104
  customer: s.customer,
@@ -41,15 +106,39 @@ export function ComnyxSupport({
41
106
  }));
42
107
  const [initLoading, setInitLoading] = useState(true);
43
108
  const themeColors = useThemeColors();
109
+ const insets = useSafeAreaInsets();
44
110
  usePolling();
45
111
 
112
+ const supportConfig = useMemo<SupportConfig>(
113
+ () => ({
114
+ renderHeader,
115
+ renderEmptyState,
116
+ renderErrorState,
117
+ renderMessage,
118
+ onBeforeSend,
119
+ }),
120
+ [
121
+ renderHeader,
122
+ renderEmptyState,
123
+ renderErrorState,
124
+ renderMessage,
125
+ onBeforeSend,
126
+ ]
127
+ );
128
+
46
129
  useEffect(() => {
47
130
  if (!accumulator.isListenerCalledOnce()) {
48
- accumulator.flush().then(() => {
49
- setTimeout(() => {
131
+ accumulator
132
+ .flush()
133
+ .then(() => {
134
+ setTimeout(() => {
135
+ setInitLoading(false);
136
+ }, 100);
137
+ })
138
+ .catch((err) => {
139
+ console.error('[Comnyx] accumulator.flush failed', err);
50
140
  setInitLoading(false);
51
- }, 100);
52
- });
141
+ });
53
142
  } else {
54
143
  setInitLoading(false);
55
144
  }
@@ -62,25 +151,31 @@ export function ComnyxSupport({
62
151
  }, [customer, customer?.external_id]);
63
152
 
64
153
  useEffect(() => {
65
- //TOOD: getState().initApp({language,theme,fake,rtlFix,themes})
66
154
  useAppStore.getState().setLanguage(language);
67
155
  useAppStore.getState().setTheme(theme);
68
156
  useAppStore.getState().setFake(fake);
69
157
  if (themes) {
70
158
  useAppStore.getState().setThemes(themes);
71
159
  }
72
- }, [language, theme, fake, themes]);
160
+ if (themeOverride) {
161
+ useAppStore.getState().setThemeOverride(themeOverride);
162
+ }
163
+ }, [language, theme, fake, themes, themeOverride]);
73
164
 
165
+ let body: ReactElement;
74
166
  if (!customer) {
75
167
  //NOTE: customer yoksa register hiç çalışmamış
76
- return (
168
+ body = (
77
169
  <View
78
170
  style={[styles.container, { backgroundColor: themeColors.background }]}
79
171
  >
80
172
  <TouchableOpacity
81
173
  activeOpacity={1}
82
- style={[styles.iconContainer]}
174
+ style={[styles.iconContainer, { top: Math.max(insets.top, 16) + 16 }]}
83
175
  onPress={onBack}
176
+ accessibilityRole="button"
177
+ accessibilityLabel="Close support"
178
+ hitSlop={{ top: 12, bottom: 12, left: 12, right: 12 }}
84
179
  >
85
180
  <Image
86
181
  source={closeIcon}
@@ -95,25 +190,37 @@ export function ComnyxSupport({
95
190
  );
96
191
  } else if (!formSubmitted) {
97
192
  //NOTE: customer var ama forSubmitted false (name===null)
98
- return (
193
+ body = (
99
194
  <CustomerForm
100
195
  loading={initLoading}
101
196
  onBack={onBack}
102
197
  containerStyle={containerStyle}
103
198
  />
104
199
  );
200
+ } else {
201
+ body = (
202
+ <View
203
+ style={[
204
+ styles.container,
205
+ { backgroundColor: themeColors.background },
206
+ containerStyle,
207
+ ]}
208
+ >
209
+ <ChatList initLoading={initLoading} onBack={onBack} />
210
+ </View>
211
+ );
105
212
  }
106
213
 
107
214
  return (
108
- <View
109
- style={[
110
- styles.container,
111
- { backgroundColor: themeColors.background },
112
- containerStyle,
113
- ]}
114
- >
115
- <ChatList initLoading={initLoading} onBack={onBack} />
116
- </View>
215
+ <SupportConfigProvider value={supportConfig}>{body}</SupportConfigProvider>
216
+ );
217
+ }
218
+
219
+ export function ComnyxSupport(props: SupportComnyxProps) {
220
+ return (
221
+ <ComnyxErrorBoundary onBack={props.onBack}>
222
+ <ComnyxSupportInner {...props} />
223
+ </ComnyxErrorBoundary>
117
224
  );
118
225
  }
119
226
 
@@ -124,7 +231,6 @@ const styles = ScaledSheet.create({
124
231
  },
125
232
  iconContainer: {
126
233
  position: 'absolute',
127
- top: '60@vs',
128
234
  left: '24@s',
129
235
  },
130
236
  closeIcon: {
@@ -0,0 +1,79 @@
1
+ import { createContext, useContext, type ReactNode } from 'react';
2
+ import type { AppConversationMessage } from '../types/Conversation';
3
+
4
+ export interface SupportHeaderRenderProps {
5
+ /** Invoke to close the support screen — same callback passed to `onBack`. */
6
+ onBack?: () => void;
7
+ }
8
+
9
+ export interface SupportErrorRenderProps {
10
+ /** Trigger a retry. Safe to call multiple times. */
11
+ retry: () => void;
12
+ }
13
+
14
+ export interface SupportMessageRenderProps {
15
+ message: AppConversationMessage;
16
+ /** The built-in bubble node — render it as-is, wrap it, or ignore it. */
17
+ defaultNode: ReactNode;
18
+ }
19
+
20
+ export interface SupportSendPayload {
21
+ /** Plain text content the user is sending. Empty when only media is attached. */
22
+ content: string;
23
+ /** Number of media attachments. 0 for text-only messages. */
24
+ mediaCount: number;
25
+ /** Types of the attached media. Empty for text-only messages. */
26
+ mediaTypes: Array<'image' | 'video'>;
27
+ }
28
+
29
+ export interface SupportConfig {
30
+ /**
31
+ * Replace the built-in top header (close button + "Support team / Live" row).
32
+ * Return any React node; returning `null` hides the header entirely.
33
+ */
34
+ renderHeader?: (props: SupportHeaderRenderProps) => ReactNode;
35
+ /** Replace the built-in empty-state card shown when there are no messages. */
36
+ renderEmptyState?: () => ReactNode;
37
+ /**
38
+ * Replace the built-in init-failed screen. The `retry` callback re-runs the
39
+ * first-page fetch.
40
+ */
41
+ renderErrorState?: (props: SupportErrorRenderProps) => ReactNode;
42
+ /**
43
+ * Replace or wrap individual message bubbles. `defaultNode` is the
44
+ * built-in `<MessageItem />` — return it for no change, wrap it to add
45
+ * decorations (reactions, swipe actions), or substitute a fully custom
46
+ * bubble.
47
+ */
48
+ renderMessage?: (props: SupportMessageRenderProps) => ReactNode;
49
+ /**
50
+ * Called immediately before a message is dispatched to the backend. Throw
51
+ * (or reject the returned promise) to cancel the send — the user's input
52
+ * and any pending media are preserved and the UI re-enables.
53
+ *
54
+ * Use for analytics, rate limiting, moderation, or injecting extra context.
55
+ */
56
+ onBeforeSend?: (payload: SupportSendPayload) => void | Promise<void>;
57
+ }
58
+
59
+ const EMPTY_CONFIG: SupportConfig = {};
60
+
61
+ const SupportConfigContext = createContext<SupportConfig>(EMPTY_CONFIG);
62
+
63
+ export function SupportConfigProvider({
64
+ value,
65
+ children,
66
+ }: {
67
+ value: SupportConfig;
68
+ children: ReactNode;
69
+ }) {
70
+ return (
71
+ <SupportConfigContext.Provider value={value}>
72
+ {children}
73
+ </SupportConfigContext.Provider>
74
+ );
75
+ }
76
+
77
+ export function useSupportConfig(): SupportConfig {
78
+ return useContext(SupportConfigContext);
79
+ }
@@ -1 +1,8 @@
1
1
  export { ComnyxSupport } from './ComnyxSupport';
2
+ export type {
3
+ SupportConfig,
4
+ SupportHeaderRenderProps,
5
+ SupportErrorRenderProps,
6
+ SupportMessageRenderProps,
7
+ SupportSendPayload,
8
+ } from './SupportConfigContext';
@@ -2,8 +2,8 @@ export interface ConversationMessage {
2
2
  id: number;
3
3
  content: string;
4
4
  created_at: string;
5
- user?: any | null;
6
- bot?: any | null;
5
+ user?: unknown | null;
6
+ bot?: unknown | null;
7
7
  customer?: { name: string; profile_photo_url: null } | null;
8
8
  files?: Array<{
9
9
  id: number;
@@ -1,7 +1,6 @@
1
1
  export interface CustomParameter {
2
2
  name: string;
3
- //JSON any, Record<string, T>, Its added to prevent any type errors. T could be any primitive type.
4
- value: any;
3
+ value: string | number | boolean | null;
5
4
  }
6
5
 
7
6
  export interface OneSignalIntegrationParameters {
@@ -6,9 +6,9 @@ export interface MessageResponse {
6
6
  updated_at: string;
7
7
  created_at: string;
8
8
  id: number;
9
- user: null | any;
10
- customer: null | any;
11
- bot: null | any;
9
+ user: null | unknown;
10
+ customer: null | { name: string; profile_photo_url: null };
11
+ bot: null | unknown;
12
12
  conversation: {
13
13
  id: number;
14
14
  project_id: number;
@@ -55,6 +55,6 @@ export interface MessageResponse {
55
55
  };
56
56
  };
57
57
  };
58
- events: any[];
58
+ events: unknown[];
59
59
  };
60
60
  }