@antzsoft/chat-core 1.2.2 → 1.2.4

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@antzsoft/chat-core",
3
- "version": "1.2.2",
3
+ "version": "1.2.4",
4
4
  "description": "Platform-agnostic core for Antz Chat — API, socket, stores, types. Works in browser, React Native (Expo or bare), and Node.js.",
5
5
  "author": "Antz",
6
6
  "license": "MIT",
@@ -1,7 +0,0 @@
1
- import {
2
- useChatStore
3
- } from "./chunk-GUO5QQGK.js";
4
- export {
5
- useChatStore
6
- };
7
- //# sourceMappingURL=chat.store-JC6QYDDL.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/stores/chat.store.ts"],"sourcesContent":["import { create } from 'zustand';\nimport type { Message } from '../types/index.js';\n\ninterface TypingUser {\n userId: string;\n displayName: string;\n avatarUrl?: string;\n}\n\nexport interface LastReadEntry {\n messageId: string;\n readAt: string;\n}\n\ninterface ChatState {\n activeConversationId: string | null;\n pendingTarget: { conversationId: string; messageId: string } | null;\n typingUsers: Record<string, TypingUser[]>;\n onlineUsers: string[];\n /** keyed by conversationId — current user's last read pointer per conversation */\n lastRead: Record<string, LastReadEntry>;\n /** keyed by userId — last seen timestamp for each user */\n lastSeen: Record<string, string>;\n replyingTo: Message | null;\n editingMessage: Message | null;\n isSidebarOpen: boolean;\n isGroupInfoOpen: boolean;\n isStarredPanelOpen: boolean;\n\n setActiveConversation: (id: string | null) => void;\n setPendingTarget: (target: { conversationId: string; messageId: string } | null) => void;\n addTypingUser: (conversationId: string, user: TypingUser) => void;\n removeTypingUser: (conversationId: string, userId: string) => void;\n setUserOnline: (userId: string) => void;\n setUserOffline: (userId: string) => void;\n setOnlineUsers: (userIds: string[]) => void;\n setLastRead: (conversationId: string, messageId: string, readAt: string) => void;\n setLastSeen: (userId: string, lastSeenAt: string | null) => void;\n setReplyingTo: (message: Message | null) => void;\n setEditingMessage: (message: Message | null) => void;\n toggleSidebar: () => void;\n setSidebarOpen: (open: boolean) => void;\n toggleGroupInfo: () => void;\n setGroupInfoOpen: (open: boolean) => void;\n toggleStarredPanel: () => void;\n setStarredPanelOpen: (open: boolean) => void;\n}\n\nexport const useChatStore = create<ChatState>((set) => ({\n activeConversationId: null,\n pendingTarget: null,\n typingUsers: {},\n onlineUsers: [],\n lastRead: {},\n lastSeen: {},\n replyingTo: null,\n editingMessage: null,\n isSidebarOpen: true,\n isGroupInfoOpen: false,\n isStarredPanelOpen: false,\n\n setActiveConversation: (id) =>\n set({ activeConversationId: id, replyingTo: null, editingMessage: null }),\n\n setPendingTarget: (target) => set({ pendingTarget: target }),\n\n addTypingUser: (conversationId, user) =>\n set((state) => {\n const existing = state.typingUsers[conversationId] ?? [];\n const deduped = existing.filter((u) => u.userId !== user.userId);\n return { typingUsers: { ...state.typingUsers, [conversationId]: [...deduped, user] } };\n }),\n\n removeTypingUser: (conversationId, userId) =>\n set((state) => ({\n typingUsers: {\n ...state.typingUsers,\n [conversationId]: (state.typingUsers[conversationId] ?? []).filter(\n (u) => u.userId !== userId,\n ),\n },\n })),\n\n setUserOnline: (userId) =>\n set((state) => ({\n onlineUsers: state.onlineUsers.includes(userId)\n ? state.onlineUsers\n : [...state.onlineUsers, userId],\n })),\n\n setUserOffline: (userId) =>\n set((state) => ({ onlineUsers: state.onlineUsers.filter((id) => id !== userId) })),\n\n setOnlineUsers: (userIds) => set({ onlineUsers: userIds }),\n\n setLastRead: (conversationId, messageId, readAt) =>\n set((state) => ({\n lastRead: { ...state.lastRead, [conversationId]: { messageId, readAt } },\n })),\n\n setLastSeen: (userId, lastSeenAt) =>\n set((state) => {\n if (lastSeenAt === null) {\n const { [userId]: _, ...rest } = state.lastSeen;\n return { lastSeen: rest };\n }\n return { lastSeen: { ...state.lastSeen, [userId]: lastSeenAt } };\n }),\n\n setReplyingTo: (message) => set({ replyingTo: message, editingMessage: null }),\n\n setEditingMessage: (message) => set({ editingMessage: message, replyingTo: null }),\n\n toggleSidebar: () => set((state) => ({ isSidebarOpen: !state.isSidebarOpen })),\n setSidebarOpen: (open) => set({ isSidebarOpen: open }),\n\n toggleGroupInfo: () => set((state) => ({ isGroupInfoOpen: !state.isGroupInfoOpen })),\n setGroupInfoOpen: (open) => set({ isGroupInfoOpen: open }),\n\n toggleStarredPanel: () => set((state) => ({ isStarredPanelOpen: !state.isStarredPanelOpen })),\n setStarredPanelOpen: (open) => set({ isStarredPanelOpen: open }),\n}));\n"],"mappings":"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","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/compression/compress.ts","../src/crypto/session.ts","../src/api/client.ts","../src/crypto/transit.ts","../src/crypto/uuid.ts","../src/api/storage.ts"],"sourcesContent":["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 type { TransitAlgo } from './detect.js';\n\ninterface TransitSession {\n sessionKey: CryptoKey | Uint8Array; // CryptoKey on Web Crypto, Uint8Array on noble/RN\n algo: TransitAlgo;\n sessionId: string;\n enabled: boolean;\n}\n\n// All state lives on globalThis so Turbopack <locals> module splits and any\n// other bundler that creates multiple instances of this module still share a\n// single source of truth. The symbol key prevents accidental collisions.\nconst _KEY = Symbol.for('__antz_chat_transit__');\n\ninterface TransitState {\n session: TransitSession | null;\n sessionEverEstablished: boolean;\n readyResolve: (() => void) | null;\n readyPromise: Promise<void> | null;\n transitConfigured: boolean | null;\n}\n\nfunction getState(): TransitState {\n const g = globalThis as any;\n if (!g[_KEY]) {\n g[_KEY] = {\n session: null,\n sessionEverEstablished: false,\n readyResolve: null,\n readyPromise: null,\n transitConfigured: null,\n } satisfies TransitState;\n }\n return g[_KEY] as TransitState;\n}\n\nexport function configureTransit(enabled: boolean): void {\n const s = getState();\n s.transitConfigured = enabled;\n if (!enabled) {\n // Transit disabled — resolve immediately so HTTP requests don't block\n s.readyResolve?.();\n s.readyResolve = null;\n }\n}\n\nexport function waitForTransitReady(): Promise<void> {\n const s = getState();\n // Not configured yet or disabled — resolve immediately\n if (!s.transitConfigured) return Promise.resolve();\n // Already have an active session — resolve immediately\n if (s.session) return Promise.resolve();\n // Session was previously established but is now cleared (socket dropped/reconnecting).\n // The encryption block in the interceptor is guarded by isTransitEnabled() and will\n // skip itself with no session, so there is no point blocking here.\n if (s.sessionEverEstablished) return Promise.resolve();\n // First startup — block until the initial ECDH handshake completes.\n if (!s.readyPromise) {\n s.readyPromise = new Promise<void>((resolve) => {\n s.readyResolve = resolve;\n });\n }\n return s.readyPromise;\n}\n\nexport function setTransitSession(session: TransitSession): void {\n const s = getState();\n s.session = session;\n s.sessionEverEstablished = true;\n // Resolve any pending HTTP requests waiting for the session key\n s.readyResolve?.();\n s.readyResolve = null;\n}\n\nexport function getTransitSession(): TransitSession | null {\n return getState().session;\n}\n\nexport function clearTransitSession(): void {\n const s = getState();\n s.session = null;\n // Reset the ready promise so it can be recreated on next waitForTransitReady call.\n // sessionEverEstablished intentionally left true — HTTP requests fired while\n // reconnecting should not block (isTransitEnabled() guards encryption anyway).\n s.readyPromise = null;\n s.readyResolve = null;\n}\n\nexport function isTransitEnabled(): boolean {\n return getState().session?.enabled === true;\n}\n\nexport function getSessionKey(): CryptoKey | Uint8Array | null {\n return getState().session?.sessionKey ?? null;\n}\n\n// Returns sessionId for the x-transit-session header sent with HTTP requests.\nexport function getSessionId(): string | null {\n return getState().session?.sessionId ?? null;\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 } from '../crypto/session.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;\n// Avatar headers are sent only on the first authenticated request after init.\n// The server hashes on receive and deduplicates — subsequent requests don't need them.\nlet _avatarSent = false;\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 // ── 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(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(error);\n } finally {\n isRefreshing = false;\n }\n }\n\n return Promise.reject(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","export interface TransitEnvelope {\n v: 1;\n iv: string; // base64, 12 bytes\n tag: string; // base64, 16 bytes\n ct: string; // base64, ciphertext\n}\n\n// sessionKey is CryptoKey on Web Crypto path, Uint8Array on noble/RN path.\ntype AnySessionKey = CryptoKey | Uint8Array;\n\nfunction hasWebCrypto(): boolean {\n return typeof globalThis.crypto?.subtle !== 'undefined';\n}\n\n// ─── Encrypt ─────────────────────────────────────────────────────────────────\n\nexport async function encryptPayload(\n data: unknown,\n sessionKey: AnySessionKey,\n): Promise<TransitEnvelope> {\n const plaintext = new TextEncoder().encode(JSON.stringify(data));\n\n if (!hasWebCrypto() || sessionKey instanceof Uint8Array) {\n return encryptNoble(plaintext, sessionKey as Uint8Array);\n }\n return encryptWebCrypto(plaintext, sessionKey as CryptoKey);\n}\n\nasync function encryptWebCrypto(plaintext: Uint8Array, sessionKey: CryptoKey): Promise<TransitEnvelope> {\n const iv = globalThis.crypto.getRandomValues(new Uint8Array(12));\n const encrypted = await globalThis.crypto.subtle.encrypt({ name: 'AES-GCM', iv: iv as Uint8Array<ArrayBuffer> }, sessionKey, plaintext as Uint8Array<ArrayBuffer>);\n const ct = encrypted.slice(0, encrypted.byteLength - 16);\n const tag = encrypted.slice(encrypted.byteLength - 16);\n return { v: 1, iv: bufToB64(iv), tag: bufToB64(tag), ct: bufToB64(ct) };\n}\n\nasync function encryptNoble(plaintext: Uint8Array, sessionKey: Uint8Array): Promise<TransitEnvelope> {\n const { gcm } = await import('@noble/ciphers/aes');\n const { randomBytes } = await import('@noble/hashes/utils');\n const iv = randomBytes(12);\n const cipher = gcm(sessionKey, iv);\n const encrypted = cipher.encrypt(plaintext); // ct + 16-byte tag appended\n const ct = encrypted.slice(0, encrypted.length - 16);\n const tag = encrypted.slice(encrypted.length - 16);\n return { v: 1, iv: uint8ToB64(iv), tag: uint8ToB64(tag), ct: uint8ToB64(ct) };\n}\n\n// ─── Decrypt ─────────────────────────────────────────────────────────────────\n\nexport async function decryptPayload(\n envelope: TransitEnvelope,\n sessionKey: AnySessionKey,\n): Promise<unknown> {\n if (!hasWebCrypto() || sessionKey instanceof Uint8Array) {\n return decryptNoble(envelope, sessionKey as Uint8Array);\n }\n return decryptWebCrypto(envelope, sessionKey as CryptoKey);\n}\n\nasync function decryptWebCrypto(envelope: TransitEnvelope, sessionKey: CryptoKey): Promise<unknown> {\n const iv = b64ToBuf(envelope.iv);\n const tag = b64ToBuf(envelope.tag);\n const ct = b64ToBuf(envelope.ct);\n const combined = new Uint8Array(ct.byteLength + tag.byteLength);\n combined.set(new Uint8Array(ct), 0);\n combined.set(new Uint8Array(tag), ct.byteLength);\n const decrypted = await globalThis.crypto.subtle.decrypt(\n { name: 'AES-GCM', iv: new Uint8Array(iv) },\n sessionKey,\n combined,\n );\n return JSON.parse(new TextDecoder().decode(decrypted));\n}\n\nasync function decryptNoble(envelope: TransitEnvelope, sessionKey: Uint8Array): Promise<unknown> {\n const { gcm } = await import('@noble/ciphers/aes');\n const iv = base64ToUint8(envelope.iv);\n const tag = base64ToUint8(envelope.tag);\n const ct = base64ToUint8(envelope.ct);\n const combined = new Uint8Array(ct.length + tag.length);\n combined.set(ct, 0);\n combined.set(tag, ct.length);\n const cipher = gcm(sessionKey, iv);\n const decrypted = cipher.decrypt(combined);\n return JSON.parse(new TextDecoder().decode(decrypted));\n}\n\nexport function isTransitEnvelope(v: unknown): v is TransitEnvelope {\n return (\n typeof v === 'object' &&\n v !== null &&\n (v as any).v === 1 &&\n typeof (v as any).iv === 'string' &&\n typeof (v as any).tag === 'string' &&\n typeof (v as any).ct === 'string'\n );\n}\n\n// ─── Helpers ─────────────────────────────────────────────────────────────────\n\nfunction bufToB64(buf: ArrayBuffer | Uint8Array): string {\n const bytes = buf instanceof Uint8Array ? buf : new Uint8Array(buf);\n let str = '';\n bytes.forEach(b => { str += String.fromCharCode(b); });\n return btoa(str);\n}\n\nfunction uint8ToB64(bytes: Uint8Array): string {\n let str = '';\n bytes.forEach(b => { str += String.fromCharCode(b); });\n return btoa(str);\n}\n\nfunction b64ToBuf(b64: string): ArrayBuffer {\n const bin = atob(b64);\n const buf = new Uint8Array(bin.length);\n for (let i = 0; i < bin.length; i++) buf[i] = bin.charCodeAt(i);\n return buf.buffer;\n}\n\nfunction base64ToUint8(b64: string): Uint8Array {\n const bin = atob(b64);\n const buf = new Uint8Array(bin.length);\n for (let i = 0; i < bin.length; i++) buf[i] = bin.charCodeAt(i);\n return buf;\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} from '../types/index.js';\nimport type { PlatformUploadFn, PlatformCompressFn, 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 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\n/**\n * High-level batch upload.\n * The actual binary transfer is delegated to platformUploadFn so this\n * function is platform-agnostic (works on web and React Native).\n * If platformCompressFn + compressionConfig are provided, each file is\n * compressed before the presigned URL is requested (so the server receives\n * the correct compressed size and MIME type).\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): 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 await platformUploadFn(presigned, file, (pct) => {\n progressMap[originalIdx] = Math.round(pct * 0.9);\n reportProgress();\n });\n\n const fileResponse = await storageApi.confirmUpload(presigned.fileId);\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): Promise<BatchUploadResult> {\n const slotIds = files.map(() => generateUUID());\n const { result } = await runUploadBatch(files, platformUploadFn, slotIds, conversationId, onProgress, platformCompressFn, compressionConfig);\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): Promise<{ result: BatchUploadResult; slotToFile: Map<string, FileResponse> }> {\n return runUploadBatch(files, platformUploadFn, slotIds, conversationId, onProgress, platformCompressFn, compressionConfig);\n}\n"],"mappings":"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","names":[]}