@parlr/react-native 0.1.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 (223) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +918 -0
  3. package/lib/commonjs/components/AttachmentPicker.js +292 -0
  4. package/lib/commonjs/components/AttachmentPicker.js.map +1 -0
  5. package/lib/commonjs/components/AttachmentPreview.js +200 -0
  6. package/lib/commonjs/components/AttachmentPreview.js.map +1 -0
  7. package/lib/commonjs/components/ChatBubble.js +391 -0
  8. package/lib/commonjs/components/ChatBubble.js.map +1 -0
  9. package/lib/commonjs/components/EmptyState.js +115 -0
  10. package/lib/commonjs/components/EmptyState.js.map +1 -0
  11. package/lib/commonjs/components/ParlrChat.js +745 -0
  12. package/lib/commonjs/components/ParlrChat.js.map +1 -0
  13. package/lib/commonjs/components/ParlrConversationList.js +509 -0
  14. package/lib/commonjs/components/ParlrConversationList.js.map +1 -0
  15. package/lib/commonjs/components/PreChatForm.js +263 -0
  16. package/lib/commonjs/components/PreChatForm.js.map +1 -0
  17. package/lib/commonjs/components/RichMessage.js +284 -0
  18. package/lib/commonjs/components/RichMessage.js.map +1 -0
  19. package/lib/commonjs/components/SatisfactionSurvey.js +292 -0
  20. package/lib/commonjs/components/SatisfactionSurvey.js.map +1 -0
  21. package/lib/commonjs/components/TypingIndicator.js +86 -0
  22. package/lib/commonjs/components/TypingIndicator.js.map +1 -0
  23. package/lib/commonjs/core/api.js +310 -0
  24. package/lib/commonjs/core/api.js.map +1 -0
  25. package/lib/commonjs/core/config.js +40 -0
  26. package/lib/commonjs/core/config.js.map +1 -0
  27. package/lib/commonjs/core/errors.js +73 -0
  28. package/lib/commonjs/core/errors.js.map +1 -0
  29. package/lib/commonjs/core/offlineQueue.js +89 -0
  30. package/lib/commonjs/core/offlineQueue.js.map +1 -0
  31. package/lib/commonjs/core/pushNotifications.js +21 -0
  32. package/lib/commonjs/core/pushNotifications.js.map +1 -0
  33. package/lib/commonjs/core/session.js +130 -0
  34. package/lib/commonjs/core/session.js.map +1 -0
  35. package/lib/commonjs/core/theme.js +110 -0
  36. package/lib/commonjs/core/theme.js.map +1 -0
  37. package/lib/commonjs/core/types.js +6 -0
  38. package/lib/commonjs/core/types.js.map +1 -0
  39. package/lib/commonjs/core/websocket.js +245 -0
  40. package/lib/commonjs/core/websocket.js.map +1 -0
  41. package/lib/commonjs/hooks/useChat.js +462 -0
  42. package/lib/commonjs/hooks/useChat.js.map +1 -0
  43. package/lib/commonjs/hooks/useParlr.js +44 -0
  44. package/lib/commonjs/hooks/useParlr.js.map +1 -0
  45. package/lib/commonjs/index.js +185 -0
  46. package/lib/commonjs/index.js.map +1 -0
  47. package/lib/commonjs/package.json +1 -0
  48. package/lib/commonjs/provider/ParlrContext.js +38 -0
  49. package/lib/commonjs/provider/ParlrContext.js.map +1 -0
  50. package/lib/commonjs/provider/ParlrProvider.js +256 -0
  51. package/lib/commonjs/provider/ParlrProvider.js.map +1 -0
  52. package/lib/module/components/AttachmentPicker.js +287 -0
  53. package/lib/module/components/AttachmentPicker.js.map +1 -0
  54. package/lib/module/components/AttachmentPreview.js +195 -0
  55. package/lib/module/components/AttachmentPreview.js.map +1 -0
  56. package/lib/module/components/ChatBubble.js +386 -0
  57. package/lib/module/components/ChatBubble.js.map +1 -0
  58. package/lib/module/components/EmptyState.js +110 -0
  59. package/lib/module/components/EmptyState.js.map +1 -0
  60. package/lib/module/components/ParlrChat.js +740 -0
  61. package/lib/module/components/ParlrChat.js.map +1 -0
  62. package/lib/module/components/ParlrConversationList.js +504 -0
  63. package/lib/module/components/ParlrConversationList.js.map +1 -0
  64. package/lib/module/components/PreChatForm.js +258 -0
  65. package/lib/module/components/PreChatForm.js.map +1 -0
  66. package/lib/module/components/RichMessage.js +280 -0
  67. package/lib/module/components/RichMessage.js.map +1 -0
  68. package/lib/module/components/SatisfactionSurvey.js +287 -0
  69. package/lib/module/components/SatisfactionSurvey.js.map +1 -0
  70. package/lib/module/components/TypingIndicator.js +81 -0
  71. package/lib/module/components/TypingIndicator.js.map +1 -0
  72. package/lib/module/core/api.js +305 -0
  73. package/lib/module/core/api.js.map +1 -0
  74. package/lib/module/core/config.js +36 -0
  75. package/lib/module/core/config.js.map +1 -0
  76. package/lib/module/core/errors.js +64 -0
  77. package/lib/module/core/errors.js.map +1 -0
  78. package/lib/module/core/offlineQueue.js +82 -0
  79. package/lib/module/core/offlineQueue.js.map +1 -0
  80. package/lib/module/core/pushNotifications.js +16 -0
  81. package/lib/module/core/pushNotifications.js.map +1 -0
  82. package/lib/module/core/session.js +122 -0
  83. package/lib/module/core/session.js.map +1 -0
  84. package/lib/module/core/theme.js +105 -0
  85. package/lib/module/core/theme.js.map +1 -0
  86. package/lib/module/core/types.js +4 -0
  87. package/lib/module/core/types.js.map +1 -0
  88. package/lib/module/core/websocket.js +241 -0
  89. package/lib/module/core/websocket.js.map +1 -0
  90. package/lib/module/hooks/useChat.js +458 -0
  91. package/lib/module/hooks/useChat.js.map +1 -0
  92. package/lib/module/hooks/useParlr.js +40 -0
  93. package/lib/module/hooks/useParlr.js.map +1 -0
  94. package/lib/module/index.js +58 -0
  95. package/lib/module/index.js.map +1 -0
  96. package/lib/module/package.json +1 -0
  97. package/lib/module/provider/ParlrContext.js +35 -0
  98. package/lib/module/provider/ParlrContext.js.map +1 -0
  99. package/lib/module/provider/ParlrProvider.js +251 -0
  100. package/lib/module/provider/ParlrProvider.js.map +1 -0
  101. package/lib/typescript/commonjs/components/AttachmentPicker.d.ts +23 -0
  102. package/lib/typescript/commonjs/components/AttachmentPicker.d.ts.map +1 -0
  103. package/lib/typescript/commonjs/components/AttachmentPreview.d.ts +16 -0
  104. package/lib/typescript/commonjs/components/AttachmentPreview.d.ts.map +1 -0
  105. package/lib/typescript/commonjs/components/ChatBubble.d.ts +14 -0
  106. package/lib/typescript/commonjs/components/ChatBubble.d.ts.map +1 -0
  107. package/lib/typescript/commonjs/components/EmptyState.d.ts +10 -0
  108. package/lib/typescript/commonjs/components/EmptyState.d.ts.map +1 -0
  109. package/lib/typescript/commonjs/components/ParlrChat.d.ts +34 -0
  110. package/lib/typescript/commonjs/components/ParlrChat.d.ts.map +1 -0
  111. package/lib/typescript/commonjs/components/ParlrConversationList.d.ts +17 -0
  112. package/lib/typescript/commonjs/components/ParlrConversationList.d.ts.map +1 -0
  113. package/lib/typescript/commonjs/components/PreChatForm.d.ts +20 -0
  114. package/lib/typescript/commonjs/components/PreChatForm.d.ts.map +1 -0
  115. package/lib/typescript/commonjs/components/RichMessage.d.ts +41 -0
  116. package/lib/typescript/commonjs/components/RichMessage.d.ts.map +1 -0
  117. package/lib/typescript/commonjs/components/SatisfactionSurvey.d.ts +17 -0
  118. package/lib/typescript/commonjs/components/SatisfactionSurvey.d.ts.map +1 -0
  119. package/lib/typescript/commonjs/components/TypingIndicator.d.ts +7 -0
  120. package/lib/typescript/commonjs/components/TypingIndicator.d.ts.map +1 -0
  121. package/lib/typescript/commonjs/core/api.d.ts +37 -0
  122. package/lib/typescript/commonjs/core/api.d.ts.map +1 -0
  123. package/lib/typescript/commonjs/core/config.d.ts +9 -0
  124. package/lib/typescript/commonjs/core/config.d.ts.map +1 -0
  125. package/lib/typescript/commonjs/core/errors.d.ts +35 -0
  126. package/lib/typescript/commonjs/core/errors.d.ts.map +1 -0
  127. package/lib/typescript/commonjs/core/offlineQueue.d.ts +16 -0
  128. package/lib/typescript/commonjs/core/offlineQueue.d.ts.map +1 -0
  129. package/lib/typescript/commonjs/core/pushNotifications.d.ts +6 -0
  130. package/lib/typescript/commonjs/core/pushNotifications.d.ts.map +1 -0
  131. package/lib/typescript/commonjs/core/session.d.ts +15 -0
  132. package/lib/typescript/commonjs/core/session.d.ts.map +1 -0
  133. package/lib/typescript/commonjs/core/theme.d.ts +43 -0
  134. package/lib/typescript/commonjs/core/theme.d.ts.map +1 -0
  135. package/lib/typescript/commonjs/core/types.d.ts +185 -0
  136. package/lib/typescript/commonjs/core/types.d.ts.map +1 -0
  137. package/lib/typescript/commonjs/core/websocket.d.ts +17 -0
  138. package/lib/typescript/commonjs/core/websocket.d.ts.map +1 -0
  139. package/lib/typescript/commonjs/hooks/useChat.d.ts +35 -0
  140. package/lib/typescript/commonjs/hooks/useChat.d.ts.map +1 -0
  141. package/lib/typescript/commonjs/hooks/useParlr.d.ts +11 -0
  142. package/lib/typescript/commonjs/hooks/useParlr.d.ts.map +1 -0
  143. package/lib/typescript/commonjs/index.d.ts +30 -0
  144. package/lib/typescript/commonjs/index.d.ts.map +1 -0
  145. package/lib/typescript/commonjs/package.json +1 -0
  146. package/lib/typescript/commonjs/provider/ParlrContext.d.ts +13 -0
  147. package/lib/typescript/commonjs/provider/ParlrContext.d.ts.map +1 -0
  148. package/lib/typescript/commonjs/provider/ParlrProvider.d.ts +5 -0
  149. package/lib/typescript/commonjs/provider/ParlrProvider.d.ts.map +1 -0
  150. package/lib/typescript/module/components/AttachmentPicker.d.ts +23 -0
  151. package/lib/typescript/module/components/AttachmentPicker.d.ts.map +1 -0
  152. package/lib/typescript/module/components/AttachmentPreview.d.ts +16 -0
  153. package/lib/typescript/module/components/AttachmentPreview.d.ts.map +1 -0
  154. package/lib/typescript/module/components/ChatBubble.d.ts +14 -0
  155. package/lib/typescript/module/components/ChatBubble.d.ts.map +1 -0
  156. package/lib/typescript/module/components/EmptyState.d.ts +10 -0
  157. package/lib/typescript/module/components/EmptyState.d.ts.map +1 -0
  158. package/lib/typescript/module/components/ParlrChat.d.ts +34 -0
  159. package/lib/typescript/module/components/ParlrChat.d.ts.map +1 -0
  160. package/lib/typescript/module/components/ParlrConversationList.d.ts +17 -0
  161. package/lib/typescript/module/components/ParlrConversationList.d.ts.map +1 -0
  162. package/lib/typescript/module/components/PreChatForm.d.ts +20 -0
  163. package/lib/typescript/module/components/PreChatForm.d.ts.map +1 -0
  164. package/lib/typescript/module/components/RichMessage.d.ts +41 -0
  165. package/lib/typescript/module/components/RichMessage.d.ts.map +1 -0
  166. package/lib/typescript/module/components/SatisfactionSurvey.d.ts +17 -0
  167. package/lib/typescript/module/components/SatisfactionSurvey.d.ts.map +1 -0
  168. package/lib/typescript/module/components/TypingIndicator.d.ts +7 -0
  169. package/lib/typescript/module/components/TypingIndicator.d.ts.map +1 -0
  170. package/lib/typescript/module/core/api.d.ts +37 -0
  171. package/lib/typescript/module/core/api.d.ts.map +1 -0
  172. package/lib/typescript/module/core/config.d.ts +9 -0
  173. package/lib/typescript/module/core/config.d.ts.map +1 -0
  174. package/lib/typescript/module/core/errors.d.ts +35 -0
  175. package/lib/typescript/module/core/errors.d.ts.map +1 -0
  176. package/lib/typescript/module/core/offlineQueue.d.ts +16 -0
  177. package/lib/typescript/module/core/offlineQueue.d.ts.map +1 -0
  178. package/lib/typescript/module/core/pushNotifications.d.ts +6 -0
  179. package/lib/typescript/module/core/pushNotifications.d.ts.map +1 -0
  180. package/lib/typescript/module/core/session.d.ts +15 -0
  181. package/lib/typescript/module/core/session.d.ts.map +1 -0
  182. package/lib/typescript/module/core/theme.d.ts +43 -0
  183. package/lib/typescript/module/core/theme.d.ts.map +1 -0
  184. package/lib/typescript/module/core/types.d.ts +185 -0
  185. package/lib/typescript/module/core/types.d.ts.map +1 -0
  186. package/lib/typescript/module/core/websocket.d.ts +17 -0
  187. package/lib/typescript/module/core/websocket.d.ts.map +1 -0
  188. package/lib/typescript/module/hooks/useChat.d.ts +35 -0
  189. package/lib/typescript/module/hooks/useChat.d.ts.map +1 -0
  190. package/lib/typescript/module/hooks/useParlr.d.ts +11 -0
  191. package/lib/typescript/module/hooks/useParlr.d.ts.map +1 -0
  192. package/lib/typescript/module/index.d.ts +30 -0
  193. package/lib/typescript/module/index.d.ts.map +1 -0
  194. package/lib/typescript/module/package.json +1 -0
  195. package/lib/typescript/module/provider/ParlrContext.d.ts +13 -0
  196. package/lib/typescript/module/provider/ParlrContext.d.ts.map +1 -0
  197. package/lib/typescript/module/provider/ParlrProvider.d.ts +5 -0
  198. package/lib/typescript/module/provider/ParlrProvider.d.ts.map +1 -0
  199. package/package.json +120 -0
  200. package/src/components/AttachmentPicker.tsx +310 -0
  201. package/src/components/AttachmentPreview.tsx +209 -0
  202. package/src/components/ChatBubble.tsx +424 -0
  203. package/src/components/EmptyState.tsx +118 -0
  204. package/src/components/ParlrChat.tsx +863 -0
  205. package/src/components/ParlrConversationList.tsx +559 -0
  206. package/src/components/PreChatForm.tsx +313 -0
  207. package/src/components/RichMessage.tsx +353 -0
  208. package/src/components/SatisfactionSurvey.tsx +333 -0
  209. package/src/components/TypingIndicator.tsx +89 -0
  210. package/src/core/api.ts +406 -0
  211. package/src/core/config.ts +39 -0
  212. package/src/core/errors.ts +68 -0
  213. package/src/core/offlineQueue.ts +94 -0
  214. package/src/core/pushNotifications.ts +22 -0
  215. package/src/core/session.ts +156 -0
  216. package/src/core/theme.ts +133 -0
  217. package/src/core/types.ts +237 -0
  218. package/src/core/websocket.ts +270 -0
  219. package/src/hooks/useChat.ts +534 -0
  220. package/src/hooks/useParlr.ts +43 -0
  221. package/src/index.ts +98 -0
  222. package/src/provider/ParlrContext.ts +40 -0
  223. package/src/provider/ParlrProvider.tsx +338 -0
