@auxiora/channels 1.0.0 → 1.3.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 (94) hide show
  1. package/dist/adapters/bluebubbles.d.ts +0 -1
  2. package/dist/adapters/bluebubbles.d.ts.map +1 -1
  3. package/dist/adapters/bluebubbles.js +2 -24
  4. package/dist/adapters/bluebubbles.js.map +1 -1
  5. package/dist/adapters/discord.d.ts +2 -1
  6. package/dist/adapters/discord.d.ts.map +1 -1
  7. package/dist/adapters/discord.js +20 -25
  8. package/dist/adapters/discord.js.map +1 -1
  9. package/dist/adapters/googlechat.d.ts +0 -1
  10. package/dist/adapters/googlechat.d.ts.map +1 -1
  11. package/dist/adapters/googlechat.js +2 -24
  12. package/dist/adapters/googlechat.js.map +1 -1
  13. package/dist/adapters/matrix.d.ts +0 -1
  14. package/dist/adapters/matrix.d.ts.map +1 -1
  15. package/dist/adapters/matrix.js +2 -24
  16. package/dist/adapters/matrix.js.map +1 -1
  17. package/dist/adapters/signal.d.ts +0 -1
  18. package/dist/adapters/signal.d.ts.map +1 -1
  19. package/dist/adapters/signal.js +2 -24
  20. package/dist/adapters/signal.js.map +1 -1
  21. package/dist/adapters/slack.d.ts +1 -1
  22. package/dist/adapters/slack.d.ts.map +1 -1
  23. package/dist/adapters/slack.js +12 -21
  24. package/dist/adapters/slack.js.map +1 -1
  25. package/dist/adapters/teams.d.ts +0 -1
  26. package/dist/adapters/teams.d.ts.map +1 -1
  27. package/dist/adapters/teams.js +2 -24
  28. package/dist/adapters/teams.js.map +1 -1
  29. package/dist/adapters/telegram.d.ts +1 -1
  30. package/dist/adapters/telegram.d.ts.map +1 -1
  31. package/dist/adapters/telegram.js +9 -21
  32. package/dist/adapters/telegram.js.map +1 -1
  33. package/dist/adapters/twilio.d.ts +0 -1
  34. package/dist/adapters/twilio.d.ts.map +1 -1
  35. package/dist/adapters/twilio.js +2 -24
  36. package/dist/adapters/twilio.js.map +1 -1
  37. package/dist/adapters/whatsapp.d.ts +0 -1
  38. package/dist/adapters/whatsapp.d.ts.map +1 -1
  39. package/dist/adapters/whatsapp.js +2 -24
  40. package/dist/adapters/whatsapp.js.map +1 -1
  41. package/dist/adapters/zalo.d.ts +0 -1
  42. package/dist/adapters/zalo.d.ts.map +1 -1
  43. package/dist/adapters/zalo.js +2 -24
  44. package/dist/adapters/zalo.js.map +1 -1
  45. package/dist/chunk.d.ts +14 -0
  46. package/dist/chunk.d.ts.map +1 -0
  47. package/dist/chunk.js +123 -0
  48. package/dist/chunk.js.map +1 -0
  49. package/dist/draft-stream-loop.d.ts +28 -0
  50. package/dist/draft-stream-loop.d.ts.map +1 -0
  51. package/dist/draft-stream-loop.js +80 -0
  52. package/dist/draft-stream-loop.js.map +1 -0
  53. package/dist/inbound-dedup.d.ts +17 -0
  54. package/dist/inbound-dedup.d.ts.map +1 -0
  55. package/dist/inbound-dedup.js +54 -0
  56. package/dist/inbound-dedup.js.map +1 -0
  57. package/dist/index.d.ts +2 -0
  58. package/dist/index.d.ts.map +1 -1
  59. package/dist/index.js +4 -0
  60. package/dist/index.js.map +1 -1
  61. package/dist/manager.d.ts +12 -0
  62. package/dist/manager.d.ts.map +1 -1
  63. package/dist/manager.js +35 -0
  64. package/dist/manager.js.map +1 -1
  65. package/dist/types.d.ts +2 -0
  66. package/dist/types.d.ts.map +1 -1
  67. package/package.json +10 -4
  68. package/src/adapters/bluebubbles.ts +0 -294
  69. package/src/adapters/discord.ts +0 -253
  70. package/src/adapters/email.ts +0 -457
  71. package/src/adapters/googlechat.ts +0 -364
  72. package/src/adapters/matrix.ts +0 -376
  73. package/src/adapters/signal.ts +0 -313
  74. package/src/adapters/slack.ts +0 -252
  75. package/src/adapters/teams.ts +0 -320
  76. package/src/adapters/telegram.ts +0 -208
  77. package/src/adapters/twilio.ts +0 -256
  78. package/src/adapters/whatsapp.ts +0 -342
  79. package/src/adapters/zalo.ts +0 -319
  80. package/src/index.ts +0 -78
  81. package/src/manager.ts +0 -180
  82. package/src/types.ts +0 -84
  83. package/tests/bluebubbles.test.ts +0 -438
  84. package/tests/email.test.ts +0 -136
  85. package/tests/googlechat.test.ts +0 -439
  86. package/tests/matrix.test.ts +0 -564
  87. package/tests/signal.test.ts +0 -404
  88. package/tests/slack.test.ts +0 -343
  89. package/tests/teams.test.ts +0 -429
  90. package/tests/twilio.test.ts +0 -269
  91. package/tests/whatsapp.test.ts +0 -530
  92. package/tests/zalo.test.ts +0 -499
  93. package/tsconfig.json +0 -8
  94. package/tsconfig.tsbuildinfo +0 -1
