@antzsoft/chat-core 1.4.3 → 1.4.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +26 -3
- package/dist/{chunk-XTQYF5HU.js → chunk-WUNH3UTE.js} +92 -22
- package/dist/chunk-WUNH3UTE.js.map +1 -0
- package/dist/index.cjs +944 -781
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +40 -5
- package/dist/index.d.ts +40 -5
- package/dist/index.js +557 -454
- package/dist/index.js.map +1 -1
- package/dist/internal.cjs.map +1 -1
- package/dist/internal.d.cts +1 -1
- package/dist/internal.d.ts +1 -1
- package/dist/internal.js +1 -1
- package/dist/{storage-D7GPq-Mm.d.cts → storage-DlxfLVqx.d.cts} +43 -1
- package/dist/{storage-D7GPq-Mm.d.ts → storage-DlxfLVqx.d.ts} +43 -1
- package/docs/integration-guide.html +187 -3
- package/package.json +1 -1
- package/dist/chunk-XTQYF5HU.js.map +0 -1
package/dist/internal.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/internal.ts","../src/compression/compress.ts","../src/api/client.ts","../src/errors.ts","../src/crypto/uuid.ts","../src/api/storage.ts"],"sourcesContent":["// Internal exports for use by the web and RN SDKs only.\n// Not part of the public API — do not use in application code.\nexport { uploadBatchWithSlots } from './api/storage.js';\nexport { generateUUID } from './crypto/uuid.js';\n","import type { UploadableFile, CompressedFile, CompressionAlgorithm } from '../types/index.js';\nimport type { PlatformCompressFn, ResolvedCompressionConfig } from '../config/types.js';\n\n// MIME types that benefit from gzip (text-based, not already compressed)\nconst GZIP_MIME_TYPES = new Set([\n 'text/plain', 'text/csv', 'text/markdown', 'text/x-markdown',\n 'text/xml', 'application/xml', 'text/yaml', 'text/x-yaml',\n 'application/x-yaml', 'application/rtf', 'text/rtf',\n 'application/json', 'image/svg+xml',\n]);\n\nconst IMAGE_MIME_TYPES = new Set([\n 'image/jpeg', 'image/png', 'image/gif', 'image/webp', 'image/bmp', 'image/tiff',\n]);\n\n// Already-compressed formats — no gain from recompressing\nconst SKIP_MIME_TYPES = new Set([\n 'video/mp4', 'video/webm', 'video/quicktime',\n 'audio/mpeg', 'audio/wav', 'audio/ogg', 'audio/webm', 'audio/mp4',\n 'application/zip', 'application/pdf',\n 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',\n 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',\n 'application/vnd.openxmlformats-officedocument.presentationml.presentation',\n]);\n\nexport type CompressionStrategy = 'image' | 'gzip' | 'skip';\n\nexport function getCompressionStrategy(\n mimeType: string,\n config: ResolvedCompressionConfig,\n): CompressionStrategy {\n if (SKIP_MIME_TYPES.has(mimeType)) return 'skip';\n if (IMAGE_MIME_TYPES.has(mimeType)) return 'image';\n if (config.compressDocuments && GZIP_MIME_TYPES.has(mimeType)) return 'gzip';\n return 'skip';\n}\n\n/**\n * Attempt to compress a file using the platform-provided compressor.\n * Returns the original file unchanged (as a CompressedFile with compressed=false)\n * if compression is disabled, no compressor is provided, or the strategy is 'skip'.\n */\nexport async function compressFile(\n file: UploadableFile,\n platformCompressFn: PlatformCompressFn | undefined,\n config: ResolvedCompressionConfig,\n): Promise<CompressedFile> {\n const noop: CompressedFile = {\n ...file,\n originalSize: file.size,\n compressed: false,\n compressionAlgorithm: 'none' as CompressionAlgorithm,\n };\n\n if (!config.enabled || !platformCompressFn) return noop;\n\n const strategy = getCompressionStrategy(file.type, config);\n if (strategy === 'skip') return noop;\n\n try {\n return await platformCompressFn(file, config);\n } catch {\n // Compression failure is non-fatal — fall back to original\n return noop;\n }\n}\n","import axios, {\n AxiosInstance,\n InternalAxiosRequestConfig,\n} from 'axios';\nimport type { ResolvedConfig } from '../config/types.js';\nimport type { AuthTokens } from '../types/index.js';\nimport { encryptPayload, decryptPayload, isTransitEnvelope } from '../crypto/transit.js';\nimport { getSessionKey, getSessionId, isTransitEnabled, waitForTransitReady, configureTransit, setTransitSession, getTransitSession } from '../crypto/session.js';\nimport { createRestTransitSession } from '../crypto/handshake.js';\nimport { detectTransitAlgo } from '../crypto/detect.js';\nimport { normalizeAxiosError } from '../errors.js';\n\nexport type TokenStore = {\n getAccessToken: () => string | null | undefined;\n getRefreshToken: () => string | null | undefined;\n setTokens: (tokens: AuthTokens) => void;\n clearTokens: () => void;\n};\n\nlet _tokenStore: TokenStore | null = null;\nlet _config: ResolvedConfig | null = null;\nlet _avatarSent = false;\n// In-flight transit handshake promise — shared between initApiClient and connectSocket\n// so they never fire two concurrent HTTPS handshakes for the same session.\nlet _transitHandshakePromise: Promise<void> | null = null;\n\nexport function getTransitHandshakePromise(): Promise<void> | null {\n return _transitHandshakePromise;\n}\n\nexport function initApiClient(config: ResolvedConfig, tokenStore: TokenStore): AxiosInstance {\n _config = config;\n _tokenStore = tokenStore;\n _avatarSent = false; // reset on re-init (new session / authToken change)\n\n const client = axios.create({\n baseURL: config.apiUrl,\n headers: { 'Content-Type': 'application/json' },\n });\n\n // Configure transit as early as possible — before any requests fire —\n // so waitForTransitReady() in the interceptor knows whether to block or not.\n configureTransit(config.transitEncryption);\n\n // Kick off the HTTPS transit handshake immediately so REST calls that fire\n // before connectSocket (e.g. getMe() right after initApiClient) are not\n // blocked indefinitely. Store the promise so connectSocket can await it\n // instead of firing a duplicate handshake.\n if (config.transitEncryption && !getTransitSession() && !_transitHandshakePromise) {\n _transitHandshakePromise = (async () => {\n try {\n const session = await createRestTransitSession(config.apiUrl);\n if (session && !getTransitSession()) {\n const algo = typeof globalThis.crypto?.subtle !== 'undefined'\n ? await detectTransitAlgo()\n : 'x25519';\n setTransitSession({ sessionKey: session.sessionKey as CryptoKey, algo, sessionId: session.sessionId, enabled: true });\n } else if (!session) {\n configureTransit(false);\n }\n } catch {\n configureTransit(false);\n } finally {\n _transitHandshakePromise = null;\n }\n })();\n }\n\n // ── Request interceptor ──────────────────────────────────────────────────\n client.interceptors.request.use(async (req: InternalAxiosRequestConfig) => {\n const token = _tokenStore?.getAccessToken();\n if (token) req.headers['Authorization'] = `Bearer ${token}`;\n if (_config?.userId) req.headers['x-user-id'] = _config.userId;\n if (_config?.tenantId) req.headers['X-Tenant-ID'] = _config.tenantId;\n // Send avatar on the first request only — server hashes and deduplicates\n if (token && !_avatarSent && _config?.avatar) {\n if (_config.avatar.base64) req.headers['x-avatar-base64'] = _config.avatar.base64;\n else if (_config.avatar.url) req.headers['x-avatar-url'] = _config.avatar.url;\n _avatarSent = true;\n }\n\n // Wait for the transit session key before sending authenticated requests.\n // Unauthenticated requests (no token) fire before login/socket connect —\n // they can never have a transit session so never block.\n if (token) await waitForTransitReady();\n\n if (isTransitEnabled()) {\n const sessionId = getSessionId();\n const key = getSessionKey();\n if (sessionId && key) {\n req.headers['x-transit-session'] = sessionId;\n if (req.data !== undefined && req.data !== null) {\n const envelope = await encryptPayload(req.data, key);\n req.data = envelope;\n req.headers['x-transit-encrypted'] = '1';\n }\n }\n }\n\n return req;\n });\n\n let isRefreshing = false;\n let refreshQueue: Array<(token: string) => void> = [];\n\n // ── Response interceptor ─────────────────────────────────────────────────\n client.interceptors.response.use(\n async (response) => {\n // Transit decryption — server wraps encrypted payload inside { success, data: <envelope> }.\n // Decrypt the inner envelope, then let the standard unwrap below handle { success, data }.\n if (isTransitEnabled()) {\n const key = getSessionKey();\n if (key) {\n // Case 1: entire response is the envelope (unlikely but handle it)\n if (isTransitEnvelope(response.data)) {\n response.data = await decryptPayload(response.data, key);\n }\n // Case 2: envelope is nested inside { success, data: <envelope> }\n else if (response.data?.data && isTransitEnvelope(response.data.data)) {\n response.data.data = await decryptPayload(response.data.data, key);\n }\n }\n }\n\n // Standard { success, data } unwrap\n if (\n response.data &&\n typeof response.data === 'object' &&\n 'success' in response.data &&\n 'data' in response.data\n ) {\n response.data = response.data.data;\n }\n return response;\n },\n async (error) => {\n // Decrypt error response body — error filter encrypts it too\n if (isTransitEnabled() && error.response?.data) {\n const key = getSessionKey();\n if (key) {\n try {\n if (isTransitEnvelope(error.response.data)) {\n error.response.data = await decryptPayload(error.response.data, key);\n } else if (error.response.data?.data && isTransitEnvelope(error.response.data.data)) {\n error.response.data.data = await decryptPayload(error.response.data.data, key);\n }\n } catch { /* decryption failed — leave as-is */ }\n }\n }\n\n const original = error.config as InternalAxiosRequestConfig & { _retry?: boolean };\n\n if (error.response?.status === 401 && !original._retry) {\n const refreshToken = _tokenStore?.getRefreshToken();\n if (!refreshToken) {\n _tokenStore?.clearTokens();\n return Promise.reject(normalizeAxiosError(error));\n }\n\n if (isRefreshing) {\n return new Promise((resolve) => {\n refreshQueue.push((newToken) => {\n original.headers['Authorization'] = `Bearer ${newToken}`;\n resolve(client(original));\n });\n });\n }\n\n original._retry = true;\n isRefreshing = true;\n\n try {\n const { data } = await axios.post<{ data: AuthTokens }>(\n `${_config!.apiUrl}/auth/refresh`,\n { refreshToken },\n );\n const tokens: AuthTokens = (data as any).data ?? data;\n _tokenStore?.setTokens(tokens);\n refreshQueue.forEach((cb) => cb(tokens.accessToken));\n refreshQueue = [];\n original.headers['Authorization'] = `Bearer ${tokens.accessToken}`;\n return client(original);\n } catch {\n _tokenStore?.clearTokens();\n return Promise.reject(normalizeAxiosError(error));\n } finally {\n isRefreshing = false;\n }\n }\n\n return Promise.reject(normalizeAxiosError(error));\n },\n );\n\n return client;\n}\n\nlet _instance: AxiosInstance | null = null;\n\nexport function setApiClientInstance(instance: AxiosInstance) {\n _instance = instance;\n}\n\nexport function getApiClient(): AxiosInstance {\n if (!_instance) throw new Error('[AntzChat] API client not initialized. Call initApiClient first.');\n return _instance;\n}\n","import { isAxiosError } from 'axios';\nimport { isTransitEnvelope } from './crypto/transit.js';\n\n// ─── Base error class ─────────────────────────────────────────────────────────\n\nexport class AntzChatError extends Error {\n readonly code: string;\n readonly retryable: boolean;\n readonly context?: Record<string, unknown>;\n\n constructor(\n code: string,\n message: string,\n retryable = false,\n context?: Record<string, unknown>,\n ) {\n super(message);\n this.name = 'AntzChatError';\n this.code = code;\n this.retryable = retryable;\n this.context = context;\n // Maintain proper prototype chain in transpiled environments\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\n// ─── Semantic subclasses ──────────────────────────────────────────────────────\n\n/** Thrown on 401 (after refresh also fails) or when no refresh token exists. */\nexport class AntzChatAuthError extends AntzChatError {\n constructor(message: string, code = 'AUTH_FAILED', context?: Record<string, unknown>) {\n super(code, message, false, context);\n this.name = 'AntzChatAuthError';\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\n/** Thrown on 400 / 422 — bad input, validation failure. */\nexport class AntzChatValidationError extends AntzChatError {\n /** Server-returned field error array (when the server sends message as string[]). */\n readonly fields?: string[];\n\n constructor(message: string | string[], context?: Record<string, unknown>) {\n const msg = Array.isArray(message) ? message.join('; ') : message;\n super('VALIDATION_ERROR', msg, false, context);\n this.name = 'AntzChatValidationError';\n this.fields = Array.isArray(message) ? message : undefined;\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\n/** Thrown on network failures, timeouts, socket disconnections, and queue overflow. retryable = true. */\nexport class AntzChatNetworkError extends AntzChatError {\n constructor(message: string, code = 'NETWORK_ERROR', context?: Record<string, unknown>) {\n super(code, message, true, context);\n this.name = 'AntzChatNetworkError';\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\n/** Thrown on 403 — insufficient permissions. */\nexport class AntzChatPermissionError extends AntzChatError {\n constructor(message: string, context?: Record<string, unknown>) {\n super('PERMISSION_DENIED', message, false, context);\n this.name = 'AntzChatPermissionError';\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\n/** Thrown on 5xx or other unexpected server errors. retryable = true. */\nexport class AntzChatServerError extends AntzChatError {\n readonly httpStatus?: number;\n\n constructor(message: string, httpStatus?: number, context?: Record<string, unknown>) {\n super('SERVER_ERROR', message, true, context);\n this.name = 'AntzChatServerError';\n this.httpStatus = httpStatus;\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\n// ─── Error code reference ─────────────────────────────────────────────────────\n//\n// Code Class Source\n// ───────────────────── ──────────────────────── ─────────────────────────\n// AUTH_FAILED AntzChatAuthError 401 after refresh fails\n// SESSION_EXPIRED AntzChatAuthError 401, no refresh token\n// PERMISSION_DENIED AntzChatPermissionError 403\n// VALIDATION_ERROR AntzChatValidationError 400 / 422\n// NOT_FOUND AntzChatServerError 404\n// RATE_LIMITED AntzChatNetworkError 429\n// NETWORK_ERROR AntzChatNetworkError No response / conn failure\n// SOCKET_TIMEOUT AntzChatNetworkError ACK timeout / reconnect timeout\n// SOCKET_NOT_CONNECTED AntzChatNetworkError withAck when socket is down\n// SEND_QUEUE_FULL AntzChatNetworkError Queue overflow (>100 msgs)\n// MESSAGE_DROPPED AntzChatNetworkError Queue TTL expired (30s)\n// TRANSIT_MISMATCH AntzChatError SDK/server encryption config mismatch\n// SERVER_ERROR AntzChatServerError 5xx or unknown HTTP error\n\n// ─── REST error normaliser ────────────────────────────────────────────────────\n\n/**\n * Converts a raw axios error (or any unknown throw) into a typed AntzChatError.\n *\n * Call site: client.ts response interceptor — runs AFTER transit decryption,\n * so error.response.data is always plaintext by the time this function sees it.\n * If decryption itself failed, error.response.data remains the raw encrypted\n * envelope — detected via isTransitEnvelope() and noted in context.\n */\nexport function normalizeAxiosError(error: unknown): AntzChatError {\n if (error instanceof AntzChatError) return error;\n\n if (isAxiosError(error)) {\n const status = error.response?.status;\n const body = error.response?.data;\n\n // Detect if decryption failed — body is still an encrypted envelope\n const decryptionFailed = body != null && isTransitEnvelope(body);\n\n const rawMessage: string | string[] | undefined = decryptionFailed\n ? undefined\n : (body?.message ?? undefined);\n\n const message: string =\n (Array.isArray(rawMessage) ? rawMessage.join('; ') : rawMessage) ||\n error.message ||\n 'Request failed';\n\n const ctx: Record<string, unknown> = {\n ...(status != null && { httpStatus: status }),\n ...(body?.path != null && { path: body.path }),\n ...(body?.error != null && { serverError: body.error }),\n ...(error.code != null && { axiosCode: error.code }),\n ...(decryptionFailed && { decryptionFailed: true, note: 'Transit decryption failed — server error body is an encrypted envelope' }),\n };\n\n // No response at all — network/timeout failure\n if (!error.response) {\n return new AntzChatNetworkError(message || 'Network error', 'NETWORK_ERROR', ctx);\n }\n\n if (status === 401) {\n // AUTH_FAILED is used when a refresh was attempted but failed (set by interceptor).\n // SESSION_EXPIRED is the default: 401 with no prior retry = token simply expired.\n const code = (error.config as any)?._retry ? 'AUTH_FAILED' : 'SESSION_EXPIRED';\n return new AntzChatAuthError(message, code, ctx);\n }\n if (status === 403) return new AntzChatPermissionError(message, ctx);\n if (status === 400 || status === 422) {\n return new AntzChatValidationError(\n Array.isArray(rawMessage) ? rawMessage : message,\n ctx,\n );\n }\n if (status === 404) return new AntzChatServerError(message, 404, ctx);\n if (status === 429) return new AntzChatNetworkError(message, 'RATE_LIMITED', ctx);\n if (status != null && status >= 500) return new AntzChatServerError(message, status, ctx);\n\n return new AntzChatServerError(message, status, ctx);\n }\n\n const msg = error instanceof Error ? error.message : String(error);\n return new AntzChatError('UNKNOWN_ERROR', msg, false);\n}\n","// crypto.randomUUID() doesn't exist in React Native's Hermes engine.\n// Fall back to a RFC 4122 v4 UUID built from Math.random() when unavailable.\nexport function generateUUID(): string {\n if (typeof globalThis.crypto?.randomUUID === 'function') {\n return globalThis.crypto.randomUUID();\n }\n return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {\n const r = (Math.random() * 16) | 0;\n return (c === 'x' ? r : (r & 0x3) | 0x8).toString(16);\n });\n}\n","import type {\n BatchUploadResult,\n FileResponse,\n PaginatedResponse,\n PresignedUrlRequest,\n PresignedUrlResponse,\n FileType,\n UploadableFile,\n CompletedPart,\n} from '../types/index.js';\nimport type { PlatformUploadFn, PlatformCompressFn, PlatformUploadPartFn, ResolvedCompressionConfig } from '../config/types.js';\nimport { compressFile } from '../compression/compress.js';\nimport { getApiClient } from './client.js';\nimport { generateUUID } from '../crypto/uuid.js';\n\nexport const storageApi = {\n async requestPresignedUrl(payload: PresignedUrlRequest): Promise<PresignedUrlResponse> {\n const { data } = await getApiClient().post<PresignedUrlResponse>('/storage/presigned-url', payload);\n return data;\n },\n\n async requestPresignedUrlBatch(files: PresignedUrlRequest[]): Promise<{\n urls: PresignedUrlResponse[];\n errors: Array<{ filename: string; error: string; clientIndex?: number }>;\n }> {\n const { data } = await getApiClient().post('/storage/presigned-url/batch', { files });\n return data;\n },\n\n async confirmUpload(fileId: string): Promise<FileResponse> {\n const { data } = await getApiClient().post<FileResponse>(`/storage/confirm/${fileId}`);\n return data;\n },\n\n async getFile(fileId: string): Promise<FileResponse> {\n const { data } = await getApiClient().get<FileResponse>(`/storage/files/${fileId}`);\n return data;\n },\n\n async getFileUrl(fileId: string, expiresIn?: number): Promise<{ url: string; expiresAt: string }> {\n const { data } = await getApiClient().get(`/storage/files/${fileId}/url`, {\n params: expiresIn ? { expiresIn } : {},\n });\n return data;\n },\n\n async deleteFile(fileId: string): Promise<void> {\n await getApiClient().post(`/storage/files/${fileId}/delete`);\n },\n\n async completeMultipartUpload(\n fileId: string,\n uploadId: string,\n parts: CompletedPart[],\n ): Promise<FileResponse> {\n const { data } = await getApiClient().post<FileResponse>(\n `/storage/multipart/complete/${fileId}`,\n { uploadId, parts },\n );\n return data;\n },\n\n async getConversationFiles(\n conversationId: string,\n params: { page?: number; limit?: number; type?: FileType } = {},\n ): Promise<PaginatedResponse<FileResponse>> {\n const { data } = await getApiClient().get(\n `/storage/conversations/${conversationId}/files`,\n { params },\n );\n return data;\n },\n\n async getMyFiles(params: { page?: number; limit?: number } = {}): Promise<PaginatedResponse<FileResponse>> {\n const { data } = await getApiClient().get('/storage/my-files', { params });\n return data;\n },\n};\n\nasync function runMultipartUpload(\n presigned: PresignedUrlResponse,\n file: UploadableFile,\n platformUploadPartFn: PlatformUploadPartFn,\n onProgress?: (pct: number) => void,\n): Promise<FileResponse> {\n const { multipart } = presigned;\n if (!multipart) throw new Error('No multipart info on presigned response');\n\n const CONCURRENCY = 3;\n const completedParts: CompletedPart[] = [];\n const partProgress: Record<number, number> = {};\n\n multipart.partUrls.forEach(({ partNumber }) => { partProgress[partNumber] = 0; });\n\n const reportProgress = () => {\n if (!onProgress) return;\n const vals = Object.values(partProgress);\n const avg = vals.reduce((s, v) => s + v, 0) / Math.max(vals.length, 1);\n onProgress(Math.round(avg * 0.95));\n };\n\n const uploadPart = async (partNumber: number, uploadUrl: string, method: 'PUT' | 'POST'): Promise<void> => {\n const offset = (partNumber - 1) * multipart.chunkSize;\n const end = Math.min(offset + multipart.chunkSize, file.size);\n const blob = await fetch(file.uri).then((r) => r.blob());\n const slice = blob.slice(offset, end);\n\n const etag = await platformUploadPartFn(uploadUrl, slice, (pct) => {\n partProgress[partNumber] = pct;\n reportProgress();\n }, method);\n\n completedParts.push({ partNumber, etag });\n partProgress[partNumber] = 100;\n reportProgress();\n };\n\n for (let i = 0; i < multipart.partUrls.length; i += CONCURRENCY) {\n const batch = multipart.partUrls.slice(i, i + CONCURRENCY);\n const results = await Promise.allSettled(\n batch.map(({ partNumber, uploadUrl, method }) => uploadPart(partNumber, uploadUrl, method ?? 'PUT')),\n );\n const failed = results.find((r) => r.status === 'rejected') as PromiseRejectedResult | undefined;\n if (failed) throw failed.reason;\n }\n\n completedParts.sort((a, b) => a.partNumber - b.partNumber);\n\n const fileResponse = await storageApi.completeMultipartUpload(\n presigned.fileId,\n multipart.uploadId,\n completedParts,\n );\n onProgress?.(100);\n return fileResponse;\n}\n\n/**\n * Core upload implementation. Returns the public BatchUploadResult plus a\n * slotId → FileResponse map that useChat hooks use internally to match\n * confirmed uploads back to optimistic UI slots by position rather than\n * filename. The slotToFile map is never part of the public API.\n */\nasync function runUploadBatch(\n files: UploadableFile[],\n platformUploadFn: PlatformUploadFn,\n slotIds: string[],\n conversationId?: string,\n onProgress?: (pct: number) => void,\n platformCompressFn?: PlatformCompressFn,\n compressionConfig?: ResolvedCompressionConfig,\n platformUploadPartFn?: PlatformUploadPartFn,\n): Promise<{ result: BatchUploadResult; slotToFile: Map<string, FileResponse> }> {\n // Compress all files first (no-ops for unsupported types or when disabled)\n const compressedFiles = await Promise.all(\n files.map((f) => compressFile(f, platformCompressFn, compressionConfig ?? { enabled: false, imageQuality: 0.85, imageMaxDimension: 1920, compressDocuments: true })),\n );\n\n // Pair each compressed file with its slot ID and a clientIndex.\n // clientIndex is sent to the server and echoed back in both urls and errors,\n // giving us a reliable position mapping regardless of which files fail.\n const slotted = compressedFiles.map((f, i) => ({ file: f, slotId: slotIds[i], clientIndex: i }));\n\n const requests: PresignedUrlRequest[] = slotted.map(({ file: f, clientIndex }) => ({\n filename: f.name,\n mimeType: f.type,\n size: f.size,\n conversationId,\n clientIndex,\n ...(f.compressed && {\n metadata: {\n compressed: f.compressed,\n originalSize: f.originalSize,\n compressionAlgorithm: f.compressionAlgorithm,\n },\n }),\n }));\n\n const { urls, errors: requestErrors } = await storageApi.requestPresignedUrlBatch(requests);\n\n // Use the echoed clientIndex to identify which original slots failed.\n // This is reliable even for same-named files and any failure pattern.\n const failedSlotIds = new Set<string>();\n const failed: Array<{ filename: string; error: string }> = requestErrors.map((e) => {\n const idx = e.clientIndex ?? slotted.findIndex((s) => s.file.name === e.filename);\n const slotId = slotted[idx]?.slotId;\n if (slotId) failedSlotIds.add(slotId);\n return { filename: e.filename, error: e.error };\n });\n\n // Map each presigned URL back to its original slot via clientIndex.\n const progressMap: Record<number, number> = {};\n const reportProgress = () => {\n if (!onProgress) return;\n const vals = Object.values(progressMap);\n const avg = vals.reduce((s, v) => s + v, 0) / Math.max(vals.length, 1);\n onProgress(Math.round(avg));\n };\n\n const successful: FileResponse[] = [];\n const slotToFile = new Map<string, FileResponse>();\n\n await Promise.all(\n urls.map(async (presigned, idx) => {\n // Resolve the original slot via echoed clientIndex; fall back to position\n // in urls[] only if the server didn't echo it (older server version).\n const originalIdx = presigned.clientIndex ?? idx;\n const { file, slotId } = slotted[originalIdx];\n progressMap[originalIdx] = 0;\n try {\n let fileResponse: FileResponse;\n if (presigned.multipart && platformUploadPartFn) {\n fileResponse = await runMultipartUpload(presigned, file, platformUploadPartFn, (pct) => {\n progressMap[originalIdx] = pct;\n reportProgress();\n });\n } else {\n await platformUploadFn(presigned, file, (pct) => {\n progressMap[originalIdx] = Math.round(pct * 0.9);\n reportProgress();\n });\n fileResponse = await storageApi.confirmUpload(presigned.fileId);\n }\n progressMap[originalIdx] = 100;\n reportProgress();\n successful.push(fileResponse);\n slotToFile.set(slotId, fileResponse);\n } catch (err) {\n failed.push({ filename: file.name, error: (err as Error).message });\n }\n }),\n );\n\n return { result: { successful, failed }, slotToFile };\n}\n\n/** Public API — returns standard BatchUploadResult, slot tracking is internal. */\nexport async function uploadBatch(\n files: UploadableFile[],\n platformUploadFn: PlatformUploadFn,\n conversationId?: string,\n onProgress?: (pct: number) => void,\n platformCompressFn?: PlatformCompressFn,\n compressionConfig?: ResolvedCompressionConfig,\n platformUploadPartFn?: PlatformUploadPartFn,\n): Promise<BatchUploadResult> {\n const slotIds = files.map(() => generateUUID());\n const { result } = await runUploadBatch(files, platformUploadFn, slotIds, conversationId, onProgress, platformCompressFn, compressionConfig, platformUploadPartFn);\n return result;\n}\n\n/**\n * Used only by useChat hooks (web + RN) to get the slotId → FileResponse map\n * for matching confirmed uploads back to optimistic UI slots.\n * Not exported from the package index — internal SDK use only.\n */\nexport async function uploadBatchWithSlots(\n files: UploadableFile[],\n platformUploadFn: PlatformUploadFn,\n slotIds: string[],\n conversationId?: string,\n onProgress?: (pct: number) => void,\n platformCompressFn?: PlatformCompressFn,\n compressionConfig?: ResolvedCompressionConfig,\n platformUploadPartFn?: PlatformUploadPartFn,\n): Promise<{ result: BatchUploadResult; slotToFile: Map<string, FileResponse> }> {\n return runUploadBatch(files, platformUploadFn, slotIds, conversationId, onProgress, platformCompressFn, compressionConfig, platformUploadPartFn);\n}\n"],"mappings":"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","names":["import_axios"]}
|
|
1
|
+
{"version":3,"sources":["../src/internal.ts","../src/compression/compress.ts","../src/api/client.ts","../src/errors.ts","../src/crypto/uuid.ts","../src/api/storage.ts"],"sourcesContent":["// Internal exports for use by the web and RN SDKs only.\n// Not part of the public API — do not use in application code.\nexport { uploadBatchWithSlots } from './api/storage.js';\nexport { generateUUID } from './crypto/uuid.js';\n","import type { UploadableFile, CompressedFile, CompressionAlgorithm } from '../types/index.js';\nimport type { PlatformCompressFn, ResolvedCompressionConfig } from '../config/types.js';\n\n// MIME types that benefit from gzip (text-based, not already compressed)\nconst GZIP_MIME_TYPES = new Set([\n 'text/plain', 'text/csv', 'text/markdown', 'text/x-markdown',\n 'text/xml', 'application/xml', 'text/yaml', 'text/x-yaml',\n 'application/x-yaml', 'application/rtf', 'text/rtf',\n 'application/json', 'image/svg+xml',\n]);\n\nconst IMAGE_MIME_TYPES = new Set([\n 'image/jpeg', 'image/png', 'image/gif', 'image/webp', 'image/bmp', 'image/tiff',\n]);\n\n// Already-compressed formats — no gain from recompressing\nconst SKIP_MIME_TYPES = new Set([\n 'video/mp4', 'video/webm', 'video/quicktime',\n 'audio/mpeg', 'audio/wav', 'audio/ogg', 'audio/webm', 'audio/mp4',\n 'application/zip', 'application/pdf',\n 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',\n 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',\n 'application/vnd.openxmlformats-officedocument.presentationml.presentation',\n]);\n\nexport type CompressionStrategy = 'image' | 'gzip' | 'skip';\n\nexport function getCompressionStrategy(\n mimeType: string,\n config: ResolvedCompressionConfig,\n): CompressionStrategy {\n if (SKIP_MIME_TYPES.has(mimeType)) return 'skip';\n if (IMAGE_MIME_TYPES.has(mimeType)) return 'image';\n if (config.compressDocuments && GZIP_MIME_TYPES.has(mimeType)) return 'gzip';\n return 'skip';\n}\n\n/**\n * Attempt to compress a file using the platform-provided compressor.\n * Returns the original file unchanged (as a CompressedFile with compressed=false)\n * if compression is disabled, no compressor is provided, or the strategy is 'skip'.\n */\nexport async function compressFile(\n file: UploadableFile,\n platformCompressFn: PlatformCompressFn | undefined,\n config: ResolvedCompressionConfig,\n): Promise<CompressedFile> {\n const noop: CompressedFile = {\n ...file,\n originalSize: file.size,\n compressed: false,\n compressionAlgorithm: 'none' as CompressionAlgorithm,\n };\n\n if (!config.enabled || !platformCompressFn) return noop;\n\n const strategy = getCompressionStrategy(file.type, config);\n if (strategy === 'skip') return noop;\n\n try {\n return await platformCompressFn(file, config);\n } catch {\n // Compression failure is non-fatal — fall back to original\n return noop;\n }\n}\n","import axios, {\n AxiosInstance,\n InternalAxiosRequestConfig,\n} from 'axios';\nimport type { ResolvedConfig } from '../config/types.js';\nimport type { AuthTokens } from '../types/index.js';\nimport { encryptPayload, decryptPayload, isTransitEnvelope } from '../crypto/transit.js';\nimport { getSessionKey, getSessionId, isTransitEnabled, awaitTransitReadyOr, configureTransit, setTransitSession, getTransitSession } from '../crypto/session.js';\nimport { createRestTransitSession, fetchServerKeys } from '../crypto/handshake.js';\nimport { detectTransitAlgo } from '../crypto/detect.js';\nimport { normalizeAxiosError, AntzChatNetworkError } from '../errors.js';\n\n// Hard ceiling on how long a single request will block waiting for the transit\n// handshake. A legitimate cold-start handshake resolves in well under this even\n// on a slow link (TransitGate already spent ~6s, establishTransit keeps\n// retrying). Past this we FAIL the request with a retryable error rather than\n// leave it pending forever — react-query cannot retry / refetch-on-focus a\n// request that never settles, so an unbounded wait here is an unrecoverable\n// silent hang. Each failed+retried request also re-arms ensureRestTransitHandshake.\nconst TRANSIT_GATE_MAX_WAIT_MS = 30_000;\n\nexport type TokenStore = {\n getAccessToken: () => string | null | undefined;\n getRefreshToken: () => string | null | undefined;\n setTokens: (tokens: AuthTokens) => void;\n clearTokens: () => void;\n};\n\nlet _tokenStore: TokenStore | null = null;\nlet _config: ResolvedConfig | null = null;\nlet _avatarSent = false;\n// In-flight transit handshake promise — shared between initApiClient and connectSocket\n// so they never fire two concurrent HTTPS handshakes for the same session.\nlet _transitHandshakePromise: Promise<void> | null = null;\n// Gate the request interceptor until the SDK has resolved a token (async\n// authProvider) and, where wired, the transit handshake. Set by the SDK\n// provider; the interceptor awaits it before attaching the Authorization\n// header so early requests (e.g. useConversations' initial fetch) don't race\n// ahead unauthenticated.\nlet _authReadyPromise: Promise<unknown> | null = null;\n\nexport function getTransitHandshakePromise(): Promise<void> | null {\n return _transitHandshakePromise;\n}\n\nexport function setAuthReadyPromise(promise: Promise<unknown> | null): void {\n _authReadyPromise = promise;\n}\n\n// True once initApiClient() has run and its config has not been torn down by a\n// subsequent disconnectSocket(). Lets the SDK provider detect the case where a\n// React remount skipped re-init (its key was unchanged) but disconnectSocket()\n// had nulled _config in between — leaving the request interceptor unable to see\n// transitEncryption and firing every request as unencrypted plaintext.\nexport function isApiClientConfigured(): boolean {\n return _config !== null;\n}\n\n// (Re-)kick the HTTPS transit handshake. Idempotent: no-ops when transit is\n// disabled, a session already exists, or an attempt is already in flight.\n// Called both at init and from the request interceptor when a request is about\n// to block on waitForTransitReady() with no session — e.g. after a socket\n// disconnect cleared the session and nothing else re-established it.\n//\n// It only calls configureTransit(false) — which un-gates the interceptor and\n// lets requests go out as PLAINTEXT — when the server itself reports transit\n// disabled (GET /crypto/pubkey → enabled:false, i.e. an old server). A transient\n// failure of POST /crypto/session (network blip, rate limit, 5xx) must NOT\n// disable transit: the server still requires it, so plaintext would just 403.\n// Instead we retry with backoff; waitForTransitReady() keeps requests pending\n// and they dispatch the moment a retry sets the session.\nexport function ensureRestTransitHandshake(): void {\n if (!_config?.transitEncryption || getTransitSession() || _transitHandshakePromise) return;\n const apiUrl = _config.apiUrl;\n _transitHandshakePromise = (async () => {\n try {\n for (let attempt = 0; attempt < 5; attempt++) {\n if (getTransitSession()) return;\n try {\n const keys = await fetchServerKeys(apiUrl);\n if (!keys?.enabled) {\n configureTransit(false); // server genuinely doesn't want transit\n return;\n }\n const session = await createRestTransitSession(apiUrl);\n if (session && !getTransitSession()) {\n const algo = typeof globalThis.crypto?.subtle !== 'undefined'\n ? await detectTransitAlgo()\n : 'x25519';\n setTransitSession({ sessionKey: session.sessionKey as CryptoKey, algo, sessionId: session.sessionId, enabled: true });\n return;\n }\n } catch {\n /* transient — fall through to backoff */\n }\n await new Promise((r) => setTimeout(r, Math.min(500 * 2 ** attempt, 8_000)));\n }\n console.error(\n '[AntzChat] transit handshake could not establish a session after 5 attempts — ' +\n \"chat requests stay gated until one succeeds (server requires transit).\",\n );\n } finally {\n _transitHandshakePromise = null;\n }\n })();\n}\n\nexport function initApiClient(config: ResolvedConfig, tokenStore: TokenStore): AxiosInstance {\n _config = config;\n _tokenStore = tokenStore;\n _avatarSent = false; // reset on re-init (new session / authToken change)\n\n const client = axios.create({\n baseURL: config.apiUrl,\n headers: { 'Content-Type': 'application/json' },\n });\n\n // Configure transit as early as possible — before any requests fire —\n // so waitForTransitReady() in the interceptor knows whether to block or not.\n configureTransit(config.transitEncryption);\n\n // Kick off the HTTPS transit handshake immediately so REST calls that fire\n // before connectSocket (e.g. getMe() right after initApiClient) are not\n // blocked indefinitely. Store the promise so connectSocket can await it\n // instead of firing a duplicate handshake.\n ensureRestTransitHandshake();\n\n // ── Request interceptor ──────────────────────────────────────────────────\n client.interceptors.request.use(async (req: InternalAxiosRequestConfig) => {\n // Wait for the SDK's auth (and, where wired, transit) gate before reading\n // the token — otherwise a request fired during boot goes out with no\n // Authorization header.\n if (_authReadyPromise) await _authReadyPromise;\n\n const token = _tokenStore?.getAccessToken();\n if (token) req.headers['Authorization'] = `Bearer ${token}`;\n if (_config?.userId) req.headers['x-user-id'] = _config.userId;\n if (_config?.tenantId) req.headers['X-Tenant-ID'] = _config.tenantId;\n // Send avatar on the first request only — server hashes and deduplicates\n if (token && !_avatarSent && _config?.avatar) {\n if (_config.avatar.base64) req.headers['x-avatar-base64'] = _config.avatar.base64;\n else if (_config.avatar.url) req.headers['x-avatar-url'] = _config.avatar.url;\n _avatarSent = true;\n }\n\n // Wait for the transit session key before sending any request. The server\n // enforces transit encryption independent of auth (e.g. GET /app/config\n // fires before the async authProvider token resolves) — gating this on\n // `token` let those pre-auth requests race ahead of the handshake and get\n // rejected with 403 \"Transit encryption required\".\n if (_config?.transitEncryption) {\n // If the session is gone (socket disconnect cleared it, first boot still\n // pending), make sure a handshake is running before we block — otherwise\n // the wait could hang with nothing to resolve it.\n if (!getTransitSession()) ensureRestTransitHandshake();\n const ready = await awaitTransitReadyOr(TRANSIT_GATE_MAX_WAIT_MS);\n if (!ready) {\n // Handshake still hasn't produced a session. Do NOT send plaintext\n // (server requires transit); fail loudly instead so the error surfaces\n // in the UI and react-query's retry re-drives the handshake.\n throw new AntzChatNetworkError(\n 'Secure channel to chat server not established — request not sent. It will retry automatically.',\n 'TRANSIT_NOT_READY',\n { url: req.url },\n );\n }\n }\n\n if (isTransitEnabled()) {\n const sessionId = getSessionId();\n const key = getSessionKey();\n if (sessionId && key) {\n req.headers['x-transit-session'] = sessionId;\n if (req.data !== undefined && req.data !== null) {\n const envelope = await encryptPayload(req.data, key);\n req.data = envelope;\n req.headers['x-transit-encrypted'] = '1';\n }\n }\n }\n\n return req;\n });\n\n let isRefreshing = false;\n let refreshQueue: Array<(token: string) => void> = [];\n\n // ── Response interceptor ─────────────────────────────────────────────────\n client.interceptors.response.use(\n async (response) => {\n // Transit decryption — server wraps encrypted payload inside { success, data: <envelope> }.\n // Decrypt the inner envelope, then let the standard unwrap below handle { success, data }.\n if (isTransitEnabled()) {\n const key = getSessionKey();\n if (key) {\n // Case 1: entire response is the envelope (unlikely but handle it)\n if (isTransitEnvelope(response.data)) {\n response.data = await decryptPayload(response.data, key);\n }\n // Case 2: envelope is nested inside { success, data: <envelope> }\n else if (response.data?.data && isTransitEnvelope(response.data.data)) {\n response.data.data = await decryptPayload(response.data.data, key);\n }\n }\n }\n\n // Standard { success, data } unwrap\n if (\n response.data &&\n typeof response.data === 'object' &&\n 'success' in response.data &&\n 'data' in response.data\n ) {\n response.data = response.data.data;\n }\n return response;\n },\n async (error) => {\n // Decrypt error response body — error filter encrypts it too\n if (isTransitEnabled() && error.response?.data) {\n const key = getSessionKey();\n if (key) {\n try {\n if (isTransitEnvelope(error.response.data)) {\n error.response.data = await decryptPayload(error.response.data, key);\n } else if (error.response.data?.data && isTransitEnvelope(error.response.data.data)) {\n error.response.data.data = await decryptPayload(error.response.data.data, key);\n }\n } catch { /* decryption failed — leave as-is */ }\n }\n }\n\n const original = error.config as InternalAxiosRequestConfig & { _retry?: boolean };\n\n if (error.response?.status === 401 && !original._retry) {\n const refreshToken = _tokenStore?.getRefreshToken();\n if (!refreshToken) {\n _tokenStore?.clearTokens();\n return Promise.reject(normalizeAxiosError(error));\n }\n\n if (isRefreshing) {\n return new Promise((resolve) => {\n refreshQueue.push((newToken) => {\n original.headers['Authorization'] = `Bearer ${newToken}`;\n resolve(client(original));\n });\n });\n }\n\n original._retry = true;\n isRefreshing = true;\n\n try {\n const { data } = await axios.post<{ data: AuthTokens }>(\n `${_config!.apiUrl}/auth/refresh`,\n { refreshToken },\n );\n const tokens: AuthTokens = (data as any).data ?? data;\n _tokenStore?.setTokens(tokens);\n refreshQueue.forEach((cb) => cb(tokens.accessToken));\n refreshQueue = [];\n original.headers['Authorization'] = `Bearer ${tokens.accessToken}`;\n return client(original);\n } catch {\n _tokenStore?.clearTokens();\n return Promise.reject(normalizeAxiosError(error));\n } finally {\n isRefreshing = false;\n }\n }\n\n return Promise.reject(normalizeAxiosError(error));\n },\n );\n\n return client;\n}\n\nlet _instance: AxiosInstance | null = null;\n\nexport function setApiClientInstance(instance: AxiosInstance) {\n _instance = instance;\n}\n\nexport function getApiClient(): AxiosInstance {\n if (!_instance) throw new Error('[AntzChat] API client not initialized. Call initApiClient first.');\n return _instance;\n}\n","import { isAxiosError } from 'axios';\nimport { isTransitEnvelope } from './crypto/transit.js';\n\n// ─── Base error class ─────────────────────────────────────────────────────────\n\nexport class AntzChatError extends Error {\n readonly code: string;\n readonly retryable: boolean;\n readonly context?: Record<string, unknown>;\n\n constructor(\n code: string,\n message: string,\n retryable = false,\n context?: Record<string, unknown>,\n ) {\n super(message);\n this.name = 'AntzChatError';\n this.code = code;\n this.retryable = retryable;\n this.context = context;\n // Maintain proper prototype chain in transpiled environments\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\n// ─── Semantic subclasses ──────────────────────────────────────────────────────\n\n/** Thrown on 401 (after refresh also fails) or when no refresh token exists. */\nexport class AntzChatAuthError extends AntzChatError {\n constructor(message: string, code = 'AUTH_FAILED', context?: Record<string, unknown>) {\n super(code, message, false, context);\n this.name = 'AntzChatAuthError';\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\n/** Thrown on 400 / 422 — bad input, validation failure. */\nexport class AntzChatValidationError extends AntzChatError {\n /** Server-returned field error array (when the server sends message as string[]). */\n readonly fields?: string[];\n\n constructor(message: string | string[], context?: Record<string, unknown>) {\n const msg = Array.isArray(message) ? message.join('; ') : message;\n super('VALIDATION_ERROR', msg, false, context);\n this.name = 'AntzChatValidationError';\n this.fields = Array.isArray(message) ? message : undefined;\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\n/** Thrown on network failures, timeouts, socket disconnections, and queue overflow. retryable = true. */\nexport class AntzChatNetworkError extends AntzChatError {\n constructor(message: string, code = 'NETWORK_ERROR', context?: Record<string, unknown>) {\n super(code, message, true, context);\n this.name = 'AntzChatNetworkError';\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\n/** Thrown on 403 — insufficient permissions. */\nexport class AntzChatPermissionError extends AntzChatError {\n constructor(message: string, context?: Record<string, unknown>) {\n super('PERMISSION_DENIED', message, false, context);\n this.name = 'AntzChatPermissionError';\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\n/** Thrown on 5xx or other unexpected server errors. retryable = true. */\nexport class AntzChatServerError extends AntzChatError {\n readonly httpStatus?: number;\n\n constructor(message: string, httpStatus?: number, context?: Record<string, unknown>) {\n super('SERVER_ERROR', message, true, context);\n this.name = 'AntzChatServerError';\n this.httpStatus = httpStatus;\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\n// ─── Error code reference ─────────────────────────────────────────────────────\n//\n// Code Class Source\n// ───────────────────── ──────────────────────── ─────────────────────────\n// AUTH_FAILED AntzChatAuthError 401 after refresh fails\n// SESSION_EXPIRED AntzChatAuthError 401, no refresh token\n// PERMISSION_DENIED AntzChatPermissionError 403\n// VALIDATION_ERROR AntzChatValidationError 400 / 422\n// NOT_FOUND AntzChatServerError 404\n// RATE_LIMITED AntzChatNetworkError 429\n// NETWORK_ERROR AntzChatNetworkError No response / conn failure\n// SOCKET_TIMEOUT AntzChatNetworkError ACK timeout / reconnect timeout\n// SOCKET_NOT_CONNECTED AntzChatNetworkError withAck when socket is down\n// SEND_QUEUE_FULL AntzChatNetworkError Queue overflow (>100 msgs)\n// MESSAGE_DROPPED AntzChatNetworkError Queue TTL expired (30s)\n// TRANSIT_MISMATCH AntzChatError SDK/server encryption config mismatch\n// SERVER_ERROR AntzChatServerError 5xx or unknown HTTP error\n\n// ─── REST error normaliser ────────────────────────────────────────────────────\n\n/**\n * Converts a raw axios error (or any unknown throw) into a typed AntzChatError.\n *\n * Call site: client.ts response interceptor — runs AFTER transit decryption,\n * so error.response.data is always plaintext by the time this function sees it.\n * If decryption itself failed, error.response.data remains the raw encrypted\n * envelope — detected via isTransitEnvelope() and noted in context.\n */\nexport function normalizeAxiosError(error: unknown): AntzChatError {\n if (error instanceof AntzChatError) return error;\n\n if (isAxiosError(error)) {\n const status = error.response?.status;\n const body = error.response?.data;\n\n // Detect if decryption failed — body is still an encrypted envelope\n const decryptionFailed = body != null && isTransitEnvelope(body);\n\n const rawMessage: string | string[] | undefined = decryptionFailed\n ? undefined\n : (body?.message ?? undefined);\n\n const message: string =\n (Array.isArray(rawMessage) ? rawMessage.join('; ') : rawMessage) ||\n error.message ||\n 'Request failed';\n\n const ctx: Record<string, unknown> = {\n ...(status != null && { httpStatus: status }),\n ...(body?.path != null && { path: body.path }),\n ...(body?.error != null && { serverError: body.error }),\n ...(error.code != null && { axiosCode: error.code }),\n ...(decryptionFailed && { decryptionFailed: true, note: 'Transit decryption failed — server error body is an encrypted envelope' }),\n };\n\n // No response at all — network/timeout failure\n if (!error.response) {\n return new AntzChatNetworkError(message || 'Network error', 'NETWORK_ERROR', ctx);\n }\n\n if (status === 401) {\n // AUTH_FAILED is used when a refresh was attempted but failed (set by interceptor).\n // SESSION_EXPIRED is the default: 401 with no prior retry = token simply expired.\n const code = (error.config as any)?._retry ? 'AUTH_FAILED' : 'SESSION_EXPIRED';\n return new AntzChatAuthError(message, code, ctx);\n }\n if (status === 403) return new AntzChatPermissionError(message, ctx);\n if (status === 400 || status === 422) {\n return new AntzChatValidationError(\n Array.isArray(rawMessage) ? rawMessage : message,\n ctx,\n );\n }\n if (status === 404) return new AntzChatServerError(message, 404, ctx);\n if (status === 429) return new AntzChatNetworkError(message, 'RATE_LIMITED', ctx);\n if (status != null && status >= 500) return new AntzChatServerError(message, status, ctx);\n\n return new AntzChatServerError(message, status, ctx);\n }\n\n const msg = error instanceof Error ? error.message : String(error);\n return new AntzChatError('UNKNOWN_ERROR', msg, false);\n}\n","// crypto.randomUUID() doesn't exist in React Native's Hermes engine.\n// Fall back to a RFC 4122 v4 UUID built from Math.random() when unavailable.\nexport function generateUUID(): string {\n if (typeof globalThis.crypto?.randomUUID === 'function') {\n return globalThis.crypto.randomUUID();\n }\n return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {\n const r = (Math.random() * 16) | 0;\n return (c === 'x' ? r : (r & 0x3) | 0x8).toString(16);\n });\n}\n","import type {\n BatchUploadResult,\n FileResponse,\n PaginatedResponse,\n PresignedUrlRequest,\n PresignedUrlResponse,\n FileType,\n UploadableFile,\n CompletedPart,\n} from '../types/index.js';\nimport type { PlatformUploadFn, PlatformCompressFn, PlatformUploadPartFn, ResolvedCompressionConfig } from '../config/types.js';\nimport { compressFile } from '../compression/compress.js';\nimport { getApiClient } from './client.js';\nimport { generateUUID } from '../crypto/uuid.js';\n\nexport const storageApi = {\n async requestPresignedUrl(payload: PresignedUrlRequest): Promise<PresignedUrlResponse> {\n const { data } = await getApiClient().post<PresignedUrlResponse>('/storage/presigned-url', payload);\n return data;\n },\n\n async requestPresignedUrlBatch(files: PresignedUrlRequest[]): Promise<{\n urls: PresignedUrlResponse[];\n errors: Array<{ filename: string; error: string; clientIndex?: number }>;\n }> {\n const { data } = await getApiClient().post('/storage/presigned-url/batch', { files });\n return data;\n },\n\n async confirmUpload(fileId: string): Promise<FileResponse> {\n const { data } = await getApiClient().post<FileResponse>(`/storage/confirm/${fileId}`);\n return data;\n },\n\n async getFile(fileId: string): Promise<FileResponse> {\n const { data } = await getApiClient().get<FileResponse>(`/storage/files/${fileId}`);\n return data;\n },\n\n async getFileUrl(fileId: string, expiresIn?: number): Promise<{ url: string; expiresAt: string }> {\n const { data } = await getApiClient().get(`/storage/files/${fileId}/url`, {\n params: expiresIn ? { expiresIn } : {},\n });\n return data;\n },\n\n async deleteFile(fileId: string): Promise<void> {\n await getApiClient().post(`/storage/files/${fileId}/delete`);\n },\n\n async completeMultipartUpload(\n fileId: string,\n uploadId: string,\n parts: CompletedPart[],\n ): Promise<FileResponse> {\n const { data } = await getApiClient().post<FileResponse>(\n `/storage/multipart/complete/${fileId}`,\n { uploadId, parts },\n );\n return data;\n },\n\n async getConversationFiles(\n conversationId: string,\n params: { page?: number; limit?: number; type?: FileType } = {},\n ): Promise<PaginatedResponse<FileResponse>> {\n const { data } = await getApiClient().get(\n `/storage/conversations/${conversationId}/files`,\n { params },\n );\n return data;\n },\n\n async getMyFiles(params: { page?: number; limit?: number } = {}): Promise<PaginatedResponse<FileResponse>> {\n const { data } = await getApiClient().get('/storage/my-files', { params });\n return data;\n },\n};\n\nasync function runMultipartUpload(\n presigned: PresignedUrlResponse,\n file: UploadableFile,\n platformUploadPartFn: PlatformUploadPartFn,\n onProgress?: (pct: number) => void,\n): Promise<FileResponse> {\n const { multipart } = presigned;\n if (!multipart) throw new Error('No multipart info on presigned response');\n\n const CONCURRENCY = 3;\n const completedParts: CompletedPart[] = [];\n const partProgress: Record<number, number> = {};\n\n multipart.partUrls.forEach(({ partNumber }) => { partProgress[partNumber] = 0; });\n\n const reportProgress = () => {\n if (!onProgress) return;\n const vals = Object.values(partProgress);\n const avg = vals.reduce((s, v) => s + v, 0) / Math.max(vals.length, 1);\n onProgress(Math.round(avg * 0.95));\n };\n\n const uploadPart = async (partNumber: number, uploadUrl: string, method: 'PUT' | 'POST'): Promise<void> => {\n const offset = (partNumber - 1) * multipart.chunkSize;\n const end = Math.min(offset + multipart.chunkSize, file.size);\n const blob = await fetch(file.uri).then((r) => r.blob());\n const slice = blob.slice(offset, end);\n\n const etag = await platformUploadPartFn(uploadUrl, slice, (pct) => {\n partProgress[partNumber] = pct;\n reportProgress();\n }, method);\n\n completedParts.push({ partNumber, etag });\n partProgress[partNumber] = 100;\n reportProgress();\n };\n\n for (let i = 0; i < multipart.partUrls.length; i += CONCURRENCY) {\n const batch = multipart.partUrls.slice(i, i + CONCURRENCY);\n const results = await Promise.allSettled(\n batch.map(({ partNumber, uploadUrl, method }) => uploadPart(partNumber, uploadUrl, method ?? 'PUT')),\n );\n const failed = results.find((r) => r.status === 'rejected') as PromiseRejectedResult | undefined;\n if (failed) throw failed.reason;\n }\n\n completedParts.sort((a, b) => a.partNumber - b.partNumber);\n\n const fileResponse = await storageApi.completeMultipartUpload(\n presigned.fileId,\n multipart.uploadId,\n completedParts,\n );\n onProgress?.(100);\n return fileResponse;\n}\n\n/**\n * Core upload implementation. Returns the public BatchUploadResult plus a\n * slotId → FileResponse map that useChat hooks use internally to match\n * confirmed uploads back to optimistic UI slots by position rather than\n * filename. The slotToFile map is never part of the public API.\n */\nasync function runUploadBatch(\n files: UploadableFile[],\n platformUploadFn: PlatformUploadFn,\n slotIds: string[],\n conversationId?: string,\n onProgress?: (pct: number) => void,\n platformCompressFn?: PlatformCompressFn,\n compressionConfig?: ResolvedCompressionConfig,\n platformUploadPartFn?: PlatformUploadPartFn,\n): Promise<{ result: BatchUploadResult; slotToFile: Map<string, FileResponse> }> {\n // Compress all files first (no-ops for unsupported types or when disabled)\n const compressedFiles = await Promise.all(\n files.map((f) => compressFile(f, platformCompressFn, compressionConfig ?? { enabled: false, imageQuality: 0.85, imageMaxDimension: 1920, compressDocuments: true })),\n );\n\n // Pair each compressed file with its slot ID and a clientIndex.\n // clientIndex is sent to the server and echoed back in both urls and errors,\n // giving us a reliable position mapping regardless of which files fail.\n const slotted = compressedFiles.map((f, i) => ({ file: f, slotId: slotIds[i], clientIndex: i }));\n\n const requests: PresignedUrlRequest[] = slotted.map(({ file: f, clientIndex }) => ({\n filename: f.name,\n mimeType: f.type,\n size: f.size,\n conversationId,\n clientIndex,\n ...(f.compressed && {\n metadata: {\n compressed: f.compressed,\n originalSize: f.originalSize,\n compressionAlgorithm: f.compressionAlgorithm,\n },\n }),\n }));\n\n const { urls, errors: requestErrors } = await storageApi.requestPresignedUrlBatch(requests);\n\n // Use the echoed clientIndex to identify which original slots failed.\n // This is reliable even for same-named files and any failure pattern.\n const failedSlotIds = new Set<string>();\n const failed: Array<{ filename: string; error: string }> = requestErrors.map((e) => {\n const idx = e.clientIndex ?? slotted.findIndex((s) => s.file.name === e.filename);\n const slotId = slotted[idx]?.slotId;\n if (slotId) failedSlotIds.add(slotId);\n return { filename: e.filename, error: e.error };\n });\n\n // Map each presigned URL back to its original slot via clientIndex.\n const progressMap: Record<number, number> = {};\n const reportProgress = () => {\n if (!onProgress) return;\n const vals = Object.values(progressMap);\n const avg = vals.reduce((s, v) => s + v, 0) / Math.max(vals.length, 1);\n onProgress(Math.round(avg));\n };\n\n const successful: FileResponse[] = [];\n const slotToFile = new Map<string, FileResponse>();\n\n await Promise.all(\n urls.map(async (presigned, idx) => {\n // Resolve the original slot via echoed clientIndex; fall back to position\n // in urls[] only if the server didn't echo it (older server version).\n const originalIdx = presigned.clientIndex ?? idx;\n const { file, slotId } = slotted[originalIdx];\n progressMap[originalIdx] = 0;\n try {\n let fileResponse: FileResponse;\n if (presigned.multipart && platformUploadPartFn) {\n fileResponse = await runMultipartUpload(presigned, file, platformUploadPartFn, (pct) => {\n progressMap[originalIdx] = pct;\n reportProgress();\n });\n } else {\n await platformUploadFn(presigned, file, (pct) => {\n progressMap[originalIdx] = Math.round(pct * 0.9);\n reportProgress();\n });\n fileResponse = await storageApi.confirmUpload(presigned.fileId);\n }\n progressMap[originalIdx] = 100;\n reportProgress();\n successful.push(fileResponse);\n slotToFile.set(slotId, fileResponse);\n } catch (err) {\n failed.push({ filename: file.name, error: (err as Error).message });\n }\n }),\n );\n\n return { result: { successful, failed }, slotToFile };\n}\n\n/** Public API — returns standard BatchUploadResult, slot tracking is internal. */\nexport async function uploadBatch(\n files: UploadableFile[],\n platformUploadFn: PlatformUploadFn,\n conversationId?: string,\n onProgress?: (pct: number) => void,\n platformCompressFn?: PlatformCompressFn,\n compressionConfig?: ResolvedCompressionConfig,\n platformUploadPartFn?: PlatformUploadPartFn,\n): Promise<BatchUploadResult> {\n const slotIds = files.map(() => generateUUID());\n const { result } = await runUploadBatch(files, platformUploadFn, slotIds, conversationId, onProgress, platformCompressFn, compressionConfig, platformUploadPartFn);\n return result;\n}\n\n/**\n * Used only by useChat hooks (web + RN) to get the slotId → FileResponse map\n * for matching confirmed uploads back to optimistic UI slots.\n * Not exported from the package index — internal SDK use only.\n */\nexport async function uploadBatchWithSlots(\n files: UploadableFile[],\n platformUploadFn: PlatformUploadFn,\n slotIds: string[],\n conversationId?: string,\n onProgress?: (pct: number) => void,\n platformCompressFn?: PlatformCompressFn,\n compressionConfig?: ResolvedCompressionConfig,\n platformUploadPartFn?: PlatformUploadPartFn,\n): Promise<{ result: BatchUploadResult; slotToFile: Map<string, FileResponse> }> {\n return runUploadBatch(files, platformUploadFn, slotIds, conversationId, onProgress, platformCompressFn, compressionConfig, platformUploadPartFn);\n}\n"],"mappings":"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","names":["import_axios"]}
|
package/dist/internal.d.cts
CHANGED
package/dist/internal.d.ts
CHANGED
package/dist/internal.js
CHANGED
|
@@ -602,6 +602,28 @@ interface SendMessagePayload {
|
|
|
602
602
|
/** Mentioned userIds (use `"all"` / MENTION_ALL_ID for @all). Derived from the tokens in `text`. */
|
|
603
603
|
mentions?: string[];
|
|
604
604
|
}
|
|
605
|
+
interface ForwardMessagePayload {
|
|
606
|
+
messageId: string;
|
|
607
|
+
targetConversationIds: string[];
|
|
608
|
+
/** Omit to forward the whole source message, including all its attachments, unchanged. */
|
|
609
|
+
attachmentIds?: string[];
|
|
610
|
+
/** Client-generated idempotency key — see ForwardData.tempId (api/messages.ts) for the full contract. */
|
|
611
|
+
tempId: string;
|
|
612
|
+
}
|
|
613
|
+
/** Per-target result for a 'forward_message' socket call. Mirrors the REST ForwardResult shape. */
|
|
614
|
+
interface ForwardResultPayload {
|
|
615
|
+
conversationId: string;
|
|
616
|
+
success: boolean;
|
|
617
|
+
message?: Message;
|
|
618
|
+
error?: string;
|
|
619
|
+
}
|
|
620
|
+
/** Ack for the 'forward_message' socket event. */
|
|
621
|
+
interface ForwardAckPayload {
|
|
622
|
+
tempId?: string;
|
|
623
|
+
results: ForwardResultPayload[];
|
|
624
|
+
/** Present only when the server-side forward() call itself threw (e.g. source message not found) — results is [] in that case. */
|
|
625
|
+
error?: string;
|
|
626
|
+
}
|
|
605
627
|
interface QuietHours {
|
|
606
628
|
enabled: boolean;
|
|
607
629
|
/** HH:MM — e.g. "22:00" */
|
|
@@ -704,6 +726,19 @@ interface UploadConfig {
|
|
|
704
726
|
/** Called with 0–100 aggregate progress during a batch upload */
|
|
705
727
|
onProgress?: (progress: number) => void;
|
|
706
728
|
}
|
|
729
|
+
/** Context passed to onSendError. */
|
|
730
|
+
interface SendErrorContext {
|
|
731
|
+
conversationId: string;
|
|
732
|
+
/** tempId of the optimistic message the failure belongs to. */
|
|
733
|
+
tempId: string;
|
|
734
|
+
/**
|
|
735
|
+
* True when resending the identical payload can't succeed (file size/type
|
|
736
|
+
* limits, message too long, no longer a participant, …). The SDK has removed
|
|
737
|
+
* the optimistic message from the timeline. False for transient failures
|
|
738
|
+
* (network / socket / generic server error), where a "! Retry" bubble stays.
|
|
739
|
+
*/
|
|
740
|
+
permanent: boolean;
|
|
741
|
+
}
|
|
707
742
|
interface AntzChatConfig {
|
|
708
743
|
/** REST API base URL — e.g. "https://api.yourapp.com/api/v1" */
|
|
709
744
|
apiUrl: string;
|
|
@@ -758,6 +793,12 @@ interface AntzChatConfig {
|
|
|
758
793
|
* Each SDK (web, RN) provides its own default; Node.js users wire in their own.
|
|
759
794
|
*/
|
|
760
795
|
platformCompressFn?: PlatformCompressFn;
|
|
796
|
+
/**
|
|
797
|
+
* Called when sending a message ultimately fails, after the SDK has updated
|
|
798
|
+
* the timeline (removed the bubble for permanent failures, flagged it retryable
|
|
799
|
+
* otherwise). Use it to surface a toast / error banner to the user.
|
|
800
|
+
*/
|
|
801
|
+
onSendError?: (error: Error, context: SendErrorContext) => void;
|
|
761
802
|
/**
|
|
762
803
|
* Number of messages fetched per page when loading chat history.
|
|
763
804
|
* Default: 40
|
|
@@ -822,6 +863,7 @@ interface ResolvedConfig {
|
|
|
822
863
|
platformCompressFn?: PlatformCompressFn;
|
|
823
864
|
compression: ResolvedCompressionConfig;
|
|
824
865
|
persistStorage: PersistStorage;
|
|
866
|
+
onSendError?: (error: Error, context: SendErrorContext) => void;
|
|
825
867
|
messagePageSize: number;
|
|
826
868
|
starredMessagePageSize: number;
|
|
827
869
|
searchPageSize: number;
|
|
@@ -868,4 +910,4 @@ declare function uploadBatchWithSlots(files: UploadableFile[], platformUploadFn:
|
|
|
868
910
|
slotToFile: Map<string, FileResponse>;
|
|
869
911
|
}>;
|
|
870
912
|
|
|
871
|
-
export { type
|
|
913
|
+
export { type MessageStarUpdatedEvent as $, type AuthResponse as A, type BatchUploadResult as B, type CrossConversationSyncResponse as C, type CompressionConfig as D, type ConversationType as E, type ForwardMessagePayload as F, type ConversationUpdatedEvent as G, type FileSizeLimits as H, type ForwardResultPayload as I, type LastReaction as J, MENTION_ALL_ID as K, type LoginCredentials as L, type Message as M, type MessageAckEvent as N, type MessageContent as O, type PaginatedResponse as P, type MessageDeletedEvent as Q, type ResolvedCompressionConfig as R, type SendMessagePayload as S, type MessageDeletedForMeEvent as T, type User as U, type MessageDeliveredEvent as V, type MessageForwardReference as W, type MessageMetadata as X, type MessageReaction as Y, type MessageReceiptEntry as Z, type MessageReplyReference as _, type RegisterData as a, type MessageUpdatedEvent as a0, type MessagesDeliveredEvent as a1, type MultipartPartUrl as a2, type MultipartUploadInfo as a3, type NewMessageEvent as a4, type OptimisticAttachment as a5, type PlatformCompressFn as a6, type PlatformUploadFn as a7, type PlatformUploadPartFn as a8, type QuietHours as a9, type ReactionGroup as aa, type ReactionUpdatedEvent as ab, type ReactionUser as ac, type ReadReceiptEvent as ad, type ReplyAttachmentSnapshot as ae, type ResolvedFileSizeLimits as af, type SendErrorContext as ag, type SendMessageAttachment as ah, type SyncDeletedForMe as ai, type SyncDeliveredReceipt as aj, type SyncParticipantChange as ak, type SyncReactionEntry as al, type SyncReactions as am, type SyncReadReceipt as an, type SyncStarEntry as ao, type SystemMessageMetadata as ap, type TypingIndicatorEvent as aq, type UploadConfig as ar, type UploadProgress as as, type UserStatusEvent as at, resolveConfig as au, storageApi as av, uploadBatch as aw, uploadBatchWithSlots as ax, type AuthTokens as b, type ConversationSyncResponse as c, type AppConfig as d, type CursorPaginatedResponse as e, type MessageReactionsResponse as f, type MessageReceiptsResponse as g, type ConversationListParams as h, type Conversation as i, type Participant as j, type ConversationUnreadCount as k, type UnreadSummary as l, type UserPreferences as m, type ResolvedConfig as n, type ForwardAckPayload as o, type PersistStorage as p, type PresignedUrlRequest as q, type PresignedUrlResponse as r, type FileResponse as s, type CompletedPart as t, type FileType as u, type AntzChatConfig as v, type UploadableFile as w, type Attachment as x, type CompressedFile as y, type CompressionAlgorithm as z };
|
|
@@ -602,6 +602,28 @@ interface SendMessagePayload {
|
|
|
602
602
|
/** Mentioned userIds (use `"all"` / MENTION_ALL_ID for @all). Derived from the tokens in `text`. */
|
|
603
603
|
mentions?: string[];
|
|
604
604
|
}
|
|
605
|
+
interface ForwardMessagePayload {
|
|
606
|
+
messageId: string;
|
|
607
|
+
targetConversationIds: string[];
|
|
608
|
+
/** Omit to forward the whole source message, including all its attachments, unchanged. */
|
|
609
|
+
attachmentIds?: string[];
|
|
610
|
+
/** Client-generated idempotency key — see ForwardData.tempId (api/messages.ts) for the full contract. */
|
|
611
|
+
tempId: string;
|
|
612
|
+
}
|
|
613
|
+
/** Per-target result for a 'forward_message' socket call. Mirrors the REST ForwardResult shape. */
|
|
614
|
+
interface ForwardResultPayload {
|
|
615
|
+
conversationId: string;
|
|
616
|
+
success: boolean;
|
|
617
|
+
message?: Message;
|
|
618
|
+
error?: string;
|
|
619
|
+
}
|
|
620
|
+
/** Ack for the 'forward_message' socket event. */
|
|
621
|
+
interface ForwardAckPayload {
|
|
622
|
+
tempId?: string;
|
|
623
|
+
results: ForwardResultPayload[];
|
|
624
|
+
/** Present only when the server-side forward() call itself threw (e.g. source message not found) — results is [] in that case. */
|
|
625
|
+
error?: string;
|
|
626
|
+
}
|
|
605
627
|
interface QuietHours {
|
|
606
628
|
enabled: boolean;
|
|
607
629
|
/** HH:MM — e.g. "22:00" */
|
|
@@ -704,6 +726,19 @@ interface UploadConfig {
|
|
|
704
726
|
/** Called with 0–100 aggregate progress during a batch upload */
|
|
705
727
|
onProgress?: (progress: number) => void;
|
|
706
728
|
}
|
|
729
|
+
/** Context passed to onSendError. */
|
|
730
|
+
interface SendErrorContext {
|
|
731
|
+
conversationId: string;
|
|
732
|
+
/** tempId of the optimistic message the failure belongs to. */
|
|
733
|
+
tempId: string;
|
|
734
|
+
/**
|
|
735
|
+
* True when resending the identical payload can't succeed (file size/type
|
|
736
|
+
* limits, message too long, no longer a participant, …). The SDK has removed
|
|
737
|
+
* the optimistic message from the timeline. False for transient failures
|
|
738
|
+
* (network / socket / generic server error), where a "! Retry" bubble stays.
|
|
739
|
+
*/
|
|
740
|
+
permanent: boolean;
|
|
741
|
+
}
|
|
707
742
|
interface AntzChatConfig {
|
|
708
743
|
/** REST API base URL — e.g. "https://api.yourapp.com/api/v1" */
|
|
709
744
|
apiUrl: string;
|
|
@@ -758,6 +793,12 @@ interface AntzChatConfig {
|
|
|
758
793
|
* Each SDK (web, RN) provides its own default; Node.js users wire in their own.
|
|
759
794
|
*/
|
|
760
795
|
platformCompressFn?: PlatformCompressFn;
|
|
796
|
+
/**
|
|
797
|
+
* Called when sending a message ultimately fails, after the SDK has updated
|
|
798
|
+
* the timeline (removed the bubble for permanent failures, flagged it retryable
|
|
799
|
+
* otherwise). Use it to surface a toast / error banner to the user.
|
|
800
|
+
*/
|
|
801
|
+
onSendError?: (error: Error, context: SendErrorContext) => void;
|
|
761
802
|
/**
|
|
762
803
|
* Number of messages fetched per page when loading chat history.
|
|
763
804
|
* Default: 40
|
|
@@ -822,6 +863,7 @@ interface ResolvedConfig {
|
|
|
822
863
|
platformCompressFn?: PlatformCompressFn;
|
|
823
864
|
compression: ResolvedCompressionConfig;
|
|
824
865
|
persistStorage: PersistStorage;
|
|
866
|
+
onSendError?: (error: Error, context: SendErrorContext) => void;
|
|
825
867
|
messagePageSize: number;
|
|
826
868
|
starredMessagePageSize: number;
|
|
827
869
|
searchPageSize: number;
|
|
@@ -868,4 +910,4 @@ declare function uploadBatchWithSlots(files: UploadableFile[], platformUploadFn:
|
|
|
868
910
|
slotToFile: Map<string, FileResponse>;
|
|
869
911
|
}>;
|
|
870
912
|
|
|
871
|
-
export { type
|
|
913
|
+
export { type MessageStarUpdatedEvent as $, type AuthResponse as A, type BatchUploadResult as B, type CrossConversationSyncResponse as C, type CompressionConfig as D, type ConversationType as E, type ForwardMessagePayload as F, type ConversationUpdatedEvent as G, type FileSizeLimits as H, type ForwardResultPayload as I, type LastReaction as J, MENTION_ALL_ID as K, type LoginCredentials as L, type Message as M, type MessageAckEvent as N, type MessageContent as O, type PaginatedResponse as P, type MessageDeletedEvent as Q, type ResolvedCompressionConfig as R, type SendMessagePayload as S, type MessageDeletedForMeEvent as T, type User as U, type MessageDeliveredEvent as V, type MessageForwardReference as W, type MessageMetadata as X, type MessageReaction as Y, type MessageReceiptEntry as Z, type MessageReplyReference as _, type RegisterData as a, type MessageUpdatedEvent as a0, type MessagesDeliveredEvent as a1, type MultipartPartUrl as a2, type MultipartUploadInfo as a3, type NewMessageEvent as a4, type OptimisticAttachment as a5, type PlatformCompressFn as a6, type PlatformUploadFn as a7, type PlatformUploadPartFn as a8, type QuietHours as a9, type ReactionGroup as aa, type ReactionUpdatedEvent as ab, type ReactionUser as ac, type ReadReceiptEvent as ad, type ReplyAttachmentSnapshot as ae, type ResolvedFileSizeLimits as af, type SendErrorContext as ag, type SendMessageAttachment as ah, type SyncDeletedForMe as ai, type SyncDeliveredReceipt as aj, type SyncParticipantChange as ak, type SyncReactionEntry as al, type SyncReactions as am, type SyncReadReceipt as an, type SyncStarEntry as ao, type SystemMessageMetadata as ap, type TypingIndicatorEvent as aq, type UploadConfig as ar, type UploadProgress as as, type UserStatusEvent as at, resolveConfig as au, storageApi as av, uploadBatch as aw, uploadBatchWithSlots as ax, type AuthTokens as b, type ConversationSyncResponse as c, type AppConfig as d, type CursorPaginatedResponse as e, type MessageReactionsResponse as f, type MessageReceiptsResponse as g, type ConversationListParams as h, type Conversation as i, type Participant as j, type ConversationUnreadCount as k, type UnreadSummary as l, type UserPreferences as m, type ResolvedConfig as n, type ForwardAckPayload as o, type PersistStorage as p, type PresignedUrlRequest as q, type PresignedUrlResponse as r, type FileResponse as s, type CompletedPart as t, type FileType as u, type AntzChatConfig as v, type UploadableFile as w, type Attachment as x, type CompressedFile as y, type CompressionAlgorithm as z };
|