@ion299/sdk-react-native 0.1.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (199) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/ChatPlatformSdk.podspec +20 -0
  3. package/README.md +315 -0
  4. package/android/build.gradle +54 -0
  5. package/android/src/main/AndroidManifest.xml +18 -0
  6. package/android/src/main/java/com/chatplatform/sdk/ChatSdkDownloaderModule.kt +240 -0
  7. package/android/src/main/java/com/chatplatform/sdk/ChatSdkFilePickerModule.kt +165 -0
  8. package/android/src/main/java/com/chatplatform/sdk/ChatSdkPackage.kt +15 -0
  9. package/android/src/main/res/xml/chat_sdk_file_paths.xml +7 -0
  10. package/ios/ChatSdkDownloader.m +10 -0
  11. package/ios/ChatSdkDownloader.swift +141 -0
  12. package/ios/ChatSdkFilePicker.m +9 -0
  13. package/ios/ChatSdkFilePicker.swift +161 -0
  14. package/lib/commonjs/ChatSDK.js +193 -0
  15. package/lib/commonjs/ChatSDK.js.map +1 -0
  16. package/lib/commonjs/api.js +195 -0
  17. package/lib/commonjs/api.js.map +1 -0
  18. package/lib/commonjs/attachmentUtils.js +39 -0
  19. package/lib/commonjs/attachmentUtils.js.map +1 -0
  20. package/lib/commonjs/components/AttachmentGallery.js +367 -0
  21. package/lib/commonjs/components/AttachmentGallery.js.map +1 -0
  22. package/lib/commonjs/components/ChatScreen.js +286 -0
  23. package/lib/commonjs/components/ChatScreen.js.map +1 -0
  24. package/lib/commonjs/components/MessageBubble.js +227 -0
  25. package/lib/commonjs/components/MessageBubble.js.map +1 -0
  26. package/lib/commonjs/components/MessageInput.js +273 -0
  27. package/lib/commonjs/components/MessageInput.js.map +1 -0
  28. package/lib/commonjs/components/SurveyOverlay.js +499 -0
  29. package/lib/commonjs/components/SurveyOverlay.js.map +1 -0
  30. package/lib/commonjs/downloaders/defaultAttachmentDownloader.js +28 -0
  31. package/lib/commonjs/downloaders/defaultAttachmentDownloader.js.map +1 -0
  32. package/lib/commonjs/filePicker.js +25 -0
  33. package/lib/commonjs/filePicker.js.map +1 -0
  34. package/lib/commonjs/index.js +27 -0
  35. package/lib/commonjs/index.js.map +1 -0
  36. package/lib/commonjs/native/NativeChatSdkDownloader.js +28 -0
  37. package/lib/commonjs/native/NativeChatSdkDownloader.js.map +1 -0
  38. package/lib/commonjs/native/NativeChatSdkFilePicker.js +17 -0
  39. package/lib/commonjs/native/NativeChatSdkFilePicker.js.map +1 -0
  40. package/lib/commonjs/package.json +1 -0
  41. package/lib/commonjs/realtime.js +242 -0
  42. package/lib/commonjs/realtime.js.map +1 -0
  43. package/lib/commonjs/safeArea.js +18 -0
  44. package/lib/commonjs/safeArea.js.map +1 -0
  45. package/lib/commonjs/session.js +159 -0
  46. package/lib/commonjs/session.js.map +1 -0
  47. package/lib/commonjs/surveyCache.js +30 -0
  48. package/lib/commonjs/surveyCache.js.map +1 -0
  49. package/lib/commonjs/theme.js +29 -0
  50. package/lib/commonjs/theme.js.map +1 -0
  51. package/lib/commonjs/types.js +2 -0
  52. package/lib/commonjs/types.js.map +1 -0
  53. package/lib/commonjs/useChat.js +145 -0
  54. package/lib/commonjs/useChat.js.map +1 -0
  55. package/lib/module/ChatSDK.js +189 -0
  56. package/lib/module/ChatSDK.js.map +1 -0
  57. package/lib/module/api.js +190 -0
  58. package/lib/module/api.js.map +1 -0
  59. package/lib/module/attachmentUtils.js +33 -0
  60. package/lib/module/attachmentUtils.js.map +1 -0
  61. package/lib/module/components/AttachmentGallery.js +362 -0
  62. package/lib/module/components/AttachmentGallery.js.map +1 -0
  63. package/lib/module/components/ChatScreen.js +281 -0
  64. package/lib/module/components/ChatScreen.js.map +1 -0
  65. package/lib/module/components/MessageBubble.js +222 -0
  66. package/lib/module/components/MessageBubble.js.map +1 -0
  67. package/lib/module/components/MessageInput.js +268 -0
  68. package/lib/module/components/MessageInput.js.map +1 -0
  69. package/lib/module/components/SurveyOverlay.js +494 -0
  70. package/lib/module/components/SurveyOverlay.js.map +1 -0
  71. package/lib/module/downloaders/defaultAttachmentDownloader.js +22 -0
  72. package/lib/module/downloaders/defaultAttachmentDownloader.js.map +1 -0
  73. package/lib/module/filePicker.js +20 -0
  74. package/lib/module/filePicker.js.map +1 -0
  75. package/lib/module/index.js +6 -0
  76. package/lib/module/index.js.map +1 -0
  77. package/lib/module/native/NativeChatSdkDownloader.js +23 -0
  78. package/lib/module/native/NativeChatSdkDownloader.js.map +1 -0
  79. package/lib/module/native/NativeChatSdkFilePicker.js +13 -0
  80. package/lib/module/native/NativeChatSdkFilePicker.js.map +1 -0
  81. package/lib/module/package.json +1 -0
  82. package/lib/module/realtime.js +236 -0
  83. package/lib/module/realtime.js.map +1 -0
  84. package/lib/module/safeArea.js +14 -0
  85. package/lib/module/safeArea.js.map +1 -0
  86. package/lib/module/session.js +154 -0
  87. package/lib/module/session.js.map +1 -0
  88. package/lib/module/surveyCache.js +23 -0
  89. package/lib/module/surveyCache.js.map +1 -0
  90. package/lib/module/theme.js +25 -0
  91. package/lib/module/theme.js.map +1 -0
  92. package/lib/module/types.js +2 -0
  93. package/lib/module/types.js.map +1 -0
  94. package/lib/module/useChat.js +141 -0
  95. package/lib/module/useChat.js.map +1 -0
  96. package/lib/typescript/commonjs/ChatSDK.d.ts +49 -0
  97. package/lib/typescript/commonjs/ChatSDK.d.ts.map +1 -0
  98. package/lib/typescript/commonjs/api.d.ts +31 -0
  99. package/lib/typescript/commonjs/api.d.ts.map +1 -0
  100. package/lib/typescript/commonjs/attachmentUtils.d.ts +12 -0
  101. package/lib/typescript/commonjs/attachmentUtils.d.ts.map +1 -0
  102. package/lib/typescript/commonjs/components/AttachmentGallery.d.ts +16 -0
  103. package/lib/typescript/commonjs/components/AttachmentGallery.d.ts.map +1 -0
  104. package/lib/typescript/commonjs/components/ChatScreen.d.ts +16 -0
  105. package/lib/typescript/commonjs/components/ChatScreen.d.ts.map +1 -0
  106. package/lib/typescript/commonjs/components/MessageBubble.d.ts +12 -0
  107. package/lib/typescript/commonjs/components/MessageBubble.d.ts.map +1 -0
  108. package/lib/typescript/commonjs/components/MessageInput.d.ts +14 -0
  109. package/lib/typescript/commonjs/components/MessageInput.d.ts.map +1 -0
  110. package/lib/typescript/commonjs/components/SurveyOverlay.d.ts +13 -0
  111. package/lib/typescript/commonjs/components/SurveyOverlay.d.ts.map +1 -0
  112. package/lib/typescript/commonjs/downloaders/defaultAttachmentDownloader.d.ts +3 -0
  113. package/lib/typescript/commonjs/downloaders/defaultAttachmentDownloader.d.ts.map +1 -0
  114. package/lib/typescript/commonjs/filePicker.d.ts +4 -0
  115. package/lib/typescript/commonjs/filePicker.d.ts.map +1 -0
  116. package/lib/typescript/commonjs/index.d.ts +7 -0
  117. package/lib/typescript/commonjs/index.d.ts.map +1 -0
  118. package/lib/typescript/commonjs/native/NativeChatSdkDownloader.d.ts +24 -0
  119. package/lib/typescript/commonjs/native/NativeChatSdkDownloader.d.ts.map +1 -0
  120. package/lib/typescript/commonjs/native/NativeChatSdkFilePicker.d.ts +17 -0
  121. package/lib/typescript/commonjs/native/NativeChatSdkFilePicker.d.ts.map +1 -0
  122. package/lib/typescript/commonjs/package.json +1 -0
  123. package/lib/typescript/commonjs/realtime.d.ts +42 -0
  124. package/lib/typescript/commonjs/realtime.d.ts.map +1 -0
  125. package/lib/typescript/commonjs/safeArea.d.ts +4 -0
  126. package/lib/typescript/commonjs/safeArea.d.ts.map +1 -0
  127. package/lib/typescript/commonjs/session.d.ts +45 -0
  128. package/lib/typescript/commonjs/session.d.ts.map +1 -0
  129. package/lib/typescript/commonjs/surveyCache.d.ts +5 -0
  130. package/lib/typescript/commonjs/surveyCache.d.ts.map +1 -0
  131. package/lib/typescript/commonjs/theme.d.ts +21 -0
  132. package/lib/typescript/commonjs/theme.d.ts.map +1 -0
  133. package/lib/typescript/commonjs/types.d.ts +156 -0
  134. package/lib/typescript/commonjs/types.d.ts.map +1 -0
  135. package/lib/typescript/commonjs/useChat.d.ts +16 -0
  136. package/lib/typescript/commonjs/useChat.d.ts.map +1 -0
  137. package/lib/typescript/module/ChatSDK.d.ts +49 -0
  138. package/lib/typescript/module/ChatSDK.d.ts.map +1 -0
  139. package/lib/typescript/module/api.d.ts +31 -0
  140. package/lib/typescript/module/api.d.ts.map +1 -0
  141. package/lib/typescript/module/attachmentUtils.d.ts +12 -0
  142. package/lib/typescript/module/attachmentUtils.d.ts.map +1 -0
  143. package/lib/typescript/module/components/AttachmentGallery.d.ts +16 -0
  144. package/lib/typescript/module/components/AttachmentGallery.d.ts.map +1 -0
  145. package/lib/typescript/module/components/ChatScreen.d.ts +16 -0
  146. package/lib/typescript/module/components/ChatScreen.d.ts.map +1 -0
  147. package/lib/typescript/module/components/MessageBubble.d.ts +12 -0
  148. package/lib/typescript/module/components/MessageBubble.d.ts.map +1 -0
  149. package/lib/typescript/module/components/MessageInput.d.ts +14 -0
  150. package/lib/typescript/module/components/MessageInput.d.ts.map +1 -0
  151. package/lib/typescript/module/components/SurveyOverlay.d.ts +13 -0
  152. package/lib/typescript/module/components/SurveyOverlay.d.ts.map +1 -0
  153. package/lib/typescript/module/downloaders/defaultAttachmentDownloader.d.ts +3 -0
  154. package/lib/typescript/module/downloaders/defaultAttachmentDownloader.d.ts.map +1 -0
  155. package/lib/typescript/module/filePicker.d.ts +4 -0
  156. package/lib/typescript/module/filePicker.d.ts.map +1 -0
  157. package/lib/typescript/module/index.d.ts +7 -0
  158. package/lib/typescript/module/index.d.ts.map +1 -0
  159. package/lib/typescript/module/native/NativeChatSdkDownloader.d.ts +24 -0
  160. package/lib/typescript/module/native/NativeChatSdkDownloader.d.ts.map +1 -0
  161. package/lib/typescript/module/native/NativeChatSdkFilePicker.d.ts +17 -0
  162. package/lib/typescript/module/native/NativeChatSdkFilePicker.d.ts.map +1 -0
  163. package/lib/typescript/module/package.json +1 -0
  164. package/lib/typescript/module/realtime.d.ts +42 -0
  165. package/lib/typescript/module/realtime.d.ts.map +1 -0
  166. package/lib/typescript/module/safeArea.d.ts +4 -0
  167. package/lib/typescript/module/safeArea.d.ts.map +1 -0
  168. package/lib/typescript/module/session.d.ts +45 -0
  169. package/lib/typescript/module/session.d.ts.map +1 -0
  170. package/lib/typescript/module/surveyCache.d.ts +5 -0
  171. package/lib/typescript/module/surveyCache.d.ts.map +1 -0
  172. package/lib/typescript/module/theme.d.ts +21 -0
  173. package/lib/typescript/module/theme.d.ts.map +1 -0
  174. package/lib/typescript/module/types.d.ts +156 -0
  175. package/lib/typescript/module/types.d.ts.map +1 -0
  176. package/lib/typescript/module/useChat.d.ts +16 -0
  177. package/lib/typescript/module/useChat.d.ts.map +1 -0
  178. package/package.json +75 -0
  179. package/react-native.config.js +10 -0
  180. package/src/ChatSDK.ts +237 -0
  181. package/src/api.ts +228 -0
  182. package/src/attachmentUtils.ts +49 -0
  183. package/src/components/AttachmentGallery.tsx +363 -0
  184. package/src/components/ChatScreen.tsx +267 -0
  185. package/src/components/MessageBubble.tsx +208 -0
  186. package/src/components/MessageInput.tsx +280 -0
  187. package/src/components/SurveyOverlay.tsx +469 -0
  188. package/src/downloaders/defaultAttachmentDownloader.ts +27 -0
  189. package/src/filePicker.ts +22 -0
  190. package/src/index.ts +30 -0
  191. package/src/native/NativeChatSdkDownloader.ts +49 -0
  192. package/src/native/NativeChatSdkFilePicker.ts +30 -0
  193. package/src/realtime.ts +278 -0
  194. package/src/safeArea.ts +8 -0
  195. package/src/session.ts +196 -0
  196. package/src/surveyCache.ts +24 -0
  197. package/src/theme.ts +49 -0
  198. package/src/types.ts +199 -0
  199. package/src/useChat.ts +190 -0