@@ -1,342 +0,0 @@
1
- import { audit } from '@auxiora/audit';
2
- import type {
3
- ChannelAdapter,
4
- InboundMessage,
5
- OutboundMessage,
6
- SendResult,
7
- } from '../types.js';
8
-
9
- export interface WhatsAppAdapterConfig {
10
- phoneNumberId: string;
11
- accessToken: string;
12
- verifyToken: string;
13
- allowedNumbers?: string[];
14
- }
15
-
16
- interface WhatsAppWebhookBody {
17
- object: string;
18
- entry: Array<{
19
- id: string;
20
- changes: Array<{
21
- value: {
22
- messaging_product: string;
23
- metadata: {
24
- display_phone_number: string;
25
- phone_number_id: string;
26
- };
27
- contacts?: Array<{
28
- profile: { name: string };
29
- wa_id: string;
30
- }>;
31
- messages?: Array<WhatsAppIncomingMessage>;
32
- statuses?: Array<{
33
- id: string;
34
- status: string;
35
- timestamp: string;
36
- }>;
37
- };
38
- field: string;
39
- }>;
40
- }>;
41
- }
42
-
43
- interface WhatsAppIncomingMessage {
44
- from: string;
45
- id: string;
46
- timestamp: string;
47
- type: string;
48
- text?: { body: string };
49
- image?: {
50
- id: string;
51
- mime_type: string;
52
- sha256: string;
53
- caption?: string;
54
- };
55
- document?: {
56
- id: string;
57
- mime_type: string;
58
- filename?: string;
59
- sha256: string;
60
- caption?: string;
61
- };
62
- audio?: {
63
- id: string;
64
- mime_type: string;
65
- };
66
- video?: {
67
- id: string;
68
- mime_type: string;
69
- };
70
- context?: {
71
- from: string;
72
- id: string;
73
- };
74
- }
75
-
76
- interface WhatsAppSendResponse {
77
- messaging_product: string;
78
- contacts: Array<{ wa_id: string }>;
79
- messages: Array<{ id: string }>;
80
- }
81
-
82
- const GRAPH_API_BASE = 'https://graph.facebook.com/v18.0';
83
- const MAX_MESSAGE_LENGTH = 4096;
84
-
85
- export class WhatsAppAdapter implements ChannelAdapter {
86
- readonly type = 'whatsapp' as const;
87
- readonly name = 'WhatsApp';
88
-
89
- private config: WhatsAppAdapterConfig;
90
- private messageHandler?: (message: InboundMessage) => Promise<void>;
91
- private errorHandler?: (error: Error) => void;
92
- private connected = false;
93
-
94
- constructor(config: WhatsAppAdapterConfig) {
95
- this.config = config;
96
- }
97
-
98
- async connect(): Promise<void> {
99
- // Verify credentials by checking the phone number ID
100
- const response = await fetch(
101
- `${GRAPH_API_BASE}/${this.config.phoneNumberId}`,
102
- {
103
- headers: {
104
- 'Authorization': `Bearer ${this.config.accessToken}`,
105
- },
106
- },
107
- );
108
-
109
- if (!response.ok) {
110
- throw new Error(`Failed to verify WhatsApp credentials: ${response.status} ${response.statusText}`);
111
- }
112
-
113
- this.connected = true;
114
-
115
- audit('channel.connected', {
116
- channelType: 'whatsapp',
117
- phoneNumberId: this.config.phoneNumberId,
118
- });
119
- }
120
-
121
- async disconnect(): Promise<void> {
122
- this.connected = false;
123
- audit('channel.disconnected', { channelType: 'whatsapp' });
124
- }
125
-
126
- isConnected(): boolean {
127
- return this.connected;
128
- }
129
-
130
- /**
131
- * Verify webhook subscription from Meta.
132
- * Returns the challenge string if the verify token matches.
133
- */
134
- verifyWebhook(mode: string, token: string, challenge: string): string | null {
135
- if (mode === 'subscribe' && token === this.config.verifyToken) {
136
- return challenge;
137
- }
138
- return null;
139
- }
140
-
141
- /**
142
- * Handle incoming webhook from WhatsApp Business API.
143
- * Call this from your HTTP server when receiving POST to your webhook URL.
144
- */
145
- async handleWebhook(body: WhatsAppWebhookBody): Promise<void> {
146
- if (body.object !== 'whatsapp_business_account') return;
147
-
148
- for (const entry of body.entry) {
149
- for (const change of entry.changes) {
150
- if (change.field !== 'messages') continue;
151
-
152
- const messages = change.value.messages;
153
- if (!messages) continue;
154
-
155
- const contacts = change.value.contacts;
156
-
157
- for (const msg of messages) {
158
- const contact = contacts?.find((c) => c.wa_id === msg.from);
159
- await this.handleMessage(msg, contact);
160
- }
161
- }
162
- }
163
- }
164
-
165
- private async handleMessage(
166
- msg: WhatsAppIncomingMessage,
167
- contact?: { profile: { name: string }; wa_id: string },
168
- ): Promise<void> {
169
- // Only process text messages for now
170
- if (msg.type !== 'text' && msg.type !== 'image' && msg.type !== 'document') return;
171
-
172
- // Check allowed numbers
173
- if (this.config.allowedNumbers?.length && !this.config.allowedNumbers.includes(msg.from)) {
174
- audit('message.filtered', { channelType: 'whatsapp', senderId: msg.from, reason: 'number_not_allowed' });
175
- return;
176
- }
177
-
178
- const inbound = this.toInboundMessage(msg, contact);
179
-
180
- audit('message.received', {
181
- channelType: 'whatsapp',
182
- senderId: inbound.senderId,
183
- channelId: inbound.channelId,
184
- });
185
-
186
- if (this.messageHandler) {
187
- try {
188
- await this.messageHandler(inbound);
189
- } catch (error) {
190
- this.errorHandler?.(error instanceof Error ? error : new Error(String(error)));
191
- }
192
- }
193
- }
194
-
195
- private toInboundMessage(
196
- msg: WhatsAppIncomingMessage,
197
- contact?: { profile: { name: string }; wa_id: string },
198
- ): InboundMessage {
199
- let content = '';
200
- const attachments: InboundMessage['attachments'] = [];
201
-
202
- switch (msg.type) {
203
- case 'text':
204
- content = msg.text?.body || '';
205
- break;
206
- case 'image':
207
- content = msg.image?.caption || '';
208
- attachments.push({
209
- type: 'image',
210
- mimeType: msg.image?.mime_type,
211
- });
212
- break;
213
- case 'document':
214
- content = msg.document?.caption || '';
215
- attachments.push({
216
- type: 'file',
217
- mimeType: msg.document?.mime_type,
218
- filename: msg.document?.filename,
219
- });
220
- break;
221
- case 'audio':
222
- attachments.push({
223
- type: 'audio',
224
- mimeType: msg.audio?.mime_type,
225
- });
226
- break;
227
- case 'video':
228
- attachments.push({
229
- type: 'video',
230
- mimeType: msg.video?.mime_type,
231
- });
232
- break;
233
- }
234
-
235
- return {
236
- id: msg.id,
237
- channelType: 'whatsapp',
238
- channelId: msg.from,
239
- senderId: msg.from,
240
- senderName: contact?.profile.name,
241
- content,
242
- timestamp: parseInt(msg.timestamp, 10) * 1000,
243
- replyToId: msg.context?.id,
244
- attachments: attachments.length > 0 ? attachments : undefined,
245
- raw: msg,
246
- };
247
- }
248
-
249
- async send(channelId: string, message: OutboundMessage): Promise<SendResult> {
250
- try {
251
- const chunks = this.chunkMessage(message.content);
252
- let lastMessageId: string | undefined;
253
-
254
- for (const chunk of chunks) {
255
- const body: Record<string, unknown> = {
256
- messaging_product: 'whatsapp',
257
- recipient_type: 'individual',
258
- to: channelId,
259
- type: 'text',
260
- text: { body: chunk },
261
- };
262
-
263
- if (message.replyToId) {
264
- body.context = { message_id: message.replyToId };
265
- }
266
-
267
- const response = await fetch(
268
- `${GRAPH_API_BASE}/${this.config.phoneNumberId}/messages`,
269
- {
270
- method: 'POST',
271
- headers: {
272
- 'Authorization': `Bearer ${this.config.accessToken}`,
273
- 'Content-Type': 'application/json',
274
- },
275
- body: JSON.stringify(body),
276
- },
277
- );
278
-
279
- if (!response.ok) {
280
- const errorText = await response.text().catch(() => response.statusText);
281
- throw new Error(`WhatsApp API error ${response.status}: ${errorText}`);
282
- }
283
-
284
- const result = await response.json() as WhatsAppSendResponse;
285
- lastMessageId = result.messages?.[0]?.id;
286
- }
287
-
288
- audit('message.sent', {
289
- channelType: 'whatsapp',
290
- channelId,
291
- messageId: lastMessageId,
292
- });
293
-
294
- return { success: true, messageId: lastMessageId };
295
- } catch (error) {
296
- const errorMessage = error instanceof Error ? error.message : String(error);
297
- audit('channel.error', {
298
- channelType: 'whatsapp',
299
- action: 'send',
300
- error: errorMessage,
301
- });
302
- return { success: false, error: errorMessage };
303
- }
304
- }
305
-
306
- private chunkMessage(content: string): string[] {
307
- if (content.length <= MAX_MESSAGE_LENGTH) {
308
- return [content];
309
- }
310
-
311
- const chunks: string[] = [];
312
- let remaining = content;
313
-
314
- while (remaining.length > 0) {
315
- if (remaining.length <= MAX_MESSAGE_LENGTH) {
316
- chunks.push(remaining);
317
- break;
318
- }
319
-
320
- let breakPoint = remaining.lastIndexOf('\n', MAX_MESSAGE_LENGTH);
321
- if (breakPoint === -1 || breakPoint < MAX_MESSAGE_LENGTH / 2) {
322
- breakPoint = remaining.lastIndexOf(' ', MAX_MESSAGE_LENGTH);
323
- }
324
- if (breakPoint === -1 || breakPoint < MAX_MESSAGE_LENGTH / 2) {
325
- breakPoint = MAX_MESSAGE_LENGTH;
326
- }
327
-
328
- chunks.push(remaining.slice(0, breakPoint));
329
- remaining = remaining.slice(breakPoint).trimStart();
330
- }
331
-
332
- return chunks;
333
- }
334
-
335
- onMessage(handler: (message: InboundMessage) => Promise<void>): void {
336
- this.messageHandler = handler;
337
- }
338
-
339
- onError(handler: (error: Error) => void): void {
340
- this.errorHandler = handler;
341
- }
342
- }
@@ -1,319 +0,0 @@
1
- import { createHmac } from 'node:crypto';
2
- import { audit } from '@auxiora/audit';
3
- import type {
4
- ChannelAdapter,
5
- InboundMessage,
6
- OutboundMessage,
7
- SendResult,
8
- } from '../types.js';
9
-
10
- export interface ZaloAdapterConfig {
11
- oaAccessToken: string;
12
- oaSecretKey: string;
13
- allowedUserIds?: string[];
14
- }
15
-
16
- interface ZaloWebhookEvent {
17
- app_id: string;
18
- sender: {
19
- id: string;
20
- };
21
- recipient: {
22
- id: string;
23
- };
24
- event_name: string;
25
- message?: {
26
- msg_id: string;
27
- text?: string;
28
- attachments?: Array<{
29
- type: string;
30
- payload: {
31
- url?: string;
32
- thumbnail?: string;
33
- id?: string;
34
- size?: number;
35
- name?: string;
36
- type?: string;
37
- };
38
- }>;
39
- quote_msg_id?: string;
40
- };
41
- timestamp: string;
42
- }
43
-
44
- interface ZaloSendResponse {
45
- error: number;
46
- message: string;
47
- data?: {
48
- message_id: string;
49
- };
50
- }
51
-
52
- interface ZaloUserProfileResponse {
53
- error: number;
54
- message: string;
55
- data?: {
56
- display_name: string;
57
- user_id: string;
58
- avatar?: string;
59
- };
60
- }
61
-
62
- const ZALO_OA_API_BASE = 'https://openapi.zalo.me/v3.0/oa';
63
- const MAX_MESSAGE_LENGTH = 2000;
64
-
65
- export class ZaloAdapter implements ChannelAdapter {
66
- readonly type = 'zalo' as const;
67
- readonly name = 'Zalo';
68
-
69
- private config: ZaloAdapterConfig;
70
- private messageHandler?: (message: InboundMessage) => Promise<void>;
71
- private errorHandler?: (error: Error) => void;
72
- private connected = false;
73
- private userNameCache: Map<string, string> = new Map();
74
-
75
- constructor(config: ZaloAdapterConfig) {
76
- this.config = config;
77
- }
78
-
79
- async connect(): Promise<void> {
80
- // Verify credentials by fetching OA info
81
- const response = await fetch(`${ZALO_OA_API_BASE}/getoa`, {
82
- headers: {
83
- access_token: this.config.oaAccessToken,
84
- },
85
- });
86
-
87
- if (!response.ok) {
88
- throw new Error(
89
- `Failed to verify Zalo credentials: ${response.status} ${response.statusText}`,
90
- );
91
- }
92
-
93
- const result = (await response.json()) as { error: number; message: string };
94
- if (result.error !== 0) {
95
- throw new Error(`Zalo API error: ${result.message}`);
96
- }
97
-
98
- this.connected = true;
99
-
100
- audit('channel.connected', { channelType: 'zalo' });
101
- }
102
-
103
- async disconnect(): Promise<void> {
104
- this.connected = false;
105
- this.userNameCache.clear();
106
- audit('channel.disconnected', { channelType: 'zalo' });
107
- }
108
-
109
- isConnected(): boolean {
110
- return this.connected;
111
- }
112
-
113
- /**
114
- * Verify the webhook signature from Zalo.
115
- * Returns true if the signature is valid.
116
- */
117
- verifyWebhookSignature(body: string, signature: string): boolean {
118
- const hmac = createHmac('sha256', this.config.oaSecretKey);
119
- hmac.update(body);
120
- const expected = hmac.digest('hex');
121
- return expected === signature;
122
- }
123
-
124
- /**
125
- * Handle incoming webhook from Zalo OA.
126
- * Call this from your HTTP server when receiving POST to your webhook URL.
127
- */
128
- async handleWebhook(event: ZaloWebhookEvent): Promise<void> {
129
- if (event.event_name !== 'user_send_text' && event.event_name !== 'user_send_image') {
130
- return;
131
- }
132
-
133
- if (!event.message) return;
134
-
135
- // Check allowed users
136
- const senderId = event.sender.id;
137
- if (
138
- this.config.allowedUserIds?.length &&
139
- !this.config.allowedUserIds.includes(senderId)
140
- ) {
141
- audit('message.filtered', {
142
- channelType: 'zalo',
143
- senderId,
144
- reason: 'user_not_allowed',
145
- });
146
- return;
147
- }
148
-
149
- const senderName = await this.getUserName(senderId);
150
- const inbound = this.toInboundMessage(event, senderName);
151
-
152
- audit('message.received', {
153
- channelType: 'zalo',
154
- senderId: inbound.senderId,
155
- channelId: inbound.channelId,
156
- });
157
-
158
- if (this.messageHandler) {
159
- try {
160
- await this.messageHandler(inbound);
161
- } catch (error) {
162
- this.errorHandler?.(error instanceof Error ? error : new Error(String(error)));
163
- }
164
- }
165
- }
166
-
167
- private async getUserName(userId: string): Promise<string | undefined> {
168
- const cached = this.userNameCache.get(userId);
169
- if (cached) return cached;
170
-
171
- try {
172
- const response = await fetch(
173
- `${ZALO_OA_API_BASE}/getprofile?data=${encodeURIComponent(JSON.stringify({ user_id: userId }))}`,
174
- {
175
- headers: {
176
- access_token: this.config.oaAccessToken,
177
- },
178
- },
179
- );
180
-
181
- if (response.ok) {
182
- const result = (await response.json()) as ZaloUserProfileResponse;
183
- if (result.data?.display_name) {
184
- this.userNameCache.set(userId, result.data.display_name);
185
- return result.data.display_name;
186
- }
187
- }
188
- } catch {
189
- // Silently fail - name is optional
190
- }
191
-
192
- return undefined;
193
- }
194
-
195
- private toInboundMessage(
196
- event: ZaloWebhookEvent,
197
- senderName?: string,
198
- ): InboundMessage {
199
- const message = event.message!;
200
- const content = message.text || '';
201
-
202
- return {
203
- id: message.msg_id,
204
- channelType: 'zalo',
205
- channelId: event.sender.id,
206
- senderId: event.sender.id,
207
- senderName,
208
- content,
209
- timestamp: parseInt(event.timestamp, 10),
210
- replyToId: message.quote_msg_id,
211
- attachments: message.attachments?.map((a) => ({
212
- type: a.type === 'image'
213
- ? ('image' as const)
214
- : a.type === 'audio'
215
- ? ('audio' as const)
216
- : a.type === 'video'
217
- ? ('video' as const)
218
- : ('file' as const),
219
- url: a.payload.url || a.payload.thumbnail,
220
- mimeType: a.payload.type,
221
- filename: a.payload.name,
222
- size: a.payload.size,
223
- })),
224
- raw: event,
225
- };
226
- }
227
-
228
- async send(channelId: string, message: OutboundMessage): Promise<SendResult> {
229
- try {
230
- const chunks = this.chunkMessage(message.content);
231
- let lastMessageId: string | undefined;
232
-
233
- for (const chunk of chunks) {
234
- const body: Record<string, unknown> = {
235
- recipient: { user_id: channelId },
236
- message: { text: chunk },
237
- };
238
-
239
- if (message.replyToId) {
240
- body.quote_message_id = message.replyToId;
241
- }
242
-
243
- const response = await fetch(`${ZALO_OA_API_BASE}/message/cs`, {
244
- method: 'POST',
245
- headers: {
246
- 'Content-Type': 'application/json',
247
- access_token: this.config.oaAccessToken,
248
- },
249
- body: JSON.stringify(body),
250
- });
251
-
252
- if (!response.ok) {
253
- const errorText = await response.text().catch(() => response.statusText);
254
- throw new Error(`Zalo API error ${response.status}: ${errorText}`);
255
- }
256
-
257
- const result = (await response.json()) as ZaloSendResponse;
258
- if (result.error !== 0) {
259
- throw new Error(`Zalo send error: ${result.message}`);
260
- }
261
-
262
- lastMessageId = result.data?.message_id;
263
- }
264
-
265
- audit('message.sent', {
266
- channelType: 'zalo',
267
- channelId,
268
- messageId: lastMessageId,
269
- });
270
-
271
- return { success: true, messageId: lastMessageId };
272
- } catch (error) {
273
- const errorMessage = error instanceof Error ? error.message : String(error);
274
- audit('channel.error', {
275
- channelType: 'zalo',
276
- action: 'send',
277
- error: errorMessage,
278
- });
279
- return { success: false, error: errorMessage };
280
- }
281
- }
282
-
283
- private chunkMessage(content: string): string[] {
284
- if (content.length <= MAX_MESSAGE_LENGTH) {
285
- return [content];
286
- }
287
-
288
- const chunks: string[] = [];
289
- let remaining = content;
290
-
291
- while (remaining.length > 0) {
292
- if (remaining.length <= MAX_MESSAGE_LENGTH) {
293
- chunks.push(remaining);
294
- break;
295
- }
296
-
297
- let breakPoint = remaining.lastIndexOf('\n', MAX_MESSAGE_LENGTH);
298
- if (breakPoint === -1 || breakPoint < MAX_MESSAGE_LENGTH / 2) {
299
- breakPoint = remaining.lastIndexOf(' ', MAX_MESSAGE_LENGTH);
300
- }
301
- if (breakPoint === -1 || breakPoint < MAX_MESSAGE_LENGTH / 2) {
302
- breakPoint = MAX_MESSAGE_LENGTH;
303
- }
304
-
305
- chunks.push(remaining.slice(0, breakPoint));
306
- remaining = remaining.slice(breakPoint).trimStart();
307
- }
308
-
309
- return chunks;
310
- }
311
-
312
- onMessage(handler: (message: InboundMessage) => Promise<void>): void {
313
- this.messageHandler = handler;
314
- }
315
-
316
- onError(handler: (error: Error) => void): void {
317
- this.errorHandler = handler;
318
- }
319
- }