@mobileai/react-native 0.9.16 → 0.9.18

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 (214) hide show
  1. package/README.md +2 -2
  2. package/package.json +5 -8
  3. package/lib/module/__cli_tmp__.js.map +0 -1
  4. package/lib/module/components/AIAgent.js.map +0 -1
  5. package/lib/module/components/AIZone.js.map +0 -1
  6. package/lib/module/components/AgentChatBar.js.map +0 -1
  7. package/lib/module/components/AgentErrorBoundary.js.map +0 -1
  8. package/lib/module/components/AgentOverlay.js.map +0 -1
  9. package/lib/module/components/DiscoveryTooltip.js.map +0 -1
  10. package/lib/module/components/HighlightOverlay.js.map +0 -1
  11. package/lib/module/components/Icons.js.map +0 -1
  12. package/lib/module/components/ProactiveHint.js.map +0 -1
  13. package/lib/module/components/cards/InfoCard.js.map +0 -1
  14. package/lib/module/components/cards/ReviewSummary.js.map +0 -1
  15. package/lib/module/config/endpoints.js.map +0 -1
  16. package/lib/module/core/ActionRegistry.js.map +0 -1
  17. package/lib/module/core/AgentRuntime.js.map +0 -1
  18. package/lib/module/core/FiberTreeWalker.js.map +0 -1
  19. package/lib/module/core/IdleDetector.js.map +0 -1
  20. package/lib/module/core/MCPBridge.js.map +0 -1
  21. package/lib/module/core/ScreenDehydrator.js.map +0 -1
  22. package/lib/module/core/ZoneRegistry.js.map +0 -1
  23. package/lib/module/core/systemPrompt.js.map +0 -1
  24. package/lib/module/core/types.js.map +0 -1
  25. package/lib/module/hooks/useAction.js.map +0 -1
  26. package/lib/module/index.js.map +0 -1
  27. package/lib/module/plugin/withAppIntents.js.map +0 -1
  28. package/lib/module/providers/GeminiProvider.js.map +0 -1
  29. package/lib/module/providers/OpenAIProvider.js.map +0 -1
  30. package/lib/module/providers/ProviderFactory.js.map +0 -1
  31. package/lib/module/services/AudioInputService.js.map +0 -1
  32. package/lib/module/services/AudioOutputService.js.map +0 -1
  33. package/lib/module/services/KnowledgeBaseService.js.map +0 -1
  34. package/lib/module/services/VoiceService.js.map +0 -1
  35. package/lib/module/services/flags/FlagService.js.map +0 -1
  36. package/lib/module/services/telemetry/MobileAI.js.map +0 -1
  37. package/lib/module/services/telemetry/PiiScrubber.js.map +0 -1
  38. package/lib/module/services/telemetry/TelemetryService.js.map +0 -1
  39. package/lib/module/services/telemetry/TouchAutoCapture.js.map +0 -1
  40. package/lib/module/services/telemetry/device.js.map +0 -1
  41. package/lib/module/services/telemetry/deviceMetadata.js.map +0 -1
  42. package/lib/module/services/telemetry/index.js.map +0 -1
  43. package/lib/module/services/telemetry/types.js.map +0 -1
  44. package/lib/module/support/CSATSurvey.js.map +0 -1
  45. package/lib/module/support/EscalationEventSource.js.map +0 -1
  46. package/lib/module/support/EscalationSocket.js.map +0 -1
  47. package/lib/module/support/SupportChatModal.js.map +0 -1
  48. package/lib/module/support/SupportGreeting.js.map +0 -1
  49. package/lib/module/support/TicketStore.js.map +0 -1
  50. package/lib/module/support/escalateTool.js.map +0 -1
  51. package/lib/module/support/index.js.map +0 -1
  52. package/lib/module/support/supportPrompt.js.map +0 -1
  53. package/lib/module/support/types.js.map +0 -1
  54. package/lib/module/tools/datePickerTool.js.map +0 -1
  55. package/lib/module/tools/guideTool.js.map +0 -1
  56. package/lib/module/tools/index.js.map +0 -1
  57. package/lib/module/tools/keyboardTool.js.map +0 -1
  58. package/lib/module/tools/longPressTool.js.map +0 -1
  59. package/lib/module/tools/pickerTool.js.map +0 -1
  60. package/lib/module/tools/restoreTool.js.map +0 -1
  61. package/lib/module/tools/scrollTool.js.map +0 -1
  62. package/lib/module/tools/simplifyTool.js.map +0 -1
  63. package/lib/module/tools/sliderTool.js.map +0 -1
  64. package/lib/module/tools/tapTool.js.map +0 -1
  65. package/lib/module/tools/typeTool.js.map +0 -1
  66. package/lib/module/tools/types.js.map +0 -1
  67. package/lib/module/types/jsx.d.js.map +0 -1
  68. package/lib/module/utils/audioUtils.js.map +0 -1
  69. package/lib/module/utils/logger.js.map +0 -1
  70. package/lib/typescript/babel.config.d.ts.map +0 -1
  71. package/lib/typescript/bin/generate-map.d.cts.map +0 -1
  72. package/lib/typescript/eslint.config.d.mts.map +0 -1
  73. package/lib/typescript/generate-map.d.ts.map +0 -1
  74. package/lib/typescript/src/__cli_tmp__.d.ts.map +0 -1
  75. package/lib/typescript/src/components/AIAgent.d.ts.map +0 -1
  76. package/lib/typescript/src/components/AIZone.d.ts.map +0 -1
  77. package/lib/typescript/src/components/AgentChatBar.d.ts.map +0 -1
  78. package/lib/typescript/src/components/AgentErrorBoundary.d.ts.map +0 -1
  79. package/lib/typescript/src/components/AgentOverlay.d.ts.map +0 -1
  80. package/lib/typescript/src/components/DiscoveryTooltip.d.ts.map +0 -1
  81. package/lib/typescript/src/components/HighlightOverlay.d.ts.map +0 -1
  82. package/lib/typescript/src/components/Icons.d.ts.map +0 -1
  83. package/lib/typescript/src/components/ProactiveHint.d.ts.map +0 -1
  84. package/lib/typescript/src/components/cards/InfoCard.d.ts.map +0 -1
  85. package/lib/typescript/src/components/cards/ReviewSummary.d.ts.map +0 -1
  86. package/lib/typescript/src/config/endpoints.d.ts.map +0 -1
  87. package/lib/typescript/src/core/ActionRegistry.d.ts.map +0 -1
  88. package/lib/typescript/src/core/AgentRuntime.d.ts.map +0 -1
  89. package/lib/typescript/src/core/FiberTreeWalker.d.ts.map +0 -1
  90. package/lib/typescript/src/core/IdleDetector.d.ts.map +0 -1
  91. package/lib/typescript/src/core/MCPBridge.d.ts.map +0 -1
  92. package/lib/typescript/src/core/ScreenDehydrator.d.ts.map +0 -1
  93. package/lib/typescript/src/core/ZoneRegistry.d.ts.map +0 -1
  94. package/lib/typescript/src/core/systemPrompt.d.ts.map +0 -1
  95. package/lib/typescript/src/core/types.d.ts.map +0 -1
  96. package/lib/typescript/src/hooks/useAction.d.ts.map +0 -1
  97. package/lib/typescript/src/index.d.ts.map +0 -1
  98. package/lib/typescript/src/plugin/withAppIntents.d.ts.map +0 -1
  99. package/lib/typescript/src/providers/GeminiProvider.d.ts.map +0 -1
  100. package/lib/typescript/src/providers/OpenAIProvider.d.ts.map +0 -1
  101. package/lib/typescript/src/providers/ProviderFactory.d.ts.map +0 -1
  102. package/lib/typescript/src/services/AudioInputService.d.ts.map +0 -1
  103. package/lib/typescript/src/services/AudioOutputService.d.ts.map +0 -1
  104. package/lib/typescript/src/services/KnowledgeBaseService.d.ts.map +0 -1
  105. package/lib/typescript/src/services/VoiceService.d.ts.map +0 -1
  106. package/lib/typescript/src/services/flags/FlagService.d.ts.map +0 -1
  107. package/lib/typescript/src/services/telemetry/MobileAI.d.ts.map +0 -1
  108. package/lib/typescript/src/services/telemetry/PiiScrubber.d.ts.map +0 -1
  109. package/lib/typescript/src/services/telemetry/TelemetryService.d.ts.map +0 -1
  110. package/lib/typescript/src/services/telemetry/TouchAutoCapture.d.ts.map +0 -1
  111. package/lib/typescript/src/services/telemetry/device.d.ts.map +0 -1
  112. package/lib/typescript/src/services/telemetry/deviceMetadata.d.ts.map +0 -1
  113. package/lib/typescript/src/services/telemetry/index.d.ts.map +0 -1
  114. package/lib/typescript/src/services/telemetry/types.d.ts.map +0 -1
  115. package/lib/typescript/src/support/CSATSurvey.d.ts.map +0 -1
  116. package/lib/typescript/src/support/EscalationEventSource.d.ts.map +0 -1
  117. package/lib/typescript/src/support/EscalationSocket.d.ts.map +0 -1
  118. package/lib/typescript/src/support/SupportChatModal.d.ts.map +0 -1
  119. package/lib/typescript/src/support/SupportGreeting.d.ts.map +0 -1
  120. package/lib/typescript/src/support/TicketStore.d.ts.map +0 -1
  121. package/lib/typescript/src/support/escalateTool.d.ts.map +0 -1
  122. package/lib/typescript/src/support/index.d.ts.map +0 -1
  123. package/lib/typescript/src/support/supportPrompt.d.ts.map +0 -1
  124. package/lib/typescript/src/support/types.d.ts.map +0 -1
  125. package/lib/typescript/src/tools/datePickerTool.d.ts.map +0 -1
  126. package/lib/typescript/src/tools/guideTool.d.ts.map +0 -1
  127. package/lib/typescript/src/tools/index.d.ts.map +0 -1
  128. package/lib/typescript/src/tools/keyboardTool.d.ts.map +0 -1
  129. package/lib/typescript/src/tools/longPressTool.d.ts.map +0 -1
  130. package/lib/typescript/src/tools/pickerTool.d.ts.map +0 -1
  131. package/lib/typescript/src/tools/restoreTool.d.ts.map +0 -1
  132. package/lib/typescript/src/tools/scrollTool.d.ts.map +0 -1
  133. package/lib/typescript/src/tools/simplifyTool.d.ts.map +0 -1
  134. package/lib/typescript/src/tools/sliderTool.d.ts.map +0 -1
  135. package/lib/typescript/src/tools/tapTool.d.ts.map +0 -1
  136. package/lib/typescript/src/tools/typeTool.d.ts.map +0 -1
  137. package/lib/typescript/src/tools/types.d.ts.map +0 -1
  138. package/lib/typescript/src/utils/audioUtils.d.ts.map +0 -1
  139. package/lib/typescript/src/utils/logger.d.ts.map +0 -1
  140. package/src/__cli_tmp__.tsx +0 -9
  141. package/src/cli/analyzers/chain-analyzer.ts +0 -183
  142. package/src/cli/extractors/ai-extractor.ts +0 -6
  143. package/src/cli/extractors/ast-extractor.ts +0 -551
  144. package/src/cli/generate-intents.ts +0 -140
  145. package/src/cli/generate-map.ts +0 -121
  146. package/src/cli/generate-swift.ts +0 -116
  147. package/src/cli/scanners/expo-scanner.ts +0 -203
  148. package/src/cli/scanners/rn-scanner.ts +0 -445
  149. package/src/components/AIAgent.tsx +0 -1716
  150. package/src/components/AIZone.tsx +0 -147
  151. package/src/components/AgentChatBar.tsx +0 -1143
  152. package/src/components/AgentErrorBoundary.tsx +0 -78
  153. package/src/components/AgentOverlay.tsx +0 -73
  154. package/src/components/DiscoveryTooltip.tsx +0 -148
  155. package/src/components/HighlightOverlay.tsx +0 -136
  156. package/src/components/Icons.tsx +0 -253
  157. package/src/components/ProactiveHint.tsx +0 -145
  158. package/src/components/cards/InfoCard.tsx +0 -58
  159. package/src/components/cards/ReviewSummary.tsx +0 -76
  160. package/src/config/endpoints.ts +0 -22
  161. package/src/core/ActionRegistry.ts +0 -105
  162. package/src/core/AgentRuntime.ts +0 -1471
  163. package/src/core/FiberTreeWalker.ts +0 -930
  164. package/src/core/IdleDetector.ts +0 -72
  165. package/src/core/MCPBridge.ts +0 -163
  166. package/src/core/ScreenDehydrator.ts +0 -53
  167. package/src/core/ZoneRegistry.ts +0 -44
  168. package/src/core/systemPrompt.ts +0 -431
  169. package/src/core/types.ts +0 -521
  170. package/src/hooks/useAction.ts +0 -182
  171. package/src/index.ts +0 -83
  172. package/src/plugin/withAppIntents.ts +0 -98
  173. package/src/providers/GeminiProvider.ts +0 -357
  174. package/src/providers/OpenAIProvider.ts +0 -379
  175. package/src/providers/ProviderFactory.ts +0 -36
  176. package/src/services/AudioInputService.ts +0 -226
  177. package/src/services/AudioOutputService.ts +0 -236
  178. package/src/services/KnowledgeBaseService.ts +0 -156
  179. package/src/services/VoiceService.ts +0 -451
  180. package/src/services/flags/FlagService.ts +0 -137
  181. package/src/services/telemetry/MobileAI.ts +0 -66
  182. package/src/services/telemetry/PiiScrubber.ts +0 -17
  183. package/src/services/telemetry/TelemetryService.ts +0 -323
  184. package/src/services/telemetry/TouchAutoCapture.ts +0 -165
  185. package/src/services/telemetry/device.ts +0 -93
  186. package/src/services/telemetry/deviceMetadata.ts +0 -13
  187. package/src/services/telemetry/index.ts +0 -13
  188. package/src/services/telemetry/types.ts +0 -75
  189. package/src/support/CSATSurvey.tsx +0 -304
  190. package/src/support/EscalationEventSource.ts +0 -190
  191. package/src/support/EscalationSocket.ts +0 -152
  192. package/src/support/SupportChatModal.tsx +0 -563
  193. package/src/support/SupportGreeting.tsx +0 -161
  194. package/src/support/TicketStore.ts +0 -100
  195. package/src/support/escalateTool.ts +0 -174
  196. package/src/support/index.ts +0 -29
  197. package/src/support/supportPrompt.ts +0 -55
  198. package/src/support/types.ts +0 -155
  199. package/src/tools/datePickerTool.ts +0 -60
  200. package/src/tools/guideTool.ts +0 -76
  201. package/src/tools/index.ts +0 -20
  202. package/src/tools/keyboardTool.ts +0 -30
  203. package/src/tools/longPressTool.ts +0 -61
  204. package/src/tools/pickerTool.ts +0 -115
  205. package/src/tools/restoreTool.ts +0 -33
  206. package/src/tools/scrollTool.ts +0 -156
  207. package/src/tools/simplifyTool.ts +0 -33
  208. package/src/tools/sliderTool.ts +0 -65
  209. package/src/tools/tapTool.ts +0 -93
  210. package/src/tools/typeTool.ts +0 -113
  211. package/src/tools/types.ts +0 -58
  212. package/src/types/jsx.d.ts +0 -20
  213. package/src/utils/audioUtils.ts +0 -54
  214. package/src/utils/logger.ts +0 -38
