@spacelr/sdk 0.2.0 → 0.2.2

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.
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../libs/sdk/src/core/errors.ts","../../../../libs/sdk/src/core/http-client.ts","../../../../libs/sdk/src/core/token-storage.ts","../../../../libs/sdk/src/core/token-manager.ts","../../../../libs/sdk/src/types/common.ts","../../../../libs/sdk/src/core/pkce.ts","../../../../libs/sdk/src/core/realtime.ts","../../../../libs/sdk/src/core/cursor-storage.ts","../../../../libs/sdk/src/modules/auth.module.ts","../../../../libs/sdk/src/modules/storage.module.ts","../../../../libs/sdk/src/modules/database.module.ts","../../../../libs/sdk/src/modules/notifications.module.ts","../../../../libs/sdk/src/modules/functions.module.ts","../../../../libs/sdk/src/client.ts"],"sourcesContent":["export class SpacelrError extends Error {\n readonly code: string;\n readonly statusCode?: number;\n readonly details?: Record<string, unknown>;\n\n constructor(\n message: string,\n code: string,\n statusCode?: number,\n details?: Record<string, unknown>\n ) {\n super(message);\n this.name = 'SpacelrError';\n this.code = code;\n this.statusCode = statusCode;\n this.details = details;\n }\n}\n\nexport class SpacelrAuthError extends SpacelrError {\n constructor(\n message: string,\n statusCode = 401,\n details?: Record<string, unknown>\n ) {\n super(message, 'AUTH_ERROR', statusCode, details);\n this.name = 'SpacelrAuthError';\n }\n}\n\nexport class SpacelrNetworkError extends SpacelrError {\n constructor(message: string, details?: Record<string, unknown>) {\n super(message, 'NETWORK_ERROR', undefined, details);\n this.name = 'SpacelrNetworkError';\n }\n}\n\nexport class SpacelrTimeoutError extends SpacelrError {\n constructor(timeoutMs: number) {\n super(\n `Request timed out after ${timeoutMs}ms`,\n 'TIMEOUT_ERROR',\n undefined,\n { timeoutMs }\n );\n this.name = 'SpacelrTimeoutError';\n }\n}\n\nexport class SpacelrTwoFactorRequiredError extends SpacelrError {\n readonly twoFactorToken: string;\n\n constructor(twoFactorToken: string, details?: Record<string, unknown>) {\n super(\n 'Two-factor authentication required',\n 'TWO_FACTOR_REQUIRED',\n 200,\n details\n );\n this.name = 'SpacelrTwoFactorRequiredError';\n this.twoFactorToken = twoFactorToken;\n }\n}\n\nexport class SpacelrEmailVerificationRequiredError extends SpacelrError {\n readonly emailSent: boolean;\n\n constructor(emailSent: boolean, details?: Record<string, unknown>) {\n super(\n emailSent\n ? 'Email verification required. A new verification email has been sent.'\n : 'Email verification required. Please check your inbox for the verification email.',\n 'EMAIL_VERIFICATION_REQUIRED',\n 200,\n details\n );\n this.name = 'SpacelrEmailVerificationRequiredError';\n this.emailSent = emailSent;\n }\n}\n","import { SpacelrClientConfig, ApiResponse } from '../types';\nimport {\n SpacelrError,\n SpacelrAuthError,\n SpacelrNetworkError,\n SpacelrTimeoutError,\n SpacelrTwoFactorRequiredError,\n SpacelrEmailVerificationRequiredError,\n} from './errors';\nimport { TokenManager } from './token-manager';\n\nexport interface HttpRequestOptions {\n method: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';\n path: string;\n body?: unknown;\n authenticated?: boolean;\n headers?: Record<string, string>;\n query?: Record<string, string | number | undefined>;\n /** Send cookies cross-origin (credentials: 'include'). Defaults to true for /auth/ paths. */\n withCredentials?: boolean;\n}\n\nexport class HttpClient {\n private config: SpacelrClientConfig;\n private tokenManager: TokenManager;\n\n constructor(config: SpacelrClientConfig, tokenManager: TokenManager) {\n this.config = config;\n this.tokenManager = tokenManager;\n }\n\n async request<T>(options: HttpRequestOptions): Promise<T> {\n return this.requestWithRetry<T>(options, false);\n }\n\n private async requestWithRetry<T>(\n options: HttpRequestOptions,\n isRetry: boolean,\n ): Promise<T> {\n const url = this.buildUrl(options.path, options.query);\n const headers = await this.buildHeaders(options);\n const timeout = this.config.timeout ?? 30000;\n const includeCredentials = options.withCredentials ?? options.path.startsWith('/auth/');\n\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), timeout);\n\n try {\n const response = await fetch(url, {\n method: options.method,\n headers,\n body: options.body ? JSON.stringify(options.body) : undefined,\n signal: controller.signal,\n ...(includeCredentials && { credentials: 'include' as RequestCredentials }),\n });\n\n const responseBody = await this.parseResponse(response);\n\n if (!response.ok) {\n if (options.authenticated && response.status === 401) {\n if (await this.recoverFromAuthFailure(isRetry)) {\n return this.requestWithRetry<T>(options, true);\n }\n }\n this.throwHttpError(response.status, responseBody);\n }\n\n return this.extractData<T>(responseBody);\n } catch (error) {\n if (error instanceof SpacelrError) throw error;\n\n if (error instanceof DOMException && error.name === 'AbortError') {\n throw new SpacelrTimeoutError(timeout);\n }\n\n throw new SpacelrNetworkError(\n error instanceof Error ? error.message : 'Network request failed'\n );\n } finally {\n clearTimeout(timeoutId);\n }\n }\n\n async uploadForm<T>(\n path: string,\n formData: FormData,\n onProgress?: (event: { loaded: number; total: number }) => void,\n ): Promise<T> {\n return this.uploadFormWithRetry<T>(path, formData, onProgress, false);\n }\n\n private async uploadFormWithRetry<T>(\n path: string,\n formData: FormData,\n onProgress: ((event: { loaded: number; total: number }) => void) | undefined,\n isRetry: boolean,\n ): Promise<T> {\n const url = this.buildUrl(path);\n const headers = await this.buildFormHeaders();\n const timeoutMs = this.config.timeout ?? 30000;\n\n try {\n if (onProgress) {\n return await this.uploadFormWithProgress<T>(url, headers, formData, onProgress, timeoutMs);\n }\n return await this.uploadFormOnce<T>(url, headers, formData, timeoutMs);\n } catch (error) {\n if (error instanceof SpacelrAuthError && error.statusCode === 401) {\n if (await this.recoverFromAuthFailure(isRetry)) {\n return this.uploadFormWithRetry<T>(path, formData, onProgress, true);\n }\n }\n throw error;\n }\n }\n\n /**\n * Shared handler for a 401 on an authenticated request. Returns true if the\n * caller should retry with a refreshed token. 403 is never routed here —\n * it means \"forbidden for this action\" and must not trigger logout.\n *\n * `emitAuthLost('unauthorized')` is deduped inside TokenManager, so it's a\n * no-op if `executeRefresh` already emitted 'refresh-failed'.\n */\n private async recoverFromAuthFailure(isRetry: boolean): Promise<boolean> {\n if (isRetry) {\n this.tokenManager.emitAuthLost('unauthorized');\n return false;\n }\n const refreshed = await this.tokenManager.forceRefresh();\n if (refreshed) return true;\n this.tokenManager.emitAuthLost('unauthorized');\n return false;\n }\n\n private async uploadFormOnce<T>(\n url: string,\n headers: Record<string, string>,\n formData: FormData,\n timeoutMs: number,\n ): Promise<T> {\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), timeoutMs);\n\n try {\n const response = await fetch(url, {\n method: 'POST',\n headers,\n body: formData,\n signal: controller.signal,\n });\n\n const responseBody = await this.parseResponse(response);\n\n if (!response.ok) {\n this.throwHttpError(response.status, responseBody);\n }\n\n return this.extractData<T>(responseBody);\n } catch (error) {\n if (error instanceof SpacelrError) throw error;\n\n if (error instanceof DOMException && error.name === 'AbortError') {\n throw new SpacelrTimeoutError(timeoutMs);\n }\n\n throw new SpacelrNetworkError(\n error instanceof Error ? error.message : 'Network request failed'\n );\n } finally {\n clearTimeout(timeoutId);\n }\n }\n\n private uploadFormWithProgress<T>(\n url: string,\n headers: Record<string, string>,\n formData: FormData,\n onProgress: (event: { loaded: number; total: number }) => void,\n timeoutMs: number,\n ): Promise<T> {\n return new Promise<T>((resolve, reject) => {\n const xhr = new XMLHttpRequest();\n xhr.open('POST', url);\n xhr.timeout = timeoutMs;\n\n for (const [key, value] of Object.entries(headers)) {\n xhr.setRequestHeader(key, value);\n }\n\n xhr.upload.addEventListener('progress', (e) => {\n if (e.lengthComputable) {\n onProgress({ loaded: e.loaded, total: e.total });\n }\n });\n\n xhr.addEventListener('load', () => {\n try {\n const contentType = xhr.getResponseHeader('content-type') ?? '';\n if (!contentType.includes('application/json')) {\n if (xhr.status >= 200 && xhr.status < 300) {\n resolve({ success: true, data: xhr.responseText } as unknown as T);\n } else if (xhr.status === 401 || xhr.status === 403) {\n // Surface as SpacelrAuthError for consistent typing with the\n // JSON path below. Only 401 actually triggers the upload retry\n // wrapper (uploadFormWithRetry filters on statusCode === 401);\n // 403 propagates unchanged as a permission denial.\n reject(new SpacelrAuthError(`HTTP ${xhr.status}`, xhr.status));\n } else {\n reject(new SpacelrNetworkError(`HTTP ${xhr.status}: ${xhr.responseText.slice(0, 200)}`));\n }\n return;\n }\n const body = JSON.parse(xhr.responseText);\n if (xhr.status >= 200 && xhr.status < 300) {\n resolve(this.extractData<T>(body));\n } else {\n this.throwHttpError(xhr.status, body);\n }\n } catch (error) {\n if (error instanceof SpacelrError) {\n reject(error);\n } else {\n reject(new SpacelrNetworkError('Failed to parse response'));\n }\n }\n });\n\n xhr.addEventListener('error', () => {\n reject(new SpacelrNetworkError('Network request failed'));\n });\n\n xhr.addEventListener('timeout', () => {\n xhr.abort();\n reject(new SpacelrTimeoutError(timeoutMs));\n });\n\n xhr.send(formData);\n });\n }\n\n private buildUrl(\n path: string,\n query?: Record<string, string | number | undefined>\n ): string {\n const cleanPath = path.startsWith('/') ? path : `/${path}`;\n // .well-known endpoints are served at the origin without the API prefix\n const base = cleanPath.startsWith('/.well-known')\n ? new URL(this.config.apiUrl).origin\n : this.config.apiUrl.replace(/\\/+$/, '');\n const url = new URL(`${base}${cleanPath}`);\n\n if (query) {\n for (const [key, value] of Object.entries(query)) {\n if (value !== undefined) {\n url.searchParams.set(key, String(value));\n }\n }\n }\n\n return url.toString();\n }\n\n private async buildHeaders(\n options: HttpRequestOptions\n ): Promise<Record<string, string>> {\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n 'x-client-id': this.config.clientId,\n 'x-project-id': this.config.projectId,\n ...options.headers,\n };\n\n if (options.authenticated) {\n const token = await this.tokenManager.getAccessToken();\n if (token) {\n headers['Authorization'] = `Bearer ${token}`;\n }\n }\n\n return headers;\n }\n\n private async buildFormHeaders(): Promise<Record<string, string>> {\n const headers: Record<string, string> = {\n 'x-client-id': this.config.clientId,\n 'x-project-id': this.config.projectId,\n };\n\n const token = await this.tokenManager.getAccessToken();\n if (token) {\n headers['Authorization'] = `Bearer ${token}`;\n }\n\n return headers;\n }\n\n private async parseResponse(\n response: Response\n ): Promise<ApiResponse | Record<string, unknown>> {\n const contentType = response.headers.get('content-type') ?? '';\n if (contentType.includes('application/json')) {\n return response.json();\n }\n const text = await response.text();\n return { success: response.ok, data: text };\n }\n\n private throwHttpError(\n statusCode: number,\n body: ApiResponse | Record<string, unknown>\n ): never {\n const apiBody = body as ApiResponse;\n const message =\n apiBody.error?.message ?? `HTTP ${statusCode}`;\n const code = apiBody.error?.code ?? `HTTP_${statusCode}`;\n const details = apiBody.error?.details;\n\n if (statusCode === 401 || statusCode === 403) {\n throw new SpacelrAuthError(message, statusCode, details);\n }\n\n throw new SpacelrError(message, code, statusCode, details);\n }\n\n private extractData<T>(body: ApiResponse | Record<string, unknown>): T {\n const apiBody = body as ApiResponse;\n // If the response wraps data in a standard envelope, extract it\n if ('success' in apiBody && apiBody.data !== undefined) {\n const data = apiBody.data as Record<string, unknown>;\n // Check if the response indicates email verification is required\n if (data['emailVerificationRequired'] === true) {\n throw new SpacelrEmailVerificationRequiredError(data['emailSent'] === true);\n }\n // Check if the response indicates a 2FA challenge\n if (data['twoFactorRequired'] === true && typeof data['twoFactorToken'] === 'string') {\n throw new SpacelrTwoFactorRequiredError(data['twoFactorToken']);\n }\n return apiBody.data as T;\n }\n // Check unwrapped responses\n const rawBody = body as Record<string, unknown>;\n if (rawBody['emailVerificationRequired'] === true) {\n throw new SpacelrEmailVerificationRequiredError(rawBody['emailSent'] === true);\n }\n if (rawBody['twoFactorRequired'] === true && typeof rawBody['twoFactorToken'] === 'string') {\n throw new SpacelrTwoFactorRequiredError(rawBody['twoFactorToken']);\n }\n // Otherwise return the whole body (e.g. OIDC endpoints return raw objects)\n return body as T;\n }\n}\n","import { StoredTokens, TokenStorage } from '../types';\n\nexport type { TokenStorage };\n\nexport class MemoryTokenStorage implements TokenStorage {\n private tokens: StoredTokens | null = null;\n\n async getTokens(): Promise<StoredTokens | null> {\n return this.tokens;\n }\n\n async setTokens(tokens: StoredTokens): Promise<void> {\n this.tokens = tokens;\n }\n\n async clearTokens(): Promise<void> {\n this.tokens = null;\n }\n}\n\nexport class BrowserTokenStorage implements TokenStorage {\n private readonly storageKey: string;\n\n constructor(storageKey = 'spacelr_tokens') {\n this.storageKey = storageKey;\n }\n\n async getTokens(): Promise<StoredTokens | null> {\n try {\n const raw = localStorage.getItem(this.storageKey);\n if (!raw) return null;\n return JSON.parse(raw) as StoredTokens;\n } catch {\n return null;\n }\n }\n\n async setTokens(tokens: StoredTokens): Promise<void> {\n try {\n localStorage.setItem(this.storageKey, JSON.stringify(tokens));\n } catch {\n // Quota exceeded or private browsing — silently ignore\n }\n }\n\n async clearTokens(): Promise<void> {\n try {\n localStorage.removeItem(this.storageKey);\n } catch {\n // localStorage unavailable — silently ignore\n }\n }\n}\n","import { StoredTokens, TokenStorage } from '../types';\nimport { MemoryTokenStorage } from './token-storage';\n\nexport type RefreshCallback = (refreshToken: string) => Promise<StoredTokens>;\nexport type AuthLostReason = 'refresh-failed' | 'unauthorized';\nexport type TokenRefreshedListener = (tokens: StoredTokens) => void;\nexport type AuthLostListener = (reason: AuthLostReason) => void;\n\nexport class TokenManager {\n private storage: TokenStorage;\n private refreshBufferSeconds: number;\n private refreshCallback: RefreshCallback | null = null;\n private refreshPromise: Promise<StoredTokens> | null = null;\n private tokenRefreshedListeners = new Set<TokenRefreshedListener>();\n private authLostListeners = new Set<AuthLostListener>();\n // Guard so callbacks that run during auth-loss handling (e.g. a logout()\n // that hits `/auth/logout` with a dead token) don't re-emit and loop.\n // Cleared by setTokens() / clearTokens() to re-arm for the next session.\n private authLostEmitted = false;\n\n constructor(storage?: TokenStorage, refreshBufferSeconds = 60) {\n this.storage = storage ?? new MemoryTokenStorage();\n this.refreshBufferSeconds = refreshBufferSeconds;\n }\n\n setRefreshCallback(callback: RefreshCallback): void {\n this.refreshCallback = callback;\n }\n\n async getAccessToken(): Promise<string | null> {\n const tokens = await this.storage.getTokens();\n if (!tokens) return null;\n\n if (this.isTokenExpired(tokens)) {\n const refreshed = await this.tryRefresh(tokens);\n return refreshed?.accessToken ?? null;\n }\n\n if (this.shouldRefresh(tokens)) {\n // Trigger background refresh but return current token\n this.tryRefresh(tokens).catch(() => {\n // Ignore background refresh failures (onAuthLost will have fired)\n });\n }\n\n return tokens.accessToken;\n }\n\n async setTokens(tokens: StoredTokens): Promise<void> {\n await this.storage.setTokens(tokens);\n this.authLostEmitted = false;\n }\n\n async clearTokens(): Promise<void> {\n await this.storage.clearTokens();\n this.refreshPromise = null;\n this.authLostEmitted = false;\n }\n\n async getStoredTokens(): Promise<StoredTokens | null> {\n return this.storage.getTokens();\n }\n\n /**\n * Force a refresh using the current stored refresh token.\n * Returns the new access token, or null if refresh is not possible or fails.\n * A rejecting custom `TokenStorage.getTokens()` is treated as \"no tokens\"\n * so callers (notably `HttpClient`'s 401 retry path) see a clean `null`\n * rather than an exception that would get wrapped as a network error.\n */\n async forceRefresh(): Promise<string | null> {\n // Once auth is lost, further refresh attempts are pointless and would\n // spam the refresh endpoint while the consumer is handling the logout.\n if (this.authLostEmitted) return null;\n let tokens: StoredTokens | null;\n try {\n tokens = await this.storage.getTokens();\n } catch {\n return null;\n }\n if (!tokens) return null;\n const refreshed = await this.tryRefresh(tokens);\n return refreshed?.accessToken ?? null;\n }\n\n onTokenRefreshed(listener: TokenRefreshedListener): () => void {\n this.tokenRefreshedListeners.add(listener);\n return () => this.tokenRefreshedListeners.delete(listener);\n }\n\n onAuthLost(listener: AuthLostListener): () => void {\n this.authLostListeners.add(listener);\n return () => this.authLostListeners.delete(listener);\n }\n\n emitAuthLost(reason: AuthLostReason): void {\n if (this.authLostEmitted) return;\n this.authLostEmitted = true;\n for (const listener of this.authLostListeners) {\n try {\n listener(reason);\n } catch {\n // Ignore listener errors\n }\n }\n }\n\n private isTokenExpired(tokens: StoredTokens): boolean {\n if (!tokens.expiresAt) return false;\n return Date.now() >= tokens.expiresAt * 1000;\n }\n\n private shouldRefresh(tokens: StoredTokens): boolean {\n if (!tokens.expiresAt || !tokens.refreshToken) return false;\n const bufferMs = this.refreshBufferSeconds * 1000;\n return Date.now() >= tokens.expiresAt * 1000 - bufferMs;\n }\n\n private async tryRefresh(\n tokens: StoredTokens\n ): Promise<StoredTokens | null> {\n if (!tokens.refreshToken || !this.refreshCallback) return null;\n\n // Deduplicate concurrent refresh calls — both the leader and any followers\n // must get `null` on failure, not a rejected promise. Otherwise parallel\n // callers of getAccessToken/forceRefresh will throw unexpectedly while the\n // leader gracefully returns null.\n if (this.refreshPromise) {\n try {\n return await this.refreshPromise;\n } catch {\n return null;\n }\n }\n\n this.refreshPromise = this.executeRefresh(tokens.refreshToken);\n\n try {\n return await this.refreshPromise;\n } catch {\n return null;\n } finally {\n this.refreshPromise = null;\n }\n }\n\n private async executeRefresh(refreshToken: string): Promise<StoredTokens> {\n // `tryRefresh` guards against a missing callback before reaching here.\n const callback = this.refreshCallback as RefreshCallback;\n try {\n const newTokens = await callback(refreshToken);\n await this.storage.setTokens(newTokens);\n this.emitTokenRefreshed(newTokens);\n return newTokens;\n } catch (error) {\n this.emitAuthLost('refresh-failed');\n throw error;\n }\n }\n\n private emitTokenRefreshed(tokens: StoredTokens): void {\n for (const listener of this.tokenRefreshedListeners) {\n try {\n listener(tokens);\n } catch {\n // Ignore listener errors\n }\n }\n }\n}\n","export enum FileVisibility {\n PRIVATE = 'private',\n SHARED = 'shared',\n PUBLIC = 'public',\n}\n\nexport enum GrantType {\n AUTHORIZATION_CODE = 'authorization_code',\n CLIENT_CREDENTIALS = 'client_credentials',\n REFRESH_TOKEN = 'refresh_token',\n}\n\nexport enum CodeChallengeMethod {\n PLAIN = 'plain',\n S256 = 'S256',\n}\n\nexport enum SharePermission {\n READ = 'read',\n WRITE = 'write',\n}\n\nexport interface ApiResponse<T = unknown> {\n success: boolean;\n data?: T;\n error?: {\n message: string;\n code: string;\n details?: Record<string, unknown>;\n };\n}\n","import { PKCEChallenge, CodeChallengeMethod } from '../types';\n\nfunction generateRandomBytes(length: number): Uint8Array {\n if (\n typeof globalThis.crypto !== 'undefined' &&\n globalThis.crypto.getRandomValues\n ) {\n const bytes = new Uint8Array(length);\n globalThis.crypto.getRandomValues(bytes);\n return bytes;\n }\n\n // Node.js fallback\n const nodeCrypto = require('crypto') as typeof import('crypto');\n return new Uint8Array(nodeCrypto.randomBytes(length));\n}\n\nfunction base64UrlEncode(buffer: Uint8Array): string {\n let binary = '';\n for (let i = 0; i < buffer.length; i++) {\n binary += String.fromCharCode(buffer[i]);\n }\n\n const base64 = btoa(binary);\n return base64.replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/, '');\n}\n\nasync function sha256(input: string): Promise<Uint8Array> {\n if (\n typeof globalThis.crypto !== 'undefined' &&\n globalThis.crypto.subtle\n ) {\n const encoder = new TextEncoder();\n const data = encoder.encode(input);\n const hash = await globalThis.crypto.subtle.digest('SHA-256', data);\n return new Uint8Array(hash);\n }\n\n // Node.js fallback\n const nodeCrypto = require('crypto') as typeof import('crypto');\n const hash = nodeCrypto.createHash('sha256').update(input).digest();\n return new Uint8Array(hash);\n}\n\nexport async function generatePKCEChallenge(): Promise<PKCEChallenge> {\n const randomBytes = generateRandomBytes(32);\n const codeVerifier = base64UrlEncode(randomBytes);\n\n const hashBytes = await sha256(codeVerifier);\n const codeChallenge = base64UrlEncode(hashBytes);\n\n return {\n codeVerifier,\n codeChallenge,\n codeChallengeMethod: CodeChallengeMethod.S256,\n };\n}\n","import { io, Socket } from 'socket.io-client';\n\n// Wait this long before retrying a failed socket rebuild. Short enough that\n// a transient network stumble heals without waiting for a user-visible wake\n// event, long enough not to hammer an unreachable server.\nconst REBUILD_RETRY_DELAY_MS = 5000;\n\n/** Ack error codes that indicate a permanent server-side rejection — evict\n * the subscription on reconnect rather than retrying forever. Transient\n * codes (redis-unavailable, replay-error, disconnected) stay in the map so\n * the next reconnect tries again. */\nconst PERMANENT_STREAM_ACK_ERRORS = new Set<string>([\n 'not-stream-collection',\n 'Collection not found',\n 'Invalid sinceId format',\n 'Not a member of this project',\n 'Subscribe denied',\n]);\n\nexport type ConnectionState = 'connected' | 'reconnecting' | 'disconnected';\n\n// NOTE: This interface is duplicated in @spacelr-workspace/shared-types (database-events.ts).\n// The SDK cannot import from shared-types since it ships as a standalone npm package.\n// Keep both definitions in sync when making changes.\nexport interface DatabaseChangeEvent {\n type: 'insert' | 'update' | 'delete';\n projectId: string;\n collectionName: string;\n documentId: string;\n document?: Record<string, unknown>;\n timestamp: number;\n /**\n * Redis Stream entry ID (e.g. \"1745234123-0\") for stream-mode events, or a\n * synthesised `${projectId}:${collectionName}:${timestamp}-${documentId}`\n * for pubsub-mode events. Used by function triggers as an idempotency key.\n */\n eventId?: string;\n /**\n * Identifies the origin of the write. Stamped on the envelope (never in the\n * document body) so function-service can run a tamper-resistant recursion guard\n * (internal TCP caller can spoof — see PR notes). Absent on legacy events\n * published before this field existed.\n */\n _source?: 'user' | 'function';\n /**\n * When `_source === 'function'`, the id of the function whose runtime wrote\n * the document. Used to skip self-chain events without blocking legitimate\n * cross-function chains.\n */\n _sourceFunctionId?: string;\n /**\n * Hop-counter on function-originated events. User writes have no value.\n * Each function-write increments the incoming envelope's depth by one,\n * so an A→B→A loop bumps the counter on every hop. The reader rejects\n * dispatch when depth >= MAX_CHAIN_DEPTH so mutual chains terminate\n * deterministically rather than relying on the bounded queue to drop\n * events.\n */\n _chainDepth?: number;\n}\n\nexport interface RealtimeConfig {\n baseUrl: string;\n getToken: () => Promise<string | null>;\n /**\n * Optional subscription to token-refresh events. When wired, the realtime\n * client will push the fresh access token to the server via a\n * `reauthenticate` message so the open socket stays authenticated without\n * dropping the connection.\n */\n onTokenRefreshed?: (listener: (accessToken: string) => void) => () => void;\n}\n\nexport type GapReason =\n | 'outside-retention-window'\n | 'redis-unavailable'\n | 'replay-error'\n | 'replay-truncated';\n\nexport interface StreamGapInfo {\n projectId: string;\n collectionName: string;\n /** Configured retention as a human-readable string (e.g. \"maxLen: 10000\", \"24h\", or \"unknown\"). */\n retentionWindow: string;\n reason: GapReason;\n}\n\nexport interface StreamSubscriptionOptions {\n projectId: string;\n collectionName: string;\n sinceId?: string;\n where?: Record<string, string | number | boolean>;\n /**\n * Called for each delivered event. Awaited — the cursor does NOT advance\n * until this resolves. If the callback throws, the cursor is NOT advanced\n * and `onError` is called; the event is not retried (at-most-once on\n * handler failure, consistent with the server-side StreamReaderService).\n */\n onEvent: (entry: { eventId: string; event: DatabaseChangeEvent }) => void | Promise<void>;\n /** Called when the server emits an event-gap. See GapReason for the four codes. */\n onGap?: (info: StreamGapInfo) => void;\n /** Called on handshake rejection (not-stream-collection, invalid sinceId, etc.) and handler failures. */\n onError?: (error: Error) => void;\n}\n\ninterface StreamSubscriptionState {\n options: StreamSubscriptionOptions;\n /** Last eventId successfully delivered to onEvent. Advances after each resolved handler. */\n lastDeliveredId: string | null;\n /** `stream:<projectId>:<collectionName>` — used as the Map key. */\n streamKey: string;\n /** Serializes onEvent calls for THIS subscriber so cursor advances in order\n * even when the socket delivers multiple events before the previous handler resolves. */\n dispatchQueue: Promise<void>;\n}\n\ninterface StreamSubscribeAck {\n error?: string;\n message?: string;\n subscribed?: string;\n lastDeliveredId?: string | null;\n}\n\nexport class RealtimeClient {\n private socket: Socket | null = null;\n private config: RealtimeConfig;\n private subscriptions = new Map<string, Set<(event: DatabaseChangeEvent) => void>>();\n private connecting: Promise<void> | null = null;\n // Store original where objects per room for reconnect resubscription\n private roomWhereMap = new Map<string, Record<string, string | number | boolean>>();\n private unsubscribeFromTokenRefreshed: (() => void) | null = null;\n // Wake-up listeners (browser only) — recover from long OS suspends where\n // socket.io's internal reconnect loop may have already given up.\n private onVisibilityChange: (() => void) | null = null;\n private onOnline: (() => void) | null = null;\n // Set by `disconnect()`; checked by async paths (rebuildSocket, deferred\n // retries) to avoid reviving a client the consumer has torn down.\n private disposed = false;\n // Scheduled retry after a failed `rebuildSocket()`; cleared when it fires\n // or when `disconnect()` tears down.\n private rebuildRetryTimer: ReturnType<typeof setTimeout> | null = null;\n private connectionState: ConnectionState = 'disconnected';\n private connectionStateListeners = new Set<(state: ConnectionState) => void>();\n private streamSubscriptions = new Map<string, Set<StreamSubscriptionState>>();\n\n constructor(config: RealtimeConfig) {\n this.config = config;\n }\n\n async subscribe(\n projectId: string,\n collectionName: string,\n callback: (event: DatabaseChangeEvent) => void,\n onError?: (error: Error) => void,\n where?: Record<string, string | number | boolean>,\n ): Promise<() => void> {\n this.ensureWakeListeners();\n if (this.connectionState === 'disconnected') {\n this.setConnectionState('reconnecting');\n }\n await this.ensureConnected();\n\n const room = this.buildRoomKey(projectId, collectionName, where);\n\n // Track the callback\n if (!this.subscriptions.has(room)) {\n this.subscriptions.set(room, new Set());\n }\n const callbacks = this.subscriptions.get(room);\n callbacks?.add(callback);\n\n // If this is the first subscription for this room, tell the server\n if (callbacks?.size === 1) {\n if (where && Object.keys(where).length > 0) {\n this.roomWhereMap.set(room, where);\n }\n const payload: Record<string, unknown> = { projectId, collectionName };\n if (where && Object.keys(where).length > 0) {\n payload['where'] = where;\n }\n this.socket?.emit(\n 'subscribe',\n payload,\n (response: { subscribed?: string; error?: string }) => {\n if (response.error && onError) {\n onError(new Error(response.error));\n }\n },\n );\n }\n\n // Return unsubscribe function\n return () => {\n const callbacks = this.subscriptions.get(room);\n if (callbacks) {\n callbacks.delete(callback);\n if (callbacks.size === 0) {\n this.subscriptions.delete(room);\n this.roomWhereMap.delete(room);\n const payload: Record<string, unknown> = { projectId, collectionName };\n if (where && Object.keys(where).length > 0) {\n payload['where'] = where;\n }\n this.socket?.emit('unsubscribe', payload);\n }\n }\n };\n }\n\n /**\n * Subscribe to a stream-mode collection using Redis Streams replay +\n * cursor-based delivery. Parallel to `subscribe()` (which targets\n * pubsub-mode collections). The server validates that the collection is\n * configured in stream mode and rejects the handshake otherwise.\n *\n * The per-subscription cursor (`lastDeliveredId`) advances after each\n * `onEvent` promise resolves, so reconnects resume from the last delivered\n * event rather than the originally-subscribed `sinceId`.\n */\n async subscribeWithCursor(\n options: StreamSubscriptionOptions,\n ): Promise<() => void> {\n this.ensureWakeListeners();\n if (this.connectionState === 'disconnected') {\n this.setConnectionState('reconnecting');\n }\n await this.ensureConnected();\n\n const streamKey = `stream:${options.projectId}:${options.collectionName}`;\n const state: StreamSubscriptionState = {\n options,\n lastDeliveredId: options.sinceId ?? null,\n streamKey,\n dispatchQueue: Promise.resolve(),\n };\n\n // Dedup path: if another local subscriber already holds the server-side\n // slot for this streamKey, just piggy-back on the existing fan-out. The\n // gateway collapses duplicate `subscribe-events` per socket per streamKey\n // (replacing the prior reader), so re-emitting would tear down the other\n // subscriber's server-side state.\n const existing = this.streamSubscriptions.get(streamKey);\n if (existing && existing.size > 0) {\n existing.add(state);\n return () => this.unsubscribeStream(state);\n }\n\n // Register BEFORE the handshake so replay `event` / `event-gap` frames\n // emitted during subscribe fanout find this subscriber. If the ack comes\n // back with an error, we tear down the registration in the catch block.\n const set = new Set<StreamSubscriptionState>();\n set.add(state);\n this.streamSubscriptions.set(streamKey, set);\n\n try {\n const ack = await this.emitSubscribeEvents(state);\n if (ack.error) {\n // The primary handshake failed. Evict this subscriber AND any other\n // subscribers that joined via the dedup path for the same streamKey —\n // they have no active server subscription, so they would silently miss\n // events until the next full reconnect.\n const message = ack.message ?? ack.error;\n const err = new Error(message);\n this.evictStreamKey(streamKey, err, state);\n options.onError?.(err);\n throw err;\n }\n return () => this.unsubscribeStream(state);\n } catch (err) {\n // Safety net: emitSubscribeEvents always resolves, so this catch only\n // fires when `throw err` from the ack-error branch re-runs here. evictStreamKey\n // is idempotent — the second call is a no-op if the streamKey entry was already\n // deleted. Kept in case a future edit introduces a throw before the error branch.\n const error = err instanceof Error ? err : new Error(String(err));\n this.evictStreamKey(streamKey, error, state);\n throw error;\n }\n }\n\n /**\n * Tear down the realtime client permanently. After this call the instance\n * is disposed — subsequent `subscribe()` calls will not re-establish a\n * connection. Create a new `RealtimeClient` (via `createClient`) if you\n * need to reconnect after a logout.\n */\n disconnect(): void {\n this.setConnectionState('disconnected');\n this.disposed = true;\n if (this.rebuildRetryTimer) {\n clearTimeout(this.rebuildRetryTimer);\n this.rebuildRetryTimer = null;\n }\n if (this.unsubscribeFromTokenRefreshed) {\n this.unsubscribeFromTokenRefreshed();\n this.unsubscribeFromTokenRefreshed = null;\n }\n this.detachWakeListeners();\n if (this.socket) {\n this.socket.disconnect();\n this.socket = null;\n }\n this.subscriptions.clear();\n this.roomWhereMap.clear();\n this.streamSubscriptions.clear();\n this.connecting = null;\n }\n\n getConnectionState(): ConnectionState {\n return this.connectionState;\n }\n\n onConnectionStateChanged(listener: (state: ConnectionState) => void): () => void {\n this.connectionStateListeners.add(listener);\n return () => this.connectionStateListeners.delete(listener);\n }\n\n private setConnectionState(next: ConnectionState): void {\n if (this.connectionState === next) return;\n this.connectionState = next;\n for (const listener of this.connectionStateListeners) {\n try {\n listener(next);\n } catch {\n // Ignore listener errors, matching onTokenRefreshed / onAuthLost.\n }\n }\n }\n\n private buildRoomKey(\n projectId: string,\n collectionName: string,\n where?: Record<string, string | number | boolean>,\n ): string {\n const base = `db:${projectId}:${collectionName}`;\n if (!where || Object.keys(where).length === 0) {\n return base;\n }\n const filterStr = Object.keys(where)\n .sort()\n .map((k) => `${k}=${String(where[k])}`)\n .join('&');\n return `${base}?${filterStr}`;\n }\n\n private async ensureConnected(): Promise<void> {\n if (this.socket?.connected) return;\n\n if (this.connecting) {\n return this.connecting;\n }\n\n this.connecting = this.connect();\n try {\n await this.connecting;\n } finally {\n this.connecting = null;\n }\n }\n\n private async connect(): Promise<void> {\n const token = await this.config.getToken();\n if (!token) {\n throw new Error('No authentication token available');\n }\n\n // Derive WebSocket URL from the API URL\n const wsUrl = this.config.baseUrl.replace(/\\/api\\/v\\d+\\/?$/, '');\n\n return new Promise<void>((resolve, reject) => {\n let initialConnect = true;\n\n this.socket = io(`${wsUrl}/database`, {\n auth: async (cb: (data: Record<string, unknown>) => void) => {\n try {\n const freshToken = await this.config.getToken();\n cb({ token: freshToken });\n } catch {\n cb({ token: null });\n }\n },\n transports: ['websocket'],\n reconnection: false, // Disabled until first successful connect\n });\n\n this.socket.on('authenticated', () => {\n // If the consumer called disconnect() while an in-flight authenticate\n // was in progress, don't emit a spurious `connected` on a disposed\n // client. socket.io can deliver a queued event after the socket was\n // detached — the `this.socket = null` and disposed flag are set but\n // the handler is still bound to the old socket instance.\n if (this.disposed) return;\n this.setConnectionState('connected');\n if (initialConnect) {\n initialConnect = false;\n // Enable reconnection after first successful connect\n if (this.socket) {\n this.socket.io.opts.reconnection = true;\n this.socket.io.opts.reconnectionDelay = 1000;\n this.socket.io.opts.reconnectionDelayMax = 5000;\n this.socket.io.opts.reconnectionAttempts = 50;\n }\n // Wire up token-refresh → reauthenticate so the open socket picks up\n // new JWTs without disconnecting.\n if (this.config.onTokenRefreshed && !this.unsubscribeFromTokenRefreshed) {\n this.unsubscribeFromTokenRefreshed = this.config.onTokenRefreshed(\n (accessToken) => {\n this.socket?.emit('reauthenticate', { token: accessToken });\n },\n );\n }\n resolve();\n } else {\n // Reconnect path — server has now set socket.data.userId. It is\n // safe to re-subscribe. (Doing this on 'connect' races the async\n // handleConnection and produces silent \"Not authenticated\" errors.)\n this.resubscribeAll();\n void this.resubscribeAllStreams();\n }\n });\n\n this.socket.on('connect_error', (err) => {\n if (initialConnect) {\n reject(new Error(`WebSocket connection failed: ${err.message}`));\n }\n });\n\n // socket.io has exhausted its reconnection attempts (e.g. after a long\n // laptop suspend where all retries burned in a few seconds on resume).\n // Build a fresh socket so the session can recover.\n this.socket.io.on('reconnect_failed', () => {\n void this.rebuildSocket();\n });\n\n this.socket.on('db:event', (event: DatabaseChangeEvent) => {\n const base = `db:${event.projectId}:${event.collectionName}`;\n\n // Dispatch to whole-collection subscribers via direct Map lookup\n const baseCallbacks = this.subscriptions.get(base);\n if (baseCallbacks) {\n for (const cb of baseCallbacks) {\n try { cb(event); } catch { /* ignore callback errors */ }\n }\n }\n\n // Dispatch to matching filtered subscriptions\n for (const [room, callbacks] of this.subscriptions) {\n if (room.startsWith(`${base}?`) && this.eventMatchesRoom(event, room)) {\n for (const cb of callbacks) {\n try { cb(event); } catch { /* ignore callback errors */ }\n }\n }\n }\n });\n\n this.socket.on('event', (payload: DatabaseChangeEvent & { eventId?: string }) => {\n this.dispatchStreamEvent(payload).catch(() => undefined);\n });\n\n this.socket.on('event-gap', (info: StreamGapInfo) => {\n const streamKey = `stream:${info.projectId}:${info.collectionName}`;\n const set = this.streamSubscriptions.get(streamKey);\n if (!set) return;\n for (const state of set) {\n state.options.onGap?.(info);\n }\n });\n\n this.socket.on('disconnect', () => {\n if (!this.disposed) {\n this.setConnectionState('reconnecting');\n }\n });\n });\n }\n\n private eventMatchesRoom(event: DatabaseChangeEvent, room: string): boolean {\n const base = `db:${event.projectId}:${event.collectionName}`;\n if (room === base) return true;\n if (!room.startsWith(`${base}?`)) return false;\n\n // For filtered rooms, check the document against the where filter\n const where = this.roomWhereMap.get(room);\n if (!where) return true; // No filter stored for this room\n if (!event.document) return false; // Delete events without document can't match a filter\n\n for (const [key, value] of Object.entries(where)) {\n const docValue = event.document[key];\n if (Array.isArray(docValue)) {\n if (!docValue.some((item) => String(item) === String(value))) {\n return false;\n }\n } else if (String(docValue) !== String(value)) {\n return false;\n }\n }\n return true;\n }\n\n /**\n * Tear down the current socket and create a fresh one. Used by both\n * `reconnect_failed` (socket.io gave up) and the visibility/online wake-up\n * listeners. Preserves the `subscriptions` map so existing rooms can be\n * replayed to the server on the new connection. If the rebuild itself\n * fails it schedules one retry after 5 s — beyond that we rely on the\n * next wake-up event (visibility/online) to try again.\n */\n private async rebuildSocket(): Promise<void> {\n if (this.disposed || this.connecting) return;\n\n const previous = this.socket;\n this.socket = null;\n if (previous) previous.disconnect();\n\n this.setConnectionState('reconnecting');\n\n try {\n await this.ensureConnected();\n this.handlePostRebuild();\n } catch {\n this.scheduleRebuildRetry();\n }\n }\n\n private handlePostRebuild(): void {\n // Capture into a local first — TS narrows `this.socket` to `null` after\n // the earlier assignment and doesn't re-widen through the await above.\n const newSocket: Socket | null = this.socket;\n if (this.disposed) {\n this.socket = null;\n newSocket?.disconnect();\n return;\n }\n // The server has no record of the subscriptions we held on the old\n // (dead) socket, so replay them here. The new socket's `authenticated`\n // handler took the `initialConnect = true` branch and doesn't resubscribe.\n if (this.subscriptions.size > 0) {\n this.resubscribeAll();\n }\n if (this.streamSubscriptions.size > 0) {\n void this.resubscribeAllStreams();\n }\n }\n\n private scheduleRebuildRetry(): void {\n // A transient rebuild failure (token fetch rejected, connect_error) —\n // retry once so recovery doesn't stall waiting for a wake event.\n if (this.disposed || this.rebuildRetryTimer) return;\n this.rebuildRetryTimer = setTimeout(() => {\n this.rebuildRetryTimer = null;\n void this.rebuildSocket();\n }, REBUILD_RETRY_DELAY_MS);\n (this.rebuildRetryTimer as unknown as { unref?: () => void }).unref?.();\n }\n\n private ensureWakeListeners(): void {\n if (typeof document === 'undefined' || typeof window === 'undefined') return;\n if (this.onVisibilityChange !== null) return;\n\n const wakeIfUnhealthy = () => {\n if (typeof document !== 'undefined' && document.visibilityState !== 'visible') return;\n if (this.socket?.connected) return;\n if (this.connecting) return;\n void this.rebuildSocket();\n };\n\n this.onVisibilityChange = wakeIfUnhealthy;\n this.onOnline = wakeIfUnhealthy;\n document.addEventListener('visibilitychange', this.onVisibilityChange);\n window.addEventListener('online', this.onOnline);\n }\n\n private detachWakeListeners(): void {\n if (typeof document !== 'undefined' && this.onVisibilityChange) {\n document.removeEventListener('visibilitychange', this.onVisibilityChange);\n }\n if (typeof window !== 'undefined' && this.onOnline) {\n window.removeEventListener('online', this.onOnline);\n }\n this.onVisibilityChange = null;\n this.onOnline = null;\n }\n\n private resubscribeAll(): void {\n for (const [room] of this.subscriptions) {\n // Parse room format: db:{projectId}:{collectionName} or db:{projectId}:{collectionName}?filter\n const queryIdx = room.indexOf('?');\n const basePart = queryIdx >= 0 ? room.substring(0, queryIdx) : room;\n const parts = basePart.split(':');\n if (parts.length >= 3) {\n const projectId = parts[1];\n const collectionName = parts.slice(2).join(':');\n const payload: Record<string, unknown> = { projectId, collectionName };\n\n // Use stored where object to preserve original types (number, boolean)\n const where = this.roomWhereMap.get(room);\n if (where) {\n payload['where'] = where;\n }\n\n this.socket?.emit(\n 'subscribe',\n payload,\n (response: { subscribed?: string; error?: string }) => {\n if (response?.error) {\n // Server rejected resubscription — clean up the stale subscription\n this.subscriptions.delete(room);\n this.roomWhereMap.delete(room);\n }\n },\n );\n }\n }\n }\n\n private emitSubscribeEvents(\n state: StreamSubscriptionState,\n ): Promise<StreamSubscribeAck> {\n return new Promise((resolve) => {\n if (!this.socket) {\n resolve({ error: 'disconnected' });\n return;\n }\n const socket = this.socket;\n\n // socket.io does not call ack callbacks when the socket disconnects mid-handshake.\n // Resolve on disconnect so the Promise can't hang.\n const onDisconnect = (): void => {\n socket.off('disconnect', onDisconnect);\n resolve({ error: 'disconnected' });\n };\n socket.once('disconnect', onDisconnect);\n\n socket.emit(\n 'subscribe-events',\n this.buildStreamPayload(state),\n (ack: StreamSubscribeAck) => {\n socket.off('disconnect', onDisconnect);\n resolve(ack);\n },\n );\n });\n }\n\n private buildStreamPayload(state: StreamSubscriptionState): Record<string, unknown> {\n const { projectId, collectionName, where } = state.options;\n const payload: Record<string, unknown> = { projectId, collectionName };\n // Use the current cursor (advanced by onEvent) so a reconnect picks up\n // from the last delivered event, not the originally-subscribed sinceId.\n if (state.lastDeliveredId) payload['sinceId'] = state.lastDeliveredId;\n if (where && Object.keys(where).length > 0) payload['where'] = where;\n return payload;\n }\n\n private unsubscribeStream(state: StreamSubscriptionState): void {\n const set = this.streamSubscriptions.get(state.streamKey);\n if (!set) return;\n set.delete(state);\n if (set.size === 0) {\n this.streamSubscriptions.delete(state.streamKey);\n // Only release the server-side reader slot when no local subscriber\n // remains for this streamKey. Avoids tearing down a shared slot when\n // one of several local consumers unsubscribes.\n this.socket?.emit('unsubscribe-events', {\n projectId: state.options.projectId,\n collectionName: state.options.collectionName,\n });\n }\n }\n\n private dispatchStreamEvent(\n payload: DatabaseChangeEvent & { eventId?: string },\n ): Promise<void> {\n if (!payload.eventId) return Promise.resolve();\n const streamKey = `stream:${payload.projectId}:${payload.collectionName}`;\n const set = this.streamSubscriptions.get(streamKey);\n if (!set) return Promise.resolve();\n\n const entryId = payload.eventId;\n // Per-subscriber queue — serializes onEvent calls within a subscriber while\n // allowing different subscribers to run in parallel. Without this, two\n // events arriving while the first handler is still awaiting could advance\n // the cursor out of order.\n for (const state of set) {\n state.dispatchQueue = state.dispatchQueue.then(async () => {\n // Guard: subscription may have been cancelled while this was queued.\n const liveSet = this.streamSubscriptions.get(state.streamKey);\n if (!liveSet?.has(state)) return;\n\n try {\n await state.options.onEvent({ eventId: entryId, event: payload });\n state.lastDeliveredId = entryId;\n } catch (err) {\n // At-most-once on handler failure — don't advance the cursor.\n // Surface via onError if provided.\n const error = err instanceof Error ? err : new Error(String(err));\n try {\n state.options.onError?.(error);\n } catch {\n // onError must not break the dispatch chain — swallow any throw.\n }\n }\n });\n }\n return Promise.resolve();\n }\n\n private async resubscribeAllStreams(): Promise<void> {\n // Re-subscribe any active stream subscriptions with the CURRENT cursor so\n // reconnects pick up exactly where we left off. Emit ONE handshake per\n // streamKey — the gateway collapses duplicate `subscribe-events` per\n // socket per streamKey, so re-emitting for every local subscriber would\n // tear down prior state. Replay events delivered over the single\n // handshake fan out to all local subscribers via dispatchStreamEvent.\n //\n // For shared-slot subscriptions, use the EARLIEST `lastDeliveredId` across\n // subscribers so none miss events they had not yet observed.\n const work: Promise<void>[] = [];\n for (const set of this.streamSubscriptions.values()) {\n const states = Array.from(set);\n if (states.length === 0) continue;\n const primary = this.pickEarliestCursorState(states);\n work.push(this.resubscribeOne(primary, set));\n }\n try {\n await Promise.all(work);\n } catch {\n // Defensive — each resubscribeOne has its own try/catch,\n // but add a belt-and-suspenders here for the void call site.\n }\n }\n\n private async resubscribeOne(\n primary: StreamSubscriptionState,\n set: Set<StreamSubscriptionState>,\n ): Promise<void> {\n try {\n const ack = await this.emitSubscribeEvents(primary);\n if (ack.error) {\n const err = new Error(ack.message ?? ack.error);\n for (const state of [...set]) {\n state.options.onError?.(err);\n }\n if (PERMANENT_STREAM_ACK_ERRORS.has(ack.error)) {\n // Permanent rejection — don't retry on the next reconnect.\n this.streamSubscriptions.delete(primary.streamKey);\n }\n return;\n }\n\n // Happy path — ack succeeded. If the local set was fully torn down during\n // the await (last unsubscribe raced with the reconnect), release the freshly\n // acquired server-side reader slot so we don't leak it.\n if (!this.streamSubscriptions.has(primary.streamKey)) {\n this.socket?.emit('unsubscribe-events', {\n projectId: primary.options.projectId,\n collectionName: primary.options.collectionName,\n });\n }\n } catch (err) {\n const error = err instanceof Error ? err : new Error(String(err));\n for (const state of [...set]) {\n state.options.onError?.(error);\n }\n // Thrown path is exceptional (emitSubscribeEvents always resolves), treat\n // as transient and let the next reconnect retry.\n }\n }\n\n private pickEarliestCursorState(\n states: StreamSubscriptionState[],\n ): StreamSubscriptionState {\n // Stream entry IDs look like \"<ms>-<seq>\"; compare numerically so lexical\n // ordering doesn't produce wrong results once the ms digit count changes.\n const parse = (id: string | null): [number, number] => {\n // null = \"no cursor preference\" — never overrides a real cursor.\n if (!id) return [Number.MAX_SAFE_INTEGER, Number.MAX_SAFE_INTEGER];\n const [ms, seq = '0'] = id.split('-');\n return [Number(ms), Number(seq)];\n };\n let earliest = states[0];\n let [eMs, eSeq] = parse(earliest.lastDeliveredId);\n for (let i = 1; i < states.length; i++) {\n const [ms, seq] = parse(states[i].lastDeliveredId);\n if (ms < eMs || (ms === eMs && seq < eSeq)) {\n earliest = states[i];\n eMs = ms;\n eSeq = seq;\n }\n }\n return earliest;\n }\n\n /**\n * Evict the primary subscriber AND every sibling that joined the same\n * streamKey via the dedup path. Fires `onError` on all siblings, then\n * removes the entire streamKey entry from `streamSubscriptions`.\n */\n private evictStreamKey(\n streamKey: string,\n err: Error,\n primary: StreamSubscriptionState,\n ): void {\n const set = this.streamSubscriptions.get(streamKey);\n if (!set) return;\n for (const sibling of [...set]) {\n if (sibling !== primary) {\n sibling.options.onError?.(err);\n }\n set.delete(sibling);\n }\n this.streamSubscriptions.delete(streamKey);\n }\n}\n","/**\n * Persist the stream-mode resume cursor across app restarts and reconnects.\n *\n * Pass an implementation to `subscribeEvents({ cursorStorage })` and the SDK\n * will load the previous cursor before subscribing and save after each event\n * is handled. All methods may be sync or async — the SDK awaits the returned\n * value so React Native's AsyncStorage and other async backends work without\n * extra adapter code.\n */\nexport interface CursorStorage {\n /**\n * Return the saved cursor for this key, or `null` if none.\n *\n * An empty string is also treated as \"no cursor\" — Redis stream entry ids\n * are never empty, so an empty value is always the result of a misconfigured\n * adapter or a zero-length write. Returning `null` is preferred for clarity.\n */\n load(key: string): Promise<string | null> | string | null;\n /** Persist the cursor for this key. Called after each delivered event. */\n save(key: string, cursor: string): Promise<void> | void;\n}\n\n/**\n * In-memory cursor store. Useful for tests and short-lived scripts; state is\n * lost on process exit. Pass this to `subscribeEvents({ cursorStorage })`\n * when you want non-persistent in-memory resume behaviour within a single\n * session — omitting `cursorStorage` entirely disables persistence instead.\n */\nexport function memoryCursorStorage(): CursorStorage {\n const store = new Map<string, string>();\n return {\n load(key) {\n return store.get(key) ?? null;\n },\n save(key, cursor) {\n store.set(key, cursor);\n },\n };\n}\n\n/**\n * Browser `localStorage`-backed cursor store. Gracefully no-ops in\n * non-browser environments (Node without a `localStorage` polyfill), so\n * isomorphic code can use this factory unconditionally.\n *\n * @param prefix Key prefix applied to every stored entry. Defaults to\n * `spacelr:cursor:` so multiple SDK consumers on the same origin don't\n * collide.\n */\nexport function localStorageCursorStorage(\n prefix = 'spacelr:cursor:',\n): CursorStorage {\n // Sandboxed cross-origin iframes and browsers with storage disabled throw\n // `SecurityError` on the property *access* itself — not just on method\n // calls — so the guard has to wrap the read, not just typeof-check.\n const storage = ((): Storage | null => {\n try {\n const candidate = (globalThis as { localStorage?: Storage }).localStorage;\n return typeof candidate === 'undefined' ? null : candidate;\n } catch {\n return null;\n }\n })();\n\n return {\n load(key) {\n if (!storage) return null;\n try {\n return storage.getItem(prefix + key);\n } catch {\n return null;\n }\n },\n save(key, cursor) {\n if (!storage) return;\n try {\n storage.setItem(prefix + key, cursor);\n } catch {\n // Quota exceeded or storage disabled — silently drop; cursor\n // persistence is a best-effort resume hint, not a correctness\n // guarantee.\n }\n },\n };\n}\n","import { HttpClient, TokenManager, generatePKCEChallenge } from '../core';\nimport {\n SpacelrClientConfig,\n GrantType,\n LoginParams,\n LoginResponse,\n RegisterParams,\n RegisterResponse,\n TokenResponse,\n UserInfo,\n UserProfile,\n AuthorizationUrlParams,\n ExchangeCodeParams,\n PKCEChallenge,\n OpenIDConfiguration,\n JWKSResponse,\n TwoFactorVerifyParams,\n} from '../types';\n\nexport type AuthState = 'authenticated' | 'unauthenticated';\nexport type AuthStateListener = (state: AuthState) => void | Promise<void>;\n\nexport class AuthModule {\n private http: HttpClient;\n private tokenManager: TokenManager;\n private config: SpacelrClientConfig;\n private stateListeners = new Set<AuthStateListener>();\n private unsubscribeAuthLost!: () => void;\n private lastEmittedState: AuthState | null = null;\n\n constructor(\n http: HttpClient,\n tokenManager: TokenManager,\n config: SpacelrClientConfig\n ) {\n this.http = http;\n this.tokenManager = tokenManager;\n this.config = config;\n\n // Wire up refresh callback to avoid circular deps\n this.tokenManager.setRefreshCallback(async (refreshToken: string) => {\n const result = await this.refresh(refreshToken);\n const expiresAt = result.expires_in\n ? Math.floor(Date.now() / 1000) + result.expires_in\n : undefined;\n return {\n accessToken: result.access_token,\n refreshToken: result.refresh_token,\n expiresAt,\n };\n });\n\n // A failed refresh (triggered by 401 / refresh endpoint error) drops the\n // session — mirror that into our auth-state subscribers so UIs can react.\n // Keep the unsubscribe so `dispose()` can detach if this AuthModule is\n // ever discarded while the TokenManager lives on (shared client, HMR).\n this.unsubscribeAuthLost = this.tokenManager.onAuthLost(() =>\n this.emitState('unauthenticated'),\n );\n }\n\n /**\n * Returns true if a non-expired access token is currently in storage.\n * Does NOT make a network request — safe for route guards and other\n * hot paths that run on every navigation.\n *\n * A token within the refresh buffer (about to expire) still counts as\n * authenticated because the next protected request will auto-refresh it.\n *\n * Any error from the underlying TokenStorage (corrupt JSON, quota, etc.)\n * is treated as \"not authenticated\" rather than propagated, so route\n * guards can't be crashed by a misbehaving storage backend.\n */\n async isAuthenticated(): Promise<boolean> {\n try {\n const tokens = await this.tokenManager.getStoredTokens();\n if (!tokens?.accessToken) return false;\n if (tokens.expiresAt && tokens.expiresAt * 1000 <= Date.now()) return false;\n return true;\n } catch {\n return false;\n }\n }\n\n /**\n * Subscribe to auth-state transitions. The callback fires:\n * - 'authenticated' after a successful login/register/exchange/2FA-verify\n * - 'unauthenticated' after logout or when a token refresh fails\n *\n * Only fires for transitions that happen after the subscription. If the\n * user is already logged in at subscribe time (e.g. tokens restored from\n * storage on app boot), no 'authenticated' event is emitted — call\n * `isAuthenticated()` once up-front for the initial state.\n *\n * Silent token refreshes do NOT produce an event (auth state is\n * unchanged). Subscribe to `spacelrClient.onTokenRefreshed(...)` if you\n * need to observe successful refreshes.\n *\n * Listener may return `void` or `Promise<void>`. Rejections are swallowed\n * so one broken subscriber can't poison others or the auth flow. The\n * dispatch is fire-and-forget: `logout()` / `login()` resolve as soon as\n * the dispatch loop returns, without awaiting async listeners.\n *\n * Returns an unsubscribe function.\n */\n onAuthStateChange(listener: AuthStateListener): () => void {\n this.stateListeners.add(listener);\n return () => {\n this.stateListeners.delete(listener);\n };\n }\n\n /**\n * Detach this AuthModule from the TokenManager. Call when discarding the\n * client (tests, HMR, multi-client setups) to avoid leaking the internal\n * onAuthLost subscription. Idempotent — safe to call more than once.\n */\n dispose(): void {\n this.unsubscribeAuthLost();\n this.unsubscribeAuthLost = () => {\n // Second and later dispose() calls no-op.\n };\n this.stateListeners.clear();\n // Defensive reset: if this instance is ever reused (HMR, test reuse),\n // the dedup guard shouldn't swallow the first post-dispose emission\n // just because it happens to match the state we last emitted.\n this.lastEmittedState = null;\n }\n\n private emitState(state: AuthState): void {\n // Dedupe consecutive identical emits: a failed logout call can both\n // trigger onAuthLost (via 401 in the request) and then fall through to\n // our explicit emit — without dedup, subscribers would see two\n // unauthenticated events for a single user action.\n if (state === this.lastEmittedState) return;\n this.lastEmittedState = state;\n\n for (const listener of this.stateListeners) {\n try {\n const result = listener(state);\n // Duck-type via `.then` (spec definition of a thenable) so any\n // Promise-like return, including non-native thenables from JS\n // callers that bypass the TS types, is handled uniformly.\n if (result && typeof (result as Promise<void>).then === 'function') {\n (result as Promise<void>).then(undefined, () => {\n // Async listener rejections must not break siblings or callers.\n });\n }\n } catch {\n // Synchronous listener throws must not break siblings or callers.\n }\n }\n }\n\n async login(params: LoginParams): Promise<LoginResponse> {\n const response = await this.http.request<LoginResponse>({\n method: 'POST',\n path: '/auth/login',\n body: params,\n });\n\n await this.storeTokensFromLogin(response);\n return response;\n }\n\n async register(params: RegisterParams): Promise<RegisterResponse> {\n const response = await this.http.request<RegisterResponse>({\n method: 'POST',\n path: '/auth/register',\n body: params,\n });\n\n // Only store tokens if they were returned (not when email verification is required)\n if (response.access_token) {\n await this.storeTokensFromRegister(response);\n }\n return response;\n }\n\n async refresh(refreshToken: string): Promise<TokenResponse> {\n return this.http.request<TokenResponse>({\n method: 'POST',\n path: '/auth/refresh',\n body: { refreshToken },\n });\n }\n\n async getProfile(): Promise<UserProfile> {\n return this.http.request<UserProfile>({\n method: 'GET',\n path: '/auth/me',\n authenticated: true,\n });\n }\n\n async logout(): Promise<void> {\n try {\n await this.http.request<void>({\n method: 'POST',\n path: '/auth/logout',\n authenticated: true,\n });\n } catch {\n // Server-side revocation is best-effort. If the token is already invalid\n // the server has effectively revoked the session anyway — always clear\n // the local state so the UI doesn't get stuck.\n }\n await this.tokenManager.clearTokens();\n this.emitState('unauthenticated');\n }\n\n async verifyEmail(token: string): Promise<{ message: string }> {\n return this.http.request<{ message: string }>({\n method: 'GET',\n path: '/auth/verify-email',\n query: { token },\n });\n }\n\n async resendVerification(email: string): Promise<{ message: string }> {\n return this.http.request<{ message: string }>({\n method: 'POST',\n path: '/auth/resend-verification',\n body: { email },\n });\n }\n\n async getUserInfo(): Promise<UserInfo> {\n return this.http.request<UserInfo>({\n method: 'GET',\n path: this.config.userInfoEndpoint ?? '/auth/userinfo',\n authenticated: true,\n });\n }\n\n getAuthorizationUrl(params: AuthorizationUrlParams): string {\n const baseUrl = this.config.apiUrl.replace(/\\/+$/, '');\n const endpoint =\n this.config.authorizationEndpoint ?? '/auth/authorize';\n const url = new URL(`${baseUrl}${endpoint}`);\n\n url.searchParams.set('client_id', this.config.clientId);\n url.searchParams.set('redirect_uri', params.redirectUri);\n url.searchParams.set('response_type', params.responseType ?? 'code');\n\n const scope =\n params.scope ?? this.config.scopes?.join(' ') ?? 'openid';\n url.searchParams.set('scope', scope);\n\n if (params.state) {\n url.searchParams.set('state', params.state);\n }\n if (params.codeChallenge) {\n url.searchParams.set('code_challenge', params.codeChallenge);\n }\n if (params.codeChallengeMethod) {\n url.searchParams.set(\n 'code_challenge_method',\n params.codeChallengeMethod\n );\n }\n\n return url.toString();\n }\n\n async exchangeCode(params: ExchangeCodeParams): Promise<TokenResponse> {\n const body: Record<string, string> = {\n grant_type: params.grantType ?? GrantType.AUTHORIZATION_CODE,\n code: params.code,\n redirect_uri: params.redirectUri,\n client_id: this.config.clientId,\n };\n\n if (params.clientSecret) {\n body['client_secret'] = params.clientSecret;\n }\n if (params.codeVerifier) {\n body['code_verifier'] = params.codeVerifier;\n }\n\n const tokenEndpoint =\n this.config.tokenEndpoint ?? '/auth/token';\n\n const response = await this.http.request<TokenResponse>({\n method: 'POST',\n path: tokenEndpoint,\n body,\n });\n\n const expiresAt = response.expires_in\n ? Math.floor(Date.now() / 1000) + response.expires_in\n : undefined;\n\n await this.tokenManager.setTokens({\n accessToken: response.access_token,\n refreshToken: response.refresh_token,\n expiresAt,\n });\n this.emitState('authenticated');\n\n return response;\n }\n\n async generatePKCE(): Promise<PKCEChallenge> {\n return generatePKCEChallenge();\n }\n\n async getOpenIDConfiguration(): Promise<OpenIDConfiguration> {\n return this.http.request<OpenIDConfiguration>({\n method: 'GET',\n path: '/.well-known/openid-configuration',\n });\n }\n\n async getJWKS(): Promise<JWKSResponse> {\n return this.http.request<JWKSResponse>({\n method: 'GET',\n path: '/.well-known/jwks.json',\n });\n }\n\n /**\n * Request a password reset email.\n * Always returns a generic message regardless of whether the email exists.\n */\n async requestPasswordReset(email: string): Promise<{ message: string }> {\n return this.http.request<{ message: string }>({\n method: 'POST',\n path: '/auth/request-password-reset',\n body: { email },\n });\n }\n\n /**\n * Reset password using a token received via email.\n */\n async resetPassword(\n token: string,\n password: string,\n ): Promise<{ message: string }> {\n return this.http.request<{ message: string }>({\n method: 'POST',\n path: '/auth/reset-password',\n body: { token, password },\n });\n }\n\n /**\n * Exchange a one-time verification code for tokens.\n * Use this after email verification redirects the user with a ?loginCode= parameter.\n */\n async exchangeVerificationCode(code: string): Promise<LoginResponse> {\n const response = await this.http.request<LoginResponse>({\n method: 'POST',\n path: '/auth/exchange-code',\n body: { code },\n });\n\n await this.storeTokensFromLogin(response);\n return response;\n }\n\n /**\n * Resend a two-factor authentication code email.\n * Call this when the user hasn't received the code or it expired.\n */\n async resendTwoFactorCode(token: string): Promise<{ message: string }> {\n return this.http.request<{ message: string }>({\n method: 'POST',\n path: '/auth/resend-two-factor-code',\n body: { token },\n });\n }\n\n /**\n * Verify a two-factor authentication code.\n * Call this after catching SpacelrTwoFactorRequiredError from login().\n */\n async verifyTwoFactor(params: TwoFactorVerifyParams): Promise<LoginResponse> {\n const response = await this.http.request<LoginResponse>({\n method: 'POST',\n path: '/auth/verify-two-factor',\n body: { token: params.token, code: params.code },\n });\n\n await this.storeTokensFromLogin(response);\n return response;\n }\n\n private async storeTokensFromLogin(response: LoginResponse): Promise<void> {\n const expiresAt = response.expires_in\n ? Math.floor(Date.now() / 1000) + response.expires_in\n : undefined;\n\n await this.tokenManager.setTokens({\n accessToken: response.access_token,\n refreshToken: response.refresh_token,\n expiresAt,\n });\n this.emitState('authenticated');\n }\n\n private async storeTokensFromRegister(\n response: RegisterResponse\n ): Promise<void> {\n // Guard mirrors the optional `access_token?: string` in RegisterResponse:\n // the server omits tokens when email verification is required. The\n // caller already pre-checks, but keeping the guard here means the helper\n // stays correct against its declared input type rather than relying on\n // the single current call site.\n if (!response.access_token) return;\n await this.tokenManager.setTokens({\n accessToken: response.access_token,\n refreshToken: response.refresh_token,\n });\n this.emitState('authenticated');\n }\n}\n","import { HttpClient, TokenManager } from '../core';\nimport {\n SpacelrClientConfig,\n FileVisibility,\n FileInfo,\n FileListResponse,\n ListFilesParams,\n InitMultipartUploadParams,\n InitMultipartUploadResponse,\n PartEtag,\n ShareFileParams,\n UnshareFileParams,\n QuotaInfo,\n UploadFileParams,\n UploadLargeFileParams,\n UploadProgress,\n DownloadUrlResponse,\n} from '../types';\n\nexport class StorageModule {\n private http: HttpClient;\n private tokenManager: TokenManager;\n private config: SpacelrClientConfig;\n\n constructor(\n http: HttpClient,\n tokenManager: TokenManager,\n config: SpacelrClientConfig\n ) {\n this.http = http;\n this.tokenManager = tokenManager;\n this.config = config;\n }\n\n /**\n * Upload a file through the gateway (no direct S3 access).\n * Accepts a Blob/File (browser) or ArrayBuffer/Uint8Array (Node).\n */\n async uploadFile(\n file: Blob | ArrayBuffer | Uint8Array,\n params: UploadFileParams,\n onProgress?: (progress: UploadProgress) => void,\n ): Promise<FileInfo> {\n const formData = new FormData();\n const blob =\n file instanceof Blob\n ? file\n : new Blob([file as BlobPart], { type: params.mimeType });\n formData.append('file', blob, params.filename);\n if (params.visibility) formData.append('visibility', params.visibility);\n if (params.description) formData.append('description', params.description);\n\n const progressHandler = onProgress\n ? (e: { loaded: number; total: number }) => {\n onProgress({\n loaded: e.loaded,\n total: e.total,\n percentage: e.total > 0 ? Math.round((e.loaded / e.total) * 100) : 0,\n });\n }\n : undefined;\n\n return this.http.uploadForm<FileInfo>('/storage/files', formData, progressHandler);\n }\n\n /**\n * Upload a large file using multipart upload through the gateway.\n * Splits into parts, uploads concurrently, and completes.\n */\n async uploadLargeFile(\n file: Blob | ArrayBuffer | Uint8Array,\n params: UploadLargeFileParams,\n onProgress?: (progress: UploadProgress) => void\n ): Promise<FileInfo> {\n const fileSize = file instanceof Blob ? file.size : file.byteLength;\n const concurrency = params.concurrency ?? 3;\n\n const init = await this.initMultipartUpload({\n filename: params.filename,\n mimeType: params.mimeType,\n totalSizeBytes: fileSize,\n visibility: params.visibility,\n description: params.description,\n });\n\n const { partSize, totalParts, fileId } = init;\n const completedParts: PartEtag[] = [];\n let completedBytes = 0;\n const partProgressMap = new Map<number, number>();\n\n try {\n const allPartNumbers = Array.from(\n { length: totalParts },\n (_, i) => i + 1\n );\n\n for (let i = 0; i < allPartNumbers.length; i += concurrency) {\n const batch = allPartNumbers.slice(i, i + concurrency);\n\n const uploads = batch.map(async (partNumber) => {\n const start = (partNumber - 1) * partSize;\n const end = Math.min(start + partSize, fileSize);\n const chunkSize = end - start;\n const chunk =\n file instanceof Blob\n ? file.slice(start, end)\n : new Blob([file.slice(start, end)]);\n\n const formData = new FormData();\n formData.append('file', chunk, `part-${partNumber}`);\n formData.append('partNumber', String(partNumber));\n\n const partProgress = onProgress && typeof XMLHttpRequest !== 'undefined'\n ? (e: { loaded: number; total: number }) => {\n const ratio = e.total > 0 ? e.loaded / e.total : 0;\n partProgressMap.set(partNumber, Math.min(ratio * chunkSize, chunkSize));\n const inFlightBytes = Array.from(partProgressMap.values())\n .reduce((sum, v) => sum + v, 0);\n const totalLoaded = Math.min(completedBytes + inFlightBytes, fileSize);\n onProgress({\n loaded: totalLoaded,\n total: fileSize,\n percentage: fileSize > 0\n ? Math.min(100, Math.round((totalLoaded / fileSize) * 100))\n : 0,\n });\n }\n : undefined;\n\n const result = await this.http.uploadForm<{ etag: string }>(\n `/storage/files/${fileId}/multipart/upload-part`,\n formData,\n partProgress,\n );\n\n partProgressMap.delete(partNumber);\n completedBytes += chunkSize;\n\n if (onProgress) {\n onProgress({\n loaded: Math.min(completedBytes, fileSize),\n total: fileSize,\n percentage: fileSize > 0\n ? Math.min(100, Math.round((completedBytes / fileSize) * 100))\n : 0,\n });\n }\n\n completedParts.push({\n partNumber,\n etag: result.etag,\n });\n });\n\n await Promise.all(uploads);\n partProgressMap.clear();\n }\n\n // Sort parts by number before completing\n completedParts.sort((a, b) => a.partNumber - b.partNumber);\n\n await this.completeMultipartUpload(fileId, completedParts);\n return this.getFileInfo(fileId);\n } catch (error) {\n // Clean up the incomplete multipart upload on failure\n try {\n await this.abortMultipartUpload(fileId);\n } catch {\n // Abort is best-effort; ignore cleanup failures\n }\n throw error;\n }\n }\n\n async initMultipartUpload(\n params: InitMultipartUploadParams\n ): Promise<InitMultipartUploadResponse> {\n return this.http.request<InitMultipartUploadResponse>({\n method: 'POST',\n path: '/storage/files/multipart/init',\n body: params,\n authenticated: true,\n });\n }\n\n async completeMultipartUpload(\n fileId: string,\n parts: PartEtag[]\n ): Promise<FileInfo> {\n return this.http.request<FileInfo>({\n method: 'POST',\n path: `/storage/files/${fileId}/multipart/complete`,\n body: { parts },\n authenticated: true,\n });\n }\n\n async abortMultipartUpload(fileId: string): Promise<void> {\n await this.http.request<void>({\n method: 'POST',\n path: `/storage/files/${fileId}/multipart/abort`,\n authenticated: true,\n });\n }\n\n async listFiles(params?: ListFilesParams): Promise<FileListResponse> {\n return this.http.request<FileListResponse>({\n method: 'GET',\n path: '/storage/files',\n query: params as Record<string, string | number | undefined>,\n authenticated: true,\n });\n }\n\n async listSharedFiles(params?: ListFilesParams): Promise<FileListResponse> {\n return this.http.request<FileListResponse>({\n method: 'GET',\n path: '/storage/shared',\n query: params as Record<string, string | number | undefined>,\n authenticated: true,\n });\n }\n\n async getFileInfo(fileId: string): Promise<FileInfo> {\n return this.http.request<FileInfo>({\n method: 'GET',\n path: `/storage/files/${fileId}`,\n authenticated: true,\n });\n }\n\n async downloadFile(fileId: string): Promise<Blob> {\n const baseUrl = this.config.apiUrl.replace(/\\/+$/, '');\n const url = `${baseUrl}/storage/files/${fileId}/download`;\n\n const headers: Record<string, string> = {\n 'x-client-id': this.config.clientId,\n 'x-project-id': this.config.projectId,\n };\n\n const token = await this.tokenManager.getAccessToken();\n if (token) {\n headers['Authorization'] = `Bearer ${token}`;\n }\n\n const response = await fetch(url, { headers });\n\n if (!response.ok) {\n throw new Error(`Download failed: ${response.status}`);\n }\n\n return response.blob();\n }\n\n async getDownloadUrl(fileId: string): Promise<DownloadUrlResponse> {\n return this.http.request<DownloadUrlResponse>({\n method: 'GET',\n path: `/storage/files/${fileId}/download-url`,\n authenticated: true,\n });\n }\n\n async deleteFile(fileId: string): Promise<void> {\n await this.http.request<void>({\n method: 'DELETE',\n path: `/storage/files/${fileId}`,\n authenticated: true,\n });\n }\n\n async shareFile(\n fileId: string,\n params: ShareFileParams\n ): Promise<void> {\n await this.http.request<void>({\n method: 'POST',\n path: `/storage/files/${fileId}/share`,\n body: params,\n authenticated: true,\n });\n }\n\n async unshareFile(\n fileId: string,\n params: UnshareFileParams\n ): Promise<void> {\n await this.http.request<void>({\n method: 'POST',\n path: `/storage/files/${fileId}/unshare`,\n body: params,\n authenticated: true,\n });\n }\n\n async updateVisibility(\n fileId: string,\n visibility: FileVisibility\n ): Promise<FileInfo> {\n return this.http.request<FileInfo>({\n method: 'PATCH',\n path: `/storage/files/${fileId}/visibility`,\n body: { visibility },\n authenticated: true,\n });\n }\n\n async getQuota(): Promise<QuotaInfo> {\n return this.http.request<QuotaInfo>({\n method: 'GET',\n path: '/storage/quota',\n authenticated: true,\n });\n }\n\n async getPublicFileUrl(fileId: string, projectId?: string): Promise<string> {\n const baseUrl = this.config.apiUrl.replace(/\\/+$/, '');\n const resolvedProjectId = projectId ?? this.config.projectId;\n return `${baseUrl}/public/files/${resolvedProjectId}/${fileId}`;\n }\n}\n","import { HttpClient } from '../core';\nimport { RealtimeClient, DatabaseChangeEvent } from '../core';\nimport type { GapReason, StreamGapInfo } from '../core/realtime';\nimport type { CursorStorage } from '../core/cursor-storage';\n\n// Re-export for downstream consumers so they don't need to reach into core.\nexport type { GapReason, StreamGapInfo, CursorStorage };\n\nexport interface PopulateOption {\n field: string;\n collection: string;\n foreignField?: string;\n}\n\nexport interface DocumentResult {\n _id: string;\n [key: string]: unknown;\n}\n\nexport interface OffsetResult<T = Record<string, unknown>> {\n mode: 'offset';\n documents: (T & { _id: string })[];\n total: number;\n limit: number;\n offset: number;\n}\n\nexport interface CursorResult<T = Record<string, unknown>> {\n mode: 'cursor';\n documents: (T & { _id: string })[];\n limit: number;\n /**\n * `_id` of the last document in `documents`, or `null` if `documents` is empty.\n * Independent of `hasMore` — the cursor reflects the page contents, not\n * whether more pages exist.\n */\n nextCursor: string | null;\n /**\n * True when more documents exist past the returned page. Computed via the\n * `limit + 1` server-side fetch trick.\n */\n hasMore: boolean;\n}\n\nexport type FindResult<T = Record<string, unknown>> = OffsetResult<T> | CursorResult<T>;\n\n// SearchResult retains the pre-discriminator shape; search() does not yet\n// support cursor pagination.\nexport interface SearchResult<T = Record<string, unknown>> {\n documents: (T & { _id: string })[];\n total: number;\n limit: number;\n offset: number;\n}\n\nexport interface InsertResult {\n insertedCount: number;\n insertedIds: string[];\n}\n\nexport interface UpdateResult {\n matchedCount: number;\n modifiedCount: number;\n}\n\nexport interface DeleteResult {\n deletedCount: number;\n}\n\nexport interface CountResult {\n count: number;\n}\n\nexport interface FindByIdOptions {\n populate?: PopulateOption[];\n}\n\n/**\n * Options for a server-side substring search.\n *\n * Limits: `query` 1–200 chars, `fields` 1–10 entries (each matching\n * `/^[a-zA-Z0-9_.]+$/`, max 64 chars), `limit` max 100.\n */\nexport interface SearchOptions {\n query: string;\n fields: string[];\n filter?: Record<string, unknown>;\n sort?: Record<string, 1 | -1>;\n limit?: number;\n offset?: number;\n select?: string[];\n}\n\nexport interface SubscribeEventsHandlers<T = Record<string, unknown>> {\n /** Cursor to resume from. Undefined = fresh subscription, deliver only new events. */\n sinceId?: string;\n where?: Record<string, string | number | boolean>;\n /**\n * Optional resume-cursor store. When set, the SDK loads the previous cursor\n * before subscribing and persists the new cursor after each delivered\n * event. An explicit `sinceId` takes precedence over a loaded cursor —\n * `load()` is skipped entirely and saves resume from `sinceId` forward,\n * overwriting any previously persisted cursor.\n *\n * Pair `where`-filtered subscriptions with a unique `cursorKey`; subscribers\n * with different filters must not share a cursor since the stream position\n * represents \"events seen\" — filtered-out entries still advance it.\n */\n cursorStorage?: CursorStorage;\n /**\n * Override the default cursor key. Defaults to `{projectId}:{collectionName}`.\n * Useful when multiple subscriptions on the same collection (e.g. different\n * `where` filters or per-user feeds) must persist independent cursors.\n */\n cursorKey?: string;\n /** Called on inserts. Receives the document merged with `_eventId`. */\n onInsert?: (doc: T & { _id: string; _eventId: string }) => void | Promise<void>;\n onUpdate?: (doc: T & { _id: string; _eventId: string }) => void | Promise<void>;\n /** Called on deletes. `document` may be absent on tombstones — only documentId + eventId are guaranteed. */\n onDelete?: (documentId: string, eventId: string) => void | Promise<void>;\n /** Called when the server emits an event-gap. See GapReason for the four codes. */\n onGap?: (info: StreamGapInfo) => void;\n /** Called on handshake rejection (not-stream-collection, invalid sinceId, etc.) and handler failures. */\n onError?: (error: Error) => void;\n}\n\nexport interface StreamSubscription {\n /**\n * Stop receiving events and release the server-side reader slot. Idempotent.\n *\n * When `cursorStorage` is set, an in-flight async `save()` may still deliver\n * `onError` asynchronously after `unsubscribe()` returns — the fire-and-forget\n * save chain is not cancelled. Callers whose `onError` is tied to a component\n * lifecycle should guard for post-unmount delivery.\n */\n unsubscribe(): void;\n /**\n * Most recently processed eventId — advances after the subscription's\n * onEvent pipeline completes for each event, whether or not a user\n * handler was registered for that event type. `undefined` before any\n * event arrives.\n *\n * Persist this across app restarts and pass it back as `sinceId` on\n * the next `subscribeEvents()` call to catch up on missed events.\n */\n getCursor(): string | undefined;\n}\n\nexport interface SubscribeHandlers<T = Record<string, unknown>> {\n where?: Record<string, string | number | boolean>;\n onInsert?: (doc: T & { _id: string }) => void;\n onUpdate?: (doc: T & { _id: string }) => void;\n onDelete?: (documentId: string) => void;\n onError?: (error: Error) => void;\n}\n\nexport class QueryBuilder<\n T = Record<string, unknown>,\n M extends 'offset' | 'cursor' = 'offset',\n> {\n private _filter?: Record<string, unknown>;\n private _sort?: Record<string, 1 | -1>;\n private _limit?: number;\n private _offset?: number;\n private _before?: string;\n private _after?: string;\n private _fields?: string[];\n private _populate: PopulateOption[] = [];\n\n constructor(\n private http: HttpClient,\n private basePath: string,\n filter?: Record<string, unknown>,\n ) {\n this._filter = filter;\n }\n\n sort(sort: Record<string, 1 | -1>): this {\n this._sort = sort;\n return this;\n }\n\n limit(limit: number): this {\n this._limit = limit;\n return this;\n }\n\n offset(offset: number): this {\n this._offset = offset;\n return this;\n }\n\n /**\n * **Constraint:** the cursor value must be a 24-hex ObjectId string.\n * Collections using custom non-ObjectId `_id` strings will not work\n * correctly with cursor pagination — the server's `$lt`/`$gt` comparison\n * uses BSON type ordering, mixing string `_id`s with ObjectId comparison\n * produces undefined behaviour. Documented limitation; future work may\n * add opaque cursor tokens that abstract over `_id` types.\n *\n * Switch to cursor-pagination mode: return documents with `_id < id`\n * (in the sort-defined order). The cursor refers to the cursor *value*,\n * not visual UI direction. Requires `.sort()` to be `{ _id: 1 }` or\n * `{ _id: -1 }` (or omitted — server defaults to `{ _id: 1 }`).\n *\n * Narrows the builder's mode parameter so subsequent `.execute()` returns\n * a `CursorResult<T>` instead of `OffsetResult<T>`.\n *\n * **For paginating further:** the `nextCursor` field returned by\n * `execute()` is the `_id` of the last document on the page. To load the\n * next older page, pass it again to `.before()`. (Do NOT pass it to\n * `.after()` — that would request docs newer than this page.)\n *\n * **Cannot be combined with `.offset()`.** Type system allows the chain\n * for ergonomics, but the server rejects it with HTTP 400.\n */\n before(id: string): QueryBuilder<T, 'cursor'> {\n this._before = id;\n return this as QueryBuilder<T, 'cursor'>;\n }\n\n /**\n * Switch to cursor-pagination mode: return documents with `_id > id`.\n * See `.before()` for full semantics — including the ObjectId-only cursor\n * constraint and the `.offset()` incompatibility (server-enforced 400).\n *\n * **For paginating further:** pass the returned `nextCursor` to\n * `.after()` again to load the next newer page.\n */\n after(id: string): QueryBuilder<T, 'cursor'> {\n this._after = id;\n return this as QueryBuilder<T, 'cursor'>;\n }\n\n select(fields: string[]): this {\n this._fields = fields;\n return this;\n }\n\n populate(\n field: string,\n collection: string,\n foreignField?: string,\n ): this {\n this._populate.push({ field, collection, foreignField });\n return this;\n }\n\n async execute(): Promise<\n M extends 'cursor' ? CursorResult<T> : OffsetResult<T>\n > {\n const query: Record<string, string | number | undefined> = {};\n if (this._filter) query['filter'] = JSON.stringify(this._filter);\n if (this._sort) query['sort'] = JSON.stringify(this._sort);\n if (this._limit !== undefined) query['limit'] = this._limit;\n if (this._offset !== undefined) query['offset'] = this._offset;\n if (this._before !== undefined) query['before'] = this._before;\n if (this._after !== undefined) query['after'] = this._after;\n if (this._fields) query['fields'] = this._fields.join(',');\n if (this._populate.length) {\n query['populate'] = this._populate\n .map((p) =>\n p.foreignField\n ? `${p.field}:${p.collection}:${p.foreignField}`\n : `${p.field}:${p.collection}`,\n )\n .join(',');\n }\n\n return this.http.request<M extends 'cursor' ? CursorResult<T> : OffsetResult<T>>({\n method: 'GET',\n path: this.basePath,\n query,\n authenticated: true,\n });\n }\n}\n\nexport class CollectionRef<T = Record<string, unknown>> {\n private basePath: string;\n private collectionName: string;\n\n constructor(\n private http: HttpClient,\n private realtime: RealtimeClient | null,\n private projectId: string,\n collectionName: string,\n ) {\n this.collectionName = collectionName;\n this.basePath = `/db/${collectionName}`;\n }\n\n async insert(\n document: Partial<T> & { _id?: string },\n ): Promise<InsertResult> {\n return this.http.request<InsertResult>({\n method: 'POST',\n path: this.basePath,\n body: { documents: [document] },\n authenticated: true,\n });\n }\n\n async insertMany(\n documents: (Partial<T> & { _id?: string })[],\n ): Promise<InsertResult> {\n return this.http.request<InsertResult>({\n method: 'POST',\n path: this.basePath,\n body: { documents },\n authenticated: true,\n });\n }\n\n find(filter?: Record<string, unknown>): QueryBuilder<T> {\n return new QueryBuilder<T>(this.http, this.basePath, filter);\n }\n\n /**\n * Server-side substring search across the specified fields.\n *\n * The query is regex-escaped server-side and matched case-insensitively via\n * MongoDB `$regex`. Performance note: unanchored case-insensitive regex\n * cannot use a standard B-tree index — on very large collections consider\n * narrowing with `filter` to scope the scan.\n *\n * Limits: `query` 1–200 chars, `fields` 1–10 entries (each matching\n * `/^[a-zA-Z0-9_.]+$/`, max 64 chars), `limit` max 100.\n */\n async search(opts: SearchOptions): Promise<SearchResult<T>> {\n return this.http.request<SearchResult<T>>({\n method: 'POST',\n path: `${this.basePath}/search`,\n body: opts,\n authenticated: true,\n });\n }\n\n async findById(\n id: string,\n options?: FindByIdOptions,\n ): Promise<T & { _id: string }> {\n const query: Record<string, string> = {};\n if (options?.populate?.length) {\n query['populate'] = options.populate\n .map((p) =>\n p.foreignField\n ? `${p.field}:${p.collection}:${p.foreignField}`\n : `${p.field}:${p.collection}`,\n )\n .join(',');\n }\n\n return this.http.request<T & { _id: string }>({\n method: 'GET',\n path: `${this.basePath}/${id}`,\n query,\n authenticated: true,\n });\n }\n\n async update(id: string, update: Partial<T>): Promise<UpdateResult> {\n return this.http.request<UpdateResult>({\n method: 'PATCH',\n path: `${this.basePath}/${id}`,\n body: { update },\n authenticated: true,\n });\n }\n\n async delete(id: string): Promise<DeleteResult> {\n return this.http.request<DeleteResult>({\n method: 'DELETE',\n path: `${this.basePath}/${id}`,\n authenticated: true,\n });\n }\n\n async count(filter?: Record<string, unknown>): Promise<number> {\n const result = await this.http.request<CountResult>({\n method: 'POST',\n path: `${this.basePath}/count`,\n body: { filter },\n authenticated: true,\n });\n return result.count;\n }\n\n subscribe(handlers: SubscribeHandlers<T>): () => void {\n if (!this.realtime) {\n throw new Error('Realtime not available: no RealtimeClient configured');\n }\n\n let unsubscribeFn: (() => void) | null = null;\n let pendingUnsub = false;\n\n const callback = (event: DatabaseChangeEvent) => {\n switch (event.type) {\n case 'insert':\n if (handlers.onInsert && event.document) {\n handlers.onInsert(event.document as T & { _id: string });\n }\n break;\n case 'update':\n if (handlers.onUpdate && event.document) {\n handlers.onUpdate(event.document as T & { _id: string });\n }\n break;\n case 'delete':\n if (handlers.onDelete) {\n handlers.onDelete(event.documentId);\n }\n break;\n }\n };\n\n this.realtime\n .subscribe(this.projectId, this.collectionName, callback, handlers.onError, handlers.where)\n .then((unsub) => {\n if (pendingUnsub) {\n // Unsubscribe was called before async subscribe resolved\n unsub();\n } else {\n unsubscribeFn = unsub;\n }\n })\n .catch((err) => {\n if (handlers.onError) {\n handlers.onError(err instanceof Error ? err : new Error(String(err)));\n }\n });\n\n // Return synchronous unsubscribe\n return () => {\n if (unsubscribeFn) {\n unsubscribeFn();\n } else {\n pendingUnsub = true;\n }\n };\n }\n\n subscribeEvents(handlers: SubscribeEventsHandlers<T>): StreamSubscription {\n if (!this.realtime) {\n throw new Error('Realtime not available: no RealtimeClient configured');\n }\n\n let lastCursor: string | undefined;\n let unsub: (() => void) | null = null;\n let unsubscribed = false;\n\n const cursorStorage = handlers.cursorStorage;\n const cursorKey =\n handlers.cursorKey ?? `${this.projectId}:${this.collectionName}`;\n\n // Loud warning for the #1 footgun: sharing a persisted cursor across\n // differently-filtered subscriptions silently skips events on reconnect\n // because the stream position represents \"seen\", not \"matched\".\n if (\n cursorStorage &&\n handlers.where &&\n Object.keys(handlers.where).length > 0 &&\n handlers.cursorKey === undefined\n ) {\n console.warn(\n `[spacelr] subscribeEvents on ${this.projectId}:${this.collectionName} uses a 'where' filter with cursorStorage but no explicit cursorKey — ` +\n `filtered subscriptions sharing the default key will silently skip events across reconnects. ` +\n `Pass a unique cursorKey (e.g. include a hash of the filter).`,\n );\n }\n // Capture after the null-guard so the closures below don't need `!`.\n const realtime = this.realtime;\n // Async saves must not complete out of order — that would regress the\n // persisted cursor below an already-emitted event. Chain every save\n // onto the previous so they serialize end-to-end while still being\n // decoupled from event delivery.\n let lastSavePromise: Promise<unknown> = Promise.resolve();\n\n const onEvent = async ({\n eventId,\n event,\n }: {\n eventId: string;\n event: DatabaseChangeEvent;\n }) => {\n try {\n if (event.type === 'insert' && handlers.onInsert && event.document) {\n await handlers.onInsert({\n ...(event.document as T & { _id: string }),\n _eventId: eventId,\n });\n } else if (event.type === 'update' && handlers.onUpdate && event.document) {\n await handlers.onUpdate({\n ...(event.document as T & { _id: string }),\n _eventId: eventId,\n });\n } else if (event.type === 'delete' && handlers.onDelete) {\n await handlers.onDelete(event.documentId, eventId);\n }\n lastCursor = eventId;\n if (cursorStorage) {\n // Chain this save onto the previous one so async backends can't\n // complete out of order and regress the stored cursor. Both\n // fulfilled and rejected continuations still run `save()` —\n // we don't want one failed save to skip the next event's save.\n // Each save's own failure surfaces via onError asynchronously.\n lastSavePromise = lastSavePromise\n .then(\n () => cursorStorage.save(cursorKey, eventId),\n () => cursorStorage.save(cursorKey, eventId),\n )\n .catch((err) => {\n handlers.onError?.(err instanceof Error ? err : new Error(String(err)));\n });\n }\n } catch (err) {\n // Rethrow so RealtimeClient's per-subscriber queue sees the rejection and\n // skips advancing its own `lastDeliveredId`. That same catch calls\n // `handlers.onError` for us — do NOT call it here or the user would see\n // the error twice.\n throw err instanceof Error ? err : new Error(String(err));\n }\n };\n\n const callSubscribe = (sinceId: string | undefined): Promise<() => void> =>\n realtime.subscribeWithCursor({\n projectId: this.projectId,\n collectionName: this.collectionName,\n sinceId,\n where: handlers.where,\n onEvent,\n onGap: handlers.onGap,\n onError: handlers.onError,\n });\n\n // Fast path: no storage (or explicit sinceId) — skip the extra await\n // so consumers that don't opt into cursor persistence keep the same\n // one-microtask handshake timing they had before.\n let promise: Promise<() => void>;\n if (handlers.sinceId !== undefined || !cursorStorage) {\n promise = callSubscribe(handlers.sinceId);\n } else {\n promise = (async () => {\n let resumeFrom: string | undefined;\n try {\n const loaded = await Promise.resolve(cursorStorage.load(cursorKey));\n if (loaded) resumeFrom = loaded;\n } catch (err) {\n // Storage read failure is never a reason to skip the subscription —\n // resume fresh and let the consumer's onError see the warning.\n handlers.onError?.(err instanceof Error ? err : new Error(String(err)));\n }\n return callSubscribe(resumeFrom);\n })();\n }\n\n promise\n .then((u) => {\n if (unsubscribed) {\n // Caller already asked to cancel; release the reader immediately.\n u();\n } else {\n unsub = u;\n }\n })\n .catch(() => undefined); // onError already surfaced inside the realtime layer.\n\n return {\n unsubscribe(): void {\n unsubscribed = true;\n if (unsub) {\n unsub();\n unsub = null;\n }\n },\n getCursor(): string | undefined {\n return lastCursor;\n },\n };\n }\n}\n\nexport class DatabaseModule {\n private http: HttpClient;\n private realtime: RealtimeClient | null;\n private projectId: string;\n\n constructor(http: HttpClient, projectId: string, realtime?: RealtimeClient) {\n this.http = http;\n this.projectId = projectId;\n this.realtime = realtime ?? null;\n }\n\n collection<T = Record<string, unknown>>(name: string): CollectionRef<T> {\n return new CollectionRef<T>(this.http, this.realtime, this.projectId, name);\n }\n}\n","import { HttpClient } from '../core';\n\nexport interface PushSubscriptionInfo {\n id: string;\n platform: 'web' | 'android' | 'ios';\n endpoint?: string;\n deviceToken?: string;\n deviceId?: string;\n deviceName?: string;\n userAgent?: string;\n isActive: boolean;\n lastUsedAt?: string;\n createdAt: string;\n updatedAt: string;\n}\n\nexport interface VapidKeyResponse {\n publicKey: string;\n}\n\nconst DEVICE_ID_KEY = 'spacelr_device_id';\n\nexport class NotificationsModule {\n private customDeviceId: string | null = null;\n private customDeviceName: string | null = null;\n\n constructor(private http: HttpClient) {}\n\n /** Set a custom device ID (e.g. from Capacitor Preferences for persistence beyond localStorage) */\n setDeviceId(id: string): void {\n this.customDeviceId = id;\n }\n\n /** Set a custom device name (e.g. \"iOS App\", \"macOS - Chrome\") */\n setDeviceName(name: string): void {\n this.customDeviceName = name;\n }\n\n /** Get or generate a stable device identifier. Custom ID takes priority over localStorage. */\n private getDeviceId(): string | undefined {\n if (this.customDeviceId) return this.customDeviceId;\n if (typeof localStorage === 'undefined') return undefined;\n let id = localStorage.getItem(DEVICE_ID_KEY);\n if (!id) {\n id = crypto.randomUUID();\n localStorage.setItem(DEVICE_ID_KEY, id);\n }\n return id;\n }\n\n /** Get device name: custom name > auto-detected from user agent > undefined */\n private getDeviceName(): string | undefined {\n if (this.customDeviceName) return this.customDeviceName;\n return this.detectDeviceName();\n }\n\n /** Auto-detect a short device label from navigator.userAgent (e.g. \"macOS - Chrome\") */\n private detectDeviceName(): string | undefined {\n if (typeof navigator === 'undefined' || !navigator.userAgent) return undefined;\n const ua = navigator.userAgent;\n\n let os = 'Unknown';\n if (/iPad|iPhone|iPod/.test(ua)) os = 'iOS';\n else if (/Android/.test(ua)) os = 'Android';\n else if (/Mac OS X/.test(ua)) os = 'macOS';\n else if (/Windows/.test(ua)) os = 'Windows';\n else if (/Linux/.test(ua)) os = 'Linux';\n\n let browser = 'Unknown';\n if (/Edg\\//.test(ua)) browser = 'Edge';\n else if (/OPR\\/|Opera/.test(ua)) browser = 'Opera';\n else if (/Chrome\\//.test(ua)) browser = 'Chrome';\n else if (/Safari\\//.test(ua) && !/Chrome/.test(ua)) browser = 'Safari';\n else if (/Firefox\\//.test(ua)) browser = 'Firefox';\n\n return `${os} - ${browser}`;\n }\n\n /** Get the VAPID public key for Web Push setup */\n async getVapidPublicKey(): Promise<VapidKeyResponse> {\n return this.http.request<VapidKeyResponse>({\n method: 'GET',\n path: '/notifications/vapid-key',\n authenticated: true,\n });\n }\n\n /** Register a push subscription (web, android, or ios) */\n async subscribe(\n subscription: {\n platform: 'web' | 'android' | 'ios';\n endpoint?: string;\n keys?: { p256dh: string; auth: string };\n deviceToken?: string;\n },\n deviceName?: string,\n ): Promise<PushSubscriptionInfo> {\n return this.http.request<PushSubscriptionInfo>({\n method: 'POST',\n path: '/notifications/subscribe',\n body: {\n ...subscription,\n deviceName: deviceName ?? this.getDeviceName(),\n deviceId: this.getDeviceId(),\n },\n authenticated: true,\n });\n }\n\n /** Unregister a push subscription */\n async unsubscribe(\n platform: 'web' | 'android' | 'ios',\n identifier: string,\n ): Promise<{ deleted: boolean }> {\n const body: Record<string, string> = { platform };\n if (platform === 'web') {\n body['endpoint'] = identifier;\n } else {\n body['deviceToken'] = identifier;\n }\n\n return this.http.request<{ deleted: boolean }>({\n method: 'DELETE',\n path: '/notifications/subscribe',\n body,\n authenticated: true,\n });\n }\n\n /** Get all subscriptions for the current user */\n async getSubscriptions(): Promise<PushSubscriptionInfo[]> {\n return this.http.request<PushSubscriptionInfo[]>({\n method: 'GET',\n path: '/notifications/subscriptions',\n authenticated: true,\n });\n }\n\n /**\n * Helper: Register browser Web Push subscription.\n * Requests notification permission, subscribes via Push API,\n * and registers the subscription with the server.\n */\n async registerBrowserPush(\n serviceWorkerRegistration: ServiceWorkerRegistration,\n deviceName?: string,\n ): Promise<PushSubscriptionInfo> {\n // Get VAPID key\n const { publicKey } = await this.getVapidPublicKey();\n\n if (!publicKey) {\n throw new Error(\n 'VAPID public key not configured on the server',\n );\n }\n\n // Request notification permission\n const permission = await Notification.requestPermission();\n if (permission !== 'granted') {\n throw new Error(\n `Notification permission denied: ${permission}`,\n );\n }\n\n // Convert VAPID key to Uint8Array\n const applicationServerKey = this.urlBase64ToUint8Array(publicKey);\n\n // Subscribe via Push API\n const pushSubscription =\n await serviceWorkerRegistration.pushManager.subscribe({\n userVisibleOnly: true,\n applicationServerKey,\n });\n\n const subJson = pushSubscription.toJSON();\n const p256dh = subJson.keys?.['p256dh'];\n const auth = subJson.keys?.['auth'];\n\n if (!subJson.endpoint || !p256dh || !auth) {\n throw new Error('Invalid push subscription: missing endpoint or keys');\n }\n\n // Register with server\n return this.subscribe(\n {\n platform: 'web',\n endpoint: subJson.endpoint,\n keys: { p256dh, auth },\n },\n deviceName,\n );\n }\n\n /**\n * Helper: Register a mobile device push token (FCM or APNs).\n */\n async registerDevicePush(\n deviceToken: string,\n platform: 'android' | 'ios',\n deviceName?: string,\n ): Promise<PushSubscriptionInfo> {\n return this.subscribe(\n {\n platform,\n deviceToken,\n },\n deviceName,\n );\n }\n\n private urlBase64ToUint8Array(base64String: string): ArrayBuffer {\n const padding = '='.repeat((4 - (base64String.length % 4)) % 4);\n const base64 = (base64String + padding)\n .replace(/-/g, '+')\n .replace(/_/g, '/');\n\n const rawData = atob(base64);\n const outputArray = new Uint8Array(rawData.length);\n\n for (let i = 0; i < rawData.length; ++i) {\n outputArray[i] = rawData.charCodeAt(i);\n }\n return outputArray.buffer as ArrayBuffer;\n }\n}\n","import { HttpClient } from '../core';\n\nexport interface FunctionInvokeOptions {\n /** Webhook secret — used for `webhook` and `hybrid` invokeMode. */\n secret?: string;\n /**\n * Whether to attach the signed-in user's bearer token via `Authorization`\n * (managed by the SDK's TokenManager).\n *\n * Default: `true` when no `secret` is provided, `false` when `secret` is\n * set. Override explicitly for the hybrid case (provide both) or to\n * suppress the header even when logged in.\n *\n * If `true` but the user is not signed in, the header is simply omitted —\n * safe for `public` invokeMode.\n */\n authenticated?: boolean;\n payload?: Record<string, unknown>;\n}\n\nexport interface FunctionInvokeResult {\n success: boolean;\n executionId?: string;\n error?: string;\n}\n\nexport class FunctionsModule {\n private http: HttpClient;\n\n constructor(http: HttpClient) {\n this.http = http;\n }\n\n /**\n * Invoke a function.\n * Calls POST `/functions/:projectId/:functionId/invoke`, resolved against\n * `config.apiUrl` (which already carries the `/api/v1` prefix).\n *\n * Auth defaults, based on `invokeMode` semantics:\n * - webhook: pass `secret` → Authorization is NOT attached\n * - authenticated: pass nothing → Authorization IS attached (from token manager)\n * - public: pass nothing → Authorization is attached if logged in, else omitted\n * - hybrid: pass both `secret` and `authenticated: true`\n *\n * To force a specific behaviour, set `authenticated` explicitly — it wins\n * over the `secret`-based default.\n */\n async invoke(\n projectId: string,\n functionId: string,\n options: FunctionInvokeOptions = {},\n ): Promise<FunctionInvokeResult> {\n // Empty-string secret is treated as absent — both the header guard and\n // the authenticated-default check use `?.length`, so the two stay in\n // lock-step (no \"header omitted but still treated as webhook mode\").\n const hasSecret = (options.secret?.length ?? 0) > 0;\n\n const headers: Record<string, string> = {};\n if (hasSecret) {\n headers['X-Webhook-Secret'] = options.secret as string;\n }\n\n // Default: authenticate unless a webhook secret is provided.\n // Explicit `authenticated` overrides this.\n const authenticated = options.authenticated ?? !hasSecret;\n\n return this.http.request<FunctionInvokeResult>({\n method: 'POST',\n path: `/functions/${encodeURIComponent(projectId)}/${encodeURIComponent(functionId)}/invoke`,\n headers,\n body: options.payload ?? {},\n authenticated,\n });\n }\n}\n","import { SpacelrClientConfig, StoredTokens } from './types';\nimport { HttpClient, TokenManager, MemoryTokenStorage, BrowserTokenStorage, RealtimeClient, ConnectionState } from './core';\nimport { AuthLostReason } from './core/token-manager';\nimport { AuthModule, StorageModule, DatabaseModule, NotificationsModule, FunctionsModule } from './modules';\n\nexport interface SpacelrClient {\n readonly auth: AuthModule;\n readonly storage: StorageModule;\n readonly db: DatabaseModule;\n readonly notifications: NotificationsModule;\n readonly functions: FunctionsModule;\n /**\n * Store auth tokens externally (e.g. obtained via the auth-components\n * library that bypasses the SDK). Makes the tokens available to HTTP +\n * realtime clients. Refresh is on-demand: the SDK will refresh the\n * access token when it next makes a request and the token is within the\n * expiry buffer (or on a 401 retry).\n */\n setTokens(tokens: StoredTokens): Promise<void>;\n /** Clear stored tokens and reset auth-loss state. */\n clearTokens(): Promise<void>;\n /** Disconnect realtime WebSocket (if connected) */\n disconnect(): void;\n /**\n * Subscribe to auth loss. Fires in three situations:\n * - `'refresh-failed'` — a refresh call was rejected by the server\n * (refresh token revoked or expired). Note: this can also fire from a\n * *background* refresh triggered inside the expiry buffer window, at\n * which point the current access token may still be valid for up to\n * `refreshBufferSeconds` (default 60). Treat the session as over anyway;\n * the refresh token is dead.\n * - `'unauthorized'` — an authenticated request came back 401 and no\n * fresh token could be produced (no refresh token, or the retry after\n * a successful refresh was itself 401).\n *\n * Typical consumer reaction: clear UI state and show the login screen.\n * The event is deduped until `setTokens`/`clearTokens` is called again.\n */\n onAuthLost(listener: (reason: AuthLostReason) => void): () => void;\n /** Subscribe to successful token refreshes. */\n onTokenRefreshed(listener: (tokens: StoredTokens) => void): () => void;\n /**\n * Subscribe to realtime socket connection-state changes. Fires on every\n * transition (dedup: identical consecutive states are skipped). Returns\n * an unsubscribe function.\n *\n * States:\n * - `'disconnected'` — initial state before the first subscribe, or after\n * the SDK has torn the realtime socket down (e.g. on logout via the\n * internal `realtime.disconnect()`). This is a terminal state for the\n * current realtime client; reconnect happens on the next `subscribe()`.\n * - `'reconnecting'` — connecting or rebuilding the socket (initial\n * connect, socket drop, `reconnect_failed` rebuild, or a wake-up event\n * after suspend).\n * - `'connected'` — authenticated and actively listening for events.\n */\n onConnectionStateChanged(listener: (state: ConnectionState) => void): () => void;\n /** Read the current realtime socket state without waiting for an event. */\n getConnectionState(): ConnectionState;\n}\n\nexport function createClient(config: SpacelrClientConfig): SpacelrClient {\n const tokenStorage = config.tokenStorage\n ?? (typeof window !== 'undefined' && typeof window.localStorage !== 'undefined'\n ? new BrowserTokenStorage()\n : new MemoryTokenStorage());\n const tokenManager = new TokenManager(\n tokenStorage,\n config.refreshBufferSeconds ?? 60\n );\n const httpClient = new HttpClient(config, tokenManager);\n\n const realtime = new RealtimeClient({\n baseUrl: config.apiUrl,\n getToken: () => tokenManager.getAccessToken(),\n onTokenRefreshed: (listener) =>\n tokenManager.onTokenRefreshed((tokens) => listener(tokens.accessToken)),\n });\n\n const auth = new AuthModule(httpClient, tokenManager, config);\n const storage = new StorageModule(httpClient, tokenManager, config);\n const db = new DatabaseModule(httpClient, config.projectId, realtime);\n const notifications = new NotificationsModule(httpClient);\n const functions = new FunctionsModule(httpClient);\n\n return {\n auth,\n storage,\n db,\n notifications,\n functions,\n setTokens(tokens) {\n return tokenManager.setTokens(tokens);\n },\n clearTokens() {\n return tokenManager.clearTokens();\n },\n disconnect() {\n realtime.disconnect();\n },\n onAuthLost(listener) {\n return tokenManager.onAuthLost(listener);\n },\n onTokenRefreshed(listener) {\n return tokenManager.onTokenRefreshed(listener);\n },\n onConnectionStateChanged(listener) {\n return realtime.onConnectionStateChanged(listener);\n },\n getConnectionState() {\n return realtime.getConnectionState();\n },\n };\n}\n"],"mappings":"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","names":["FileVisibility","GrantType","CodeChallengeMethod","SharePermission","hash","callbacks","document"]}
1
+ {"version":3,"sources":["../../../../libs/sdk/src/core/errors.ts","../../../../libs/sdk/src/core/http-client.ts","../../../../libs/sdk/src/core/token-storage.ts","../../../../libs/sdk/src/core/token-manager.ts","../../../../libs/sdk/src/types/common.ts","../../../../libs/sdk/src/core/pkce.ts","../../../../libs/sdk/src/core/realtime.ts","../../../../libs/sdk/src/core/cursor-storage.ts","../../../../libs/sdk/src/modules/auth.module.ts","../../../../libs/sdk/src/modules/storage.module.ts","../../../../libs/sdk/src/modules/database.module.ts","../../../../libs/sdk/src/modules/notifications.module.ts","../../../../libs/sdk/src/modules/functions.module.ts","../../../../libs/sdk/src/client.ts"],"sourcesContent":["export class SpacelrError extends Error {\n readonly code: string;\n readonly statusCode?: number;\n readonly details?: Record<string, unknown>;\n\n constructor(\n message: string,\n code: string,\n statusCode?: number,\n details?: Record<string, unknown>\n ) {\n super(message);\n this.name = 'SpacelrError';\n this.code = code;\n this.statusCode = statusCode;\n this.details = details;\n }\n}\n\nexport class SpacelrAuthError extends SpacelrError {\n constructor(\n message: string,\n statusCode = 401,\n details?: Record<string, unknown>\n ) {\n super(message, 'AUTH_ERROR', statusCode, details);\n this.name = 'SpacelrAuthError';\n }\n}\n\nexport class SpacelrNetworkError extends SpacelrError {\n constructor(message: string, details?: Record<string, unknown>) {\n super(message, 'NETWORK_ERROR', undefined, details);\n this.name = 'SpacelrNetworkError';\n }\n}\n\nexport class SpacelrTimeoutError extends SpacelrError {\n constructor(timeoutMs: number) {\n super(\n `Request timed out after ${timeoutMs}ms`,\n 'TIMEOUT_ERROR',\n undefined,\n { timeoutMs }\n );\n this.name = 'SpacelrTimeoutError';\n }\n}\n\nexport class SpacelrTwoFactorRequiredError extends SpacelrError {\n readonly twoFactorToken: string;\n\n constructor(twoFactorToken: string, details?: Record<string, unknown>) {\n super(\n 'Two-factor authentication required',\n 'TWO_FACTOR_REQUIRED',\n 200,\n details\n );\n this.name = 'SpacelrTwoFactorRequiredError';\n this.twoFactorToken = twoFactorToken;\n }\n}\n\nexport class SpacelrEmailVerificationRequiredError extends SpacelrError {\n readonly emailSent: boolean;\n\n constructor(emailSent: boolean, details?: Record<string, unknown>) {\n super(\n emailSent\n ? 'Email verification required. A new verification email has been sent.'\n : 'Email verification required. Please check your inbox for the verification email.',\n 'EMAIL_VERIFICATION_REQUIRED',\n 200,\n details\n );\n this.name = 'SpacelrEmailVerificationRequiredError';\n this.emailSent = emailSent;\n }\n}\n","import { SpacelrClientConfig, ApiResponse } from '../types';\nimport {\n SpacelrError,\n SpacelrAuthError,\n SpacelrNetworkError,\n SpacelrTimeoutError,\n SpacelrTwoFactorRequiredError,\n SpacelrEmailVerificationRequiredError,\n} from './errors';\nimport { TokenManager } from './token-manager';\n\nexport interface HttpRequestOptions {\n method: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';\n path: string;\n body?: unknown;\n authenticated?: boolean;\n headers?: Record<string, string>;\n query?: Record<string, string | number | undefined>;\n /** Send cookies cross-origin (credentials: 'include'). Defaults to true for /auth/ paths. */\n withCredentials?: boolean;\n}\n\nexport class HttpClient {\n private config: SpacelrClientConfig;\n private tokenManager: TokenManager;\n\n constructor(config: SpacelrClientConfig, tokenManager: TokenManager) {\n this.config = config;\n this.tokenManager = tokenManager;\n }\n\n async request<T>(options: HttpRequestOptions): Promise<T> {\n return this.requestWithRetry<T>(options, false);\n }\n\n private async requestWithRetry<T>(\n options: HttpRequestOptions,\n isRetry: boolean,\n ): Promise<T> {\n const url = this.buildUrl(options.path, options.query);\n const headers = await this.buildHeaders(options);\n const timeout = this.config.timeout ?? 30000;\n const includeCredentials = options.withCredentials ?? options.path.startsWith('/auth/');\n\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), timeout);\n\n try {\n const response = await fetch(url, {\n method: options.method,\n headers,\n body: options.body ? JSON.stringify(options.body) : undefined,\n signal: controller.signal,\n ...(includeCredentials && { credentials: 'include' as RequestCredentials }),\n });\n\n const responseBody = await this.parseResponse(response);\n\n if (!response.ok) {\n if (options.authenticated && response.status === 401) {\n if (await this.recoverFromAuthFailure(isRetry)) {\n return this.requestWithRetry<T>(options, true);\n }\n }\n this.throwHttpError(response.status, responseBody);\n }\n\n return this.extractData<T>(responseBody);\n } catch (error) {\n if (error instanceof SpacelrError) throw error;\n\n if (error instanceof DOMException && error.name === 'AbortError') {\n throw new SpacelrTimeoutError(timeout);\n }\n\n throw new SpacelrNetworkError(\n error instanceof Error ? error.message : 'Network request failed'\n );\n } finally {\n clearTimeout(timeoutId);\n }\n }\n\n async uploadForm<T>(\n path: string,\n formData: FormData,\n onProgress?: (event: { loaded: number; total: number }) => void,\n ): Promise<T> {\n return this.uploadFormWithRetry<T>(path, formData, onProgress, false);\n }\n\n private async uploadFormWithRetry<T>(\n path: string,\n formData: FormData,\n onProgress: ((event: { loaded: number; total: number }) => void) | undefined,\n isRetry: boolean,\n ): Promise<T> {\n const url = this.buildUrl(path);\n const headers = await this.buildFormHeaders();\n const timeoutMs = this.config.timeout ?? 30000;\n\n try {\n if (onProgress) {\n return await this.uploadFormWithProgress<T>(url, headers, formData, onProgress, timeoutMs);\n }\n return await this.uploadFormOnce<T>(url, headers, formData, timeoutMs);\n } catch (error) {\n if (error instanceof SpacelrAuthError && error.statusCode === 401) {\n if (await this.recoverFromAuthFailure(isRetry)) {\n return this.uploadFormWithRetry<T>(path, formData, onProgress, true);\n }\n }\n throw error;\n }\n }\n\n /**\n * Shared handler for a 401 on an authenticated request. Returns true if the\n * caller should retry with a refreshed token. 403 is never routed here —\n * it means \"forbidden for this action\" and must not trigger logout.\n *\n * `emitAuthLost('unauthorized')` is deduped inside TokenManager, so it's a\n * no-op if `executeRefresh` already emitted 'refresh-failed'.\n */\n private async recoverFromAuthFailure(isRetry: boolean): Promise<boolean> {\n if (isRetry) {\n this.tokenManager.emitAuthLost('unauthorized');\n return false;\n }\n const refreshed = await this.tokenManager.forceRefresh();\n if (refreshed) return true;\n this.tokenManager.emitAuthLost('unauthorized');\n return false;\n }\n\n private async uploadFormOnce<T>(\n url: string,\n headers: Record<string, string>,\n formData: FormData,\n timeoutMs: number,\n ): Promise<T> {\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), timeoutMs);\n\n try {\n const response = await fetch(url, {\n method: 'POST',\n headers,\n body: formData,\n signal: controller.signal,\n });\n\n const responseBody = await this.parseResponse(response);\n\n if (!response.ok) {\n this.throwHttpError(response.status, responseBody);\n }\n\n return this.extractData<T>(responseBody);\n } catch (error) {\n if (error instanceof SpacelrError) throw error;\n\n if (error instanceof DOMException && error.name === 'AbortError') {\n throw new SpacelrTimeoutError(timeoutMs);\n }\n\n throw new SpacelrNetworkError(\n error instanceof Error ? error.message : 'Network request failed'\n );\n } finally {\n clearTimeout(timeoutId);\n }\n }\n\n private uploadFormWithProgress<T>(\n url: string,\n headers: Record<string, string>,\n formData: FormData,\n onProgress: (event: { loaded: number; total: number }) => void,\n timeoutMs: number,\n ): Promise<T> {\n return new Promise<T>((resolve, reject) => {\n const xhr = new XMLHttpRequest();\n xhr.open('POST', url);\n xhr.timeout = timeoutMs;\n\n for (const [key, value] of Object.entries(headers)) {\n xhr.setRequestHeader(key, value);\n }\n\n xhr.upload.addEventListener('progress', (e) => {\n if (e.lengthComputable) {\n onProgress({ loaded: e.loaded, total: e.total });\n }\n });\n\n xhr.addEventListener('load', () => {\n try {\n const contentType = xhr.getResponseHeader('content-type') ?? '';\n if (!contentType.includes('application/json')) {\n if (xhr.status >= 200 && xhr.status < 300) {\n resolve({ success: true, data: xhr.responseText } as unknown as T);\n } else if (xhr.status === 401 || xhr.status === 403) {\n // Surface as SpacelrAuthError for consistent typing with the\n // JSON path below. Only 401 actually triggers the upload retry\n // wrapper (uploadFormWithRetry filters on statusCode === 401);\n // 403 propagates unchanged as a permission denial.\n reject(new SpacelrAuthError(`HTTP ${xhr.status}`, xhr.status));\n } else {\n reject(new SpacelrNetworkError(`HTTP ${xhr.status}: ${xhr.responseText.slice(0, 200)}`));\n }\n return;\n }\n const body = JSON.parse(xhr.responseText);\n if (xhr.status >= 200 && xhr.status < 300) {\n resolve(this.extractData<T>(body));\n } else {\n this.throwHttpError(xhr.status, body);\n }\n } catch (error) {\n if (error instanceof SpacelrError) {\n reject(error);\n } else {\n reject(new SpacelrNetworkError('Failed to parse response'));\n }\n }\n });\n\n xhr.addEventListener('error', () => {\n reject(new SpacelrNetworkError('Network request failed'));\n });\n\n xhr.addEventListener('timeout', () => {\n xhr.abort();\n reject(new SpacelrTimeoutError(timeoutMs));\n });\n\n xhr.send(formData);\n });\n }\n\n private buildUrl(\n path: string,\n query?: Record<string, string | number | undefined>\n ): string {\n const cleanPath = path.startsWith('/') ? path : `/${path}`;\n // .well-known endpoints are served at the origin without the API prefix\n const base = cleanPath.startsWith('/.well-known')\n ? new URL(this.config.apiUrl).origin\n : this.config.apiUrl.replace(/\\/+$/, '');\n const url = new URL(`${base}${cleanPath}`);\n\n if (query) {\n for (const [key, value] of Object.entries(query)) {\n if (value !== undefined) {\n url.searchParams.set(key, String(value));\n }\n }\n }\n\n return url.toString();\n }\n\n private async buildHeaders(\n options: HttpRequestOptions\n ): Promise<Record<string, string>> {\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n 'x-client-id': this.config.clientId,\n 'x-project-id': this.config.projectId,\n ...options.headers,\n };\n\n if (options.authenticated) {\n const token = await this.tokenManager.getAccessToken();\n if (token) {\n headers['Authorization'] = `Bearer ${token}`;\n }\n }\n\n return headers;\n }\n\n private async buildFormHeaders(): Promise<Record<string, string>> {\n const headers: Record<string, string> = {\n 'x-client-id': this.config.clientId,\n 'x-project-id': this.config.projectId,\n };\n\n const token = await this.tokenManager.getAccessToken();\n if (token) {\n headers['Authorization'] = `Bearer ${token}`;\n }\n\n return headers;\n }\n\n private async parseResponse(\n response: Response\n ): Promise<ApiResponse | Record<string, unknown>> {\n const contentType = response.headers.get('content-type') ?? '';\n if (contentType.includes('application/json')) {\n return response.json();\n }\n const text = await response.text();\n return { success: response.ok, data: text };\n }\n\n private throwHttpError(\n statusCode: number,\n body: ApiResponse | Record<string, unknown>\n ): never {\n const apiBody = body as ApiResponse;\n const message =\n apiBody.error?.message ?? `HTTP ${statusCode}`;\n const code = apiBody.error?.code ?? `HTTP_${statusCode}`;\n const details = apiBody.error?.details;\n\n if (statusCode === 401 || statusCode === 403) {\n throw new SpacelrAuthError(message, statusCode, details);\n }\n\n throw new SpacelrError(message, code, statusCode, details);\n }\n\n private extractData<T>(body: ApiResponse | Record<string, unknown>): T {\n const apiBody = body as ApiResponse;\n // If the response wraps data in a standard envelope, extract it\n if ('success' in apiBody && apiBody.data !== undefined) {\n const data = apiBody.data as Record<string, unknown>;\n // Check if the response indicates email verification is required\n if (data['emailVerificationRequired'] === true) {\n throw new SpacelrEmailVerificationRequiredError(data['emailSent'] === true);\n }\n // Check if the response indicates a 2FA challenge\n if (data['twoFactorRequired'] === true && typeof data['twoFactorToken'] === 'string') {\n throw new SpacelrTwoFactorRequiredError(data['twoFactorToken']);\n }\n return apiBody.data as T;\n }\n // Check unwrapped responses\n const rawBody = body as Record<string, unknown>;\n if (rawBody['emailVerificationRequired'] === true) {\n throw new SpacelrEmailVerificationRequiredError(rawBody['emailSent'] === true);\n }\n if (rawBody['twoFactorRequired'] === true && typeof rawBody['twoFactorToken'] === 'string') {\n throw new SpacelrTwoFactorRequiredError(rawBody['twoFactorToken']);\n }\n // Otherwise return the whole body (e.g. OIDC endpoints return raw objects)\n return body as T;\n }\n}\n","import { StoredTokens, TokenStorage } from '../types';\n\nexport type { TokenStorage };\n\nexport class MemoryTokenStorage implements TokenStorage {\n private tokens: StoredTokens | null = null;\n\n async getTokens(): Promise<StoredTokens | null> {\n return this.tokens;\n }\n\n async setTokens(tokens: StoredTokens): Promise<void> {\n this.tokens = tokens;\n }\n\n async clearTokens(): Promise<void> {\n this.tokens = null;\n }\n}\n\nexport class BrowserTokenStorage implements TokenStorage {\n private readonly storageKey: string;\n\n constructor(storageKey = 'spacelr_tokens') {\n this.storageKey = storageKey;\n }\n\n async getTokens(): Promise<StoredTokens | null> {\n try {\n const raw = localStorage.getItem(this.storageKey);\n if (!raw) return null;\n return JSON.parse(raw) as StoredTokens;\n } catch {\n return null;\n }\n }\n\n async setTokens(tokens: StoredTokens): Promise<void> {\n try {\n localStorage.setItem(this.storageKey, JSON.stringify(tokens));\n } catch {\n // Quota exceeded or private browsing — silently ignore\n }\n }\n\n async clearTokens(): Promise<void> {\n try {\n localStorage.removeItem(this.storageKey);\n } catch {\n // localStorage unavailable — silently ignore\n }\n }\n}\n","import { StoredTokens, TokenStorage } from '../types';\nimport { MemoryTokenStorage } from './token-storage';\n\nexport type RefreshCallback = (refreshToken: string) => Promise<StoredTokens>;\nexport type AuthLostReason = 'refresh-failed' | 'unauthorized';\nexport type TokenRefreshedListener = (tokens: StoredTokens) => void;\nexport type AuthLostListener = (reason: AuthLostReason) => void;\n\nexport class TokenManager {\n private storage: TokenStorage;\n private refreshBufferSeconds: number;\n private refreshCallback: RefreshCallback | null = null;\n private refreshPromise: Promise<StoredTokens> | null = null;\n private tokenRefreshedListeners = new Set<TokenRefreshedListener>();\n private authLostListeners = new Set<AuthLostListener>();\n // Guard so callbacks that run during auth-loss handling (e.g. a logout()\n // that hits `/auth/logout` with a dead token) don't re-emit and loop.\n // Cleared by setTokens() / clearTokens() to re-arm for the next session.\n private authLostEmitted = false;\n\n constructor(storage?: TokenStorage, refreshBufferSeconds = 60) {\n this.storage = storage ?? new MemoryTokenStorage();\n this.refreshBufferSeconds = refreshBufferSeconds;\n }\n\n setRefreshCallback(callback: RefreshCallback): void {\n this.refreshCallback = callback;\n }\n\n async getAccessToken(): Promise<string | null> {\n const tokens = await this.storage.getTokens();\n if (!tokens) return null;\n\n if (this.isTokenExpired(tokens)) {\n const refreshed = await this.tryRefresh(tokens);\n return refreshed?.accessToken ?? null;\n }\n\n if (this.shouldRefresh(tokens)) {\n // Trigger background refresh but return current token\n this.tryRefresh(tokens).catch(() => {\n // Ignore background refresh failures (onAuthLost will have fired)\n });\n }\n\n return tokens.accessToken;\n }\n\n async setTokens(tokens: StoredTokens): Promise<void> {\n // Defensive default: when a refreshable session comes in without an\n // explicit expiry (typical of bridge code that forgot to thread\n // `expires_in`), treat the access-token as already due. The next\n // `getAccessToken()` then takes the BLOCKING-refresh branch and the\n // very next authenticated request hits the gateway with a fresh\n // token. Worst case: one extra refresh round-trip — vs. silently\n // letting an unrefreshable session run forever client-side until a\n // 401 surfaces the expiry.\n //\n // Skip the default when no refreshToken is present: there's nothing\n // to refresh with, so forcing an \"expired\" state would just produce\n // useless `null` returns from getAccessToken. This also keeps\n // ephemeral test fixtures working.\n //\n // Use the same falsy-guard as tryRefresh (`!tokens.refreshToken`) so a\n // `refreshToken: ''` (some servers return an empty string when refresh\n // is unavailable for this session) doesn't trip the default and brick\n // an otherwise-valid ephemeral session by routing it into a refresh\n // path that immediately bails on the empty-string check.\n const needsDefault =\n tokens.expiresAt === undefined && !!tokens.refreshToken;\n const normalised: StoredTokens = needsDefault\n ? { ...tokens, expiresAt: Math.floor(Date.now() / 1000) }\n : tokens;\n await this.storage.setTokens(normalised);\n this.authLostEmitted = false;\n }\n\n async clearTokens(): Promise<void> {\n await this.storage.clearTokens();\n this.refreshPromise = null;\n this.authLostEmitted = false;\n }\n\n async getStoredTokens(): Promise<StoredTokens | null> {\n return this.storage.getTokens();\n }\n\n /**\n * Force a refresh using the current stored refresh token.\n * Returns the new access token, or null if refresh is not possible or fails.\n * A rejecting custom `TokenStorage.getTokens()` is treated as \"no tokens\"\n * so callers (notably `HttpClient`'s 401 retry path) see a clean `null`\n * rather than an exception that would get wrapped as a network error.\n */\n async forceRefresh(): Promise<string | null> {\n // Once auth is lost, further refresh attempts are pointless and would\n // spam the refresh endpoint while the consumer is handling the logout.\n if (this.authLostEmitted) return null;\n let tokens: StoredTokens | null;\n try {\n tokens = await this.storage.getTokens();\n } catch {\n return null;\n }\n if (!tokens) return null;\n const refreshed = await this.tryRefresh(tokens);\n return refreshed?.accessToken ?? null;\n }\n\n onTokenRefreshed(listener: TokenRefreshedListener): () => void {\n this.tokenRefreshedListeners.add(listener);\n return () => this.tokenRefreshedListeners.delete(listener);\n }\n\n onAuthLost(listener: AuthLostListener): () => void {\n this.authLostListeners.add(listener);\n return () => this.authLostListeners.delete(listener);\n }\n\n emitAuthLost(reason: AuthLostReason): void {\n if (this.authLostEmitted) return;\n this.authLostEmitted = true;\n for (const listener of this.authLostListeners) {\n try {\n listener(reason);\n } catch {\n // Ignore listener errors\n }\n }\n }\n\n private isTokenExpired(tokens: StoredTokens): boolean {\n if (!tokens.expiresAt) return false;\n return Date.now() >= tokens.expiresAt * 1000;\n }\n\n private shouldRefresh(tokens: StoredTokens): boolean {\n if (!tokens.expiresAt || !tokens.refreshToken) return false;\n const bufferMs = this.refreshBufferSeconds * 1000;\n return Date.now() >= tokens.expiresAt * 1000 - bufferMs;\n }\n\n private async tryRefresh(\n tokens: StoredTokens\n ): Promise<StoredTokens | null> {\n if (!tokens.refreshToken || !this.refreshCallback) return null;\n\n // Deduplicate concurrent refresh calls — both the leader and any followers\n // must get `null` on failure, not a rejected promise. Otherwise parallel\n // callers of getAccessToken/forceRefresh will throw unexpectedly while the\n // leader gracefully returns null.\n if (this.refreshPromise) {\n try {\n return await this.refreshPromise;\n } catch {\n return null;\n }\n }\n\n this.refreshPromise = this.executeRefresh(tokens.refreshToken);\n\n try {\n return await this.refreshPromise;\n } catch {\n return null;\n } finally {\n this.refreshPromise = null;\n }\n }\n\n private async executeRefresh(refreshToken: string): Promise<StoredTokens> {\n // `tryRefresh` guards against a missing callback before reaching here.\n const callback = this.refreshCallback as RefreshCallback;\n try {\n const newTokens = await callback(refreshToken);\n await this.storage.setTokens(newTokens);\n this.emitTokenRefreshed(newTokens);\n return newTokens;\n } catch (error) {\n this.emitAuthLost('refresh-failed');\n throw error;\n }\n }\n\n private emitTokenRefreshed(tokens: StoredTokens): void {\n for (const listener of this.tokenRefreshedListeners) {\n try {\n listener(tokens);\n } catch {\n // Ignore listener errors\n }\n }\n }\n}\n","export enum FileVisibility {\n PRIVATE = 'private',\n SHARED = 'shared',\n PUBLIC = 'public',\n}\n\nexport enum GrantType {\n AUTHORIZATION_CODE = 'authorization_code',\n CLIENT_CREDENTIALS = 'client_credentials',\n REFRESH_TOKEN = 'refresh_token',\n}\n\nexport enum CodeChallengeMethod {\n PLAIN = 'plain',\n S256 = 'S256',\n}\n\nexport enum SharePermission {\n READ = 'read',\n WRITE = 'write',\n}\n\nexport interface ApiResponse<T = unknown> {\n success: boolean;\n data?: T;\n error?: {\n message: string;\n code: string;\n details?: Record<string, unknown>;\n };\n}\n","import { PKCEChallenge, CodeChallengeMethod } from '../types';\n\nfunction generateRandomBytes(length: number): Uint8Array {\n if (\n typeof globalThis.crypto !== 'undefined' &&\n globalThis.crypto.getRandomValues\n ) {\n const bytes = new Uint8Array(length);\n globalThis.crypto.getRandomValues(bytes);\n return bytes;\n }\n\n // Node.js fallback\n const nodeCrypto = require('crypto') as typeof import('crypto');\n return new Uint8Array(nodeCrypto.randomBytes(length));\n}\n\nfunction base64UrlEncode(buffer: Uint8Array): string {\n let binary = '';\n for (let i = 0; i < buffer.length; i++) {\n binary += String.fromCharCode(buffer[i]);\n }\n\n const base64 = btoa(binary);\n return base64.replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/, '');\n}\n\nasync function sha256(input: string): Promise<Uint8Array> {\n if (\n typeof globalThis.crypto !== 'undefined' &&\n globalThis.crypto.subtle\n ) {\n const encoder = new TextEncoder();\n const data = encoder.encode(input);\n const hash = await globalThis.crypto.subtle.digest('SHA-256', data);\n return new Uint8Array(hash);\n }\n\n // Node.js fallback\n const nodeCrypto = require('crypto') as typeof import('crypto');\n const hash = nodeCrypto.createHash('sha256').update(input).digest();\n return new Uint8Array(hash);\n}\n\nexport async function generatePKCEChallenge(): Promise<PKCEChallenge> {\n const randomBytes = generateRandomBytes(32);\n const codeVerifier = base64UrlEncode(randomBytes);\n\n const hashBytes = await sha256(codeVerifier);\n const codeChallenge = base64UrlEncode(hashBytes);\n\n return {\n codeVerifier,\n codeChallenge,\n codeChallengeMethod: CodeChallengeMethod.S256,\n };\n}\n","import { io, Socket } from 'socket.io-client';\n\n// Wait this long before retrying a failed socket rebuild. Short enough that\n// a transient network stumble heals without waiting for a user-visible wake\n// event, long enough not to hammer an unreachable server.\nconst REBUILD_RETRY_DELAY_MS = 5000;\n\n/** Ack error codes that indicate a permanent server-side rejection — evict\n * the subscription on reconnect rather than retrying forever. Transient\n * codes (redis-unavailable, replay-error, disconnected) stay in the map so\n * the next reconnect tries again. */\nconst PERMANENT_STREAM_ACK_ERRORS = new Set<string>([\n 'not-stream-collection',\n 'Collection not found',\n 'Invalid sinceId format',\n 'Not a member of this project',\n 'Subscribe denied',\n]);\n\nexport type ConnectionState = 'connected' | 'reconnecting' | 'disconnected';\n\n// NOTE: This is the public, client-facing event shape. It intentionally\n// diverges from `DatabaseChangeEvent` in\n// @spacelr-workspace/shared-types/database-events.ts, which still carries\n// the `_source`, `_sourceFunctionId`, and `_chainDepth` orchestration\n// fields used by function-service for loop prevention. The api-gateway\n// strips those fields before forwarding to SDK clients (see\n// `toPublicEvent` in apps/api-gateway-public/.../database-events.gateway.ts),\n// so the SDK type accurately reflects what subscribers actually receive.\nexport interface DatabaseChangeEvent {\n type: 'insert' | 'update' | 'delete';\n projectId: string;\n collectionName: string;\n documentId: string;\n document?: Record<string, unknown>;\n timestamp: number;\n /**\n * Redis Stream entry ID (e.g. \"1745234123-0\") for stream-mode events, or a\n * synthesised `${projectId}:${collectionName}:${timestamp}-${documentId}`\n * for pubsub-mode events. Used by function triggers as an idempotency key.\n */\n eventId?: string;\n}\n\nexport interface RealtimeConfig {\n baseUrl: string;\n getToken: () => Promise<string | null>;\n /**\n * Optional subscription to token-refresh events. When wired, the realtime\n * client will push the fresh access token to the server via a\n * `reauthenticate` message so the open socket stays authenticated without\n * dropping the connection.\n */\n onTokenRefreshed?: (listener: (accessToken: string) => void) => () => void;\n}\n\nexport type GapReason =\n | 'outside-retention-window'\n | 'redis-unavailable'\n | 'replay-error'\n | 'replay-truncated';\n\nexport interface StreamGapInfo {\n projectId: string;\n collectionName: string;\n /** Configured retention as a human-readable string (e.g. \"maxLen: 10000\", \"24h\", or \"unknown\"). */\n retentionWindow: string;\n reason: GapReason;\n}\n\nexport interface StreamSubscriptionOptions {\n projectId: string;\n collectionName: string;\n sinceId?: string;\n where?: Record<string, string | number | boolean>;\n /**\n * Called for each delivered event. Awaited — the cursor does NOT advance\n * until this resolves. If the callback throws, the cursor is NOT advanced\n * and `onError` is called; the event is not retried (at-most-once on\n * handler failure, consistent with the server-side StreamReaderService).\n */\n onEvent: (entry: { eventId: string; event: DatabaseChangeEvent }) => void | Promise<void>;\n /** Called when the server emits an event-gap. See GapReason for the four codes. */\n onGap?: (info: StreamGapInfo) => void;\n /** Called on handshake rejection (not-stream-collection, invalid sinceId, etc.) and handler failures. */\n onError?: (error: Error) => void;\n}\n\ninterface StreamSubscriptionState {\n options: StreamSubscriptionOptions;\n /** Last eventId successfully delivered to onEvent. Advances after each resolved handler. */\n lastDeliveredId: string | null;\n /** `stream:<projectId>:<collectionName>` — used as the Map key. */\n streamKey: string;\n /** Serializes onEvent calls for THIS subscriber so cursor advances in order\n * even when the socket delivers multiple events before the previous handler resolves. */\n dispatchQueue: Promise<void>;\n}\n\ninterface StreamSubscribeAck {\n error?: string;\n message?: string;\n subscribed?: string;\n lastDeliveredId?: string | null;\n}\n\nexport class RealtimeClient {\n private socket: Socket | null = null;\n private config: RealtimeConfig;\n private subscriptions = new Map<string, Set<(event: DatabaseChangeEvent) => void>>();\n private connecting: Promise<void> | null = null;\n // Store original where objects per room for reconnect resubscription\n private roomWhereMap = new Map<string, Record<string, string | number | boolean>>();\n private unsubscribeFromTokenRefreshed: (() => void) | null = null;\n // Wake-up listeners (browser only) — recover from long OS suspends where\n // socket.io's internal reconnect loop may have already given up.\n private onVisibilityChange: (() => void) | null = null;\n private onOnline: (() => void) | null = null;\n // Set by `disconnect()`; checked by async paths (rebuildSocket, deferred\n // retries) to avoid reviving a client the consumer has torn down.\n private disposed = false;\n // Scheduled retry after a failed `rebuildSocket()`; cleared when it fires\n // or when `disconnect()` tears down.\n private rebuildRetryTimer: ReturnType<typeof setTimeout> | null = null;\n private connectionState: ConnectionState = 'disconnected';\n private connectionStateListeners = new Set<(state: ConnectionState) => void>();\n private streamSubscriptions = new Map<string, Set<StreamSubscriptionState>>();\n\n constructor(config: RealtimeConfig) {\n this.config = config;\n }\n\n async subscribe(\n projectId: string,\n collectionName: string,\n callback: (event: DatabaseChangeEvent) => void,\n onError?: (error: Error) => void,\n where?: Record<string, string | number | boolean>,\n ): Promise<() => void> {\n this.ensureWakeListeners();\n if (this.connectionState === 'disconnected') {\n this.setConnectionState('reconnecting');\n }\n await this.ensureConnected();\n\n const room = this.buildRoomKey(projectId, collectionName, where);\n\n // Track the callback\n if (!this.subscriptions.has(room)) {\n this.subscriptions.set(room, new Set());\n }\n const callbacks = this.subscriptions.get(room);\n callbacks?.add(callback);\n\n // If this is the first subscription for this room, tell the server\n if (callbacks?.size === 1) {\n if (where && Object.keys(where).length > 0) {\n this.roomWhereMap.set(room, where);\n }\n const payload: Record<string, unknown> = { projectId, collectionName };\n if (where && Object.keys(where).length > 0) {\n payload['where'] = where;\n }\n this.socket?.emit(\n 'subscribe',\n payload,\n (response: { subscribed?: string; error?: string }) => {\n if (response.error && onError) {\n onError(new Error(response.error));\n }\n },\n );\n }\n\n // Return unsubscribe function\n return () => {\n const callbacks = this.subscriptions.get(room);\n if (callbacks) {\n callbacks.delete(callback);\n if (callbacks.size === 0) {\n this.subscriptions.delete(room);\n this.roomWhereMap.delete(room);\n const payload: Record<string, unknown> = { projectId, collectionName };\n if (where && Object.keys(where).length > 0) {\n payload['where'] = where;\n }\n this.socket?.emit('unsubscribe', payload);\n }\n }\n };\n }\n\n /**\n * Subscribe to a stream-mode collection using Redis Streams replay +\n * cursor-based delivery. Parallel to `subscribe()` (which targets\n * pubsub-mode collections). The server validates that the collection is\n * configured in stream mode and rejects the handshake otherwise.\n *\n * The per-subscription cursor (`lastDeliveredId`) advances after each\n * `onEvent` promise resolves, so reconnects resume from the last delivered\n * event rather than the originally-subscribed `sinceId`.\n */\n async subscribeWithCursor(\n options: StreamSubscriptionOptions,\n ): Promise<() => void> {\n this.ensureWakeListeners();\n if (this.connectionState === 'disconnected') {\n this.setConnectionState('reconnecting');\n }\n await this.ensureConnected();\n\n const streamKey = `stream:${options.projectId}:${options.collectionName}`;\n const state: StreamSubscriptionState = {\n options,\n lastDeliveredId: options.sinceId ?? null,\n streamKey,\n dispatchQueue: Promise.resolve(),\n };\n\n // Dedup path: if another local subscriber already holds the server-side\n // slot for this streamKey, just piggy-back on the existing fan-out. The\n // gateway collapses duplicate `subscribe-events` per socket per streamKey\n // (replacing the prior reader), so re-emitting would tear down the other\n // subscriber's server-side state.\n const existing = this.streamSubscriptions.get(streamKey);\n if (existing && existing.size > 0) {\n existing.add(state);\n return () => this.unsubscribeStream(state);\n }\n\n // Register BEFORE the handshake so replay `event` / `event-gap` frames\n // emitted during subscribe fanout find this subscriber. If the ack comes\n // back with an error, we tear down the registration in the catch block.\n const set = new Set<StreamSubscriptionState>();\n set.add(state);\n this.streamSubscriptions.set(streamKey, set);\n\n try {\n const ack = await this.emitSubscribeEvents(state);\n if (ack.error) {\n // The primary handshake failed. Evict this subscriber AND any other\n // subscribers that joined via the dedup path for the same streamKey —\n // they have no active server subscription, so they would silently miss\n // events until the next full reconnect.\n const message = ack.message ?? ack.error;\n const err = new Error(message);\n this.evictStreamKey(streamKey, err, state);\n options.onError?.(err);\n throw err;\n }\n return () => this.unsubscribeStream(state);\n } catch (err) {\n // Safety net: emitSubscribeEvents always resolves, so this catch only\n // fires when `throw err` from the ack-error branch re-runs here. evictStreamKey\n // is idempotent — the second call is a no-op if the streamKey entry was already\n // deleted. Kept in case a future edit introduces a throw before the error branch.\n const error = err instanceof Error ? err : new Error(String(err));\n this.evictStreamKey(streamKey, error, state);\n throw error;\n }\n }\n\n /**\n * Tear down the realtime client permanently. After this call the instance\n * is disposed — subsequent `subscribe()` calls will not re-establish a\n * connection. Create a new `RealtimeClient` (via `createClient`) if you\n * need to reconnect after a logout.\n */\n disconnect(): void {\n this.setConnectionState('disconnected');\n this.disposed = true;\n if (this.rebuildRetryTimer) {\n clearTimeout(this.rebuildRetryTimer);\n this.rebuildRetryTimer = null;\n }\n if (this.unsubscribeFromTokenRefreshed) {\n this.unsubscribeFromTokenRefreshed();\n this.unsubscribeFromTokenRefreshed = null;\n }\n this.detachWakeListeners();\n if (this.socket) {\n this.socket.disconnect();\n this.socket = null;\n }\n this.subscriptions.clear();\n this.roomWhereMap.clear();\n this.streamSubscriptions.clear();\n this.connecting = null;\n }\n\n getConnectionState(): ConnectionState {\n return this.connectionState;\n }\n\n onConnectionStateChanged(listener: (state: ConnectionState) => void): () => void {\n this.connectionStateListeners.add(listener);\n return () => this.connectionStateListeners.delete(listener);\n }\n\n private setConnectionState(next: ConnectionState): void {\n if (this.connectionState === next) return;\n this.connectionState = next;\n for (const listener of this.connectionStateListeners) {\n try {\n listener(next);\n } catch {\n // Ignore listener errors, matching onTokenRefreshed / onAuthLost.\n }\n }\n }\n\n private buildRoomKey(\n projectId: string,\n collectionName: string,\n where?: Record<string, string | number | boolean>,\n ): string {\n const base = `db:${projectId}:${collectionName}`;\n if (!where || Object.keys(where).length === 0) {\n return base;\n }\n const filterStr = Object.keys(where)\n .sort()\n .map((k) => `${k}=${String(where[k])}`)\n .join('&');\n return `${base}?${filterStr}`;\n }\n\n private async ensureConnected(): Promise<void> {\n if (this.socket?.connected) return;\n\n if (this.connecting) {\n return this.connecting;\n }\n\n this.connecting = this.connect();\n try {\n await this.connecting;\n } finally {\n this.connecting = null;\n }\n }\n\n private async connect(): Promise<void> {\n const token = await this.config.getToken();\n if (!token) {\n throw new Error('No authentication token available');\n }\n\n // Derive WebSocket URL from the API URL\n const wsUrl = this.config.baseUrl.replace(/\\/api\\/v\\d+\\/?$/, '');\n\n return new Promise<void>((resolve, reject) => {\n let initialConnect = true;\n\n this.socket = io(`${wsUrl}/database`, {\n auth: async (cb: (data: Record<string, unknown>) => void) => {\n try {\n const freshToken = await this.config.getToken();\n cb({ token: freshToken });\n } catch {\n cb({ token: null });\n }\n },\n transports: ['websocket'],\n reconnection: false, // Disabled until first successful connect\n });\n\n this.socket.on('authenticated', () => {\n // If the consumer called disconnect() while an in-flight authenticate\n // was in progress, don't emit a spurious `connected` on a disposed\n // client. socket.io can deliver a queued event after the socket was\n // detached — the `this.socket = null` and disposed flag are set but\n // the handler is still bound to the old socket instance.\n if (this.disposed) return;\n this.setConnectionState('connected');\n if (initialConnect) {\n initialConnect = false;\n // Enable reconnection after first successful connect\n if (this.socket) {\n this.socket.io.opts.reconnection = true;\n this.socket.io.opts.reconnectionDelay = 1000;\n this.socket.io.opts.reconnectionDelayMax = 5000;\n this.socket.io.opts.reconnectionAttempts = 50;\n }\n // Wire up token-refresh → reauthenticate so the open socket picks up\n // new JWTs without disconnecting.\n if (this.config.onTokenRefreshed && !this.unsubscribeFromTokenRefreshed) {\n this.unsubscribeFromTokenRefreshed = this.config.onTokenRefreshed(\n (accessToken) => {\n this.socket?.emit('reauthenticate', { token: accessToken });\n },\n );\n }\n resolve();\n } else {\n // Reconnect path — server has now set socket.data.userId. It is\n // safe to re-subscribe. (Doing this on 'connect' races the async\n // handleConnection and produces silent \"Not authenticated\" errors.)\n this.resubscribeAll();\n void this.resubscribeAllStreams();\n }\n });\n\n this.socket.on('connect_error', (err) => {\n if (initialConnect) {\n reject(new Error(`WebSocket connection failed: ${err.message}`));\n }\n });\n\n // socket.io has exhausted its reconnection attempts (e.g. after a long\n // laptop suspend where all retries burned in a few seconds on resume).\n // Build a fresh socket so the session can recover.\n this.socket.io.on('reconnect_failed', () => {\n void this.rebuildSocket();\n });\n\n this.socket.on('db:event', (event: DatabaseChangeEvent) => {\n const base = `db:${event.projectId}:${event.collectionName}`;\n\n // Dispatch to whole-collection subscribers via direct Map lookup\n const baseCallbacks = this.subscriptions.get(base);\n if (baseCallbacks) {\n for (const cb of baseCallbacks) {\n try { cb(event); } catch { /* ignore callback errors */ }\n }\n }\n\n // Dispatch to matching filtered subscriptions\n for (const [room, callbacks] of this.subscriptions) {\n if (room.startsWith(`${base}?`) && this.eventMatchesRoom(event, room)) {\n for (const cb of callbacks) {\n try { cb(event); } catch { /* ignore callback errors */ }\n }\n }\n }\n });\n\n this.socket.on('event', (payload: DatabaseChangeEvent & { eventId?: string }) => {\n this.dispatchStreamEvent(payload).catch(() => undefined);\n });\n\n this.socket.on('event-gap', (info: StreamGapInfo) => {\n const streamKey = `stream:${info.projectId}:${info.collectionName}`;\n const set = this.streamSubscriptions.get(streamKey);\n if (!set) return;\n for (const state of set) {\n state.options.onGap?.(info);\n }\n });\n\n this.socket.on('disconnect', () => {\n if (!this.disposed) {\n this.setConnectionState('reconnecting');\n }\n });\n });\n }\n\n private eventMatchesRoom(event: DatabaseChangeEvent, room: string): boolean {\n const base = `db:${event.projectId}:${event.collectionName}`;\n if (room === base) return true;\n if (!room.startsWith(`${base}?`)) return false;\n\n // For filtered rooms, check the document against the where filter\n const where = this.roomWhereMap.get(room);\n if (!where) return true; // No filter stored for this room\n if (!event.document) return false; // Delete events without document can't match a filter\n\n for (const [key, value] of Object.entries(where)) {\n const docValue = event.document[key];\n if (Array.isArray(docValue)) {\n if (!docValue.some((item) => String(item) === String(value))) {\n return false;\n }\n } else if (String(docValue) !== String(value)) {\n return false;\n }\n }\n return true;\n }\n\n /**\n * Tear down the current socket and create a fresh one. Used by both\n * `reconnect_failed` (socket.io gave up) and the visibility/online wake-up\n * listeners. Preserves the `subscriptions` map so existing rooms can be\n * replayed to the server on the new connection. If the rebuild itself\n * fails it schedules one retry after 5 s — beyond that we rely on the\n * next wake-up event (visibility/online) to try again.\n */\n private async rebuildSocket(): Promise<void> {\n if (this.disposed || this.connecting) return;\n\n const previous = this.socket;\n this.socket = null;\n if (previous) previous.disconnect();\n\n this.setConnectionState('reconnecting');\n\n try {\n await this.ensureConnected();\n this.handlePostRebuild();\n } catch {\n this.scheduleRebuildRetry();\n }\n }\n\n private handlePostRebuild(): void {\n // Capture into a local first — TS narrows `this.socket` to `null` after\n // the earlier assignment and doesn't re-widen through the await above.\n const newSocket: Socket | null = this.socket;\n if (this.disposed) {\n this.socket = null;\n newSocket?.disconnect();\n return;\n }\n // The server has no record of the subscriptions we held on the old\n // (dead) socket, so replay them here. The new socket's `authenticated`\n // handler took the `initialConnect = true` branch and doesn't resubscribe.\n if (this.subscriptions.size > 0) {\n this.resubscribeAll();\n }\n if (this.streamSubscriptions.size > 0) {\n void this.resubscribeAllStreams();\n }\n }\n\n private scheduleRebuildRetry(): void {\n // A transient rebuild failure (token fetch rejected, connect_error) —\n // retry once so recovery doesn't stall waiting for a wake event.\n if (this.disposed || this.rebuildRetryTimer) return;\n this.rebuildRetryTimer = setTimeout(() => {\n this.rebuildRetryTimer = null;\n void this.rebuildSocket();\n }, REBUILD_RETRY_DELAY_MS);\n (this.rebuildRetryTimer as unknown as { unref?: () => void }).unref?.();\n }\n\n private ensureWakeListeners(): void {\n if (typeof document === 'undefined' || typeof window === 'undefined') return;\n if (this.onVisibilityChange !== null) return;\n\n const wakeIfUnhealthy = () => {\n if (typeof document !== 'undefined' && document.visibilityState !== 'visible') return;\n if (this.socket?.connected) return;\n if (this.connecting) return;\n void this.rebuildSocket();\n };\n\n this.onVisibilityChange = wakeIfUnhealthy;\n this.onOnline = wakeIfUnhealthy;\n document.addEventListener('visibilitychange', this.onVisibilityChange);\n window.addEventListener('online', this.onOnline);\n }\n\n private detachWakeListeners(): void {\n if (typeof document !== 'undefined' && this.onVisibilityChange) {\n document.removeEventListener('visibilitychange', this.onVisibilityChange);\n }\n if (typeof window !== 'undefined' && this.onOnline) {\n window.removeEventListener('online', this.onOnline);\n }\n this.onVisibilityChange = null;\n this.onOnline = null;\n }\n\n private resubscribeAll(): void {\n for (const [room] of this.subscriptions) {\n // Parse room format: db:{projectId}:{collectionName} or db:{projectId}:{collectionName}?filter\n const queryIdx = room.indexOf('?');\n const basePart = queryIdx >= 0 ? room.substring(0, queryIdx) : room;\n const parts = basePart.split(':');\n if (parts.length >= 3) {\n const projectId = parts[1];\n const collectionName = parts.slice(2).join(':');\n const payload: Record<string, unknown> = { projectId, collectionName };\n\n // Use stored where object to preserve original types (number, boolean)\n const where = this.roomWhereMap.get(room);\n if (where) {\n payload['where'] = where;\n }\n\n this.socket?.emit(\n 'subscribe',\n payload,\n (response: { subscribed?: string; error?: string }) => {\n if (response?.error) {\n // Server rejected resubscription — clean up the stale subscription\n this.subscriptions.delete(room);\n this.roomWhereMap.delete(room);\n }\n },\n );\n }\n }\n }\n\n private emitSubscribeEvents(\n state: StreamSubscriptionState,\n ): Promise<StreamSubscribeAck> {\n return new Promise((resolve) => {\n if (!this.socket) {\n resolve({ error: 'disconnected' });\n return;\n }\n const socket = this.socket;\n\n // socket.io does not call ack callbacks when the socket disconnects mid-handshake.\n // Resolve on disconnect so the Promise can't hang.\n const onDisconnect = (): void => {\n socket.off('disconnect', onDisconnect);\n resolve({ error: 'disconnected' });\n };\n socket.once('disconnect', onDisconnect);\n\n socket.emit(\n 'subscribe-events',\n this.buildStreamPayload(state),\n (ack: StreamSubscribeAck) => {\n socket.off('disconnect', onDisconnect);\n resolve(ack);\n },\n );\n });\n }\n\n private buildStreamPayload(state: StreamSubscriptionState): Record<string, unknown> {\n const { projectId, collectionName, where } = state.options;\n const payload: Record<string, unknown> = { projectId, collectionName };\n // Use the current cursor (advanced by onEvent) so a reconnect picks up\n // from the last delivered event, not the originally-subscribed sinceId.\n if (state.lastDeliveredId) payload['sinceId'] = state.lastDeliveredId;\n if (where && Object.keys(where).length > 0) payload['where'] = where;\n return payload;\n }\n\n private unsubscribeStream(state: StreamSubscriptionState): void {\n const set = this.streamSubscriptions.get(state.streamKey);\n if (!set) return;\n set.delete(state);\n if (set.size === 0) {\n this.streamSubscriptions.delete(state.streamKey);\n // Only release the server-side reader slot when no local subscriber\n // remains for this streamKey. Avoids tearing down a shared slot when\n // one of several local consumers unsubscribes.\n this.socket?.emit('unsubscribe-events', {\n projectId: state.options.projectId,\n collectionName: state.options.collectionName,\n });\n }\n }\n\n private dispatchStreamEvent(\n payload: DatabaseChangeEvent & { eventId?: string },\n ): Promise<void> {\n if (!payload.eventId) return Promise.resolve();\n const streamKey = `stream:${payload.projectId}:${payload.collectionName}`;\n const set = this.streamSubscriptions.get(streamKey);\n if (!set) return Promise.resolve();\n\n const entryId = payload.eventId;\n // Per-subscriber queue — serializes onEvent calls within a subscriber while\n // allowing different subscribers to run in parallel. Without this, two\n // events arriving while the first handler is still awaiting could advance\n // the cursor out of order.\n for (const state of set) {\n state.dispatchQueue = state.dispatchQueue.then(async () => {\n // Guard: subscription may have been cancelled while this was queued.\n const liveSet = this.streamSubscriptions.get(state.streamKey);\n if (!liveSet?.has(state)) return;\n\n try {\n await state.options.onEvent({ eventId: entryId, event: payload });\n state.lastDeliveredId = entryId;\n } catch (err) {\n // At-most-once on handler failure — don't advance the cursor.\n // Surface via onError if provided.\n const error = err instanceof Error ? err : new Error(String(err));\n try {\n state.options.onError?.(error);\n } catch {\n // onError must not break the dispatch chain — swallow any throw.\n }\n }\n });\n }\n return Promise.resolve();\n }\n\n private async resubscribeAllStreams(): Promise<void> {\n // Re-subscribe any active stream subscriptions with the CURRENT cursor so\n // reconnects pick up exactly where we left off. Emit ONE handshake per\n // streamKey — the gateway collapses duplicate `subscribe-events` per\n // socket per streamKey, so re-emitting for every local subscriber would\n // tear down prior state. Replay events delivered over the single\n // handshake fan out to all local subscribers via dispatchStreamEvent.\n //\n // For shared-slot subscriptions, use the EARLIEST `lastDeliveredId` across\n // subscribers so none miss events they had not yet observed.\n const work: Promise<void>[] = [];\n for (const set of this.streamSubscriptions.values()) {\n const states = Array.from(set);\n if (states.length === 0) continue;\n const primary = this.pickEarliestCursorState(states);\n work.push(this.resubscribeOne(primary, set));\n }\n try {\n await Promise.all(work);\n } catch {\n // Defensive — each resubscribeOne has its own try/catch,\n // but add a belt-and-suspenders here for the void call site.\n }\n }\n\n private async resubscribeOne(\n primary: StreamSubscriptionState,\n set: Set<StreamSubscriptionState>,\n ): Promise<void> {\n try {\n const ack = await this.emitSubscribeEvents(primary);\n if (ack.error) {\n const err = new Error(ack.message ?? ack.error);\n for (const state of [...set]) {\n state.options.onError?.(err);\n }\n if (PERMANENT_STREAM_ACK_ERRORS.has(ack.error)) {\n // Permanent rejection — don't retry on the next reconnect.\n this.streamSubscriptions.delete(primary.streamKey);\n }\n return;\n }\n\n // Happy path — ack succeeded. If the local set was fully torn down during\n // the await (last unsubscribe raced with the reconnect), release the freshly\n // acquired server-side reader slot so we don't leak it.\n if (!this.streamSubscriptions.has(primary.streamKey)) {\n this.socket?.emit('unsubscribe-events', {\n projectId: primary.options.projectId,\n collectionName: primary.options.collectionName,\n });\n }\n } catch (err) {\n const error = err instanceof Error ? err : new Error(String(err));\n for (const state of [...set]) {\n state.options.onError?.(error);\n }\n // Thrown path is exceptional (emitSubscribeEvents always resolves), treat\n // as transient and let the next reconnect retry.\n }\n }\n\n private pickEarliestCursorState(\n states: StreamSubscriptionState[],\n ): StreamSubscriptionState {\n // Stream entry IDs look like \"<ms>-<seq>\"; compare numerically so lexical\n // ordering doesn't produce wrong results once the ms digit count changes.\n const parse = (id: string | null): [number, number] => {\n // null = \"no cursor preference\" — never overrides a real cursor.\n if (!id) return [Number.MAX_SAFE_INTEGER, Number.MAX_SAFE_INTEGER];\n const [ms, seq = '0'] = id.split('-');\n return [Number(ms), Number(seq)];\n };\n let earliest = states[0];\n let [eMs, eSeq] = parse(earliest.lastDeliveredId);\n for (let i = 1; i < states.length; i++) {\n const [ms, seq] = parse(states[i].lastDeliveredId);\n if (ms < eMs || (ms === eMs && seq < eSeq)) {\n earliest = states[i];\n eMs = ms;\n eSeq = seq;\n }\n }\n return earliest;\n }\n\n /**\n * Evict the primary subscriber AND every sibling that joined the same\n * streamKey via the dedup path. Fires `onError` on all siblings, then\n * removes the entire streamKey entry from `streamSubscriptions`.\n */\n private evictStreamKey(\n streamKey: string,\n err: Error,\n primary: StreamSubscriptionState,\n ): void {\n const set = this.streamSubscriptions.get(streamKey);\n if (!set) return;\n for (const sibling of [...set]) {\n if (sibling !== primary) {\n sibling.options.onError?.(err);\n }\n set.delete(sibling);\n }\n this.streamSubscriptions.delete(streamKey);\n }\n}\n","/**\n * Persist the stream-mode resume cursor across app restarts and reconnects.\n *\n * Pass an implementation to `subscribeEvents({ cursorStorage })` and the SDK\n * will load the previous cursor before subscribing and save after each event\n * is handled. All methods may be sync or async — the SDK awaits the returned\n * value so React Native's AsyncStorage and other async backends work without\n * extra adapter code.\n */\nexport interface CursorStorage {\n /**\n * Return the saved cursor for this key, or `null` if none.\n *\n * An empty string is also treated as \"no cursor\" — Redis stream entry ids\n * are never empty, so an empty value is always the result of a misconfigured\n * adapter or a zero-length write. Returning `null` is preferred for clarity.\n */\n load(key: string): Promise<string | null> | string | null;\n /** Persist the cursor for this key. Called after each delivered event. */\n save(key: string, cursor: string): Promise<void> | void;\n}\n\n/**\n * In-memory cursor store. Useful for tests and short-lived scripts; state is\n * lost on process exit. Pass this to `subscribeEvents({ cursorStorage })`\n * when you want non-persistent in-memory resume behaviour within a single\n * session — omitting `cursorStorage` entirely disables persistence instead.\n */\nexport function memoryCursorStorage(): CursorStorage {\n const store = new Map<string, string>();\n return {\n load(key) {\n return store.get(key) ?? null;\n },\n save(key, cursor) {\n store.set(key, cursor);\n },\n };\n}\n\n/**\n * Browser `localStorage`-backed cursor store. Gracefully no-ops in\n * non-browser environments (Node without a `localStorage` polyfill), so\n * isomorphic code can use this factory unconditionally.\n *\n * @param prefix Key prefix applied to every stored entry. Defaults to\n * `spacelr:cursor:` so multiple SDK consumers on the same origin don't\n * collide.\n */\nexport function localStorageCursorStorage(\n prefix = 'spacelr:cursor:',\n): CursorStorage {\n // Sandboxed cross-origin iframes and browsers with storage disabled throw\n // `SecurityError` on the property *access* itself — not just on method\n // calls — so the guard has to wrap the read, not just typeof-check.\n const storage = ((): Storage | null => {\n try {\n const candidate = (globalThis as { localStorage?: Storage }).localStorage;\n return typeof candidate === 'undefined' ? null : candidate;\n } catch {\n return null;\n }\n })();\n\n return {\n load(key) {\n if (!storage) return null;\n try {\n return storage.getItem(prefix + key);\n } catch {\n return null;\n }\n },\n save(key, cursor) {\n if (!storage) return;\n try {\n storage.setItem(prefix + key, cursor);\n } catch {\n // Quota exceeded or storage disabled — silently drop; cursor\n // persistence is a best-effort resume hint, not a correctness\n // guarantee.\n }\n },\n };\n}\n","import { HttpClient, TokenManager, generatePKCEChallenge } from '../core';\nimport {\n SpacelrClientConfig,\n GrantType,\n LoginParams,\n LoginResponse,\n RegisterParams,\n RegisterResponse,\n TokenResponse,\n UserInfo,\n UserProfile,\n AuthorizationUrlParams,\n ExchangeCodeParams,\n PKCEChallenge,\n OpenIDConfiguration,\n JWKSResponse,\n TwoFactorVerifyParams,\n} from '../types';\n\nexport type AuthState = 'authenticated' | 'unauthenticated';\nexport type AuthStateListener = (state: AuthState) => void | Promise<void>;\n\nexport class AuthModule {\n private http: HttpClient;\n private tokenManager: TokenManager;\n private config: SpacelrClientConfig;\n private stateListeners = new Set<AuthStateListener>();\n private unsubscribeAuthLost!: () => void;\n private lastEmittedState: AuthState | null = null;\n\n constructor(\n http: HttpClient,\n tokenManager: TokenManager,\n config: SpacelrClientConfig\n ) {\n this.http = http;\n this.tokenManager = tokenManager;\n this.config = config;\n\n // Wire up refresh callback to avoid circular deps\n this.tokenManager.setRefreshCallback(async (refreshToken: string) => {\n const result = await this.refresh(refreshToken);\n const expiresAt = result.expires_in\n ? Math.floor(Date.now() / 1000) + result.expires_in\n : undefined;\n return {\n accessToken: result.access_token,\n refreshToken: result.refresh_token,\n expiresAt,\n };\n });\n\n // A failed refresh (triggered by 401 / refresh endpoint error) drops the\n // session — mirror that into our auth-state subscribers so UIs can react.\n // Keep the unsubscribe so `dispose()` can detach if this AuthModule is\n // ever discarded while the TokenManager lives on (shared client, HMR).\n this.unsubscribeAuthLost = this.tokenManager.onAuthLost(() =>\n this.emitState('unauthenticated'),\n );\n }\n\n /**\n * Returns true if a non-expired access token is currently in storage.\n * Does NOT make a network request — safe for route guards and other\n * hot paths that run on every navigation.\n *\n * A token within the refresh buffer (about to expire) still counts as\n * authenticated because the next protected request will auto-refresh it.\n *\n * Any error from the underlying TokenStorage (corrupt JSON, quota, etc.)\n * is treated as \"not authenticated\" rather than propagated, so route\n * guards can't be crashed by a misbehaving storage backend.\n */\n async isAuthenticated(): Promise<boolean> {\n try {\n const tokens = await this.tokenManager.getStoredTokens();\n if (!tokens?.accessToken) return false;\n if (tokens.expiresAt && tokens.expiresAt * 1000 <= Date.now()) return false;\n return true;\n } catch {\n return false;\n }\n }\n\n /**\n * Subscribe to auth-state transitions. The callback fires:\n * - 'authenticated' after a successful login/register/exchange/2FA-verify\n * - 'unauthenticated' after logout or when a token refresh fails\n *\n * Only fires for transitions that happen after the subscription. If the\n * user is already logged in at subscribe time (e.g. tokens restored from\n * storage on app boot), no 'authenticated' event is emitted — call\n * `isAuthenticated()` once up-front for the initial state.\n *\n * Silent token refreshes do NOT produce an event (auth state is\n * unchanged). Subscribe to `spacelrClient.onTokenRefreshed(...)` if you\n * need to observe successful refreshes.\n *\n * Listener may return `void` or `Promise<void>`. Rejections are swallowed\n * so one broken subscriber can't poison others or the auth flow. The\n * dispatch is fire-and-forget: `logout()` / `login()` resolve as soon as\n * the dispatch loop returns, without awaiting async listeners.\n *\n * Returns an unsubscribe function.\n */\n onAuthStateChange(listener: AuthStateListener): () => void {\n this.stateListeners.add(listener);\n return () => {\n this.stateListeners.delete(listener);\n };\n }\n\n /**\n * Detach this AuthModule from the TokenManager. Call when discarding the\n * client (tests, HMR, multi-client setups) to avoid leaking the internal\n * onAuthLost subscription. Idempotent — safe to call more than once.\n */\n dispose(): void {\n this.unsubscribeAuthLost();\n this.unsubscribeAuthLost = () => {\n // Second and later dispose() calls no-op.\n };\n this.stateListeners.clear();\n // Defensive reset: if this instance is ever reused (HMR, test reuse),\n // the dedup guard shouldn't swallow the first post-dispose emission\n // just because it happens to match the state we last emitted.\n this.lastEmittedState = null;\n }\n\n private emitState(state: AuthState): void {\n // Dedupe consecutive identical emits: a failed logout call can both\n // trigger onAuthLost (via 401 in the request) and then fall through to\n // our explicit emit — without dedup, subscribers would see two\n // unauthenticated events for a single user action.\n if (state === this.lastEmittedState) return;\n this.lastEmittedState = state;\n\n for (const listener of this.stateListeners) {\n try {\n const result = listener(state);\n // Duck-type via `.then` (spec definition of a thenable) so any\n // Promise-like return, including non-native thenables from JS\n // callers that bypass the TS types, is handled uniformly.\n if (result && typeof (result as Promise<void>).then === 'function') {\n (result as Promise<void>).then(undefined, () => {\n // Async listener rejections must not break siblings or callers.\n });\n }\n } catch {\n // Synchronous listener throws must not break siblings or callers.\n }\n }\n }\n\n async login(params: LoginParams): Promise<LoginResponse> {\n const response = await this.http.request<LoginResponse>({\n method: 'POST',\n path: '/auth/login',\n body: params,\n });\n\n await this.storeTokensFromLogin(response);\n return response;\n }\n\n async register(params: RegisterParams): Promise<RegisterResponse> {\n const response = await this.http.request<RegisterResponse>({\n method: 'POST',\n path: '/auth/register',\n body: params,\n });\n\n // Only store tokens if they were returned (not when email verification is required)\n if (response.access_token) {\n await this.storeTokensFromRegister(response);\n }\n return response;\n }\n\n async refresh(refreshToken: string): Promise<TokenResponse> {\n return this.http.request<TokenResponse>({\n method: 'POST',\n path: '/auth/refresh',\n body: { refreshToken },\n });\n }\n\n async getProfile(): Promise<UserProfile> {\n return this.http.request<UserProfile>({\n method: 'GET',\n path: '/auth/me',\n authenticated: true,\n });\n }\n\n async logout(): Promise<void> {\n try {\n await this.http.request<void>({\n method: 'POST',\n path: '/auth/logout',\n authenticated: true,\n });\n } catch {\n // Server-side revocation is best-effort. If the token is already invalid\n // the server has effectively revoked the session anyway — always clear\n // the local state so the UI doesn't get stuck.\n }\n await this.tokenManager.clearTokens();\n this.emitState('unauthenticated');\n }\n\n async verifyEmail(token: string): Promise<{ message: string }> {\n return this.http.request<{ message: string }>({\n method: 'GET',\n path: '/auth/verify-email',\n query: { token },\n });\n }\n\n async resendVerification(email: string): Promise<{ message: string }> {\n return this.http.request<{ message: string }>({\n method: 'POST',\n path: '/auth/resend-verification',\n body: { email },\n });\n }\n\n async getUserInfo(): Promise<UserInfo> {\n return this.http.request<UserInfo>({\n method: 'GET',\n path: this.config.userInfoEndpoint ?? '/auth/userinfo',\n authenticated: true,\n });\n }\n\n getAuthorizationUrl(params: AuthorizationUrlParams): string {\n const baseUrl = this.config.apiUrl.replace(/\\/+$/, '');\n const endpoint =\n this.config.authorizationEndpoint ?? '/auth/authorize';\n const url = new URL(`${baseUrl}${endpoint}`);\n\n url.searchParams.set('client_id', this.config.clientId);\n url.searchParams.set('redirect_uri', params.redirectUri);\n url.searchParams.set('response_type', params.responseType ?? 'code');\n\n const scope =\n params.scope ?? this.config.scopes?.join(' ') ?? 'openid';\n url.searchParams.set('scope', scope);\n\n if (params.state) {\n url.searchParams.set('state', params.state);\n }\n if (params.codeChallenge) {\n url.searchParams.set('code_challenge', params.codeChallenge);\n }\n if (params.codeChallengeMethod) {\n url.searchParams.set(\n 'code_challenge_method',\n params.codeChallengeMethod\n );\n }\n\n return url.toString();\n }\n\n async exchangeCode(params: ExchangeCodeParams): Promise<TokenResponse> {\n const body: Record<string, string> = {\n grant_type: params.grantType ?? GrantType.AUTHORIZATION_CODE,\n code: params.code,\n redirect_uri: params.redirectUri,\n client_id: this.config.clientId,\n };\n\n if (params.clientSecret) {\n body['client_secret'] = params.clientSecret;\n }\n if (params.codeVerifier) {\n body['code_verifier'] = params.codeVerifier;\n }\n\n const tokenEndpoint =\n this.config.tokenEndpoint ?? '/auth/token';\n\n const response = await this.http.request<TokenResponse>({\n method: 'POST',\n path: tokenEndpoint,\n body,\n });\n\n const expiresAt = response.expires_in\n ? Math.floor(Date.now() / 1000) + response.expires_in\n : undefined;\n\n await this.tokenManager.setTokens({\n accessToken: response.access_token,\n refreshToken: response.refresh_token,\n expiresAt,\n });\n this.emitState('authenticated');\n\n return response;\n }\n\n async generatePKCE(): Promise<PKCEChallenge> {\n return generatePKCEChallenge();\n }\n\n async getOpenIDConfiguration(): Promise<OpenIDConfiguration> {\n return this.http.request<OpenIDConfiguration>({\n method: 'GET',\n path: '/.well-known/openid-configuration',\n });\n }\n\n async getJWKS(): Promise<JWKSResponse> {\n return this.http.request<JWKSResponse>({\n method: 'GET',\n path: '/.well-known/jwks.json',\n });\n }\n\n /**\n * Request a password reset email.\n * Always returns a generic message regardless of whether the email exists.\n */\n async requestPasswordReset(email: string): Promise<{ message: string }> {\n return this.http.request<{ message: string }>({\n method: 'POST',\n path: '/auth/request-password-reset',\n body: { email },\n });\n }\n\n /**\n * Reset password using a token received via email.\n */\n async resetPassword(\n token: string,\n password: string,\n ): Promise<{ message: string }> {\n return this.http.request<{ message: string }>({\n method: 'POST',\n path: '/auth/reset-password',\n body: { token, password },\n });\n }\n\n /**\n * Exchange a one-time verification code for tokens.\n * Use this after email verification redirects the user with a ?loginCode= parameter.\n */\n async exchangeVerificationCode(code: string): Promise<LoginResponse> {\n const response = await this.http.request<LoginResponse>({\n method: 'POST',\n path: '/auth/exchange-code',\n body: { code },\n });\n\n await this.storeTokensFromLogin(response);\n return response;\n }\n\n /**\n * Resend a two-factor authentication code email.\n * Call this when the user hasn't received the code or it expired.\n */\n async resendTwoFactorCode(token: string): Promise<{ message: string }> {\n return this.http.request<{ message: string }>({\n method: 'POST',\n path: '/auth/resend-two-factor-code',\n body: { token },\n });\n }\n\n /**\n * Verify a two-factor authentication code.\n * Call this after catching SpacelrTwoFactorRequiredError from login().\n */\n async verifyTwoFactor(params: TwoFactorVerifyParams): Promise<LoginResponse> {\n const response = await this.http.request<LoginResponse>({\n method: 'POST',\n path: '/auth/verify-two-factor',\n body: { token: params.token, code: params.code },\n });\n\n await this.storeTokensFromLogin(response);\n return response;\n }\n\n private async storeTokensFromLogin(response: LoginResponse): Promise<void> {\n const expiresAt = response.expires_in\n ? Math.floor(Date.now() / 1000) + response.expires_in\n : undefined;\n\n await this.tokenManager.setTokens({\n accessToken: response.access_token,\n refreshToken: response.refresh_token,\n expiresAt,\n });\n this.emitState('authenticated');\n }\n\n private async storeTokensFromRegister(\n response: RegisterResponse\n ): Promise<void> {\n // Guard mirrors the optional `access_token?: string` in RegisterResponse:\n // the server omits tokens when email verification is required. The\n // caller already pre-checks, but keeping the guard here means the helper\n // stays correct against its declared input type rather than relying on\n // the single current call site.\n if (!response.access_token) return;\n await this.tokenManager.setTokens({\n accessToken: response.access_token,\n refreshToken: response.refresh_token,\n });\n this.emitState('authenticated');\n }\n}\n","import { HttpClient, TokenManager } from '../core';\nimport {\n SpacelrClientConfig,\n FileVisibility,\n FileInfo,\n FileListResponse,\n ListFilesParams,\n InitMultipartUploadParams,\n InitMultipartUploadResponse,\n PartEtag,\n ShareFileParams,\n UnshareFileParams,\n QuotaInfo,\n UploadFileParams,\n UploadLargeFileParams,\n UploadProgress,\n DownloadUrlResponse,\n} from '../types';\n\nexport class StorageModule {\n private http: HttpClient;\n private tokenManager: TokenManager;\n private config: SpacelrClientConfig;\n\n constructor(\n http: HttpClient,\n tokenManager: TokenManager,\n config: SpacelrClientConfig\n ) {\n this.http = http;\n this.tokenManager = tokenManager;\n this.config = config;\n }\n\n /**\n * Upload a file through the gateway (no direct S3 access).\n * Accepts a Blob/File (browser) or ArrayBuffer/Uint8Array (Node).\n */\n async uploadFile(\n file: Blob | ArrayBuffer | Uint8Array,\n params: UploadFileParams,\n onProgress?: (progress: UploadProgress) => void,\n ): Promise<FileInfo> {\n const formData = new FormData();\n const blob =\n file instanceof Blob\n ? file\n : new Blob([file as BlobPart], { type: params.mimeType });\n formData.append('file', blob, params.filename);\n if (params.visibility) formData.append('visibility', params.visibility);\n if (params.description) formData.append('description', params.description);\n\n const progressHandler = onProgress\n ? (e: { loaded: number; total: number }) => {\n onProgress({\n loaded: e.loaded,\n total: e.total,\n percentage: e.total > 0 ? Math.round((e.loaded / e.total) * 100) : 0,\n });\n }\n : undefined;\n\n return this.http.uploadForm<FileInfo>('/storage/files', formData, progressHandler);\n }\n\n /**\n * Upload a large file using multipart upload through the gateway.\n * Splits into parts, uploads concurrently, and completes.\n */\n async uploadLargeFile(\n file: Blob | ArrayBuffer | Uint8Array,\n params: UploadLargeFileParams,\n onProgress?: (progress: UploadProgress) => void\n ): Promise<FileInfo> {\n const fileSize = file instanceof Blob ? file.size : file.byteLength;\n const concurrency = params.concurrency ?? 3;\n\n const init = await this.initMultipartUpload({\n filename: params.filename,\n mimeType: params.mimeType,\n totalSizeBytes: fileSize,\n visibility: params.visibility,\n description: params.description,\n });\n\n const { partSize, totalParts, fileId } = init;\n const completedParts: PartEtag[] = [];\n let completedBytes = 0;\n const partProgressMap = new Map<number, number>();\n\n try {\n const allPartNumbers = Array.from(\n { length: totalParts },\n (_, i) => i + 1\n );\n\n for (let i = 0; i < allPartNumbers.length; i += concurrency) {\n const batch = allPartNumbers.slice(i, i + concurrency);\n\n const uploads = batch.map(async (partNumber) => {\n const start = (partNumber - 1) * partSize;\n const end = Math.min(start + partSize, fileSize);\n const chunkSize = end - start;\n const chunk =\n file instanceof Blob\n ? file.slice(start, end)\n : new Blob([file.slice(start, end)]);\n\n const formData = new FormData();\n formData.append('file', chunk, `part-${partNumber}`);\n formData.append('partNumber', String(partNumber));\n\n const partProgress = onProgress && typeof XMLHttpRequest !== 'undefined'\n ? (e: { loaded: number; total: number }) => {\n const ratio = e.total > 0 ? e.loaded / e.total : 0;\n partProgressMap.set(partNumber, Math.min(ratio * chunkSize, chunkSize));\n const inFlightBytes = Array.from(partProgressMap.values())\n .reduce((sum, v) => sum + v, 0);\n const totalLoaded = Math.min(completedBytes + inFlightBytes, fileSize);\n onProgress({\n loaded: totalLoaded,\n total: fileSize,\n percentage: fileSize > 0\n ? Math.min(100, Math.round((totalLoaded / fileSize) * 100))\n : 0,\n });\n }\n : undefined;\n\n const result = await this.http.uploadForm<{ etag: string }>(\n `/storage/files/${fileId}/multipart/upload-part`,\n formData,\n partProgress,\n );\n\n partProgressMap.delete(partNumber);\n completedBytes += chunkSize;\n\n if (onProgress) {\n onProgress({\n loaded: Math.min(completedBytes, fileSize),\n total: fileSize,\n percentage: fileSize > 0\n ? Math.min(100, Math.round((completedBytes / fileSize) * 100))\n : 0,\n });\n }\n\n completedParts.push({\n partNumber,\n etag: result.etag,\n });\n });\n\n await Promise.all(uploads);\n partProgressMap.clear();\n }\n\n // Sort parts by number before completing\n completedParts.sort((a, b) => a.partNumber - b.partNumber);\n\n await this.completeMultipartUpload(fileId, completedParts);\n return this.getFileInfo(fileId);\n } catch (error) {\n // Clean up the incomplete multipart upload on failure\n try {\n await this.abortMultipartUpload(fileId);\n } catch {\n // Abort is best-effort; ignore cleanup failures\n }\n throw error;\n }\n }\n\n async initMultipartUpload(\n params: InitMultipartUploadParams\n ): Promise<InitMultipartUploadResponse> {\n return this.http.request<InitMultipartUploadResponse>({\n method: 'POST',\n path: '/storage/files/multipart/init',\n body: params,\n authenticated: true,\n });\n }\n\n async completeMultipartUpload(\n fileId: string,\n parts: PartEtag[]\n ): Promise<FileInfo> {\n return this.http.request<FileInfo>({\n method: 'POST',\n path: `/storage/files/${fileId}/multipart/complete`,\n body: { parts },\n authenticated: true,\n });\n }\n\n async abortMultipartUpload(fileId: string): Promise<void> {\n await this.http.request<void>({\n method: 'POST',\n path: `/storage/files/${fileId}/multipart/abort`,\n authenticated: true,\n });\n }\n\n async listFiles(params?: ListFilesParams): Promise<FileListResponse> {\n return this.http.request<FileListResponse>({\n method: 'GET',\n path: '/storage/files',\n query: params as Record<string, string | number | undefined>,\n authenticated: true,\n });\n }\n\n async listSharedFiles(params?: ListFilesParams): Promise<FileListResponse> {\n return this.http.request<FileListResponse>({\n method: 'GET',\n path: '/storage/shared',\n query: params as Record<string, string | number | undefined>,\n authenticated: true,\n });\n }\n\n async getFileInfo(fileId: string): Promise<FileInfo> {\n return this.http.request<FileInfo>({\n method: 'GET',\n path: `/storage/files/${fileId}`,\n authenticated: true,\n });\n }\n\n async downloadFile(fileId: string): Promise<Blob> {\n const baseUrl = this.config.apiUrl.replace(/\\/+$/, '');\n const url = `${baseUrl}/storage/files/${fileId}/download`;\n\n const headers: Record<string, string> = {\n 'x-client-id': this.config.clientId,\n 'x-project-id': this.config.projectId,\n };\n\n const token = await this.tokenManager.getAccessToken();\n if (token) {\n headers['Authorization'] = `Bearer ${token}`;\n }\n\n const response = await fetch(url, { headers });\n\n if (!response.ok) {\n throw new Error(`Download failed: ${response.status}`);\n }\n\n return response.blob();\n }\n\n async getDownloadUrl(fileId: string): Promise<DownloadUrlResponse> {\n return this.http.request<DownloadUrlResponse>({\n method: 'GET',\n path: `/storage/files/${fileId}/download-url`,\n authenticated: true,\n });\n }\n\n async deleteFile(fileId: string): Promise<void> {\n await this.http.request<void>({\n method: 'DELETE',\n path: `/storage/files/${fileId}`,\n authenticated: true,\n });\n }\n\n async shareFile(\n fileId: string,\n params: ShareFileParams\n ): Promise<void> {\n await this.http.request<void>({\n method: 'POST',\n path: `/storage/files/${fileId}/share`,\n body: params,\n authenticated: true,\n });\n }\n\n async unshareFile(\n fileId: string,\n params: UnshareFileParams\n ): Promise<void> {\n await this.http.request<void>({\n method: 'POST',\n path: `/storage/files/${fileId}/unshare`,\n body: params,\n authenticated: true,\n });\n }\n\n async updateVisibility(\n fileId: string,\n visibility: FileVisibility\n ): Promise<FileInfo> {\n return this.http.request<FileInfo>({\n method: 'PATCH',\n path: `/storage/files/${fileId}/visibility`,\n body: { visibility },\n authenticated: true,\n });\n }\n\n async getQuota(): Promise<QuotaInfo> {\n return this.http.request<QuotaInfo>({\n method: 'GET',\n path: '/storage/quota',\n authenticated: true,\n });\n }\n\n async getPublicFileUrl(fileId: string, projectId?: string): Promise<string> {\n const baseUrl = this.config.apiUrl.replace(/\\/+$/, '');\n const resolvedProjectId = projectId ?? this.config.projectId;\n return `${baseUrl}/public/files/${resolvedProjectId}/${fileId}`;\n }\n}\n","import { HttpClient } from '../core';\nimport { RealtimeClient, DatabaseChangeEvent } from '../core';\nimport type { GapReason, StreamGapInfo } from '../core/realtime';\nimport type { CursorStorage } from '../core/cursor-storage';\n\n// Re-export for downstream consumers so they don't need to reach into core.\nexport type { GapReason, StreamGapInfo, CursorStorage };\n\nexport interface PopulateOption {\n field: string;\n collection: string;\n foreignField?: string;\n}\n\nexport interface DocumentResult {\n _id: string;\n [key: string]: unknown;\n}\n\nexport interface OffsetResult<T = Record<string, unknown>> {\n mode: 'offset';\n documents: (T & { _id: string })[];\n total: number;\n limit: number;\n offset: number;\n}\n\nexport interface CursorResult<T = Record<string, unknown>> {\n mode: 'cursor';\n documents: (T & { _id: string })[];\n limit: number;\n /**\n * `_id` of the last document in `documents`, or `null` if `documents` is empty.\n * Independent of `hasMore` — the cursor reflects the page contents, not\n * whether more pages exist.\n */\n nextCursor: string | null;\n /**\n * True when more documents exist past the returned page. Computed via the\n * `limit + 1` server-side fetch trick.\n */\n hasMore: boolean;\n}\n\nexport type FindResult<T = Record<string, unknown>> = OffsetResult<T> | CursorResult<T>;\n\n// SearchResult retains the pre-discriminator shape; search() does not yet\n// support cursor pagination.\nexport interface SearchResult<T = Record<string, unknown>> {\n documents: (T & { _id: string })[];\n total: number;\n limit: number;\n offset: number;\n}\n\nexport interface InsertResult {\n insertedCount: number;\n insertedIds: string[];\n}\n\nexport interface UpdateResult {\n matchedCount: number;\n modifiedCount: number;\n}\n\nexport interface DeleteResult {\n deletedCount: number;\n}\n\nexport interface CountResult {\n count: number;\n}\n\nexport interface FindByIdOptions {\n populate?: PopulateOption[];\n}\n\n/**\n * Options for a server-side substring search.\n *\n * Limits: `query` 1–200 chars, `fields` 1–10 entries (each matching\n * `/^[a-zA-Z0-9_.]+$/`, max 64 chars), `limit` max 100.\n */\nexport interface SearchOptions {\n query: string;\n fields: string[];\n filter?: Record<string, unknown>;\n sort?: Record<string, 1 | -1>;\n limit?: number;\n offset?: number;\n select?: string[];\n}\n\nexport interface SubscribeEventsHandlers<T = Record<string, unknown>> {\n /** Cursor to resume from. Undefined = fresh subscription, deliver only new events. */\n sinceId?: string;\n where?: Record<string, string | number | boolean>;\n /**\n * Optional resume-cursor store. When set, the SDK loads the previous cursor\n * before subscribing and persists the new cursor after each delivered\n * event. An explicit `sinceId` takes precedence over a loaded cursor —\n * `load()` is skipped entirely and saves resume from `sinceId` forward,\n * overwriting any previously persisted cursor.\n *\n * Pair `where`-filtered subscriptions with a unique `cursorKey`; subscribers\n * with different filters must not share a cursor since the stream position\n * represents \"events seen\" — filtered-out entries still advance it.\n */\n cursorStorage?: CursorStorage;\n /**\n * Override the default cursor key. Defaults to `{projectId}:{collectionName}`.\n * Useful when multiple subscriptions on the same collection (e.g. different\n * `where` filters or per-user feeds) must persist independent cursors.\n */\n cursorKey?: string;\n /** Called on inserts. Receives the document merged with `_eventId`. */\n onInsert?: (doc: T & { _id: string; _eventId: string }) => void | Promise<void>;\n onUpdate?: (doc: T & { _id: string; _eventId: string }) => void | Promise<void>;\n /** Called on deletes. `document` may be absent on tombstones — only documentId + eventId are guaranteed. */\n onDelete?: (documentId: string, eventId: string) => void | Promise<void>;\n /** Called when the server emits an event-gap. See GapReason for the four codes. */\n onGap?: (info: StreamGapInfo) => void;\n /** Called on handshake rejection (not-stream-collection, invalid sinceId, etc.) and handler failures. */\n onError?: (error: Error) => void;\n}\n\nexport interface StreamSubscription {\n /**\n * Stop receiving events and release the server-side reader slot. Idempotent.\n *\n * When `cursorStorage` is set, an in-flight async `save()` may still deliver\n * `onError` asynchronously after `unsubscribe()` returns — the fire-and-forget\n * save chain is not cancelled. Callers whose `onError` is tied to a component\n * lifecycle should guard for post-unmount delivery.\n */\n unsubscribe(): void;\n /**\n * Most recently processed eventId — advances after the subscription's\n * onEvent pipeline completes for each event, whether or not a user\n * handler was registered for that event type. `undefined` before any\n * event arrives.\n *\n * Persist this across app restarts and pass it back as `sinceId` on\n * the next `subscribeEvents()` call to catch up on missed events.\n */\n getCursor(): string | undefined;\n}\n\nexport interface SubscribeHandlers<T = Record<string, unknown>> {\n where?: Record<string, string | number | boolean>;\n onInsert?: (doc: T & { _id: string }) => void;\n onUpdate?: (doc: T & { _id: string }) => void;\n onDelete?: (documentId: string) => void;\n onError?: (error: Error) => void;\n}\n\nexport interface PaginateOptions {\n where?: Record<string, unknown>;\n /** Sort direction by `_id`. Defaults to -1 (newest first — chat scroll-back). */\n sort?: { _id: 1 | -1 };\n /** Page size. Default 50. Server typically caps at 100. */\n limit?: number;\n /**\n * Optional initial cursor — paginate from this `_id` exclusively. Useful\n * when the caller already holds a boundary document (e.g. the oldest\n * message currently rendered in a chat view paired with `subscribeEvents`).\n */\n cursor?: string;\n}\n\nexport interface PaginatorPage<T = Record<string, unknown>> {\n documents: (T & { _id: string })[];\n /**\n * True when more documents exist past this page. The paginator also flips\n * `exhausted` to true when this returns false, so callers can poll either\n * `page.hasMore` after each call or `paginator.exhausted` between calls.\n */\n hasMore: boolean;\n}\n\n/**\n * Cursor-based scroll helper around `find()`. Tracks the last-seen `_id`\n * across calls and applies it as `.before()` (descending sort) or\n * `.after()` (ascending sort) on the next page so the keyset stays stable\n * under concurrent inserts. Use `subscribeEvents` for new docs and\n * `paginate` for older — the two compose into a chat-style timeline.\n */\nexport class Paginator<T = Record<string, unknown>> {\n private cursor?: string;\n private _exhausted = false;\n private readonly direction: 1 | -1;\n private readonly pageSize: number;\n private readonly where?: Record<string, unknown>;\n /**\n * Tail of the serialized `next()` chain. Each call appends to this so\n * concurrent invocations from a UI scroll handler (e.g. user spam-tapping\n * \"load more\") don't issue parallel requests with the same cursor — which\n * would return duplicate pages and clobber the cursor based on whichever\n * response settles last. Calls execute strictly in invocation order.\n */\n private chain: Promise<PaginatorPage<T>> = Promise.resolve({\n documents: [],\n hasMore: false,\n });\n\n constructor(\n private readonly http: HttpClient,\n private readonly basePath: string,\n opts: PaginateOptions,\n ) {\n this.cursor = opts.cursor;\n this.direction = opts.sort?._id ?? -1;\n this.pageSize = opts.limit ?? 50;\n this.where = opts.where;\n }\n\n /**\n * True once a `next()` call has returned an empty page. Subsequent\n * `next()` calls return an empty page without hitting the server.\n *\n * Note: only the empty-page signal flips this flag; a server response\n * that returns documents but `hasMore: false` does NOT exhaust. This\n * lets ascending paginators (`sort: { _id: 1 }`) keep polling for\n * documents inserted after the caller caught up to the current tail —\n * the next `.after(lastSeen)` call will simply return zero documents\n * until something new lands.\n */\n get exhausted(): boolean {\n return this._exhausted;\n }\n\n next(): Promise<PaginatorPage<T>> {\n // Serialize: each call resolves only after the previous one settles.\n // We swallow rejections from the predecessor so a single failed page\n // doesn't poison the whole chain — the caller of the failed call\n // already saw the rejection.\n const run = this.chain.then(\n () => this.fetchNextPage(),\n () => this.fetchNextPage(),\n );\n this.chain = run;\n return run;\n }\n\n private async fetchNextPage(): Promise<PaginatorPage<T>> {\n if (this._exhausted) return { documents: [], hasMore: false };\n\n const builder = new QueryBuilder<T>(this.http, this.basePath, this.where)\n .sort({ _id: this.direction })\n .limit(this.pageSize);\n\n let result: FindResult<T>;\n if (this.cursor !== undefined) {\n // Cursor mode: keyset pagination, immune to concurrent inserts.\n const cursorBuilder =\n this.direction === -1\n ? builder.before(this.cursor)\n : builder.after(this.cursor);\n result = await cursorBuilder.execute();\n } else {\n // First page without an initial cursor — offset mode on the server.\n // Sort + limit are honoured the same way; we capture the last `_id`\n // here so all subsequent pages can switch to cursor mode.\n result = await builder.execute();\n }\n\n if (result.documents.length === 0) {\n // Empty page is the only signal that exhausts the paginator. A\n // populated page with `hasMore: false` leaves us re-callable so\n // ascending mode can pick up future inserts past `lastSeen`.\n this._exhausted = true;\n return { documents: [], hasMore: false };\n }\n\n this.cursor = result.documents[result.documents.length - 1]._id;\n\n const hasMore =\n result.mode === 'cursor'\n ? result.hasMore\n : result.documents.length < result.total;\n return { documents: result.documents, hasMore };\n }\n}\n\nexport class QueryBuilder<\n T = Record<string, unknown>,\n M extends 'offset' | 'cursor' = 'offset',\n> {\n private _filter?: Record<string, unknown>;\n private _sort?: Record<string, 1 | -1>;\n private _limit?: number;\n private _offset?: number;\n private _before?: string;\n private _after?: string;\n private _fields?: string[];\n private _populate: PopulateOption[] = [];\n\n constructor(\n private http: HttpClient,\n private basePath: string,\n filter?: Record<string, unknown>,\n ) {\n this._filter = filter;\n }\n\n sort(sort: Record<string, 1 | -1>): this {\n this._sort = sort;\n return this;\n }\n\n limit(limit: number): this {\n this._limit = limit;\n return this;\n }\n\n offset(offset: number): this {\n this._offset = offset;\n return this;\n }\n\n /**\n * **Constraint:** the cursor value must be a 24-hex ObjectId string.\n * Collections using custom non-ObjectId `_id` strings will not work\n * correctly with cursor pagination — the server's `$lt`/`$gt` comparison\n * uses BSON type ordering, mixing string `_id`s with ObjectId comparison\n * produces undefined behaviour. Documented limitation; future work may\n * add opaque cursor tokens that abstract over `_id` types.\n *\n * Switch to cursor-pagination mode: return documents with `_id < id`\n * (in the sort-defined order). The cursor refers to the cursor *value*,\n * not visual UI direction. Requires `.sort()` to be `{ _id: 1 }` or\n * `{ _id: -1 }` (or omitted — server defaults to `{ _id: 1 }`).\n *\n * Narrows the builder's mode parameter so subsequent `.execute()` returns\n * a `CursorResult<T>` instead of `OffsetResult<T>`.\n *\n * **For paginating further:** the `nextCursor` field returned by\n * `execute()` is the `_id` of the last document on the page. To load the\n * next older page, pass it again to `.before()`. (Do NOT pass it to\n * `.after()` — that would request docs newer than this page.)\n *\n * **Cannot be combined with `.offset()`.** Type system allows the chain\n * for ergonomics, but the server rejects it with HTTP 400.\n */\n before(id: string): QueryBuilder<T, 'cursor'> {\n this._before = id;\n return this as QueryBuilder<T, 'cursor'>;\n }\n\n /**\n * Switch to cursor-pagination mode: return documents with `_id > id`.\n * See `.before()` for full semantics — including the ObjectId-only cursor\n * constraint and the `.offset()` incompatibility (server-enforced 400).\n *\n * **For paginating further:** pass the returned `nextCursor` to\n * `.after()` again to load the next newer page.\n */\n after(id: string): QueryBuilder<T, 'cursor'> {\n this._after = id;\n return this as QueryBuilder<T, 'cursor'>;\n }\n\n select(fields: string[]): this {\n this._fields = fields;\n return this;\n }\n\n populate(\n field: string,\n collection: string,\n foreignField?: string,\n ): this {\n this._populate.push({ field, collection, foreignField });\n return this;\n }\n\n async execute(): Promise<\n M extends 'cursor' ? CursorResult<T> : OffsetResult<T>\n > {\n const query: Record<string, string | number | undefined> = {};\n if (this._filter) query['filter'] = JSON.stringify(this._filter);\n if (this._sort) query['sort'] = JSON.stringify(this._sort);\n if (this._limit !== undefined) query['limit'] = this._limit;\n if (this._offset !== undefined) query['offset'] = this._offset;\n if (this._before !== undefined) query['before'] = this._before;\n if (this._after !== undefined) query['after'] = this._after;\n if (this._fields) query['fields'] = this._fields.join(',');\n if (this._populate.length) {\n query['populate'] = this._populate\n .map((p) =>\n p.foreignField\n ? `${p.field}:${p.collection}:${p.foreignField}`\n : `${p.field}:${p.collection}`,\n )\n .join(',');\n }\n\n return this.http.request<M extends 'cursor' ? CursorResult<T> : OffsetResult<T>>({\n method: 'GET',\n path: this.basePath,\n query,\n authenticated: true,\n });\n }\n}\n\nexport class CollectionRef<T = Record<string, unknown>> {\n private basePath: string;\n private collectionName: string;\n\n constructor(\n private http: HttpClient,\n private realtime: RealtimeClient | null,\n private projectId: string,\n collectionName: string,\n ) {\n this.collectionName = collectionName;\n this.basePath = `/db/${collectionName}`;\n }\n\n async insert(\n document: Partial<T> & { _id?: string },\n ): Promise<InsertResult> {\n return this.http.request<InsertResult>({\n method: 'POST',\n path: this.basePath,\n body: { documents: [document] },\n authenticated: true,\n });\n }\n\n async insertMany(\n documents: (Partial<T> & { _id?: string })[],\n ): Promise<InsertResult> {\n return this.http.request<InsertResult>({\n method: 'POST',\n path: this.basePath,\n body: { documents },\n authenticated: true,\n });\n }\n\n find(filter?: Record<string, unknown>): QueryBuilder<T> {\n return new QueryBuilder<T>(this.http, this.basePath, filter);\n }\n\n /**\n * Cursor-based scroll-back helper. Returns a `Paginator` whose `.next()`\n * yields successive pages and tracks the last-seen `_id` internally.\n * Defaults to descending sort and 50 docs per page (chat scroll-back is\n * the canonical use case). See `PaginateOptions` for tuning.\n *\n * **Cursor constraint:** uses `_id` keyset pagination, which requires\n * 24-hex ObjectId `_id`s. Collections with custom string `_id` schemes\n * fall back to comparing strings as ObjectIds — the server's handler\n * documents this limitation on `before` / `after` directly.\n */\n paginate(opts: PaginateOptions = {}): Paginator<T> {\n return new Paginator<T>(this.http, this.basePath, opts);\n }\n\n /**\n * Server-side substring search across the specified fields.\n *\n * The query is regex-escaped server-side and matched case-insensitively via\n * MongoDB `$regex`. Performance note: unanchored case-insensitive regex\n * cannot use a standard B-tree index — on very large collections consider\n * narrowing with `filter` to scope the scan.\n *\n * Limits: `query` 1–200 chars, `fields` 1–10 entries (each matching\n * `/^[a-zA-Z0-9_.]+$/`, max 64 chars), `limit` max 100.\n */\n async search(opts: SearchOptions): Promise<SearchResult<T>> {\n return this.http.request<SearchResult<T>>({\n method: 'POST',\n path: `${this.basePath}/search`,\n body: opts,\n authenticated: true,\n });\n }\n\n async findById(\n id: string,\n options?: FindByIdOptions,\n ): Promise<T & { _id: string }> {\n const query: Record<string, string> = {};\n if (options?.populate?.length) {\n query['populate'] = options.populate\n .map((p) =>\n p.foreignField\n ? `${p.field}:${p.collection}:${p.foreignField}`\n : `${p.field}:${p.collection}`,\n )\n .join(',');\n }\n\n return this.http.request<T & { _id: string }>({\n method: 'GET',\n path: `${this.basePath}/${id}`,\n query,\n authenticated: true,\n });\n }\n\n async update(id: string, update: Partial<T>): Promise<UpdateResult> {\n return this.http.request<UpdateResult>({\n method: 'PATCH',\n path: `${this.basePath}/${id}`,\n body: { update },\n authenticated: true,\n });\n }\n\n async delete(id: string): Promise<DeleteResult> {\n return this.http.request<DeleteResult>({\n method: 'DELETE',\n path: `${this.basePath}/${id}`,\n authenticated: true,\n });\n }\n\n async count(filter?: Record<string, unknown>): Promise<number> {\n const result = await this.http.request<CountResult>({\n method: 'POST',\n path: `${this.basePath}/count`,\n body: { filter },\n authenticated: true,\n });\n return result.count;\n }\n\n subscribe(handlers: SubscribeHandlers<T>): () => void {\n if (!this.realtime) {\n throw new Error('Realtime not available: no RealtimeClient configured');\n }\n\n let unsubscribeFn: (() => void) | null = null;\n let pendingUnsub = false;\n\n const callback = (event: DatabaseChangeEvent) => {\n switch (event.type) {\n case 'insert':\n if (handlers.onInsert && event.document) {\n handlers.onInsert(event.document as T & { _id: string });\n }\n break;\n case 'update':\n if (handlers.onUpdate && event.document) {\n handlers.onUpdate(event.document as T & { _id: string });\n }\n break;\n case 'delete':\n if (handlers.onDelete) {\n handlers.onDelete(event.documentId);\n }\n break;\n }\n };\n\n this.realtime\n .subscribe(this.projectId, this.collectionName, callback, handlers.onError, handlers.where)\n .then((unsub) => {\n if (pendingUnsub) {\n // Unsubscribe was called before async subscribe resolved\n unsub();\n } else {\n unsubscribeFn = unsub;\n }\n })\n .catch((err) => {\n if (handlers.onError) {\n handlers.onError(err instanceof Error ? err : new Error(String(err)));\n }\n });\n\n // Return synchronous unsubscribe\n return () => {\n if (unsubscribeFn) {\n unsubscribeFn();\n } else {\n pendingUnsub = true;\n }\n };\n }\n\n subscribeEvents(handlers: SubscribeEventsHandlers<T>): StreamSubscription {\n if (!this.realtime) {\n throw new Error('Realtime not available: no RealtimeClient configured');\n }\n\n let lastCursor: string | undefined;\n let unsub: (() => void) | null = null;\n let unsubscribed = false;\n\n const cursorStorage = handlers.cursorStorage;\n const cursorKey =\n handlers.cursorKey ?? `${this.projectId}:${this.collectionName}`;\n\n // Loud warning for the #1 footgun: sharing a persisted cursor across\n // differently-filtered subscriptions silently skips events on reconnect\n // because the stream position represents \"seen\", not \"matched\".\n if (\n cursorStorage &&\n handlers.where &&\n Object.keys(handlers.where).length > 0 &&\n handlers.cursorKey === undefined\n ) {\n console.warn(\n `[spacelr] subscribeEvents on ${this.projectId}:${this.collectionName} uses a 'where' filter with cursorStorage but no explicit cursorKey — ` +\n `filtered subscriptions sharing the default key will silently skip events across reconnects. ` +\n `Pass a unique cursorKey (e.g. include a hash of the filter).`,\n );\n }\n // Capture after the null-guard so the closures below don't need `!`.\n const realtime = this.realtime;\n // Async saves must not complete out of order — that would regress the\n // persisted cursor below an already-emitted event. Chain every save\n // onto the previous so they serialize end-to-end while still being\n // decoupled from event delivery.\n let lastSavePromise: Promise<unknown> = Promise.resolve();\n\n const onEvent = async ({\n eventId,\n event,\n }: {\n eventId: string;\n event: DatabaseChangeEvent;\n }) => {\n try {\n if (event.type === 'insert' && handlers.onInsert && event.document) {\n await handlers.onInsert({\n ...(event.document as T & { _id: string }),\n _eventId: eventId,\n });\n } else if (event.type === 'update' && handlers.onUpdate && event.document) {\n await handlers.onUpdate({\n ...(event.document as T & { _id: string }),\n _eventId: eventId,\n });\n } else if (event.type === 'delete' && handlers.onDelete) {\n await handlers.onDelete(event.documentId, eventId);\n }\n lastCursor = eventId;\n if (cursorStorage) {\n // Chain this save onto the previous one so async backends can't\n // complete out of order and regress the stored cursor. Both\n // fulfilled and rejected continuations still run `save()` —\n // we don't want one failed save to skip the next event's save.\n // Each save's own failure surfaces via onError asynchronously.\n lastSavePromise = lastSavePromise\n .then(\n () => cursorStorage.save(cursorKey, eventId),\n () => cursorStorage.save(cursorKey, eventId),\n )\n .catch((err) => {\n handlers.onError?.(err instanceof Error ? err : new Error(String(err)));\n });\n }\n } catch (err) {\n // Rethrow so RealtimeClient's per-subscriber queue sees the rejection and\n // skips advancing its own `lastDeliveredId`. That same catch calls\n // `handlers.onError` for us — do NOT call it here or the user would see\n // the error twice.\n throw err instanceof Error ? err : new Error(String(err));\n }\n };\n\n const callSubscribe = (sinceId: string | undefined): Promise<() => void> =>\n realtime.subscribeWithCursor({\n projectId: this.projectId,\n collectionName: this.collectionName,\n sinceId,\n where: handlers.where,\n onEvent,\n onGap: handlers.onGap,\n onError: handlers.onError,\n });\n\n // Fast path: no storage (or explicit sinceId) — skip the extra await\n // so consumers that don't opt into cursor persistence keep the same\n // one-microtask handshake timing they had before.\n let promise: Promise<() => void>;\n if (handlers.sinceId !== undefined || !cursorStorage) {\n promise = callSubscribe(handlers.sinceId);\n } else {\n promise = (async () => {\n let resumeFrom: string | undefined;\n try {\n const loaded = await Promise.resolve(cursorStorage.load(cursorKey));\n if (loaded) resumeFrom = loaded;\n } catch (err) {\n // Storage read failure is never a reason to skip the subscription —\n // resume fresh and let the consumer's onError see the warning.\n handlers.onError?.(err instanceof Error ? err : new Error(String(err)));\n }\n return callSubscribe(resumeFrom);\n })();\n }\n\n promise\n .then((u) => {\n if (unsubscribed) {\n // Caller already asked to cancel; release the reader immediately.\n u();\n } else {\n unsub = u;\n }\n })\n .catch(() => undefined); // onError already surfaced inside the realtime layer.\n\n return {\n unsubscribe(): void {\n unsubscribed = true;\n if (unsub) {\n unsub();\n unsub = null;\n }\n },\n getCursor(): string | undefined {\n return lastCursor;\n },\n };\n }\n}\n\nexport class DatabaseModule {\n private http: HttpClient;\n private realtime: RealtimeClient | null;\n private projectId: string;\n\n constructor(http: HttpClient, projectId: string, realtime?: RealtimeClient) {\n this.http = http;\n this.projectId = projectId;\n this.realtime = realtime ?? null;\n }\n\n collection<T = Record<string, unknown>>(name: string): CollectionRef<T> {\n return new CollectionRef<T>(this.http, this.realtime, this.projectId, name);\n }\n}\n","import { HttpClient } from '../core';\n\nexport interface PushSubscriptionInfo {\n id: string;\n platform: 'web' | 'android' | 'ios';\n endpoint?: string;\n deviceToken?: string;\n deviceId?: string;\n deviceName?: string;\n userAgent?: string;\n isActive: boolean;\n lastUsedAt?: string;\n createdAt: string;\n updatedAt: string;\n}\n\nexport interface VapidKeyResponse {\n publicKey: string;\n}\n\nconst DEVICE_ID_KEY = 'spacelr_device_id';\n\nexport class NotificationsModule {\n private customDeviceId: string | null = null;\n private customDeviceName: string | null = null;\n\n constructor(private http: HttpClient) {}\n\n /** Set a custom device ID (e.g. from Capacitor Preferences for persistence beyond localStorage) */\n setDeviceId(id: string): void {\n this.customDeviceId = id;\n }\n\n /** Set a custom device name (e.g. \"iOS App\", \"macOS - Chrome\") */\n setDeviceName(name: string): void {\n this.customDeviceName = name;\n }\n\n /** Get or generate a stable device identifier. Custom ID takes priority over localStorage. */\n private getDeviceId(): string | undefined {\n if (this.customDeviceId) return this.customDeviceId;\n if (typeof localStorage === 'undefined') return undefined;\n let id = localStorage.getItem(DEVICE_ID_KEY);\n if (!id) {\n id = crypto.randomUUID();\n localStorage.setItem(DEVICE_ID_KEY, id);\n }\n return id;\n }\n\n /** Get device name: custom name > auto-detected from user agent > undefined */\n private getDeviceName(): string | undefined {\n if (this.customDeviceName) return this.customDeviceName;\n return this.detectDeviceName();\n }\n\n /** Auto-detect a short device label from navigator.userAgent (e.g. \"macOS - Chrome\") */\n private detectDeviceName(): string | undefined {\n if (typeof navigator === 'undefined' || !navigator.userAgent) return undefined;\n const ua = navigator.userAgent;\n\n let os = 'Unknown';\n if (/iPad|iPhone|iPod/.test(ua)) os = 'iOS';\n else if (/Android/.test(ua)) os = 'Android';\n else if (/Mac OS X/.test(ua)) os = 'macOS';\n else if (/Windows/.test(ua)) os = 'Windows';\n else if (/Linux/.test(ua)) os = 'Linux';\n\n let browser = 'Unknown';\n if (/Edg\\//.test(ua)) browser = 'Edge';\n else if (/OPR\\/|Opera/.test(ua)) browser = 'Opera';\n else if (/Chrome\\//.test(ua)) browser = 'Chrome';\n else if (/Safari\\//.test(ua) && !/Chrome/.test(ua)) browser = 'Safari';\n else if (/Firefox\\//.test(ua)) browser = 'Firefox';\n\n return `${os} - ${browser}`;\n }\n\n /** Get the VAPID public key for Web Push setup */\n async getVapidPublicKey(): Promise<VapidKeyResponse> {\n return this.http.request<VapidKeyResponse>({\n method: 'GET',\n path: '/notifications/vapid-key',\n authenticated: true,\n });\n }\n\n /** Register a push subscription (web, android, or ios) */\n async subscribe(\n subscription: {\n platform: 'web' | 'android' | 'ios';\n endpoint?: string;\n keys?: { p256dh: string; auth: string };\n deviceToken?: string;\n },\n deviceName?: string,\n ): Promise<PushSubscriptionInfo> {\n return this.http.request<PushSubscriptionInfo>({\n method: 'POST',\n path: '/notifications/subscribe',\n body: {\n ...subscription,\n deviceName: deviceName ?? this.getDeviceName(),\n deviceId: this.getDeviceId(),\n },\n authenticated: true,\n });\n }\n\n /** Unregister a push subscription */\n async unsubscribe(\n platform: 'web' | 'android' | 'ios',\n identifier: string,\n ): Promise<{ deleted: boolean }> {\n const body: Record<string, string> = { platform };\n if (platform === 'web') {\n body['endpoint'] = identifier;\n } else {\n body['deviceToken'] = identifier;\n }\n\n return this.http.request<{ deleted: boolean }>({\n method: 'DELETE',\n path: '/notifications/subscribe',\n body,\n authenticated: true,\n });\n }\n\n /** Get all subscriptions for the current user */\n async getSubscriptions(): Promise<PushSubscriptionInfo[]> {\n return this.http.request<PushSubscriptionInfo[]>({\n method: 'GET',\n path: '/notifications/subscriptions',\n authenticated: true,\n });\n }\n\n /**\n * Helper: Register browser Web Push subscription.\n * Requests notification permission, subscribes via Push API,\n * and registers the subscription with the server.\n */\n async registerBrowserPush(\n serviceWorkerRegistration: ServiceWorkerRegistration,\n deviceName?: string,\n ): Promise<PushSubscriptionInfo> {\n // Get VAPID key\n const { publicKey } = await this.getVapidPublicKey();\n\n if (!publicKey) {\n throw new Error(\n 'VAPID public key not configured on the server',\n );\n }\n\n // Request notification permission\n const permission = await Notification.requestPermission();\n if (permission !== 'granted') {\n throw new Error(\n `Notification permission denied: ${permission}`,\n );\n }\n\n // Convert VAPID key to Uint8Array\n const applicationServerKey = this.urlBase64ToUint8Array(publicKey);\n\n // Subscribe via Push API\n const pushSubscription =\n await serviceWorkerRegistration.pushManager.subscribe({\n userVisibleOnly: true,\n applicationServerKey,\n });\n\n const subJson = pushSubscription.toJSON();\n const p256dh = subJson.keys?.['p256dh'];\n const auth = subJson.keys?.['auth'];\n\n if (!subJson.endpoint || !p256dh || !auth) {\n throw new Error('Invalid push subscription: missing endpoint or keys');\n }\n\n // Register with server\n return this.subscribe(\n {\n platform: 'web',\n endpoint: subJson.endpoint,\n keys: { p256dh, auth },\n },\n deviceName,\n );\n }\n\n /**\n * Helper: Register a mobile device push token (FCM or APNs).\n */\n async registerDevicePush(\n deviceToken: string,\n platform: 'android' | 'ios',\n deviceName?: string,\n ): Promise<PushSubscriptionInfo> {\n return this.subscribe(\n {\n platform,\n deviceToken,\n },\n deviceName,\n );\n }\n\n private urlBase64ToUint8Array(base64String: string): ArrayBuffer {\n const padding = '='.repeat((4 - (base64String.length % 4)) % 4);\n const base64 = (base64String + padding)\n .replace(/-/g, '+')\n .replace(/_/g, '/');\n\n const rawData = atob(base64);\n const outputArray = new Uint8Array(rawData.length);\n\n for (let i = 0; i < rawData.length; ++i) {\n outputArray[i] = rawData.charCodeAt(i);\n }\n return outputArray.buffer as ArrayBuffer;\n }\n}\n","import { HttpClient } from '../core';\n\nexport interface FunctionInvokeOptions {\n /** Webhook secret — used for `webhook` and `hybrid` invokeMode. */\n secret?: string;\n /**\n * Whether to attach the signed-in user's bearer token via `Authorization`\n * (managed by the SDK's TokenManager).\n *\n * Default: `true` when no `secret` is provided, `false` when `secret` is\n * set. Override explicitly for the hybrid case (provide both) or to\n * suppress the header even when logged in.\n *\n * If `true` but the user is not signed in, the header is simply omitted —\n * safe for `public` invokeMode.\n */\n authenticated?: boolean;\n payload?: Record<string, unknown>;\n}\n\nexport interface FunctionInvokeResult {\n success: boolean;\n executionId?: string;\n error?: string;\n}\n\nexport class FunctionsModule {\n private http: HttpClient;\n\n constructor(http: HttpClient) {\n this.http = http;\n }\n\n /**\n * Invoke a function.\n * Calls POST `/functions/:projectId/:functionId/invoke`, resolved against\n * `config.apiUrl` (which already carries the `/api/v1` prefix).\n *\n * Auth defaults, based on `invokeMode` semantics:\n * - webhook: pass `secret` → Authorization is NOT attached\n * - authenticated: pass nothing → Authorization IS attached (from token manager)\n * - public: pass nothing → Authorization is attached if logged in, else omitted\n * - hybrid: pass both `secret` and `authenticated: true`\n *\n * To force a specific behaviour, set `authenticated` explicitly — it wins\n * over the `secret`-based default.\n */\n async invoke(\n projectId: string,\n functionId: string,\n options: FunctionInvokeOptions = {},\n ): Promise<FunctionInvokeResult> {\n // Empty-string secret is treated as absent — both the header guard and\n // the authenticated-default check use `?.length`, so the two stay in\n // lock-step (no \"header omitted but still treated as webhook mode\").\n const hasSecret = (options.secret?.length ?? 0) > 0;\n\n const headers: Record<string, string> = {};\n if (hasSecret) {\n headers['X-Webhook-Secret'] = options.secret as string;\n }\n\n // Default: authenticate unless a webhook secret is provided.\n // Explicit `authenticated` overrides this.\n const authenticated = options.authenticated ?? !hasSecret;\n\n return this.http.request<FunctionInvokeResult>({\n method: 'POST',\n path: `/functions/${encodeURIComponent(projectId)}/${encodeURIComponent(functionId)}/invoke`,\n headers,\n body: options.payload ?? {},\n authenticated,\n });\n }\n}\n","import { SpacelrClientConfig, StoredTokens } from './types';\nimport { HttpClient, TokenManager, MemoryTokenStorage, BrowserTokenStorage, RealtimeClient, ConnectionState } from './core';\nimport { AuthLostReason } from './core/token-manager';\nimport { AuthModule, StorageModule, DatabaseModule, NotificationsModule, FunctionsModule } from './modules';\n\nexport interface SpacelrClient {\n readonly auth: AuthModule;\n readonly storage: StorageModule;\n readonly db: DatabaseModule;\n readonly notifications: NotificationsModule;\n readonly functions: FunctionsModule;\n /**\n * Store auth tokens externally (e.g. obtained via the auth-components\n * library that bypasses the SDK). Makes the tokens available to HTTP +\n * realtime clients. Refresh is on-demand: the SDK will refresh the\n * access token when it next makes a request and the token is within the\n * expiry buffer (or on a 401 retry).\n */\n setTokens(tokens: StoredTokens): Promise<void>;\n /** Clear stored tokens and reset auth-loss state. */\n clearTokens(): Promise<void>;\n /** Disconnect realtime WebSocket (if connected) */\n disconnect(): void;\n /**\n * Subscribe to auth loss. Fires in three situations:\n * - `'refresh-failed'` — a refresh call was rejected by the server\n * (refresh token revoked or expired). Note: this can also fire from a\n * *background* refresh triggered inside the expiry buffer window, at\n * which point the current access token may still be valid for up to\n * `refreshBufferSeconds` (default 60). Treat the session as over anyway;\n * the refresh token is dead.\n * - `'unauthorized'` — an authenticated request came back 401 and no\n * fresh token could be produced (no refresh token, or the retry after\n * a successful refresh was itself 401).\n *\n * Typical consumer reaction: clear UI state and show the login screen.\n * The event is deduped until `setTokens`/`clearTokens` is called again.\n */\n onAuthLost(listener: (reason: AuthLostReason) => void): () => void;\n /** Subscribe to successful token refreshes. */\n onTokenRefreshed(listener: (tokens: StoredTokens) => void): () => void;\n /**\n * Subscribe to realtime socket connection-state changes. Fires on every\n * transition (dedup: identical consecutive states are skipped). Returns\n * an unsubscribe function.\n *\n * States:\n * - `'disconnected'` — initial state before the first subscribe, or after\n * the SDK has torn the realtime socket down (e.g. on logout via the\n * internal `realtime.disconnect()`). This is a terminal state for the\n * current realtime client; reconnect happens on the next `subscribe()`.\n * - `'reconnecting'` — connecting or rebuilding the socket (initial\n * connect, socket drop, `reconnect_failed` rebuild, or a wake-up event\n * after suspend).\n * - `'connected'` — authenticated and actively listening for events.\n */\n onConnectionStateChanged(listener: (state: ConnectionState) => void): () => void;\n /** Read the current realtime socket state without waiting for an event. */\n getConnectionState(): ConnectionState;\n}\n\nexport function createClient(config: SpacelrClientConfig): SpacelrClient {\n const tokenStorage = config.tokenStorage\n ?? (typeof window !== 'undefined' && typeof window.localStorage !== 'undefined'\n ? new BrowserTokenStorage()\n : new MemoryTokenStorage());\n const tokenManager = new TokenManager(\n tokenStorage,\n config.refreshBufferSeconds ?? 60\n );\n const httpClient = new HttpClient(config, tokenManager);\n\n const realtime = new RealtimeClient({\n baseUrl: config.apiUrl,\n getToken: () => tokenManager.getAccessToken(),\n onTokenRefreshed: (listener) =>\n tokenManager.onTokenRefreshed((tokens) => listener(tokens.accessToken)),\n });\n\n const auth = new AuthModule(httpClient, tokenManager, config);\n const storage = new StorageModule(httpClient, tokenManager, config);\n const db = new DatabaseModule(httpClient, config.projectId, realtime);\n const notifications = new NotificationsModule(httpClient);\n const functions = new FunctionsModule(httpClient);\n\n return {\n auth,\n storage,\n db,\n notifications,\n functions,\n setTokens(tokens) {\n return tokenManager.setTokens(tokens);\n },\n clearTokens() {\n return tokenManager.clearTokens();\n },\n disconnect() {\n realtime.disconnect();\n },\n onAuthLost(listener) {\n return tokenManager.onAuthLost(listener);\n },\n onTokenRefreshed(listener) {\n return tokenManager.onTokenRefreshed(listener);\n },\n onConnectionStateChanged(listener) {\n return realtime.onConnectionStateChanged(listener);\n },\n getConnectionState() {\n return realtime.getConnectionState();\n },\n };\n}\n"],"mappings":"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","names":["FileVisibility","GrantType","CodeChallengeMethod","SharePermission","hash","callbacks","document"]}