@@ -0,0 +1,270 @@
1
+ // ---------------------------------------------------------------------------
2
+ // Parlr React Native SDK - WebSocket Real-Time Client
3
+ // ---------------------------------------------------------------------------
4
+ //
5
+ // Manages the persistent WebSocket connection to the Parlr gateway.
6
+ // Features: auto-reconnect with exponential backoff, keep-alive pings,
7
+ // typed event emitter, and clean teardown.
8
+ // ---------------------------------------------------------------------------
9
+
10
+ import type {
11
+ ResolvedConfig,
12
+ WsEventMap,
13
+ WsIncomingPayload,
14
+ WsIncomingType,
15
+ WsOutgoingPayload,
16
+ } from './types';
17
+
18
+ // ---------------------------------------------------------------------------
19
+ // Types
20
+ // ---------------------------------------------------------------------------
21
+
22
+ type Listener<T extends WsIncomingType> = (payload: WsEventMap[T]) => void;
23
+
24
+ export interface ParlrWebSocket {
25
+ /** Open the connection (idempotent). */
26
+ connect(sessionToken: string): void;
27
+ /** Gracefully close the connection. Will NOT auto-reconnect. */
28
+ disconnect(): void;
29
+ /** Send a typed frame. */
30
+ send(frame: WsOutgoingPayload): void;
31
+ /** Subscribe to a specific incoming event type. Returns unsubscribe fn. */
32
+ on<T extends WsIncomingType>(type: T, listener: Listener<T>): () => void;
33
+ /** Whether the socket is currently open. */
34
+ readonly connected: boolean;
35
+ }
36
+
37
+ // ---------------------------------------------------------------------------
38
+ // Constants
39
+ // ---------------------------------------------------------------------------
40
+
41
+ const PING_INTERVAL_MS = 25_000;
42
+ const INITIAL_RECONNECT_MS = 1_000;
43
+ const MAX_RECONNECT_MS = 30_000;
44
+
45
+ // ---------------------------------------------------------------------------
46
+ // Factory
47
+ // ---------------------------------------------------------------------------
48
+
49
+ /**
50
+ * Create a no-op WebSocket stub when wsUrl is not configured.
51
+ * All methods are safe to call but do nothing.
52
+ */
53
+ function createNoopWebSocket(): ParlrWebSocket {
54
+ return {
55
+ get connected() { return false; },
56
+ connect() {},
57
+ disconnect() {},
58
+ send() {},
59
+ on() { return () => {}; },
60
+ };
61
+ }
62
+
63
+ export function createWebSocket(config: ResolvedConfig): ParlrWebSocket {
64
+ if (!config.wsUrl) {
65
+ return createNoopWebSocket();
66
+ }
67
+ let ws: WebSocket | null = null;
68
+ let sessionToken: string | null = null;
69
+ let intentionalClose = false;
70
+ let reconnectDelay = INITIAL_RECONNECT_MS;
71
+ let reconnectTimer: ReturnType<typeof setTimeout> | null = null;
72
+ let pingTimer: ReturnType<typeof setInterval> | null = null;
73
+ let lastPongAt: number = 0;
74
+ let pongCheckTimer: ReturnType<typeof setInterval> | null = null;
75
+
76
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
77
+ const listeners = new Map<WsIncomingType, Set<(payload: any) => void>>();
78
+
79
+ // --- Helpers ---------------------------------------------------------------
80
+
81
+ function log(...args: unknown[]) {
82
+ if (config.debug) {
83
+ console.log('[@parlr/react-native][ws]', ...args);
84
+ }
85
+ }
86
+
87
+ function emit(type: WsIncomingType, /* istanbul ignore next */ payload: Record<string, unknown> = {}) {
88
+ const set = listeners.get(type);
89
+ if (set) {
90
+ for (const fn of set) {
91
+ try {
92
+ fn(payload);
93
+ } catch (err) {
94
+ console.error('[@parlr/react-native][ws] listener error:', err);
95
+ }
96
+ }
97
+ }
98
+ }
99
+
100
+ function clearTimers() {
101
+ if (pingTimer) {
102
+ clearInterval(pingTimer);
103
+ pingTimer = null;
104
+ }
105
+ if (reconnectTimer) {
106
+ clearTimeout(reconnectTimer);
107
+ reconnectTimer = null;
108
+ }
109
+ if (pongCheckTimer) {
110
+ clearInterval(pongCheckTimer);
111
+ pongCheckTimer = null;
112
+ }
113
+ }
114
+
115
+ function startPing() {
116
+ lastPongAt = Date.now();
117
+
118
+ pingTimer = setInterval(() => {
119
+ if (ws?.readyState === WebSocket.OPEN) {
120
+ ws.send(JSON.stringify({ type: 'ping' }));
121
+ }
122
+ }, PING_INTERVAL_MS);
123
+
124
+ // Check for pong timeout: if no pong received in 2x PING_INTERVAL, force reconnect.
125
+ pongCheckTimer = setInterval(() => {
126
+ if (Date.now() - lastPongAt > PING_INTERVAL_MS * 2) {
127
+ log('Pong timeout — forcing reconnect');
128
+ clearTimers();
129
+ if (ws) {
130
+ try { ws.close(); } catch { /* ignore */ }
131
+ }
132
+ scheduleReconnect();
133
+ }
134
+ }, PING_INTERVAL_MS);
135
+ }
136
+
137
+ function scheduleReconnect() {
138
+ if (intentionalClose) return;
139
+
140
+ const jitter = Math.random() * 500;
141
+ const wait = Math.min(reconnectDelay + jitter, MAX_RECONNECT_MS);
142
+ log(`Reconnecting in ${Math.round(wait)}ms`);
143
+
144
+ reconnectTimer = setTimeout(() => {
145
+ reconnectDelay = Math.min(reconnectDelay * 2, MAX_RECONNECT_MS);
146
+ if (sessionToken) {
147
+ doConnect(sessionToken);
148
+ }
149
+ }, wait);
150
+ }
151
+
152
+ // --- Core connection -------------------------------------------------------
153
+
154
+ function doConnect(token: string) {
155
+ clearTimers();
156
+
157
+ if (ws) {
158
+ try {
159
+ ws.close();
160
+ } catch {
161
+ // ignore
162
+ }
163
+ }
164
+
165
+ const url = config.wsUrl;
166
+ log('Connecting to', url);
167
+
168
+ ws = new WebSocket(url);
169
+
170
+ ws.onopen = () => {
171
+ log('Connected');
172
+ reconnectDelay = INITIAL_RECONNECT_MS;
173
+
174
+ // Authenticate immediately after opening.
175
+ ws!.send(
176
+ JSON.stringify({
177
+ type: 'auth',
178
+ payload: { token, workspaceId: config.workspaceId },
179
+ }),
180
+ );
181
+
182
+ startPing();
183
+ };
184
+
185
+ ws.onmessage = (event) => {
186
+ try {
187
+ const raw = String(event.data);
188
+ const frame = JSON.parse(raw) as WsIncomingPayload;
189
+
190
+ if (!frame || typeof frame.type !== 'string') {
191
+ log('Received invalid frame (missing type):', raw);
192
+ return;
193
+ }
194
+
195
+ // Track pong responses for keep-alive validation.
196
+ if (frame.type === 'pong') {
197
+ lastPongAt = Date.now();
198
+ }
199
+
200
+ log('<-', frame.type, frame.payload);
201
+ emit(frame.type, frame.payload ?? {});
202
+ } catch (err) {
203
+ log('Failed to parse frame:', err);
204
+ }
205
+ };
206
+
207
+ ws.onerror = (event) => {
208
+ log('Error:', event);
209
+ };
210
+
211
+ ws.onclose = (event) => {
212
+ log('Closed:', event.code, event.reason);
213
+ clearTimers();
214
+
215
+ // Only emit auth_error for authentication-related close codes.
216
+ if (event.code === 4001 || event.code === 4003) {
217
+ emit('auth_error', { code: event.code, reason: event.reason });
218
+ } else if (!intentionalClose) {
219
+ emit('disconnected', { code: event.code, reason: event.reason });
220
+ }
221
+
222
+ scheduleReconnect();
223
+ };
224
+ }
225
+
226
+ // --- Public API ------------------------------------------------------------
227
+
228
+ return {
229
+ get connected(): boolean {
230
+ return ws?.readyState === WebSocket.OPEN;
231
+ },
232
+
233
+ connect(token: string) {
234
+ sessionToken = token;
235
+ intentionalClose = false;
236
+ reconnectDelay = INITIAL_RECONNECT_MS;
237
+ doConnect(token);
238
+ },
239
+
240
+ disconnect() {
241
+ intentionalClose = true;
242
+ clearTimers();
243
+ sessionToken = null;
244
+ if (ws) {
245
+ ws.close(1000, 'Client disconnect');
246
+ ws = null;
247
+ }
248
+ },
249
+
250
+ send(frame: WsOutgoingPayload) {
251
+ if (ws?.readyState === WebSocket.OPEN) {
252
+ log('->', frame.type, frame.payload);
253
+ ws.send(JSON.stringify(frame));
254
+ } else {
255
+ log('Cannot send, socket not open. Frame:', frame.type);
256
+ }
257
+ },
258
+
259
+ on<T extends WsIncomingType>(type: T, listener: Listener<T>): () => void {
260
+ if (!listeners.has(type)) {
261
+ listeners.set(type, new Set());
262
+ }
263
+ listeners.get(type)!.add(listener);
264
+
265
+ return () => {
266
+ listeners.get(type)?.delete(listener);
267
+ };
268
+ },
269
+ };
270
+ }