package/src/types.ts ADDED
@@ -0,0 +1,199 @@
1
+ // Конфигурация при инициализации SDK.
2
+ // token может быть либо plain-строкой (тогда baseUrl обязателен),
3
+ // либо base64-закодированным JSON вида {"token":"...","baseUrl":"..."}.
4
+ export interface ChatSDKConfig {
5
+ token: string
6
+ baseUrl?: string
7
+ locale?: 'ru' | 'en'
8
+ }
9
+
10
+ // Данные пользователя при логине
11
+ export interface ChatSDKUser {
12
+ userId: string
13
+ name?: string
14
+ surname?: string
15
+ email?: string
16
+ phone?: string
17
+ }
18
+
19
+ // Данные устройства (опционально)
20
+ export interface ChatSDKDevice {
21
+ platform?: 'ios' | 'android' | 'other'
22
+ appVersion?: string
23
+ bundleId?: string
24
+ }
25
+
26
+ // Тема виджета из ЧП
27
+ export interface ChatSDKTheme {
28
+ widgetTitle: string
29
+ colorType: 'single' | 'gradient'
30
+ themeColor: string
31
+ colorStart: string | null
32
+ colorEnd: string | null
33
+ imageLogo: string | null
34
+ }
35
+
36
+ // Параметры Reverb из ответа /session
37
+ export interface ReverbConfig {
38
+ key: string
39
+ host: string
40
+ port: number
41
+ scheme: 'http' | 'https'
42
+ }
43
+
44
+ // Полный конфиг из /session или /config
45
+ export interface MobileConfig extends ChatSDKTheme {
46
+ apiBaseUrl: string
47
+ reverb: ReverbConfig
48
+ broadcastAuthEndpoint: string
49
+ }
50
+
51
+ // Ответ POST /session
52
+ export interface SessionResponse {
53
+ sessionToken: string
54
+ expiresAt: string
55
+ contactId: string
56
+ config: MobileConfig
57
+ }
58
+
59
+ // Одно сообщение в чате
60
+ export interface ChatMessage {
61
+ id: string
62
+ type: 'contact' | 'user' | 'system' | 'event'
63
+ text: string | null
64
+ time: string
65
+ createdAt: number
66
+ sender?: { name: string } | null
67
+ attachments?: ChatAttachment[]
68
+ buttons?: ChatButton[] | null
69
+ serverMessageId?: number | null
70
+ }
71
+
72
+ // Вложение
73
+ export interface ChatAttachment {
74
+ id: number
75
+ url: string
76
+ filename: string
77
+ mime: string
78
+ size: number
79
+ type: 'image' | 'video' | 'audio' | 'document'
80
+ }
81
+
82
+ // Файл для отправки из SDK (используется хост-приложением)
83
+ export interface AttachmentInput {
84
+ uri: string
85
+ name: string
86
+ type: string
87
+ size?: number
88
+ }
89
+
90
+ // Вложение с контекстом сообщения (для галереи)
91
+ export interface GalleryAttachment extends ChatAttachment {
92
+ messageTime: string
93
+ }
94
+
95
+ // Кнопка бота
96
+ export interface ChatButton {
97
+ text: string
98
+ callback_data: string
99
+ }
100
+
101
+ // Ответ GET /messages
102
+ export interface MessagesResponse {
103
+ operator: { id: number; name: string } | null
104
+ messages: ChatMessage[]
105
+ }
106
+
107
+ export interface ChatOperator {
108
+ id: number
109
+ name: string
110
+ }
111
+
112
+ // Payload для handleNotification (из push)
113
+ export interface NotificationPayload {
114
+ token: string
115
+ contactId: string
116
+ conversationId?: string
117
+ messageId?: string
118
+ }
119
+
120
+ export interface OperatorChangedEventPayload {
121
+ token: string
122
+ contactId: string
123
+ previousOperator: ChatOperator | null
124
+ operator: ChatOperator | null
125
+ occurredAt: string
126
+ }
127
+
128
+ export interface NewMessageEventPayload {
129
+ token: string
130
+ contactId: string
131
+ message: ChatMessage
132
+ operator: ChatOperator | null
133
+ occurredAt: string
134
+ }
135
+
136
+ export interface MessagesUpdatedEventPayload {
137
+ messages: ChatMessage[]
138
+ operator: ChatOperator | null
139
+ }
140
+
141
+ export type ChatSDKEventName =
142
+ | 'stateChange'
143
+ | 'error'
144
+ | 'operatorChanged'
145
+ | 'newMessage'
146
+ | 'messagesUpdated'
147
+ | 'connectedChange'
148
+
149
+ // Конфиг CSI-опроса с сервера
150
+ export interface SurveyConfig {
151
+ visible: true
152
+ title: string
153
+ description: string
154
+ range: [number, number]
155
+ badRange: [number, number]
156
+ badMessage: string
157
+ goodMessage: string
158
+ afterCommentMessage: string
159
+ commentEnabled: boolean
160
+ badRangeEnabled: boolean
161
+ commentRequired: boolean
162
+ }
163
+
164
+ export type SurveyConfigResponse = SurveyConfig | { visible: false }
165
+
166
+ // Внутреннее состояние SDK
167
+ export type SDKState =
168
+ | 'idle'
169
+ | 'initializing'
170
+ | 'ready'
171
+ | 'authenticated'
172
+ | 'error'
173
+
174
+ /**
175
+ * Кастомные тексты для <ChatScreen />.
176
+ * Все поля опциональны — непереданные значения берутся из встроенного дефолта.
177
+ */
178
+ export interface ChatStrings {
179
+ /** Заголовок хедера. По умолчанию: widgetTitle из конфига → 'Чат' */
180
+ headerTitle?: string
181
+ /** Текст пустого экрана чата */
182
+ emptyStateText?: string
183
+ /** Placeholder в поле ввода */
184
+ inputPlaceholder?: string
185
+ /** Текст кнопки/индикатора отправки */
186
+ sendingText?: string
187
+ /** Текст кнопки повтора при ошибке */
188
+ errorRetry?: string
189
+ /** Заголовок оверлея CSI-опроса (если не задан сервером) */
190
+ surveyTitle?: string
191
+ /** Текст кнопки отправки оценки в опросе */
192
+ surveySubmit?: string
193
+ /** Текст кнопки пропуска опроса */
194
+ surveySkip?: string
195
+ /** Текст кнопки закрытия результата опроса */
196
+ surveyClose?: string
197
+ /** Текст кнопки скачивания в галерее */
198
+ galleryDownload?: string
199
+ }
package/src/useChat.ts ADDED
@@ -0,0 +1,190 @@
1
+ import { useCallback, useEffect, useRef, useState } from 'react'
2
+ import { ChatSDK } from './ChatSDK'
3
+ import { isSurveyEventSeen, markSurveyEventSeen, deleteSurveyEvent } from './surveyCache'
4
+ import type {
5
+ AttachmentInput,
6
+ ChatAttachment,
7
+ ChatMessage,
8
+ SurveyConfig,
9
+ } from './types'
10
+
11
+ export interface UseChatReturn {
12
+ messages: ChatMessage[]
13
+ isLoading: boolean
14
+ isSending: boolean
15
+ connected: boolean
16
+ error: string | null
17
+ survey: SurveyConfig | null
18
+ sendMessage: (text: string, files?: AttachmentInput[]) => Promise<void>
19
+ sendCallback: (messageId: number, callbackData: string) => Promise<void>
20
+ submitSurvey: (rating: number, comment?: string) => Promise<void>
21
+ dismissSurvey: () => void
22
+ retry: () => void
23
+ }
24
+
25
+ function filterVisible(all: ChatMessage[]): ChatMessage[] {
26
+ return all.filter((m) => !(m.type === 'event' && m.text === 'status_changed'))
27
+ }
28
+
29
+ export function useChat(): UseChatReturn {
30
+ const [messages, setMessages] = useState<ChatMessage[]>(() => filterVisible(ChatSDK.getMessages()))
31
+ const [isLoading, setIsLoading] = useState(ChatSDK.getMessages().length === 0)
32
+ const [isSending, setIsSending] = useState(false)
33
+ const [connected, setConnected] = useState(ChatSDK.isRealtimeConnected())
34
+ const [error, setError] = useState<string | null>(null)
35
+ const [survey, setSurvey] = useState<SurveyConfig | null>(null)
36
+
37
+ const api = ChatSDK.getApi()
38
+ const lastCloseEventRef = useRef<string | null>(null)
39
+
40
+ const checkSurvey = useCallback(
41
+ async (eventMessageId: string, closeEventCreatedAt?: number) => {
42
+ const contactId = api.getContactId()
43
+ if (!contactId) return
44
+ if (isSurveyEventSeen(contactId, eventMessageId)) return
45
+
46
+ markSurveyEventSeen(contactId, eventMessageId)
47
+
48
+ try {
49
+ const cfg = await api.getSurveyConfig(closeEventCreatedAt)
50
+ if (cfg.visible) {
51
+ setSurvey(cfg as SurveyConfig)
52
+ }
53
+ } catch {
54
+ deleteSurveyEvent(contactId, eventMessageId)
55
+ }
56
+ },
57
+ [api],
58
+ )
59
+
60
+ useEffect(() => {
61
+ const unsubMessages = ChatSDK.on('messagesUpdated', ({ messages: all }) => {
62
+ setMessages(filterVisible(all))
63
+ setIsLoading(false)
64
+ setError(null)
65
+
66
+ const closeEvent = all.findLast(
67
+ (m) => m.type === 'event' && m.text === 'status_changed',
68
+ )
69
+ if (closeEvent && closeEvent.id !== lastCloseEventRef.current) {
70
+ lastCloseEventRef.current = closeEvent.id
71
+ void checkSurvey(closeEvent.id, closeEvent.createdAt)
72
+ }
73
+ })
74
+
75
+ const unsubConnected = ChatSDK.on('connectedChange', (value) => setConnected(value))
76
+
77
+ return () => {
78
+ unsubMessages()
79
+ unsubConnected()
80
+ }
81
+ }, [checkSurvey])
82
+
83
+ useEffect(() => {
84
+ void (async () => {
85
+ try {
86
+ const user = ChatSDK.getUser()
87
+ await api.startDialog({
88
+ name: user?.name,
89
+ surname: user?.surname,
90
+ email: user?.email,
91
+ phone: user?.phone,
92
+ })
93
+ } catch (e) {
94
+ console.warn('ChatSDK: startDialog failed (non-fatal):', e)
95
+ }
96
+ try {
97
+ await ChatSDK.refreshMessages()
98
+ } catch (e) {
99
+ setError(e instanceof Error ? e.message : 'Ошибка загрузки')
100
+ setIsLoading(false)
101
+ }
102
+ })()
103
+ }, [api])
104
+
105
+ const sendMessage = useCallback(
106
+ async (text: string, files?: AttachmentInput[]) => {
107
+ if (!text.trim() && (!files || files.length === 0)) return
108
+ setIsSending(true)
109
+ setError(null)
110
+
111
+ const tempId = `temp-${Date.now()}`
112
+ const tempAttachments: ChatAttachment[] = (files ?? []).map((f, i) => ({
113
+ id: -(i + 1),
114
+ url: f.uri,
115
+ filename: f.name,
116
+ mime: f.type,
117
+ size: f.size ?? 0,
118
+ type: f.type.startsWith('image/') ? 'image'
119
+ : f.type.startsWith('video/') ? 'video'
120
+ : f.type.startsWith('audio/') ? 'audio'
121
+ : 'document',
122
+ }))
123
+
124
+ const tempMsg: ChatMessage = {
125
+ id: tempId,
126
+ type: 'contact',
127
+ text: text.trim() || null,
128
+ time: new Date().toLocaleTimeString('ru', { hour: '2-digit', minute: '2-digit' }),
129
+ createdAt: Date.now(),
130
+ attachments: tempAttachments,
131
+ }
132
+ setMessages((prev) => [...prev, tempMsg])
133
+
134
+ try {
135
+ await api.sendMessage(text, files)
136
+ await ChatSDK.refreshMessages()
137
+ } catch (e) {
138
+ setError(e instanceof Error ? e.message : 'Ошибка отправки')
139
+ setMessages((prev) => prev.filter((m) => m.id !== tempId))
140
+ } finally {
141
+ setIsSending(false)
142
+ }
143
+ },
144
+ [api],
145
+ )
146
+
147
+ const sendCallback = useCallback(
148
+ async (messageId: number, callbackData: string) => {
149
+ try {
150
+ await api.sendCallback(messageId, callbackData)
151
+ await ChatSDK.refreshMessages()
152
+ } catch (e) {
153
+ setError(e instanceof Error ? e.message : 'Ошибка отправки')
154
+ }
155
+ },
156
+ [api],
157
+ )
158
+
159
+ const submitSurvey = useCallback(
160
+ async (rating: number, comment?: string) => {
161
+ await api.submitCsi(rating, comment)
162
+ },
163
+ [api],
164
+ )
165
+
166
+ const dismissSurvey = useCallback(() => setSurvey(null), [])
167
+
168
+ const retry = useCallback(() => {
169
+ setIsLoading(true)
170
+ setError(null)
171
+ void ChatSDK.refreshMessages().catch((e) => {
172
+ setError(e instanceof Error ? e.message : 'Ошибка загрузки')
173
+ setIsLoading(false)
174
+ })
175
+ }, [])
176
+
177
+ return {
178
+ messages,
179
+ isLoading,
180
+ isSending,
181
+ connected,
182
+ error,
183
+ survey,
184
+ sendMessage,
185
+ sendCallback,
186
+ submitSurvey,
187
+ dismissSurvey,
188
+ retry,
189
+ }
190
+ }