@elqnt/chat 3.1.0 → 3.5.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.
- package/README.md +6 -0
- package/SKILL.md +531 -0
- package/TIER2_AUTH.md +258 -0
- package/dist/api/index.d.mts +11 -0
- package/dist/api/index.d.ts +11 -0
- package/dist/api/index.js +27 -9
- package/dist/api/index.js.map +1 -1
- package/dist/api/index.mjs +27 -10
- package/dist/api/index.mjs.map +1 -1
- package/dist/hooks/index.d.mts +75 -37
- package/dist/hooks/index.d.ts +75 -37
- package/dist/hooks/index.js +116 -16
- package/dist/hooks/index.js.map +1 -1
- package/dist/hooks/index.mjs +115 -15
- package/dist/hooks/index.mjs.map +1 -1
- package/dist/index.d.mts +2 -7
- package/dist/index.d.ts +2 -7
- package/dist/index.js +14 -1420
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +9 -1413
- package/dist/index.mjs.map +1 -1
- package/dist/models/index.d.mts +89 -4
- package/dist/models/index.d.ts +89 -4
- package/dist/models/index.js +12 -1
- package/dist/models/index.js.map +1 -1
- package/dist/models/index.mjs +8 -2
- package/dist/models/index.mjs.map +1 -1
- package/dist/transport/index.d.mts +2 -2
- package/dist/transport/index.d.ts +2 -2
- package/dist/transport/index.js +100 -11
- package/dist/transport/index.js.map +1 -1
- package/dist/transport/index.mjs +100 -12
- package/dist/transport/index.mjs.map +1 -1
- package/dist/{types-CQHtUQ6p.d.mts → types-CLtQA6Qq.d.mts} +16 -0
- package/dist/{types-7UNI1iYv.d.ts → types-CxibhkqW.d.ts} +16 -0
- package/package.json +8 -6
package/dist/transport/index.mjs
CHANGED
|
@@ -1,6 +1,16 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
1
|
// transport/types.ts
|
|
2
|
+
async function resolveTransportToken(cfg) {
|
|
3
|
+
if (!cfg) return void 0;
|
|
4
|
+
if (cfg.getToken) {
|
|
5
|
+
try {
|
|
6
|
+
const t = await cfg.getToken();
|
|
7
|
+
return t ?? void 0;
|
|
8
|
+
} catch {
|
|
9
|
+
return void 0;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
return cfg.token ?? void 0;
|
|
13
|
+
}
|
|
4
14
|
function createLogger(debug = false) {
|
|
5
15
|
return {
|
|
6
16
|
debug: debug ? console.log.bind(console, "[chat]") : () => {
|
|
@@ -81,9 +91,12 @@ function createSSETransport(options = {}) {
|
|
|
81
91
|
}
|
|
82
92
|
const url = `${config.baseUrl}/${endpoint}`;
|
|
83
93
|
logger.debug(`POST ${endpoint}`, body);
|
|
94
|
+
const token = await resolveTransportToken(config);
|
|
95
|
+
const headers = { "Content-Type": "application/json" };
|
|
96
|
+
if (token) headers["Authorization"] = `Bearer ${token}`;
|
|
84
97
|
const response = await fetch(url, {
|
|
85
98
|
method: "POST",
|
|
86
|
-
headers
|
|
99
|
+
headers,
|
|
87
100
|
body: JSON.stringify(body)
|
|
88
101
|
});
|
|
89
102
|
if (!response.ok) {
|
|
@@ -109,6 +122,7 @@ function createSSETransport(options = {}) {
|
|
|
109
122
|
function setupEventListeners(es) {
|
|
110
123
|
es.addEventListener("message", handleMessage);
|
|
111
124
|
const eventTypes = [
|
|
125
|
+
"error",
|
|
112
126
|
"reconnected",
|
|
113
127
|
"typing",
|
|
114
128
|
"stopped_typing",
|
|
@@ -118,21 +132,53 @@ function createSSETransport(options = {}) {
|
|
|
118
132
|
"human_agent_left",
|
|
119
133
|
"chat_ended",
|
|
120
134
|
"chat_updated",
|
|
135
|
+
"chat_removed",
|
|
121
136
|
"load_chat_response",
|
|
122
137
|
"new_chat_created",
|
|
123
138
|
"show_csat_survey",
|
|
124
139
|
"csat_response",
|
|
125
140
|
"user_suggested_actions",
|
|
141
|
+
"user_suggested_action_selected",
|
|
126
142
|
"agent_execution_started",
|
|
127
143
|
"agent_execution_ended",
|
|
128
144
|
"agent_context_update",
|
|
145
|
+
"load_agent_context_response",
|
|
129
146
|
"plan_pending_approval",
|
|
147
|
+
"plan_approved",
|
|
148
|
+
"plan_rejected",
|
|
130
149
|
"step_started",
|
|
131
150
|
"step_completed",
|
|
132
151
|
"step_failed",
|
|
133
152
|
"plan_completed",
|
|
134
153
|
"skills_changed",
|
|
135
|
-
"summary_update"
|
|
154
|
+
"summary_update",
|
|
155
|
+
"message_status_update",
|
|
156
|
+
"delivered",
|
|
157
|
+
"read",
|
|
158
|
+
"sync_metadata_response",
|
|
159
|
+
"sync_user_session_response",
|
|
160
|
+
"client_action",
|
|
161
|
+
"client_action_callback",
|
|
162
|
+
"attachment_processing_started",
|
|
163
|
+
"attachment_processing_progress",
|
|
164
|
+
"attachment_processing_complete",
|
|
165
|
+
"attachment_processing_error",
|
|
166
|
+
"observer_joined",
|
|
167
|
+
"observer_left",
|
|
168
|
+
"block_user",
|
|
169
|
+
"message_edited",
|
|
170
|
+
"message_deleted",
|
|
171
|
+
"message_reaction",
|
|
172
|
+
"user_presence_changed",
|
|
173
|
+
"online_users",
|
|
174
|
+
"get_agents_response",
|
|
175
|
+
"room_created",
|
|
176
|
+
"room_updated",
|
|
177
|
+
"room_deleted",
|
|
178
|
+
"rooms_response",
|
|
179
|
+
"room_user_joined",
|
|
180
|
+
"room_user_left",
|
|
181
|
+
"user_invited"
|
|
136
182
|
];
|
|
137
183
|
eventTypes.forEach((type) => {
|
|
138
184
|
es.addEventListener(type, handleMessage);
|
|
@@ -177,20 +223,38 @@ function createSSETransport(options = {}) {
|
|
|
177
223
|
reconnectTimeout = void 0;
|
|
178
224
|
}
|
|
179
225
|
state = retryCount > 0 ? "reconnecting" : "connecting";
|
|
226
|
+
const connectToken = await resolveTransportToken(cfg);
|
|
180
227
|
return new Promise((resolve, reject) => {
|
|
181
228
|
const connectionStart = Date.now();
|
|
182
|
-
const
|
|
229
|
+
const streamParams = new URLSearchParams({
|
|
230
|
+
orgId: cfg.orgId,
|
|
231
|
+
userId: cfg.userId,
|
|
232
|
+
clientType: cfg.clientType
|
|
233
|
+
});
|
|
234
|
+
if (cfg.chatKey) streamParams.set("chatId", cfg.chatKey);
|
|
235
|
+
if (connectToken) streamParams.set("token", connectToken);
|
|
236
|
+
const url = `${cfg.baseUrl}/stream?${streamParams.toString()}`;
|
|
183
237
|
logger.debug("Connecting to:", url);
|
|
184
238
|
const es = new EventSource(url);
|
|
185
239
|
es.onopen = () => {
|
|
186
240
|
const connectionTime = Date.now() - connectionStart;
|
|
187
241
|
logger.info(`Connected in ${connectionTime}ms`);
|
|
242
|
+
const wasReconnect = retryCount > 0;
|
|
188
243
|
state = "connected";
|
|
189
244
|
error = void 0;
|
|
190
245
|
retryCount = 0;
|
|
191
246
|
metrics.connectedAt = Date.now();
|
|
192
247
|
metrics.latency = connectionTime;
|
|
193
248
|
setupEventListeners(es);
|
|
249
|
+
if (wasReconnect) {
|
|
250
|
+
emit({
|
|
251
|
+
type: "transport_reconnected",
|
|
252
|
+
orgId: cfg.orgId,
|
|
253
|
+
chatKey: cfg.chatKey || "",
|
|
254
|
+
userId: cfg.userId,
|
|
255
|
+
timestamp: Date.now()
|
|
256
|
+
});
|
|
257
|
+
}
|
|
194
258
|
resolve();
|
|
195
259
|
};
|
|
196
260
|
es.onerror = () => {
|
|
@@ -422,9 +486,12 @@ function createFetchSSETransport(options = {}) {
|
|
|
422
486
|
}
|
|
423
487
|
const url = `${config.baseUrl}/${endpoint}`;
|
|
424
488
|
logger.debug(`POST ${endpoint}`, body);
|
|
489
|
+
const token = await resolveTransportToken(config);
|
|
490
|
+
const headers = { "Content-Type": "application/json" };
|
|
491
|
+
if (token) headers["Authorization"] = `Bearer ${token}`;
|
|
425
492
|
const response = await customFetch(url, {
|
|
426
493
|
method: "POST",
|
|
427
|
-
headers
|
|
494
|
+
headers,
|
|
428
495
|
body: JSON.stringify(body)
|
|
429
496
|
});
|
|
430
497
|
if (!response.ok) {
|
|
@@ -461,15 +528,25 @@ function createFetchSSETransport(options = {}) {
|
|
|
461
528
|
return events;
|
|
462
529
|
}
|
|
463
530
|
async function startStream(cfg) {
|
|
464
|
-
const
|
|
531
|
+
const streamParams = new URLSearchParams({
|
|
532
|
+
orgId: cfg.orgId,
|
|
533
|
+
userId: cfg.userId,
|
|
534
|
+
clientType: cfg.clientType
|
|
535
|
+
});
|
|
536
|
+
if (cfg.chatKey) streamParams.set("chatId", cfg.chatKey);
|
|
537
|
+
const token = await resolveTransportToken(cfg);
|
|
538
|
+
if (token) streamParams.set("token", token);
|
|
539
|
+
const url = `${cfg.baseUrl}/stream?${streamParams.toString()}`;
|
|
465
540
|
logger.debug("Connecting to:", url);
|
|
466
541
|
abortController = new AbortController();
|
|
542
|
+
const streamHeaders = {
|
|
543
|
+
Accept: "text/event-stream",
|
|
544
|
+
"Cache-Control": "no-cache"
|
|
545
|
+
};
|
|
546
|
+
if (token) streamHeaders["Authorization"] = `Bearer ${token}`;
|
|
467
547
|
const response = await customFetch(url, {
|
|
468
548
|
method: "GET",
|
|
469
|
-
headers:
|
|
470
|
-
Accept: "text/event-stream",
|
|
471
|
-
"Cache-Control": "no-cache"
|
|
472
|
-
},
|
|
549
|
+
headers: streamHeaders,
|
|
473
550
|
signal: abortController.signal
|
|
474
551
|
});
|
|
475
552
|
if (!response.ok) {
|
|
@@ -567,6 +644,7 @@ function createFetchSSETransport(options = {}) {
|
|
|
567
644
|
state = retryCount > 0 ? "reconnecting" : "connecting";
|
|
568
645
|
const connectionStart = Date.now();
|
|
569
646
|
try {
|
|
647
|
+
const wasReconnect = retryCount > 0;
|
|
570
648
|
await startStream(cfg);
|
|
571
649
|
const connectionTime = Date.now() - connectionStart;
|
|
572
650
|
logger.info(`Connected in ${connectionTime}ms`);
|
|
@@ -575,6 +653,15 @@ function createFetchSSETransport(options = {}) {
|
|
|
575
653
|
retryCount = 0;
|
|
576
654
|
metrics.connectedAt = Date.now();
|
|
577
655
|
metrics.latency = connectionTime;
|
|
656
|
+
if (wasReconnect) {
|
|
657
|
+
emit({
|
|
658
|
+
type: "transport_reconnected",
|
|
659
|
+
orgId: cfg.orgId,
|
|
660
|
+
chatKey: cfg.chatKey || "",
|
|
661
|
+
userId: cfg.userId,
|
|
662
|
+
timestamp: Date.now()
|
|
663
|
+
});
|
|
664
|
+
}
|
|
578
665
|
} catch (err) {
|
|
579
666
|
const connectError = {
|
|
580
667
|
code: "CONNECTION_FAILED",
|
|
@@ -616,7 +703,8 @@ function createFetchSSETransport(options = {}) {
|
|
|
616
703
|
orgId: event.orgId,
|
|
617
704
|
chatKey: event.chatKey,
|
|
618
705
|
userId: event.userId,
|
|
619
|
-
message: event.message
|
|
706
|
+
message: event.message,
|
|
707
|
+
...event.data ? { data: event.data } : {}
|
|
620
708
|
});
|
|
621
709
|
break;
|
|
622
710
|
case "typing":
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../transport/types.ts","../../transport/sse.ts","../../transport/sse-fetch.ts","../../transport/whatsapp.ts"],"sourcesContent":["/**\n * Transport Abstraction Layer Types\n *\n * Platform-agnostic transport interfaces for chat communication.\n * Supports browser SSE, React Native fetch-based SSE, and extensible\n * transports like WhatsApp Business API.\n */\n\nimport type { Chat, ChatEvent, ChatMessage } from \"../models\";\n\n/**\n * Transport connection state\n */\nexport type TransportState =\n | \"disconnected\"\n | \"connecting\"\n | \"connected\"\n | \"reconnecting\";\n\n/**\n * Transport error with retry information\n */\nexport interface TransportError {\n code:\n | \"CONNECTION_FAILED\"\n | \"PARSE_ERROR\"\n | \"SEND_FAILED\"\n | \"TIMEOUT\"\n | \"NETWORK_ERROR\"\n | \"AUTH_FAILED\";\n message: string;\n retryable: boolean;\n timestamp: number;\n originalError?: unknown;\n}\n\n/**\n * Configuration for transport connection\n */\nexport interface TransportConfig {\n /** Base URL for the chat server */\n baseUrl: string;\n /** Organization ID */\n orgId: string;\n /** User ID for authentication */\n userId: string;\n /** Client type for routing */\n clientType: \"customer\" | \"humanAgent\" | \"observer\";\n /** Optional current chat key for reconnection */\n chatKey?: string;\n /** Enable debug logging */\n debug?: boolean;\n}\n\n/**\n * Retry configuration for connection attempts\n */\nexport interface RetryConfig {\n /** Maximum number of retry attempts */\n maxRetries?: number;\n /** Initial retry intervals in milliseconds */\n intervals?: number[];\n /** Multiplier for exponential backoff */\n backoffMultiplier?: number;\n /** Maximum backoff time in milliseconds */\n maxBackoffTime?: number;\n}\n\n/**\n * Connection metrics for monitoring\n */\nexport interface ConnectionMetrics {\n /** Round-trip latency in milliseconds */\n latency: number;\n /** Total messages sent */\n messagesSent: number;\n /** Total messages received */\n messagesReceived: number;\n /** Messages waiting to be sent */\n messagesQueued: number;\n /** Number of reconnection attempts */\n reconnectCount: number;\n /** Last error encountered */\n lastError?: TransportError;\n /** Timestamp when connection was established */\n connectedAt?: number;\n /** Timestamp of last message */\n lastMessageAt?: number;\n /** Current transport type */\n transportType?: string;\n}\n\n/**\n * Event handler function type\n */\nexport type EventHandler<T = ChatEvent> = (event: T) => void;\n\n/**\n * Unsubscribe function returned by event subscription\n */\nexport type Unsubscribe = () => void;\n\n/**\n * Send message options\n */\nexport interface SendMessageOptions {\n /** Message content */\n content: string;\n /** Optional attachments */\n attachments?: unknown[];\n /** Optional message metadata */\n metadata?: Record<string, unknown>;\n}\n\n/**\n * Create chat options\n */\nexport interface CreateChatOptions {\n /** Organization ID */\n orgId: string;\n /** User ID */\n userId: string;\n /** Optional chat metadata */\n metadata?: Record<string, unknown>;\n}\n\n/**\n * Load chat options\n */\nexport interface LoadChatOptions {\n /** Organization ID */\n orgId: string;\n /** Chat key to load */\n chatKey: string;\n /** User ID */\n userId: string;\n}\n\n/**\n * End chat options\n */\nexport interface EndChatOptions {\n /** Organization ID */\n orgId: string;\n /** Chat key to end */\n chatKey: string;\n /** User ID */\n userId: string;\n /** Optional end reason */\n reason?: string;\n}\n\n/**\n * Response from creating a new chat\n */\nexport interface CreateChatResponse {\n /** The key of the created chat */\n chatKey: string;\n}\n\n/**\n * Response from loading a chat\n */\nexport interface LoadChatResponse {\n /** The loaded chat object */\n chat: Chat;\n /** Agent ID if applicable */\n agentId?: string;\n}\n\n/**\n * Core transport interface\n *\n * Implementations must handle:\n * - Connection lifecycle (connect, disconnect, reconnect)\n * - Message sending (POST requests)\n * - Event receiving (SSE stream)\n * - Error handling and recovery\n */\nexport interface ChatTransport {\n /**\n * Connect to the chat server\n * @param config - Transport configuration\n * @returns Promise that resolves when connected\n */\n connect(config: TransportConfig): Promise<void>;\n\n /**\n * Disconnect from the server\n * @param intentional - Whether disconnect was user-initiated\n */\n disconnect(intentional?: boolean): void;\n\n /**\n * Send a chat event to the server\n * @param event - Chat event to send\n */\n send(event: ChatEvent): Promise<void>;\n\n /**\n * Send a message in the current chat\n * @param message - Message to send\n */\n sendMessage(message: ChatMessage): Promise<void>;\n\n /**\n * Create a new chat and return the chat key directly\n * @param options - Create chat options\n * @returns Promise with the created chat key\n */\n createChat(options: CreateChatOptions): Promise<CreateChatResponse>;\n\n /**\n * Load an existing chat and return the chat data directly\n * @param options - Load chat options\n * @returns Promise with the loaded chat data\n */\n loadChatData(options: LoadChatOptions): Promise<LoadChatResponse>;\n\n /**\n * Subscribe to incoming events\n * @param handler - Event handler function\n * @returns Unsubscribe function\n */\n onMessage(handler: EventHandler): Unsubscribe;\n\n /**\n * Subscribe to specific event type\n * @param eventType - Type of event to listen for\n * @param handler - Event handler function\n * @returns Unsubscribe function\n */\n on(eventType: string, handler: EventHandler): Unsubscribe;\n\n /**\n * Get current connection state\n */\n getState(): TransportState;\n\n /**\n * Get connection metrics\n */\n getMetrics(): ConnectionMetrics;\n\n /**\n * Get last error\n */\n getError(): TransportError | undefined;\n\n /**\n * Clear error state\n */\n clearError(): void;\n}\n\n/**\n * Transport factory function type\n */\nexport type TransportFactory = (config?: Partial<TransportConfig>) => ChatTransport;\n\n/**\n * Logger interface for transport debugging\n */\nexport interface TransportLogger {\n debug: (message: string, ...args: unknown[]) => void;\n info: (message: string, ...args: unknown[]) => void;\n warn: (message: string, ...args: unknown[]) => void;\n error: (message: string, ...args: unknown[]) => void;\n}\n\n/**\n * Create a default logger\n * @param debug - Enable debug logging\n */\nexport function createLogger(debug: boolean = false): TransportLogger {\n return {\n debug: debug ? console.log.bind(console, \"[chat]\") : () => {},\n info: console.info.bind(console, \"[chat]\"),\n warn: console.warn.bind(console, \"[chat]\"),\n error: console.error.bind(console, \"[chat]\"),\n };\n}\n\n/**\n * Default retry configuration\n */\nexport const DEFAULT_RETRY_CONFIG: Required<RetryConfig> = {\n maxRetries: 10,\n intervals: [1000, 2000, 5000],\n backoffMultiplier: 1.5,\n maxBackoffTime: 30000,\n};\n\n/**\n * Calculate retry interval with exponential backoff\n * @param retryCount - Current retry attempt number\n * @param config - Retry configuration\n */\nexport function calculateRetryInterval(\n retryCount: number,\n config: RetryConfig = DEFAULT_RETRY_CONFIG\n): number {\n const {\n intervals = DEFAULT_RETRY_CONFIG.intervals,\n backoffMultiplier = DEFAULT_RETRY_CONFIG.backoffMultiplier,\n maxBackoffTime = DEFAULT_RETRY_CONFIG.maxBackoffTime,\n } = config;\n\n if (retryCount < intervals.length) {\n return intervals[retryCount];\n }\n\n const baseInterval = intervals[intervals.length - 1] || 5000;\n const backoffTime =\n baseInterval * Math.pow(backoffMultiplier, retryCount - intervals.length + 1);\n return Math.min(backoffTime, maxBackoffTime);\n}\n","/**\n * SSE Transport (Browser)\n *\n * Uses native EventSource for receiving server events and fetch POST for sending.\n * This is the default transport for browser environments.\n *\n * @example\n * ```typescript\n * import { createSSETransport } from \"@elqnt/chat/transport\";\n *\n * const transport = createSSETransport();\n * await transport.connect({ baseUrl, orgId, userId, clientType: \"customer\" });\n * ```\n */\n\nimport type { Chat, ChatEvent, ChatMessage } from \"../models\";\nimport type {\n ChatTransport,\n TransportConfig,\n TransportState,\n TransportError,\n ConnectionMetrics,\n EventHandler,\n Unsubscribe,\n RetryConfig,\n TransportLogger,\n CreateChatOptions,\n CreateChatResponse,\n LoadChatOptions,\n LoadChatResponse,\n} from \"./types\";\nimport { createLogger, calculateRetryInterval, DEFAULT_RETRY_CONFIG } from \"./types\";\n\n/**\n * SSE Transport options\n */\nexport interface SSETransportOptions {\n /** Retry configuration */\n retryConfig?: RetryConfig;\n /** Enable debug logging */\n debug?: boolean;\n /** Custom logger */\n logger?: TransportLogger;\n}\n\n/**\n * Create an SSE transport for browser environments\n */\nexport function createSSETransport(options: SSETransportOptions = {}): ChatTransport {\n const {\n retryConfig = DEFAULT_RETRY_CONFIG,\n debug = false,\n logger = createLogger(debug),\n } = options;\n\n // Internal state\n let eventSource: EventSource | undefined;\n let config: TransportConfig | undefined;\n let state: TransportState = \"disconnected\";\n let error: TransportError | undefined;\n let retryCount = 0;\n let reconnectTimeout: ReturnType<typeof setTimeout> | undefined;\n let intentionalDisconnect = false;\n\n // Metrics\n const metrics: ConnectionMetrics = {\n latency: 0,\n messagesSent: 0,\n messagesReceived: 0,\n messagesQueued: 0,\n reconnectCount: 0,\n transportType: \"sse\",\n };\n\n // Event handlers\n const globalHandlers = new Set<EventHandler>();\n const typeHandlers = new Map<string, Set<EventHandler>>();\n\n // Helper to emit events\n function emit(event: ChatEvent): void {\n // Update metrics\n metrics.messagesReceived++;\n metrics.lastMessageAt = Date.now();\n\n // Call global handlers\n globalHandlers.forEach((handler) => {\n try {\n handler(event);\n } catch (err) {\n logger.error(\"Error in message handler:\", err);\n }\n });\n\n // Call type-specific handlers\n const handlers = typeHandlers.get(event.type);\n if (handlers) {\n handlers.forEach((handler) => {\n try {\n handler(event);\n } catch (err) {\n logger.error(`Error in ${event.type} handler:`, err);\n }\n });\n }\n }\n\n // REST API helper\n async function sendRest(endpoint: string, body: Record<string, unknown>): Promise<unknown> {\n if (!config) {\n throw new Error(\"Transport not connected\");\n }\n\n const url = `${config.baseUrl}/${endpoint}`;\n logger.debug(`POST ${endpoint}`, body);\n\n const response = await fetch(url, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify(body),\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n throw new Error(`API error: ${response.status} - ${errorText}`);\n }\n\n const json = await response.json() as { success?: boolean; data?: unknown };\n\n // Backend wraps responses in { success: true, data: {...} }\n // Unwrap to return just the data\n if (json && typeof json === \"object\" && \"data\" in json) {\n return json.data;\n }\n\n return json;\n }\n\n // Handle SSE message\n function handleMessage(event: MessageEvent): void {\n if (!event.data || event.data === \"\") return;\n\n try {\n const data = JSON.parse(event.data) as ChatEvent;\n logger.debug(\"Received:\", data.type);\n emit(data);\n } catch (err) {\n logger.error(\"Failed to parse SSE message:\", err);\n }\n }\n\n // Setup SSE event listeners\n function setupEventListeners(es: EventSource): void {\n // Generic message handler\n es.addEventListener(\"message\", handleMessage);\n\n // Add handlers for known event types\n const eventTypes = [\n \"reconnected\", \"typing\", \"stopped_typing\", \"waiting\",\n \"waiting_for_agent\", \"human_agent_joined\", \"human_agent_left\",\n \"chat_ended\", \"chat_updated\", \"load_chat_response\",\n \"new_chat_created\", \"show_csat_survey\", \"csat_response\",\n \"user_suggested_actions\", \"agent_execution_started\",\n \"agent_execution_ended\", \"agent_context_update\",\n \"plan_pending_approval\", \"step_started\", \"step_completed\",\n \"step_failed\", \"plan_completed\", \"skills_changed\", \"summary_update\",\n ];\n\n eventTypes.forEach((type) => {\n es.addEventListener(type, handleMessage);\n });\n }\n\n // Schedule reconnect\n function scheduleReconnect(): void {\n if (intentionalDisconnect || !config) return;\n\n const maxRetries = retryConfig.maxRetries ?? DEFAULT_RETRY_CONFIG.maxRetries;\n if (retryCount >= maxRetries) {\n logger.error(`Max retries (${maxRetries}) exceeded`);\n error = {\n code: \"CONNECTION_FAILED\",\n message: `Max retries (${maxRetries}) exceeded`,\n retryable: false,\n timestamp: Date.now(),\n };\n return;\n }\n\n const interval = calculateRetryInterval(retryCount, retryConfig);\n retryCount++;\n metrics.reconnectCount++;\n\n logger.info(`Reconnecting in ${interval}ms (attempt ${retryCount})`);\n state = \"reconnecting\";\n\n reconnectTimeout = setTimeout(() => {\n if (config) {\n transport.connect(config).catch((err) => {\n logger.error(\"Reconnect failed:\", err);\n });\n }\n }, interval);\n }\n\n const transport: ChatTransport = {\n async connect(cfg: TransportConfig): Promise<void> {\n config = cfg;\n intentionalDisconnect = false;\n\n // Clean up existing connection\n if (eventSource) {\n eventSource.close();\n eventSource = undefined;\n }\n if (reconnectTimeout) {\n clearTimeout(reconnectTimeout);\n reconnectTimeout = undefined;\n }\n\n state = retryCount > 0 ? \"reconnecting\" : \"connecting\";\n\n return new Promise((resolve, reject) => {\n const connectionStart = Date.now();\n const url = `${cfg.baseUrl}/stream?orgId=${cfg.orgId}&userId=${cfg.userId}&clientType=${cfg.clientType}${cfg.chatKey ? `&chatId=${cfg.chatKey}` : \"\"}`;\n\n logger.debug(\"Connecting to:\", url);\n const es = new EventSource(url);\n\n es.onopen = () => {\n const connectionTime = Date.now() - connectionStart;\n logger.info(`Connected in ${connectionTime}ms`);\n\n state = \"connected\";\n error = undefined;\n retryCount = 0;\n metrics.connectedAt = Date.now();\n metrics.latency = connectionTime;\n\n setupEventListeners(es);\n resolve();\n };\n\n es.onerror = () => {\n if (es.readyState === EventSource.CLOSED) {\n const sseError: TransportError = {\n code: \"CONNECTION_FAILED\",\n message: \"SSE connection failed\",\n retryable: true,\n timestamp: Date.now(),\n };\n error = sseError;\n metrics.lastError = sseError;\n state = \"disconnected\";\n\n if (!intentionalDisconnect) {\n scheduleReconnect();\n }\n\n // Only reject if this is the initial connection attempt\n if (retryCount === 0) {\n reject(sseError);\n }\n }\n };\n\n eventSource = es;\n });\n },\n\n disconnect(intentional = true): void {\n logger.info(\"Disconnecting\", { intentional });\n intentionalDisconnect = intentional;\n\n if (reconnectTimeout) {\n clearTimeout(reconnectTimeout);\n reconnectTimeout = undefined;\n }\n\n if (eventSource) {\n eventSource.close();\n eventSource = undefined;\n }\n\n state = \"disconnected\";\n retryCount = 0;\n },\n\n async send(event: ChatEvent): Promise<void> {\n if (!config) {\n throw new Error(\"Transport not connected\");\n }\n\n // Map event types to REST endpoints\n switch (event.type) {\n case \"message\":\n await sendRest(\"send\", {\n orgId: event.orgId,\n chatKey: event.chatKey,\n userId: event.userId,\n message: event.message,\n ...(event.data ? { data: event.data } : {}),\n });\n break;\n\n case \"typing\":\n await sendRest(\"typing\", {\n orgId: event.orgId,\n chatKey: event.chatKey,\n userId: event.userId,\n typing: true,\n });\n break;\n\n case \"stopped_typing\":\n await sendRest(\"typing\", {\n orgId: event.orgId,\n chatKey: event.chatKey,\n userId: event.userId,\n typing: false,\n });\n break;\n\n case \"load_chat\":\n // Deprecated: Use loadChatData() instead for direct response\n await sendRest(\"load\", {\n orgId: event.orgId,\n chatKey: event.chatKey,\n userId: event.userId,\n });\n break;\n\n case \"new_chat\":\n // Deprecated: Use createChat() instead for direct response\n await sendRest(\"create\", {\n orgId: event.orgId,\n userId: event.userId,\n metadata: event.data,\n });\n break;\n\n case \"end_chat\":\n await sendRest(\"end\", {\n orgId: event.orgId,\n chatKey: event.chatKey,\n userId: event.userId,\n data: event.data,\n });\n break;\n\n default:\n // Generic event endpoint for other types\n await sendRest(\"event\", {\n type: event.type,\n orgId: event.orgId,\n chatKey: event.chatKey,\n userId: event.userId,\n data: event.data,\n });\n }\n\n metrics.messagesSent++;\n },\n\n async sendMessage(message: ChatMessage): Promise<void> {\n if (!config) {\n throw new Error(\"Transport not connected\");\n }\n\n await sendRest(\"send\", {\n orgId: config.orgId,\n chatKey: config.chatKey,\n userId: config.userId,\n message,\n });\n\n metrics.messagesSent++;\n },\n\n async createChat(options: CreateChatOptions): Promise<CreateChatResponse> {\n if (!config) {\n throw new Error(\"Transport not connected\");\n }\n\n const response = await sendRest(\"create\", {\n orgId: options.orgId,\n userId: options.userId,\n metadata: options.metadata,\n }) as { chatKey?: string };\n\n if (!response?.chatKey) {\n throw new Error(\"Failed to create chat: no chatKey returned\");\n }\n\n return { chatKey: response.chatKey };\n },\n\n async loadChatData(options: LoadChatOptions): Promise<LoadChatResponse> {\n if (!config) {\n throw new Error(\"Transport not connected\");\n }\n\n const response = await sendRest(\"load\", {\n orgId: options.orgId,\n chatKey: options.chatKey,\n userId: options.userId,\n }) as { chat?: unknown; agentId?: string };\n\n if (!response?.chat) {\n throw new Error(\"Failed to load chat: no chat data returned\");\n }\n\n return {\n chat: response.chat as Chat,\n agentId: response.agentId,\n };\n },\n\n onMessage(handler: EventHandler): Unsubscribe {\n globalHandlers.add(handler);\n return () => globalHandlers.delete(handler);\n },\n\n on(eventType: string, handler: EventHandler): Unsubscribe {\n if (!typeHandlers.has(eventType)) {\n typeHandlers.set(eventType, new Set());\n }\n typeHandlers.get(eventType)!.add(handler);\n return () => {\n const handlers = typeHandlers.get(eventType);\n if (handlers) {\n handlers.delete(handler);\n if (handlers.size === 0) {\n typeHandlers.delete(eventType);\n }\n }\n };\n },\n\n getState(): TransportState {\n return state;\n },\n\n getMetrics(): ConnectionMetrics {\n return { ...metrics };\n },\n\n getError(): TransportError | undefined {\n return error;\n },\n\n clearError(): void {\n error = undefined;\n },\n };\n\n return transport;\n}\n","/**\n * SSE Transport (Fetch-based)\n *\n * Uses fetch with ReadableStream for receiving server events and fetch POST for sending.\n * Designed for React Native and environments without native EventSource support.\n *\n * @example\n * ```typescript\n * import { createFetchSSETransport } from \"@elqnt/chat/transport\";\n *\n * const transport = createFetchSSETransport();\n * await transport.connect({ baseUrl, orgId, userId, clientType: \"customer\" });\n * ```\n */\n\nimport type { Chat, ChatEvent, ChatMessage } from \"../models\";\nimport type {\n ChatTransport,\n TransportConfig,\n TransportState,\n TransportError,\n ConnectionMetrics,\n EventHandler,\n Unsubscribe,\n RetryConfig,\n TransportLogger,\n CreateChatOptions,\n CreateChatResponse,\n LoadChatOptions,\n LoadChatResponse,\n} from \"./types\";\nimport { createLogger, calculateRetryInterval, DEFAULT_RETRY_CONFIG } from \"./types\";\n\n/**\n * Fetch SSE Transport options\n */\nexport interface FetchSSETransportOptions {\n /** Retry configuration */\n retryConfig?: RetryConfig;\n /** Enable debug logging */\n debug?: boolean;\n /** Custom logger */\n logger?: TransportLogger;\n /** Custom fetch implementation (useful for React Native) */\n customFetch?: typeof fetch;\n}\n\n/**\n * Create a fetch-based SSE transport for React Native and polyfill environments\n */\nexport function createFetchSSETransport(options: FetchSSETransportOptions = {}): ChatTransport {\n const {\n retryConfig = DEFAULT_RETRY_CONFIG,\n debug = false,\n logger = createLogger(debug),\n customFetch = fetch,\n } = options;\n\n // Internal state\n let abortController: AbortController | undefined;\n let config: TransportConfig | undefined;\n let state: TransportState = \"disconnected\";\n let error: TransportError | undefined;\n let retryCount = 0;\n let reconnectTimeout: ReturnType<typeof setTimeout> | undefined;\n let intentionalDisconnect = false;\n\n // Metrics\n const metrics: ConnectionMetrics = {\n latency: 0,\n messagesSent: 0,\n messagesReceived: 0,\n messagesQueued: 0,\n reconnectCount: 0,\n transportType: \"sse-fetch\",\n };\n\n // Event handlers\n const globalHandlers = new Set<EventHandler>();\n const typeHandlers = new Map<string, Set<EventHandler>>();\n\n // Helper to emit events\n function emit(event: ChatEvent): void {\n metrics.messagesReceived++;\n metrics.lastMessageAt = Date.now();\n\n globalHandlers.forEach((handler) => {\n try {\n handler(event);\n } catch (err) {\n logger.error(\"Error in message handler:\", err);\n }\n });\n\n const handlers = typeHandlers.get(event.type);\n if (handlers) {\n handlers.forEach((handler) => {\n try {\n handler(event);\n } catch (err) {\n logger.error(`Error in ${event.type} handler:`, err);\n }\n });\n }\n }\n\n // REST API helper\n async function sendRest(endpoint: string, body: Record<string, unknown>): Promise<unknown> {\n if (!config) {\n throw new Error(\"Transport not connected\");\n }\n\n const url = `${config.baseUrl}/${endpoint}`;\n logger.debug(`POST ${endpoint}`, body);\n\n const response = await customFetch(url, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify(body),\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n throw new Error(`API error: ${response.status} - ${errorText}`);\n }\n\n const json = await response.json() as { success?: boolean; data?: unknown };\n\n // Backend wraps responses in { success: true, data: {...} }\n // Unwrap to return just the data\n if (json && typeof json === \"object\" && \"data\" in json) {\n return json.data;\n }\n\n return json;\n }\n\n // Parse SSE data from a text chunk\n function parseSSEChunk(chunk: string): ChatEvent[] {\n const events: ChatEvent[] = [];\n const lines = chunk.split(\"\\n\");\n\n let eventType = \"message\";\n let data = \"\";\n\n for (const line of lines) {\n if (line.startsWith(\"event:\")) {\n eventType = line.slice(6).trim();\n } else if (line.startsWith(\"data:\")) {\n data = line.slice(5).trim();\n } else if (line === \"\" && data) {\n // End of event\n try {\n const parsed = JSON.parse(data) as ChatEvent;\n events.push(parsed);\n } catch {\n logger.warn(\"Failed to parse SSE data:\", data);\n }\n eventType = \"message\";\n data = \"\";\n }\n }\n\n return events;\n }\n\n // Start SSE stream using fetch\n async function startStream(cfg: TransportConfig): Promise<void> {\n const url = `${cfg.baseUrl}/stream?orgId=${cfg.orgId}&userId=${cfg.userId}&clientType=${cfg.clientType}${cfg.chatKey ? `&chatId=${cfg.chatKey}` : \"\"}`;\n\n logger.debug(\"Connecting to:\", url);\n\n abortController = new AbortController();\n\n const response = await customFetch(url, {\n method: \"GET\",\n headers: {\n Accept: \"text/event-stream\",\n \"Cache-Control\": \"no-cache\",\n },\n signal: abortController.signal,\n });\n\n if (!response.ok) {\n throw new Error(`Stream connection failed: ${response.status}`);\n }\n\n if (!response.body) {\n throw new Error(\"Response body is null - ReadableStream not supported\");\n }\n\n const reader = response.body.getReader();\n const decoder = new TextDecoder();\n let buffer = \"\";\n\n // Read stream in background\n const readStream = async () => {\n try {\n while (true) {\n const { done, value } = await reader.read();\n\n if (done) {\n logger.info(\"Stream ended\");\n break;\n }\n\n buffer += decoder.decode(value, { stream: true });\n\n // Process complete events in buffer\n const lastNewline = buffer.lastIndexOf(\"\\n\\n\");\n if (lastNewline !== -1) {\n const complete = buffer.slice(0, lastNewline + 2);\n buffer = buffer.slice(lastNewline + 2);\n\n const events = parseSSEChunk(complete);\n events.forEach(emit);\n }\n }\n } catch (err) {\n if ((err as Error).name === \"AbortError\") {\n logger.debug(\"Stream aborted\");\n return;\n }\n logger.error(\"Stream error:\", err);\n throw err;\n }\n\n // Stream closed - handle reconnection\n if (!intentionalDisconnect) {\n state = \"disconnected\";\n scheduleReconnect();\n }\n };\n\n // Start reading in background\n readStream().catch((err) => {\n if (!intentionalDisconnect) {\n error = {\n code: \"CONNECTION_FAILED\",\n message: err.message,\n retryable: true,\n timestamp: Date.now(),\n originalError: err,\n };\n metrics.lastError = error;\n state = \"disconnected\";\n scheduleReconnect();\n }\n });\n }\n\n // Schedule reconnect\n function scheduleReconnect(): void {\n if (intentionalDisconnect || !config) return;\n\n const maxRetries = retryConfig.maxRetries ?? DEFAULT_RETRY_CONFIG.maxRetries;\n if (retryCount >= maxRetries) {\n logger.error(`Max retries (${maxRetries}) exceeded`);\n error = {\n code: \"CONNECTION_FAILED\",\n message: `Max retries (${maxRetries}) exceeded`,\n retryable: false,\n timestamp: Date.now(),\n };\n return;\n }\n\n const interval = calculateRetryInterval(retryCount, retryConfig);\n retryCount++;\n metrics.reconnectCount++;\n\n logger.info(`Reconnecting in ${interval}ms (attempt ${retryCount})`);\n state = \"reconnecting\";\n\n reconnectTimeout = setTimeout(() => {\n if (config) {\n transport.connect(config).catch((err) => {\n logger.error(\"Reconnect failed:\", err);\n });\n }\n }, interval);\n }\n\n const transport: ChatTransport = {\n async connect(cfg: TransportConfig): Promise<void> {\n config = cfg;\n intentionalDisconnect = false;\n\n // Clean up existing connection\n if (abortController) {\n abortController.abort();\n abortController = undefined;\n }\n if (reconnectTimeout) {\n clearTimeout(reconnectTimeout);\n reconnectTimeout = undefined;\n }\n\n state = retryCount > 0 ? \"reconnecting\" : \"connecting\";\n const connectionStart = Date.now();\n\n try {\n await startStream(cfg);\n\n const connectionTime = Date.now() - connectionStart;\n logger.info(`Connected in ${connectionTime}ms`);\n\n state = \"connected\";\n error = undefined;\n retryCount = 0;\n metrics.connectedAt = Date.now();\n metrics.latency = connectionTime;\n } catch (err) {\n const connectError: TransportError = {\n code: \"CONNECTION_FAILED\",\n message: (err as Error).message,\n retryable: true,\n timestamp: Date.now(),\n originalError: err,\n };\n error = connectError;\n metrics.lastError = connectError;\n state = \"disconnected\";\n\n if (!intentionalDisconnect) {\n scheduleReconnect();\n }\n\n throw connectError;\n }\n },\n\n disconnect(intentional = true): void {\n logger.info(\"Disconnecting\", { intentional });\n intentionalDisconnect = intentional;\n\n if (reconnectTimeout) {\n clearTimeout(reconnectTimeout);\n reconnectTimeout = undefined;\n }\n\n if (abortController) {\n abortController.abort();\n abortController = undefined;\n }\n\n state = \"disconnected\";\n retryCount = 0;\n },\n\n async send(event: ChatEvent): Promise<void> {\n if (!config) {\n throw new Error(\"Transport not connected\");\n }\n\n switch (event.type) {\n case \"message\":\n await sendRest(\"send\", {\n orgId: event.orgId,\n chatKey: event.chatKey,\n userId: event.userId,\n message: event.message,\n });\n break;\n\n case \"typing\":\n await sendRest(\"typing\", {\n orgId: event.orgId,\n chatKey: event.chatKey,\n userId: event.userId,\n typing: true,\n });\n break;\n\n case \"stopped_typing\":\n await sendRest(\"typing\", {\n orgId: event.orgId,\n chatKey: event.chatKey,\n userId: event.userId,\n typing: false,\n });\n break;\n\n case \"load_chat\":\n await sendRest(\"load\", {\n orgId: event.orgId,\n chatKey: event.chatKey,\n userId: event.userId,\n });\n break;\n\n case \"new_chat\":\n await sendRest(\"create\", {\n orgId: event.orgId,\n userId: event.userId,\n metadata: event.data,\n });\n break;\n\n case \"end_chat\":\n await sendRest(\"end\", {\n orgId: event.orgId,\n chatKey: event.chatKey,\n userId: event.userId,\n data: event.data,\n });\n break;\n\n default:\n await sendRest(\"event\", {\n type: event.type,\n orgId: event.orgId,\n chatKey: event.chatKey,\n userId: event.userId,\n data: event.data,\n });\n }\n\n metrics.messagesSent++;\n },\n\n async sendMessage(message: ChatMessage): Promise<void> {\n if (!config) {\n throw new Error(\"Transport not connected\");\n }\n\n await sendRest(\"send\", {\n orgId: config.orgId,\n chatKey: config.chatKey,\n userId: config.userId,\n message,\n });\n\n metrics.messagesSent++;\n },\n\n async createChat(options: CreateChatOptions): Promise<CreateChatResponse> {\n if (!config) {\n throw new Error(\"Transport not connected\");\n }\n\n const response = await sendRest(\"create\", {\n orgId: options.orgId,\n userId: options.userId,\n metadata: options.metadata,\n }) as { chatKey?: string };\n\n if (!response?.chatKey) {\n throw new Error(\"Failed to create chat: no chatKey returned\");\n }\n\n return { chatKey: response.chatKey };\n },\n\n async loadChatData(options: LoadChatOptions): Promise<LoadChatResponse> {\n if (!config) {\n throw new Error(\"Transport not connected\");\n }\n\n const response = await sendRest(\"load\", {\n orgId: options.orgId,\n chatKey: options.chatKey,\n userId: options.userId,\n }) as { chat?: unknown; agentId?: string };\n\n if (!response?.chat) {\n throw new Error(\"Failed to load chat: no chat data returned\");\n }\n\n return {\n chat: response.chat as Chat,\n agentId: response.agentId,\n };\n },\n\n onMessage(handler: EventHandler): Unsubscribe {\n globalHandlers.add(handler);\n return () => globalHandlers.delete(handler);\n },\n\n on(eventType: string, handler: EventHandler): Unsubscribe {\n if (!typeHandlers.has(eventType)) {\n typeHandlers.set(eventType, new Set());\n }\n typeHandlers.get(eventType)!.add(handler);\n return () => {\n const handlers = typeHandlers.get(eventType);\n if (handlers) {\n handlers.delete(handler);\n if (handlers.size === 0) {\n typeHandlers.delete(eventType);\n }\n }\n };\n },\n\n getState(): TransportState {\n return state;\n },\n\n getMetrics(): ConnectionMetrics {\n return { ...metrics };\n },\n\n getError(): TransportError | undefined {\n return error;\n },\n\n clearError(): void {\n error = undefined;\n },\n };\n\n return transport;\n}\n","/**\n * WhatsApp Business API Transport (Stub)\n *\n * This module provides the interface for integrating with WhatsApp Business API.\n * The implementation connects to a webhook receiver that bridges WhatsApp messages\n * to the chat system.\n *\n * ## Integration Architecture\n *\n * ```\n * WhatsApp User → WhatsApp Cloud API → Your Webhook → Chat Service → SSE to Admin\n * ↓\n * NATS Events\n * ```\n *\n * ## Setup Requirements\n *\n * 1. Register a WhatsApp Business Account\n * 2. Create a Meta Business App with WhatsApp integration\n * 3. Configure webhook URL pointing to your chat service\n * 4. Set up message templates for outbound messages\n *\n * ## Backend Integration\n *\n * Your backend should expose webhook endpoints that:\n * - Verify webhook (GET /webhook with hub.verify_token)\n * - Receive messages (POST /webhook with message payload)\n * - Map WhatsApp user IDs to chat users\n * - Create/load chats for new conversations\n *\n * @example Backend Webhook Handler (Go)\n * ```go\n * func HandleWhatsAppWebhook(w http.ResponseWriter, r *http.Request) {\n * if r.Method == \"GET\" {\n * // Webhook verification\n * verifyToken := r.URL.Query().Get(\"hub.verify_token\")\n * if verifyToken == os.Getenv(\"WHATSAPP_VERIFY_TOKEN\") {\n * w.Write([]byte(r.URL.Query().Get(\"hub.challenge\")))\n * return\n * }\n * http.Error(w, \"Invalid verify token\", 403)\n * return\n * }\n *\n * // Handle incoming message\n * var payload WhatsAppPayload\n * json.NewDecoder(r.Body).Decode(&payload)\n *\n * // Map to chat event and publish to NATS\n * chatEvent := mapWhatsAppToChatEvent(payload)\n * natsClient.Publish(\"chat.events\", chatEvent)\n * }\n * ```\n *\n * @see https://developers.facebook.com/docs/whatsapp/cloud-api\n */\n\nimport type {\n ChatTransport,\n TransportConfig,\n TransportState,\n TransportError,\n ConnectionMetrics,\n EventHandler,\n Unsubscribe,\n TransportLogger,\n CreateChatOptions,\n CreateChatResponse,\n LoadChatOptions,\n LoadChatResponse,\n} from \"./types\";\nimport type { Chat, ChatEvent, ChatMessage, Attachment, AttachmentTypeTS } from \"../models\";\nimport { createLogger } from \"./types\";\n\n/**\n * WhatsApp transport configuration\n */\nexport interface WhatsAppTransportConfig extends TransportConfig {\n /** WhatsApp Business API access token */\n accessToken: string;\n /** Phone number ID from Meta Business */\n phoneNumberId: string;\n /** Webhook verify token for setup */\n verifyToken?: string;\n}\n\n/**\n * WhatsApp transport options\n */\nexport interface WhatsAppTransportOptions {\n /** Enable debug logging */\n debug?: boolean;\n /** Custom logger */\n logger?: TransportLogger;\n}\n\n/**\n * WhatsApp message types\n */\nexport type WhatsAppMessageType =\n | \"text\"\n | \"image\"\n | \"document\"\n | \"audio\"\n | \"video\"\n | \"sticker\"\n | \"location\"\n | \"contacts\"\n | \"interactive\"\n | \"template\";\n\n/**\n * Create a WhatsApp Business API transport\n *\n * NOTE: This is a stub implementation. The actual WhatsApp integration\n * happens on the backend via webhooks. This transport is for:\n * - Type definitions\n * - Documentation\n * - Future client-side WhatsApp Web integration\n */\nexport function createWhatsAppTransport(\n options: WhatsAppTransportOptions = {}\n): ChatTransport {\n const { debug = false, logger = createLogger(debug) } = options;\n\n let state: TransportState = \"disconnected\";\n let error: TransportError | undefined;\n let config: WhatsAppTransportConfig | undefined;\n\n const metrics: ConnectionMetrics = {\n latency: 0,\n messagesSent: 0,\n messagesReceived: 0,\n messagesQueued: 0,\n reconnectCount: 0,\n transportType: \"whatsapp\",\n };\n\n const globalHandlers = new Set<EventHandler>();\n const typeHandlers = new Map<string, Set<EventHandler>>();\n\n const transport: ChatTransport = {\n async connect(cfg: TransportConfig): Promise<void> {\n logger.warn(\n \"WhatsApp transport is a stub. WhatsApp integration happens via backend webhooks.\"\n );\n logger.info(\n \"See transport/whatsapp.ts for integration documentation.\"\n );\n\n config = cfg as WhatsAppTransportConfig;\n\n // In a real implementation, this would:\n // 1. Validate WhatsApp Business API credentials\n // 2. Register webhook endpoints\n // 3. Set up SSE connection to receive webhook-relayed events\n\n state = \"connected\";\n metrics.connectedAt = Date.now();\n },\n\n disconnect(): void {\n state = \"disconnected\";\n },\n\n async send(event: ChatEvent): Promise<void> {\n if (!config) {\n throw new Error(\"Transport not connected\");\n }\n\n logger.warn(\"WhatsApp send is a stub. Implement backend webhook relay.\");\n\n // In a real implementation, this would:\n // 1. Convert ChatEvent to WhatsApp message format\n // 2. Call WhatsApp Cloud API to send message\n // 3. Handle delivery receipts\n\n metrics.messagesSent++;\n },\n\n async sendMessage(message: ChatMessage): Promise<void> {\n if (!config) {\n throw new Error(\"Transport not connected\");\n }\n\n logger.warn(\n \"WhatsApp sendMessage is a stub. Implement backend webhook relay.\"\n );\n\n // In a real implementation:\n // POST https://graph.facebook.com/v17.0/{phone_number_id}/messages\n // {\n // \"messaging_product\": \"whatsapp\",\n // \"to\": \"{recipient_phone}\",\n // \"type\": \"text\",\n // \"text\": { \"body\": message.content }\n // }\n\n metrics.messagesSent++;\n },\n\n async createChat(options: CreateChatOptions): Promise<CreateChatResponse> {\n logger.warn(\n \"WhatsApp createChat is a stub. WhatsApp chats are created via incoming webhooks.\"\n );\n\n // In WhatsApp, chats are typically initiated by users messaging first.\n // This stub returns a placeholder - real implementation would create\n // a chat record on the backend for outbound messaging.\n return { chatKey: `whatsapp_${options.userId}_${Date.now()}` };\n },\n\n async loadChatData(options: LoadChatOptions): Promise<LoadChatResponse> {\n logger.warn(\n \"WhatsApp loadChatData is a stub. Implement backend API call.\"\n );\n\n // In a real implementation, this would fetch chat data from the backend\n return {\n chat: {\n orgId: options.orgId,\n key: options.chatKey,\n title: \"WhatsApp Chat\",\n messages: [],\n lastUpdated: Date.now(),\n startTime: Date.now(),\n users: [],\n status: \"active\",\n aiEngaged: false,\n humanAgentEngaged: false,\n isWaiting: false,\n isWaitingForAgent: false,\n } as Chat,\n };\n },\n\n onMessage(handler: EventHandler): Unsubscribe {\n globalHandlers.add(handler);\n return () => globalHandlers.delete(handler);\n },\n\n on(eventType: string, handler: EventHandler): Unsubscribe {\n if (!typeHandlers.has(eventType)) {\n typeHandlers.set(eventType, new Set());\n }\n typeHandlers.get(eventType)!.add(handler);\n return () => {\n const handlers = typeHandlers.get(eventType);\n if (handlers) {\n handlers.delete(handler);\n if (handlers.size === 0) {\n typeHandlers.delete(eventType);\n }\n }\n };\n },\n\n getState(): TransportState {\n return state;\n },\n\n getMetrics(): ConnectionMetrics {\n return { ...metrics };\n },\n\n getError(): TransportError | undefined {\n return error;\n },\n\n clearError(): void {\n error = undefined;\n },\n };\n\n return transport;\n}\n\n/**\n * Helper: Convert WhatsApp webhook payload to ChatEvent\n *\n * Use this in your backend webhook handler to map incoming\n * WhatsApp messages to the chat system format.\n */\nexport interface WhatsAppWebhookPayload {\n object: \"whatsapp_business_account\";\n entry: Array<{\n id: string;\n changes: Array<{\n value: {\n messaging_product: \"whatsapp\";\n metadata: {\n display_phone_number: string;\n phone_number_id: string;\n };\n contacts?: Array<{\n profile: { name: string };\n wa_id: string;\n }>;\n messages?: Array<{\n from: string;\n id: string;\n timestamp: string;\n type: WhatsAppMessageType;\n text?: { body: string };\n image?: { id: string; mime_type: string; sha256: string };\n document?: { id: string; mime_type: string; filename: string };\n audio?: { id: string; mime_type: string };\n video?: { id: string; mime_type: string };\n location?: { latitude: number; longitude: number; name?: string };\n }>;\n statuses?: Array<{\n id: string;\n status: \"sent\" | \"delivered\" | \"read\" | \"failed\";\n timestamp: string;\n recipient_id: string;\n }>;\n };\n }>;\n }>;\n}\n\n/**\n * Example: Map WhatsApp payload to ChatEvent (for backend use)\n *\n * @example\n * ```typescript\n * // In your backend webhook handler\n * import { mapWhatsAppToChatEvent } from \"@elqnt/chat/transport\";\n *\n * app.post(\"/webhook/whatsapp\", (req, res) => {\n * const events = mapWhatsAppToChatEvent(req.body, orgId);\n * events.forEach(event => publishToChatService(event));\n * res.sendStatus(200);\n * });\n * ```\n */\nexport function mapWhatsAppToChatEvent(\n payload: WhatsAppWebhookPayload,\n orgId: string\n): ChatEvent[] {\n const events: ChatEvent[] = [];\n\n for (const entry of payload.entry) {\n for (const change of entry.changes) {\n const value = change.value;\n\n // Handle incoming messages\n if (value.messages) {\n for (const msg of value.messages) {\n const contact = value.contacts?.find((c) => c.wa_id === msg.from);\n\n events.push({\n type: \"message\",\n orgId,\n chatKey: `wa_${msg.from}`, // Use phone number as chat key\n userId: msg.from,\n timestamp: parseInt(msg.timestamp) * 1000,\n message: {\n id: msg.id,\n role: \"user\",\n content: msg.text?.body || \"\",\n time: parseInt(msg.timestamp) * 1000,\n status: \"delivered\",\n senderId: msg.from,\n senderName: contact?.profile.name,\n createdAt: parseInt(msg.timestamp) * 1000,\n // Map attachments if present\n attachments: mapWhatsAppAttachments(msg),\n },\n });\n }\n }\n\n // Handle message status updates\n if (value.statuses) {\n for (const status of value.statuses) {\n events.push({\n type: \"message_status_update\",\n orgId,\n chatKey: `wa_${status.recipient_id}`,\n userId: status.recipient_id,\n timestamp: parseInt(status.timestamp) * 1000,\n data: {\n messageId: status.id,\n status: status.status,\n },\n });\n }\n }\n }\n }\n\n return events;\n}\n\n// Helper to map WhatsApp attachments\nfunction mapWhatsAppAttachments(msg: {\n type: WhatsAppMessageType;\n image?: { id: string };\n document?: { id: string; filename: string };\n audio?: { id: string };\n video?: { id: string };\n location?: { latitude: number; longitude: number; name?: string };\n}): Attachment[] | undefined {\n switch (msg.type) {\n case \"image\":\n return msg.image\n ? [{ type: \"image\" as AttachmentTypeTS, url: `whatsapp://media/${msg.image.id}` }]\n : undefined;\n case \"document\":\n return msg.document\n ? [\n {\n type: \"document\" as AttachmentTypeTS,\n url: `whatsapp://media/${msg.document.id}`,\n },\n ]\n : undefined;\n case \"audio\":\n return msg.audio\n ? [{ type: \"audio\" as AttachmentTypeTS, url: `whatsapp://media/${msg.audio.id}` }]\n : undefined;\n case \"video\":\n return msg.video\n ? [{ type: \"video\" as AttachmentTypeTS, url: `whatsapp://media/${msg.video.id}` }]\n : undefined;\n case \"location\":\n return msg.location\n ? [\n {\n type: \"location\" as AttachmentTypeTS,\n url: `geo:${msg.location.latitude},${msg.location.longitude}`,\n },\n ]\n : undefined;\n default:\n return undefined;\n }\n}\n"],"mappings":"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","names":["options","options","options"]}
|
|
1
|
+
{"version":3,"sources":["../../transport/types.ts","../../transport/sse.ts","../../transport/sse-fetch.ts","../../transport/whatsapp.ts"],"sourcesContent":["/**\n * Transport Abstraction Layer Types\n *\n * Platform-agnostic transport interfaces for chat communication.\n * Supports browser SSE, React Native fetch-based SSE, and extensible\n * transports like WhatsApp Business API.\n */\n\nimport type { Chat, ChatEvent, ChatMessage } from \"../models\";\n\n/**\n * Transport connection state\n */\nexport type TransportState =\n | \"disconnected\"\n | \"connecting\"\n | \"connected\"\n | \"reconnecting\";\n\n/**\n * Transport error with retry information\n */\nexport interface TransportError {\n code:\n | \"CONNECTION_FAILED\"\n | \"PARSE_ERROR\"\n | \"SEND_FAILED\"\n | \"TIMEOUT\"\n | \"NETWORK_ERROR\"\n | \"AUTH_FAILED\";\n message: string;\n retryable: boolean;\n timestamp: number;\n originalError?: unknown;\n}\n\n/**\n * Configuration for transport connection\n */\nexport interface TransportConfig {\n /** Base URL for the chat server */\n baseUrl: string;\n /** Organization ID */\n orgId: string;\n /** User ID for authentication */\n userId: string;\n /** Client type for routing */\n clientType: \"customer\" | \"humanAgent\" | \"observer\";\n /** Optional current chat key for reconnection */\n chatKey?: string;\n /** Enable debug logging */\n debug?: boolean;\n /**\n * Tier-2 authorization (REQUIRED in production). Chat realtime talks to the\n * chat service DIRECTLY, bypassing the API gateway — so the service has no\n * gateway JWT to trust and authorizes each connection from this signed\n * token instead. The service derives orgId/userId/product from the token's\n * claims, so the token MUST be scoped to the same org/user as this config.\n *\n * Prefer `getToken`: it is invoked before every connect and every REST send,\n * so a long-lived stream keeps working as short-lived tokens roll over. Use\n * the static `token` only for one-shot/non-refreshing callers. Obtain the\n * token from a server route that mints a gateway-shaped JWT (e.g. the app's\n * /api/gateway-token or a dedicated chat-token route). See SKILL.md.\n */\n getToken?: () => Promise<string | null | undefined> | string | null | undefined;\n /** Static signed token. Ignored when `getToken` is provided. */\n token?: string;\n}\n\n/**\n * Resolve the connect/send token from a transport config. Prefers the\n * `getToken` provider (so tokens refresh) and falls back to the static\n * `token`. Never throws — a failing provider resolves to undefined and the\n * request goes out tokenless (the service rejects it when enforcing).\n */\nexport async function resolveTransportToken(\n cfg: { getToken?: TransportConfig[\"getToken\"]; token?: string } | undefined\n): Promise<string | undefined> {\n if (!cfg) return undefined;\n if (cfg.getToken) {\n try {\n const t = await cfg.getToken();\n return t ?? undefined;\n } catch {\n return undefined;\n }\n }\n return cfg.token ?? undefined;\n}\n\n/**\n * Retry configuration for connection attempts\n */\nexport interface RetryConfig {\n /** Maximum number of retry attempts */\n maxRetries?: number;\n /** Initial retry intervals in milliseconds */\n intervals?: number[];\n /** Multiplier for exponential backoff */\n backoffMultiplier?: number;\n /** Maximum backoff time in milliseconds */\n maxBackoffTime?: number;\n}\n\n/**\n * Connection metrics for monitoring\n */\nexport interface ConnectionMetrics {\n /** Round-trip latency in milliseconds */\n latency: number;\n /** Total messages sent */\n messagesSent: number;\n /** Total messages received */\n messagesReceived: number;\n /** Messages waiting to be sent */\n messagesQueued: number;\n /** Number of reconnection attempts */\n reconnectCount: number;\n /** Last error encountered */\n lastError?: TransportError;\n /** Timestamp when connection was established */\n connectedAt?: number;\n /** Timestamp of last message */\n lastMessageAt?: number;\n /** Current transport type */\n transportType?: string;\n}\n\n/**\n * Event handler function type\n */\nexport type EventHandler<T = ChatEvent> = (event: T) => void;\n\n/**\n * Unsubscribe function returned by event subscription\n */\nexport type Unsubscribe = () => void;\n\n/**\n * Send message options\n */\nexport interface SendMessageOptions {\n /** Message content */\n content: string;\n /** Optional attachments */\n attachments?: unknown[];\n /** Optional message metadata */\n metadata?: Record<string, unknown>;\n}\n\n/**\n * Create chat options\n */\nexport interface CreateChatOptions {\n /** Organization ID */\n orgId: string;\n /** User ID */\n userId: string;\n /** Optional chat metadata */\n metadata?: Record<string, unknown>;\n}\n\n/**\n * Load chat options\n */\nexport interface LoadChatOptions {\n /** Organization ID */\n orgId: string;\n /** Chat key to load */\n chatKey: string;\n /** User ID */\n userId: string;\n}\n\n/**\n * End chat options\n */\nexport interface EndChatOptions {\n /** Organization ID */\n orgId: string;\n /** Chat key to end */\n chatKey: string;\n /** User ID */\n userId: string;\n /** Optional end reason */\n reason?: string;\n}\n\n/**\n * Response from creating a new chat\n */\nexport interface CreateChatResponse {\n /** The key of the created chat */\n chatKey: string;\n}\n\n/**\n * Response from loading a chat\n */\nexport interface LoadChatResponse {\n /** The loaded chat object */\n chat: Chat;\n /** Agent ID if applicable */\n agentId?: string;\n}\n\n/**\n * Core transport interface\n *\n * Implementations must handle:\n * - Connection lifecycle (connect, disconnect, reconnect)\n * - Message sending (POST requests)\n * - Event receiving (SSE stream)\n * - Error handling and recovery\n */\nexport interface ChatTransport {\n /**\n * Connect to the chat server\n * @param config - Transport configuration\n * @returns Promise that resolves when connected\n */\n connect(config: TransportConfig): Promise<void>;\n\n /**\n * Disconnect from the server\n * @param intentional - Whether disconnect was user-initiated\n */\n disconnect(intentional?: boolean): void;\n\n /**\n * Send a chat event to the server\n * @param event - Chat event to send\n */\n send(event: ChatEvent): Promise<void>;\n\n /**\n * Send a message in the current chat\n * @param message - Message to send\n */\n sendMessage(message: ChatMessage): Promise<void>;\n\n /**\n * Create a new chat and return the chat key directly\n * @param options - Create chat options\n * @returns Promise with the created chat key\n */\n createChat(options: CreateChatOptions): Promise<CreateChatResponse>;\n\n /**\n * Load an existing chat and return the chat data directly\n * @param options - Load chat options\n * @returns Promise with the loaded chat data\n */\n loadChatData(options: LoadChatOptions): Promise<LoadChatResponse>;\n\n /**\n * Subscribe to incoming events\n * @param handler - Event handler function\n * @returns Unsubscribe function\n */\n onMessage(handler: EventHandler): Unsubscribe;\n\n /**\n * Subscribe to specific event type\n * @param eventType - Type of event to listen for\n * @param handler - Event handler function\n * @returns Unsubscribe function\n */\n on(eventType: string, handler: EventHandler): Unsubscribe;\n\n /**\n * Get current connection state\n */\n getState(): TransportState;\n\n /**\n * Get connection metrics\n */\n getMetrics(): ConnectionMetrics;\n\n /**\n * Get last error\n */\n getError(): TransportError | undefined;\n\n /**\n * Clear error state\n */\n clearError(): void;\n}\n\n/**\n * Transport factory function type\n */\nexport type TransportFactory = (config?: Partial<TransportConfig>) => ChatTransport;\n\n/**\n * Logger interface for transport debugging\n */\nexport interface TransportLogger {\n debug: (message: string, ...args: unknown[]) => void;\n info: (message: string, ...args: unknown[]) => void;\n warn: (message: string, ...args: unknown[]) => void;\n error: (message: string, ...args: unknown[]) => void;\n}\n\n/**\n * Create a default logger\n * @param debug - Enable debug logging\n */\nexport function createLogger(debug: boolean = false): TransportLogger {\n return {\n debug: debug ? console.log.bind(console, \"[chat]\") : () => {},\n info: console.info.bind(console, \"[chat]\"),\n warn: console.warn.bind(console, \"[chat]\"),\n error: console.error.bind(console, \"[chat]\"),\n };\n}\n\n/**\n * Default retry configuration\n */\nexport const DEFAULT_RETRY_CONFIG: Required<RetryConfig> = {\n maxRetries: 10,\n intervals: [1000, 2000, 5000],\n backoffMultiplier: 1.5,\n maxBackoffTime: 30000,\n};\n\n/**\n * Calculate retry interval with exponential backoff\n * @param retryCount - Current retry attempt number\n * @param config - Retry configuration\n */\nexport function calculateRetryInterval(\n retryCount: number,\n config: RetryConfig = DEFAULT_RETRY_CONFIG\n): number {\n const {\n intervals = DEFAULT_RETRY_CONFIG.intervals,\n backoffMultiplier = DEFAULT_RETRY_CONFIG.backoffMultiplier,\n maxBackoffTime = DEFAULT_RETRY_CONFIG.maxBackoffTime,\n } = config;\n\n if (retryCount < intervals.length) {\n return intervals[retryCount];\n }\n\n const baseInterval = intervals[intervals.length - 1] || 5000;\n const backoffTime =\n baseInterval * Math.pow(backoffMultiplier, retryCount - intervals.length + 1);\n return Math.min(backoffTime, maxBackoffTime);\n}\n","/**\n * SSE Transport (Browser)\n *\n * Uses native EventSource for receiving server events and fetch POST for sending.\n * This is the default transport for browser environments.\n *\n * @example\n * ```typescript\n * import { createSSETransport } from \"@elqnt/chat/transport\";\n *\n * const transport = createSSETransport();\n * await transport.connect({ baseUrl, orgId, userId, clientType: \"customer\" });\n * ```\n */\n\nimport type { Chat, ChatEvent, ChatMessage } from \"../models\";\nimport type {\n ChatTransport,\n TransportConfig,\n TransportState,\n TransportError,\n ConnectionMetrics,\n EventHandler,\n Unsubscribe,\n RetryConfig,\n TransportLogger,\n CreateChatOptions,\n CreateChatResponse,\n LoadChatOptions,\n LoadChatResponse,\n} from \"./types\";\nimport { createLogger, calculateRetryInterval, DEFAULT_RETRY_CONFIG, resolveTransportToken } from \"./types\";\n\n/**\n * SSE Transport options\n */\nexport interface SSETransportOptions {\n /** Retry configuration */\n retryConfig?: RetryConfig;\n /** Enable debug logging */\n debug?: boolean;\n /** Custom logger */\n logger?: TransportLogger;\n}\n\n/**\n * Create an SSE transport for browser environments\n */\nexport function createSSETransport(options: SSETransportOptions = {}): ChatTransport {\n const {\n retryConfig = DEFAULT_RETRY_CONFIG,\n debug = false,\n logger = createLogger(debug),\n } = options;\n\n // Internal state\n let eventSource: EventSource | undefined;\n let config: TransportConfig | undefined;\n let state: TransportState = \"disconnected\";\n let error: TransportError | undefined;\n let retryCount = 0;\n let reconnectTimeout: ReturnType<typeof setTimeout> | undefined;\n let intentionalDisconnect = false;\n\n // Metrics\n const metrics: ConnectionMetrics = {\n latency: 0,\n messagesSent: 0,\n messagesReceived: 0,\n messagesQueued: 0,\n reconnectCount: 0,\n transportType: \"sse\",\n };\n\n // Event handlers\n const globalHandlers = new Set<EventHandler>();\n const typeHandlers = new Map<string, Set<EventHandler>>();\n\n // Helper to emit events\n function emit(event: ChatEvent): void {\n // Update metrics\n metrics.messagesReceived++;\n metrics.lastMessageAt = Date.now();\n\n // Call global handlers\n globalHandlers.forEach((handler) => {\n try {\n handler(event);\n } catch (err) {\n logger.error(\"Error in message handler:\", err);\n }\n });\n\n // Call type-specific handlers\n const handlers = typeHandlers.get(event.type);\n if (handlers) {\n handlers.forEach((handler) => {\n try {\n handler(event);\n } catch (err) {\n logger.error(`Error in ${event.type} handler:`, err);\n }\n });\n }\n }\n\n // REST API helper\n async function sendRest(endpoint: string, body: Record<string, unknown>): Promise<unknown> {\n if (!config) {\n throw new Error(\"Transport not connected\");\n }\n\n const url = `${config.baseUrl}/${endpoint}`;\n logger.debug(`POST ${endpoint}`, body);\n\n // Tier-2 authorization: attach the signed token as a bearer header.\n const token = await resolveTransportToken(config);\n const headers: Record<string, string> = { \"Content-Type\": \"application/json\" };\n if (token) headers[\"Authorization\"] = `Bearer ${token}`;\n\n const response = await fetch(url, {\n method: \"POST\",\n headers,\n body: JSON.stringify(body),\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n throw new Error(`API error: ${response.status} - ${errorText}`);\n }\n\n const json = await response.json() as { success?: boolean; data?: unknown };\n\n // Backend wraps responses in { success: true, data: {...} }\n // Unwrap to return just the data\n if (json && typeof json === \"object\" && \"data\" in json) {\n return json.data;\n }\n\n return json;\n }\n\n // Handle SSE message\n function handleMessage(event: MessageEvent): void {\n if (!event.data || event.data === \"\") return;\n\n try {\n const data = JSON.parse(event.data) as ChatEvent;\n logger.debug(\"Received:\", data.type);\n emit(data);\n } catch (err) {\n logger.error(\"Failed to parse SSE message:\", err);\n }\n }\n\n // Setup SSE event listeners\n function setupEventListeners(es: EventSource): void {\n // Generic message handler\n es.addEventListener(\"message\", handleMessage);\n\n // Add handlers for known event types.\n // Note \"error\": the server emits failed-turn notices as named `event:\n // error` SSE events. EventSource also fires a native data-less \"error\"\n // event on connection problems — handleMessage's empty-data guard\n // filters those out, so registering here is safe.\n // The server names EVERY SSE event (event: <type>), and EventSource\n // only fires listeners that were registered by name — any type missing\n // here is silently dropped. Keep this in sync with the server→client\n // types in ChatEventTypeTS; registering an unused name is harmless.\n const eventTypes = [\n \"error\",\n \"reconnected\", \"typing\", \"stopped_typing\", \"waiting\",\n \"waiting_for_agent\", \"human_agent_joined\", \"human_agent_left\",\n \"chat_ended\", \"chat_updated\", \"chat_removed\", \"load_chat_response\",\n \"new_chat_created\", \"show_csat_survey\", \"csat_response\",\n \"user_suggested_actions\", \"user_suggested_action_selected\",\n \"agent_execution_started\", \"agent_execution_ended\",\n \"agent_context_update\", \"load_agent_context_response\",\n \"plan_pending_approval\", \"plan_approved\", \"plan_rejected\",\n \"step_started\", \"step_completed\", \"step_failed\", \"plan_completed\",\n \"skills_changed\", \"summary_update\",\n \"message_status_update\", \"delivered\", \"read\",\n \"sync_metadata_response\", \"sync_user_session_response\",\n \"client_action\", \"client_action_callback\",\n \"attachment_processing_started\", \"attachment_processing_progress\",\n \"attachment_processing_complete\", \"attachment_processing_error\",\n \"observer_joined\", \"observer_left\", \"block_user\",\n \"message_edited\", \"message_deleted\", \"message_reaction\",\n \"user_presence_changed\", \"online_users\", \"get_agents_response\",\n \"room_created\", \"room_updated\", \"room_deleted\", \"rooms_response\",\n \"room_user_joined\", \"room_user_left\", \"user_invited\",\n ];\n\n eventTypes.forEach((type) => {\n es.addEventListener(type, handleMessage);\n });\n }\n\n // Schedule reconnect\n function scheduleReconnect(): void {\n if (intentionalDisconnect || !config) return;\n\n const maxRetries = retryConfig.maxRetries ?? DEFAULT_RETRY_CONFIG.maxRetries;\n if (retryCount >= maxRetries) {\n logger.error(`Max retries (${maxRetries}) exceeded`);\n error = {\n code: \"CONNECTION_FAILED\",\n message: `Max retries (${maxRetries}) exceeded`,\n retryable: false,\n timestamp: Date.now(),\n };\n return;\n }\n\n const interval = calculateRetryInterval(retryCount, retryConfig);\n retryCount++;\n metrics.reconnectCount++;\n\n logger.info(`Reconnecting in ${interval}ms (attempt ${retryCount})`);\n state = \"reconnecting\";\n\n reconnectTimeout = setTimeout(() => {\n if (config) {\n transport.connect(config).catch((err) => {\n logger.error(\"Reconnect failed:\", err);\n });\n }\n }, interval);\n }\n\n const transport: ChatTransport = {\n async connect(cfg: TransportConfig): Promise<void> {\n config = cfg;\n intentionalDisconnect = false;\n\n // Clean up existing connection\n if (eventSource) {\n eventSource.close();\n eventSource = undefined;\n }\n if (reconnectTimeout) {\n clearTimeout(reconnectTimeout);\n reconnectTimeout = undefined;\n }\n\n state = retryCount > 0 ? \"reconnecting\" : \"connecting\";\n\n // Resolve a fresh token BEFORE opening the stream. EventSource cannot set\n // headers, so the token must ride as a ?token= query param — the chat\n // service reads it there (the gateway uses the same pattern for SSE).\n const connectToken = await resolveTransportToken(cfg);\n\n return new Promise((resolve, reject) => {\n const connectionStart = Date.now();\n // URLSearchParams encodes reserved characters — a raw \"+\" in a\n // userId email decodes server-side to a space, silently breaking\n // targeted delivery (connection registered under the wrong user).\n const streamParams = new URLSearchParams({\n orgId: cfg.orgId,\n userId: cfg.userId,\n clientType: cfg.clientType,\n });\n if (cfg.chatKey) streamParams.set(\"chatId\", cfg.chatKey);\n if (connectToken) streamParams.set(\"token\", connectToken);\n const url = `${cfg.baseUrl}/stream?${streamParams.toString()}`;\n\n logger.debug(\"Connecting to:\", url);\n const es = new EventSource(url);\n\n es.onopen = () => {\n const connectionTime = Date.now() - connectionStart;\n logger.info(`Connected in ${connectionTime}ms`);\n\n const wasReconnect = retryCount > 0;\n state = \"connected\";\n error = undefined;\n retryCount = 0;\n metrics.connectedAt = Date.now();\n metrics.latency = connectionTime;\n\n setupEventListeners(es);\n\n // Synthetic event so the app layer can re-sync chat state —\n // broadcasts emitted while the stream was down are gone for good.\n if (wasReconnect) {\n emit({\n type: \"transport_reconnected\",\n orgId: cfg.orgId,\n chatKey: cfg.chatKey || \"\",\n userId: cfg.userId,\n timestamp: Date.now(),\n } as ChatEvent);\n }\n\n resolve();\n };\n\n es.onerror = () => {\n if (es.readyState === EventSource.CLOSED) {\n const sseError: TransportError = {\n code: \"CONNECTION_FAILED\",\n message: \"SSE connection failed\",\n retryable: true,\n timestamp: Date.now(),\n };\n error = sseError;\n metrics.lastError = sseError;\n state = \"disconnected\";\n\n if (!intentionalDisconnect) {\n scheduleReconnect();\n }\n\n // Only reject if this is the initial connection attempt\n if (retryCount === 0) {\n reject(sseError);\n }\n }\n };\n\n eventSource = es;\n });\n },\n\n disconnect(intentional = true): void {\n logger.info(\"Disconnecting\", { intentional });\n intentionalDisconnect = intentional;\n\n if (reconnectTimeout) {\n clearTimeout(reconnectTimeout);\n reconnectTimeout = undefined;\n }\n\n if (eventSource) {\n eventSource.close();\n eventSource = undefined;\n }\n\n state = \"disconnected\";\n retryCount = 0;\n },\n\n async send(event: ChatEvent): Promise<void> {\n if (!config) {\n throw new Error(\"Transport not connected\");\n }\n\n // Map event types to REST endpoints\n switch (event.type) {\n case \"message\":\n await sendRest(\"send\", {\n orgId: event.orgId,\n chatKey: event.chatKey,\n userId: event.userId,\n message: event.message,\n ...(event.data ? { data: event.data } : {}),\n });\n break;\n\n case \"typing\":\n await sendRest(\"typing\", {\n orgId: event.orgId,\n chatKey: event.chatKey,\n userId: event.userId,\n typing: true,\n });\n break;\n\n case \"stopped_typing\":\n await sendRest(\"typing\", {\n orgId: event.orgId,\n chatKey: event.chatKey,\n userId: event.userId,\n typing: false,\n });\n break;\n\n case \"load_chat\":\n // Deprecated: Use loadChatData() instead for direct response\n await sendRest(\"load\", {\n orgId: event.orgId,\n chatKey: event.chatKey,\n userId: event.userId,\n });\n break;\n\n case \"new_chat\":\n // Deprecated: Use createChat() instead for direct response\n await sendRest(\"create\", {\n orgId: event.orgId,\n userId: event.userId,\n metadata: event.data,\n });\n break;\n\n case \"end_chat\":\n await sendRest(\"end\", {\n orgId: event.orgId,\n chatKey: event.chatKey,\n userId: event.userId,\n data: event.data,\n });\n break;\n\n default:\n // Generic event endpoint for other types\n await sendRest(\"event\", {\n type: event.type,\n orgId: event.orgId,\n chatKey: event.chatKey,\n userId: event.userId,\n data: event.data,\n });\n }\n\n metrics.messagesSent++;\n },\n\n async sendMessage(message: ChatMessage): Promise<void> {\n if (!config) {\n throw new Error(\"Transport not connected\");\n }\n\n await sendRest(\"send\", {\n orgId: config.orgId,\n chatKey: config.chatKey,\n userId: config.userId,\n message,\n });\n\n metrics.messagesSent++;\n },\n\n async createChat(options: CreateChatOptions): Promise<CreateChatResponse> {\n if (!config) {\n throw new Error(\"Transport not connected\");\n }\n\n const response = await sendRest(\"create\", {\n orgId: options.orgId,\n userId: options.userId,\n metadata: options.metadata,\n }) as { chatKey?: string };\n\n if (!response?.chatKey) {\n throw new Error(\"Failed to create chat: no chatKey returned\");\n }\n\n return { chatKey: response.chatKey };\n },\n\n async loadChatData(options: LoadChatOptions): Promise<LoadChatResponse> {\n if (!config) {\n throw new Error(\"Transport not connected\");\n }\n\n const response = await sendRest(\"load\", {\n orgId: options.orgId,\n chatKey: options.chatKey,\n userId: options.userId,\n }) as { chat?: unknown; agentId?: string };\n\n if (!response?.chat) {\n throw new Error(\"Failed to load chat: no chat data returned\");\n }\n\n return {\n chat: response.chat as Chat,\n agentId: response.agentId,\n };\n },\n\n onMessage(handler: EventHandler): Unsubscribe {\n globalHandlers.add(handler);\n return () => globalHandlers.delete(handler);\n },\n\n on(eventType: string, handler: EventHandler): Unsubscribe {\n if (!typeHandlers.has(eventType)) {\n typeHandlers.set(eventType, new Set());\n }\n typeHandlers.get(eventType)!.add(handler);\n return () => {\n const handlers = typeHandlers.get(eventType);\n if (handlers) {\n handlers.delete(handler);\n if (handlers.size === 0) {\n typeHandlers.delete(eventType);\n }\n }\n };\n },\n\n getState(): TransportState {\n return state;\n },\n\n getMetrics(): ConnectionMetrics {\n return { ...metrics };\n },\n\n getError(): TransportError | undefined {\n return error;\n },\n\n clearError(): void {\n error = undefined;\n },\n };\n\n return transport;\n}\n","/**\n * SSE Transport (Fetch-based)\n *\n * Uses fetch with ReadableStream for receiving server events and fetch POST for sending.\n * Designed for React Native and environments without native EventSource support.\n *\n * @example\n * ```typescript\n * import { createFetchSSETransport } from \"@elqnt/chat/transport\";\n *\n * const transport = createFetchSSETransport();\n * await transport.connect({ baseUrl, orgId, userId, clientType: \"customer\" });\n * ```\n */\n\nimport type { Chat, ChatEvent, ChatMessage } from \"../models\";\nimport type {\n ChatTransport,\n TransportConfig,\n TransportState,\n TransportError,\n ConnectionMetrics,\n EventHandler,\n Unsubscribe,\n RetryConfig,\n TransportLogger,\n CreateChatOptions,\n CreateChatResponse,\n LoadChatOptions,\n LoadChatResponse,\n} from \"./types\";\nimport { createLogger, calculateRetryInterval, DEFAULT_RETRY_CONFIG, resolveTransportToken } from \"./types\";\n\n/**\n * Fetch SSE Transport options\n */\nexport interface FetchSSETransportOptions {\n /** Retry configuration */\n retryConfig?: RetryConfig;\n /** Enable debug logging */\n debug?: boolean;\n /** Custom logger */\n logger?: TransportLogger;\n /** Custom fetch implementation (useful for React Native) */\n customFetch?: typeof fetch;\n}\n\n/**\n * Create a fetch-based SSE transport for React Native and polyfill environments\n */\nexport function createFetchSSETransport(options: FetchSSETransportOptions = {}): ChatTransport {\n const {\n retryConfig = DEFAULT_RETRY_CONFIG,\n debug = false,\n logger = createLogger(debug),\n customFetch = fetch,\n } = options;\n\n // Internal state\n let abortController: AbortController | undefined;\n let config: TransportConfig | undefined;\n let state: TransportState = \"disconnected\";\n let error: TransportError | undefined;\n let retryCount = 0;\n let reconnectTimeout: ReturnType<typeof setTimeout> | undefined;\n let intentionalDisconnect = false;\n\n // Metrics\n const metrics: ConnectionMetrics = {\n latency: 0,\n messagesSent: 0,\n messagesReceived: 0,\n messagesQueued: 0,\n reconnectCount: 0,\n transportType: \"sse-fetch\",\n };\n\n // Event handlers\n const globalHandlers = new Set<EventHandler>();\n const typeHandlers = new Map<string, Set<EventHandler>>();\n\n // Helper to emit events\n function emit(event: ChatEvent): void {\n metrics.messagesReceived++;\n metrics.lastMessageAt = Date.now();\n\n globalHandlers.forEach((handler) => {\n try {\n handler(event);\n } catch (err) {\n logger.error(\"Error in message handler:\", err);\n }\n });\n\n const handlers = typeHandlers.get(event.type);\n if (handlers) {\n handlers.forEach((handler) => {\n try {\n handler(event);\n } catch (err) {\n logger.error(`Error in ${event.type} handler:`, err);\n }\n });\n }\n }\n\n // REST API helper\n async function sendRest(endpoint: string, body: Record<string, unknown>): Promise<unknown> {\n if (!config) {\n throw new Error(\"Transport not connected\");\n }\n\n const url = `${config.baseUrl}/${endpoint}`;\n logger.debug(`POST ${endpoint}`, body);\n\n // Tier-2 authorization: attach the signed token as a bearer header.\n const token = await resolveTransportToken(config);\n const headers: Record<string, string> = { \"Content-Type\": \"application/json\" };\n if (token) headers[\"Authorization\"] = `Bearer ${token}`;\n\n const response = await customFetch(url, {\n method: \"POST\",\n headers,\n body: JSON.stringify(body),\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n throw new Error(`API error: ${response.status} - ${errorText}`);\n }\n\n const json = await response.json() as { success?: boolean; data?: unknown };\n\n // Backend wraps responses in { success: true, data: {...} }\n // Unwrap to return just the data\n if (json && typeof json === \"object\" && \"data\" in json) {\n return json.data;\n }\n\n return json;\n }\n\n // Parse SSE data from a text chunk\n function parseSSEChunk(chunk: string): ChatEvent[] {\n const events: ChatEvent[] = [];\n const lines = chunk.split(\"\\n\");\n\n let eventType = \"message\";\n let data = \"\";\n\n for (const line of lines) {\n if (line.startsWith(\"event:\")) {\n eventType = line.slice(6).trim();\n } else if (line.startsWith(\"data:\")) {\n data = line.slice(5).trim();\n } else if (line === \"\" && data) {\n // End of event\n try {\n const parsed = JSON.parse(data) as ChatEvent;\n events.push(parsed);\n } catch {\n logger.warn(\"Failed to parse SSE data:\", data);\n }\n eventType = \"message\";\n data = \"\";\n }\n }\n\n return events;\n }\n\n // Start SSE stream using fetch\n async function startStream(cfg: TransportConfig): Promise<void> {\n // URLSearchParams encodes reserved characters — a raw \"+\" in a userId\n // email decodes server-side to a space, silently breaking targeted\n // delivery (connection registered under the wrong user).\n const streamParams = new URLSearchParams({\n orgId: cfg.orgId,\n userId: cfg.userId,\n clientType: cfg.clientType,\n });\n if (cfg.chatKey) streamParams.set(\"chatId\", cfg.chatKey);\n // Tier-2 authorization. fetch CAN set headers, but we also pass ?token=\n // for parity with EventSource and so the same server query path works.\n const token = await resolveTransportToken(cfg);\n if (token) streamParams.set(\"token\", token);\n const url = `${cfg.baseUrl}/stream?${streamParams.toString()}`;\n\n logger.debug(\"Connecting to:\", url);\n\n abortController = new AbortController();\n\n const streamHeaders: Record<string, string> = {\n Accept: \"text/event-stream\",\n \"Cache-Control\": \"no-cache\",\n };\n if (token) streamHeaders[\"Authorization\"] = `Bearer ${token}`;\n\n const response = await customFetch(url, {\n method: \"GET\",\n headers: streamHeaders,\n signal: abortController.signal,\n });\n\n if (!response.ok) {\n throw new Error(`Stream connection failed: ${response.status}`);\n }\n\n if (!response.body) {\n throw new Error(\"Response body is null - ReadableStream not supported\");\n }\n\n const reader = response.body.getReader();\n const decoder = new TextDecoder();\n let buffer = \"\";\n\n // Read stream in background\n const readStream = async () => {\n try {\n while (true) {\n const { done, value } = await reader.read();\n\n if (done) {\n logger.info(\"Stream ended\");\n break;\n }\n\n buffer += decoder.decode(value, { stream: true });\n\n // Process complete events in buffer\n const lastNewline = buffer.lastIndexOf(\"\\n\\n\");\n if (lastNewline !== -1) {\n const complete = buffer.slice(0, lastNewline + 2);\n buffer = buffer.slice(lastNewline + 2);\n\n const events = parseSSEChunk(complete);\n events.forEach(emit);\n }\n }\n } catch (err) {\n if ((err as Error).name === \"AbortError\") {\n logger.debug(\"Stream aborted\");\n return;\n }\n logger.error(\"Stream error:\", err);\n throw err;\n }\n\n // Stream closed - handle reconnection\n if (!intentionalDisconnect) {\n state = \"disconnected\";\n scheduleReconnect();\n }\n };\n\n // Start reading in background\n readStream().catch((err) => {\n if (!intentionalDisconnect) {\n error = {\n code: \"CONNECTION_FAILED\",\n message: err.message,\n retryable: true,\n timestamp: Date.now(),\n originalError: err,\n };\n metrics.lastError = error;\n state = \"disconnected\";\n scheduleReconnect();\n }\n });\n }\n\n // Schedule reconnect\n function scheduleReconnect(): void {\n if (intentionalDisconnect || !config) return;\n\n const maxRetries = retryConfig.maxRetries ?? DEFAULT_RETRY_CONFIG.maxRetries;\n if (retryCount >= maxRetries) {\n logger.error(`Max retries (${maxRetries}) exceeded`);\n error = {\n code: \"CONNECTION_FAILED\",\n message: `Max retries (${maxRetries}) exceeded`,\n retryable: false,\n timestamp: Date.now(),\n };\n return;\n }\n\n const interval = calculateRetryInterval(retryCount, retryConfig);\n retryCount++;\n metrics.reconnectCount++;\n\n logger.info(`Reconnecting in ${interval}ms (attempt ${retryCount})`);\n state = \"reconnecting\";\n\n reconnectTimeout = setTimeout(() => {\n if (config) {\n transport.connect(config).catch((err) => {\n logger.error(\"Reconnect failed:\", err);\n });\n }\n }, interval);\n }\n\n const transport: ChatTransport = {\n async connect(cfg: TransportConfig): Promise<void> {\n config = cfg;\n intentionalDisconnect = false;\n\n // Clean up existing connection\n if (abortController) {\n abortController.abort();\n abortController = undefined;\n }\n if (reconnectTimeout) {\n clearTimeout(reconnectTimeout);\n reconnectTimeout = undefined;\n }\n\n state = retryCount > 0 ? \"reconnecting\" : \"connecting\";\n const connectionStart = Date.now();\n\n try {\n const wasReconnect = retryCount > 0;\n await startStream(cfg);\n\n const connectionTime = Date.now() - connectionStart;\n logger.info(`Connected in ${connectionTime}ms`);\n\n state = \"connected\";\n error = undefined;\n retryCount = 0;\n metrics.connectedAt = Date.now();\n metrics.latency = connectionTime;\n\n // Synthetic event so the app layer can re-sync chat state —\n // broadcasts emitted while the stream was down are gone for good.\n if (wasReconnect) {\n emit({\n type: \"transport_reconnected\",\n orgId: cfg.orgId,\n chatKey: cfg.chatKey || \"\",\n userId: cfg.userId,\n timestamp: Date.now(),\n } as ChatEvent);\n }\n } catch (err) {\n const connectError: TransportError = {\n code: \"CONNECTION_FAILED\",\n message: (err as Error).message,\n retryable: true,\n timestamp: Date.now(),\n originalError: err,\n };\n error = connectError;\n metrics.lastError = connectError;\n state = \"disconnected\";\n\n if (!intentionalDisconnect) {\n scheduleReconnect();\n }\n\n throw connectError;\n }\n },\n\n disconnect(intentional = true): void {\n logger.info(\"Disconnecting\", { intentional });\n intentionalDisconnect = intentional;\n\n if (reconnectTimeout) {\n clearTimeout(reconnectTimeout);\n reconnectTimeout = undefined;\n }\n\n if (abortController) {\n abortController.abort();\n abortController = undefined;\n }\n\n state = \"disconnected\";\n retryCount = 0;\n },\n\n async send(event: ChatEvent): Promise<void> {\n if (!config) {\n throw new Error(\"Transport not connected\");\n }\n\n switch (event.type) {\n case \"message\":\n await sendRest(\"send\", {\n orgId: event.orgId,\n chatKey: event.chatKey,\n userId: event.userId,\n message: event.message,\n ...(event.data ? { data: event.data } : {}),\n });\n break;\n\n case \"typing\":\n await sendRest(\"typing\", {\n orgId: event.orgId,\n chatKey: event.chatKey,\n userId: event.userId,\n typing: true,\n });\n break;\n\n case \"stopped_typing\":\n await sendRest(\"typing\", {\n orgId: event.orgId,\n chatKey: event.chatKey,\n userId: event.userId,\n typing: false,\n });\n break;\n\n case \"load_chat\":\n await sendRest(\"load\", {\n orgId: event.orgId,\n chatKey: event.chatKey,\n userId: event.userId,\n });\n break;\n\n case \"new_chat\":\n await sendRest(\"create\", {\n orgId: event.orgId,\n userId: event.userId,\n metadata: event.data,\n });\n break;\n\n case \"end_chat\":\n await sendRest(\"end\", {\n orgId: event.orgId,\n chatKey: event.chatKey,\n userId: event.userId,\n data: event.data,\n });\n break;\n\n default:\n await sendRest(\"event\", {\n type: event.type,\n orgId: event.orgId,\n chatKey: event.chatKey,\n userId: event.userId,\n data: event.data,\n });\n }\n\n metrics.messagesSent++;\n },\n\n async sendMessage(message: ChatMessage): Promise<void> {\n if (!config) {\n throw new Error(\"Transport not connected\");\n }\n\n await sendRest(\"send\", {\n orgId: config.orgId,\n chatKey: config.chatKey,\n userId: config.userId,\n message,\n });\n\n metrics.messagesSent++;\n },\n\n async createChat(options: CreateChatOptions): Promise<CreateChatResponse> {\n if (!config) {\n throw new Error(\"Transport not connected\");\n }\n\n const response = await sendRest(\"create\", {\n orgId: options.orgId,\n userId: options.userId,\n metadata: options.metadata,\n }) as { chatKey?: string };\n\n if (!response?.chatKey) {\n throw new Error(\"Failed to create chat: no chatKey returned\");\n }\n\n return { chatKey: response.chatKey };\n },\n\n async loadChatData(options: LoadChatOptions): Promise<LoadChatResponse> {\n if (!config) {\n throw new Error(\"Transport not connected\");\n }\n\n const response = await sendRest(\"load\", {\n orgId: options.orgId,\n chatKey: options.chatKey,\n userId: options.userId,\n }) as { chat?: unknown; agentId?: string };\n\n if (!response?.chat) {\n throw new Error(\"Failed to load chat: no chat data returned\");\n }\n\n return {\n chat: response.chat as Chat,\n agentId: response.agentId,\n };\n },\n\n onMessage(handler: EventHandler): Unsubscribe {\n globalHandlers.add(handler);\n return () => globalHandlers.delete(handler);\n },\n\n on(eventType: string, handler: EventHandler): Unsubscribe {\n if (!typeHandlers.has(eventType)) {\n typeHandlers.set(eventType, new Set());\n }\n typeHandlers.get(eventType)!.add(handler);\n return () => {\n const handlers = typeHandlers.get(eventType);\n if (handlers) {\n handlers.delete(handler);\n if (handlers.size === 0) {\n typeHandlers.delete(eventType);\n }\n }\n };\n },\n\n getState(): TransportState {\n return state;\n },\n\n getMetrics(): ConnectionMetrics {\n return { ...metrics };\n },\n\n getError(): TransportError | undefined {\n return error;\n },\n\n clearError(): void {\n error = undefined;\n },\n };\n\n return transport;\n}\n","/**\n * WhatsApp Business API Transport (Stub)\n *\n * This module provides the interface for integrating with WhatsApp Business API.\n * The implementation connects to a webhook receiver that bridges WhatsApp messages\n * to the chat system.\n *\n * ## Integration Architecture\n *\n * ```\n * WhatsApp User → WhatsApp Cloud API → Your Webhook → Chat Service → SSE to Admin\n * ↓\n * NATS Events\n * ```\n *\n * ## Setup Requirements\n *\n * 1. Register a WhatsApp Business Account\n * 2. Create a Meta Business App with WhatsApp integration\n * 3. Configure webhook URL pointing to your chat service\n * 4. Set up message templates for outbound messages\n *\n * ## Backend Integration\n *\n * Your backend should expose webhook endpoints that:\n * - Verify webhook (GET /webhook with hub.verify_token)\n * - Receive messages (POST /webhook with message payload)\n * - Map WhatsApp user IDs to chat users\n * - Create/load chats for new conversations\n *\n * @example Backend Webhook Handler (Go)\n * ```go\n * func HandleWhatsAppWebhook(w http.ResponseWriter, r *http.Request) {\n * if r.Method == \"GET\" {\n * // Webhook verification\n * verifyToken := r.URL.Query().Get(\"hub.verify_token\")\n * if verifyToken == os.Getenv(\"WHATSAPP_VERIFY_TOKEN\") {\n * w.Write([]byte(r.URL.Query().Get(\"hub.challenge\")))\n * return\n * }\n * http.Error(w, \"Invalid verify token\", 403)\n * return\n * }\n *\n * // Handle incoming message\n * var payload WhatsAppPayload\n * json.NewDecoder(r.Body).Decode(&payload)\n *\n * // Map to chat event and publish to NATS\n * chatEvent := mapWhatsAppToChatEvent(payload)\n * natsClient.Publish(\"chat.events\", chatEvent)\n * }\n * ```\n *\n * @see https://developers.facebook.com/docs/whatsapp/cloud-api\n */\n\nimport type {\n ChatTransport,\n TransportConfig,\n TransportState,\n TransportError,\n ConnectionMetrics,\n EventHandler,\n Unsubscribe,\n TransportLogger,\n CreateChatOptions,\n CreateChatResponse,\n LoadChatOptions,\n LoadChatResponse,\n} from \"./types\";\nimport type { Chat, ChatEvent, ChatMessage, Attachment, AttachmentTypeTS } from \"../models\";\nimport { createLogger } from \"./types\";\n\n/**\n * WhatsApp transport configuration\n */\nexport interface WhatsAppTransportConfig extends TransportConfig {\n /** WhatsApp Business API access token */\n accessToken: string;\n /** Phone number ID from Meta Business */\n phoneNumberId: string;\n /** Webhook verify token for setup */\n verifyToken?: string;\n}\n\n/**\n * WhatsApp transport options\n */\nexport interface WhatsAppTransportOptions {\n /** Enable debug logging */\n debug?: boolean;\n /** Custom logger */\n logger?: TransportLogger;\n}\n\n/**\n * WhatsApp message types\n */\nexport type WhatsAppMessageType =\n | \"text\"\n | \"image\"\n | \"document\"\n | \"audio\"\n | \"video\"\n | \"sticker\"\n | \"location\"\n | \"contacts\"\n | \"interactive\"\n | \"template\";\n\n/**\n * Create a WhatsApp Business API transport\n *\n * NOTE: This is a stub implementation. The actual WhatsApp integration\n * happens on the backend via webhooks. This transport is for:\n * - Type definitions\n * - Documentation\n * - Future client-side WhatsApp Web integration\n */\nexport function createWhatsAppTransport(\n options: WhatsAppTransportOptions = {}\n): ChatTransport {\n const { debug = false, logger = createLogger(debug) } = options;\n\n let state: TransportState = \"disconnected\";\n let error: TransportError | undefined;\n let config: WhatsAppTransportConfig | undefined;\n\n const metrics: ConnectionMetrics = {\n latency: 0,\n messagesSent: 0,\n messagesReceived: 0,\n messagesQueued: 0,\n reconnectCount: 0,\n transportType: \"whatsapp\",\n };\n\n const globalHandlers = new Set<EventHandler>();\n const typeHandlers = new Map<string, Set<EventHandler>>();\n\n const transport: ChatTransport = {\n async connect(cfg: TransportConfig): Promise<void> {\n logger.warn(\n \"WhatsApp transport is a stub. WhatsApp integration happens via backend webhooks.\"\n );\n logger.info(\n \"See transport/whatsapp.ts for integration documentation.\"\n );\n\n config = cfg as WhatsAppTransportConfig;\n\n // In a real implementation, this would:\n // 1. Validate WhatsApp Business API credentials\n // 2. Register webhook endpoints\n // 3. Set up SSE connection to receive webhook-relayed events\n\n state = \"connected\";\n metrics.connectedAt = Date.now();\n },\n\n disconnect(): void {\n state = \"disconnected\";\n },\n\n async send(event: ChatEvent): Promise<void> {\n if (!config) {\n throw new Error(\"Transport not connected\");\n }\n\n logger.warn(\"WhatsApp send is a stub. Implement backend webhook relay.\");\n\n // In a real implementation, this would:\n // 1. Convert ChatEvent to WhatsApp message format\n // 2. Call WhatsApp Cloud API to send message\n // 3. Handle delivery receipts\n\n metrics.messagesSent++;\n },\n\n async sendMessage(message: ChatMessage): Promise<void> {\n if (!config) {\n throw new Error(\"Transport not connected\");\n }\n\n logger.warn(\n \"WhatsApp sendMessage is a stub. Implement backend webhook relay.\"\n );\n\n // In a real implementation:\n // POST https://graph.facebook.com/v17.0/{phone_number_id}/messages\n // {\n // \"messaging_product\": \"whatsapp\",\n // \"to\": \"{recipient_phone}\",\n // \"type\": \"text\",\n // \"text\": { \"body\": message.content }\n // }\n\n metrics.messagesSent++;\n },\n\n async createChat(options: CreateChatOptions): Promise<CreateChatResponse> {\n logger.warn(\n \"WhatsApp createChat is a stub. WhatsApp chats are created via incoming webhooks.\"\n );\n\n // In WhatsApp, chats are typically initiated by users messaging first.\n // This stub returns a placeholder - real implementation would create\n // a chat record on the backend for outbound messaging.\n return { chatKey: `whatsapp_${options.userId}_${Date.now()}` };\n },\n\n async loadChatData(options: LoadChatOptions): Promise<LoadChatResponse> {\n logger.warn(\n \"WhatsApp loadChatData is a stub. Implement backend API call.\"\n );\n\n // In a real implementation, this would fetch chat data from the backend\n return {\n chat: {\n orgId: options.orgId,\n key: options.chatKey,\n title: \"WhatsApp Chat\",\n messages: [],\n lastUpdated: Date.now(),\n startTime: Date.now(),\n users: [],\n status: \"active\",\n aiEngaged: false,\n humanAgentEngaged: false,\n isWaiting: false,\n isWaitingForAgent: false,\n } as Chat,\n };\n },\n\n onMessage(handler: EventHandler): Unsubscribe {\n globalHandlers.add(handler);\n return () => globalHandlers.delete(handler);\n },\n\n on(eventType: string, handler: EventHandler): Unsubscribe {\n if (!typeHandlers.has(eventType)) {\n typeHandlers.set(eventType, new Set());\n }\n typeHandlers.get(eventType)!.add(handler);\n return () => {\n const handlers = typeHandlers.get(eventType);\n if (handlers) {\n handlers.delete(handler);\n if (handlers.size === 0) {\n typeHandlers.delete(eventType);\n }\n }\n };\n },\n\n getState(): TransportState {\n return state;\n },\n\n getMetrics(): ConnectionMetrics {\n return { ...metrics };\n },\n\n getError(): TransportError | undefined {\n return error;\n },\n\n clearError(): void {\n error = undefined;\n },\n };\n\n return transport;\n}\n\n/**\n * Helper: Convert WhatsApp webhook payload to ChatEvent\n *\n * Use this in your backend webhook handler to map incoming\n * WhatsApp messages to the chat system format.\n */\nexport interface WhatsAppWebhookPayload {\n object: \"whatsapp_business_account\";\n entry: Array<{\n id: string;\n changes: Array<{\n value: {\n messaging_product: \"whatsapp\";\n metadata: {\n display_phone_number: string;\n phone_number_id: string;\n };\n contacts?: Array<{\n profile: { name: string };\n wa_id: string;\n }>;\n messages?: Array<{\n from: string;\n id: string;\n timestamp: string;\n type: WhatsAppMessageType;\n text?: { body: string };\n image?: { id: string; mime_type: string; sha256: string };\n document?: { id: string; mime_type: string; filename: string };\n audio?: { id: string; mime_type: string };\n video?: { id: string; mime_type: string };\n location?: { latitude: number; longitude: number; name?: string };\n }>;\n statuses?: Array<{\n id: string;\n status: \"sent\" | \"delivered\" | \"read\" | \"failed\";\n timestamp: string;\n recipient_id: string;\n }>;\n };\n }>;\n }>;\n}\n\n/**\n * Example: Map WhatsApp payload to ChatEvent (for backend use)\n *\n * @example\n * ```typescript\n * // In your backend webhook handler\n * import { mapWhatsAppToChatEvent } from \"@elqnt/chat/transport\";\n *\n * app.post(\"/webhook/whatsapp\", (req, res) => {\n * const events = mapWhatsAppToChatEvent(req.body, orgId);\n * events.forEach(event => publishToChatService(event));\n * res.sendStatus(200);\n * });\n * ```\n */\nexport function mapWhatsAppToChatEvent(\n payload: WhatsAppWebhookPayload,\n orgId: string\n): ChatEvent[] {\n const events: ChatEvent[] = [];\n\n for (const entry of payload.entry) {\n for (const change of entry.changes) {\n const value = change.value;\n\n // Handle incoming messages\n if (value.messages) {\n for (const msg of value.messages) {\n const contact = value.contacts?.find((c) => c.wa_id === msg.from);\n\n events.push({\n type: \"message\",\n orgId,\n chatKey: `wa_${msg.from}`, // Use phone number as chat key\n userId: msg.from,\n timestamp: parseInt(msg.timestamp) * 1000,\n message: {\n id: msg.id,\n role: \"user\",\n content: msg.text?.body || \"\",\n time: parseInt(msg.timestamp) * 1000,\n status: \"delivered\",\n senderId: msg.from,\n senderName: contact?.profile.name,\n createdAt: parseInt(msg.timestamp) * 1000,\n // Map attachments if present\n attachments: mapWhatsAppAttachments(msg),\n },\n });\n }\n }\n\n // Handle message status updates\n if (value.statuses) {\n for (const status of value.statuses) {\n events.push({\n type: \"message_status_update\",\n orgId,\n chatKey: `wa_${status.recipient_id}`,\n userId: status.recipient_id,\n timestamp: parseInt(status.timestamp) * 1000,\n data: {\n messageId: status.id,\n status: status.status,\n },\n });\n }\n }\n }\n }\n\n return events;\n}\n\n// Helper to map WhatsApp attachments\nfunction mapWhatsAppAttachments(msg: {\n type: WhatsAppMessageType;\n image?: { id: string };\n document?: { id: string; filename: string };\n audio?: { id: string };\n video?: { id: string };\n location?: { latitude: number; longitude: number; name?: string };\n}): Attachment[] | undefined {\n switch (msg.type) {\n case \"image\":\n return msg.image\n ? [{ type: \"image\" as AttachmentTypeTS, url: `whatsapp://media/${msg.image.id}` }]\n : undefined;\n case \"document\":\n return msg.document\n ? [\n {\n type: \"document\" as AttachmentTypeTS,\n url: `whatsapp://media/${msg.document.id}`,\n },\n ]\n : undefined;\n case \"audio\":\n return msg.audio\n ? [{ type: \"audio\" as AttachmentTypeTS, url: `whatsapp://media/${msg.audio.id}` }]\n : undefined;\n case \"video\":\n return msg.video\n ? [{ type: \"video\" as AttachmentTypeTS, url: `whatsapp://media/${msg.video.id}` }]\n : undefined;\n case \"location\":\n return msg.location\n ? [\n {\n type: \"location\" as AttachmentTypeTS,\n url: `geo:${msg.location.latitude},${msg.location.longitude}`,\n },\n ]\n : undefined;\n default:\n return undefined;\n }\n}\n"],"mappings":"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","names":["options","options","options"]}
|
|
@@ -38,6 +38,22 @@ interface TransportConfig {
|
|
|
38
38
|
chatKey?: string;
|
|
39
39
|
/** Enable debug logging */
|
|
40
40
|
debug?: boolean;
|
|
41
|
+
/**
|
|
42
|
+
* Tier-2 authorization (REQUIRED in production). Chat realtime talks to the
|
|
43
|
+
* chat service DIRECTLY, bypassing the API gateway — so the service has no
|
|
44
|
+
* gateway JWT to trust and authorizes each connection from this signed
|
|
45
|
+
* token instead. The service derives orgId/userId/product from the token's
|
|
46
|
+
* claims, so the token MUST be scoped to the same org/user as this config.
|
|
47
|
+
*
|
|
48
|
+
* Prefer `getToken`: it is invoked before every connect and every REST send,
|
|
49
|
+
* so a long-lived stream keeps working as short-lived tokens roll over. Use
|
|
50
|
+
* the static `token` only for one-shot/non-refreshing callers. Obtain the
|
|
51
|
+
* token from a server route that mints a gateway-shaped JWT (e.g. the app's
|
|
52
|
+
* /api/gateway-token or a dedicated chat-token route). See SKILL.md.
|
|
53
|
+
*/
|
|
54
|
+
getToken?: () => Promise<string | null | undefined> | string | null | undefined;
|
|
55
|
+
/** Static signed token. Ignored when `getToken` is provided. */
|
|
56
|
+
token?: string;
|
|
41
57
|
}
|
|
42
58
|
/**
|
|
43
59
|
* Retry configuration for connection attempts
|
|
@@ -38,6 +38,22 @@ interface TransportConfig {
|
|
|
38
38
|
chatKey?: string;
|
|
39
39
|
/** Enable debug logging */
|
|
40
40
|
debug?: boolean;
|
|
41
|
+
/**
|
|
42
|
+
* Tier-2 authorization (REQUIRED in production). Chat realtime talks to the
|
|
43
|
+
* chat service DIRECTLY, bypassing the API gateway — so the service has no
|
|
44
|
+
* gateway JWT to trust and authorizes each connection from this signed
|
|
45
|
+
* token instead. The service derives orgId/userId/product from the token's
|
|
46
|
+
* claims, so the token MUST be scoped to the same org/user as this config.
|
|
47
|
+
*
|
|
48
|
+
* Prefer `getToken`: it is invoked before every connect and every REST send,
|
|
49
|
+
* so a long-lived stream keeps working as short-lived tokens roll over. Use
|
|
50
|
+
* the static `token` only for one-shot/non-refreshing callers. Obtain the
|
|
51
|
+
* token from a server route that mints a gateway-shaped JWT (e.g. the app's
|
|
52
|
+
* /api/gateway-token or a dedicated chat-token route). See SKILL.md.
|
|
53
|
+
*/
|
|
54
|
+
getToken?: () => Promise<string | null | undefined> | string | null | undefined;
|
|
55
|
+
/** Static signed token. Ignored when `getToken` is provided. */
|
|
56
|
+
token?: string;
|
|
41
57
|
}
|
|
42
58
|
/**
|
|
43
59
|
* Retry configuration for connection attempts
|