@pedi/chika-sdk 1.0.7 → 1.0.8
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/dist/index.js +41 -23
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +41 -23
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -445,8 +445,7 @@ async function createChatSession(config, channelId, profile, callbacks, networkM
|
|
|
445
445
|
body: JSON.stringify({
|
|
446
446
|
participant_id: profile.id,
|
|
447
447
|
message_id: messageId
|
|
448
|
-
})
|
|
449
|
-
signal: sessionAbort.signal
|
|
448
|
+
})
|
|
450
449
|
});
|
|
451
450
|
if (!res.ok) {
|
|
452
451
|
await throwHttpError(res);
|
|
@@ -454,7 +453,7 @@ async function createChatSession(config, channelId, profile, callbacks, networkM
|
|
|
454
453
|
};
|
|
455
454
|
if (retryConfig) {
|
|
456
455
|
try {
|
|
457
|
-
await withRetry(readFn, MARK_READ_RETRY_CONFIG
|
|
456
|
+
await withRetry(readFn, MARK_READ_RETRY_CONFIG);
|
|
458
457
|
} catch (err) {
|
|
459
458
|
if (err instanceof HttpError) {
|
|
460
459
|
callbacks.onError(err);
|
|
@@ -770,6 +769,7 @@ var MessageQueue = class {
|
|
|
770
769
|
entry.status = "failed";
|
|
771
770
|
entry.error = err instanceof Error ? err : new Error(String(err));
|
|
772
771
|
entry.retryCount++;
|
|
772
|
+
entry.reject(entry.error);
|
|
773
773
|
this.config.onStatusChange?.();
|
|
774
774
|
this.persist();
|
|
775
775
|
}
|
|
@@ -827,6 +827,7 @@ function useChat({ config, channelId, profile, onMessage }) {
|
|
|
827
827
|
const startingRef = (0, import_react.useRef)(false);
|
|
828
828
|
const pendingOptimisticIds = (0, import_react.useRef)(/* @__PURE__ */ new Set());
|
|
829
829
|
const [monitor, setMonitor] = (0, import_react.useState)(null);
|
|
830
|
+
const [monitorReady, setMonitorReady] = (0, import_react.useState)(false);
|
|
830
831
|
const monitorRef = (0, import_react.useRef)(null);
|
|
831
832
|
monitorRef.current = monitor;
|
|
832
833
|
const queueRef = (0, import_react.useRef)(null);
|
|
@@ -839,18 +840,26 @@ function useChat({ config, channelId, profile, onMessage }) {
|
|
|
839
840
|
(0, import_react.useEffect)(() => {
|
|
840
841
|
if (!resilienceEnabled) {
|
|
841
842
|
setMonitor(null);
|
|
843
|
+
setMonitorReady(true);
|
|
842
844
|
return;
|
|
843
845
|
}
|
|
844
846
|
if (injectedMonitor) {
|
|
845
847
|
setMonitor(injectedMonitor);
|
|
848
|
+
setMonitorReady(true);
|
|
846
849
|
return;
|
|
847
850
|
}
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
851
|
+
try {
|
|
852
|
+
const m = createNetworkMonitor();
|
|
853
|
+
setMonitor(m);
|
|
854
|
+
setMonitorReady(true);
|
|
855
|
+
return () => {
|
|
856
|
+
m.dispose();
|
|
857
|
+
setMonitor(null);
|
|
858
|
+
};
|
|
859
|
+
} catch {
|
|
852
860
|
setMonitor(null);
|
|
853
|
-
|
|
861
|
+
setMonitorReady(true);
|
|
862
|
+
}
|
|
854
863
|
}, [resilienceEnabled, injectedMonitor]);
|
|
855
864
|
const callbacks = {
|
|
856
865
|
onMessage: (message) => {
|
|
@@ -943,7 +952,7 @@ function useChat({ config, channelId, profile, onMessage }) {
|
|
|
943
952
|
}
|
|
944
953
|
(0, import_react.useEffect)(() => {
|
|
945
954
|
disposedRef.current = false;
|
|
946
|
-
if (
|
|
955
|
+
if (!monitorReady) return;
|
|
947
956
|
startSession();
|
|
948
957
|
return () => {
|
|
949
958
|
disposedRef.current = true;
|
|
@@ -961,7 +970,7 @@ function useChat({ config, channelId, profile, onMessage }) {
|
|
|
961
970
|
sessionRef.current?.disconnect();
|
|
962
971
|
sessionRef.current = null;
|
|
963
972
|
};
|
|
964
|
-
}, [channelId,
|
|
973
|
+
}, [channelId, monitorReady]);
|
|
965
974
|
(0, import_react.useEffect)(() => {
|
|
966
975
|
if (!queueEnabled || !monitor || !retryConfig) return;
|
|
967
976
|
let entry = queueRegistry.get(channelId);
|
|
@@ -1170,24 +1179,33 @@ function useUnread(options) {
|
|
|
1170
1179
|
const appStateRef = (0, import_react2.useRef)(import_react_native2.AppState.currentState);
|
|
1171
1180
|
const backgroundTimerRef = (0, import_react2.useRef)(null);
|
|
1172
1181
|
const backgroundGraceMs = config.backgroundGraceMs ?? (import_react_native2.Platform.OS === "android" ? DEFAULT_BACKGROUND_GRACE_MS2 : 0);
|
|
1173
|
-
const [
|
|
1182
|
+
const [monitorReady, setMonitorReady] = (0, import_react2.useState)(false);
|
|
1183
|
+
const monitorRef = (0, import_react2.useRef)(null);
|
|
1174
1184
|
const resilienceEnabled = config.resilience !== false;
|
|
1175
1185
|
const injectedMonitor = typeof config.resilience === "object" ? config.resilience.networkMonitor : void 0;
|
|
1176
1186
|
(0, import_react2.useEffect)(() => {
|
|
1177
1187
|
if (!resilienceEnabled) {
|
|
1178
|
-
|
|
1188
|
+
monitorRef.current = null;
|
|
1189
|
+
setMonitorReady(true);
|
|
1179
1190
|
return;
|
|
1180
1191
|
}
|
|
1181
1192
|
if (injectedMonitor) {
|
|
1182
|
-
|
|
1193
|
+
monitorRef.current = injectedMonitor;
|
|
1194
|
+
setMonitorReady(true);
|
|
1183
1195
|
return;
|
|
1184
1196
|
}
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1197
|
+
try {
|
|
1198
|
+
const m = createNetworkMonitor();
|
|
1199
|
+
monitorRef.current = m;
|
|
1200
|
+
setMonitorReady(true);
|
|
1201
|
+
return () => {
|
|
1202
|
+
m.dispose();
|
|
1203
|
+
monitorRef.current = null;
|
|
1204
|
+
};
|
|
1205
|
+
} catch {
|
|
1206
|
+
monitorRef.current = null;
|
|
1207
|
+
setMonitorReady(true);
|
|
1208
|
+
}
|
|
1191
1209
|
}, [resilienceEnabled, injectedMonitor]);
|
|
1192
1210
|
const connect = (0, import_react2.useCallback)(() => {
|
|
1193
1211
|
connRef.current?.close();
|
|
@@ -1201,7 +1219,7 @@ function useUnread(options) {
|
|
|
1201
1219
|
headers: customHeaders,
|
|
1202
1220
|
reconnectDelayMs: configRef.current.reconnectDelayMs,
|
|
1203
1221
|
customEvents: UNREAD_CUSTOM_EVENTS,
|
|
1204
|
-
networkMonitor:
|
|
1222
|
+
networkMonitor: monitorRef.current ?? void 0
|
|
1205
1223
|
},
|
|
1206
1224
|
{
|
|
1207
1225
|
onOpen: () => {
|
|
@@ -1233,7 +1251,7 @@ function useUnread(options) {
|
|
|
1233
1251
|
}
|
|
1234
1252
|
}
|
|
1235
1253
|
);
|
|
1236
|
-
}, [channelId, participantId
|
|
1254
|
+
}, [channelId, participantId]);
|
|
1237
1255
|
const disconnect = (0, import_react2.useCallback)(() => {
|
|
1238
1256
|
connRef.current?.close();
|
|
1239
1257
|
connRef.current = null;
|
|
@@ -1246,7 +1264,7 @@ function useUnread(options) {
|
|
|
1246
1264
|
disconnect();
|
|
1247
1265
|
return;
|
|
1248
1266
|
}
|
|
1249
|
-
if (
|
|
1267
|
+
if (!monitorReady) return;
|
|
1250
1268
|
connect();
|
|
1251
1269
|
return () => {
|
|
1252
1270
|
disconnect();
|
|
@@ -1255,7 +1273,7 @@ function useUnread(options) {
|
|
|
1255
1273
|
backgroundTimerRef.current = null;
|
|
1256
1274
|
}
|
|
1257
1275
|
};
|
|
1258
|
-
}, [channelId, participantId, enabled, connect, disconnect]);
|
|
1276
|
+
}, [channelId, participantId, enabled, monitorReady, connect, disconnect]);
|
|
1259
1277
|
(0, import_react2.useEffect)(() => {
|
|
1260
1278
|
if (!enabled) return;
|
|
1261
1279
|
const subscription = import_react_native2.AppState.addEventListener("change", (nextAppState) => {
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/use-chat.ts","../src/errors.ts","../src/retry.ts","../src/resolve-url.ts","../src/sse-connection.ts","../src/session.ts","../src/network-monitor.ts","../src/message-queue.ts","../src/use-unread.ts"],"sourcesContent":["export { useChat } from './use-chat';\nexport { useUnread } from './use-unread';\nexport { createChatSession } from './session';\nexport { resolveServerUrl, createManifest } from './resolve-url';\nexport { createSSEConnection } from './sse-connection';\nexport { ChatDisconnectedError, ChannelClosedError, HttpError, RetryExhaustedError, QueueFullError } from './errors';\nexport { withRetry, isRetryableError, calculateBackoff, resolveRetryConfig } from './retry';\nexport { createNetworkMonitor } from './network-monitor';\nexport { createQueueStorage, createAsyncStorageAdapter } from './message-queue';\n\nexport type { ChatConfig, ChatStatus, UseChatOptions, UseChatReturn, ResilienceConfig } from './types';\nexport type { ChatSession, SessionCallbacks } from './session';\nexport type { UseUnreadOptions, UseUnreadReturn } from './use-unread';\nexport type { SSEConnection, SSEConnectionConfig, SSEConnectionCallbacks } from './sse-connection';\nexport type { RetryConfig } from './retry';\nexport type { NetworkMonitor } from './network-monitor';\nexport type { MessageSendStatus, QueuedMessage, QueueStorage } from './message-queue';\n\nexport type {\n ChatDomain,\n DefaultDomain,\n Message,\n Participant,\n MessageAttributes,\n SendMessageResponse,\n ChatManifest,\n ChatBucket,\n UnreadCountResponse,\n MarkReadRequest,\n SSEUnreadUpdateEvent,\n SSEUnreadClearEvent,\n SSEUnreadEvent,\n PediChat,\n PediRole,\n PediVehicle,\n PediLocation,\n PediParticipantMeta,\n PediMessageType,\n PediMessageAttributes,\n} from '@pedi/chika-types';\n","import { useEffect, useRef, useState, useCallback } from 'react';\nimport { AppState, Platform, type AppStateStatus } from 'react-native';\nimport type {\n ChatDomain,\n DefaultDomain,\n Message,\n Participant,\n MessageAttributes,\n SendMessageResponse,\n} from '@pedi/chika-types';\nimport type { UseChatOptions, UseChatReturn, ChatStatus } from './types';\nimport { ChatDisconnectedError, ChannelClosedError, QueueFullError, RetryExhaustedError } from './errors';\nimport { isRetryableError, resolveRetryConfig } from './retry';\nimport { createChatSession, type ChatSession, type SessionCallbacks } from './session';\nimport { createNetworkMonitor, type NetworkMonitor } from './network-monitor';\nimport { MessageQueue, type QueuedMessage } from './message-queue';\n\nconst DEFAULT_BACKGROUND_GRACE_MS = 2000;\nconst DEFAULT_MAX_QUEUE_SIZE = 50;\n\n// Module-scope queue registry, keyed by channelId. Survives component remounts.\nconst queueRegistry = new Map<string, { queue: MessageQueue; refCount: number }>();\n\n/**\n * React hook for real-time chat over SSE.\n * Manages connection lifecycle, AppState transitions, message deduplication, reconnection,\n * and optional network resilience (retry, offline queue, network monitoring).\n *\n * @template D - Chat domain type for role/message type narrowing. Defaults to DefaultDomain.\n */\nexport function useChat<D extends ChatDomain = DefaultDomain>(\n { config, channelId, profile, onMessage }: UseChatOptions<D>,\n): UseChatReturn<D> {\n const [messages, setMessages] = useState<Message<D>[]>([]);\n const [participants, setParticipants] = useState<Participant<D>[]>([]);\n const [status, setStatus] = useState<ChatStatus>('connecting');\n const [error, setError] = useState<Error | null>(null);\n const [pendingMessages, setPendingMessages] = useState<QueuedMessage[]>([]);\n\n const sessionRef = useRef<ChatSession<D> | null>(null);\n const disposedRef = useRef(false);\n const messagesRef = useRef(messages);\n messagesRef.current = messages;\n const statusRef = useRef(status);\n statusRef.current = status;\n const appStateRef = useRef<AppStateStatus>(AppState.currentState);\n const backgroundTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n const profileRef = useRef(profile);\n profileRef.current = profile;\n const configRef = useRef(config);\n configRef.current = config;\n const onMessageRef = useRef(onMessage);\n onMessageRef.current = onMessage;\n const startingRef = useRef(false);\n const pendingOptimisticIds = useRef(new Set<string>());\n const [monitor, setMonitor] = useState<NetworkMonitor | null>(null);\n const monitorRef = useRef<NetworkMonitor | null>(null);\n monitorRef.current = monitor;\n const queueRef = useRef<MessageQueue | null>(null);\n\n const resilienceEnabled = config.resilience !== false;\n const queueEnabled =\n resilienceEnabled &&\n (typeof config.resilience === 'object' ? config.resilience.offlineQueue !== false : true);\n const retryConfig = resolveRetryConfig(config.resilience);\n const maxQueueSize =\n (resilienceEnabled && config.resilience && typeof config.resilience === 'object'\n ? config.resilience.maxQueueSize\n : undefined) ?? DEFAULT_MAX_QUEUE_SIZE;\n\n const backgroundGraceMs =\n config.backgroundGraceMs ?? (Platform.OS === 'android' ? DEFAULT_BACKGROUND_GRACE_MS : 0);\n\n // Resolve user-injected monitor (stable reference, no side effect)\n const injectedMonitor =\n typeof config.resilience === 'object' ? config.resilience.networkMonitor : undefined;\n\n // Create monitor in useEffect to avoid side effects during render.\n // Uses state (not ref) so the queue effect re-runs when monitor is ready.\n useEffect(() => {\n if (!resilienceEnabled) {\n setMonitor(null);\n return;\n }\n if (injectedMonitor) {\n setMonitor(injectedMonitor);\n return; // user owns lifecycle\n }\n const m = createNetworkMonitor();\n setMonitor(m);\n return () => {\n m.dispose();\n setMonitor(null);\n };\n }, [resilienceEnabled, injectedMonitor]);\n\n const callbacks: SessionCallbacks<D> = {\n onMessage: (message) => {\n if (disposedRef.current) return;\n let matchedOptimisticId: string | null = null;\n setMessages((prev: Message<D>[]) => {\n if (pendingOptimisticIds.current.size === 0) {\n return [...prev, message];\n }\n\n const optimisticIdx = prev.findIndex(\n (m) =>\n pendingOptimisticIds.current.has(m.id) &&\n m.sender_id === message.sender_id &&\n m.body === message.body &&\n m.type === message.type,\n );\n if (optimisticIdx !== -1) {\n matchedOptimisticId = prev[optimisticIdx]!.id;\n pendingOptimisticIds.current.delete(matchedOptimisticId);\n const next = [...prev];\n next[optimisticIdx] = message;\n return next;\n }\n return [...prev, message];\n });\n // Clean up any queued/failed queue entry — SSE confirmed delivery.\n // Done outside setMessages updater to avoid setState-in-setState.\n if (matchedOptimisticId) {\n const s = queueRef.current?.getStatus(matchedOptimisticId);\n if (s && s.status !== 'sending') {\n queueRef.current?.cancel(matchedOptimisticId);\n }\n }\n onMessageRef.current?.(message);\n },\n onStatusChange: (nextStatus) => {\n if (disposedRef.current) return;\n setStatus(nextStatus);\n if (nextStatus === 'connected') {\n setError(null);\n }\n },\n onError: (err) => {\n if (disposedRef.current) return;\n setError(err);\n },\n onResync: () => {\n if (disposedRef.current) return;\n startSession();\n },\n };\n\n async function startSession(): Promise<void> {\n if (startingRef.current) return;\n startingRef.current = true;\n\n const existing = sessionRef.current;\n if (existing) {\n existing.disconnect();\n sessionRef.current = null;\n }\n\n try {\n const session = await createChatSession<D>(\n configRef.current,\n channelId,\n profileRef.current,\n callbacks,\n monitorRef.current ?? undefined,\n );\n\n if (disposedRef.current) {\n session.disconnect();\n return;\n }\n\n sessionRef.current = session;\n setParticipants(session.initialParticipants);\n\n // Flush any messages queued while session was unavailable\n queueRef.current?.flush();\n\n // Re-merge any pending optimistic messages after resync\n if (pendingOptimisticIds.current.size > 0) {\n const pendingIds = pendingOptimisticIds.current;\n setMessages((prev) => {\n const pendingMsgs = prev.filter((m) => pendingIds.has(m.id));\n return [...session.initialMessages, ...pendingMsgs];\n });\n } else {\n setMessages(session.initialMessages);\n }\n } catch (err) {\n if (disposedRef.current) return;\n\n if (err instanceof ChannelClosedError) {\n setStatus('closed');\n setError(err);\n return;\n }\n\n setStatus('error');\n setError(err instanceof Error ? err : new Error(String(err)));\n } finally {\n startingRef.current = false;\n }\n }\n\n // Session lifecycle\n useEffect(() => {\n disposedRef.current = false;\n // Don't start until monitor is resolved (or resilience is off)\n if (resilienceEnabled && !monitor) return;\n startSession();\n\n return () => {\n disposedRef.current = true;\n if (statusRef.current === 'connected' && sessionRef.current) {\n const lastMsg = messagesRef.current[messagesRef.current.length - 1];\n if (lastMsg) {\n sessionRef.current.markAsRead(lastMsg.id).catch(() => {});\n }\n }\n if (backgroundTimerRef.current) {\n clearTimeout(backgroundTimerRef.current);\n backgroundTimerRef.current = null;\n }\n sessionRef.current?.disconnect();\n sessionRef.current = null;\n };\n }, [channelId, monitor]);\n\n // Module-scope queue lifecycle (ref-counted, survives remounts)\n useEffect(() => {\n if (!queueEnabled || !monitor || !retryConfig) return;\n\n let entry = queueRegistry.get(channelId);\n if (!entry) {\n const queue = new MessageQueue({\n channelId,\n maxSize: maxQueueSize,\n retryConfig,\n networkMonitor: monitor,\n storage:\n typeof config.resilience === 'object'\n ? config.resilience.queueStorage\n : undefined,\n onError: (err) => {\n if (!disposedRef.current) setError(err);\n },\n onStatusChange: () => {\n if (!disposedRef.current) {\n setPendingMessages(queueRef.current?.getAll() ?? []);\n }\n },\n });\n entry = { queue, refCount: 0 };\n queueRegistry.set(channelId, entry);\n }\n entry.refCount++;\n queueRef.current = entry.queue;\n\n return () => {\n const e = queueRegistry.get(channelId);\n if (e) {\n e.refCount--;\n if (e.refCount <= 0) {\n e.queue.dispose();\n queueRegistry.delete(channelId);\n }\n }\n queueRef.current = null;\n };\n }, [channelId, queueEnabled, monitor]);\n\n // Network monitor: auto-rejoin on connectivity return when in error state\n useEffect(() => {\n if (!monitor || !resilienceEnabled) return;\n\n const unsub = monitor.subscribe((connected: boolean) => {\n if (connected && statusRef.current === 'error' && !startingRef.current) {\n startSession();\n }\n });\n\n return unsub;\n }, [channelId, resilienceEnabled, monitor]);\n\n // AppState lifecycle\n useEffect(() => {\n function teardownSession(): void {\n sessionRef.current?.disconnect();\n sessionRef.current = null;\n setStatus('disconnected');\n }\n\n const subscription = AppState.addEventListener('change', (nextAppState) => {\n const prev = appStateRef.current;\n appStateRef.current = nextAppState;\n\n if (!sessionRef.current && nextAppState !== 'active') return;\n\n const shouldTeardown =\n nextAppState === 'background' ||\n (Platform.OS === 'ios' && nextAppState === 'inactive');\n\n if (nextAppState === 'active') {\n if (backgroundTimerRef.current) {\n clearTimeout(backgroundTimerRef.current);\n backgroundTimerRef.current = null;\n return;\n }\n\n if (prev.match(/inactive|background/) && !sessionRef.current) {\n startSession();\n }\n } else if (shouldTeardown) {\n if (backgroundTimerRef.current) return;\n\n if (backgroundGraceMs === 0) {\n teardownSession();\n } else {\n backgroundTimerRef.current = setTimeout(() => {\n backgroundTimerRef.current = null;\n teardownSession();\n }, backgroundGraceMs);\n }\n }\n });\n\n return () => {\n subscription.remove();\n if (backgroundTimerRef.current) {\n clearTimeout(backgroundTimerRef.current);\n backgroundTimerRef.current = null;\n }\n };\n }, [channelId, backgroundGraceMs]);\n\n const sendMessage = useCallback(\n async (type: D['messageType'], body: string, attributes?: MessageAttributes<D>): Promise<SendMessageResponse> => {\n const session = sessionRef.current;\n if (!session) throw new ChatDisconnectedError(statusRef.current);\n\n const optimistic = configRef.current.optimisticSend !== false;\n // Serves as both the optimistic message ID and the server-side idempotency key\n const messageKey = `optimistic_${Date.now()}_${Math.random().toString(36).slice(2, 7)}`;\n\n if (optimistic) {\n pendingOptimisticIds.current.add(messageKey);\n const provisionalMsg: Message<D> = {\n id: messageKey,\n channel_id: channelId,\n sender_id: profileRef.current.id,\n sender_role: profileRef.current.role,\n type,\n body,\n attributes: (attributes ?? {}) as MessageAttributes<D>,\n created_at: new Date().toISOString(),\n };\n setMessages((prev) => [...prev, provisionalMsg]);\n }\n\n const doSend = () => {\n const s = sessionRef.current;\n if (!s) throw new ChatDisconnectedError(statusRef.current);\n return s.sendMessage(type, body, attributes, messageKey);\n };\n\n const handleSuccess = (response: SendMessageResponse): void => {\n if (optimistic) {\n pendingOptimisticIds.current.delete(messageKey);\n setMessages((prev) => {\n const stillPending = prev.some((m) => m.id === messageKey);\n if (!stillPending) return prev;\n return prev.map((m) =>\n m.id === messageKey\n ? { ...m, id: response.id, created_at: response.created_at }\n : m,\n );\n });\n }\n };\n\n const handleError = (_err: unknown): void => {\n if (optimistic) {\n pendingOptimisticIds.current.delete(messageKey);\n setMessages((prev) => prev.filter((m) => m.id !== messageKey));\n }\n };\n\n // Queue path: enqueue and let queue handle retry + offline\n if (queueRef.current) {\n try {\n const response = await queueRef.current.enqueue(doSend, messageKey);\n handleSuccess(response);\n return response;\n } catch (err) {\n if (err instanceof QueueFullError) {\n handleError(err);\n throw err;\n }\n if (err instanceof RetryExhaustedError || !isRetryableError(err)) {\n // For failed messages: if optimistic, mark as failed (keep in UI)\n // The pendingMessages state shows the status\n if (optimistic && err instanceof RetryExhaustedError) {\n // Keep optimistic message visible — queue tracks status as 'failed'.\n // Keep messageKey in pendingOptimisticIds so SSE reconciliation can\n // still match if the server eventually received the message.\n // Removed on explicit cancelMessage() call.\n } else {\n handleError(err);\n }\n throw err;\n }\n handleError(err);\n throw err;\n }\n }\n\n // Non-queue path: direct send (session.ts handles retry if enabled)\n try {\n const response = await doSend();\n handleSuccess(response);\n return response;\n } catch (err) {\n handleError(err);\n throw err;\n }\n },\n [channelId],\n );\n\n const cancelMessage = useCallback(\n (optimisticId: string) => {\n queueRef.current?.cancel(optimisticId);\n pendingOptimisticIds.current.delete(optimisticId);\n setMessages((prev) => prev.filter((m) => m.id !== optimisticId));\n },\n [],\n );\n\n const retryMessage = useCallback(\n (optimisticId: string) => {\n queueRef.current?.retry(optimisticId);\n },\n [],\n );\n\n const disconnect = useCallback(() => {\n sessionRef.current?.disconnect();\n sessionRef.current = null;\n setStatus('disconnected');\n }, []);\n\n return {\n messages,\n participants,\n status,\n error,\n sendMessage,\n disconnect,\n pendingMessages,\n cancelMessage,\n retryMessage,\n };\n}\n","import type { ChatStatus } from './types';\n\nexport class ChatDisconnectedError extends Error {\n constructor(public readonly status: ChatStatus) {\n super(`Cannot send message while ${status}`);\n this.name = 'ChatDisconnectedError';\n }\n}\n\nexport class ChannelClosedError extends Error {\n constructor(public readonly channelId: string) {\n super(`Channel ${channelId} is closed`);\n this.name = 'ChannelClosedError';\n }\n}\n\nexport class HttpError extends Error {\n constructor(\n public readonly status: number,\n public readonly body: string,\n public readonly retryAfter?: number,\n ) {\n super(`HTTP ${status}: ${body}`);\n this.name = 'HttpError';\n }\n}\n\nexport class RetryExhaustedError extends Error {\n constructor(\n public readonly operation: string,\n public readonly attempts: number,\n public readonly lastError: Error,\n ) {\n super(`${operation} failed after ${attempts} attempts: ${lastError.message}`);\n this.name = 'RetryExhaustedError';\n }\n}\n\nexport class QueueFullError extends Error {\n constructor(public readonly maxSize: number) {\n super(`Message queue full (max ${maxSize})`);\n this.name = 'QueueFullError';\n }\n}\n","import {\n HttpError,\n RetryExhaustedError,\n ChannelClosedError,\n ChatDisconnectedError,\n QueueFullError,\n} from './errors';\n\nexport interface RetryConfig {\n maxAttempts: number;\n baseDelayMs: number;\n maxDelayMs: number;\n jitterFactor: number;\n}\n\nexport const DEFAULT_RETRY_CONFIG: RetryConfig = {\n maxAttempts: 3,\n baseDelayMs: 1000,\n maxDelayMs: 10000,\n jitterFactor: 0.3,\n};\n\nexport function calculateBackoff(attempt: number, config: RetryConfig): number {\n const delay = Math.min(config.baseDelayMs * 2 ** attempt, config.maxDelayMs);\n const jitter = 1 + (Math.random() * 2 - 1) * config.jitterFactor;\n return Math.round(delay * jitter);\n}\n\nexport function isRetryableError(error: unknown): boolean {\n if (error instanceof ChannelClosedError) return false;\n if (error instanceof ChatDisconnectedError) return false;\n if (error instanceof QueueFullError) return false;\n if (error instanceof DOMException && error.name === 'AbortError') return false;\n\n if (error instanceof HttpError) {\n const { status } = error;\n if (status === 408 || status === 429) return true;\n if (status >= 500) return true;\n return false;\n }\n\n // TypeError from fetch = network failure\n if (error instanceof TypeError) return true;\n\n return false;\n}\n\nexport function sleep(ms: number, signal?: AbortSignal): Promise<void> {\n return new Promise((resolve, reject) => {\n if (signal?.aborted) {\n reject(signal.reason ?? new DOMException('Aborted', 'AbortError'));\n return;\n }\n\n const timer = setTimeout(resolve, ms);\n\n signal?.addEventListener(\n 'abort',\n () => {\n clearTimeout(timer);\n reject(signal.reason ?? new DOMException('Aborted', 'AbortError'));\n },\n { once: true },\n );\n });\n}\n\nexport function resolveRetryConfig(\n resilience: { retry?: Partial<RetryConfig> | false } | false | undefined,\n): RetryConfig | null {\n if (resilience === false) return null;\n if (!resilience || resilience.retry === undefined) return DEFAULT_RETRY_CONFIG;\n if (resilience.retry === false) return null;\n return { ...DEFAULT_RETRY_CONFIG, ...resilience.retry };\n}\n\nexport async function withRetry<T>(\n fn: () => Promise<T>,\n config: RetryConfig = DEFAULT_RETRY_CONFIG,\n signal?: AbortSignal,\n): Promise<T> {\n let lastError: Error | undefined;\n\n for (let attempt = 0; attempt < config.maxAttempts; attempt++) {\n if (signal?.aborted) {\n throw signal.reason ?? new DOMException('Aborted', 'AbortError');\n }\n\n try {\n return await fn();\n } catch (err) {\n lastError = err instanceof Error ? err : new Error(String(err));\n\n if (!isRetryableError(err)) throw lastError;\n\n if (attempt < config.maxAttempts - 1) {\n // Respect Retry-After header for 429 responses\n const delayMs =\n err instanceof HttpError && err.retryAfter != null\n ? err.retryAfter * 1000\n : calculateBackoff(attempt, config);\n\n await sleep(delayMs, signal);\n }\n }\n }\n\n throw new RetryExhaustedError(\n 'operation',\n config.maxAttempts,\n lastError!,\n );\n}\n","import type { ChatManifest } from '@pedi/chika-types';\n\n/**\n * Creates a single-server manifest. Use this when all channels route to the same server.\n *\n * @example\n * ```ts\n * const config: ChatConfig = { manifest: createManifest('https://chat.example.com') };\n * ```\n */\nexport function createManifest(serverUrl: string): ChatManifest {\n return { buckets: [{ group: 'default', range: [0, 99], server_url: serverUrl }] };\n}\n\nexport function resolveServerUrl(manifest: ChatManifest, channelId: string): string {\n const hash = [...channelId].reduce((sum, c) => sum + c.charCodeAt(0), 0) % 100;\n const bucket = manifest.buckets.find(\n (b) => hash >= b.range[0] && hash <= b.range[1],\n );\n if (!bucket) throw new Error(`No chat bucket for hash ${hash}`);\n return bucket.server_url;\n}\n","import EventSource from 'react-native-sse';\nimport { calculateBackoff, type RetryConfig } from './retry';\nimport type { NetworkMonitor } from './network-monitor';\n\nconst DEFAULT_RECONNECT_DELAY_MS = 3000;\n\nexport interface SSEConnectionConfig {\n url: string;\n headers?: Record<string, string>;\n reconnectDelayMs?: number;\n lastEventId?: string;\n customEvents?: string[];\n networkMonitor?: NetworkMonitor;\n}\n\nexport interface SSEConnectionCallbacks {\n onOpen?: () => void;\n onEvent: (eventType: string, data: string, lastEventId?: string) => void;\n onError?: (error: Error) => void;\n onClosed?: () => void;\n onReconnecting?: () => void;\n}\n\nexport interface SSEConnection {\n close: () => void;\n reconnectImmediate: () => void;\n}\n\nexport function createSSEConnection(\n config: SSEConnectionConfig,\n callbacks: SSEConnectionCallbacks,\n): SSEConnection {\n const baseDelay = config.reconnectDelayMs ?? DEFAULT_RECONNECT_DELAY_MS;\n const customEvents = config.customEvents ?? [];\n const monitor = config.networkMonitor;\n\n const backoffConfig: RetryConfig = {\n maxAttempts: Infinity,\n baseDelayMs: baseDelay,\n maxDelayMs: 30000,\n jitterFactor: 0.3,\n };\n\n let currentLastEventId = config.lastEventId;\n let es: EventSource<string> | null = null;\n let disposed = false;\n let reconnectTimer: ReturnType<typeof setTimeout> | null = null;\n let attempt = 0;\n let waitAbort: AbortController | null = null;\n\n function cleanup(): void {\n if (es) {\n es.removeAllEventListeners();\n es.close();\n es = null;\n }\n if (reconnectTimer) {\n clearTimeout(reconnectTimer);\n reconnectTimer = null;\n }\n if (waitAbort) {\n waitAbort.abort();\n waitAbort = null;\n }\n }\n\n async function scheduleReconnect(): Promise<void> {\n if (disposed || reconnectTimer || waitAbort) return;\n callbacks.onReconnecting?.();\n cleanup();\n\n // Wait for network if monitor available\n if (monitor && !monitor.isConnected()) {\n waitAbort = new AbortController();\n try {\n await monitor.waitForOnline(waitAbort.signal);\n } catch {\n return; // aborted via dispose\n }\n waitAbort = null;\n if (disposed) return;\n }\n\n const delay = calculateBackoff(attempt++, backoffConfig);\n reconnectTimer = setTimeout(() => {\n reconnectTimer = null;\n connect();\n }, delay);\n }\n\n function connect(): void {\n if (disposed) return;\n\n es = new EventSource<string>(config.url, {\n headers: {\n ...config.headers,\n ...(currentLastEventId && { 'Last-Event-ID': currentLastEventId }),\n },\n pollingInterval: 0,\n });\n\n es.addEventListener('open', () => {\n if (disposed) return;\n attempt = 0;\n callbacks.onOpen?.();\n });\n\n es.addEventListener('message', (event) => {\n if (disposed || !event.data) return;\n if (event.lastEventId) {\n currentLastEventId = event.lastEventId;\n }\n callbacks.onEvent('message', event.data, event.lastEventId ?? undefined);\n });\n\n for (const eventName of customEvents) {\n es.addEventListener(eventName, (event) => {\n if (disposed) return;\n callbacks.onEvent(eventName, event.data ?? '', undefined);\n });\n }\n\n es.addEventListener('error', (event) => {\n if (disposed) return;\n\n const msg = 'message' in event ? String(event.message) : '';\n\n if (msg.includes('Channel is closed') || msg.includes('410')) {\n callbacks.onClosed?.();\n cleanup();\n disposed = true;\n return;\n }\n\n if (msg) callbacks.onError?.(new Error(msg));\n\n scheduleReconnect();\n });\n\n es.addEventListener('close', () => {\n if (disposed) return;\n scheduleReconnect();\n });\n }\n\n connect();\n\n return {\n close: () => {\n disposed = true;\n cleanup();\n },\n\n reconnectImmediate: () => {\n if (disposed) return;\n cleanup(); // does NOT set disposed\n attempt = 0;\n connect();\n },\n };\n}\n","import type {\n ChatDomain,\n DefaultDomain,\n Participant,\n Message,\n JoinResponse,\n SendMessageRequest,\n SendMessageResponse,\n MessageAttributes,\n} from '@pedi/chika-types';\nimport type { ChatConfig, ChatStatus } from './types';\nimport { ChatDisconnectedError, ChannelClosedError, HttpError } from './errors';\nimport { resolveServerUrl } from './resolve-url';\nimport { createSSEConnection, type SSEConnection } from './sse-connection';\nimport { withRetry, resolveRetryConfig, type RetryConfig } from './retry';\nimport type { NetworkMonitor } from './network-monitor';\n\nconst DEFAULT_RECONNECT_DELAY_MS = 3000;\nconst MAX_SEEN_IDS = 500;\n\nconst MARK_READ_RETRY_CONFIG: RetryConfig = {\n maxAttempts: 2,\n baseDelayMs: 500,\n maxDelayMs: 2000,\n jitterFactor: 0.3,\n};\n\nexport interface SessionCallbacks<D extends ChatDomain = DefaultDomain> {\n onMessage: (message: Message<D>) => void;\n onStatusChange: (status: ChatStatus) => void;\n onError: (error: Error) => void;\n onResync: () => void;\n}\n\nexport interface ChatSession<D extends ChatDomain = DefaultDomain> {\n serviceUrl: string;\n channelId: string;\n initialParticipants: Participant<D>[];\n initialMessages: Message<D>[];\n networkMonitor: NetworkMonitor | null;\n sendMessage: (\n type: D['messageType'],\n body: string,\n attributes?: MessageAttributes<D>,\n idempotencyKey?: string,\n ) => Promise<SendMessageResponse>;\n markAsRead: (messageId: string) => Promise<void>;\n disconnect: () => void;\n}\n\nfunction parseRetryAfter(res: Response): number | undefined {\n const header = res.headers.get('Retry-After');\n if (!header) return undefined;\n const seconds = Number(header);\n return Number.isFinite(seconds) ? seconds : undefined;\n}\n\nasync function throwHttpError(res: Response): Promise<never> {\n const body = await res.text().catch(() => '');\n throw new HttpError(res.status, body, parseRetryAfter(res));\n}\n\nexport async function createChatSession<D extends ChatDomain = DefaultDomain>(\n config: ChatConfig,\n channelId: string,\n profile: Participant<D>,\n callbacks: SessionCallbacks<D>,\n networkMonitor?: NetworkMonitor,\n): Promise<ChatSession<D>> {\n const serviceUrl = resolveServerUrl(config.manifest, channelId);\n const customHeaders = config.headers ?? {};\n const reconnectDelay = config.reconnectDelayMs ?? DEFAULT_RECONNECT_DELAY_MS;\n const retryConfig = resolveRetryConfig(config.resilience);\n\n const sessionAbort = new AbortController();\n\n callbacks.onStatusChange('connecting');\n\n const joinFn = async (): Promise<JoinResponse<D>> => {\n const joinRes = await fetch(`${serviceUrl}/channels/${channelId}/join`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', ...customHeaders },\n body: JSON.stringify(profile),\n signal: sessionAbort.signal,\n });\n\n if (joinRes.status === 410) {\n throw new ChannelClosedError(channelId);\n }\n\n if (!joinRes.ok) {\n await throwHttpError(joinRes);\n }\n\n return joinRes.json();\n };\n\n const joinData = retryConfig\n ? await withRetry(joinFn, retryConfig, sessionAbort.signal)\n : await joinFn();\n\n const { messages, participants, joined_at }: JoinResponse<D> = joinData;\n\n let lastEventId =\n messages.length > 0 ? messages[messages.length - 1]!.id : undefined;\n\n const joinedAt = joined_at;\n\n const seenMessageIds = new Set<string>(messages.map((m) => m.id));\n\n let sseConn: SSEConnection | null = null;\n let disposed = false;\n\n const TRIM_THRESHOLD = MAX_SEEN_IDS * 1.5;\n\n function trimSeenIds(): void {\n if (seenMessageIds.size <= TRIM_THRESHOLD) return;\n const ids = [...seenMessageIds];\n seenMessageIds.clear();\n for (const id of ids.slice(-MAX_SEEN_IDS)) {\n seenMessageIds.add(id);\n }\n }\n\n function connect(): void {\n if (disposed) return;\n\n const streamUrl = lastEventId\n ? `${serviceUrl}/channels/${channelId}/stream`\n : `${serviceUrl}/channels/${channelId}/stream?since_time=${encodeURIComponent(joinedAt)}`;\n\n sseConn = createSSEConnection(\n {\n url: streamUrl,\n headers: customHeaders,\n reconnectDelayMs: reconnectDelay,\n lastEventId,\n customEvents: ['resync'],\n networkMonitor,\n },\n {\n onOpen: () => {\n if (!disposed) callbacks.onStatusChange('connected');\n },\n onEvent: (eventType, data, eventId) => {\n if (disposed) return;\n\n if (eventType === 'message') {\n let message: Message<D>;\n try {\n message = JSON.parse(data);\n } catch {\n callbacks.onError(new Error('Failed to parse SSE message'));\n return;\n }\n\n if (eventId) {\n lastEventId = eventId;\n }\n\n if (seenMessageIds.has(message.id)) return;\n seenMessageIds.add(message.id);\n trimSeenIds();\n\n callbacks.onMessage(message);\n } else if (eventType === 'resync') {\n // Let onResync → startSession handle full session recreation.\n // Don't call reconnectImmediate() — startSession disconnects this\n // session anyway, so the reconnect would be immediately thrown away.\n callbacks.onResync();\n }\n },\n onError: (err) => {\n if (!disposed) callbacks.onError(err);\n },\n onClosed: () => {\n callbacks.onStatusChange('closed');\n disposed = true;\n },\n onReconnecting: () => {\n if (!disposed) callbacks.onStatusChange('reconnecting');\n },\n },\n );\n }\n\n connect();\n\n return {\n serviceUrl,\n channelId,\n initialParticipants: participants,\n initialMessages: messages,\n networkMonitor: networkMonitor ?? null,\n\n sendMessage: async (type, body, attributes, idempotencyKey) => {\n if (disposed) throw new ChatDisconnectedError('disconnected');\n\n const payload: SendMessageRequest<D> = {\n sender_id: profile.id,\n type,\n body,\n attributes,\n ...(idempotencyKey ? { idempotency_key: idempotencyKey } : {}),\n };\n\n const sendFn = async (): Promise<SendMessageResponse> => {\n const res = await fetch(\n `${serviceUrl}/channels/${channelId}/messages`,\n {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', ...customHeaders },\n body: JSON.stringify(payload),\n signal: sessionAbort.signal,\n },\n );\n\n if (!res.ok) {\n await throwHttpError(res);\n }\n\n return res.json();\n };\n\n const response = retryConfig\n ? await withRetry(sendFn, retryConfig, sessionAbort.signal)\n : await sendFn();\n\n seenMessageIds.add(response.id);\n return response;\n },\n\n markAsRead: async (messageId: string) => {\n const readFn = async (): Promise<void> => {\n const res = await fetch(`${serviceUrl}/channels/${channelId}/read`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', ...customHeaders },\n body: JSON.stringify({\n participant_id: profile.id,\n message_id: messageId,\n }),\n signal: sessionAbort.signal,\n });\n if (!res.ok) {\n await throwHttpError(res);\n }\n };\n\n if (retryConfig) {\n try {\n await withRetry(readFn, MARK_READ_RETRY_CONFIG, sessionAbort.signal);\n } catch (err) {\n // Surface non-retryable errors (403, 404) for dev diagnostics\n if (err instanceof HttpError) {\n callbacks.onError(err);\n }\n // Swallow RetryExhaustedError — markAsRead is best-effort\n }\n } else {\n await readFn();\n }\n },\n\n disconnect: () => {\n disposed = true;\n sessionAbort.abort();\n sseConn?.close();\n sseConn = null;\n callbacks.onStatusChange('disconnected');\n },\n };\n}\n","export interface NetworkMonitor {\n isConnected(): boolean;\n subscribe(cb: (connected: boolean) => void): () => void;\n waitForOnline(signal?: AbortSignal): Promise<void>;\n dispose(): void;\n}\n\nfunction createStubMonitor(): NetworkMonitor {\n return {\n isConnected: () => true,\n subscribe: () => () => {},\n waitForOnline: () => Promise.resolve(),\n dispose: () => {},\n };\n}\n\nlet resolvedNetInfo: any = undefined;\nlet netInfoResolved = false;\n\nfunction getNetInfo(): any {\n if (netInfoResolved) return resolvedNetInfo;\n netInfoResolved = true;\n try {\n resolvedNetInfo = require('@react-native-community/netinfo');\n } catch {\n resolvedNetInfo = null;\n }\n return resolvedNetInfo;\n}\n\nexport function createNetworkMonitor(): NetworkMonitor {\n const NetInfo = getNetInfo();\n if (!NetInfo) return createStubMonitor();\n\n const netInfoModule = NetInfo.default ?? NetInfo;\n\n let connected = true;\n const listeners = new Set<(connected: boolean) => void>();\n let unsubscribeNetInfo: (() => void) | null = null;\n\n unsubscribeNetInfo = netInfoModule.addEventListener(\n (state: { isConnected: boolean | null }) => {\n const next = state.isConnected !== false;\n if (next === connected) return;\n connected = next;\n for (const cb of listeners) {\n cb(connected);\n }\n },\n );\n\n return {\n isConnected: () => connected,\n\n subscribe: (cb) => {\n listeners.add(cb);\n return () => {\n listeners.delete(cb);\n };\n },\n\n waitForOnline: (signal?) => {\n if (connected) return Promise.resolve();\n\n return new Promise<void>((resolve, reject) => {\n if (signal?.aborted) {\n reject(signal.reason ?? new DOMException('Aborted', 'AbortError'));\n return;\n }\n\n const unsub = (): void => {\n listeners.delete(handler);\n signal?.removeEventListener('abort', onAbort);\n };\n\n const handler = (isOnline: boolean): void => {\n if (isOnline) {\n unsub();\n resolve();\n }\n };\n\n const onAbort = (): void => {\n unsub();\n reject(signal!.reason ?? new DOMException('Aborted', 'AbortError'));\n };\n\n listeners.add(handler);\n signal?.addEventListener('abort', onAbort, { once: true });\n });\n },\n\n dispose: () => {\n listeners.clear();\n unsubscribeNetInfo?.();\n unsubscribeNetInfo = null;\n },\n };\n}\n","import type { SendMessageResponse } from '@pedi/chika-types';\nimport { withRetry, type RetryConfig } from './retry';\nimport { QueueFullError, ChatDisconnectedError } from './errors';\nimport type { NetworkMonitor } from './network-monitor';\n\nexport interface QueueStorage {\n getItem(key: string): Promise<string | null>;\n setItem(key: string, value: string): Promise<void>;\n removeItem(key: string): Promise<void>;\n}\n\nlet resolvedStorage: { type: string; adapter: QueueStorage } | null | undefined;\n\nfunction tryRequire(name: string): any {\n try {\n return require(name);\n } catch {\n return null;\n }\n}\n\nfunction createMmkvAdapter(mod: any): QueueStorage {\n const MMKV = mod.MMKV ?? mod.default?.MMKV ?? mod;\n const instance = new MMKV({ id: 'chika-queue' });\n return {\n getItem: (key) => Promise.resolve(instance.getString(key) ?? null),\n setItem: (key, value) => { instance.set(key, value); return Promise.resolve(); },\n removeItem: (key) => { instance.delete(key); return Promise.resolve(); },\n };\n}\n\nfunction createAsyncStorageAdapterFrom(mod: any): QueueStorage {\n const storage = mod.default ?? mod;\n return {\n getItem: (key) => storage.getItem(key),\n setItem: (key, value) => storage.setItem(key, value),\n removeItem: (key) => storage.removeItem(key),\n };\n}\n\n/**\n * Auto-detect the best available storage for queue persistence.\n * Priority: react-native-mmkv (fastest, sync) > AsyncStorage > null.\n * Returns `null` if neither is installed — no hard dependencies.\n *\n * Usage:\n * ```ts\n * import { createQueueStorage } from '@pedi/chika-sdk';\n *\n * const config: ChatConfig = {\n * resilience: {\n * queueStorage: createQueueStorage() ?? undefined,\n * },\n * };\n * ```\n */\nexport function createQueueStorage(): QueueStorage | null {\n if (resolvedStorage !== undefined) return resolvedStorage?.adapter ?? null;\n\n // Priority 1: MMKV (synchronous, fastest)\n const mmkv = tryRequire('react-native-mmkv');\n if (mmkv) {\n try {\n const adapter = createMmkvAdapter(mmkv);\n resolvedStorage = { type: 'mmkv', adapter };\n return adapter;\n } catch {\n // MMKV instantiation can fail if native module isn't linked\n }\n }\n\n // Priority 2: AsyncStorage\n const asyncStorage = tryRequire('@react-native-async-storage/async-storage');\n if (asyncStorage) {\n const adapter = createAsyncStorageAdapterFrom(asyncStorage);\n resolvedStorage = { type: 'async-storage', adapter };\n return adapter;\n }\n\n resolvedStorage = null;\n return null;\n}\n\n/**\n * Creates a QueueStorage adapter backed by `@react-native-async-storage/async-storage`.\n * Returns `null` if the package is not installed.\n */\nexport function createAsyncStorageAdapter(): QueueStorage | null {\n const mod = tryRequire('@react-native-async-storage/async-storage');\n if (!mod) return null;\n return createAsyncStorageAdapterFrom(mod);\n}\n\nexport type MessageSendStatus = 'sending' | 'queued' | 'failed';\n\nexport interface QueuedMessage {\n optimisticId: string;\n status: MessageSendStatus;\n error?: Error;\n retryCount: number;\n}\n\ntype SendFn = () => Promise<SendMessageResponse>;\n\ninterface QueueEntry {\n optimisticId: string;\n sendFn: SendFn;\n status: MessageSendStatus;\n error?: Error;\n retryCount: number;\n abort: AbortController;\n resolve: (value: SendMessageResponse) => void;\n reject: (reason: Error) => void;\n}\n\n/** Serializable subset persisted to storage. */\ninterface PersistedEntry {\n optimisticId: string;\n retryCount: number;\n}\n\nexport interface MessageQueueConfig {\n channelId: string;\n maxSize: number;\n retryConfig: RetryConfig;\n networkMonitor: NetworkMonitor;\n storage?: QueueStorage;\n onError?: (error: Error) => void;\n onStatusChange?: () => void;\n}\n\nexport class MessageQueue {\n private entries: QueueEntry[] = [];\n private flushing = false;\n private unsubNetwork: (() => void) | null = null;\n private readonly storageKey: string;\n\n constructor(private readonly config: MessageQueueConfig) {\n this.storageKey = `chika_queue_${config.channelId}`;\n\n this.unsubNetwork = config.networkMonitor.subscribe((connected) => {\n if (connected) this.flush();\n });\n }\n\n /**\n * Restore queued messages from persistent storage on cold start.\n * Restored entries are fire-and-forget — there is no caller awaiting their\n * promise (the original enqueue() caller is gone after app restart).\n * Success/failure is reported via onStatusChange/onError callbacks.\n */\n async restore(\n rebuildSendFn: (optimisticId: string) => SendFn | null,\n ): Promise<void> {\n if (!this.config.storage) return;\n\n try {\n const raw = await this.config.storage.getItem(this.storageKey);\n if (!raw) return;\n\n const persisted: PersistedEntry[] = JSON.parse(raw);\n for (const entry of persisted) {\n const sendFn = rebuildSendFn(entry.optimisticId);\n if (!sendFn) continue;\n\n const abort = new AbortController();\n this.entries.push({\n optimisticId: entry.optimisticId,\n sendFn,\n status: 'queued',\n retryCount: entry.retryCount,\n abort,\n // No-op: restored entries have no caller awaiting the promise.\n resolve: () => {},\n reject: () => {},\n });\n }\n\n if (this.entries.length > 0) {\n this.config.onStatusChange?.();\n this.flush();\n }\n } catch {\n this.config.onError?.(new Error('Failed to restore message queue from storage'));\n }\n }\n\n get pendingCount(): number {\n return this.entries.length;\n }\n\n getAll(): QueuedMessage[] {\n return this.entries.map((e) => ({\n optimisticId: e.optimisticId,\n status: e.status,\n error: e.error,\n retryCount: e.retryCount,\n }));\n }\n\n getStatus(optimisticId: string): QueuedMessage | undefined {\n const entry = this.entries.find((e) => e.optimisticId === optimisticId);\n if (!entry) return undefined;\n return {\n optimisticId: entry.optimisticId,\n status: entry.status,\n error: entry.error,\n retryCount: entry.retryCount,\n };\n }\n\n enqueue(sendFn: SendFn, optimisticId: string): Promise<SendMessageResponse> {\n if (this.entries.length >= this.config.maxSize) {\n throw new QueueFullError(this.config.maxSize);\n }\n\n const abort = new AbortController();\n\n return new Promise<SendMessageResponse>((resolve, reject) => {\n const entry: QueueEntry = {\n optimisticId,\n sendFn,\n status: 'queued',\n retryCount: 0,\n abort,\n resolve,\n reject,\n };\n\n this.entries.push(entry);\n this.config.onStatusChange?.();\n this.persist();\n this.flush();\n });\n }\n\n cancel(optimisticId: string): void {\n const idx = this.entries.findIndex((e) => e.optimisticId === optimisticId);\n if (idx === -1) return;\n\n const entry = this.entries[idx]!;\n entry.abort.abort();\n entry.reject(new DOMException('Cancelled', 'AbortError'));\n this.entries.splice(idx, 1);\n this.config.onStatusChange?.();\n this.persist();\n }\n\n retry(optimisticId: string): void {\n const entry = this.entries.find((e) => e.optimisticId === optimisticId);\n if (!entry || entry.status !== 'failed') return;\n\n entry.status = 'queued';\n entry.error = undefined;\n entry.abort = new AbortController();\n this.config.onStatusChange?.();\n this.flush();\n }\n\n dispose(): void {\n this.unsubNetwork?.();\n this.unsubNetwork = null;\n\n for (const entry of this.entries) {\n entry.abort.abort();\n entry.reject(new DOMException('Queue disposed', 'AbortError'));\n }\n this.entries = [];\n }\n\n /**\n * Trigger a flush of queued messages. Returns immediately if a flush is\n * already in progress or the network is offline. The in-progress flush\n * will pick up any newly queued entries via its while loop.\n */\n async flush(): Promise<void> {\n if (this.flushing) return;\n if (!this.config.networkMonitor.isConnected()) return;\n\n this.flushing = true;\n let awaitingSession = false;\n\n try {\n while (this.entries.length > 0) {\n // Only pick up 'queued' entries — 'sending' means a previous flush is\n // mid-request. Server-side idempotency key prevents duplicates if the\n // prior request actually succeeded but we never got the response.\n const entry = this.entries.find((e) => e.status === 'queued');\n if (!entry) break;\n if (!this.config.networkMonitor.isConnected()) break;\n\n entry.status = 'sending';\n this.config.onStatusChange?.();\n\n try {\n const result = await withRetry(\n entry.sendFn,\n this.config.retryConfig,\n entry.abort.signal,\n );\n\n entry.resolve(result);\n const idx = this.entries.indexOf(entry);\n if (idx !== -1) this.entries.splice(idx, 1);\n this.config.onStatusChange?.();\n this.persist();\n } catch (err) {\n if (entry.abort.signal.aborted) continue; // cancelled, already removed\n\n // Session not yet reconnected — revert to 'queued' and stop flushing.\n // flush() will be called again when the session is re-established\n // via the explicit flush() call in use-chat.ts startSession().\n if (err instanceof ChatDisconnectedError) {\n entry.status = 'queued';\n this.config.onStatusChange?.();\n awaitingSession = true;\n break;\n }\n\n entry.status = 'failed';\n entry.error = err instanceof Error ? err : new Error(String(err));\n entry.retryCount++;\n this.config.onStatusChange?.();\n this.persist();\n\n // Don't block the queue on a failed entry — skip to next\n }\n }\n } finally {\n this.flushing = false;\n // Re-check: entries may have been added while we were flushing.\n // Skip if we're waiting for session — startSession() will call flush().\n if (\n !awaitingSession &&\n this.entries.some((e) => e.status === 'queued') &&\n this.config.networkMonitor.isConnected()\n ) {\n queueMicrotask(() => this.flush());\n }\n }\n }\n\n private persist(): void {\n if (!this.config.storage) return;\n\n const data: PersistedEntry[] = this.entries.map((e) => ({\n optimisticId: e.optimisticId,\n retryCount: e.retryCount,\n }));\n\n const onErr = (err: unknown) => {\n this.config.onError?.(\n err instanceof Error ? err : new Error('Queue storage write failed'),\n );\n };\n\n if (data.length === 0) {\n this.config.storage.removeItem(this.storageKey).catch(onErr);\n } else {\n this.config.storage\n .setItem(this.storageKey, JSON.stringify(data))\n .catch(onErr);\n }\n }\n}\n","import { useEffect, useRef, useState, useCallback } from 'react';\nimport { AppState, Platform, type AppStateStatus } from 'react-native';\nimport type { UnreadCountResponse, SSEUnreadUpdateEvent } from '@pedi/chika-types';\nimport type { ChatConfig } from './types';\nimport { resolveServerUrl } from './resolve-url';\nimport { createSSEConnection, type SSEConnection } from './sse-connection';\nimport { createNetworkMonitor, type NetworkMonitor } from './network-monitor';\n\nconst DEFAULT_BACKGROUND_GRACE_MS = 2000;\nconst UNREAD_CUSTOM_EVENTS = ['unread_snapshot', 'unread_update', 'unread_clear'];\n\nexport interface UseUnreadOptions {\n config: ChatConfig;\n channelId: string;\n participantId: string;\n enabled?: boolean;\n}\n\nexport interface UseUnreadReturn {\n unreadCount: number;\n hasUnread: boolean;\n lastMessageAt: string | null;\n error: Error | null;\n}\n\nexport function useUnread(options: UseUnreadOptions): UseUnreadReturn {\n const { config, channelId, participantId, enabled = true } = options;\n\n const [unreadCount, setUnreadCount] = useState(0);\n const [lastMessageAt, setLastMessageAt] = useState<string | null>(null);\n const [error, setError] = useState<Error | null>(null);\n\n const connRef = useRef<SSEConnection | null>(null);\n const configRef = useRef(config);\n configRef.current = config;\n const appStateRef = useRef<AppStateStatus>(AppState.currentState);\n const backgroundTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n\n const backgroundGraceMs =\n config.backgroundGraceMs ?? (Platform.OS === 'android' ? DEFAULT_BACKGROUND_GRACE_MS : 0);\n\n const [monitor, setMonitor] = useState<NetworkMonitor | null>(null);\n const resilienceEnabled = config.resilience !== false;\n const injectedMonitor =\n typeof config.resilience === 'object' ? config.resilience.networkMonitor : undefined;\n\n useEffect(() => {\n if (!resilienceEnabled) {\n setMonitor(null);\n return;\n }\n if (injectedMonitor) {\n setMonitor(injectedMonitor);\n return;\n }\n const m = createNetworkMonitor();\n setMonitor(m);\n return () => {\n m.dispose();\n setMonitor(null);\n };\n }, [resilienceEnabled, injectedMonitor]);\n\n const connect = useCallback(() => {\n connRef.current?.close();\n connRef.current = null;\n\n const serviceUrl = resolveServerUrl(configRef.current.manifest, channelId);\n const customHeaders = configRef.current.headers ?? {};\n const url = `${serviceUrl}/channels/${channelId}/unread?participant_id=${encodeURIComponent(participantId)}`;\n\n connRef.current = createSSEConnection(\n {\n url,\n headers: customHeaders,\n reconnectDelayMs: configRef.current.reconnectDelayMs,\n customEvents: UNREAD_CUSTOM_EVENTS,\n networkMonitor: monitor ?? undefined,\n },\n {\n onOpen: () => {\n setError(null);\n },\n onEvent: (eventType, data) => {\n try {\n if (eventType === 'unread_snapshot') {\n const snapshot: UnreadCountResponse = JSON.parse(data);\n setUnreadCount(snapshot.unread_count);\n setLastMessageAt(snapshot.last_message_at);\n } else if (eventType === 'unread_update') {\n const update: SSEUnreadUpdateEvent['data'] = JSON.parse(data);\n setUnreadCount((prev) => prev + 1);\n setLastMessageAt(update.created_at);\n } else if (eventType === 'unread_clear') {\n const clear: { channel_id: string; unread_count: number } = JSON.parse(data);\n setUnreadCount(clear.unread_count);\n }\n } catch {\n setError(new Error('Failed to parse unread SSE event'));\n }\n },\n onError: (err) => {\n setError(err);\n },\n onClosed: () => {\n connRef.current = null;\n },\n },\n );\n }, [channelId, participantId, monitor]);\n\n const disconnect = useCallback(() => {\n connRef.current?.close();\n connRef.current = null;\n }, []);\n\n useEffect(() => {\n setUnreadCount(0);\n setLastMessageAt(null);\n setError(null);\n\n if (!enabled) {\n disconnect();\n return;\n }\n\n // Wait for monitor before connecting (avoids double-connect on mount)\n if (resilienceEnabled && !monitor) return;\n\n connect();\n\n return () => {\n disconnect();\n if (backgroundTimerRef.current) {\n clearTimeout(backgroundTimerRef.current);\n backgroundTimerRef.current = null;\n }\n };\n }, [channelId, participantId, enabled, connect, disconnect]);\n\n useEffect(() => {\n if (!enabled) return;\n\n const subscription = AppState.addEventListener('change', (nextAppState) => {\n const prev = appStateRef.current;\n appStateRef.current = nextAppState;\n\n if (!connRef.current && nextAppState !== 'active') return;\n\n const shouldTeardown =\n nextAppState === 'background' ||\n (Platform.OS === 'ios' && nextAppState === 'inactive');\n\n if (nextAppState === 'active') {\n if (backgroundTimerRef.current) {\n clearTimeout(backgroundTimerRef.current);\n backgroundTimerRef.current = null;\n return;\n }\n\n if (prev.match(/inactive|background/) && !connRef.current) {\n connect();\n }\n } else if (shouldTeardown) {\n if (backgroundTimerRef.current) return;\n\n if (backgroundGraceMs === 0) {\n disconnect();\n } else {\n backgroundTimerRef.current = setTimeout(() => {\n backgroundTimerRef.current = null;\n disconnect();\n }, backgroundGraceMs);\n }\n }\n });\n\n return () => {\n subscription.remove();\n if (backgroundTimerRef.current) {\n clearTimeout(backgroundTimerRef.current);\n backgroundTimerRef.current = null;\n }\n };\n }, [enabled, backgroundGraceMs, connect, disconnect]);\n\n return { unreadCount, hasUnread: unreadCount > 0, lastMessageAt, error };\n}\n"],"mappings":"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","names":["EventSource","DEFAULT_RECONNECT_DELAY_MS","import_react","import_react_native","DEFAULT_BACKGROUND_GRACE_MS"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/use-chat.ts","../src/errors.ts","../src/retry.ts","../src/resolve-url.ts","../src/sse-connection.ts","../src/session.ts","../src/network-monitor.ts","../src/message-queue.ts","../src/use-unread.ts"],"sourcesContent":["export { useChat } from './use-chat';\nexport { useUnread } from './use-unread';\nexport { createChatSession } from './session';\nexport { resolveServerUrl, createManifest } from './resolve-url';\nexport { createSSEConnection } from './sse-connection';\nexport { ChatDisconnectedError, ChannelClosedError, HttpError, RetryExhaustedError, QueueFullError } from './errors';\nexport { withRetry, isRetryableError, calculateBackoff, resolveRetryConfig } from './retry';\nexport { createNetworkMonitor } from './network-monitor';\nexport { createQueueStorage, createAsyncStorageAdapter } from './message-queue';\n\nexport type { ChatConfig, ChatStatus, UseChatOptions, UseChatReturn, ResilienceConfig } from './types';\nexport type { ChatSession, SessionCallbacks } from './session';\nexport type { UseUnreadOptions, UseUnreadReturn } from './use-unread';\nexport type { SSEConnection, SSEConnectionConfig, SSEConnectionCallbacks } from './sse-connection';\nexport type { RetryConfig } from './retry';\nexport type { NetworkMonitor } from './network-monitor';\nexport type { MessageSendStatus, QueuedMessage, QueueStorage } from './message-queue';\n\nexport type {\n ChatDomain,\n DefaultDomain,\n Message,\n Participant,\n MessageAttributes,\n SendMessageResponse,\n ChatManifest,\n ChatBucket,\n UnreadCountResponse,\n MarkReadRequest,\n SSEUnreadUpdateEvent,\n SSEUnreadClearEvent,\n SSEUnreadEvent,\n PediChat,\n PediRole,\n PediVehicle,\n PediLocation,\n PediParticipantMeta,\n PediMessageType,\n PediMessageAttributes,\n} from '@pedi/chika-types';\n","import { useEffect, useRef, useState, useCallback } from 'react';\nimport { AppState, Platform, type AppStateStatus } from 'react-native';\nimport type {\n ChatDomain,\n DefaultDomain,\n Message,\n Participant,\n MessageAttributes,\n SendMessageResponse,\n} from '@pedi/chika-types';\nimport type { UseChatOptions, UseChatReturn, ChatStatus } from './types';\nimport { ChatDisconnectedError, ChannelClosedError, QueueFullError, RetryExhaustedError } from './errors';\nimport { isRetryableError, resolveRetryConfig } from './retry';\nimport { createChatSession, type ChatSession, type SessionCallbacks } from './session';\nimport { createNetworkMonitor, type NetworkMonitor } from './network-monitor';\nimport { MessageQueue, type QueuedMessage } from './message-queue';\n\nconst DEFAULT_BACKGROUND_GRACE_MS = 2000;\nconst DEFAULT_MAX_QUEUE_SIZE = 50;\n\n// Module-scope queue registry, keyed by channelId. Survives component remounts.\nconst queueRegistry = new Map<string, { queue: MessageQueue; refCount: number }>();\n\n/**\n * React hook for real-time chat over SSE.\n * Manages connection lifecycle, AppState transitions, message deduplication, reconnection,\n * and optional network resilience (retry, offline queue, network monitoring).\n *\n * @template D - Chat domain type for role/message type narrowing. Defaults to DefaultDomain.\n */\nexport function useChat<D extends ChatDomain = DefaultDomain>(\n { config, channelId, profile, onMessage }: UseChatOptions<D>,\n): UseChatReturn<D> {\n const [messages, setMessages] = useState<Message<D>[]>([]);\n const [participants, setParticipants] = useState<Participant<D>[]>([]);\n const [status, setStatus] = useState<ChatStatus>('connecting');\n const [error, setError] = useState<Error | null>(null);\n const [pendingMessages, setPendingMessages] = useState<QueuedMessage[]>([]);\n\n const sessionRef = useRef<ChatSession<D> | null>(null);\n const disposedRef = useRef(false);\n const messagesRef = useRef(messages);\n messagesRef.current = messages;\n const statusRef = useRef(status);\n statusRef.current = status;\n const appStateRef = useRef<AppStateStatus>(AppState.currentState);\n const backgroundTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n const profileRef = useRef(profile);\n profileRef.current = profile;\n const configRef = useRef(config);\n configRef.current = config;\n const onMessageRef = useRef(onMessage);\n onMessageRef.current = onMessage;\n const startingRef = useRef(false);\n const pendingOptimisticIds = useRef(new Set<string>());\n const [monitor, setMonitor] = useState<NetworkMonitor | null>(null);\n const [monitorReady, setMonitorReady] = useState(false);\n const monitorRef = useRef<NetworkMonitor | null>(null);\n monitorRef.current = monitor;\n const queueRef = useRef<MessageQueue | null>(null);\n\n const resilienceEnabled = config.resilience !== false;\n const queueEnabled =\n resilienceEnabled &&\n (typeof config.resilience === 'object' ? config.resilience.offlineQueue !== false : true);\n const retryConfig = resolveRetryConfig(config.resilience);\n const maxQueueSize =\n (resilienceEnabled && config.resilience && typeof config.resilience === 'object'\n ? config.resilience.maxQueueSize\n : undefined) ?? DEFAULT_MAX_QUEUE_SIZE;\n\n const backgroundGraceMs =\n config.backgroundGraceMs ?? (Platform.OS === 'android' ? DEFAULT_BACKGROUND_GRACE_MS : 0);\n\n // Resolve user-injected monitor (stable reference, no side effect)\n const injectedMonitor =\n typeof config.resilience === 'object' ? config.resilience.networkMonitor : undefined;\n\n // Create monitor in useEffect to avoid side effects during render.\n // Uses state (not ref) so the queue effect re-runs when monitor is ready.\n useEffect(() => {\n if (!resilienceEnabled) {\n setMonitor(null);\n setMonitorReady(true);\n return;\n }\n if (injectedMonitor) {\n setMonitor(injectedMonitor);\n setMonitorReady(true);\n return; // user owns lifecycle\n }\n try {\n const m = createNetworkMonitor();\n setMonitor(m);\n setMonitorReady(true);\n return () => {\n m.dispose();\n setMonitor(null);\n };\n } catch {\n // NetInfo native module may be present but not linked — fall back to\n // no monitor so the session effect guard doesn't block forever.\n setMonitor(null);\n setMonitorReady(true);\n }\n }, [resilienceEnabled, injectedMonitor]);\n\n const callbacks: SessionCallbacks<D> = {\n onMessage: (message) => {\n if (disposedRef.current) return;\n let matchedOptimisticId: string | null = null;\n setMessages((prev: Message<D>[]) => {\n if (pendingOptimisticIds.current.size === 0) {\n return [...prev, message];\n }\n\n const optimisticIdx = prev.findIndex(\n (m) =>\n pendingOptimisticIds.current.has(m.id) &&\n m.sender_id === message.sender_id &&\n m.body === message.body &&\n m.type === message.type,\n );\n if (optimisticIdx !== -1) {\n matchedOptimisticId = prev[optimisticIdx]!.id;\n pendingOptimisticIds.current.delete(matchedOptimisticId);\n const next = [...prev];\n next[optimisticIdx] = message;\n return next;\n }\n return [...prev, message];\n });\n // Clean up any queued/failed queue entry — SSE confirmed delivery.\n // Done outside setMessages updater to avoid setState-in-setState.\n if (matchedOptimisticId) {\n const s = queueRef.current?.getStatus(matchedOptimisticId);\n if (s && s.status !== 'sending') {\n queueRef.current?.cancel(matchedOptimisticId);\n }\n }\n onMessageRef.current?.(message);\n },\n onStatusChange: (nextStatus) => {\n if (disposedRef.current) return;\n setStatus(nextStatus);\n if (nextStatus === 'connected') {\n setError(null);\n }\n },\n onError: (err) => {\n if (disposedRef.current) return;\n setError(err);\n },\n onResync: () => {\n if (disposedRef.current) return;\n startSession();\n },\n };\n\n async function startSession(): Promise<void> {\n if (startingRef.current) return;\n startingRef.current = true;\n\n const existing = sessionRef.current;\n if (existing) {\n existing.disconnect();\n sessionRef.current = null;\n }\n\n try {\n const session = await createChatSession<D>(\n configRef.current,\n channelId,\n profileRef.current,\n callbacks,\n monitorRef.current ?? undefined,\n );\n\n if (disposedRef.current) {\n session.disconnect();\n return;\n }\n\n sessionRef.current = session;\n setParticipants(session.initialParticipants);\n\n // Flush any messages queued while session was unavailable\n queueRef.current?.flush();\n\n // Re-merge any pending optimistic messages after resync\n if (pendingOptimisticIds.current.size > 0) {\n const pendingIds = pendingOptimisticIds.current;\n setMessages((prev) => {\n const pendingMsgs = prev.filter((m) => pendingIds.has(m.id));\n return [...session.initialMessages, ...pendingMsgs];\n });\n } else {\n setMessages(session.initialMessages);\n }\n } catch (err) {\n if (disposedRef.current) return;\n\n if (err instanceof ChannelClosedError) {\n setStatus('closed');\n setError(err);\n return;\n }\n\n setStatus('error');\n setError(err instanceof Error ? err : new Error(String(err)));\n } finally {\n startingRef.current = false;\n }\n }\n\n // Session lifecycle\n useEffect(() => {\n disposedRef.current = false;\n // Don't start until monitor initialization is complete\n if (!monitorReady) return;\n startSession();\n\n return () => {\n disposedRef.current = true;\n if (statusRef.current === 'connected' && sessionRef.current) {\n const lastMsg = messagesRef.current[messagesRef.current.length - 1];\n if (lastMsg) {\n sessionRef.current.markAsRead(lastMsg.id).catch(() => {});\n }\n }\n if (backgroundTimerRef.current) {\n clearTimeout(backgroundTimerRef.current);\n backgroundTimerRef.current = null;\n }\n sessionRef.current?.disconnect();\n sessionRef.current = null;\n };\n }, [channelId, monitorReady]);\n\n // Module-scope queue lifecycle (ref-counted, survives remounts)\n useEffect(() => {\n if (!queueEnabled || !monitor || !retryConfig) return;\n\n let entry = queueRegistry.get(channelId);\n if (!entry) {\n const queue = new MessageQueue({\n channelId,\n maxSize: maxQueueSize,\n retryConfig,\n networkMonitor: monitor,\n storage:\n typeof config.resilience === 'object'\n ? config.resilience.queueStorage\n : undefined,\n onError: (err) => {\n if (!disposedRef.current) setError(err);\n },\n onStatusChange: () => {\n if (!disposedRef.current) {\n setPendingMessages(queueRef.current?.getAll() ?? []);\n }\n },\n });\n entry = { queue, refCount: 0 };\n queueRegistry.set(channelId, entry);\n }\n entry.refCount++;\n queueRef.current = entry.queue;\n\n return () => {\n const e = queueRegistry.get(channelId);\n if (e) {\n e.refCount--;\n if (e.refCount <= 0) {\n e.queue.dispose();\n queueRegistry.delete(channelId);\n }\n }\n queueRef.current = null;\n };\n }, [channelId, queueEnabled, monitor]);\n\n // Network monitor: auto-rejoin on connectivity return when in error state\n useEffect(() => {\n if (!monitor || !resilienceEnabled) return;\n\n const unsub = monitor.subscribe((connected: boolean) => {\n if (connected && statusRef.current === 'error' && !startingRef.current) {\n startSession();\n }\n });\n\n return unsub;\n }, [channelId, resilienceEnabled, monitor]);\n\n // AppState lifecycle\n useEffect(() => {\n function teardownSession(): void {\n sessionRef.current?.disconnect();\n sessionRef.current = null;\n setStatus('disconnected');\n }\n\n const subscription = AppState.addEventListener('change', (nextAppState) => {\n const prev = appStateRef.current;\n appStateRef.current = nextAppState;\n\n if (!sessionRef.current && nextAppState !== 'active') return;\n\n const shouldTeardown =\n nextAppState === 'background' ||\n (Platform.OS === 'ios' && nextAppState === 'inactive');\n\n if (nextAppState === 'active') {\n if (backgroundTimerRef.current) {\n clearTimeout(backgroundTimerRef.current);\n backgroundTimerRef.current = null;\n return;\n }\n\n if (prev.match(/inactive|background/) && !sessionRef.current) {\n startSession();\n }\n } else if (shouldTeardown) {\n if (backgroundTimerRef.current) return;\n\n if (backgroundGraceMs === 0) {\n teardownSession();\n } else {\n backgroundTimerRef.current = setTimeout(() => {\n backgroundTimerRef.current = null;\n teardownSession();\n }, backgroundGraceMs);\n }\n }\n });\n\n return () => {\n subscription.remove();\n if (backgroundTimerRef.current) {\n clearTimeout(backgroundTimerRef.current);\n backgroundTimerRef.current = null;\n }\n };\n }, [channelId, backgroundGraceMs]);\n\n const sendMessage = useCallback(\n async (type: D['messageType'], body: string, attributes?: MessageAttributes<D>): Promise<SendMessageResponse> => {\n const session = sessionRef.current;\n if (!session) throw new ChatDisconnectedError(statusRef.current);\n\n const optimistic = configRef.current.optimisticSend !== false;\n // Serves as both the optimistic message ID and the server-side idempotency key\n const messageKey = `optimistic_${Date.now()}_${Math.random().toString(36).slice(2, 7)}`;\n\n if (optimistic) {\n pendingOptimisticIds.current.add(messageKey);\n const provisionalMsg: Message<D> = {\n id: messageKey,\n channel_id: channelId,\n sender_id: profileRef.current.id,\n sender_role: profileRef.current.role,\n type,\n body,\n attributes: (attributes ?? {}) as MessageAttributes<D>,\n created_at: new Date().toISOString(),\n };\n setMessages((prev) => [...prev, provisionalMsg]);\n }\n\n const doSend = () => {\n const s = sessionRef.current;\n if (!s) throw new ChatDisconnectedError(statusRef.current);\n return s.sendMessage(type, body, attributes, messageKey);\n };\n\n const handleSuccess = (response: SendMessageResponse): void => {\n if (optimistic) {\n pendingOptimisticIds.current.delete(messageKey);\n setMessages((prev) => {\n const stillPending = prev.some((m) => m.id === messageKey);\n if (!stillPending) return prev;\n return prev.map((m) =>\n m.id === messageKey\n ? { ...m, id: response.id, created_at: response.created_at }\n : m,\n );\n });\n }\n };\n\n const handleError = (_err: unknown): void => {\n if (optimistic) {\n pendingOptimisticIds.current.delete(messageKey);\n setMessages((prev) => prev.filter((m) => m.id !== messageKey));\n }\n };\n\n // Queue path: enqueue and let queue handle retry + offline\n if (queueRef.current) {\n try {\n const response = await queueRef.current.enqueue(doSend, messageKey);\n handleSuccess(response);\n return response;\n } catch (err) {\n if (err instanceof QueueFullError) {\n handleError(err);\n throw err;\n }\n if (err instanceof RetryExhaustedError || !isRetryableError(err)) {\n // For failed messages: if optimistic, mark as failed (keep in UI)\n // The pendingMessages state shows the status\n if (optimistic && err instanceof RetryExhaustedError) {\n // Keep optimistic message visible — queue tracks status as 'failed'.\n // Keep messageKey in pendingOptimisticIds so SSE reconciliation can\n // still match if the server eventually received the message.\n // Removed on explicit cancelMessage() call.\n } else {\n handleError(err);\n }\n throw err;\n }\n handleError(err);\n throw err;\n }\n }\n\n // Non-queue path: direct send (session.ts handles retry if enabled)\n try {\n const response = await doSend();\n handleSuccess(response);\n return response;\n } catch (err) {\n handleError(err);\n throw err;\n }\n },\n [channelId],\n );\n\n const cancelMessage = useCallback(\n (optimisticId: string) => {\n queueRef.current?.cancel(optimisticId);\n pendingOptimisticIds.current.delete(optimisticId);\n setMessages((prev) => prev.filter((m) => m.id !== optimisticId));\n },\n [],\n );\n\n const retryMessage = useCallback(\n (optimisticId: string) => {\n queueRef.current?.retry(optimisticId);\n },\n [],\n );\n\n const disconnect = useCallback(() => {\n sessionRef.current?.disconnect();\n sessionRef.current = null;\n setStatus('disconnected');\n }, []);\n\n return {\n messages,\n participants,\n status,\n error,\n sendMessage,\n disconnect,\n pendingMessages,\n cancelMessage,\n retryMessage,\n };\n}\n","import type { ChatStatus } from './types';\n\nexport class ChatDisconnectedError extends Error {\n constructor(public readonly status: ChatStatus) {\n super(`Cannot send message while ${status}`);\n this.name = 'ChatDisconnectedError';\n }\n}\n\nexport class ChannelClosedError extends Error {\n constructor(public readonly channelId: string) {\n super(`Channel ${channelId} is closed`);\n this.name = 'ChannelClosedError';\n }\n}\n\nexport class HttpError extends Error {\n constructor(\n public readonly status: number,\n public readonly body: string,\n public readonly retryAfter?: number,\n ) {\n super(`HTTP ${status}: ${body}`);\n this.name = 'HttpError';\n }\n}\n\nexport class RetryExhaustedError extends Error {\n constructor(\n public readonly operation: string,\n public readonly attempts: number,\n public readonly lastError: Error,\n ) {\n super(`${operation} failed after ${attempts} attempts: ${lastError.message}`);\n this.name = 'RetryExhaustedError';\n }\n}\n\nexport class QueueFullError extends Error {\n constructor(public readonly maxSize: number) {\n super(`Message queue full (max ${maxSize})`);\n this.name = 'QueueFullError';\n }\n}\n","import {\n HttpError,\n RetryExhaustedError,\n ChannelClosedError,\n ChatDisconnectedError,\n QueueFullError,\n} from './errors';\n\nexport interface RetryConfig {\n maxAttempts: number;\n baseDelayMs: number;\n maxDelayMs: number;\n jitterFactor: number;\n}\n\nexport const DEFAULT_RETRY_CONFIG: RetryConfig = {\n maxAttempts: 3,\n baseDelayMs: 1000,\n maxDelayMs: 10000,\n jitterFactor: 0.3,\n};\n\nexport function calculateBackoff(attempt: number, config: RetryConfig): number {\n const delay = Math.min(config.baseDelayMs * 2 ** attempt, config.maxDelayMs);\n const jitter = 1 + (Math.random() * 2 - 1) * config.jitterFactor;\n return Math.round(delay * jitter);\n}\n\nexport function isRetryableError(error: unknown): boolean {\n if (error instanceof ChannelClosedError) return false;\n if (error instanceof ChatDisconnectedError) return false;\n if (error instanceof QueueFullError) return false;\n if (error instanceof DOMException && error.name === 'AbortError') return false;\n\n if (error instanceof HttpError) {\n const { status } = error;\n if (status === 408 || status === 429) return true;\n if (status >= 500) return true;\n return false;\n }\n\n // TypeError from fetch = network failure\n if (error instanceof TypeError) return true;\n\n return false;\n}\n\nexport function sleep(ms: number, signal?: AbortSignal): Promise<void> {\n return new Promise((resolve, reject) => {\n if (signal?.aborted) {\n reject(signal.reason ?? new DOMException('Aborted', 'AbortError'));\n return;\n }\n\n const timer = setTimeout(resolve, ms);\n\n signal?.addEventListener(\n 'abort',\n () => {\n clearTimeout(timer);\n reject(signal.reason ?? new DOMException('Aborted', 'AbortError'));\n },\n { once: true },\n );\n });\n}\n\nexport function resolveRetryConfig(\n resilience: { retry?: Partial<RetryConfig> | false } | false | undefined,\n): RetryConfig | null {\n if (resilience === false) return null;\n if (!resilience || resilience.retry === undefined) return DEFAULT_RETRY_CONFIG;\n if (resilience.retry === false) return null;\n return { ...DEFAULT_RETRY_CONFIG, ...resilience.retry };\n}\n\nexport async function withRetry<T>(\n fn: () => Promise<T>,\n config: RetryConfig = DEFAULT_RETRY_CONFIG,\n signal?: AbortSignal,\n): Promise<T> {\n let lastError: Error | undefined;\n\n for (let attempt = 0; attempt < config.maxAttempts; attempt++) {\n if (signal?.aborted) {\n throw signal.reason ?? new DOMException('Aborted', 'AbortError');\n }\n\n try {\n return await fn();\n } catch (err) {\n lastError = err instanceof Error ? err : new Error(String(err));\n\n if (!isRetryableError(err)) throw lastError;\n\n if (attempt < config.maxAttempts - 1) {\n // Respect Retry-After header for 429 responses\n const delayMs =\n err instanceof HttpError && err.retryAfter != null\n ? err.retryAfter * 1000\n : calculateBackoff(attempt, config);\n\n await sleep(delayMs, signal);\n }\n }\n }\n\n throw new RetryExhaustedError(\n 'operation',\n config.maxAttempts,\n lastError!,\n );\n}\n","import type { ChatManifest } from '@pedi/chika-types';\n\n/**\n * Creates a single-server manifest. Use this when all channels route to the same server.\n *\n * @example\n * ```ts\n * const config: ChatConfig = { manifest: createManifest('https://chat.example.com') };\n * ```\n */\nexport function createManifest(serverUrl: string): ChatManifest {\n return { buckets: [{ group: 'default', range: [0, 99], server_url: serverUrl }] };\n}\n\nexport function resolveServerUrl(manifest: ChatManifest, channelId: string): string {\n const hash = [...channelId].reduce((sum, c) => sum + c.charCodeAt(0), 0) % 100;\n const bucket = manifest.buckets.find(\n (b) => hash >= b.range[0] && hash <= b.range[1],\n );\n if (!bucket) throw new Error(`No chat bucket for hash ${hash}`);\n return bucket.server_url;\n}\n","import EventSource from 'react-native-sse';\nimport { calculateBackoff, type RetryConfig } from './retry';\nimport type { NetworkMonitor } from './network-monitor';\n\nconst DEFAULT_RECONNECT_DELAY_MS = 3000;\n\nexport interface SSEConnectionConfig {\n url: string;\n headers?: Record<string, string>;\n reconnectDelayMs?: number;\n lastEventId?: string;\n customEvents?: string[];\n networkMonitor?: NetworkMonitor;\n}\n\nexport interface SSEConnectionCallbacks {\n onOpen?: () => void;\n onEvent: (eventType: string, data: string, lastEventId?: string) => void;\n onError?: (error: Error) => void;\n onClosed?: () => void;\n onReconnecting?: () => void;\n}\n\nexport interface SSEConnection {\n close: () => void;\n reconnectImmediate: () => void;\n}\n\nexport function createSSEConnection(\n config: SSEConnectionConfig,\n callbacks: SSEConnectionCallbacks,\n): SSEConnection {\n const baseDelay = config.reconnectDelayMs ?? DEFAULT_RECONNECT_DELAY_MS;\n const customEvents = config.customEvents ?? [];\n const monitor = config.networkMonitor;\n\n const backoffConfig: RetryConfig = {\n maxAttempts: Infinity,\n baseDelayMs: baseDelay,\n maxDelayMs: 30000,\n jitterFactor: 0.3,\n };\n\n let currentLastEventId = config.lastEventId;\n let es: EventSource<string> | null = null;\n let disposed = false;\n let reconnectTimer: ReturnType<typeof setTimeout> | null = null;\n let attempt = 0;\n let waitAbort: AbortController | null = null;\n\n function cleanup(): void {\n if (es) {\n es.removeAllEventListeners();\n es.close();\n es = null;\n }\n if (reconnectTimer) {\n clearTimeout(reconnectTimer);\n reconnectTimer = null;\n }\n if (waitAbort) {\n waitAbort.abort();\n waitAbort = null;\n }\n }\n\n async function scheduleReconnect(): Promise<void> {\n if (disposed || reconnectTimer || waitAbort) return;\n callbacks.onReconnecting?.();\n cleanup();\n\n // Wait for network if monitor available\n if (monitor && !monitor.isConnected()) {\n waitAbort = new AbortController();\n try {\n await monitor.waitForOnline(waitAbort.signal);\n } catch {\n return; // aborted via dispose\n }\n waitAbort = null;\n if (disposed) return;\n }\n\n const delay = calculateBackoff(attempt++, backoffConfig);\n reconnectTimer = setTimeout(() => {\n reconnectTimer = null;\n connect();\n }, delay);\n }\n\n function connect(): void {\n if (disposed) return;\n\n es = new EventSource<string>(config.url, {\n headers: {\n ...config.headers,\n ...(currentLastEventId && { 'Last-Event-ID': currentLastEventId }),\n },\n pollingInterval: 0,\n });\n\n es.addEventListener('open', () => {\n if (disposed) return;\n attempt = 0;\n callbacks.onOpen?.();\n });\n\n es.addEventListener('message', (event) => {\n if (disposed || !event.data) return;\n if (event.lastEventId) {\n currentLastEventId = event.lastEventId;\n }\n callbacks.onEvent('message', event.data, event.lastEventId ?? undefined);\n });\n\n for (const eventName of customEvents) {\n es.addEventListener(eventName, (event) => {\n if (disposed) return;\n callbacks.onEvent(eventName, event.data ?? '', undefined);\n });\n }\n\n es.addEventListener('error', (event) => {\n if (disposed) return;\n\n const msg = 'message' in event ? String(event.message) : '';\n\n if (msg.includes('Channel is closed') || msg.includes('410')) {\n callbacks.onClosed?.();\n cleanup();\n disposed = true;\n return;\n }\n\n if (msg) callbacks.onError?.(new Error(msg));\n\n scheduleReconnect();\n });\n\n es.addEventListener('close', () => {\n if (disposed) return;\n scheduleReconnect();\n });\n }\n\n connect();\n\n return {\n close: () => {\n disposed = true;\n cleanup();\n },\n\n reconnectImmediate: () => {\n if (disposed) return;\n cleanup(); // does NOT set disposed\n attempt = 0;\n connect();\n },\n };\n}\n","import type {\n ChatDomain,\n DefaultDomain,\n Participant,\n Message,\n JoinResponse,\n SendMessageRequest,\n SendMessageResponse,\n MessageAttributes,\n} from '@pedi/chika-types';\nimport type { ChatConfig, ChatStatus } from './types';\nimport { ChatDisconnectedError, ChannelClosedError, HttpError } from './errors';\nimport { resolveServerUrl } from './resolve-url';\nimport { createSSEConnection, type SSEConnection } from './sse-connection';\nimport { withRetry, resolveRetryConfig, type RetryConfig } from './retry';\nimport type { NetworkMonitor } from './network-monitor';\n\nconst DEFAULT_RECONNECT_DELAY_MS = 3000;\nconst MAX_SEEN_IDS = 500;\n\nconst MARK_READ_RETRY_CONFIG: RetryConfig = {\n maxAttempts: 2,\n baseDelayMs: 500,\n maxDelayMs: 2000,\n jitterFactor: 0.3,\n};\n\nexport interface SessionCallbacks<D extends ChatDomain = DefaultDomain> {\n onMessage: (message: Message<D>) => void;\n onStatusChange: (status: ChatStatus) => void;\n onError: (error: Error) => void;\n onResync: () => void;\n}\n\nexport interface ChatSession<D extends ChatDomain = DefaultDomain> {\n serviceUrl: string;\n channelId: string;\n initialParticipants: Participant<D>[];\n initialMessages: Message<D>[];\n networkMonitor: NetworkMonitor | null;\n sendMessage: (\n type: D['messageType'],\n body: string,\n attributes?: MessageAttributes<D>,\n idempotencyKey?: string,\n ) => Promise<SendMessageResponse>;\n markAsRead: (messageId: string) => Promise<void>;\n disconnect: () => void;\n}\n\nfunction parseRetryAfter(res: Response): number | undefined {\n const header = res.headers.get('Retry-After');\n if (!header) return undefined;\n const seconds = Number(header);\n return Number.isFinite(seconds) ? seconds : undefined;\n}\n\nasync function throwHttpError(res: Response): Promise<never> {\n const body = await res.text().catch(() => '');\n throw new HttpError(res.status, body, parseRetryAfter(res));\n}\n\nexport async function createChatSession<D extends ChatDomain = DefaultDomain>(\n config: ChatConfig,\n channelId: string,\n profile: Participant<D>,\n callbacks: SessionCallbacks<D>,\n networkMonitor?: NetworkMonitor,\n): Promise<ChatSession<D>> {\n const serviceUrl = resolveServerUrl(config.manifest, channelId);\n const customHeaders = config.headers ?? {};\n const reconnectDelay = config.reconnectDelayMs ?? DEFAULT_RECONNECT_DELAY_MS;\n const retryConfig = resolveRetryConfig(config.resilience);\n\n const sessionAbort = new AbortController();\n\n callbacks.onStatusChange('connecting');\n\n const joinFn = async (): Promise<JoinResponse<D>> => {\n const joinRes = await fetch(`${serviceUrl}/channels/${channelId}/join`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', ...customHeaders },\n body: JSON.stringify(profile),\n signal: sessionAbort.signal,\n });\n\n if (joinRes.status === 410) {\n throw new ChannelClosedError(channelId);\n }\n\n if (!joinRes.ok) {\n await throwHttpError(joinRes);\n }\n\n return joinRes.json();\n };\n\n const joinData = retryConfig\n ? await withRetry(joinFn, retryConfig, sessionAbort.signal)\n : await joinFn();\n\n const { messages, participants, joined_at }: JoinResponse<D> = joinData;\n\n let lastEventId =\n messages.length > 0 ? messages[messages.length - 1]!.id : undefined;\n\n const joinedAt = joined_at;\n\n const seenMessageIds = new Set<string>(messages.map((m) => m.id));\n\n let sseConn: SSEConnection | null = null;\n let disposed = false;\n\n const TRIM_THRESHOLD = MAX_SEEN_IDS * 1.5;\n\n function trimSeenIds(): void {\n if (seenMessageIds.size <= TRIM_THRESHOLD) return;\n const ids = [...seenMessageIds];\n seenMessageIds.clear();\n for (const id of ids.slice(-MAX_SEEN_IDS)) {\n seenMessageIds.add(id);\n }\n }\n\n function connect(): void {\n if (disposed) return;\n\n const streamUrl = lastEventId\n ? `${serviceUrl}/channels/${channelId}/stream`\n : `${serviceUrl}/channels/${channelId}/stream?since_time=${encodeURIComponent(joinedAt)}`;\n\n sseConn = createSSEConnection(\n {\n url: streamUrl,\n headers: customHeaders,\n reconnectDelayMs: reconnectDelay,\n lastEventId,\n customEvents: ['resync'],\n networkMonitor,\n },\n {\n onOpen: () => {\n if (!disposed) callbacks.onStatusChange('connected');\n },\n onEvent: (eventType, data, eventId) => {\n if (disposed) return;\n\n if (eventType === 'message') {\n let message: Message<D>;\n try {\n message = JSON.parse(data);\n } catch {\n callbacks.onError(new Error('Failed to parse SSE message'));\n return;\n }\n\n if (eventId) {\n lastEventId = eventId;\n }\n\n if (seenMessageIds.has(message.id)) return;\n seenMessageIds.add(message.id);\n trimSeenIds();\n\n callbacks.onMessage(message);\n } else if (eventType === 'resync') {\n // Let onResync → startSession handle full session recreation.\n // Don't call reconnectImmediate() — startSession disconnects this\n // session anyway, so the reconnect would be immediately thrown away.\n callbacks.onResync();\n }\n },\n onError: (err) => {\n if (!disposed) callbacks.onError(err);\n },\n onClosed: () => {\n callbacks.onStatusChange('closed');\n disposed = true;\n },\n onReconnecting: () => {\n if (!disposed) callbacks.onStatusChange('reconnecting');\n },\n },\n );\n }\n\n connect();\n\n return {\n serviceUrl,\n channelId,\n initialParticipants: participants,\n initialMessages: messages,\n networkMonitor: networkMonitor ?? null,\n\n sendMessage: async (type, body, attributes, idempotencyKey) => {\n if (disposed) throw new ChatDisconnectedError('disconnected');\n\n const payload: SendMessageRequest<D> = {\n sender_id: profile.id,\n type,\n body,\n attributes,\n ...(idempotencyKey ? { idempotency_key: idempotencyKey } : {}),\n };\n\n const sendFn = async (): Promise<SendMessageResponse> => {\n const res = await fetch(\n `${serviceUrl}/channels/${channelId}/messages`,\n {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', ...customHeaders },\n body: JSON.stringify(payload),\n signal: sessionAbort.signal,\n },\n );\n\n if (!res.ok) {\n await throwHttpError(res);\n }\n\n return res.json();\n };\n\n const response = retryConfig\n ? await withRetry(sendFn, retryConfig, sessionAbort.signal)\n : await sendFn();\n\n seenMessageIds.add(response.id);\n return response;\n },\n\n markAsRead: async (messageId: string) => {\n // markAsRead intentionally does NOT use sessionAbort.signal — it must\n // survive disconnect() since it's called on unmount right before disconnect.\n const readFn = async (): Promise<void> => {\n const res = await fetch(`${serviceUrl}/channels/${channelId}/read`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', ...customHeaders },\n body: JSON.stringify({\n participant_id: profile.id,\n message_id: messageId,\n }),\n });\n if (!res.ok) {\n await throwHttpError(res);\n }\n };\n\n if (retryConfig) {\n try {\n await withRetry(readFn, MARK_READ_RETRY_CONFIG);\n } catch (err) {\n // Surface non-retryable errors (403, 404) for dev diagnostics\n if (err instanceof HttpError) {\n callbacks.onError(err);\n }\n // Swallow RetryExhaustedError — markAsRead is best-effort\n }\n } else {\n await readFn();\n }\n },\n\n disconnect: () => {\n disposed = true;\n sessionAbort.abort();\n sseConn?.close();\n sseConn = null;\n callbacks.onStatusChange('disconnected');\n },\n };\n}\n","export interface NetworkMonitor {\n isConnected(): boolean;\n subscribe(cb: (connected: boolean) => void): () => void;\n waitForOnline(signal?: AbortSignal): Promise<void>;\n dispose(): void;\n}\n\nfunction createStubMonitor(): NetworkMonitor {\n return {\n isConnected: () => true,\n subscribe: () => () => {},\n waitForOnline: () => Promise.resolve(),\n dispose: () => {},\n };\n}\n\nlet resolvedNetInfo: any = undefined;\nlet netInfoResolved = false;\n\nfunction getNetInfo(): any {\n if (netInfoResolved) return resolvedNetInfo;\n netInfoResolved = true;\n try {\n resolvedNetInfo = require('@react-native-community/netinfo');\n } catch {\n resolvedNetInfo = null;\n }\n return resolvedNetInfo;\n}\n\nexport function createNetworkMonitor(): NetworkMonitor {\n const NetInfo = getNetInfo();\n if (!NetInfo) return createStubMonitor();\n\n const netInfoModule = NetInfo.default ?? NetInfo;\n\n let connected = true;\n const listeners = new Set<(connected: boolean) => void>();\n let unsubscribeNetInfo: (() => void) | null = null;\n\n unsubscribeNetInfo = netInfoModule.addEventListener(\n (state: { isConnected: boolean | null }) => {\n const next = state.isConnected !== false;\n if (next === connected) return;\n connected = next;\n for (const cb of listeners) {\n cb(connected);\n }\n },\n );\n\n return {\n isConnected: () => connected,\n\n subscribe: (cb) => {\n listeners.add(cb);\n return () => {\n listeners.delete(cb);\n };\n },\n\n waitForOnline: (signal?) => {\n if (connected) return Promise.resolve();\n\n return new Promise<void>((resolve, reject) => {\n if (signal?.aborted) {\n reject(signal.reason ?? new DOMException('Aborted', 'AbortError'));\n return;\n }\n\n const unsub = (): void => {\n listeners.delete(handler);\n signal?.removeEventListener('abort', onAbort);\n };\n\n const handler = (isOnline: boolean): void => {\n if (isOnline) {\n unsub();\n resolve();\n }\n };\n\n const onAbort = (): void => {\n unsub();\n reject(signal!.reason ?? new DOMException('Aborted', 'AbortError'));\n };\n\n listeners.add(handler);\n signal?.addEventListener('abort', onAbort, { once: true });\n });\n },\n\n dispose: () => {\n listeners.clear();\n unsubscribeNetInfo?.();\n unsubscribeNetInfo = null;\n },\n };\n}\n","import type { SendMessageResponse } from '@pedi/chika-types';\nimport { withRetry, type RetryConfig } from './retry';\nimport { QueueFullError, ChatDisconnectedError } from './errors';\nimport type { NetworkMonitor } from './network-monitor';\n\nexport interface QueueStorage {\n getItem(key: string): Promise<string | null>;\n setItem(key: string, value: string): Promise<void>;\n removeItem(key: string): Promise<void>;\n}\n\nlet resolvedStorage: { type: string; adapter: QueueStorage } | null | undefined;\n\nfunction tryRequire(name: string): any {\n try {\n return require(name);\n } catch {\n return null;\n }\n}\n\nfunction createMmkvAdapter(mod: any): QueueStorage {\n const MMKV = mod.MMKV ?? mod.default?.MMKV ?? mod;\n const instance = new MMKV({ id: 'chika-queue' });\n return {\n getItem: (key) => Promise.resolve(instance.getString(key) ?? null),\n setItem: (key, value) => { instance.set(key, value); return Promise.resolve(); },\n removeItem: (key) => { instance.delete(key); return Promise.resolve(); },\n };\n}\n\nfunction createAsyncStorageAdapterFrom(mod: any): QueueStorage {\n const storage = mod.default ?? mod;\n return {\n getItem: (key) => storage.getItem(key),\n setItem: (key, value) => storage.setItem(key, value),\n removeItem: (key) => storage.removeItem(key),\n };\n}\n\n/**\n * Auto-detect the best available storage for queue persistence.\n * Priority: react-native-mmkv (fastest, sync) > AsyncStorage > null.\n * Returns `null` if neither is installed — no hard dependencies.\n *\n * Usage:\n * ```ts\n * import { createQueueStorage } from '@pedi/chika-sdk';\n *\n * const config: ChatConfig = {\n * resilience: {\n * queueStorage: createQueueStorage() ?? undefined,\n * },\n * };\n * ```\n */\nexport function createQueueStorage(): QueueStorage | null {\n if (resolvedStorage !== undefined) return resolvedStorage?.adapter ?? null;\n\n // Priority 1: MMKV (synchronous, fastest)\n const mmkv = tryRequire('react-native-mmkv');\n if (mmkv) {\n try {\n const adapter = createMmkvAdapter(mmkv);\n resolvedStorage = { type: 'mmkv', adapter };\n return adapter;\n } catch {\n // MMKV instantiation can fail if native module isn't linked\n }\n }\n\n // Priority 2: AsyncStorage\n const asyncStorage = tryRequire('@react-native-async-storage/async-storage');\n if (asyncStorage) {\n const adapter = createAsyncStorageAdapterFrom(asyncStorage);\n resolvedStorage = { type: 'async-storage', adapter };\n return adapter;\n }\n\n resolvedStorage = null;\n return null;\n}\n\n/**\n * Creates a QueueStorage adapter backed by `@react-native-async-storage/async-storage`.\n * Returns `null` if the package is not installed.\n */\nexport function createAsyncStorageAdapter(): QueueStorage | null {\n const mod = tryRequire('@react-native-async-storage/async-storage');\n if (!mod) return null;\n return createAsyncStorageAdapterFrom(mod);\n}\n\nexport type MessageSendStatus = 'sending' | 'queued' | 'failed';\n\nexport interface QueuedMessage {\n optimisticId: string;\n status: MessageSendStatus;\n error?: Error;\n retryCount: number;\n}\n\ntype SendFn = () => Promise<SendMessageResponse>;\n\ninterface QueueEntry {\n optimisticId: string;\n sendFn: SendFn;\n status: MessageSendStatus;\n error?: Error;\n retryCount: number;\n abort: AbortController;\n resolve: (value: SendMessageResponse) => void;\n reject: (reason: Error) => void;\n}\n\n/** Serializable subset persisted to storage. */\ninterface PersistedEntry {\n optimisticId: string;\n retryCount: number;\n}\n\nexport interface MessageQueueConfig {\n channelId: string;\n maxSize: number;\n retryConfig: RetryConfig;\n networkMonitor: NetworkMonitor;\n storage?: QueueStorage;\n onError?: (error: Error) => void;\n onStatusChange?: () => void;\n}\n\nexport class MessageQueue {\n private entries: QueueEntry[] = [];\n private flushing = false;\n private unsubNetwork: (() => void) | null = null;\n private readonly storageKey: string;\n\n constructor(private readonly config: MessageQueueConfig) {\n this.storageKey = `chika_queue_${config.channelId}`;\n\n this.unsubNetwork = config.networkMonitor.subscribe((connected) => {\n if (connected) this.flush();\n });\n }\n\n /**\n * Restore queued messages from persistent storage on cold start.\n * Restored entries are fire-and-forget — there is no caller awaiting their\n * promise (the original enqueue() caller is gone after app restart).\n * Success/failure is reported via onStatusChange/onError callbacks.\n */\n async restore(\n rebuildSendFn: (optimisticId: string) => SendFn | null,\n ): Promise<void> {\n if (!this.config.storage) return;\n\n try {\n const raw = await this.config.storage.getItem(this.storageKey);\n if (!raw) return;\n\n const persisted: PersistedEntry[] = JSON.parse(raw);\n for (const entry of persisted) {\n const sendFn = rebuildSendFn(entry.optimisticId);\n if (!sendFn) continue;\n\n const abort = new AbortController();\n this.entries.push({\n optimisticId: entry.optimisticId,\n sendFn,\n status: 'queued',\n retryCount: entry.retryCount,\n abort,\n // No-op: restored entries have no caller awaiting the promise.\n resolve: () => {},\n reject: () => {},\n });\n }\n\n if (this.entries.length > 0) {\n this.config.onStatusChange?.();\n this.flush();\n }\n } catch {\n this.config.onError?.(new Error('Failed to restore message queue from storage'));\n }\n }\n\n get pendingCount(): number {\n return this.entries.length;\n }\n\n getAll(): QueuedMessage[] {\n return this.entries.map((e) => ({\n optimisticId: e.optimisticId,\n status: e.status,\n error: e.error,\n retryCount: e.retryCount,\n }));\n }\n\n getStatus(optimisticId: string): QueuedMessage | undefined {\n const entry = this.entries.find((e) => e.optimisticId === optimisticId);\n if (!entry) return undefined;\n return {\n optimisticId: entry.optimisticId,\n status: entry.status,\n error: entry.error,\n retryCount: entry.retryCount,\n };\n }\n\n enqueue(sendFn: SendFn, optimisticId: string): Promise<SendMessageResponse> {\n if (this.entries.length >= this.config.maxSize) {\n throw new QueueFullError(this.config.maxSize);\n }\n\n const abort = new AbortController();\n\n return new Promise<SendMessageResponse>((resolve, reject) => {\n const entry: QueueEntry = {\n optimisticId,\n sendFn,\n status: 'queued',\n retryCount: 0,\n abort,\n resolve,\n reject,\n };\n\n this.entries.push(entry);\n this.config.onStatusChange?.();\n this.persist();\n this.flush();\n });\n }\n\n cancel(optimisticId: string): void {\n const idx = this.entries.findIndex((e) => e.optimisticId === optimisticId);\n if (idx === -1) return;\n\n const entry = this.entries[idx]!;\n entry.abort.abort();\n entry.reject(new DOMException('Cancelled', 'AbortError'));\n this.entries.splice(idx, 1);\n this.config.onStatusChange?.();\n this.persist();\n }\n\n retry(optimisticId: string): void {\n const entry = this.entries.find((e) => e.optimisticId === optimisticId);\n if (!entry || entry.status !== 'failed') return;\n\n entry.status = 'queued';\n entry.error = undefined;\n entry.abort = new AbortController();\n this.config.onStatusChange?.();\n this.flush();\n }\n\n dispose(): void {\n this.unsubNetwork?.();\n this.unsubNetwork = null;\n\n for (const entry of this.entries) {\n entry.abort.abort();\n entry.reject(new DOMException('Queue disposed', 'AbortError'));\n }\n this.entries = [];\n }\n\n /**\n * Trigger a flush of queued messages. Returns immediately if a flush is\n * already in progress or the network is offline. The in-progress flush\n * will pick up any newly queued entries via its while loop.\n */\n async flush(): Promise<void> {\n if (this.flushing) return;\n if (!this.config.networkMonitor.isConnected()) return;\n\n this.flushing = true;\n let awaitingSession = false;\n\n try {\n while (this.entries.length > 0) {\n // Only pick up 'queued' entries — 'sending' means a previous flush is\n // mid-request. Server-side idempotency key prevents duplicates if the\n // prior request actually succeeded but we never got the response.\n const entry = this.entries.find((e) => e.status === 'queued');\n if (!entry) break;\n if (!this.config.networkMonitor.isConnected()) break;\n\n entry.status = 'sending';\n this.config.onStatusChange?.();\n\n try {\n const result = await withRetry(\n entry.sendFn,\n this.config.retryConfig,\n entry.abort.signal,\n );\n\n entry.resolve(result);\n const idx = this.entries.indexOf(entry);\n if (idx !== -1) this.entries.splice(idx, 1);\n this.config.onStatusChange?.();\n this.persist();\n } catch (err) {\n if (entry.abort.signal.aborted) continue; // cancelled, already removed\n\n // Session not yet reconnected — revert to 'queued' and stop flushing.\n // flush() will be called again when the session is re-established\n // via the explicit flush() call in use-chat.ts startSession().\n if (err instanceof ChatDisconnectedError) {\n entry.status = 'queued';\n this.config.onStatusChange?.();\n awaitingSession = true;\n break;\n }\n\n entry.status = 'failed';\n entry.error = err instanceof Error ? err : new Error(String(err));\n entry.retryCount++;\n entry.reject(entry.error);\n this.config.onStatusChange?.();\n this.persist();\n\n // Don't block the queue on a failed entry — skip to next\n }\n }\n } finally {\n this.flushing = false;\n // Re-check: entries may have been added while we were flushing.\n // Skip if we're waiting for session — startSession() will call flush().\n if (\n !awaitingSession &&\n this.entries.some((e) => e.status === 'queued') &&\n this.config.networkMonitor.isConnected()\n ) {\n queueMicrotask(() => this.flush());\n }\n }\n }\n\n private persist(): void {\n if (!this.config.storage) return;\n\n const data: PersistedEntry[] = this.entries.map((e) => ({\n optimisticId: e.optimisticId,\n retryCount: e.retryCount,\n }));\n\n const onErr = (err: unknown) => {\n this.config.onError?.(\n err instanceof Error ? err : new Error('Queue storage write failed'),\n );\n };\n\n if (data.length === 0) {\n this.config.storage.removeItem(this.storageKey).catch(onErr);\n } else {\n this.config.storage\n .setItem(this.storageKey, JSON.stringify(data))\n .catch(onErr);\n }\n }\n}\n","import { useEffect, useRef, useState, useCallback } from 'react';\nimport { AppState, Platform, type AppStateStatus } from 'react-native';\nimport type { UnreadCountResponse, SSEUnreadUpdateEvent } from '@pedi/chika-types';\nimport type { ChatConfig } from './types';\nimport { resolveServerUrl } from './resolve-url';\nimport { createSSEConnection, type SSEConnection } from './sse-connection';\nimport { createNetworkMonitor, type NetworkMonitor } from './network-monitor';\n\nconst DEFAULT_BACKGROUND_GRACE_MS = 2000;\nconst UNREAD_CUSTOM_EVENTS = ['unread_snapshot', 'unread_update', 'unread_clear'];\n\nexport interface UseUnreadOptions {\n config: ChatConfig;\n channelId: string;\n participantId: string;\n enabled?: boolean;\n}\n\nexport interface UseUnreadReturn {\n unreadCount: number;\n hasUnread: boolean;\n lastMessageAt: string | null;\n error: Error | null;\n}\n\nexport function useUnread(options: UseUnreadOptions): UseUnreadReturn {\n const { config, channelId, participantId, enabled = true } = options;\n\n const [unreadCount, setUnreadCount] = useState(0);\n const [lastMessageAt, setLastMessageAt] = useState<string | null>(null);\n const [error, setError] = useState<Error | null>(null);\n\n const connRef = useRef<SSEConnection | null>(null);\n const configRef = useRef(config);\n configRef.current = config;\n const appStateRef = useRef<AppStateStatus>(AppState.currentState);\n const backgroundTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n\n const backgroundGraceMs =\n config.backgroundGraceMs ?? (Platform.OS === 'android' ? DEFAULT_BACKGROUND_GRACE_MS : 0);\n\n const [monitorReady, setMonitorReady] = useState(false);\n const monitorRef = useRef<NetworkMonitor | null>(null);\n const resilienceEnabled = config.resilience !== false;\n const injectedMonitor =\n typeof config.resilience === 'object' ? config.resilience.networkMonitor : undefined;\n\n useEffect(() => {\n if (!resilienceEnabled) {\n monitorRef.current = null;\n setMonitorReady(true);\n return;\n }\n if (injectedMonitor) {\n monitorRef.current = injectedMonitor;\n setMonitorReady(true);\n return;\n }\n try {\n const m = createNetworkMonitor();\n monitorRef.current = m;\n setMonitorReady(true);\n return () => {\n m.dispose();\n monitorRef.current = null;\n };\n } catch {\n monitorRef.current = null;\n setMonitorReady(true);\n }\n }, [resilienceEnabled, injectedMonitor]);\n\n const connect = useCallback(() => {\n connRef.current?.close();\n connRef.current = null;\n\n const serviceUrl = resolveServerUrl(configRef.current.manifest, channelId);\n const customHeaders = configRef.current.headers ?? {};\n const url = `${serviceUrl}/channels/${channelId}/unread?participant_id=${encodeURIComponent(participantId)}`;\n\n connRef.current = createSSEConnection(\n {\n url,\n headers: customHeaders,\n reconnectDelayMs: configRef.current.reconnectDelayMs,\n customEvents: UNREAD_CUSTOM_EVENTS,\n networkMonitor: monitorRef.current ?? undefined,\n },\n {\n onOpen: () => {\n setError(null);\n },\n onEvent: (eventType, data) => {\n try {\n if (eventType === 'unread_snapshot') {\n const snapshot: UnreadCountResponse = JSON.parse(data);\n setUnreadCount(snapshot.unread_count);\n setLastMessageAt(snapshot.last_message_at);\n } else if (eventType === 'unread_update') {\n const update: SSEUnreadUpdateEvent['data'] = JSON.parse(data);\n setUnreadCount((prev) => prev + 1);\n setLastMessageAt(update.created_at);\n } else if (eventType === 'unread_clear') {\n const clear: { channel_id: string; unread_count: number } = JSON.parse(data);\n setUnreadCount(clear.unread_count);\n }\n } catch {\n setError(new Error('Failed to parse unread SSE event'));\n }\n },\n onError: (err) => {\n setError(err);\n },\n onClosed: () => {\n connRef.current = null;\n },\n },\n );\n }, [channelId, participantId]);\n\n const disconnect = useCallback(() => {\n connRef.current?.close();\n connRef.current = null;\n }, []);\n\n useEffect(() => {\n setUnreadCount(0);\n setLastMessageAt(null);\n setError(null);\n\n if (!enabled) {\n disconnect();\n return;\n }\n\n // Don't connect until monitor initialization is complete\n if (!monitorReady) return;\n\n connect();\n\n return () => {\n disconnect();\n if (backgroundTimerRef.current) {\n clearTimeout(backgroundTimerRef.current);\n backgroundTimerRef.current = null;\n }\n };\n }, [channelId, participantId, enabled, monitorReady, connect, disconnect]);\n\n useEffect(() => {\n if (!enabled) return;\n\n const subscription = AppState.addEventListener('change', (nextAppState) => {\n const prev = appStateRef.current;\n appStateRef.current = nextAppState;\n\n if (!connRef.current && nextAppState !== 'active') return;\n\n const shouldTeardown =\n nextAppState === 'background' ||\n (Platform.OS === 'ios' && nextAppState === 'inactive');\n\n if (nextAppState === 'active') {\n if (backgroundTimerRef.current) {\n clearTimeout(backgroundTimerRef.current);\n backgroundTimerRef.current = null;\n return;\n }\n\n if (prev.match(/inactive|background/) && !connRef.current) {\n connect();\n }\n } else if (shouldTeardown) {\n if (backgroundTimerRef.current) return;\n\n if (backgroundGraceMs === 0) {\n disconnect();\n } else {\n backgroundTimerRef.current = setTimeout(() => {\n backgroundTimerRef.current = null;\n disconnect();\n }, backgroundGraceMs);\n }\n }\n });\n\n return () => {\n subscription.remove();\n if (backgroundTimerRef.current) {\n clearTimeout(backgroundTimerRef.current);\n backgroundTimerRef.current = null;\n }\n };\n }, [enabled, backgroundGraceMs, connect, disconnect]);\n\n return { unreadCount, hasUnread: unreadCount > 0, lastMessageAt, error };\n}\n"],"mappings":"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","names":["EventSource","DEFAULT_RECONNECT_DELAY_MS","import_react","import_react_native","DEFAULT_BACKGROUND_GRACE_MS"]}
|
package/dist/index.mjs
CHANGED
|
@@ -399,8 +399,7 @@ async function createChatSession(config, channelId, profile, callbacks, networkM
|
|
|
399
399
|
body: JSON.stringify({
|
|
400
400
|
participant_id: profile.id,
|
|
401
401
|
message_id: messageId
|
|
402
|
-
})
|
|
403
|
-
signal: sessionAbort.signal
|
|
402
|
+
})
|
|
404
403
|
});
|
|
405
404
|
if (!res.ok) {
|
|
406
405
|
await throwHttpError(res);
|
|
@@ -408,7 +407,7 @@ async function createChatSession(config, channelId, profile, callbacks, networkM
|
|
|
408
407
|
};
|
|
409
408
|
if (retryConfig) {
|
|
410
409
|
try {
|
|
411
|
-
await withRetry(readFn, MARK_READ_RETRY_CONFIG
|
|
410
|
+
await withRetry(readFn, MARK_READ_RETRY_CONFIG);
|
|
412
411
|
} catch (err) {
|
|
413
412
|
if (err instanceof HttpError) {
|
|
414
413
|
callbacks.onError(err);
|
|
@@ -724,6 +723,7 @@ var MessageQueue = class {
|
|
|
724
723
|
entry.status = "failed";
|
|
725
724
|
entry.error = err instanceof Error ? err : new Error(String(err));
|
|
726
725
|
entry.retryCount++;
|
|
726
|
+
entry.reject(entry.error);
|
|
727
727
|
this.config.onStatusChange?.();
|
|
728
728
|
this.persist();
|
|
729
729
|
}
|
|
@@ -781,6 +781,7 @@ function useChat({ config, channelId, profile, onMessage }) {
|
|
|
781
781
|
const startingRef = useRef(false);
|
|
782
782
|
const pendingOptimisticIds = useRef(/* @__PURE__ */ new Set());
|
|
783
783
|
const [monitor, setMonitor] = useState(null);
|
|
784
|
+
const [monitorReady, setMonitorReady] = useState(false);
|
|
784
785
|
const monitorRef = useRef(null);
|
|
785
786
|
monitorRef.current = monitor;
|
|
786
787
|
const queueRef = useRef(null);
|
|
@@ -793,18 +794,26 @@ function useChat({ config, channelId, profile, onMessage }) {
|
|
|
793
794
|
useEffect(() => {
|
|
794
795
|
if (!resilienceEnabled) {
|
|
795
796
|
setMonitor(null);
|
|
797
|
+
setMonitorReady(true);
|
|
796
798
|
return;
|
|
797
799
|
}
|
|
798
800
|
if (injectedMonitor) {
|
|
799
801
|
setMonitor(injectedMonitor);
|
|
802
|
+
setMonitorReady(true);
|
|
800
803
|
return;
|
|
801
804
|
}
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
805
|
+
try {
|
|
806
|
+
const m = createNetworkMonitor();
|
|
807
|
+
setMonitor(m);
|
|
808
|
+
setMonitorReady(true);
|
|
809
|
+
return () => {
|
|
810
|
+
m.dispose();
|
|
811
|
+
setMonitor(null);
|
|
812
|
+
};
|
|
813
|
+
} catch {
|
|
806
814
|
setMonitor(null);
|
|
807
|
-
|
|
815
|
+
setMonitorReady(true);
|
|
816
|
+
}
|
|
808
817
|
}, [resilienceEnabled, injectedMonitor]);
|
|
809
818
|
const callbacks = {
|
|
810
819
|
onMessage: (message) => {
|
|
@@ -897,7 +906,7 @@ function useChat({ config, channelId, profile, onMessage }) {
|
|
|
897
906
|
}
|
|
898
907
|
useEffect(() => {
|
|
899
908
|
disposedRef.current = false;
|
|
900
|
-
if (
|
|
909
|
+
if (!monitorReady) return;
|
|
901
910
|
startSession();
|
|
902
911
|
return () => {
|
|
903
912
|
disposedRef.current = true;
|
|
@@ -915,7 +924,7 @@ function useChat({ config, channelId, profile, onMessage }) {
|
|
|
915
924
|
sessionRef.current?.disconnect();
|
|
916
925
|
sessionRef.current = null;
|
|
917
926
|
};
|
|
918
|
-
}, [channelId,
|
|
927
|
+
}, [channelId, monitorReady]);
|
|
919
928
|
useEffect(() => {
|
|
920
929
|
if (!queueEnabled || !monitor || !retryConfig) return;
|
|
921
930
|
let entry = queueRegistry.get(channelId);
|
|
@@ -1124,24 +1133,33 @@ function useUnread(options) {
|
|
|
1124
1133
|
const appStateRef = useRef2(AppState2.currentState);
|
|
1125
1134
|
const backgroundTimerRef = useRef2(null);
|
|
1126
1135
|
const backgroundGraceMs = config.backgroundGraceMs ?? (Platform2.OS === "android" ? DEFAULT_BACKGROUND_GRACE_MS2 : 0);
|
|
1127
|
-
const [
|
|
1136
|
+
const [monitorReady, setMonitorReady] = useState2(false);
|
|
1137
|
+
const monitorRef = useRef2(null);
|
|
1128
1138
|
const resilienceEnabled = config.resilience !== false;
|
|
1129
1139
|
const injectedMonitor = typeof config.resilience === "object" ? config.resilience.networkMonitor : void 0;
|
|
1130
1140
|
useEffect2(() => {
|
|
1131
1141
|
if (!resilienceEnabled) {
|
|
1132
|
-
|
|
1142
|
+
monitorRef.current = null;
|
|
1143
|
+
setMonitorReady(true);
|
|
1133
1144
|
return;
|
|
1134
1145
|
}
|
|
1135
1146
|
if (injectedMonitor) {
|
|
1136
|
-
|
|
1147
|
+
monitorRef.current = injectedMonitor;
|
|
1148
|
+
setMonitorReady(true);
|
|
1137
1149
|
return;
|
|
1138
1150
|
}
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1151
|
+
try {
|
|
1152
|
+
const m = createNetworkMonitor();
|
|
1153
|
+
monitorRef.current = m;
|
|
1154
|
+
setMonitorReady(true);
|
|
1155
|
+
return () => {
|
|
1156
|
+
m.dispose();
|
|
1157
|
+
monitorRef.current = null;
|
|
1158
|
+
};
|
|
1159
|
+
} catch {
|
|
1160
|
+
monitorRef.current = null;
|
|
1161
|
+
setMonitorReady(true);
|
|
1162
|
+
}
|
|
1145
1163
|
}, [resilienceEnabled, injectedMonitor]);
|
|
1146
1164
|
const connect = useCallback2(() => {
|
|
1147
1165
|
connRef.current?.close();
|
|
@@ -1155,7 +1173,7 @@ function useUnread(options) {
|
|
|
1155
1173
|
headers: customHeaders,
|
|
1156
1174
|
reconnectDelayMs: configRef.current.reconnectDelayMs,
|
|
1157
1175
|
customEvents: UNREAD_CUSTOM_EVENTS,
|
|
1158
|
-
networkMonitor:
|
|
1176
|
+
networkMonitor: monitorRef.current ?? void 0
|
|
1159
1177
|
},
|
|
1160
1178
|
{
|
|
1161
1179
|
onOpen: () => {
|
|
@@ -1187,7 +1205,7 @@ function useUnread(options) {
|
|
|
1187
1205
|
}
|
|
1188
1206
|
}
|
|
1189
1207
|
);
|
|
1190
|
-
}, [channelId, participantId
|
|
1208
|
+
}, [channelId, participantId]);
|
|
1191
1209
|
const disconnect = useCallback2(() => {
|
|
1192
1210
|
connRef.current?.close();
|
|
1193
1211
|
connRef.current = null;
|
|
@@ -1200,7 +1218,7 @@ function useUnread(options) {
|
|
|
1200
1218
|
disconnect();
|
|
1201
1219
|
return;
|
|
1202
1220
|
}
|
|
1203
|
-
if (
|
|
1221
|
+
if (!monitorReady) return;
|
|
1204
1222
|
connect();
|
|
1205
1223
|
return () => {
|
|
1206
1224
|
disconnect();
|
|
@@ -1209,7 +1227,7 @@ function useUnread(options) {
|
|
|
1209
1227
|
backgroundTimerRef.current = null;
|
|
1210
1228
|
}
|
|
1211
1229
|
};
|
|
1212
|
-
}, [channelId, participantId, enabled, connect, disconnect]);
|
|
1230
|
+
}, [channelId, participantId, enabled, monitorReady, connect, disconnect]);
|
|
1213
1231
|
useEffect2(() => {
|
|
1214
1232
|
if (!enabled) return;
|
|
1215
1233
|
const subscription = AppState2.addEventListener("change", (nextAppState) => {
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/use-chat.ts","../src/errors.ts","../src/retry.ts","../src/resolve-url.ts","../src/sse-connection.ts","../src/session.ts","../src/network-monitor.ts","../src/message-queue.ts","../src/use-unread.ts"],"sourcesContent":["import { useEffect, useRef, useState, useCallback } from 'react';\nimport { AppState, Platform, type AppStateStatus } from 'react-native';\nimport type {\n ChatDomain,\n DefaultDomain,\n Message,\n Participant,\n MessageAttributes,\n SendMessageResponse,\n} from '@pedi/chika-types';\nimport type { UseChatOptions, UseChatReturn, ChatStatus } from './types';\nimport { ChatDisconnectedError, ChannelClosedError, QueueFullError, RetryExhaustedError } from './errors';\nimport { isRetryableError, resolveRetryConfig } from './retry';\nimport { createChatSession, type ChatSession, type SessionCallbacks } from './session';\nimport { createNetworkMonitor, type NetworkMonitor } from './network-monitor';\nimport { MessageQueue, type QueuedMessage } from './message-queue';\n\nconst DEFAULT_BACKGROUND_GRACE_MS = 2000;\nconst DEFAULT_MAX_QUEUE_SIZE = 50;\n\n// Module-scope queue registry, keyed by channelId. Survives component remounts.\nconst queueRegistry = new Map<string, { queue: MessageQueue; refCount: number }>();\n\n/**\n * React hook for real-time chat over SSE.\n * Manages connection lifecycle, AppState transitions, message deduplication, reconnection,\n * and optional network resilience (retry, offline queue, network monitoring).\n *\n * @template D - Chat domain type for role/message type narrowing. Defaults to DefaultDomain.\n */\nexport function useChat<D extends ChatDomain = DefaultDomain>(\n { config, channelId, profile, onMessage }: UseChatOptions<D>,\n): UseChatReturn<D> {\n const [messages, setMessages] = useState<Message<D>[]>([]);\n const [participants, setParticipants] = useState<Participant<D>[]>([]);\n const [status, setStatus] = useState<ChatStatus>('connecting');\n const [error, setError] = useState<Error | null>(null);\n const [pendingMessages, setPendingMessages] = useState<QueuedMessage[]>([]);\n\n const sessionRef = useRef<ChatSession<D> | null>(null);\n const disposedRef = useRef(false);\n const messagesRef = useRef(messages);\n messagesRef.current = messages;\n const statusRef = useRef(status);\n statusRef.current = status;\n const appStateRef = useRef<AppStateStatus>(AppState.currentState);\n const backgroundTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n const profileRef = useRef(profile);\n profileRef.current = profile;\n const configRef = useRef(config);\n configRef.current = config;\n const onMessageRef = useRef(onMessage);\n onMessageRef.current = onMessage;\n const startingRef = useRef(false);\n const pendingOptimisticIds = useRef(new Set<string>());\n const [monitor, setMonitor] = useState<NetworkMonitor | null>(null);\n const monitorRef = useRef<NetworkMonitor | null>(null);\n monitorRef.current = monitor;\n const queueRef = useRef<MessageQueue | null>(null);\n\n const resilienceEnabled = config.resilience !== false;\n const queueEnabled =\n resilienceEnabled &&\n (typeof config.resilience === 'object' ? config.resilience.offlineQueue !== false : true);\n const retryConfig = resolveRetryConfig(config.resilience);\n const maxQueueSize =\n (resilienceEnabled && config.resilience && typeof config.resilience === 'object'\n ? config.resilience.maxQueueSize\n : undefined) ?? DEFAULT_MAX_QUEUE_SIZE;\n\n const backgroundGraceMs =\n config.backgroundGraceMs ?? (Platform.OS === 'android' ? DEFAULT_BACKGROUND_GRACE_MS : 0);\n\n // Resolve user-injected monitor (stable reference, no side effect)\n const injectedMonitor =\n typeof config.resilience === 'object' ? config.resilience.networkMonitor : undefined;\n\n // Create monitor in useEffect to avoid side effects during render.\n // Uses state (not ref) so the queue effect re-runs when monitor is ready.\n useEffect(() => {\n if (!resilienceEnabled) {\n setMonitor(null);\n return;\n }\n if (injectedMonitor) {\n setMonitor(injectedMonitor);\n return; // user owns lifecycle\n }\n const m = createNetworkMonitor();\n setMonitor(m);\n return () => {\n m.dispose();\n setMonitor(null);\n };\n }, [resilienceEnabled, injectedMonitor]);\n\n const callbacks: SessionCallbacks<D> = {\n onMessage: (message) => {\n if (disposedRef.current) return;\n let matchedOptimisticId: string | null = null;\n setMessages((prev: Message<D>[]) => {\n if (pendingOptimisticIds.current.size === 0) {\n return [...prev, message];\n }\n\n const optimisticIdx = prev.findIndex(\n (m) =>\n pendingOptimisticIds.current.has(m.id) &&\n m.sender_id === message.sender_id &&\n m.body === message.body &&\n m.type === message.type,\n );\n if (optimisticIdx !== -1) {\n matchedOptimisticId = prev[optimisticIdx]!.id;\n pendingOptimisticIds.current.delete(matchedOptimisticId);\n const next = [...prev];\n next[optimisticIdx] = message;\n return next;\n }\n return [...prev, message];\n });\n // Clean up any queued/failed queue entry — SSE confirmed delivery.\n // Done outside setMessages updater to avoid setState-in-setState.\n if (matchedOptimisticId) {\n const s = queueRef.current?.getStatus(matchedOptimisticId);\n if (s && s.status !== 'sending') {\n queueRef.current?.cancel(matchedOptimisticId);\n }\n }\n onMessageRef.current?.(message);\n },\n onStatusChange: (nextStatus) => {\n if (disposedRef.current) return;\n setStatus(nextStatus);\n if (nextStatus === 'connected') {\n setError(null);\n }\n },\n onError: (err) => {\n if (disposedRef.current) return;\n setError(err);\n },\n onResync: () => {\n if (disposedRef.current) return;\n startSession();\n },\n };\n\n async function startSession(): Promise<void> {\n if (startingRef.current) return;\n startingRef.current = true;\n\n const existing = sessionRef.current;\n if (existing) {\n existing.disconnect();\n sessionRef.current = null;\n }\n\n try {\n const session = await createChatSession<D>(\n configRef.current,\n channelId,\n profileRef.current,\n callbacks,\n monitorRef.current ?? undefined,\n );\n\n if (disposedRef.current) {\n session.disconnect();\n return;\n }\n\n sessionRef.current = session;\n setParticipants(session.initialParticipants);\n\n // Flush any messages queued while session was unavailable\n queueRef.current?.flush();\n\n // Re-merge any pending optimistic messages after resync\n if (pendingOptimisticIds.current.size > 0) {\n const pendingIds = pendingOptimisticIds.current;\n setMessages((prev) => {\n const pendingMsgs = prev.filter((m) => pendingIds.has(m.id));\n return [...session.initialMessages, ...pendingMsgs];\n });\n } else {\n setMessages(session.initialMessages);\n }\n } catch (err) {\n if (disposedRef.current) return;\n\n if (err instanceof ChannelClosedError) {\n setStatus('closed');\n setError(err);\n return;\n }\n\n setStatus('error');\n setError(err instanceof Error ? err : new Error(String(err)));\n } finally {\n startingRef.current = false;\n }\n }\n\n // Session lifecycle\n useEffect(() => {\n disposedRef.current = false;\n // Don't start until monitor is resolved (or resilience is off)\n if (resilienceEnabled && !monitor) return;\n startSession();\n\n return () => {\n disposedRef.current = true;\n if (statusRef.current === 'connected' && sessionRef.current) {\n const lastMsg = messagesRef.current[messagesRef.current.length - 1];\n if (lastMsg) {\n sessionRef.current.markAsRead(lastMsg.id).catch(() => {});\n }\n }\n if (backgroundTimerRef.current) {\n clearTimeout(backgroundTimerRef.current);\n backgroundTimerRef.current = null;\n }\n sessionRef.current?.disconnect();\n sessionRef.current = null;\n };\n }, [channelId, monitor]);\n\n // Module-scope queue lifecycle (ref-counted, survives remounts)\n useEffect(() => {\n if (!queueEnabled || !monitor || !retryConfig) return;\n\n let entry = queueRegistry.get(channelId);\n if (!entry) {\n const queue = new MessageQueue({\n channelId,\n maxSize: maxQueueSize,\n retryConfig,\n networkMonitor: monitor,\n storage:\n typeof config.resilience === 'object'\n ? config.resilience.queueStorage\n : undefined,\n onError: (err) => {\n if (!disposedRef.current) setError(err);\n },\n onStatusChange: () => {\n if (!disposedRef.current) {\n setPendingMessages(queueRef.current?.getAll() ?? []);\n }\n },\n });\n entry = { queue, refCount: 0 };\n queueRegistry.set(channelId, entry);\n }\n entry.refCount++;\n queueRef.current = entry.queue;\n\n return () => {\n const e = queueRegistry.get(channelId);\n if (e) {\n e.refCount--;\n if (e.refCount <= 0) {\n e.queue.dispose();\n queueRegistry.delete(channelId);\n }\n }\n queueRef.current = null;\n };\n }, [channelId, queueEnabled, monitor]);\n\n // Network monitor: auto-rejoin on connectivity return when in error state\n useEffect(() => {\n if (!monitor || !resilienceEnabled) return;\n\n const unsub = monitor.subscribe((connected: boolean) => {\n if (connected && statusRef.current === 'error' && !startingRef.current) {\n startSession();\n }\n });\n\n return unsub;\n }, [channelId, resilienceEnabled, monitor]);\n\n // AppState lifecycle\n useEffect(() => {\n function teardownSession(): void {\n sessionRef.current?.disconnect();\n sessionRef.current = null;\n setStatus('disconnected');\n }\n\n const subscription = AppState.addEventListener('change', (nextAppState) => {\n const prev = appStateRef.current;\n appStateRef.current = nextAppState;\n\n if (!sessionRef.current && nextAppState !== 'active') return;\n\n const shouldTeardown =\n nextAppState === 'background' ||\n (Platform.OS === 'ios' && nextAppState === 'inactive');\n\n if (nextAppState === 'active') {\n if (backgroundTimerRef.current) {\n clearTimeout(backgroundTimerRef.current);\n backgroundTimerRef.current = null;\n return;\n }\n\n if (prev.match(/inactive|background/) && !sessionRef.current) {\n startSession();\n }\n } else if (shouldTeardown) {\n if (backgroundTimerRef.current) return;\n\n if (backgroundGraceMs === 0) {\n teardownSession();\n } else {\n backgroundTimerRef.current = setTimeout(() => {\n backgroundTimerRef.current = null;\n teardownSession();\n }, backgroundGraceMs);\n }\n }\n });\n\n return () => {\n subscription.remove();\n if (backgroundTimerRef.current) {\n clearTimeout(backgroundTimerRef.current);\n backgroundTimerRef.current = null;\n }\n };\n }, [channelId, backgroundGraceMs]);\n\n const sendMessage = useCallback(\n async (type: D['messageType'], body: string, attributes?: MessageAttributes<D>): Promise<SendMessageResponse> => {\n const session = sessionRef.current;\n if (!session) throw new ChatDisconnectedError(statusRef.current);\n\n const optimistic = configRef.current.optimisticSend !== false;\n // Serves as both the optimistic message ID and the server-side idempotency key\n const messageKey = `optimistic_${Date.now()}_${Math.random().toString(36).slice(2, 7)}`;\n\n if (optimistic) {\n pendingOptimisticIds.current.add(messageKey);\n const provisionalMsg: Message<D> = {\n id: messageKey,\n channel_id: channelId,\n sender_id: profileRef.current.id,\n sender_role: profileRef.current.role,\n type,\n body,\n attributes: (attributes ?? {}) as MessageAttributes<D>,\n created_at: new Date().toISOString(),\n };\n setMessages((prev) => [...prev, provisionalMsg]);\n }\n\n const doSend = () => {\n const s = sessionRef.current;\n if (!s) throw new ChatDisconnectedError(statusRef.current);\n return s.sendMessage(type, body, attributes, messageKey);\n };\n\n const handleSuccess = (response: SendMessageResponse): void => {\n if (optimistic) {\n pendingOptimisticIds.current.delete(messageKey);\n setMessages((prev) => {\n const stillPending = prev.some((m) => m.id === messageKey);\n if (!stillPending) return prev;\n return prev.map((m) =>\n m.id === messageKey\n ? { ...m, id: response.id, created_at: response.created_at }\n : m,\n );\n });\n }\n };\n\n const handleError = (_err: unknown): void => {\n if (optimistic) {\n pendingOptimisticIds.current.delete(messageKey);\n setMessages((prev) => prev.filter((m) => m.id !== messageKey));\n }\n };\n\n // Queue path: enqueue and let queue handle retry + offline\n if (queueRef.current) {\n try {\n const response = await queueRef.current.enqueue(doSend, messageKey);\n handleSuccess(response);\n return response;\n } catch (err) {\n if (err instanceof QueueFullError) {\n handleError(err);\n throw err;\n }\n if (err instanceof RetryExhaustedError || !isRetryableError(err)) {\n // For failed messages: if optimistic, mark as failed (keep in UI)\n // The pendingMessages state shows the status\n if (optimistic && err instanceof RetryExhaustedError) {\n // Keep optimistic message visible — queue tracks status as 'failed'.\n // Keep messageKey in pendingOptimisticIds so SSE reconciliation can\n // still match if the server eventually received the message.\n // Removed on explicit cancelMessage() call.\n } else {\n handleError(err);\n }\n throw err;\n }\n handleError(err);\n throw err;\n }\n }\n\n // Non-queue path: direct send (session.ts handles retry if enabled)\n try {\n const response = await doSend();\n handleSuccess(response);\n return response;\n } catch (err) {\n handleError(err);\n throw err;\n }\n },\n [channelId],\n );\n\n const cancelMessage = useCallback(\n (optimisticId: string) => {\n queueRef.current?.cancel(optimisticId);\n pendingOptimisticIds.current.delete(optimisticId);\n setMessages((prev) => prev.filter((m) => m.id !== optimisticId));\n },\n [],\n );\n\n const retryMessage = useCallback(\n (optimisticId: string) => {\n queueRef.current?.retry(optimisticId);\n },\n [],\n );\n\n const disconnect = useCallback(() => {\n sessionRef.current?.disconnect();\n sessionRef.current = null;\n setStatus('disconnected');\n }, []);\n\n return {\n messages,\n participants,\n status,\n error,\n sendMessage,\n disconnect,\n pendingMessages,\n cancelMessage,\n retryMessage,\n };\n}\n","import type { ChatStatus } from './types';\n\nexport class ChatDisconnectedError extends Error {\n constructor(public readonly status: ChatStatus) {\n super(`Cannot send message while ${status}`);\n this.name = 'ChatDisconnectedError';\n }\n}\n\nexport class ChannelClosedError extends Error {\n constructor(public readonly channelId: string) {\n super(`Channel ${channelId} is closed`);\n this.name = 'ChannelClosedError';\n }\n}\n\nexport class HttpError extends Error {\n constructor(\n public readonly status: number,\n public readonly body: string,\n public readonly retryAfter?: number,\n ) {\n super(`HTTP ${status}: ${body}`);\n this.name = 'HttpError';\n }\n}\n\nexport class RetryExhaustedError extends Error {\n constructor(\n public readonly operation: string,\n public readonly attempts: number,\n public readonly lastError: Error,\n ) {\n super(`${operation} failed after ${attempts} attempts: ${lastError.message}`);\n this.name = 'RetryExhaustedError';\n }\n}\n\nexport class QueueFullError extends Error {\n constructor(public readonly maxSize: number) {\n super(`Message queue full (max ${maxSize})`);\n this.name = 'QueueFullError';\n }\n}\n","import {\n HttpError,\n RetryExhaustedError,\n ChannelClosedError,\n ChatDisconnectedError,\n QueueFullError,\n} from './errors';\n\nexport interface RetryConfig {\n maxAttempts: number;\n baseDelayMs: number;\n maxDelayMs: number;\n jitterFactor: number;\n}\n\nexport const DEFAULT_RETRY_CONFIG: RetryConfig = {\n maxAttempts: 3,\n baseDelayMs: 1000,\n maxDelayMs: 10000,\n jitterFactor: 0.3,\n};\n\nexport function calculateBackoff(attempt: number, config: RetryConfig): number {\n const delay = Math.min(config.baseDelayMs * 2 ** attempt, config.maxDelayMs);\n const jitter = 1 + (Math.random() * 2 - 1) * config.jitterFactor;\n return Math.round(delay * jitter);\n}\n\nexport function isRetryableError(error: unknown): boolean {\n if (error instanceof ChannelClosedError) return false;\n if (error instanceof ChatDisconnectedError) return false;\n if (error instanceof QueueFullError) return false;\n if (error instanceof DOMException && error.name === 'AbortError') return false;\n\n if (error instanceof HttpError) {\n const { status } = error;\n if (status === 408 || status === 429) return true;\n if (status >= 500) return true;\n return false;\n }\n\n // TypeError from fetch = network failure\n if (error instanceof TypeError) return true;\n\n return false;\n}\n\nexport function sleep(ms: number, signal?: AbortSignal): Promise<void> {\n return new Promise((resolve, reject) => {\n if (signal?.aborted) {\n reject(signal.reason ?? new DOMException('Aborted', 'AbortError'));\n return;\n }\n\n const timer = setTimeout(resolve, ms);\n\n signal?.addEventListener(\n 'abort',\n () => {\n clearTimeout(timer);\n reject(signal.reason ?? new DOMException('Aborted', 'AbortError'));\n },\n { once: true },\n );\n });\n}\n\nexport function resolveRetryConfig(\n resilience: { retry?: Partial<RetryConfig> | false } | false | undefined,\n): RetryConfig | null {\n if (resilience === false) return null;\n if (!resilience || resilience.retry === undefined) return DEFAULT_RETRY_CONFIG;\n if (resilience.retry === false) return null;\n return { ...DEFAULT_RETRY_CONFIG, ...resilience.retry };\n}\n\nexport async function withRetry<T>(\n fn: () => Promise<T>,\n config: RetryConfig = DEFAULT_RETRY_CONFIG,\n signal?: AbortSignal,\n): Promise<T> {\n let lastError: Error | undefined;\n\n for (let attempt = 0; attempt < config.maxAttempts; attempt++) {\n if (signal?.aborted) {\n throw signal.reason ?? new DOMException('Aborted', 'AbortError');\n }\n\n try {\n return await fn();\n } catch (err) {\n lastError = err instanceof Error ? err : new Error(String(err));\n\n if (!isRetryableError(err)) throw lastError;\n\n if (attempt < config.maxAttempts - 1) {\n // Respect Retry-After header for 429 responses\n const delayMs =\n err instanceof HttpError && err.retryAfter != null\n ? err.retryAfter * 1000\n : calculateBackoff(attempt, config);\n\n await sleep(delayMs, signal);\n }\n }\n }\n\n throw new RetryExhaustedError(\n 'operation',\n config.maxAttempts,\n lastError!,\n );\n}\n","import type { ChatManifest } from '@pedi/chika-types';\n\n/**\n * Creates a single-server manifest. Use this when all channels route to the same server.\n *\n * @example\n * ```ts\n * const config: ChatConfig = { manifest: createManifest('https://chat.example.com') };\n * ```\n */\nexport function createManifest(serverUrl: string): ChatManifest {\n return { buckets: [{ group: 'default', range: [0, 99], server_url: serverUrl }] };\n}\n\nexport function resolveServerUrl(manifest: ChatManifest, channelId: string): string {\n const hash = [...channelId].reduce((sum, c) => sum + c.charCodeAt(0), 0) % 100;\n const bucket = manifest.buckets.find(\n (b) => hash >= b.range[0] && hash <= b.range[1],\n );\n if (!bucket) throw new Error(`No chat bucket for hash ${hash}`);\n return bucket.server_url;\n}\n","import EventSource from 'react-native-sse';\nimport { calculateBackoff, type RetryConfig } from './retry';\nimport type { NetworkMonitor } from './network-monitor';\n\nconst DEFAULT_RECONNECT_DELAY_MS = 3000;\n\nexport interface SSEConnectionConfig {\n url: string;\n headers?: Record<string, string>;\n reconnectDelayMs?: number;\n lastEventId?: string;\n customEvents?: string[];\n networkMonitor?: NetworkMonitor;\n}\n\nexport interface SSEConnectionCallbacks {\n onOpen?: () => void;\n onEvent: (eventType: string, data: string, lastEventId?: string) => void;\n onError?: (error: Error) => void;\n onClosed?: () => void;\n onReconnecting?: () => void;\n}\n\nexport interface SSEConnection {\n close: () => void;\n reconnectImmediate: () => void;\n}\n\nexport function createSSEConnection(\n config: SSEConnectionConfig,\n callbacks: SSEConnectionCallbacks,\n): SSEConnection {\n const baseDelay = config.reconnectDelayMs ?? DEFAULT_RECONNECT_DELAY_MS;\n const customEvents = config.customEvents ?? [];\n const monitor = config.networkMonitor;\n\n const backoffConfig: RetryConfig = {\n maxAttempts: Infinity,\n baseDelayMs: baseDelay,\n maxDelayMs: 30000,\n jitterFactor: 0.3,\n };\n\n let currentLastEventId = config.lastEventId;\n let es: EventSource<string> | null = null;\n let disposed = false;\n let reconnectTimer: ReturnType<typeof setTimeout> | null = null;\n let attempt = 0;\n let waitAbort: AbortController | null = null;\n\n function cleanup(): void {\n if (es) {\n es.removeAllEventListeners();\n es.close();\n es = null;\n }\n if (reconnectTimer) {\n clearTimeout(reconnectTimer);\n reconnectTimer = null;\n }\n if (waitAbort) {\n waitAbort.abort();\n waitAbort = null;\n }\n }\n\n async function scheduleReconnect(): Promise<void> {\n if (disposed || reconnectTimer || waitAbort) return;\n callbacks.onReconnecting?.();\n cleanup();\n\n // Wait for network if monitor available\n if (monitor && !monitor.isConnected()) {\n waitAbort = new AbortController();\n try {\n await monitor.waitForOnline(waitAbort.signal);\n } catch {\n return; // aborted via dispose\n }\n waitAbort = null;\n if (disposed) return;\n }\n\n const delay = calculateBackoff(attempt++, backoffConfig);\n reconnectTimer = setTimeout(() => {\n reconnectTimer = null;\n connect();\n }, delay);\n }\n\n function connect(): void {\n if (disposed) return;\n\n es = new EventSource<string>(config.url, {\n headers: {\n ...config.headers,\n ...(currentLastEventId && { 'Last-Event-ID': currentLastEventId }),\n },\n pollingInterval: 0,\n });\n\n es.addEventListener('open', () => {\n if (disposed) return;\n attempt = 0;\n callbacks.onOpen?.();\n });\n\n es.addEventListener('message', (event) => {\n if (disposed || !event.data) return;\n if (event.lastEventId) {\n currentLastEventId = event.lastEventId;\n }\n callbacks.onEvent('message', event.data, event.lastEventId ?? undefined);\n });\n\n for (const eventName of customEvents) {\n es.addEventListener(eventName, (event) => {\n if (disposed) return;\n callbacks.onEvent(eventName, event.data ?? '', undefined);\n });\n }\n\n es.addEventListener('error', (event) => {\n if (disposed) return;\n\n const msg = 'message' in event ? String(event.message) : '';\n\n if (msg.includes('Channel is closed') || msg.includes('410')) {\n callbacks.onClosed?.();\n cleanup();\n disposed = true;\n return;\n }\n\n if (msg) callbacks.onError?.(new Error(msg));\n\n scheduleReconnect();\n });\n\n es.addEventListener('close', () => {\n if (disposed) return;\n scheduleReconnect();\n });\n }\n\n connect();\n\n return {\n close: () => {\n disposed = true;\n cleanup();\n },\n\n reconnectImmediate: () => {\n if (disposed) return;\n cleanup(); // does NOT set disposed\n attempt = 0;\n connect();\n },\n };\n}\n","import type {\n ChatDomain,\n DefaultDomain,\n Participant,\n Message,\n JoinResponse,\n SendMessageRequest,\n SendMessageResponse,\n MessageAttributes,\n} from '@pedi/chika-types';\nimport type { ChatConfig, ChatStatus } from './types';\nimport { ChatDisconnectedError, ChannelClosedError, HttpError } from './errors';\nimport { resolveServerUrl } from './resolve-url';\nimport { createSSEConnection, type SSEConnection } from './sse-connection';\nimport { withRetry, resolveRetryConfig, type RetryConfig } from './retry';\nimport type { NetworkMonitor } from './network-monitor';\n\nconst DEFAULT_RECONNECT_DELAY_MS = 3000;\nconst MAX_SEEN_IDS = 500;\n\nconst MARK_READ_RETRY_CONFIG: RetryConfig = {\n maxAttempts: 2,\n baseDelayMs: 500,\n maxDelayMs: 2000,\n jitterFactor: 0.3,\n};\n\nexport interface SessionCallbacks<D extends ChatDomain = DefaultDomain> {\n onMessage: (message: Message<D>) => void;\n onStatusChange: (status: ChatStatus) => void;\n onError: (error: Error) => void;\n onResync: () => void;\n}\n\nexport interface ChatSession<D extends ChatDomain = DefaultDomain> {\n serviceUrl: string;\n channelId: string;\n initialParticipants: Participant<D>[];\n initialMessages: Message<D>[];\n networkMonitor: NetworkMonitor | null;\n sendMessage: (\n type: D['messageType'],\n body: string,\n attributes?: MessageAttributes<D>,\n idempotencyKey?: string,\n ) => Promise<SendMessageResponse>;\n markAsRead: (messageId: string) => Promise<void>;\n disconnect: () => void;\n}\n\nfunction parseRetryAfter(res: Response): number | undefined {\n const header = res.headers.get('Retry-After');\n if (!header) return undefined;\n const seconds = Number(header);\n return Number.isFinite(seconds) ? seconds : undefined;\n}\n\nasync function throwHttpError(res: Response): Promise<never> {\n const body = await res.text().catch(() => '');\n throw new HttpError(res.status, body, parseRetryAfter(res));\n}\n\nexport async function createChatSession<D extends ChatDomain = DefaultDomain>(\n config: ChatConfig,\n channelId: string,\n profile: Participant<D>,\n callbacks: SessionCallbacks<D>,\n networkMonitor?: NetworkMonitor,\n): Promise<ChatSession<D>> {\n const serviceUrl = resolveServerUrl(config.manifest, channelId);\n const customHeaders = config.headers ?? {};\n const reconnectDelay = config.reconnectDelayMs ?? DEFAULT_RECONNECT_DELAY_MS;\n const retryConfig = resolveRetryConfig(config.resilience);\n\n const sessionAbort = new AbortController();\n\n callbacks.onStatusChange('connecting');\n\n const joinFn = async (): Promise<JoinResponse<D>> => {\n const joinRes = await fetch(`${serviceUrl}/channels/${channelId}/join`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', ...customHeaders },\n body: JSON.stringify(profile),\n signal: sessionAbort.signal,\n });\n\n if (joinRes.status === 410) {\n throw new ChannelClosedError(channelId);\n }\n\n if (!joinRes.ok) {\n await throwHttpError(joinRes);\n }\n\n return joinRes.json();\n };\n\n const joinData = retryConfig\n ? await withRetry(joinFn, retryConfig, sessionAbort.signal)\n : await joinFn();\n\n const { messages, participants, joined_at }: JoinResponse<D> = joinData;\n\n let lastEventId =\n messages.length > 0 ? messages[messages.length - 1]!.id : undefined;\n\n const joinedAt = joined_at;\n\n const seenMessageIds = new Set<string>(messages.map((m) => m.id));\n\n let sseConn: SSEConnection | null = null;\n let disposed = false;\n\n const TRIM_THRESHOLD = MAX_SEEN_IDS * 1.5;\n\n function trimSeenIds(): void {\n if (seenMessageIds.size <= TRIM_THRESHOLD) return;\n const ids = [...seenMessageIds];\n seenMessageIds.clear();\n for (const id of ids.slice(-MAX_SEEN_IDS)) {\n seenMessageIds.add(id);\n }\n }\n\n function connect(): void {\n if (disposed) return;\n\n const streamUrl = lastEventId\n ? `${serviceUrl}/channels/${channelId}/stream`\n : `${serviceUrl}/channels/${channelId}/stream?since_time=${encodeURIComponent(joinedAt)}`;\n\n sseConn = createSSEConnection(\n {\n url: streamUrl,\n headers: customHeaders,\n reconnectDelayMs: reconnectDelay,\n lastEventId,\n customEvents: ['resync'],\n networkMonitor,\n },\n {\n onOpen: () => {\n if (!disposed) callbacks.onStatusChange('connected');\n },\n onEvent: (eventType, data, eventId) => {\n if (disposed) return;\n\n if (eventType === 'message') {\n let message: Message<D>;\n try {\n message = JSON.parse(data);\n } catch {\n callbacks.onError(new Error('Failed to parse SSE message'));\n return;\n }\n\n if (eventId) {\n lastEventId = eventId;\n }\n\n if (seenMessageIds.has(message.id)) return;\n seenMessageIds.add(message.id);\n trimSeenIds();\n\n callbacks.onMessage(message);\n } else if (eventType === 'resync') {\n // Let onResync → startSession handle full session recreation.\n // Don't call reconnectImmediate() — startSession disconnects this\n // session anyway, so the reconnect would be immediately thrown away.\n callbacks.onResync();\n }\n },\n onError: (err) => {\n if (!disposed) callbacks.onError(err);\n },\n onClosed: () => {\n callbacks.onStatusChange('closed');\n disposed = true;\n },\n onReconnecting: () => {\n if (!disposed) callbacks.onStatusChange('reconnecting');\n },\n },\n );\n }\n\n connect();\n\n return {\n serviceUrl,\n channelId,\n initialParticipants: participants,\n initialMessages: messages,\n networkMonitor: networkMonitor ?? null,\n\n sendMessage: async (type, body, attributes, idempotencyKey) => {\n if (disposed) throw new ChatDisconnectedError('disconnected');\n\n const payload: SendMessageRequest<D> = {\n sender_id: profile.id,\n type,\n body,\n attributes,\n ...(idempotencyKey ? { idempotency_key: idempotencyKey } : {}),\n };\n\n const sendFn = async (): Promise<SendMessageResponse> => {\n const res = await fetch(\n `${serviceUrl}/channels/${channelId}/messages`,\n {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', ...customHeaders },\n body: JSON.stringify(payload),\n signal: sessionAbort.signal,\n },\n );\n\n if (!res.ok) {\n await throwHttpError(res);\n }\n\n return res.json();\n };\n\n const response = retryConfig\n ? await withRetry(sendFn, retryConfig, sessionAbort.signal)\n : await sendFn();\n\n seenMessageIds.add(response.id);\n return response;\n },\n\n markAsRead: async (messageId: string) => {\n const readFn = async (): Promise<void> => {\n const res = await fetch(`${serviceUrl}/channels/${channelId}/read`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', ...customHeaders },\n body: JSON.stringify({\n participant_id: profile.id,\n message_id: messageId,\n }),\n signal: sessionAbort.signal,\n });\n if (!res.ok) {\n await throwHttpError(res);\n }\n };\n\n if (retryConfig) {\n try {\n await withRetry(readFn, MARK_READ_RETRY_CONFIG, sessionAbort.signal);\n } catch (err) {\n // Surface non-retryable errors (403, 404) for dev diagnostics\n if (err instanceof HttpError) {\n callbacks.onError(err);\n }\n // Swallow RetryExhaustedError — markAsRead is best-effort\n }\n } else {\n await readFn();\n }\n },\n\n disconnect: () => {\n disposed = true;\n sessionAbort.abort();\n sseConn?.close();\n sseConn = null;\n callbacks.onStatusChange('disconnected');\n },\n };\n}\n","export interface NetworkMonitor {\n isConnected(): boolean;\n subscribe(cb: (connected: boolean) => void): () => void;\n waitForOnline(signal?: AbortSignal): Promise<void>;\n dispose(): void;\n}\n\nfunction createStubMonitor(): NetworkMonitor {\n return {\n isConnected: () => true,\n subscribe: () => () => {},\n waitForOnline: () => Promise.resolve(),\n dispose: () => {},\n };\n}\n\nlet resolvedNetInfo: any = undefined;\nlet netInfoResolved = false;\n\nfunction getNetInfo(): any {\n if (netInfoResolved) return resolvedNetInfo;\n netInfoResolved = true;\n try {\n resolvedNetInfo = require('@react-native-community/netinfo');\n } catch {\n resolvedNetInfo = null;\n }\n return resolvedNetInfo;\n}\n\nexport function createNetworkMonitor(): NetworkMonitor {\n const NetInfo = getNetInfo();\n if (!NetInfo) return createStubMonitor();\n\n const netInfoModule = NetInfo.default ?? NetInfo;\n\n let connected = true;\n const listeners = new Set<(connected: boolean) => void>();\n let unsubscribeNetInfo: (() => void) | null = null;\n\n unsubscribeNetInfo = netInfoModule.addEventListener(\n (state: { isConnected: boolean | null }) => {\n const next = state.isConnected !== false;\n if (next === connected) return;\n connected = next;\n for (const cb of listeners) {\n cb(connected);\n }\n },\n );\n\n return {\n isConnected: () => connected,\n\n subscribe: (cb) => {\n listeners.add(cb);\n return () => {\n listeners.delete(cb);\n };\n },\n\n waitForOnline: (signal?) => {\n if (connected) return Promise.resolve();\n\n return new Promise<void>((resolve, reject) => {\n if (signal?.aborted) {\n reject(signal.reason ?? new DOMException('Aborted', 'AbortError'));\n return;\n }\n\n const unsub = (): void => {\n listeners.delete(handler);\n signal?.removeEventListener('abort', onAbort);\n };\n\n const handler = (isOnline: boolean): void => {\n if (isOnline) {\n unsub();\n resolve();\n }\n };\n\n const onAbort = (): void => {\n unsub();\n reject(signal!.reason ?? new DOMException('Aborted', 'AbortError'));\n };\n\n listeners.add(handler);\n signal?.addEventListener('abort', onAbort, { once: true });\n });\n },\n\n dispose: () => {\n listeners.clear();\n unsubscribeNetInfo?.();\n unsubscribeNetInfo = null;\n },\n };\n}\n","import type { SendMessageResponse } from '@pedi/chika-types';\nimport { withRetry, type RetryConfig } from './retry';\nimport { QueueFullError, ChatDisconnectedError } from './errors';\nimport type { NetworkMonitor } from './network-monitor';\n\nexport interface QueueStorage {\n getItem(key: string): Promise<string | null>;\n setItem(key: string, value: string): Promise<void>;\n removeItem(key: string): Promise<void>;\n}\n\nlet resolvedStorage: { type: string; adapter: QueueStorage } | null | undefined;\n\nfunction tryRequire(name: string): any {\n try {\n return require(name);\n } catch {\n return null;\n }\n}\n\nfunction createMmkvAdapter(mod: any): QueueStorage {\n const MMKV = mod.MMKV ?? mod.default?.MMKV ?? mod;\n const instance = new MMKV({ id: 'chika-queue' });\n return {\n getItem: (key) => Promise.resolve(instance.getString(key) ?? null),\n setItem: (key, value) => { instance.set(key, value); return Promise.resolve(); },\n removeItem: (key) => { instance.delete(key); return Promise.resolve(); },\n };\n}\n\nfunction createAsyncStorageAdapterFrom(mod: any): QueueStorage {\n const storage = mod.default ?? mod;\n return {\n getItem: (key) => storage.getItem(key),\n setItem: (key, value) => storage.setItem(key, value),\n removeItem: (key) => storage.removeItem(key),\n };\n}\n\n/**\n * Auto-detect the best available storage for queue persistence.\n * Priority: react-native-mmkv (fastest, sync) > AsyncStorage > null.\n * Returns `null` if neither is installed — no hard dependencies.\n *\n * Usage:\n * ```ts\n * import { createQueueStorage } from '@pedi/chika-sdk';\n *\n * const config: ChatConfig = {\n * resilience: {\n * queueStorage: createQueueStorage() ?? undefined,\n * },\n * };\n * ```\n */\nexport function createQueueStorage(): QueueStorage | null {\n if (resolvedStorage !== undefined) return resolvedStorage?.adapter ?? null;\n\n // Priority 1: MMKV (synchronous, fastest)\n const mmkv = tryRequire('react-native-mmkv');\n if (mmkv) {\n try {\n const adapter = createMmkvAdapter(mmkv);\n resolvedStorage = { type: 'mmkv', adapter };\n return adapter;\n } catch {\n // MMKV instantiation can fail if native module isn't linked\n }\n }\n\n // Priority 2: AsyncStorage\n const asyncStorage = tryRequire('@react-native-async-storage/async-storage');\n if (asyncStorage) {\n const adapter = createAsyncStorageAdapterFrom(asyncStorage);\n resolvedStorage = { type: 'async-storage', adapter };\n return adapter;\n }\n\n resolvedStorage = null;\n return null;\n}\n\n/**\n * Creates a QueueStorage adapter backed by `@react-native-async-storage/async-storage`.\n * Returns `null` if the package is not installed.\n */\nexport function createAsyncStorageAdapter(): QueueStorage | null {\n const mod = tryRequire('@react-native-async-storage/async-storage');\n if (!mod) return null;\n return createAsyncStorageAdapterFrom(mod);\n}\n\nexport type MessageSendStatus = 'sending' | 'queued' | 'failed';\n\nexport interface QueuedMessage {\n optimisticId: string;\n status: MessageSendStatus;\n error?: Error;\n retryCount: number;\n}\n\ntype SendFn = () => Promise<SendMessageResponse>;\n\ninterface QueueEntry {\n optimisticId: string;\n sendFn: SendFn;\n status: MessageSendStatus;\n error?: Error;\n retryCount: number;\n abort: AbortController;\n resolve: (value: SendMessageResponse) => void;\n reject: (reason: Error) => void;\n}\n\n/** Serializable subset persisted to storage. */\ninterface PersistedEntry {\n optimisticId: string;\n retryCount: number;\n}\n\nexport interface MessageQueueConfig {\n channelId: string;\n maxSize: number;\n retryConfig: RetryConfig;\n networkMonitor: NetworkMonitor;\n storage?: QueueStorage;\n onError?: (error: Error) => void;\n onStatusChange?: () => void;\n}\n\nexport class MessageQueue {\n private entries: QueueEntry[] = [];\n private flushing = false;\n private unsubNetwork: (() => void) | null = null;\n private readonly storageKey: string;\n\n constructor(private readonly config: MessageQueueConfig) {\n this.storageKey = `chika_queue_${config.channelId}`;\n\n this.unsubNetwork = config.networkMonitor.subscribe((connected) => {\n if (connected) this.flush();\n });\n }\n\n /**\n * Restore queued messages from persistent storage on cold start.\n * Restored entries are fire-and-forget — there is no caller awaiting their\n * promise (the original enqueue() caller is gone after app restart).\n * Success/failure is reported via onStatusChange/onError callbacks.\n */\n async restore(\n rebuildSendFn: (optimisticId: string) => SendFn | null,\n ): Promise<void> {\n if (!this.config.storage) return;\n\n try {\n const raw = await this.config.storage.getItem(this.storageKey);\n if (!raw) return;\n\n const persisted: PersistedEntry[] = JSON.parse(raw);\n for (const entry of persisted) {\n const sendFn = rebuildSendFn(entry.optimisticId);\n if (!sendFn) continue;\n\n const abort = new AbortController();\n this.entries.push({\n optimisticId: entry.optimisticId,\n sendFn,\n status: 'queued',\n retryCount: entry.retryCount,\n abort,\n // No-op: restored entries have no caller awaiting the promise.\n resolve: () => {},\n reject: () => {},\n });\n }\n\n if (this.entries.length > 0) {\n this.config.onStatusChange?.();\n this.flush();\n }\n } catch {\n this.config.onError?.(new Error('Failed to restore message queue from storage'));\n }\n }\n\n get pendingCount(): number {\n return this.entries.length;\n }\n\n getAll(): QueuedMessage[] {\n return this.entries.map((e) => ({\n optimisticId: e.optimisticId,\n status: e.status,\n error: e.error,\n retryCount: e.retryCount,\n }));\n }\n\n getStatus(optimisticId: string): QueuedMessage | undefined {\n const entry = this.entries.find((e) => e.optimisticId === optimisticId);\n if (!entry) return undefined;\n return {\n optimisticId: entry.optimisticId,\n status: entry.status,\n error: entry.error,\n retryCount: entry.retryCount,\n };\n }\n\n enqueue(sendFn: SendFn, optimisticId: string): Promise<SendMessageResponse> {\n if (this.entries.length >= this.config.maxSize) {\n throw new QueueFullError(this.config.maxSize);\n }\n\n const abort = new AbortController();\n\n return new Promise<SendMessageResponse>((resolve, reject) => {\n const entry: QueueEntry = {\n optimisticId,\n sendFn,\n status: 'queued',\n retryCount: 0,\n abort,\n resolve,\n reject,\n };\n\n this.entries.push(entry);\n this.config.onStatusChange?.();\n this.persist();\n this.flush();\n });\n }\n\n cancel(optimisticId: string): void {\n const idx = this.entries.findIndex((e) => e.optimisticId === optimisticId);\n if (idx === -1) return;\n\n const entry = this.entries[idx]!;\n entry.abort.abort();\n entry.reject(new DOMException('Cancelled', 'AbortError'));\n this.entries.splice(idx, 1);\n this.config.onStatusChange?.();\n this.persist();\n }\n\n retry(optimisticId: string): void {\n const entry = this.entries.find((e) => e.optimisticId === optimisticId);\n if (!entry || entry.status !== 'failed') return;\n\n entry.status = 'queued';\n entry.error = undefined;\n entry.abort = new AbortController();\n this.config.onStatusChange?.();\n this.flush();\n }\n\n dispose(): void {\n this.unsubNetwork?.();\n this.unsubNetwork = null;\n\n for (const entry of this.entries) {\n entry.abort.abort();\n entry.reject(new DOMException('Queue disposed', 'AbortError'));\n }\n this.entries = [];\n }\n\n /**\n * Trigger a flush of queued messages. Returns immediately if a flush is\n * already in progress or the network is offline. The in-progress flush\n * will pick up any newly queued entries via its while loop.\n */\n async flush(): Promise<void> {\n if (this.flushing) return;\n if (!this.config.networkMonitor.isConnected()) return;\n\n this.flushing = true;\n let awaitingSession = false;\n\n try {\n while (this.entries.length > 0) {\n // Only pick up 'queued' entries — 'sending' means a previous flush is\n // mid-request. Server-side idempotency key prevents duplicates if the\n // prior request actually succeeded but we never got the response.\n const entry = this.entries.find((e) => e.status === 'queued');\n if (!entry) break;\n if (!this.config.networkMonitor.isConnected()) break;\n\n entry.status = 'sending';\n this.config.onStatusChange?.();\n\n try {\n const result = await withRetry(\n entry.sendFn,\n this.config.retryConfig,\n entry.abort.signal,\n );\n\n entry.resolve(result);\n const idx = this.entries.indexOf(entry);\n if (idx !== -1) this.entries.splice(idx, 1);\n this.config.onStatusChange?.();\n this.persist();\n } catch (err) {\n if (entry.abort.signal.aborted) continue; // cancelled, already removed\n\n // Session not yet reconnected — revert to 'queued' and stop flushing.\n // flush() will be called again when the session is re-established\n // via the explicit flush() call in use-chat.ts startSession().\n if (err instanceof ChatDisconnectedError) {\n entry.status = 'queued';\n this.config.onStatusChange?.();\n awaitingSession = true;\n break;\n }\n\n entry.status = 'failed';\n entry.error = err instanceof Error ? err : new Error(String(err));\n entry.retryCount++;\n this.config.onStatusChange?.();\n this.persist();\n\n // Don't block the queue on a failed entry — skip to next\n }\n }\n } finally {\n this.flushing = false;\n // Re-check: entries may have been added while we were flushing.\n // Skip if we're waiting for session — startSession() will call flush().\n if (\n !awaitingSession &&\n this.entries.some((e) => e.status === 'queued') &&\n this.config.networkMonitor.isConnected()\n ) {\n queueMicrotask(() => this.flush());\n }\n }\n }\n\n private persist(): void {\n if (!this.config.storage) return;\n\n const data: PersistedEntry[] = this.entries.map((e) => ({\n optimisticId: e.optimisticId,\n retryCount: e.retryCount,\n }));\n\n const onErr = (err: unknown) => {\n this.config.onError?.(\n err instanceof Error ? err : new Error('Queue storage write failed'),\n );\n };\n\n if (data.length === 0) {\n this.config.storage.removeItem(this.storageKey).catch(onErr);\n } else {\n this.config.storage\n .setItem(this.storageKey, JSON.stringify(data))\n .catch(onErr);\n }\n }\n}\n","import { useEffect, useRef, useState, useCallback } from 'react';\nimport { AppState, Platform, type AppStateStatus } from 'react-native';\nimport type { UnreadCountResponse, SSEUnreadUpdateEvent } from '@pedi/chika-types';\nimport type { ChatConfig } from './types';\nimport { resolveServerUrl } from './resolve-url';\nimport { createSSEConnection, type SSEConnection } from './sse-connection';\nimport { createNetworkMonitor, type NetworkMonitor } from './network-monitor';\n\nconst DEFAULT_BACKGROUND_GRACE_MS = 2000;\nconst UNREAD_CUSTOM_EVENTS = ['unread_snapshot', 'unread_update', 'unread_clear'];\n\nexport interface UseUnreadOptions {\n config: ChatConfig;\n channelId: string;\n participantId: string;\n enabled?: boolean;\n}\n\nexport interface UseUnreadReturn {\n unreadCount: number;\n hasUnread: boolean;\n lastMessageAt: string | null;\n error: Error | null;\n}\n\nexport function useUnread(options: UseUnreadOptions): UseUnreadReturn {\n const { config, channelId, participantId, enabled = true } = options;\n\n const [unreadCount, setUnreadCount] = useState(0);\n const [lastMessageAt, setLastMessageAt] = useState<string | null>(null);\n const [error, setError] = useState<Error | null>(null);\n\n const connRef = useRef<SSEConnection | null>(null);\n const configRef = useRef(config);\n configRef.current = config;\n const appStateRef = useRef<AppStateStatus>(AppState.currentState);\n const backgroundTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n\n const backgroundGraceMs =\n config.backgroundGraceMs ?? (Platform.OS === 'android' ? DEFAULT_BACKGROUND_GRACE_MS : 0);\n\n const [monitor, setMonitor] = useState<NetworkMonitor | null>(null);\n const resilienceEnabled = config.resilience !== false;\n const injectedMonitor =\n typeof config.resilience === 'object' ? config.resilience.networkMonitor : undefined;\n\n useEffect(() => {\n if (!resilienceEnabled) {\n setMonitor(null);\n return;\n }\n if (injectedMonitor) {\n setMonitor(injectedMonitor);\n return;\n }\n const m = createNetworkMonitor();\n setMonitor(m);\n return () => {\n m.dispose();\n setMonitor(null);\n };\n }, [resilienceEnabled, injectedMonitor]);\n\n const connect = useCallback(() => {\n connRef.current?.close();\n connRef.current = null;\n\n const serviceUrl = resolveServerUrl(configRef.current.manifest, channelId);\n const customHeaders = configRef.current.headers ?? {};\n const url = `${serviceUrl}/channels/${channelId}/unread?participant_id=${encodeURIComponent(participantId)}`;\n\n connRef.current = createSSEConnection(\n {\n url,\n headers: customHeaders,\n reconnectDelayMs: configRef.current.reconnectDelayMs,\n customEvents: UNREAD_CUSTOM_EVENTS,\n networkMonitor: monitor ?? undefined,\n },\n {\n onOpen: () => {\n setError(null);\n },\n onEvent: (eventType, data) => {\n try {\n if (eventType === 'unread_snapshot') {\n const snapshot: UnreadCountResponse = JSON.parse(data);\n setUnreadCount(snapshot.unread_count);\n setLastMessageAt(snapshot.last_message_at);\n } else if (eventType === 'unread_update') {\n const update: SSEUnreadUpdateEvent['data'] = JSON.parse(data);\n setUnreadCount((prev) => prev + 1);\n setLastMessageAt(update.created_at);\n } else if (eventType === 'unread_clear') {\n const clear: { channel_id: string; unread_count: number } = JSON.parse(data);\n setUnreadCount(clear.unread_count);\n }\n } catch {\n setError(new Error('Failed to parse unread SSE event'));\n }\n },\n onError: (err) => {\n setError(err);\n },\n onClosed: () => {\n connRef.current = null;\n },\n },\n );\n }, [channelId, participantId, monitor]);\n\n const disconnect = useCallback(() => {\n connRef.current?.close();\n connRef.current = null;\n }, []);\n\n useEffect(() => {\n setUnreadCount(0);\n setLastMessageAt(null);\n setError(null);\n\n if (!enabled) {\n disconnect();\n return;\n }\n\n // Wait for monitor before connecting (avoids double-connect on mount)\n if (resilienceEnabled && !monitor) return;\n\n connect();\n\n return () => {\n disconnect();\n if (backgroundTimerRef.current) {\n clearTimeout(backgroundTimerRef.current);\n backgroundTimerRef.current = null;\n }\n };\n }, [channelId, participantId, enabled, connect, disconnect]);\n\n useEffect(() => {\n if (!enabled) return;\n\n const subscription = AppState.addEventListener('change', (nextAppState) => {\n const prev = appStateRef.current;\n appStateRef.current = nextAppState;\n\n if (!connRef.current && nextAppState !== 'active') return;\n\n const shouldTeardown =\n nextAppState === 'background' ||\n (Platform.OS === 'ios' && nextAppState === 'inactive');\n\n if (nextAppState === 'active') {\n if (backgroundTimerRef.current) {\n clearTimeout(backgroundTimerRef.current);\n backgroundTimerRef.current = null;\n return;\n }\n\n if (prev.match(/inactive|background/) && !connRef.current) {\n connect();\n }\n } else if (shouldTeardown) {\n if (backgroundTimerRef.current) return;\n\n if (backgroundGraceMs === 0) {\n disconnect();\n } else {\n backgroundTimerRef.current = setTimeout(() => {\n backgroundTimerRef.current = null;\n disconnect();\n }, backgroundGraceMs);\n }\n }\n });\n\n return () => {\n subscription.remove();\n if (backgroundTimerRef.current) {\n clearTimeout(backgroundTimerRef.current);\n backgroundTimerRef.current = null;\n }\n };\n }, [enabled, backgroundGraceMs, connect, disconnect]);\n\n return { unreadCount, hasUnread: unreadCount > 0, lastMessageAt, error };\n}\n"],"mappings":"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","names":["DEFAULT_RECONNECT_DELAY_MS","useEffect","useRef","useState","useCallback","AppState","Platform","DEFAULT_BACKGROUND_GRACE_MS","useState","useRef","AppState","Platform","useEffect","useCallback"]}
|
|
1
|
+
{"version":3,"sources":["../src/use-chat.ts","../src/errors.ts","../src/retry.ts","../src/resolve-url.ts","../src/sse-connection.ts","../src/session.ts","../src/network-monitor.ts","../src/message-queue.ts","../src/use-unread.ts"],"sourcesContent":["import { useEffect, useRef, useState, useCallback } from 'react';\nimport { AppState, Platform, type AppStateStatus } from 'react-native';\nimport type {\n ChatDomain,\n DefaultDomain,\n Message,\n Participant,\n MessageAttributes,\n SendMessageResponse,\n} from '@pedi/chika-types';\nimport type { UseChatOptions, UseChatReturn, ChatStatus } from './types';\nimport { ChatDisconnectedError, ChannelClosedError, QueueFullError, RetryExhaustedError } from './errors';\nimport { isRetryableError, resolveRetryConfig } from './retry';\nimport { createChatSession, type ChatSession, type SessionCallbacks } from './session';\nimport { createNetworkMonitor, type NetworkMonitor } from './network-monitor';\nimport { MessageQueue, type QueuedMessage } from './message-queue';\n\nconst DEFAULT_BACKGROUND_GRACE_MS = 2000;\nconst DEFAULT_MAX_QUEUE_SIZE = 50;\n\n// Module-scope queue registry, keyed by channelId. Survives component remounts.\nconst queueRegistry = new Map<string, { queue: MessageQueue; refCount: number }>();\n\n/**\n * React hook for real-time chat over SSE.\n * Manages connection lifecycle, AppState transitions, message deduplication, reconnection,\n * and optional network resilience (retry, offline queue, network monitoring).\n *\n * @template D - Chat domain type for role/message type narrowing. Defaults to DefaultDomain.\n */\nexport function useChat<D extends ChatDomain = DefaultDomain>(\n { config, channelId, profile, onMessage }: UseChatOptions<D>,\n): UseChatReturn<D> {\n const [messages, setMessages] = useState<Message<D>[]>([]);\n const [participants, setParticipants] = useState<Participant<D>[]>([]);\n const [status, setStatus] = useState<ChatStatus>('connecting');\n const [error, setError] = useState<Error | null>(null);\n const [pendingMessages, setPendingMessages] = useState<QueuedMessage[]>([]);\n\n const sessionRef = useRef<ChatSession<D> | null>(null);\n const disposedRef = useRef(false);\n const messagesRef = useRef(messages);\n messagesRef.current = messages;\n const statusRef = useRef(status);\n statusRef.current = status;\n const appStateRef = useRef<AppStateStatus>(AppState.currentState);\n const backgroundTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n const profileRef = useRef(profile);\n profileRef.current = profile;\n const configRef = useRef(config);\n configRef.current = config;\n const onMessageRef = useRef(onMessage);\n onMessageRef.current = onMessage;\n const startingRef = useRef(false);\n const pendingOptimisticIds = useRef(new Set<string>());\n const [monitor, setMonitor] = useState<NetworkMonitor | null>(null);\n const [monitorReady, setMonitorReady] = useState(false);\n const monitorRef = useRef<NetworkMonitor | null>(null);\n monitorRef.current = monitor;\n const queueRef = useRef<MessageQueue | null>(null);\n\n const resilienceEnabled = config.resilience !== false;\n const queueEnabled =\n resilienceEnabled &&\n (typeof config.resilience === 'object' ? config.resilience.offlineQueue !== false : true);\n const retryConfig = resolveRetryConfig(config.resilience);\n const maxQueueSize =\n (resilienceEnabled && config.resilience && typeof config.resilience === 'object'\n ? config.resilience.maxQueueSize\n : undefined) ?? DEFAULT_MAX_QUEUE_SIZE;\n\n const backgroundGraceMs =\n config.backgroundGraceMs ?? (Platform.OS === 'android' ? DEFAULT_BACKGROUND_GRACE_MS : 0);\n\n // Resolve user-injected monitor (stable reference, no side effect)\n const injectedMonitor =\n typeof config.resilience === 'object' ? config.resilience.networkMonitor : undefined;\n\n // Create monitor in useEffect to avoid side effects during render.\n // Uses state (not ref) so the queue effect re-runs when monitor is ready.\n useEffect(() => {\n if (!resilienceEnabled) {\n setMonitor(null);\n setMonitorReady(true);\n return;\n }\n if (injectedMonitor) {\n setMonitor(injectedMonitor);\n setMonitorReady(true);\n return; // user owns lifecycle\n }\n try {\n const m = createNetworkMonitor();\n setMonitor(m);\n setMonitorReady(true);\n return () => {\n m.dispose();\n setMonitor(null);\n };\n } catch {\n // NetInfo native module may be present but not linked — fall back to\n // no monitor so the session effect guard doesn't block forever.\n setMonitor(null);\n setMonitorReady(true);\n }\n }, [resilienceEnabled, injectedMonitor]);\n\n const callbacks: SessionCallbacks<D> = {\n onMessage: (message) => {\n if (disposedRef.current) return;\n let matchedOptimisticId: string | null = null;\n setMessages((prev: Message<D>[]) => {\n if (pendingOptimisticIds.current.size === 0) {\n return [...prev, message];\n }\n\n const optimisticIdx = prev.findIndex(\n (m) =>\n pendingOptimisticIds.current.has(m.id) &&\n m.sender_id === message.sender_id &&\n m.body === message.body &&\n m.type === message.type,\n );\n if (optimisticIdx !== -1) {\n matchedOptimisticId = prev[optimisticIdx]!.id;\n pendingOptimisticIds.current.delete(matchedOptimisticId);\n const next = [...prev];\n next[optimisticIdx] = message;\n return next;\n }\n return [...prev, message];\n });\n // Clean up any queued/failed queue entry — SSE confirmed delivery.\n // Done outside setMessages updater to avoid setState-in-setState.\n if (matchedOptimisticId) {\n const s = queueRef.current?.getStatus(matchedOptimisticId);\n if (s && s.status !== 'sending') {\n queueRef.current?.cancel(matchedOptimisticId);\n }\n }\n onMessageRef.current?.(message);\n },\n onStatusChange: (nextStatus) => {\n if (disposedRef.current) return;\n setStatus(nextStatus);\n if (nextStatus === 'connected') {\n setError(null);\n }\n },\n onError: (err) => {\n if (disposedRef.current) return;\n setError(err);\n },\n onResync: () => {\n if (disposedRef.current) return;\n startSession();\n },\n };\n\n async function startSession(): Promise<void> {\n if (startingRef.current) return;\n startingRef.current = true;\n\n const existing = sessionRef.current;\n if (existing) {\n existing.disconnect();\n sessionRef.current = null;\n }\n\n try {\n const session = await createChatSession<D>(\n configRef.current,\n channelId,\n profileRef.current,\n callbacks,\n monitorRef.current ?? undefined,\n );\n\n if (disposedRef.current) {\n session.disconnect();\n return;\n }\n\n sessionRef.current = session;\n setParticipants(session.initialParticipants);\n\n // Flush any messages queued while session was unavailable\n queueRef.current?.flush();\n\n // Re-merge any pending optimistic messages after resync\n if (pendingOptimisticIds.current.size > 0) {\n const pendingIds = pendingOptimisticIds.current;\n setMessages((prev) => {\n const pendingMsgs = prev.filter((m) => pendingIds.has(m.id));\n return [...session.initialMessages, ...pendingMsgs];\n });\n } else {\n setMessages(session.initialMessages);\n }\n } catch (err) {\n if (disposedRef.current) return;\n\n if (err instanceof ChannelClosedError) {\n setStatus('closed');\n setError(err);\n return;\n }\n\n setStatus('error');\n setError(err instanceof Error ? err : new Error(String(err)));\n } finally {\n startingRef.current = false;\n }\n }\n\n // Session lifecycle\n useEffect(() => {\n disposedRef.current = false;\n // Don't start until monitor initialization is complete\n if (!monitorReady) return;\n startSession();\n\n return () => {\n disposedRef.current = true;\n if (statusRef.current === 'connected' && sessionRef.current) {\n const lastMsg = messagesRef.current[messagesRef.current.length - 1];\n if (lastMsg) {\n sessionRef.current.markAsRead(lastMsg.id).catch(() => {});\n }\n }\n if (backgroundTimerRef.current) {\n clearTimeout(backgroundTimerRef.current);\n backgroundTimerRef.current = null;\n }\n sessionRef.current?.disconnect();\n sessionRef.current = null;\n };\n }, [channelId, monitorReady]);\n\n // Module-scope queue lifecycle (ref-counted, survives remounts)\n useEffect(() => {\n if (!queueEnabled || !monitor || !retryConfig) return;\n\n let entry = queueRegistry.get(channelId);\n if (!entry) {\n const queue = new MessageQueue({\n channelId,\n maxSize: maxQueueSize,\n retryConfig,\n networkMonitor: monitor,\n storage:\n typeof config.resilience === 'object'\n ? config.resilience.queueStorage\n : undefined,\n onError: (err) => {\n if (!disposedRef.current) setError(err);\n },\n onStatusChange: () => {\n if (!disposedRef.current) {\n setPendingMessages(queueRef.current?.getAll() ?? []);\n }\n },\n });\n entry = { queue, refCount: 0 };\n queueRegistry.set(channelId, entry);\n }\n entry.refCount++;\n queueRef.current = entry.queue;\n\n return () => {\n const e = queueRegistry.get(channelId);\n if (e) {\n e.refCount--;\n if (e.refCount <= 0) {\n e.queue.dispose();\n queueRegistry.delete(channelId);\n }\n }\n queueRef.current = null;\n };\n }, [channelId, queueEnabled, monitor]);\n\n // Network monitor: auto-rejoin on connectivity return when in error state\n useEffect(() => {\n if (!monitor || !resilienceEnabled) return;\n\n const unsub = monitor.subscribe((connected: boolean) => {\n if (connected && statusRef.current === 'error' && !startingRef.current) {\n startSession();\n }\n });\n\n return unsub;\n }, [channelId, resilienceEnabled, monitor]);\n\n // AppState lifecycle\n useEffect(() => {\n function teardownSession(): void {\n sessionRef.current?.disconnect();\n sessionRef.current = null;\n setStatus('disconnected');\n }\n\n const subscription = AppState.addEventListener('change', (nextAppState) => {\n const prev = appStateRef.current;\n appStateRef.current = nextAppState;\n\n if (!sessionRef.current && nextAppState !== 'active') return;\n\n const shouldTeardown =\n nextAppState === 'background' ||\n (Platform.OS === 'ios' && nextAppState === 'inactive');\n\n if (nextAppState === 'active') {\n if (backgroundTimerRef.current) {\n clearTimeout(backgroundTimerRef.current);\n backgroundTimerRef.current = null;\n return;\n }\n\n if (prev.match(/inactive|background/) && !sessionRef.current) {\n startSession();\n }\n } else if (shouldTeardown) {\n if (backgroundTimerRef.current) return;\n\n if (backgroundGraceMs === 0) {\n teardownSession();\n } else {\n backgroundTimerRef.current = setTimeout(() => {\n backgroundTimerRef.current = null;\n teardownSession();\n }, backgroundGraceMs);\n }\n }\n });\n\n return () => {\n subscription.remove();\n if (backgroundTimerRef.current) {\n clearTimeout(backgroundTimerRef.current);\n backgroundTimerRef.current = null;\n }\n };\n }, [channelId, backgroundGraceMs]);\n\n const sendMessage = useCallback(\n async (type: D['messageType'], body: string, attributes?: MessageAttributes<D>): Promise<SendMessageResponse> => {\n const session = sessionRef.current;\n if (!session) throw new ChatDisconnectedError(statusRef.current);\n\n const optimistic = configRef.current.optimisticSend !== false;\n // Serves as both the optimistic message ID and the server-side idempotency key\n const messageKey = `optimistic_${Date.now()}_${Math.random().toString(36).slice(2, 7)}`;\n\n if (optimistic) {\n pendingOptimisticIds.current.add(messageKey);\n const provisionalMsg: Message<D> = {\n id: messageKey,\n channel_id: channelId,\n sender_id: profileRef.current.id,\n sender_role: profileRef.current.role,\n type,\n body,\n attributes: (attributes ?? {}) as MessageAttributes<D>,\n created_at: new Date().toISOString(),\n };\n setMessages((prev) => [...prev, provisionalMsg]);\n }\n\n const doSend = () => {\n const s = sessionRef.current;\n if (!s) throw new ChatDisconnectedError(statusRef.current);\n return s.sendMessage(type, body, attributes, messageKey);\n };\n\n const handleSuccess = (response: SendMessageResponse): void => {\n if (optimistic) {\n pendingOptimisticIds.current.delete(messageKey);\n setMessages((prev) => {\n const stillPending = prev.some((m) => m.id === messageKey);\n if (!stillPending) return prev;\n return prev.map((m) =>\n m.id === messageKey\n ? { ...m, id: response.id, created_at: response.created_at }\n : m,\n );\n });\n }\n };\n\n const handleError = (_err: unknown): void => {\n if (optimistic) {\n pendingOptimisticIds.current.delete(messageKey);\n setMessages((prev) => prev.filter((m) => m.id !== messageKey));\n }\n };\n\n // Queue path: enqueue and let queue handle retry + offline\n if (queueRef.current) {\n try {\n const response = await queueRef.current.enqueue(doSend, messageKey);\n handleSuccess(response);\n return response;\n } catch (err) {\n if (err instanceof QueueFullError) {\n handleError(err);\n throw err;\n }\n if (err instanceof RetryExhaustedError || !isRetryableError(err)) {\n // For failed messages: if optimistic, mark as failed (keep in UI)\n // The pendingMessages state shows the status\n if (optimistic && err instanceof RetryExhaustedError) {\n // Keep optimistic message visible — queue tracks status as 'failed'.\n // Keep messageKey in pendingOptimisticIds so SSE reconciliation can\n // still match if the server eventually received the message.\n // Removed on explicit cancelMessage() call.\n } else {\n handleError(err);\n }\n throw err;\n }\n handleError(err);\n throw err;\n }\n }\n\n // Non-queue path: direct send (session.ts handles retry if enabled)\n try {\n const response = await doSend();\n handleSuccess(response);\n return response;\n } catch (err) {\n handleError(err);\n throw err;\n }\n },\n [channelId],\n );\n\n const cancelMessage = useCallback(\n (optimisticId: string) => {\n queueRef.current?.cancel(optimisticId);\n pendingOptimisticIds.current.delete(optimisticId);\n setMessages((prev) => prev.filter((m) => m.id !== optimisticId));\n },\n [],\n );\n\n const retryMessage = useCallback(\n (optimisticId: string) => {\n queueRef.current?.retry(optimisticId);\n },\n [],\n );\n\n const disconnect = useCallback(() => {\n sessionRef.current?.disconnect();\n sessionRef.current = null;\n setStatus('disconnected');\n }, []);\n\n return {\n messages,\n participants,\n status,\n error,\n sendMessage,\n disconnect,\n pendingMessages,\n cancelMessage,\n retryMessage,\n };\n}\n","import type { ChatStatus } from './types';\n\nexport class ChatDisconnectedError extends Error {\n constructor(public readonly status: ChatStatus) {\n super(`Cannot send message while ${status}`);\n this.name = 'ChatDisconnectedError';\n }\n}\n\nexport class ChannelClosedError extends Error {\n constructor(public readonly channelId: string) {\n super(`Channel ${channelId} is closed`);\n this.name = 'ChannelClosedError';\n }\n}\n\nexport class HttpError extends Error {\n constructor(\n public readonly status: number,\n public readonly body: string,\n public readonly retryAfter?: number,\n ) {\n super(`HTTP ${status}: ${body}`);\n this.name = 'HttpError';\n }\n}\n\nexport class RetryExhaustedError extends Error {\n constructor(\n public readonly operation: string,\n public readonly attempts: number,\n public readonly lastError: Error,\n ) {\n super(`${operation} failed after ${attempts} attempts: ${lastError.message}`);\n this.name = 'RetryExhaustedError';\n }\n}\n\nexport class QueueFullError extends Error {\n constructor(public readonly maxSize: number) {\n super(`Message queue full (max ${maxSize})`);\n this.name = 'QueueFullError';\n }\n}\n","import {\n HttpError,\n RetryExhaustedError,\n ChannelClosedError,\n ChatDisconnectedError,\n QueueFullError,\n} from './errors';\n\nexport interface RetryConfig {\n maxAttempts: number;\n baseDelayMs: number;\n maxDelayMs: number;\n jitterFactor: number;\n}\n\nexport const DEFAULT_RETRY_CONFIG: RetryConfig = {\n maxAttempts: 3,\n baseDelayMs: 1000,\n maxDelayMs: 10000,\n jitterFactor: 0.3,\n};\n\nexport function calculateBackoff(attempt: number, config: RetryConfig): number {\n const delay = Math.min(config.baseDelayMs * 2 ** attempt, config.maxDelayMs);\n const jitter = 1 + (Math.random() * 2 - 1) * config.jitterFactor;\n return Math.round(delay * jitter);\n}\n\nexport function isRetryableError(error: unknown): boolean {\n if (error instanceof ChannelClosedError) return false;\n if (error instanceof ChatDisconnectedError) return false;\n if (error instanceof QueueFullError) return false;\n if (error instanceof DOMException && error.name === 'AbortError') return false;\n\n if (error instanceof HttpError) {\n const { status } = error;\n if (status === 408 || status === 429) return true;\n if (status >= 500) return true;\n return false;\n }\n\n // TypeError from fetch = network failure\n if (error instanceof TypeError) return true;\n\n return false;\n}\n\nexport function sleep(ms: number, signal?: AbortSignal): Promise<void> {\n return new Promise((resolve, reject) => {\n if (signal?.aborted) {\n reject(signal.reason ?? new DOMException('Aborted', 'AbortError'));\n return;\n }\n\n const timer = setTimeout(resolve, ms);\n\n signal?.addEventListener(\n 'abort',\n () => {\n clearTimeout(timer);\n reject(signal.reason ?? new DOMException('Aborted', 'AbortError'));\n },\n { once: true },\n );\n });\n}\n\nexport function resolveRetryConfig(\n resilience: { retry?: Partial<RetryConfig> | false } | false | undefined,\n): RetryConfig | null {\n if (resilience === false) return null;\n if (!resilience || resilience.retry === undefined) return DEFAULT_RETRY_CONFIG;\n if (resilience.retry === false) return null;\n return { ...DEFAULT_RETRY_CONFIG, ...resilience.retry };\n}\n\nexport async function withRetry<T>(\n fn: () => Promise<T>,\n config: RetryConfig = DEFAULT_RETRY_CONFIG,\n signal?: AbortSignal,\n): Promise<T> {\n let lastError: Error | undefined;\n\n for (let attempt = 0; attempt < config.maxAttempts; attempt++) {\n if (signal?.aborted) {\n throw signal.reason ?? new DOMException('Aborted', 'AbortError');\n }\n\n try {\n return await fn();\n } catch (err) {\n lastError = err instanceof Error ? err : new Error(String(err));\n\n if (!isRetryableError(err)) throw lastError;\n\n if (attempt < config.maxAttempts - 1) {\n // Respect Retry-After header for 429 responses\n const delayMs =\n err instanceof HttpError && err.retryAfter != null\n ? err.retryAfter * 1000\n : calculateBackoff(attempt, config);\n\n await sleep(delayMs, signal);\n }\n }\n }\n\n throw new RetryExhaustedError(\n 'operation',\n config.maxAttempts,\n lastError!,\n );\n}\n","import type { ChatManifest } from '@pedi/chika-types';\n\n/**\n * Creates a single-server manifest. Use this when all channels route to the same server.\n *\n * @example\n * ```ts\n * const config: ChatConfig = { manifest: createManifest('https://chat.example.com') };\n * ```\n */\nexport function createManifest(serverUrl: string): ChatManifest {\n return { buckets: [{ group: 'default', range: [0, 99], server_url: serverUrl }] };\n}\n\nexport function resolveServerUrl(manifest: ChatManifest, channelId: string): string {\n const hash = [...channelId].reduce((sum, c) => sum + c.charCodeAt(0), 0) % 100;\n const bucket = manifest.buckets.find(\n (b) => hash >= b.range[0] && hash <= b.range[1],\n );\n if (!bucket) throw new Error(`No chat bucket for hash ${hash}`);\n return bucket.server_url;\n}\n","import EventSource from 'react-native-sse';\nimport { calculateBackoff, type RetryConfig } from './retry';\nimport type { NetworkMonitor } from './network-monitor';\n\nconst DEFAULT_RECONNECT_DELAY_MS = 3000;\n\nexport interface SSEConnectionConfig {\n url: string;\n headers?: Record<string, string>;\n reconnectDelayMs?: number;\n lastEventId?: string;\n customEvents?: string[];\n networkMonitor?: NetworkMonitor;\n}\n\nexport interface SSEConnectionCallbacks {\n onOpen?: () => void;\n onEvent: (eventType: string, data: string, lastEventId?: string) => void;\n onError?: (error: Error) => void;\n onClosed?: () => void;\n onReconnecting?: () => void;\n}\n\nexport interface SSEConnection {\n close: () => void;\n reconnectImmediate: () => void;\n}\n\nexport function createSSEConnection(\n config: SSEConnectionConfig,\n callbacks: SSEConnectionCallbacks,\n): SSEConnection {\n const baseDelay = config.reconnectDelayMs ?? DEFAULT_RECONNECT_DELAY_MS;\n const customEvents = config.customEvents ?? [];\n const monitor = config.networkMonitor;\n\n const backoffConfig: RetryConfig = {\n maxAttempts: Infinity,\n baseDelayMs: baseDelay,\n maxDelayMs: 30000,\n jitterFactor: 0.3,\n };\n\n let currentLastEventId = config.lastEventId;\n let es: EventSource<string> | null = null;\n let disposed = false;\n let reconnectTimer: ReturnType<typeof setTimeout> | null = null;\n let attempt = 0;\n let waitAbort: AbortController | null = null;\n\n function cleanup(): void {\n if (es) {\n es.removeAllEventListeners();\n es.close();\n es = null;\n }\n if (reconnectTimer) {\n clearTimeout(reconnectTimer);\n reconnectTimer = null;\n }\n if (waitAbort) {\n waitAbort.abort();\n waitAbort = null;\n }\n }\n\n async function scheduleReconnect(): Promise<void> {\n if (disposed || reconnectTimer || waitAbort) return;\n callbacks.onReconnecting?.();\n cleanup();\n\n // Wait for network if monitor available\n if (monitor && !monitor.isConnected()) {\n waitAbort = new AbortController();\n try {\n await monitor.waitForOnline(waitAbort.signal);\n } catch {\n return; // aborted via dispose\n }\n waitAbort = null;\n if (disposed) return;\n }\n\n const delay = calculateBackoff(attempt++, backoffConfig);\n reconnectTimer = setTimeout(() => {\n reconnectTimer = null;\n connect();\n }, delay);\n }\n\n function connect(): void {\n if (disposed) return;\n\n es = new EventSource<string>(config.url, {\n headers: {\n ...config.headers,\n ...(currentLastEventId && { 'Last-Event-ID': currentLastEventId }),\n },\n pollingInterval: 0,\n });\n\n es.addEventListener('open', () => {\n if (disposed) return;\n attempt = 0;\n callbacks.onOpen?.();\n });\n\n es.addEventListener('message', (event) => {\n if (disposed || !event.data) return;\n if (event.lastEventId) {\n currentLastEventId = event.lastEventId;\n }\n callbacks.onEvent('message', event.data, event.lastEventId ?? undefined);\n });\n\n for (const eventName of customEvents) {\n es.addEventListener(eventName, (event) => {\n if (disposed) return;\n callbacks.onEvent(eventName, event.data ?? '', undefined);\n });\n }\n\n es.addEventListener('error', (event) => {\n if (disposed) return;\n\n const msg = 'message' in event ? String(event.message) : '';\n\n if (msg.includes('Channel is closed') || msg.includes('410')) {\n callbacks.onClosed?.();\n cleanup();\n disposed = true;\n return;\n }\n\n if (msg) callbacks.onError?.(new Error(msg));\n\n scheduleReconnect();\n });\n\n es.addEventListener('close', () => {\n if (disposed) return;\n scheduleReconnect();\n });\n }\n\n connect();\n\n return {\n close: () => {\n disposed = true;\n cleanup();\n },\n\n reconnectImmediate: () => {\n if (disposed) return;\n cleanup(); // does NOT set disposed\n attempt = 0;\n connect();\n },\n };\n}\n","import type {\n ChatDomain,\n DefaultDomain,\n Participant,\n Message,\n JoinResponse,\n SendMessageRequest,\n SendMessageResponse,\n MessageAttributes,\n} from '@pedi/chika-types';\nimport type { ChatConfig, ChatStatus } from './types';\nimport { ChatDisconnectedError, ChannelClosedError, HttpError } from './errors';\nimport { resolveServerUrl } from './resolve-url';\nimport { createSSEConnection, type SSEConnection } from './sse-connection';\nimport { withRetry, resolveRetryConfig, type RetryConfig } from './retry';\nimport type { NetworkMonitor } from './network-monitor';\n\nconst DEFAULT_RECONNECT_DELAY_MS = 3000;\nconst MAX_SEEN_IDS = 500;\n\nconst MARK_READ_RETRY_CONFIG: RetryConfig = {\n maxAttempts: 2,\n baseDelayMs: 500,\n maxDelayMs: 2000,\n jitterFactor: 0.3,\n};\n\nexport interface SessionCallbacks<D extends ChatDomain = DefaultDomain> {\n onMessage: (message: Message<D>) => void;\n onStatusChange: (status: ChatStatus) => void;\n onError: (error: Error) => void;\n onResync: () => void;\n}\n\nexport interface ChatSession<D extends ChatDomain = DefaultDomain> {\n serviceUrl: string;\n channelId: string;\n initialParticipants: Participant<D>[];\n initialMessages: Message<D>[];\n networkMonitor: NetworkMonitor | null;\n sendMessage: (\n type: D['messageType'],\n body: string,\n attributes?: MessageAttributes<D>,\n idempotencyKey?: string,\n ) => Promise<SendMessageResponse>;\n markAsRead: (messageId: string) => Promise<void>;\n disconnect: () => void;\n}\n\nfunction parseRetryAfter(res: Response): number | undefined {\n const header = res.headers.get('Retry-After');\n if (!header) return undefined;\n const seconds = Number(header);\n return Number.isFinite(seconds) ? seconds : undefined;\n}\n\nasync function throwHttpError(res: Response): Promise<never> {\n const body = await res.text().catch(() => '');\n throw new HttpError(res.status, body, parseRetryAfter(res));\n}\n\nexport async function createChatSession<D extends ChatDomain = DefaultDomain>(\n config: ChatConfig,\n channelId: string,\n profile: Participant<D>,\n callbacks: SessionCallbacks<D>,\n networkMonitor?: NetworkMonitor,\n): Promise<ChatSession<D>> {\n const serviceUrl = resolveServerUrl(config.manifest, channelId);\n const customHeaders = config.headers ?? {};\n const reconnectDelay = config.reconnectDelayMs ?? DEFAULT_RECONNECT_DELAY_MS;\n const retryConfig = resolveRetryConfig(config.resilience);\n\n const sessionAbort = new AbortController();\n\n callbacks.onStatusChange('connecting');\n\n const joinFn = async (): Promise<JoinResponse<D>> => {\n const joinRes = await fetch(`${serviceUrl}/channels/${channelId}/join`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', ...customHeaders },\n body: JSON.stringify(profile),\n signal: sessionAbort.signal,\n });\n\n if (joinRes.status === 410) {\n throw new ChannelClosedError(channelId);\n }\n\n if (!joinRes.ok) {\n await throwHttpError(joinRes);\n }\n\n return joinRes.json();\n };\n\n const joinData = retryConfig\n ? await withRetry(joinFn, retryConfig, sessionAbort.signal)\n : await joinFn();\n\n const { messages, participants, joined_at }: JoinResponse<D> = joinData;\n\n let lastEventId =\n messages.length > 0 ? messages[messages.length - 1]!.id : undefined;\n\n const joinedAt = joined_at;\n\n const seenMessageIds = new Set<string>(messages.map((m) => m.id));\n\n let sseConn: SSEConnection | null = null;\n let disposed = false;\n\n const TRIM_THRESHOLD = MAX_SEEN_IDS * 1.5;\n\n function trimSeenIds(): void {\n if (seenMessageIds.size <= TRIM_THRESHOLD) return;\n const ids = [...seenMessageIds];\n seenMessageIds.clear();\n for (const id of ids.slice(-MAX_SEEN_IDS)) {\n seenMessageIds.add(id);\n }\n }\n\n function connect(): void {\n if (disposed) return;\n\n const streamUrl = lastEventId\n ? `${serviceUrl}/channels/${channelId}/stream`\n : `${serviceUrl}/channels/${channelId}/stream?since_time=${encodeURIComponent(joinedAt)}`;\n\n sseConn = createSSEConnection(\n {\n url: streamUrl,\n headers: customHeaders,\n reconnectDelayMs: reconnectDelay,\n lastEventId,\n customEvents: ['resync'],\n networkMonitor,\n },\n {\n onOpen: () => {\n if (!disposed) callbacks.onStatusChange('connected');\n },\n onEvent: (eventType, data, eventId) => {\n if (disposed) return;\n\n if (eventType === 'message') {\n let message: Message<D>;\n try {\n message = JSON.parse(data);\n } catch {\n callbacks.onError(new Error('Failed to parse SSE message'));\n return;\n }\n\n if (eventId) {\n lastEventId = eventId;\n }\n\n if (seenMessageIds.has(message.id)) return;\n seenMessageIds.add(message.id);\n trimSeenIds();\n\n callbacks.onMessage(message);\n } else if (eventType === 'resync') {\n // Let onResync → startSession handle full session recreation.\n // Don't call reconnectImmediate() — startSession disconnects this\n // session anyway, so the reconnect would be immediately thrown away.\n callbacks.onResync();\n }\n },\n onError: (err) => {\n if (!disposed) callbacks.onError(err);\n },\n onClosed: () => {\n callbacks.onStatusChange('closed');\n disposed = true;\n },\n onReconnecting: () => {\n if (!disposed) callbacks.onStatusChange('reconnecting');\n },\n },\n );\n }\n\n connect();\n\n return {\n serviceUrl,\n channelId,\n initialParticipants: participants,\n initialMessages: messages,\n networkMonitor: networkMonitor ?? null,\n\n sendMessage: async (type, body, attributes, idempotencyKey) => {\n if (disposed) throw new ChatDisconnectedError('disconnected');\n\n const payload: SendMessageRequest<D> = {\n sender_id: profile.id,\n type,\n body,\n attributes,\n ...(idempotencyKey ? { idempotency_key: idempotencyKey } : {}),\n };\n\n const sendFn = async (): Promise<SendMessageResponse> => {\n const res = await fetch(\n `${serviceUrl}/channels/${channelId}/messages`,\n {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', ...customHeaders },\n body: JSON.stringify(payload),\n signal: sessionAbort.signal,\n },\n );\n\n if (!res.ok) {\n await throwHttpError(res);\n }\n\n return res.json();\n };\n\n const response = retryConfig\n ? await withRetry(sendFn, retryConfig, sessionAbort.signal)\n : await sendFn();\n\n seenMessageIds.add(response.id);\n return response;\n },\n\n markAsRead: async (messageId: string) => {\n // markAsRead intentionally does NOT use sessionAbort.signal — it must\n // survive disconnect() since it's called on unmount right before disconnect.\n const readFn = async (): Promise<void> => {\n const res = await fetch(`${serviceUrl}/channels/${channelId}/read`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', ...customHeaders },\n body: JSON.stringify({\n participant_id: profile.id,\n message_id: messageId,\n }),\n });\n if (!res.ok) {\n await throwHttpError(res);\n }\n };\n\n if (retryConfig) {\n try {\n await withRetry(readFn, MARK_READ_RETRY_CONFIG);\n } catch (err) {\n // Surface non-retryable errors (403, 404) for dev diagnostics\n if (err instanceof HttpError) {\n callbacks.onError(err);\n }\n // Swallow RetryExhaustedError — markAsRead is best-effort\n }\n } else {\n await readFn();\n }\n },\n\n disconnect: () => {\n disposed = true;\n sessionAbort.abort();\n sseConn?.close();\n sseConn = null;\n callbacks.onStatusChange('disconnected');\n },\n };\n}\n","export interface NetworkMonitor {\n isConnected(): boolean;\n subscribe(cb: (connected: boolean) => void): () => void;\n waitForOnline(signal?: AbortSignal): Promise<void>;\n dispose(): void;\n}\n\nfunction createStubMonitor(): NetworkMonitor {\n return {\n isConnected: () => true,\n subscribe: () => () => {},\n waitForOnline: () => Promise.resolve(),\n dispose: () => {},\n };\n}\n\nlet resolvedNetInfo: any = undefined;\nlet netInfoResolved = false;\n\nfunction getNetInfo(): any {\n if (netInfoResolved) return resolvedNetInfo;\n netInfoResolved = true;\n try {\n resolvedNetInfo = require('@react-native-community/netinfo');\n } catch {\n resolvedNetInfo = null;\n }\n return resolvedNetInfo;\n}\n\nexport function createNetworkMonitor(): NetworkMonitor {\n const NetInfo = getNetInfo();\n if (!NetInfo) return createStubMonitor();\n\n const netInfoModule = NetInfo.default ?? NetInfo;\n\n let connected = true;\n const listeners = new Set<(connected: boolean) => void>();\n let unsubscribeNetInfo: (() => void) | null = null;\n\n unsubscribeNetInfo = netInfoModule.addEventListener(\n (state: { isConnected: boolean | null }) => {\n const next = state.isConnected !== false;\n if (next === connected) return;\n connected = next;\n for (const cb of listeners) {\n cb(connected);\n }\n },\n );\n\n return {\n isConnected: () => connected,\n\n subscribe: (cb) => {\n listeners.add(cb);\n return () => {\n listeners.delete(cb);\n };\n },\n\n waitForOnline: (signal?) => {\n if (connected) return Promise.resolve();\n\n return new Promise<void>((resolve, reject) => {\n if (signal?.aborted) {\n reject(signal.reason ?? new DOMException('Aborted', 'AbortError'));\n return;\n }\n\n const unsub = (): void => {\n listeners.delete(handler);\n signal?.removeEventListener('abort', onAbort);\n };\n\n const handler = (isOnline: boolean): void => {\n if (isOnline) {\n unsub();\n resolve();\n }\n };\n\n const onAbort = (): void => {\n unsub();\n reject(signal!.reason ?? new DOMException('Aborted', 'AbortError'));\n };\n\n listeners.add(handler);\n signal?.addEventListener('abort', onAbort, { once: true });\n });\n },\n\n dispose: () => {\n listeners.clear();\n unsubscribeNetInfo?.();\n unsubscribeNetInfo = null;\n },\n };\n}\n","import type { SendMessageResponse } from '@pedi/chika-types';\nimport { withRetry, type RetryConfig } from './retry';\nimport { QueueFullError, ChatDisconnectedError } from './errors';\nimport type { NetworkMonitor } from './network-monitor';\n\nexport interface QueueStorage {\n getItem(key: string): Promise<string | null>;\n setItem(key: string, value: string): Promise<void>;\n removeItem(key: string): Promise<void>;\n}\n\nlet resolvedStorage: { type: string; adapter: QueueStorage } | null | undefined;\n\nfunction tryRequire(name: string): any {\n try {\n return require(name);\n } catch {\n return null;\n }\n}\n\nfunction createMmkvAdapter(mod: any): QueueStorage {\n const MMKV = mod.MMKV ?? mod.default?.MMKV ?? mod;\n const instance = new MMKV({ id: 'chika-queue' });\n return {\n getItem: (key) => Promise.resolve(instance.getString(key) ?? null),\n setItem: (key, value) => { instance.set(key, value); return Promise.resolve(); },\n removeItem: (key) => { instance.delete(key); return Promise.resolve(); },\n };\n}\n\nfunction createAsyncStorageAdapterFrom(mod: any): QueueStorage {\n const storage = mod.default ?? mod;\n return {\n getItem: (key) => storage.getItem(key),\n setItem: (key, value) => storage.setItem(key, value),\n removeItem: (key) => storage.removeItem(key),\n };\n}\n\n/**\n * Auto-detect the best available storage for queue persistence.\n * Priority: react-native-mmkv (fastest, sync) > AsyncStorage > null.\n * Returns `null` if neither is installed — no hard dependencies.\n *\n * Usage:\n * ```ts\n * import { createQueueStorage } from '@pedi/chika-sdk';\n *\n * const config: ChatConfig = {\n * resilience: {\n * queueStorage: createQueueStorage() ?? undefined,\n * },\n * };\n * ```\n */\nexport function createQueueStorage(): QueueStorage | null {\n if (resolvedStorage !== undefined) return resolvedStorage?.adapter ?? null;\n\n // Priority 1: MMKV (synchronous, fastest)\n const mmkv = tryRequire('react-native-mmkv');\n if (mmkv) {\n try {\n const adapter = createMmkvAdapter(mmkv);\n resolvedStorage = { type: 'mmkv', adapter };\n return adapter;\n } catch {\n // MMKV instantiation can fail if native module isn't linked\n }\n }\n\n // Priority 2: AsyncStorage\n const asyncStorage = tryRequire('@react-native-async-storage/async-storage');\n if (asyncStorage) {\n const adapter = createAsyncStorageAdapterFrom(asyncStorage);\n resolvedStorage = { type: 'async-storage', adapter };\n return adapter;\n }\n\n resolvedStorage = null;\n return null;\n}\n\n/**\n * Creates a QueueStorage adapter backed by `@react-native-async-storage/async-storage`.\n * Returns `null` if the package is not installed.\n */\nexport function createAsyncStorageAdapter(): QueueStorage | null {\n const mod = tryRequire('@react-native-async-storage/async-storage');\n if (!mod) return null;\n return createAsyncStorageAdapterFrom(mod);\n}\n\nexport type MessageSendStatus = 'sending' | 'queued' | 'failed';\n\nexport interface QueuedMessage {\n optimisticId: string;\n status: MessageSendStatus;\n error?: Error;\n retryCount: number;\n}\n\ntype SendFn = () => Promise<SendMessageResponse>;\n\ninterface QueueEntry {\n optimisticId: string;\n sendFn: SendFn;\n status: MessageSendStatus;\n error?: Error;\n retryCount: number;\n abort: AbortController;\n resolve: (value: SendMessageResponse) => void;\n reject: (reason: Error) => void;\n}\n\n/** Serializable subset persisted to storage. */\ninterface PersistedEntry {\n optimisticId: string;\n retryCount: number;\n}\n\nexport interface MessageQueueConfig {\n channelId: string;\n maxSize: number;\n retryConfig: RetryConfig;\n networkMonitor: NetworkMonitor;\n storage?: QueueStorage;\n onError?: (error: Error) => void;\n onStatusChange?: () => void;\n}\n\nexport class MessageQueue {\n private entries: QueueEntry[] = [];\n private flushing = false;\n private unsubNetwork: (() => void) | null = null;\n private readonly storageKey: string;\n\n constructor(private readonly config: MessageQueueConfig) {\n this.storageKey = `chika_queue_${config.channelId}`;\n\n this.unsubNetwork = config.networkMonitor.subscribe((connected) => {\n if (connected) this.flush();\n });\n }\n\n /**\n * Restore queued messages from persistent storage on cold start.\n * Restored entries are fire-and-forget — there is no caller awaiting their\n * promise (the original enqueue() caller is gone after app restart).\n * Success/failure is reported via onStatusChange/onError callbacks.\n */\n async restore(\n rebuildSendFn: (optimisticId: string) => SendFn | null,\n ): Promise<void> {\n if (!this.config.storage) return;\n\n try {\n const raw = await this.config.storage.getItem(this.storageKey);\n if (!raw) return;\n\n const persisted: PersistedEntry[] = JSON.parse(raw);\n for (const entry of persisted) {\n const sendFn = rebuildSendFn(entry.optimisticId);\n if (!sendFn) continue;\n\n const abort = new AbortController();\n this.entries.push({\n optimisticId: entry.optimisticId,\n sendFn,\n status: 'queued',\n retryCount: entry.retryCount,\n abort,\n // No-op: restored entries have no caller awaiting the promise.\n resolve: () => {},\n reject: () => {},\n });\n }\n\n if (this.entries.length > 0) {\n this.config.onStatusChange?.();\n this.flush();\n }\n } catch {\n this.config.onError?.(new Error('Failed to restore message queue from storage'));\n }\n }\n\n get pendingCount(): number {\n return this.entries.length;\n }\n\n getAll(): QueuedMessage[] {\n return this.entries.map((e) => ({\n optimisticId: e.optimisticId,\n status: e.status,\n error: e.error,\n retryCount: e.retryCount,\n }));\n }\n\n getStatus(optimisticId: string): QueuedMessage | undefined {\n const entry = this.entries.find((e) => e.optimisticId === optimisticId);\n if (!entry) return undefined;\n return {\n optimisticId: entry.optimisticId,\n status: entry.status,\n error: entry.error,\n retryCount: entry.retryCount,\n };\n }\n\n enqueue(sendFn: SendFn, optimisticId: string): Promise<SendMessageResponse> {\n if (this.entries.length >= this.config.maxSize) {\n throw new QueueFullError(this.config.maxSize);\n }\n\n const abort = new AbortController();\n\n return new Promise<SendMessageResponse>((resolve, reject) => {\n const entry: QueueEntry = {\n optimisticId,\n sendFn,\n status: 'queued',\n retryCount: 0,\n abort,\n resolve,\n reject,\n };\n\n this.entries.push(entry);\n this.config.onStatusChange?.();\n this.persist();\n this.flush();\n });\n }\n\n cancel(optimisticId: string): void {\n const idx = this.entries.findIndex((e) => e.optimisticId === optimisticId);\n if (idx === -1) return;\n\n const entry = this.entries[idx]!;\n entry.abort.abort();\n entry.reject(new DOMException('Cancelled', 'AbortError'));\n this.entries.splice(idx, 1);\n this.config.onStatusChange?.();\n this.persist();\n }\n\n retry(optimisticId: string): void {\n const entry = this.entries.find((e) => e.optimisticId === optimisticId);\n if (!entry || entry.status !== 'failed') return;\n\n entry.status = 'queued';\n entry.error = undefined;\n entry.abort = new AbortController();\n this.config.onStatusChange?.();\n this.flush();\n }\n\n dispose(): void {\n this.unsubNetwork?.();\n this.unsubNetwork = null;\n\n for (const entry of this.entries) {\n entry.abort.abort();\n entry.reject(new DOMException('Queue disposed', 'AbortError'));\n }\n this.entries = [];\n }\n\n /**\n * Trigger a flush of queued messages. Returns immediately if a flush is\n * already in progress or the network is offline. The in-progress flush\n * will pick up any newly queued entries via its while loop.\n */\n async flush(): Promise<void> {\n if (this.flushing) return;\n if (!this.config.networkMonitor.isConnected()) return;\n\n this.flushing = true;\n let awaitingSession = false;\n\n try {\n while (this.entries.length > 0) {\n // Only pick up 'queued' entries — 'sending' means a previous flush is\n // mid-request. Server-side idempotency key prevents duplicates if the\n // prior request actually succeeded but we never got the response.\n const entry = this.entries.find((e) => e.status === 'queued');\n if (!entry) break;\n if (!this.config.networkMonitor.isConnected()) break;\n\n entry.status = 'sending';\n this.config.onStatusChange?.();\n\n try {\n const result = await withRetry(\n entry.sendFn,\n this.config.retryConfig,\n entry.abort.signal,\n );\n\n entry.resolve(result);\n const idx = this.entries.indexOf(entry);\n if (idx !== -1) this.entries.splice(idx, 1);\n this.config.onStatusChange?.();\n this.persist();\n } catch (err) {\n if (entry.abort.signal.aborted) continue; // cancelled, already removed\n\n // Session not yet reconnected — revert to 'queued' and stop flushing.\n // flush() will be called again when the session is re-established\n // via the explicit flush() call in use-chat.ts startSession().\n if (err instanceof ChatDisconnectedError) {\n entry.status = 'queued';\n this.config.onStatusChange?.();\n awaitingSession = true;\n break;\n }\n\n entry.status = 'failed';\n entry.error = err instanceof Error ? err : new Error(String(err));\n entry.retryCount++;\n entry.reject(entry.error);\n this.config.onStatusChange?.();\n this.persist();\n\n // Don't block the queue on a failed entry — skip to next\n }\n }\n } finally {\n this.flushing = false;\n // Re-check: entries may have been added while we were flushing.\n // Skip if we're waiting for session — startSession() will call flush().\n if (\n !awaitingSession &&\n this.entries.some((e) => e.status === 'queued') &&\n this.config.networkMonitor.isConnected()\n ) {\n queueMicrotask(() => this.flush());\n }\n }\n }\n\n private persist(): void {\n if (!this.config.storage) return;\n\n const data: PersistedEntry[] = this.entries.map((e) => ({\n optimisticId: e.optimisticId,\n retryCount: e.retryCount,\n }));\n\n const onErr = (err: unknown) => {\n this.config.onError?.(\n err instanceof Error ? err : new Error('Queue storage write failed'),\n );\n };\n\n if (data.length === 0) {\n this.config.storage.removeItem(this.storageKey).catch(onErr);\n } else {\n this.config.storage\n .setItem(this.storageKey, JSON.stringify(data))\n .catch(onErr);\n }\n }\n}\n","import { useEffect, useRef, useState, useCallback } from 'react';\nimport { AppState, Platform, type AppStateStatus } from 'react-native';\nimport type { UnreadCountResponse, SSEUnreadUpdateEvent } from '@pedi/chika-types';\nimport type { ChatConfig } from './types';\nimport { resolveServerUrl } from './resolve-url';\nimport { createSSEConnection, type SSEConnection } from './sse-connection';\nimport { createNetworkMonitor, type NetworkMonitor } from './network-monitor';\n\nconst DEFAULT_BACKGROUND_GRACE_MS = 2000;\nconst UNREAD_CUSTOM_EVENTS = ['unread_snapshot', 'unread_update', 'unread_clear'];\n\nexport interface UseUnreadOptions {\n config: ChatConfig;\n channelId: string;\n participantId: string;\n enabled?: boolean;\n}\n\nexport interface UseUnreadReturn {\n unreadCount: number;\n hasUnread: boolean;\n lastMessageAt: string | null;\n error: Error | null;\n}\n\nexport function useUnread(options: UseUnreadOptions): UseUnreadReturn {\n const { config, channelId, participantId, enabled = true } = options;\n\n const [unreadCount, setUnreadCount] = useState(0);\n const [lastMessageAt, setLastMessageAt] = useState<string | null>(null);\n const [error, setError] = useState<Error | null>(null);\n\n const connRef = useRef<SSEConnection | null>(null);\n const configRef = useRef(config);\n configRef.current = config;\n const appStateRef = useRef<AppStateStatus>(AppState.currentState);\n const backgroundTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n\n const backgroundGraceMs =\n config.backgroundGraceMs ?? (Platform.OS === 'android' ? DEFAULT_BACKGROUND_GRACE_MS : 0);\n\n const [monitorReady, setMonitorReady] = useState(false);\n const monitorRef = useRef<NetworkMonitor | null>(null);\n const resilienceEnabled = config.resilience !== false;\n const injectedMonitor =\n typeof config.resilience === 'object' ? config.resilience.networkMonitor : undefined;\n\n useEffect(() => {\n if (!resilienceEnabled) {\n monitorRef.current = null;\n setMonitorReady(true);\n return;\n }\n if (injectedMonitor) {\n monitorRef.current = injectedMonitor;\n setMonitorReady(true);\n return;\n }\n try {\n const m = createNetworkMonitor();\n monitorRef.current = m;\n setMonitorReady(true);\n return () => {\n m.dispose();\n monitorRef.current = null;\n };\n } catch {\n monitorRef.current = null;\n setMonitorReady(true);\n }\n }, [resilienceEnabled, injectedMonitor]);\n\n const connect = useCallback(() => {\n connRef.current?.close();\n connRef.current = null;\n\n const serviceUrl = resolveServerUrl(configRef.current.manifest, channelId);\n const customHeaders = configRef.current.headers ?? {};\n const url = `${serviceUrl}/channels/${channelId}/unread?participant_id=${encodeURIComponent(participantId)}`;\n\n connRef.current = createSSEConnection(\n {\n url,\n headers: customHeaders,\n reconnectDelayMs: configRef.current.reconnectDelayMs,\n customEvents: UNREAD_CUSTOM_EVENTS,\n networkMonitor: monitorRef.current ?? undefined,\n },\n {\n onOpen: () => {\n setError(null);\n },\n onEvent: (eventType, data) => {\n try {\n if (eventType === 'unread_snapshot') {\n const snapshot: UnreadCountResponse = JSON.parse(data);\n setUnreadCount(snapshot.unread_count);\n setLastMessageAt(snapshot.last_message_at);\n } else if (eventType === 'unread_update') {\n const update: SSEUnreadUpdateEvent['data'] = JSON.parse(data);\n setUnreadCount((prev) => prev + 1);\n setLastMessageAt(update.created_at);\n } else if (eventType === 'unread_clear') {\n const clear: { channel_id: string; unread_count: number } = JSON.parse(data);\n setUnreadCount(clear.unread_count);\n }\n } catch {\n setError(new Error('Failed to parse unread SSE event'));\n }\n },\n onError: (err) => {\n setError(err);\n },\n onClosed: () => {\n connRef.current = null;\n },\n },\n );\n }, [channelId, participantId]);\n\n const disconnect = useCallback(() => {\n connRef.current?.close();\n connRef.current = null;\n }, []);\n\n useEffect(() => {\n setUnreadCount(0);\n setLastMessageAt(null);\n setError(null);\n\n if (!enabled) {\n disconnect();\n return;\n }\n\n // Don't connect until monitor initialization is complete\n if (!monitorReady) return;\n\n connect();\n\n return () => {\n disconnect();\n if (backgroundTimerRef.current) {\n clearTimeout(backgroundTimerRef.current);\n backgroundTimerRef.current = null;\n }\n };\n }, [channelId, participantId, enabled, monitorReady, connect, disconnect]);\n\n useEffect(() => {\n if (!enabled) return;\n\n const subscription = AppState.addEventListener('change', (nextAppState) => {\n const prev = appStateRef.current;\n appStateRef.current = nextAppState;\n\n if (!connRef.current && nextAppState !== 'active') return;\n\n const shouldTeardown =\n nextAppState === 'background' ||\n (Platform.OS === 'ios' && nextAppState === 'inactive');\n\n if (nextAppState === 'active') {\n if (backgroundTimerRef.current) {\n clearTimeout(backgroundTimerRef.current);\n backgroundTimerRef.current = null;\n return;\n }\n\n if (prev.match(/inactive|background/) && !connRef.current) {\n connect();\n }\n } else if (shouldTeardown) {\n if (backgroundTimerRef.current) return;\n\n if (backgroundGraceMs === 0) {\n disconnect();\n } else {\n backgroundTimerRef.current = setTimeout(() => {\n backgroundTimerRef.current = null;\n disconnect();\n }, backgroundGraceMs);\n }\n }\n });\n\n return () => {\n subscription.remove();\n if (backgroundTimerRef.current) {\n clearTimeout(backgroundTimerRef.current);\n backgroundTimerRef.current = null;\n }\n };\n }, [enabled, backgroundGraceMs, connect, disconnect]);\n\n return { unreadCount, hasUnread: unreadCount > 0, lastMessageAt, error };\n}\n"],"mappings":"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","names":["DEFAULT_RECONNECT_DELAY_MS","useEffect","useRef","useState","useCallback","AppState","Platform","DEFAULT_BACKGROUND_GRACE_MS","useState","useRef","AppState","Platform","useEffect","useCallback"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pedi/chika-sdk",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.8",
|
|
4
4
|
"description": "React Native SDK for Pedi Chika chat service",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"typecheck": "tsc --noEmit"
|
|
34
34
|
},
|
|
35
35
|
"dependencies": {
|
|
36
|
-
"@pedi/chika-types": "^1.0.
|
|
36
|
+
"@pedi/chika-types": "^1.0.8",
|
|
37
37
|
"react-native-sse": "^1.2.1"
|
|
38
38
|
},
|
|
39
39
|
"peerDependencies": {
|