@@ -1,304 +0,0 @@
1
- /**
2
- * CSAT Survey — Customer Satisfaction component.
3
- *
4
- * Shown after a support conversation ends (or after idle timeout).
5
- * Supports three rating types: emoji, stars, thumbs.
6
- */
7
-
8
-
9
- import { useState } from 'react';
10
- import {
11
- View,
12
- Text,
13
- TouchableOpacity,
14
- TextInput,
15
- StyleSheet,
16
- } from 'react-native';
17
- import type { CSATConfig, CSATRating } from './types';
18
-
19
- interface CSATSurveyProps {
20
- config: CSATConfig;
21
- metadata: CSATRating['metadata'];
22
- onDismiss: () => void;
23
- theme?: {
24
- primaryColor?: string;
25
- textColor?: string;
26
- backgroundColor?: string;
27
- };
28
- }
29
-
30
- const EMOJI_OPTIONS = [
31
- { emoji: '😡', label: 'Terrible', score: 1 },
32
- { emoji: '😞', label: 'Bad', score: 2 },
33
- { emoji: '😐', label: 'Okay', score: 3 },
34
- { emoji: '😊', label: 'Good', score: 4 },
35
- { emoji: '🤩', label: 'Amazing', score: 5 },
36
- ];
37
-
38
- const STAR_COUNT = 5;
39
-
40
- export function CSATSurvey({
41
- config,
42
- metadata,
43
- onDismiss,
44
- theme,
45
- }: CSATSurveyProps) {
46
- const [selectedScore, setSelectedScore] = useState<number | null>(null);
47
- const [feedback, setFeedback] = useState('');
48
- const [submitted, setSubmitted] = useState(false);
49
-
50
- const primary = theme?.primaryColor ?? '#8b5cf6';
51
- const textColor = theme?.textColor ?? '#ffffff';
52
- const bgColor = theme?.backgroundColor ?? 'rgba(26, 26, 46, 0.98)';
53
- const ratingType = config.ratingType ?? 'emoji';
54
- const question = config.question ?? 'How was your experience?';
55
-
56
- const handleSubmit = () => {
57
- if (selectedScore === null) return;
58
-
59
- const rating: CSATRating = {
60
- score: selectedScore,
61
- feedback: feedback.trim() || undefined,
62
- metadata,
63
- };
64
-
65
- config.onSubmit(rating);
66
- setSubmitted(true);
67
-
68
- // Auto-dismiss after 1.5s
69
- setTimeout(onDismiss, 1500);
70
- };
71
-
72
- if (submitted) {
73
- return (
74
- <View style={[styles.container, { backgroundColor: bgColor }]}>
75
- <Text style={[styles.thankYou, { color: textColor }]}>
76
- Thank you for your feedback! 🙏
77
- </Text>
78
- </View>
79
- );
80
- }
81
-
82
- return (
83
- <View style={[styles.container, { backgroundColor: bgColor }]}>
84
- {/* Question */}
85
- <Text style={[styles.question, { color: textColor }]}>{question}</Text>
86
-
87
- {/* Rating selector */}
88
- <View style={styles.ratingContainer}>
89
- {ratingType === 'emoji' && (
90
- <View style={styles.emojiRow}>
91
- {EMOJI_OPTIONS.map((opt) => (
92
- <TouchableOpacity
93
- key={opt.score}
94
- onPress={() => setSelectedScore(opt.score)}
95
- style={[
96
- styles.emojiButton,
97
- selectedScore === opt.score && {
98
- backgroundColor: `${primary}30`,
99
- borderColor: primary,
100
- },
101
- ]}
102
- activeOpacity={0.7}
103
- >
104
- <Text style={styles.emoji}>{opt.emoji}</Text>
105
- <Text
106
- style={[
107
- styles.emojiLabel,
108
- { color: selectedScore === opt.score ? primary : '#71717a' },
109
- ]}
110
- >
111
- {opt.label}
112
- </Text>
113
- </TouchableOpacity>
114
- ))}
115
- </View>
116
- )}
117
-
118
- {ratingType === 'stars' && (
119
- <View style={styles.starsRow}>
120
- {Array.from({ length: STAR_COUNT }, (_, i) => i + 1).map(
121
- (star) => (
122
- <TouchableOpacity
123
- key={star}
124
- onPress={() => setSelectedScore(star)}
125
- activeOpacity={0.7}
126
- >
127
- <Text
128
- style={[
129
- styles.star,
130
- {
131
- color:
132
- selectedScore !== null && star <= selectedScore
133
- ? '#fbbf24'
134
- : '#52525b',
135
- },
136
- ]}
137
- >
138
-
139
- </Text>
140
- </TouchableOpacity>
141
- )
142
- )}
143
- </View>
144
- )}
145
-
146
- {ratingType === 'thumbs' && (
147
- <View style={styles.thumbsRow}>
148
- <TouchableOpacity
149
- onPress={() => setSelectedScore(0)}
150
- style={[
151
- styles.thumbButton,
152
- selectedScore === 0 && {
153
- backgroundColor: '#ef444430',
154
- borderColor: '#ef4444',
155
- },
156
- ]}
157
- activeOpacity={0.7}
158
- >
159
- <Text style={styles.thumbEmoji}>👎</Text>
160
- </TouchableOpacity>
161
- <TouchableOpacity
162
- onPress={() => setSelectedScore(1)}
163
- style={[
164
- styles.thumbButton,
165
- selectedScore === 1 && {
166
- backgroundColor: '#22c55e30',
167
- borderColor: '#22c55e',
168
- },
169
- ]}
170
- activeOpacity={0.7}
171
- >
172
- <Text style={styles.thumbEmoji}>👍</Text>
173
- </TouchableOpacity>
174
- </View>
175
- )}
176
- </View>
177
-
178
- {/* Optional feedback text */}
179
- {selectedScore !== null && (
180
- <TextInput
181
- style={[styles.feedbackInput, { color: textColor }]}
182
- placeholder="Any additional feedback? (optional)"
183
- placeholderTextColor="#52525b"
184
- value={feedback}
185
- onChangeText={setFeedback}
186
- multiline
187
- maxLength={500}
188
- />
189
- )}
190
-
191
- {/* Actions */}
192
- <View style={styles.actions}>
193
- <TouchableOpacity onPress={onDismiss} activeOpacity={0.7}>
194
- <Text style={styles.dismissText}>Skip</Text>
195
- </TouchableOpacity>
196
- {selectedScore !== null && (
197
- <TouchableOpacity
198
- style={[styles.submitButton, { backgroundColor: primary }]}
199
- onPress={handleSubmit}
200
- activeOpacity={0.7}
201
- >
202
- <Text style={[styles.submitText, { color: textColor }]}>
203
- Submit
204
- </Text>
205
- </TouchableOpacity>
206
- )}
207
- </View>
208
- </View>
209
- );
210
- }
211
-
212
- const styles = StyleSheet.create({
213
- container: {
214
- borderRadius: 16,
215
- padding: 20,
216
- margin: 12,
217
- },
218
- question: {
219
- fontSize: 16,
220
- fontWeight: '600',
221
- textAlign: 'center',
222
- marginBottom: 16,
223
- },
224
- thankYou: {
225
- fontSize: 16,
226
- fontWeight: '500',
227
- textAlign: 'center',
228
- paddingVertical: 12,
229
- },
230
- ratingContainer: {
231
- marginBottom: 12,
232
- },
233
- emojiRow: {
234
- flexDirection: 'row',
235
- justifyContent: 'center',
236
- gap: 8,
237
- },
238
- emojiButton: {
239
- alignItems: 'center',
240
- paddingHorizontal: 10,
241
- paddingVertical: 8,
242
- borderRadius: 12,
243
- borderWidth: 1,
244
- borderColor: 'transparent',
245
- },
246
- emoji: {
247
- fontSize: 28,
248
- },
249
- emojiLabel: {
250
- fontSize: 10,
251
- marginTop: 4,
252
- fontWeight: '500',
253
- },
254
- starsRow: {
255
- flexDirection: 'row',
256
- justifyContent: 'center',
257
- gap: 8,
258
- },
259
- star: {
260
- fontSize: 36,
261
- },
262
- thumbsRow: {
263
- flexDirection: 'row',
264
- justifyContent: 'center',
265
- gap: 20,
266
- },
267
- thumbButton: {
268
- padding: 12,
269
- borderRadius: 16,
270
- borderWidth: 1,
271
- borderColor: 'transparent',
272
- },
273
- thumbEmoji: {
274
- fontSize: 36,
275
- },
276
- feedbackInput: {
277
- borderWidth: 1,
278
- borderColor: '#3f3f46',
279
- borderRadius: 12,
280
- padding: 12,
281
- fontSize: 14,
282
- minHeight: 60,
283
- textAlignVertical: 'top',
284
- marginBottom: 12,
285
- },
286
- actions: {
287
- flexDirection: 'row',
288
- justifyContent: 'space-between',
289
- alignItems: 'center',
290
- },
291
- dismissText: {
292
- color: '#71717a',
293
- fontSize: 14,
294
- },
295
- submitButton: {
296
- paddingHorizontal: 20,
297
- paddingVertical: 10,
298
- borderRadius: 10,
299
- },
300
- submitText: {
301
- fontSize: 14,
302
- fontWeight: '600',
303
- },
304
- });
@@ -1,190 +0,0 @@
1
- /**
2
- * EscalationEventSource — SSE client using fetch + ReadableStream.
3
- *
4
- * Uses only the fetch API (available in all React Native runtimes)
5
- * to consume Server-Sent Events — no EventSource polyfill needed.
6
- * Provides a reliable, auto-reconnecting channel for server-push
7
- * events like `ticket_closed` that complements the bidirectional
8
- * WebSocket used for chat.
9
- *
10
- * Lifecycle:
11
- * 1. SDK calls connect() → fetch with streaming response
12
- * 2. Server holds connection open, pushes `ticket_closed` when agent resolves
13
- * 3. On disconnect, auto-reconnects with exponential backoff (max 5 attempts)
14
- * 4. If ticket is already closed, server responds immediately with the event
15
- */
16
-
17
- import { logger } from '../utils/logger';
18
-
19
- export interface EscalationEventSourceOptions {
20
- url: string;
21
- onTicketClosed?: (ticketId: string) => void;
22
- onConnected?: (ticketId: string) => void;
23
- onError?: (error: Error) => void;
24
- }
25
-
26
- export class EscalationEventSource {
27
- private abortController: AbortController | null = null;
28
- private intentionalClose = false;
29
- private reconnectAttempts = 0;
30
- private reconnectTimer: ReturnType<typeof setTimeout> | null = null;
31
- private readonly maxReconnectAttempts = 5;
32
- private readonly options: EscalationEventSourceOptions;
33
-
34
- constructor(options: EscalationEventSourceOptions) {
35
- this.options = options;
36
- }
37
-
38
- connect(): void {
39
- this.intentionalClose = false;
40
- this.reconnectAttempts = 0;
41
- this.openConnection();
42
- }
43
-
44
- disconnect(): void {
45
- this.intentionalClose = true;
46
- if (this.reconnectTimer) {
47
- clearTimeout(this.reconnectTimer);
48
- this.reconnectTimer = null;
49
- }
50
- if (this.abortController) {
51
- this.abortController.abort();
52
- this.abortController = null;
53
- }
54
- }
55
-
56
- private async openConnection(): Promise<void> {
57
- if (this.intentionalClose) return;
58
-
59
- this.abortController = new AbortController();
60
-
61
- try {
62
- const response = await fetch(this.options.url, {
63
- signal: this.abortController.signal,
64
- headers: { Accept: 'text/event-stream' },
65
- });
66
-
67
- if (!response.ok) {
68
- logger.warn('EscalationSSE', 'Non-OK response:', response.status);
69
- this.scheduleReconnect();
70
- return;
71
- }
72
-
73
- if (!response.body) {
74
- logger.warn('EscalationSSE', 'No readable body — falling back to reading full response');
75
- await this.readFullResponse(response);
76
- return;
77
- }
78
-
79
- this.reconnectAttempts = 0;
80
- await this.readStream(response.body);
81
- } catch (err) {
82
- if (this.intentionalClose) return;
83
- if ((err as Error).name === 'AbortError') return;
84
- logger.warn('EscalationSSE', 'Connection error:', (err as Error).message);
85
- this.options.onError?.(err as Error);
86
- this.scheduleReconnect();
87
- }
88
- }
89
-
90
- private async readStream(body: ReadableStream<Uint8Array>): Promise<void> {
91
- const reader = body.getReader();
92
- const decoder = new TextDecoder();
93
- let buffer = '';
94
-
95
- try {
96
- while (true) {
97
- const { done, value } = await reader.read();
98
- if (done) break;
99
-
100
- buffer += decoder.decode(value, { stream: true });
101
- const lines = buffer.split('\n');
102
- buffer = lines.pop()!;
103
-
104
- let currentEvent = '';
105
- let currentData = '';
106
-
107
- for (const line of lines) {
108
- if (line.startsWith('event: ')) {
109
- currentEvent = line.slice(7).trim();
110
- } else if (line.startsWith('data: ')) {
111
- currentData = line.slice(6).trim();
112
- } else if (line === '' && currentEvent && currentData) {
113
- this.handleEvent(currentEvent, currentData);
114
- currentEvent = '';
115
- currentData = '';
116
- }
117
- }
118
- }
119
- } catch (err) {
120
- if (this.intentionalClose) return;
121
- if ((err as Error).name === 'AbortError') return;
122
- logger.warn('EscalationSSE', 'Stream read error:', (err as Error).message);
123
- }
124
-
125
- if (!this.intentionalClose) {
126
- this.scheduleReconnect();
127
- }
128
- }
129
-
130
- private async readFullResponse(response: Response): Promise<void> {
131
- try {
132
- const text = await response.text();
133
- let currentEvent = '';
134
- let currentData = '';
135
-
136
- for (const line of text.split('\n')) {
137
- if (line.startsWith('event: ')) {
138
- currentEvent = line.slice(7).trim();
139
- } else if (line.startsWith('data: ')) {
140
- currentData = line.slice(6).trim();
141
- } else if (line === '' && currentEvent && currentData) {
142
- this.handleEvent(currentEvent, currentData);
143
- currentEvent = '';
144
- currentData = '';
145
- }
146
- }
147
- } catch (err) {
148
- if (this.intentionalClose) return;
149
- logger.warn('EscalationSSE', 'Full response read error:', (err as Error).message);
150
- }
151
-
152
- if (!this.intentionalClose) {
153
- this.scheduleReconnect();
154
- }
155
- }
156
-
157
- private handleEvent(event: string, data: string): void {
158
- try {
159
- const parsed = JSON.parse(data);
160
-
161
- if (event === 'connected') {
162
- logger.info('EscalationSSE', 'Connected for ticket:', parsed.ticketId);
163
- this.options.onConnected?.(parsed.ticketId);
164
- } else if (event === 'ticket_closed') {
165
- logger.info('EscalationSSE', 'Ticket closed event:', parsed.ticketId);
166
- this.options.onTicketClosed?.(parsed.ticketId);
167
- this.intentionalClose = true;
168
- this.abortController?.abort();
169
- }
170
- } catch {
171
- // ignore parse error
172
- }
173
- }
174
-
175
- private scheduleReconnect(): void {
176
- if (this.intentionalClose) return;
177
- if (this.reconnectAttempts >= this.maxReconnectAttempts) {
178
- logger.warn('EscalationSSE', 'Max reconnect attempts reached — giving up');
179
- return;
180
- }
181
-
182
- const delay = Math.min(1000 * 2 ** this.reconnectAttempts, 16_000);
183
- this.reconnectAttempts++;
184
- logger.info('EscalationSSE', `Reconnecting in ${delay}ms (attempt ${this.reconnectAttempts}/${this.maxReconnectAttempts})`);
185
-
186
- this.reconnectTimer = setTimeout(() => {
187
- this.openConnection();
188
- }, delay);
189
- }
190
- }
@@ -1,152 +0,0 @@
1
- /**
2
- * EscalationSocket — manages a WebSocket connection to the MobileAI platform
3
- * for receiving real-time replies from human support agents.
4
- *
5
- * Lifecycle:
6
- * 1. SDK calls escalate_to_human → POST /api/v1/escalations → gets { ticketId, wsUrl }
7
- * 2. EscalationSocket.connect(wsUrl) opens a WS connection
8
- * 3. Platform pushes { type: 'reply', ticketId, reply } when agent responds
9
- * 4. onReply callback fires → shown in chat UI as "👤 Human Agent: <reply>"
10
- * 5. disconnect() on chat close / unmount
11
- *
12
- * Handles:
13
- * - Server heartbeat pings (type: 'ping') — acknowledged silently
14
- * - Auto-reconnect on unexpected close (max 3 attempts, exponential backoff)
15
- */
16
-
17
- export type SocketReplyHandler = (reply: string, ticketId?: string) => void;
18
-
19
- interface EscalationSocketOptions {
20
- onReply: SocketReplyHandler;
21
- onError?: (error: Event) => void;
22
- onTypingChange?: (isTyping: boolean) => void;
23
- onTicketClosed?: (ticketId?: string) => void;
24
- maxReconnectAttempts?: number;
25
- }
26
-
27
- export class EscalationSocket {
28
- private ws: WebSocket | null = null;
29
- private wsUrl: string | null = null;
30
- private reconnectAttempts = 0;
31
- private reconnectTimer: ReturnType<typeof setTimeout> | null = null;
32
- private intentionalClose = false;
33
-
34
- private readonly onReply: SocketReplyHandler;
35
- private readonly onError?: (error: Event) => void;
36
- private readonly onTypingChange?: (isTyping: boolean) => void;
37
- private readonly onTicketClosed?: (ticketId?: string) => void;
38
- private readonly maxReconnectAttempts: number;
39
-
40
- constructor(options: EscalationSocketOptions) {
41
- this.onReply = options.onReply;
42
- this.onError = options.onError;
43
- this.onTypingChange = options.onTypingChange;
44
- this.onTicketClosed = options.onTicketClosed;
45
- this.maxReconnectAttempts = options.maxReconnectAttempts ?? 3;
46
- }
47
-
48
- connect(wsUrl: string): void {
49
- this.wsUrl = wsUrl;
50
- this.intentionalClose = false;
51
- this.openConnection();
52
- }
53
-
54
- sendText(text: string): boolean {
55
- if (this.ws?.readyState === 1) { // WebSocket.OPEN
56
- this.ws.send(JSON.stringify({ type: 'user_message', content: text }));
57
- return true;
58
- }
59
- return false;
60
- }
61
-
62
- sendTypingStatus(isTyping: boolean): boolean {
63
- if (this.ws?.readyState === 1) {
64
- this.ws.send(JSON.stringify({ type: isTyping ? 'typing_start' : 'typing_stop' }));
65
- return true;
66
- }
67
- return false;
68
- }
69
-
70
- disconnect(): void {
71
- this.intentionalClose = true;
72
- if (this.reconnectTimer) {
73
- clearTimeout(this.reconnectTimer);
74
- this.reconnectTimer = null;
75
- }
76
- if (this.ws) {
77
- this.ws.close();
78
- this.ws = null;
79
- }
80
- }
81
-
82
- private openConnection(): void {
83
- if (!this.wsUrl) return;
84
-
85
- try {
86
- this.ws = new WebSocket(this.wsUrl);
87
- } catch (err) {
88
- console.error('[EscalationSocket] Failed to open WebSocket:', err);
89
- return;
90
- }
91
-
92
- this.ws.onopen = () => {
93
- console.log('[EscalationSocket] ✅ Connected to:', this.wsUrl);
94
- this.reconnectAttempts = 0;
95
- };
96
-
97
- this.ws.onmessage = (event) => {
98
- try {
99
- const rawData = String(event.data);
100
- console.log('[EscalationSocket] Message received:', rawData);
101
- const msg = JSON.parse(rawData);
102
- if (msg.type === 'ping') {
103
- console.log('[EscalationSocket] Heartbeat ping received');
104
- return;
105
- }
106
- if (msg.type === 'reply' && msg.reply) {
107
- console.log('[EscalationSocket] Human reply received:', msg.reply);
108
- this.onTypingChange?.(false);
109
- this.onReply(msg.reply, msg.ticketId);
110
- } else if (msg.type === 'typing_start') {
111
- this.onTypingChange?.(true);
112
- } else if (msg.type === 'typing_stop') {
113
- this.onTypingChange?.(false);
114
- } else if (msg.type === 'ticket_closed') {
115
- console.log('[EscalationSocket] Ticket closed by agent');
116
- this.onTypingChange?.(false);
117
- this.onTicketClosed?.(msg.ticketId);
118
- this.intentionalClose = true;
119
- this.ws?.close();
120
- }
121
- } catch {
122
- // Non-JSON message — ignore
123
- }
124
- };
125
-
126
- this.ws.onerror = (event) => {
127
- console.error('[EscalationSocket] ❌ WebSocket error. URL was:', this.wsUrl, event);
128
- this.onError?.(event);
129
- };
130
-
131
- this.ws.onclose = (event) => {
132
- console.warn(`[EscalationSocket] Connection closed. Code=${event.code} Reason="${event.reason}" Intentional=${this.intentionalClose}`);
133
- if (this.intentionalClose) return;
134
- this.scheduleReconnect();
135
- };
136
- }
137
-
138
- private scheduleReconnect(): void {
139
- if (this.reconnectAttempts >= this.maxReconnectAttempts) {
140
- console.warn('[EscalationSocket] Max reconnect attempts reached — giving up');
141
- return;
142
- }
143
- const delay = Math.min(1000 * 2 ** this.reconnectAttempts, 16_000);
144
- this.reconnectAttempts++;
145
- console.log(
146
- `[EscalationSocket] Reconnecting in ${delay}ms (attempt ${this.reconnectAttempts}/${this.maxReconnectAttempts})`
147
- );
148
- this.reconnectTimer = setTimeout(() => {
149
- this.openConnection();
150
- }, delay);
151
- }
152
- }