@dotwire/client 0.0.0-stage → 0.2.1

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.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/types.ts","../src/errors.ts","../src/client.ts","../src/seq-set.ts","../src/room.ts","../src/token.ts"],"sourcesContent":["import type { HubConnection, IHttpConnectionOptions } from '@microsoft/signalr';\n\nexport type ConnectionState =\n | 'disconnected'\n | 'connecting'\n | 'connected'\n | 'reconnecting'\n | 'disconnecting';\n\nexport interface DotwireClientOptions {\n baseUrl: string;\n /** GET, `credentials: 'include'`, expects `{ token }`. One of this or `getAccessToken` is required. */\n tokenUrl?: string;\n /** Escape hatch: caller owns caching/refresh. One of this or `tokenUrl` is required. */\n getAccessToken?: () => string | Promise<string>;\n /** Default 30000. */\n tokenRefreshMarginMs?: number;\n /** POST `{roomId, content}` with `credentials: 'include'`, no bearer header; a host route. */\n sendUrl?: string;\n /** Default `/hub/rooms`. */\n hubPath?: string;\n /** Default `'websockets'` (skipNegotiation). */\n transport?: 'websockets' | 'negotiate';\n /** Default 90000. */\n serverTimeoutMs?: number;\n /** Default 15000. */\n keepAliveIntervalMs?: number;\n /** Default true. */\n automaticReconnect?: boolean;\n /** Default true. */\n gapFillOnReconnect?: boolean;\n /** Default 100. */\n gapFillPageSize?: number;\n /** Default 15000. */\n requestTimeoutMs?: number;\n autoStopTypingMs?: number;\n typingThrottleMs?: number;\n logLevel?: 'none' | 'error' | 'warning' | 'information' | 'debug';\n /** Test seam for constructing the `HubConnection`. Defaults to the real signalr builder. */\n connectionFactory?: (url: string, options: IHttpConnectionOptions) => HubConnection;\n}\n\n/**\n * Delivered for both live messages (`ReceiveMessage`) and history pages; `replayed` is set\n * `true` only for reconnect gap-fill replay (spec §6, §12 - replaces `RoomMessageDelivery`).\n */\nexport interface RoomMessage {\n roomId: string;\n seq: number;\n senderId: string;\n time: string;\n content: string;\n replayed?: boolean;\n}\n\nexport interface MessageRetracted {\n roomId: string;\n seq: number;\n}\n\nexport interface MembershipRevoked {\n roomId: string;\n userId: string;\n}\n\nexport interface TypingNotification {\n roomId: string;\n userId: string;\n isTyping: boolean;\n}\n\nexport interface PresenceDelta {\n roomId: string;\n joined: string[];\n left: string[];\n}\n\nexport interface SendAck {\n seq: number;\n time: string;\n}\n\nexport interface HistoryPage {\n messages: RoomMessage[];\n hasMore: boolean;\n}\n\nexport interface GetHistoryOptions {\n afterSeq?: number;\n beforeSeq?: number;\n limit?: number;\n timeoutMs?: number;\n}\n\nexport interface SubscribeOptions {\n /** When set, fetches and delivers the latest page of history (as `getHistory({limit: history})`). */\n history?: number;\n}\n\nexport function isSystemSender(senderId: string): boolean {\n if (!senderId) return false;\n const lower = senderId.toLowerCase();\n return lower === 'system' || lower.startsWith('system:') || lower.startsWith('bot:');\n}\n","export type DotwireErrorKind =\n | 'BadRequest'\n | 'Unauthorized'\n | 'Forbidden'\n | 'NotFound'\n | 'PresendRejected'\n | 'RateLimited'\n | 'Unavailable'\n | 'Unknown';\n\nexport interface DotwireClientErrorInit {\n status: number;\n kind: DotwireErrorKind;\n code?: string;\n reason?: string;\n retryAfterMs?: number;\n}\n\n/**\n * Client-side error type, matching the `{status, kind, code?, reason?, retryAfterMs?}` shape\n * shared with `Dotwire.Host` and `@dotwire/host` (spec §6, §4.6, §5).\n */\nexport class DotwireClientError extends Error {\n public readonly status: number;\n public readonly kind: DotwireErrorKind;\n public readonly code?: string;\n public readonly reason?: string;\n public readonly retryAfterMs?: number;\n\n constructor(init: DotwireClientErrorInit) {\n super(init.reason ?? init.code ?? `dotwire request failed with status ${init.status}`);\n this.name = 'DotwireClientError';\n this.status = init.status;\n this.kind = init.kind;\n this.code = init.code;\n this.reason = init.reason;\n this.retryAfterMs = init.retryAfterMs;\n }\n}\n\n/**\n * Maps a failed fetch `Response` to a {@link DotwireClientError}. New 4xx/5xx codes carry a\n * `{error, reason?}` JSON body (spec §3.14, `dotwire/Api/ApiResults.cs`); bare 401/403/404\n * responses have no body and are classified by status alone.\n */\nexport async function toDotwireClientError(response: Response): Promise<DotwireClientError> {\n const status = response.status;\n let code: string | undefined;\n let reason: string | undefined;\n\n try {\n const body: unknown = await response.json();\n if (body && typeof body === 'object') {\n const record = body as Record<string, unknown>;\n if (typeof record.error === 'string') code = record.error;\n if (typeof record.reason === 'string') reason = record.reason;\n }\n } catch {\n // Bare error body (e.g. 401/403/404) or non-JSON payload.\n }\n\n let retryAfterMs: number | undefined;\n const retryAfterHeader = response.headers?.get?.('Retry-After');\n if (retryAfterHeader) {\n const seconds = Number(retryAfterHeader);\n if (!Number.isNaN(seconds)) {\n retryAfterMs = seconds * 1000;\n }\n }\n\n return new DotwireClientError({ status, kind: classifyKind(status, code), code, reason, retryAfterMs });\n}\n\nfunction classifyKind(status: number, code?: string): DotwireErrorKind {\n switch (status) {\n case 400:\n return 'BadRequest';\n case 401:\n return 'Unauthorized';\n case 403:\n return 'Forbidden';\n case 404:\n return 'NotFound';\n case 422:\n return code === 'presend_rejected' ? 'PresendRejected' : 'BadRequest';\n case 429:\n return 'RateLimited';\n case 503:\n return 'Unavailable';\n default:\n return 'Unknown';\n }\n}\n","import {\n HttpTransportType,\n HubConnection,\n HubConnectionBuilder,\n HubConnectionState,\n IHttpConnectionOptions,\n LogLevel\n} from '@microsoft/signalr';\nimport { toDotwireClientError } from './errors.js';\nimport { BoundedSeqSet } from './seq-set.js';\nimport { DotwireRoomHandle } from './room.js';\nimport { TokenManager } from './token.js';\nimport {\n ConnectionState,\n DotwireClientOptions,\n GetHistoryOptions,\n HistoryPage,\n MembershipRevoked,\n MessageRetracted,\n PresenceDelta,\n RoomMessage,\n SendAck,\n TypingNotification,\n isSystemSender\n} from './types.js';\n\ninterface WireHistoryMessage {\n seq: number;\n time: string;\n senderId: string;\n content: string;\n}\n\ninterface WireHistoryResponse {\n roomId: string;\n messages: WireHistoryMessage[];\n hasMore: boolean;\n}\n\ninterface RoomState {\n subscribed: boolean;\n lastSeq: number | null;\n seen: BoundedSeqSet;\n}\n\nexport class DotwireClient {\n private readonly baseUrl: string;\n private readonly hubPath: string;\n private readonly connection: HubConnection;\n private readonly getToken: () => string | Promise<string>;\n private readonly messageListeners = new Set<(message: RoomMessage) => void>();\n private readonly retractedListeners = new Set<(payload: MessageRetracted) => void>();\n private readonly revokedListeners = new Set<(payload: MembershipRevoked) => void>();\n private readonly typingListeners = new Set<(notification: TypingNotification) => void>();\n private readonly presenceListeners = new Set<(delta: PresenceDelta) => void>();\n private readonly stateListeners = new Set<(state: ConnectionState) => void>();\n private readonly subscribedRooms = new Set<string>();\n private readonly roomState = new Map<string, RoomState>();\n private readonly activeRoomHandles = new Set<DotwireRoomHandle>();\n private unloadListener: (() => void) | null = null;\n private pendingConnect: Promise<void> | null = null;\n\n constructor(public readonly options: DotwireClientOptions) {\n if (!options.tokenUrl && !options.getAccessToken) {\n throw new Error('DotwireClient requires either \"tokenUrl\" or \"getAccessToken\".');\n }\n\n this.baseUrl = options.baseUrl.replace(/\\/$/, '');\n this.hubPath = options.hubPath ?? '/hub/rooms';\n\n if (options.getAccessToken) {\n this.getToken = options.getAccessToken;\n } else {\n const tokenManager = new TokenManager(options.tokenUrl!, options.tokenRefreshMarginMs ?? 30000);\n this.getToken = () => tokenManager.getToken();\n }\n\n this.connection = this.buildConnection();\n this.registerHubHandlers();\n this.registerUnloadHandler();\n }\n\n private buildConnection(): HubConnection {\n const url = `${this.baseUrl}${this.hubPath}`;\n const httpOptions: IHttpConnectionOptions = {\n accessTokenFactory: () => this.getToken()\n };\n\n if ((this.options.transport ?? 'websockets') === 'websockets') {\n httpOptions.transport = HttpTransportType.WebSockets;\n httpOptions.skipNegotiation = true;\n }\n\n if (this.options.connectionFactory) {\n return this.options.connectionFactory(url, httpOptions);\n }\n\n const builder = new HubConnectionBuilder()\n .withUrl(url, httpOptions)\n .configureLogging(mapLogLevel(this.options.logLevel));\n\n if (this.options.automaticReconnect !== false) {\n builder.withAutomaticReconnect();\n }\n\n const connection = builder.build();\n connection.serverTimeoutInMilliseconds = this.options.serverTimeoutMs ?? 90000;\n connection.keepAliveIntervalInMilliseconds = this.options.keepAliveIntervalMs ?? 15000;\n return connection;\n }\n\n private registerHubHandlers(): void {\n this.connection.on('ReceiveMessage', (message: RoomMessage) => {\n this.deliverMessage(message);\n });\n\n this.connection.on('UserTyping', (notification: TypingNotification) => {\n for (const listener of this.typingListeners) {\n listener(notification);\n }\n });\n\n this.connection.on('PresenceUpdated', (delta: PresenceDelta) => {\n for (const listener of this.presenceListeners) {\n listener(delta);\n }\n });\n\n this.connection.on('MessageRetracted', (payload: MessageRetracted) => {\n for (const listener of this.retractedListeners) {\n listener(payload);\n }\n });\n\n this.connection.on('MembershipRevoked', (payload: MembershipRevoked) => {\n this.subscribedRooms.delete(payload.roomId);\n const state = this.roomState.get(payload.roomId);\n if (state) state.subscribed = false;\n for (const listener of this.revokedListeners) {\n listener(payload);\n }\n });\n\n this.connection.onclose(() => {\n this.notifyStateChange('disconnected');\n });\n\n this.connection.onreconnecting(() => {\n this.notifyStateChange('reconnecting');\n });\n\n this.connection.onreconnected(async () => {\n this.notifyStateChange('connected');\n for (const roomId of Array.from(this.subscribedRooms)) {\n const resumeFrom = this.roomState.get(roomId)?.lastSeq ?? null;\n try {\n await this.connection.invoke('Subscribe', roomId);\n } catch {\n continue;\n }\n if (this.options.gapFillOnReconnect === false || resumeFrom === null) continue;\n try {\n await this.gapFill(roomId, resumeFrom);\n } catch {\n // Best-effort: a failed gap-fill leaves lastSeq as-is; a later reconnect retries.\n }\n }\n });\n }\n\n private registerUnloadHandler(): void {\n if (typeof window !== 'undefined' && typeof window.addEventListener === 'function') {\n const onUnload = () => {\n this.disconnect().catch(() => {});\n };\n window.addEventListener('beforeunload', onUnload);\n window.addEventListener('pagehide', onUnload);\n this.unloadListener = () => {\n window.removeEventListener('beforeunload', onUnload);\n window.removeEventListener('pagehide', onUnload);\n };\n }\n }\n\n private notifyStateChange(state: ConnectionState): void {\n for (const listener of this.stateListeners) {\n listener(state);\n }\n }\n\n private getOrCreateRoomState(roomId: string): RoomState {\n let state = this.roomState.get(roomId);\n if (!state) {\n state = { subscribed: false, lastSeq: null, seen: new BoundedSeqSet(1000) };\n this.roomState.set(roomId, state);\n }\n return state;\n }\n\n /**\n * Applies seq dedupe/tracking (spec §6) and, if the message is new, notifies listeners.\n * Used for both live `ReceiveMessage` delivery and history/gap-fill replay.\n */\n public deliverMessage(message: RoomMessage): void {\n const state = this.getOrCreateRoomState(message.roomId);\n if (state.seen.has(message.seq)) return;\n state.seen.add(message.seq);\n if (state.lastSeq === null || message.seq > state.lastSeq) {\n state.lastSeq = message.seq;\n }\n for (const listener of this.messageListeners) {\n listener(message);\n }\n }\n\n private async gapFill(roomId: string, fromSeq: number): Promise<void> {\n const pageSize = this.options.gapFillPageSize ?? 100;\n let afterSeq = fromSeq;\n let hasMore = true;\n\n while (hasMore) {\n const page = await this.getHistory(roomId, { afterSeq, limit: pageSize });\n if (page.messages.length === 0) break;\n\n for (const message of page.messages) {\n this.deliverMessage({ ...message, replayed: true });\n afterSeq = Math.max(afterSeq, message.seq);\n }\n\n hasMore = page.hasMore;\n }\n }\n\n public getConnectionState(): ConnectionState {\n switch (this.connection.state) {\n case HubConnectionState.Connected:\n return 'connected';\n case HubConnectionState.Connecting:\n return 'connecting';\n case HubConnectionState.Reconnecting:\n return 'reconnecting';\n case HubConnectionState.Disconnecting:\n return 'disconnecting';\n default:\n return 'disconnected';\n }\n }\n\n public async connect(): Promise<void> {\n if (this.connection.state === HubConnectionState.Connected) {\n return;\n }\n if (!this.pendingConnect) {\n this.pendingConnect = (async () => {\n this.notifyStateChange('connecting');\n await this.connection.start();\n this.notifyStateChange('connected');\n })().finally(() => {\n this.pendingConnect = null;\n });\n }\n return this.pendingConnect;\n }\n\n public async disconnect(): Promise<void> {\n if (this.connection.state === HubConnectionState.Disconnected) {\n return;\n }\n this.notifyStateChange('disconnecting');\n await this.connection.stop();\n this.subscribedRooms.clear();\n this.notifyStateChange('disconnected');\n }\n\n public room(roomId: string): DotwireRoomHandle {\n const handle = new DotwireRoomHandle(roomId, this);\n this.activeRoomHandles.add(handle);\n return handle;\n }\n\n public releaseRoomHandle(handle: DotwireRoomHandle): void {\n this.activeRoomHandles.delete(handle);\n }\n\n public async subscribe(roomId: string): Promise<void> {\n if (this.connection.state !== HubConnectionState.Connected) {\n await this.connect();\n }\n await this.connection.invoke('Subscribe', roomId);\n this.subscribedRooms.add(roomId);\n this.getOrCreateRoomState(roomId).subscribed = true;\n }\n\n public async unsubscribe(roomId: string): Promise<void> {\n if (this.connection.state === HubConnectionState.Connected) {\n await this.connection.invoke('Unsubscribe', roomId);\n }\n this.subscribedRooms.delete(roomId);\n const state = this.roomState.get(roomId);\n if (state) state.subscribed = false;\n }\n\n public async sendTyping(roomId: string, isTyping: boolean): Promise<void> {\n if (this.connection.state !== HubConnectionState.Connected) {\n return;\n }\n await this.connection.invoke('Typing', roomId, isTyping);\n }\n\n public async startTyping(roomId: string): Promise<void> {\n return this.sendTyping(roomId, true);\n }\n\n public async stopTyping(roomId: string): Promise<void> {\n return this.sendTyping(roomId, false);\n }\n\n public async sendMessage(roomId: string, content: string, timeoutMs?: number): Promise<SendAck> {\n const timeout = timeoutMs ?? this.options.requestTimeoutMs ?? 15000;\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), timeout);\n\n try {\n let response: Response;\n if (this.options.sendUrl) {\n response = await fetch(this.options.sendUrl, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n credentials: 'include',\n body: JSON.stringify({ roomId, content }),\n signal: controller.signal\n });\n } else {\n const token = await this.getToken();\n response = await fetch(`${this.baseUrl}/rooms/${roomId}/messages`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${token}`\n },\n body: JSON.stringify({ content }),\n signal: controller.signal\n });\n }\n\n if (!response.ok) {\n throw await toDotwireClientError(response);\n }\n\n return (await response.json()) as SendAck;\n } finally {\n clearTimeout(timer);\n }\n }\n\n public async getHistory(roomId: string, options: GetHistoryOptions = {}): Promise<HistoryPage> {\n const token = await this.getToken();\n const params = new URLSearchParams();\n if (options.afterSeq !== undefined) params.set('afterSeq', options.afterSeq.toString());\n if (options.beforeSeq !== undefined) params.set('beforeSeq', options.beforeSeq.toString());\n if (options.limit !== undefined) params.set('limit', options.limit.toString());\n\n const qs = params.toString();\n const url = `${this.baseUrl}/rooms/${roomId}/messages${qs ? `?${qs}` : ''}`;\n const timeout = options.timeoutMs ?? this.options.requestTimeoutMs ?? 15000;\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), timeout);\n\n try {\n const response = await fetch(url, {\n method: 'GET',\n headers: {\n Authorization: `Bearer ${token}`\n },\n signal: controller.signal\n });\n\n if (!response.ok) {\n throw await toDotwireClientError(response);\n }\n\n const body = (await response.json()) as WireHistoryResponse;\n const messages: RoomMessage[] = body.messages.map((m) => ({\n roomId: body.roomId,\n seq: m.seq,\n senderId: m.senderId,\n time: m.time,\n content: m.content\n }));\n\n return { messages, hasMore: body.hasMore };\n } finally {\n clearTimeout(timer);\n }\n }\n\n public async getParticipants(roomId: string, timeoutMs?: number): Promise<string[]> {\n const token = await this.getToken();\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), timeoutMs ?? this.options.requestTimeoutMs ?? 15000);\n try {\n const response = await fetch(`${this.baseUrl}/rooms/${roomId}/participants`, {\n method: 'GET',\n headers: { Authorization: `Bearer ${token}` },\n signal: controller.signal\n });\n if (!response.ok) throw await toDotwireClientError(response);\n return (await response.json()) as string[];\n } finally {\n clearTimeout(timer);\n }\n }\n\n public onMessage(handler: (message: RoomMessage) => void): () => void {\n this.messageListeners.add(handler);\n return () => {\n this.messageListeners.delete(handler);\n };\n }\n\n public onSystemMessage(handler: (message: RoomMessage) => void): () => void {\n return this.onMessage((message) => {\n if (isSystemSender(message.senderId)) {\n handler(message);\n }\n });\n }\n\n public onUserMessage(handler: (message: RoomMessage) => void): () => void {\n return this.onMessage((message) => {\n if (!isSystemSender(message.senderId)) {\n handler(message);\n }\n });\n }\n\n public onMessageRetracted(handler: (payload: MessageRetracted) => void): () => void {\n this.retractedListeners.add(handler);\n return () => {\n this.retractedListeners.delete(handler);\n };\n }\n\n public onMembershipRevoked(handler: (payload: MembershipRevoked) => void): () => void {\n this.revokedListeners.add(handler);\n return () => {\n this.revokedListeners.delete(handler);\n };\n }\n\n public onTyping(handler: (notification: TypingNotification) => void): () => void {\n this.typingListeners.add(handler);\n return () => {\n this.typingListeners.delete(handler);\n };\n }\n\n public onPresence(handler: (delta: PresenceDelta) => void): () => void {\n this.presenceListeners.add(handler);\n return () => {\n this.presenceListeners.delete(handler);\n };\n }\n\n public onStateChange(handler: (state: ConnectionState) => void): () => void {\n this.stateListeners.add(handler);\n return () => {\n this.stateListeners.delete(handler);\n };\n }\n\n public dispose(): void {\n if (this.unloadListener) {\n this.unloadListener();\n this.unloadListener = null;\n }\n for (const room of this.activeRoomHandles) {\n room.dispose();\n }\n this.activeRoomHandles.clear();\n this.disconnect().catch(() => {});\n }\n}\n\nfunction mapLogLevel(level?: DotwireClientOptions['logLevel']): LogLevel {\n switch (level) {\n case 'error':\n return LogLevel.Error;\n case 'warning':\n return LogLevel.Warning;\n case 'information':\n return LogLevel.Information;\n case 'debug':\n return LogLevel.Debug;\n case 'none':\n default:\n return LogLevel.None;\n }\n}\n","/**\n * Bounded, insertion-ordered set of recently-seen sequence numbers, used for per-room dedupe\n * (spec §6: \"a bounded seen-set of the last 1000 seqs\"). Backed by a `Set`, which preserves\n * insertion order in JS, so the oldest entry is always `values().next().value`.\n */\nexport class BoundedSeqSet {\n private readonly seen = new Set<number>();\n\n constructor(private readonly capacity = 1000) {}\n\n public has(seq: number): boolean {\n return this.seen.has(seq);\n }\n\n public add(seq: number): void {\n if (this.seen.has(seq)) return;\n this.seen.add(seq);\n if (this.seen.size > this.capacity) {\n const oldest = this.seen.values().next().value;\n if (oldest !== undefined) {\n this.seen.delete(oldest);\n }\n }\n }\n}\n","import type { DotwireClient } from './client.js';\nimport {\n isSystemSender,\n type GetHistoryOptions,\n type HistoryPage,\n type MembershipRevoked,\n type MessageRetracted,\n type PresenceDelta,\n type RoomMessage,\n type SendAck,\n type SubscribeOptions,\n type TypingNotification\n} from './types.js';\n\nexport class DotwireRoomHandle {\n private autoStopTypingTimer: ReturnType<typeof setTimeout> | null = null;\n private lastTypingSentAt = 0;\n private isCurrentlyTyping = false;\n private readonly listeners: (() => void)[] = [];\n\n constructor(\n public readonly roomId: string,\n private readonly client: DotwireClient\n ) {}\n\n /**\n * Subscribes on the hub and, when `history` is given, fetches and delivers that many of the\n * latest messages (spec §6) before returning them.\n */\n public async subscribe(options: SubscribeOptions = {}): Promise<HistoryPage | undefined> {\n await this.client.subscribe(this.roomId);\n\n if (options.history === undefined) {\n return undefined;\n }\n\n const page = await this.client.getHistory(this.roomId, { limit: options.history });\n for (const message of page.messages) {\n this.client.deliverMessage(message);\n }\n return page;\n }\n\n public async unsubscribe(): Promise<void> {\n this.clearTypingTimer();\n return this.client.unsubscribe(this.roomId);\n }\n\n public async sendMessage(content: string, timeoutMs?: number): Promise<SendAck> {\n return this.client.sendMessage(this.roomId, content, timeoutMs);\n }\n\n public async getHistory(options: GetHistoryOptions = {}): Promise<HistoryPage> {\n return this.client.getHistory(this.roomId, options);\n }\n\n public async getParticipants(timeoutMs?: number): Promise<string[]> {\n return this.client.getParticipants(this.roomId, timeoutMs);\n }\n\n public async sendTyping(isTyping: boolean): Promise<void> {\n this.clearTypingTimer();\n this.isCurrentlyTyping = isTyping;\n if (isTyping) {\n this.lastTypingSentAt = Date.now();\n const autoStopMs = this.client.options.autoStopTypingMs ?? 3000;\n this.autoStopTypingTimer = setTimeout(() => {\n this.stopTyping().catch(() => {});\n }, autoStopMs);\n }\n return this.client.sendTyping(this.roomId, isTyping);\n }\n\n public async startTyping(): Promise<void> {\n return this.sendTyping(true);\n }\n\n public async stopTyping(): Promise<void> {\n this.clearTypingTimer();\n if (this.isCurrentlyTyping) {\n this.isCurrentlyTyping = false;\n return this.client.sendTyping(this.roomId, false);\n }\n }\n\n public async notifyTyping(): Promise<void> {\n const now = Date.now();\n const throttleMs = this.client.options.typingThrottleMs ?? 1000;\n const autoStopMs = this.client.options.autoStopTypingMs ?? 3000;\n\n this.clearTypingTimer();\n this.autoStopTypingTimer = setTimeout(() => {\n this.stopTyping().catch(() => {});\n }, autoStopMs);\n\n if (!this.isCurrentlyTyping || now - this.lastTypingSentAt >= throttleMs) {\n this.isCurrentlyTyping = true;\n this.lastTypingSentAt = now;\n await this.client.sendTyping(this.roomId, true);\n }\n }\n\n private clearTypingTimer(): void {\n if (this.autoStopTypingTimer) {\n clearTimeout(this.autoStopTypingTimer);\n this.autoStopTypingTimer = null;\n }\n }\n\n public onMessage(handler: (message: RoomMessage) => void): () => void {\n const unsub = this.client.onMessage((message) => {\n if (message.roomId === this.roomId) {\n handler(message);\n }\n });\n this.listeners.push(unsub);\n return unsub;\n }\n\n public onSystemMessage(handler: (message: RoomMessage) => void): () => void {\n const unsub = this.client.onMessage((message) => {\n if (message.roomId === this.roomId && isSystemSender(message.senderId)) {\n handler(message);\n }\n });\n this.listeners.push(unsub);\n return unsub;\n }\n\n public onUserMessage(handler: (message: RoomMessage) => void): () => void {\n const unsub = this.client.onMessage((message) => {\n if (message.roomId === this.roomId && !isSystemSender(message.senderId)) {\n handler(message);\n }\n });\n this.listeners.push(unsub);\n return unsub;\n }\n\n public onMessageRetracted(handler: (payload: MessageRetracted) => void): () => void {\n const unsub = this.client.onMessageRetracted((payload) => {\n if (payload.roomId === this.roomId) {\n handler(payload);\n }\n });\n this.listeners.push(unsub);\n return unsub;\n }\n\n public onMembershipRevoked(handler: (payload: MembershipRevoked) => void): () => void {\n const unsub = this.client.onMembershipRevoked((payload) => {\n if (payload.roomId === this.roomId) {\n handler(payload);\n }\n });\n this.listeners.push(unsub);\n return unsub;\n }\n\n public onTyping(handler: (notification: TypingNotification) => void): () => void {\n const unsub = this.client.onTyping((notification) => {\n if (notification.roomId === this.roomId) {\n handler(notification);\n }\n });\n this.listeners.push(unsub);\n return unsub;\n }\n\n public onTypingWithExpiry(\n handler: (notification: TypingNotification) => void,\n autoExpireMs = 3500\n ): () => void {\n const expiryTimers = new Map<string, ReturnType<typeof setTimeout>>();\n\n const unsub = this.client.onTyping((notification) => {\n if (notification.roomId !== this.roomId) return;\n\n const existing = expiryTimers.get(notification.userId);\n if (existing) {\n clearTimeout(existing);\n expiryTimers.delete(notification.userId);\n }\n\n handler(notification);\n\n if (notification.isTyping) {\n const timer = setTimeout(() => {\n expiryTimers.delete(notification.userId);\n handler({\n roomId: this.roomId,\n userId: notification.userId,\n isTyping: false\n });\n }, autoExpireMs);\n expiryTimers.set(notification.userId, timer);\n }\n });\n\n const cleanup = () => {\n unsub();\n for (const timer of expiryTimers.values()) {\n clearTimeout(timer);\n }\n expiryTimers.clear();\n };\n\n this.listeners.push(cleanup);\n return cleanup;\n }\n\n public onPresence(handler: (delta: PresenceDelta) => void): () => void {\n const unsub = this.client.onPresence((delta) => {\n if (delta.roomId === this.roomId) {\n handler(delta);\n }\n });\n this.listeners.push(unsub);\n return unsub;\n }\n\n public release(): void {\n this.clearTypingTimer();\n for (const cleanup of this.listeners) {\n cleanup();\n }\n this.listeners.length = 0;\n this.client.releaseRoomHandle(this);\n }\n\n public dispose(): void {\n this.release();\n this.unsubscribe().catch(() => {});\n }\n}\n","import { toDotwireClientError } from './errors.js';\n\ninterface TokenResponseBody {\n token: string;\n}\n\n/**\n * Fetches `tokenUrl`, decodes `exp` from the JWT payload without verifying (this is only used\n * to schedule refresh, never for trust), caches the token, and refetches `marginMs` before\n * expiry (spec §6). Concurrent callers during a refresh share the same in-flight fetch.\n */\nexport class TokenManager {\n private cachedToken: string | null = null;\n private cachedExpMs: number | null = null;\n private pending: Promise<string> | null = null;\n\n constructor(\n private readonly tokenUrl: string,\n private readonly marginMs: number,\n private readonly fetchImpl: typeof fetch = (input, init) => fetch(input, init)\n ) {}\n\n public async getToken(): Promise<string> {\n if (this.cachedToken !== null && !this.isExpiringSoon()) {\n return this.cachedToken;\n }\n if (!this.pending) {\n this.pending = this.fetchAndCache().finally(() => {\n this.pending = null;\n });\n }\n return this.pending;\n }\n\n private isExpiringSoon(): boolean {\n if (this.cachedExpMs === null) return false;\n return Date.now() >= this.cachedExpMs - this.marginMs;\n }\n\n private async fetchAndCache(): Promise<string> {\n const response = await this.fetchImpl(this.tokenUrl, { credentials: 'include' });\n if (!response.ok) {\n throw await toDotwireClientError(response);\n }\n const body = (await response.json()) as TokenResponseBody;\n this.cachedToken = body.token;\n this.cachedExpMs = decodeExpMs(body.token);\n return this.cachedToken;\n }\n}\n\n/** Base64url-decodes the JWT payload segment and reads `exp`, in milliseconds. No signature check. */\nexport function decodeExpMs(token: string): number | null {\n const parts = token.split('.');\n if (parts.length < 2) return null;\n try {\n const payload = JSON.parse(base64UrlDecode(parts[1])) as Record<string, unknown>;\n return typeof payload.exp === 'number' ? payload.exp * 1000 : null;\n } catch {\n return null;\n }\n}\n\nfunction base64UrlDecode(segment: string): string {\n const base64 = segment.replace(/-/g, '+').replace(/_/g, '/');\n const padded = base64.padEnd(base64.length + ((4 - (base64.length % 4)) % 4), '=');\n if (typeof Buffer !== 'undefined') {\n return Buffer.from(padded, 'base64').toString('utf-8');\n }\n const binary = atob(padded);\n const bytes = Uint8Array.from(binary, (c) => c.charCodeAt(0));\n return new TextDecoder('utf-8').decode(bytes);\n}\n"],"mappings":";AAmGO,SAAS,eAAe,UAA2B;AACxD,MAAI,CAAC,SAAU,QAAO;AACtB,QAAM,QAAQ,SAAS,YAAY;AACnC,SAAO,UAAU,YAAY,MAAM,WAAW,SAAS,KAAK,MAAM,WAAW,MAAM;AACrF;;;ACjFO,IAAM,qBAAN,cAAiC,MAAM;AAAA,EAC5B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EAEhB,YAAY,MAA8B;AACxC,UAAM,KAAK,UAAU,KAAK,QAAQ,sCAAsC,KAAK,MAAM,EAAE;AACrF,SAAK,OAAO;AACZ,SAAK,SAAS,KAAK;AACnB,SAAK,OAAO,KAAK;AACjB,SAAK,OAAO,KAAK;AACjB,SAAK,SAAS,KAAK;AACnB,SAAK,eAAe,KAAK;AAAA,EAC3B;AACF;AAOA,eAAsB,qBAAqB,UAAiD;AAC1F,QAAM,SAAS,SAAS;AACxB,MAAI;AACJ,MAAI;AAEJ,MAAI;AACF,UAAM,OAAgB,MAAM,SAAS,KAAK;AAC1C,QAAI,QAAQ,OAAO,SAAS,UAAU;AACpC,YAAM,SAAS;AACf,UAAI,OAAO,OAAO,UAAU,SAAU,QAAO,OAAO;AACpD,UAAI,OAAO,OAAO,WAAW,SAAU,UAAS,OAAO;AAAA,IACzD;AAAA,EACF,QAAQ;AAAA,EAER;AAEA,MAAI;AACJ,QAAM,mBAAmB,SAAS,SAAS,MAAM,aAAa;AAC9D,MAAI,kBAAkB;AACpB,UAAM,UAAU,OAAO,gBAAgB;AACvC,QAAI,CAAC,OAAO,MAAM,OAAO,GAAG;AAC1B,qBAAe,UAAU;AAAA,IAC3B;AAAA,EACF;AAEA,SAAO,IAAI,mBAAmB,EAAE,QAAQ,MAAM,aAAa,QAAQ,IAAI,GAAG,MAAM,QAAQ,aAAa,CAAC;AACxG;AAEA,SAAS,aAAa,QAAgB,MAAiC;AACrE,UAAQ,QAAQ;AAAA,IACd,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO,SAAS,qBAAqB,oBAAoB;AAAA,IAC3D,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT;AACE,aAAO;AAAA,EACX;AACF;;;AC5FA;AAAA,EACE;AAAA,EAEA;AAAA,EACA;AAAA,EAEA;AAAA,OACK;;;ACFA,IAAM,gBAAN,MAAoB;AAAA,EAGzB,YAA6B,WAAW,KAAM;AAAjB;AAAA,EAAkB;AAAA,EAAlB;AAAA,EAFZ,OAAO,oBAAI,IAAY;AAAA,EAIjC,IAAI,KAAsB;AAC/B,WAAO,KAAK,KAAK,IAAI,GAAG;AAAA,EAC1B;AAAA,EAEO,IAAI,KAAmB;AAC5B,QAAI,KAAK,KAAK,IAAI,GAAG,EAAG;AACxB,SAAK,KAAK,IAAI,GAAG;AACjB,QAAI,KAAK,KAAK,OAAO,KAAK,UAAU;AAClC,YAAM,SAAS,KAAK,KAAK,OAAO,EAAE,KAAK,EAAE;AACzC,UAAI,WAAW,QAAW;AACxB,aAAK,KAAK,OAAO,MAAM;AAAA,MACzB;AAAA,IACF;AAAA,EACF;AACF;;;ACVO,IAAM,oBAAN,MAAwB;AAAA,EAM7B,YACkB,QACC,QACjB;AAFgB;AACC;AAAA,EAChB;AAAA,EAFe;AAAA,EACC;AAAA,EAPX,sBAA4D;AAAA,EAC5D,mBAAmB;AAAA,EACnB,oBAAoB;AAAA,EACX,YAA4B,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA,EAW9C,MAAa,UAAU,UAA4B,CAAC,GAAqC;AACvF,UAAM,KAAK,OAAO,UAAU,KAAK,MAAM;AAEvC,QAAI,QAAQ,YAAY,QAAW;AACjC,aAAO;AAAA,IACT;AAEA,UAAM,OAAO,MAAM,KAAK,OAAO,WAAW,KAAK,QAAQ,EAAE,OAAO,QAAQ,QAAQ,CAAC;AACjF,eAAW,WAAW,KAAK,UAAU;AACnC,WAAK,OAAO,eAAe,OAAO;AAAA,IACpC;AACA,WAAO;AAAA,EACT;AAAA,EAEA,MAAa,cAA6B;AACxC,SAAK,iBAAiB;AACtB,WAAO,KAAK,OAAO,YAAY,KAAK,MAAM;AAAA,EAC5C;AAAA,EAEA,MAAa,YAAY,SAAiB,WAAsC;AAC9E,WAAO,KAAK,OAAO,YAAY,KAAK,QAAQ,SAAS,SAAS;AAAA,EAChE;AAAA,EAEA,MAAa,WAAW,UAA6B,CAAC,GAAyB;AAC7E,WAAO,KAAK,OAAO,WAAW,KAAK,QAAQ,OAAO;AAAA,EACpD;AAAA,EAEA,MAAa,gBAAgB,WAAuC;AAClE,WAAO,KAAK,OAAO,gBAAgB,KAAK,QAAQ,SAAS;AAAA,EAC3D;AAAA,EAEA,MAAa,WAAW,UAAkC;AACxD,SAAK,iBAAiB;AACtB,SAAK,oBAAoB;AACzB,QAAI,UAAU;AACZ,WAAK,mBAAmB,KAAK,IAAI;AACjC,YAAM,aAAa,KAAK,OAAO,QAAQ,oBAAoB;AAC3D,WAAK,sBAAsB,WAAW,MAAM;AAC1C,aAAK,WAAW,EAAE,MAAM,MAAM;AAAA,QAAC,CAAC;AAAA,MAClC,GAAG,UAAU;AAAA,IACf;AACA,WAAO,KAAK,OAAO,WAAW,KAAK,QAAQ,QAAQ;AAAA,EACrD;AAAA,EAEA,MAAa,cAA6B;AACxC,WAAO,KAAK,WAAW,IAAI;AAAA,EAC7B;AAAA,EAEA,MAAa,aAA4B;AACvC,SAAK,iBAAiB;AACtB,QAAI,KAAK,mBAAmB;AAC1B,WAAK,oBAAoB;AACzB,aAAO,KAAK,OAAO,WAAW,KAAK,QAAQ,KAAK;AAAA,IAClD;AAAA,EACF;AAAA,EAEA,MAAa,eAA8B;AACzC,UAAM,MAAM,KAAK,IAAI;AACrB,UAAM,aAAa,KAAK,OAAO,QAAQ,oBAAoB;AAC3D,UAAM,aAAa,KAAK,OAAO,QAAQ,oBAAoB;AAE3D,SAAK,iBAAiB;AACtB,SAAK,sBAAsB,WAAW,MAAM;AAC1C,WAAK,WAAW,EAAE,MAAM,MAAM;AAAA,MAAC,CAAC;AAAA,IAClC,GAAG,UAAU;AAEb,QAAI,CAAC,KAAK,qBAAqB,MAAM,KAAK,oBAAoB,YAAY;AACxE,WAAK,oBAAoB;AACzB,WAAK,mBAAmB;AACxB,YAAM,KAAK,OAAO,WAAW,KAAK,QAAQ,IAAI;AAAA,IAChD;AAAA,EACF;AAAA,EAEQ,mBAAyB;AAC/B,QAAI,KAAK,qBAAqB;AAC5B,mBAAa,KAAK,mBAAmB;AACrC,WAAK,sBAAsB;AAAA,IAC7B;AAAA,EACF;AAAA,EAEO,UAAU,SAAqD;AACpE,UAAM,QAAQ,KAAK,OAAO,UAAU,CAAC,YAAY;AAC/C,UAAI,QAAQ,WAAW,KAAK,QAAQ;AAClC,gBAAQ,OAAO;AAAA,MACjB;AAAA,IACF,CAAC;AACD,SAAK,UAAU,KAAK,KAAK;AACzB,WAAO;AAAA,EACT;AAAA,EAEO,gBAAgB,SAAqD;AAC1E,UAAM,QAAQ,KAAK,OAAO,UAAU,CAAC,YAAY;AAC/C,UAAI,QAAQ,WAAW,KAAK,UAAU,eAAe,QAAQ,QAAQ,GAAG;AACtE,gBAAQ,OAAO;AAAA,MACjB;AAAA,IACF,CAAC;AACD,SAAK,UAAU,KAAK,KAAK;AACzB,WAAO;AAAA,EACT;AAAA,EAEO,cAAc,SAAqD;AACxE,UAAM,QAAQ,KAAK,OAAO,UAAU,CAAC,YAAY;AAC/C,UAAI,QAAQ,WAAW,KAAK,UAAU,CAAC,eAAe,QAAQ,QAAQ,GAAG;AACvE,gBAAQ,OAAO;AAAA,MACjB;AAAA,IACF,CAAC;AACD,SAAK,UAAU,KAAK,KAAK;AACzB,WAAO;AAAA,EACT;AAAA,EAEO,mBAAmB,SAA0D;AAClF,UAAM,QAAQ,KAAK,OAAO,mBAAmB,CAAC,YAAY;AACxD,UAAI,QAAQ,WAAW,KAAK,QAAQ;AAClC,gBAAQ,OAAO;AAAA,MACjB;AAAA,IACF,CAAC;AACD,SAAK,UAAU,KAAK,KAAK;AACzB,WAAO;AAAA,EACT;AAAA,EAEO,oBAAoB,SAA2D;AACpF,UAAM,QAAQ,KAAK,OAAO,oBAAoB,CAAC,YAAY;AACzD,UAAI,QAAQ,WAAW,KAAK,QAAQ;AAClC,gBAAQ,OAAO;AAAA,MACjB;AAAA,IACF,CAAC;AACD,SAAK,UAAU,KAAK,KAAK;AACzB,WAAO;AAAA,EACT;AAAA,EAEO,SAAS,SAAiE;AAC/E,UAAM,QAAQ,KAAK,OAAO,SAAS,CAAC,iBAAiB;AACnD,UAAI,aAAa,WAAW,KAAK,QAAQ;AACvC,gBAAQ,YAAY;AAAA,MACtB;AAAA,IACF,CAAC;AACD,SAAK,UAAU,KAAK,KAAK;AACzB,WAAO;AAAA,EACT;AAAA,EAEO,mBACL,SACA,eAAe,MACH;AACZ,UAAM,eAAe,oBAAI,IAA2C;AAEpE,UAAM,QAAQ,KAAK,OAAO,SAAS,CAAC,iBAAiB;AACnD,UAAI,aAAa,WAAW,KAAK,OAAQ;AAEzC,YAAM,WAAW,aAAa,IAAI,aAAa,MAAM;AACrD,UAAI,UAAU;AACZ,qBAAa,QAAQ;AACrB,qBAAa,OAAO,aAAa,MAAM;AAAA,MACzC;AAEA,cAAQ,YAAY;AAEpB,UAAI,aAAa,UAAU;AACzB,cAAM,QAAQ,WAAW,MAAM;AAC7B,uBAAa,OAAO,aAAa,MAAM;AACvC,kBAAQ;AAAA,YACN,QAAQ,KAAK;AAAA,YACb,QAAQ,aAAa;AAAA,YACrB,UAAU;AAAA,UACZ,CAAC;AAAA,QACH,GAAG,YAAY;AACf,qBAAa,IAAI,aAAa,QAAQ,KAAK;AAAA,MAC7C;AAAA,IACF,CAAC;AAED,UAAM,UAAU,MAAM;AACpB,YAAM;AACN,iBAAW,SAAS,aAAa,OAAO,GAAG;AACzC,qBAAa,KAAK;AAAA,MACpB;AACA,mBAAa,MAAM;AAAA,IACrB;AAEA,SAAK,UAAU,KAAK,OAAO;AAC3B,WAAO;AAAA,EACT;AAAA,EAEO,WAAW,SAAqD;AACrE,UAAM,QAAQ,KAAK,OAAO,WAAW,CAAC,UAAU;AAC9C,UAAI,MAAM,WAAW,KAAK,QAAQ;AAChC,gBAAQ,KAAK;AAAA,MACf;AAAA,IACF,CAAC;AACD,SAAK,UAAU,KAAK,KAAK;AACzB,WAAO;AAAA,EACT;AAAA,EAEO,UAAgB;AACrB,SAAK,iBAAiB;AACtB,eAAW,WAAW,KAAK,WAAW;AACpC,cAAQ;AAAA,IACV;AACA,SAAK,UAAU,SAAS;AACxB,SAAK,OAAO,kBAAkB,IAAI;AAAA,EACpC;AAAA,EAEO,UAAgB;AACrB,SAAK,QAAQ;AACb,SAAK,YAAY,EAAE,MAAM,MAAM;AAAA,IAAC,CAAC;AAAA,EACnC;AACF;;;AC/NO,IAAM,eAAN,MAAmB;AAAA,EAKxB,YACmB,UACA,UACA,YAA0B,CAAC,OAAO,SAAS,MAAM,OAAO,IAAI,GAC7E;AAHiB;AACA;AACA;AAAA,EAChB;AAAA,EAHgB;AAAA,EACA;AAAA,EACA;AAAA,EAPX,cAA6B;AAAA,EAC7B,cAA6B;AAAA,EAC7B,UAAkC;AAAA,EAQ1C,MAAa,WAA4B;AACvC,QAAI,KAAK,gBAAgB,QAAQ,CAAC,KAAK,eAAe,GAAG;AACvD,aAAO,KAAK;AAAA,IACd;AACA,QAAI,CAAC,KAAK,SAAS;AACjB,WAAK,UAAU,KAAK,cAAc,EAAE,QAAQ,MAAM;AAChD,aAAK,UAAU;AAAA,MACjB,CAAC;AAAA,IACH;AACA,WAAO,KAAK;AAAA,EACd;AAAA,EAEQ,iBAA0B;AAChC,QAAI,KAAK,gBAAgB,KAAM,QAAO;AACtC,WAAO,KAAK,IAAI,KAAK,KAAK,cAAc,KAAK;AAAA,EAC/C;AAAA,EAEA,MAAc,gBAAiC;AAC7C,UAAM,WAAW,MAAM,KAAK,UAAU,KAAK,UAAU,EAAE,aAAa,UAAU,CAAC;AAC/E,QAAI,CAAC,SAAS,IAAI;AAChB,YAAM,MAAM,qBAAqB,QAAQ;AAAA,IAC3C;AACA,UAAM,OAAQ,MAAM,SAAS,KAAK;AAClC,SAAK,cAAc,KAAK;AACxB,SAAK,cAAc,YAAY,KAAK,KAAK;AACzC,WAAO,KAAK;AAAA,EACd;AACF;AAGO,SAAS,YAAY,OAA8B;AACxD,QAAM,QAAQ,MAAM,MAAM,GAAG;AAC7B,MAAI,MAAM,SAAS,EAAG,QAAO;AAC7B,MAAI;AACF,UAAM,UAAU,KAAK,MAAM,gBAAgB,MAAM,CAAC,CAAC,CAAC;AACpD,WAAO,OAAO,QAAQ,QAAQ,WAAW,QAAQ,MAAM,MAAO;AAAA,EAChE,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,SAAS,gBAAgB,SAAyB;AAChD,QAAM,SAAS,QAAQ,QAAQ,MAAM,GAAG,EAAE,QAAQ,MAAM,GAAG;AAC3D,QAAM,SAAS,OAAO,OAAO,OAAO,UAAW,IAAK,OAAO,SAAS,KAAM,GAAI,GAAG;AACjF,MAAI,OAAO,WAAW,aAAa;AACjC,WAAO,OAAO,KAAK,QAAQ,QAAQ,EAAE,SAAS,OAAO;AAAA,EACvD;AACA,QAAM,SAAS,KAAK,MAAM;AAC1B,QAAM,QAAQ,WAAW,KAAK,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;AAC5D,SAAO,IAAI,YAAY,OAAO,EAAE,OAAO,KAAK;AAC9C;;;AH3BO,IAAM,gBAAN,MAAoB;AAAA,EAiBzB,YAA4B,SAA+B;AAA/B;AAC1B,QAAI,CAAC,QAAQ,YAAY,CAAC,QAAQ,gBAAgB;AAChD,YAAM,IAAI,MAAM,+DAA+D;AAAA,IACjF;AAEA,SAAK,UAAU,QAAQ,QAAQ,QAAQ,OAAO,EAAE;AAChD,SAAK,UAAU,QAAQ,WAAW;AAElC,QAAI,QAAQ,gBAAgB;AAC1B,WAAK,WAAW,QAAQ;AAAA,IAC1B,OAAO;AACL,YAAM,eAAe,IAAI,aAAa,QAAQ,UAAW,QAAQ,wBAAwB,GAAK;AAC9F,WAAK,WAAW,MAAM,aAAa,SAAS;AAAA,IAC9C;AAEA,SAAK,aAAa,KAAK,gBAAgB;AACvC,SAAK,oBAAoB;AACzB,SAAK,sBAAsB;AAAA,EAC7B;AAAA,EAlB4B;AAAA,EAhBX;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,mBAAmB,oBAAI,IAAoC;AAAA,EAC3D,qBAAqB,oBAAI,IAAyC;AAAA,EAClE,mBAAmB,oBAAI,IAA0C;AAAA,EACjE,kBAAkB,oBAAI,IAAgD;AAAA,EACtE,oBAAoB,oBAAI,IAAoC;AAAA,EAC5D,iBAAiB,oBAAI,IAAsC;AAAA,EAC3D,kBAAkB,oBAAI,IAAY;AAAA,EAClC,YAAY,oBAAI,IAAuB;AAAA,EACvC,oBAAoB,oBAAI,IAAuB;AAAA,EACxD,iBAAsC;AAAA,EACtC,iBAAuC;AAAA,EAsBvC,kBAAiC;AACvC,UAAM,MAAM,GAAG,KAAK,OAAO,GAAG,KAAK,OAAO;AAC1C,UAAM,cAAsC;AAAA,MAC1C,oBAAoB,MAAM,KAAK,SAAS;AAAA,IAC1C;AAEA,SAAK,KAAK,QAAQ,aAAa,kBAAkB,cAAc;AAC7D,kBAAY,YAAY,kBAAkB;AAC1C,kBAAY,kBAAkB;AAAA,IAChC;AAEA,QAAI,KAAK,QAAQ,mBAAmB;AAClC,aAAO,KAAK,QAAQ,kBAAkB,KAAK,WAAW;AAAA,IACxD;AAEA,UAAM,UAAU,IAAI,qBAAqB,EACtC,QAAQ,KAAK,WAAW,EACxB,iBAAiB,YAAY,KAAK,QAAQ,QAAQ,CAAC;AAEtD,QAAI,KAAK,QAAQ,uBAAuB,OAAO;AAC7C,cAAQ,uBAAuB;AAAA,IACjC;AAEA,UAAM,aAAa,QAAQ,MAAM;AACjC,eAAW,8BAA8B,KAAK,QAAQ,mBAAmB;AACzE,eAAW,kCAAkC,KAAK,QAAQ,uBAAuB;AACjF,WAAO;AAAA,EACT;AAAA,EAEQ,sBAA4B;AAClC,SAAK,WAAW,GAAG,kBAAkB,CAAC,YAAyB;AAC7D,WAAK,eAAe,OAAO;AAAA,IAC7B,CAAC;AAED,SAAK,WAAW,GAAG,cAAc,CAAC,iBAAqC;AACrE,iBAAW,YAAY,KAAK,iBAAiB;AAC3C,iBAAS,YAAY;AAAA,MACvB;AAAA,IACF,CAAC;AAED,SAAK,WAAW,GAAG,mBAAmB,CAAC,UAAyB;AAC9D,iBAAW,YAAY,KAAK,mBAAmB;AAC7C,iBAAS,KAAK;AAAA,MAChB;AAAA,IACF,CAAC;AAED,SAAK,WAAW,GAAG,oBAAoB,CAAC,YAA8B;AACpE,iBAAW,YAAY,KAAK,oBAAoB;AAC9C,iBAAS,OAAO;AAAA,MAClB;AAAA,IACF,CAAC;AAED,SAAK,WAAW,GAAG,qBAAqB,CAAC,YAA+B;AACtE,WAAK,gBAAgB,OAAO,QAAQ,MAAM;AAC1C,YAAM,QAAQ,KAAK,UAAU,IAAI,QAAQ,MAAM;AAC/C,UAAI,MAAO,OAAM,aAAa;AAC9B,iBAAW,YAAY,KAAK,kBAAkB;AAC5C,iBAAS,OAAO;AAAA,MAClB;AAAA,IACF,CAAC;AAED,SAAK,WAAW,QAAQ,MAAM;AAC5B,WAAK,kBAAkB,cAAc;AAAA,IACvC,CAAC;AAED,SAAK,WAAW,eAAe,MAAM;AACnC,WAAK,kBAAkB,cAAc;AAAA,IACvC,CAAC;AAED,SAAK,WAAW,cAAc,YAAY;AACxC,WAAK,kBAAkB,WAAW;AAClC,iBAAW,UAAU,MAAM,KAAK,KAAK,eAAe,GAAG;AACrD,cAAM,aAAa,KAAK,UAAU,IAAI,MAAM,GAAG,WAAW;AAC1D,YAAI;AACF,gBAAM,KAAK,WAAW,OAAO,aAAa,MAAM;AAAA,QAClD,QAAQ;AACN;AAAA,QACF;AACA,YAAI,KAAK,QAAQ,uBAAuB,SAAS,eAAe,KAAM;AACtE,YAAI;AACF,gBAAM,KAAK,QAAQ,QAAQ,UAAU;AAAA,QACvC,QAAQ;AAAA,QAER;AAAA,MACF;AAAA,IACF,CAAC;AAAA,EACH;AAAA,EAEQ,wBAA8B;AACpC,QAAI,OAAO,WAAW,eAAe,OAAO,OAAO,qBAAqB,YAAY;AAClF,YAAM,WAAW,MAAM;AACrB,aAAK,WAAW,EAAE,MAAM,MAAM;AAAA,QAAC,CAAC;AAAA,MAClC;AACA,aAAO,iBAAiB,gBAAgB,QAAQ;AAChD,aAAO,iBAAiB,YAAY,QAAQ;AAC5C,WAAK,iBAAiB,MAAM;AAC1B,eAAO,oBAAoB,gBAAgB,QAAQ;AACnD,eAAO,oBAAoB,YAAY,QAAQ;AAAA,MACjD;AAAA,IACF;AAAA,EACF;AAAA,EAEQ,kBAAkB,OAA8B;AACtD,eAAW,YAAY,KAAK,gBAAgB;AAC1C,eAAS,KAAK;AAAA,IAChB;AAAA,EACF;AAAA,EAEQ,qBAAqB,QAA2B;AACtD,QAAI,QAAQ,KAAK,UAAU,IAAI,MAAM;AACrC,QAAI,CAAC,OAAO;AACV,cAAQ,EAAE,YAAY,OAAO,SAAS,MAAM,MAAM,IAAI,cAAc,GAAI,EAAE;AAC1E,WAAK,UAAU,IAAI,QAAQ,KAAK;AAAA,IAClC;AACA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA,EAMO,eAAe,SAA4B;AAChD,UAAM,QAAQ,KAAK,qBAAqB,QAAQ,MAAM;AACtD,QAAI,MAAM,KAAK,IAAI,QAAQ,GAAG,EAAG;AACjC,UAAM,KAAK,IAAI,QAAQ,GAAG;AAC1B,QAAI,MAAM,YAAY,QAAQ,QAAQ,MAAM,MAAM,SAAS;AACzD,YAAM,UAAU,QAAQ;AAAA,IAC1B;AACA,eAAW,YAAY,KAAK,kBAAkB;AAC5C,eAAS,OAAO;AAAA,IAClB;AAAA,EACF;AAAA,EAEA,MAAc,QAAQ,QAAgB,SAAgC;AACpE,UAAM,WAAW,KAAK,QAAQ,mBAAmB;AACjD,QAAI,WAAW;AACf,QAAI,UAAU;AAEd,WAAO,SAAS;AACd,YAAM,OAAO,MAAM,KAAK,WAAW,QAAQ,EAAE,UAAU,OAAO,SAAS,CAAC;AACxE,UAAI,KAAK,SAAS,WAAW,EAAG;AAEhC,iBAAW,WAAW,KAAK,UAAU;AACnC,aAAK,eAAe,EAAE,GAAG,SAAS,UAAU,KAAK,CAAC;AAClD,mBAAW,KAAK,IAAI,UAAU,QAAQ,GAAG;AAAA,MAC3C;AAEA,gBAAU,KAAK;AAAA,IACjB;AAAA,EACF;AAAA,EAEO,qBAAsC;AAC3C,YAAQ,KAAK,WAAW,OAAO;AAAA,MAC7B,KAAK,mBAAmB;AACtB,eAAO;AAAA,MACT,KAAK,mBAAmB;AACtB,eAAO;AAAA,MACT,KAAK,mBAAmB;AACtB,eAAO;AAAA,MACT,KAAK,mBAAmB;AACtB,eAAO;AAAA,MACT;AACE,eAAO;AAAA,IACX;AAAA,EACF;AAAA,EAEA,MAAa,UAAyB;AACpC,QAAI,KAAK,WAAW,UAAU,mBAAmB,WAAW;AAC1D;AAAA,IACF;AACA,QAAI,CAAC,KAAK,gBAAgB;AACxB,WAAK,kBAAkB,YAAY;AACjC,aAAK,kBAAkB,YAAY;AACnC,cAAM,KAAK,WAAW,MAAM;AAC5B,aAAK,kBAAkB,WAAW;AAAA,MACpC,GAAG,EAAE,QAAQ,MAAM;AACjB,aAAK,iBAAiB;AAAA,MACxB,CAAC;AAAA,IACH;AACA,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,MAAa,aAA4B;AACvC,QAAI,KAAK,WAAW,UAAU,mBAAmB,cAAc;AAC7D;AAAA,IACF;AACA,SAAK,kBAAkB,eAAe;AACtC,UAAM,KAAK,WAAW,KAAK;AAC3B,SAAK,gBAAgB,MAAM;AAC3B,SAAK,kBAAkB,cAAc;AAAA,EACvC;AAAA,EAEO,KAAK,QAAmC;AAC7C,UAAM,SAAS,IAAI,kBAAkB,QAAQ,IAAI;AACjD,SAAK,kBAAkB,IAAI,MAAM;AACjC,WAAO;AAAA,EACT;AAAA,EAEO,kBAAkB,QAAiC;AACxD,SAAK,kBAAkB,OAAO,MAAM;AAAA,EACtC;AAAA,EAEA,MAAa,UAAU,QAA+B;AACpD,QAAI,KAAK,WAAW,UAAU,mBAAmB,WAAW;AAC1D,YAAM,KAAK,QAAQ;AAAA,IACrB;AACA,UAAM,KAAK,WAAW,OAAO,aAAa,MAAM;AAChD,SAAK,gBAAgB,IAAI,MAAM;AAC/B,SAAK,qBAAqB,MAAM,EAAE,aAAa;AAAA,EACjD;AAAA,EAEA,MAAa,YAAY,QAA+B;AACtD,QAAI,KAAK,WAAW,UAAU,mBAAmB,WAAW;AAC1D,YAAM,KAAK,WAAW,OAAO,eAAe,MAAM;AAAA,IACpD;AACA,SAAK,gBAAgB,OAAO,MAAM;AAClC,UAAM,QAAQ,KAAK,UAAU,IAAI,MAAM;AACvC,QAAI,MAAO,OAAM,aAAa;AAAA,EAChC;AAAA,EAEA,MAAa,WAAW,QAAgB,UAAkC;AACxE,QAAI,KAAK,WAAW,UAAU,mBAAmB,WAAW;AAC1D;AAAA,IACF;AACA,UAAM,KAAK,WAAW,OAAO,UAAU,QAAQ,QAAQ;AAAA,EACzD;AAAA,EAEA,MAAa,YAAY,QAA+B;AACtD,WAAO,KAAK,WAAW,QAAQ,IAAI;AAAA,EACrC;AAAA,EAEA,MAAa,WAAW,QAA+B;AACrD,WAAO,KAAK,WAAW,QAAQ,KAAK;AAAA,EACtC;AAAA,EAEA,MAAa,YAAY,QAAgB,SAAiB,WAAsC;AAC9F,UAAM,UAAU,aAAa,KAAK,QAAQ,oBAAoB;AAC9D,UAAM,aAAa,IAAI,gBAAgB;AACvC,UAAM,QAAQ,WAAW,MAAM,WAAW,MAAM,GAAG,OAAO;AAE1D,QAAI;AACF,UAAI;AACJ,UAAI,KAAK,QAAQ,SAAS;AACxB,mBAAW,MAAM,MAAM,KAAK,QAAQ,SAAS;AAAA,UAC3C,QAAQ;AAAA,UACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,UAC9C,aAAa;AAAA,UACb,MAAM,KAAK,UAAU,EAAE,QAAQ,QAAQ,CAAC;AAAA,UACxC,QAAQ,WAAW;AAAA,QACrB,CAAC;AAAA,MACH,OAAO;AACL,cAAM,QAAQ,MAAM,KAAK,SAAS;AAClC,mBAAW,MAAM,MAAM,GAAG,KAAK,OAAO,UAAU,MAAM,aAAa;AAAA,UACjE,QAAQ;AAAA,UACR,SAAS;AAAA,YACP,gBAAgB;AAAA,YAChB,eAAe,UAAU,KAAK;AAAA,UAChC;AAAA,UACA,MAAM,KAAK,UAAU,EAAE,QAAQ,CAAC;AAAA,UAChC,QAAQ,WAAW;AAAA,QACrB,CAAC;AAAA,MACH;AAEA,UAAI,CAAC,SAAS,IAAI;AAChB,cAAM,MAAM,qBAAqB,QAAQ;AAAA,MAC3C;AAEA,aAAQ,MAAM,SAAS,KAAK;AAAA,IAC9B,UAAE;AACA,mBAAa,KAAK;AAAA,IACpB;AAAA,EACF;AAAA,EAEA,MAAa,WAAW,QAAgB,UAA6B,CAAC,GAAyB;AAC7F,UAAM,QAAQ,MAAM,KAAK,SAAS;AAClC,UAAM,SAAS,IAAI,gBAAgB;AACnC,QAAI,QAAQ,aAAa,OAAW,QAAO,IAAI,YAAY,QAAQ,SAAS,SAAS,CAAC;AACtF,QAAI,QAAQ,cAAc,OAAW,QAAO,IAAI,aAAa,QAAQ,UAAU,SAAS,CAAC;AACzF,QAAI,QAAQ,UAAU,OAAW,QAAO,IAAI,SAAS,QAAQ,MAAM,SAAS,CAAC;AAE7E,UAAM,KAAK,OAAO,SAAS;AAC3B,UAAM,MAAM,GAAG,KAAK,OAAO,UAAU,MAAM,YAAY,KAAK,IAAI,EAAE,KAAK,EAAE;AACzE,UAAM,UAAU,QAAQ,aAAa,KAAK,QAAQ,oBAAoB;AACtE,UAAM,aAAa,IAAI,gBAAgB;AACvC,UAAM,QAAQ,WAAW,MAAM,WAAW,MAAM,GAAG,OAAO;AAE1D,QAAI;AACF,YAAM,WAAW,MAAM,MAAM,KAAK;AAAA,QAChC,QAAQ;AAAA,QACR,SAAS;AAAA,UACP,eAAe,UAAU,KAAK;AAAA,QAChC;AAAA,QACA,QAAQ,WAAW;AAAA,MACrB,CAAC;AAED,UAAI,CAAC,SAAS,IAAI;AAChB,cAAM,MAAM,qBAAqB,QAAQ;AAAA,MAC3C;AAEA,YAAM,OAAQ,MAAM,SAAS,KAAK;AAClC,YAAM,WAA0B,KAAK,SAAS,IAAI,CAAC,OAAO;AAAA,QACxD,QAAQ,KAAK;AAAA,QACb,KAAK,EAAE;AAAA,QACP,UAAU,EAAE;AAAA,QACZ,MAAM,EAAE;AAAA,QACR,SAAS,EAAE;AAAA,MACb,EAAE;AAEF,aAAO,EAAE,UAAU,SAAS,KAAK,QAAQ;AAAA,IAC3C,UAAE;AACA,mBAAa,KAAK;AAAA,IACpB;AAAA,EACF;AAAA,EAEA,MAAa,gBAAgB,QAAgB,WAAuC;AAClF,UAAM,QAAQ,MAAM,KAAK,SAAS;AAClC,UAAM,aAAa,IAAI,gBAAgB;AACvC,UAAM,QAAQ,WAAW,MAAM,WAAW,MAAM,GAAG,aAAa,KAAK,QAAQ,oBAAoB,IAAK;AACtG,QAAI;AACF,YAAM,WAAW,MAAM,MAAM,GAAG,KAAK,OAAO,UAAU,MAAM,iBAAiB;AAAA,QAC3E,QAAQ;AAAA,QACR,SAAS,EAAE,eAAe,UAAU,KAAK,GAAG;AAAA,QAC5C,QAAQ,WAAW;AAAA,MACrB,CAAC;AACD,UAAI,CAAC,SAAS,GAAI,OAAM,MAAM,qBAAqB,QAAQ;AAC3D,aAAQ,MAAM,SAAS,KAAK;AAAA,IAC9B,UAAE;AACA,mBAAa,KAAK;AAAA,IACpB;AAAA,EACF;AAAA,EAEO,UAAU,SAAqD;AACpE,SAAK,iBAAiB,IAAI,OAAO;AACjC,WAAO,MAAM;AACX,WAAK,iBAAiB,OAAO,OAAO;AAAA,IACtC;AAAA,EACF;AAAA,EAEO,gBAAgB,SAAqD;AAC1E,WAAO,KAAK,UAAU,CAAC,YAAY;AACjC,UAAI,eAAe,QAAQ,QAAQ,GAAG;AACpC,gBAAQ,OAAO;AAAA,MACjB;AAAA,IACF,CAAC;AAAA,EACH;AAAA,EAEO,cAAc,SAAqD;AACxE,WAAO,KAAK,UAAU,CAAC,YAAY;AACjC,UAAI,CAAC,eAAe,QAAQ,QAAQ,GAAG;AACrC,gBAAQ,OAAO;AAAA,MACjB;AAAA,IACF,CAAC;AAAA,EACH;AAAA,EAEO,mBAAmB,SAA0D;AAClF,SAAK,mBAAmB,IAAI,OAAO;AACnC,WAAO,MAAM;AACX,WAAK,mBAAmB,OAAO,OAAO;AAAA,IACxC;AAAA,EACF;AAAA,EAEO,oBAAoB,SAA2D;AACpF,SAAK,iBAAiB,IAAI,OAAO;AACjC,WAAO,MAAM;AACX,WAAK,iBAAiB,OAAO,OAAO;AAAA,IACtC;AAAA,EACF;AAAA,EAEO,SAAS,SAAiE;AAC/E,SAAK,gBAAgB,IAAI,OAAO;AAChC,WAAO,MAAM;AACX,WAAK,gBAAgB,OAAO,OAAO;AAAA,IACrC;AAAA,EACF;AAAA,EAEO,WAAW,SAAqD;AACrE,SAAK,kBAAkB,IAAI,OAAO;AAClC,WAAO,MAAM;AACX,WAAK,kBAAkB,OAAO,OAAO;AAAA,IACvC;AAAA,EACF;AAAA,EAEO,cAAc,SAAuD;AAC1E,SAAK,eAAe,IAAI,OAAO;AAC/B,WAAO,MAAM;AACX,WAAK,eAAe,OAAO,OAAO;AAAA,IACpC;AAAA,EACF;AAAA,EAEO,UAAgB;AACrB,QAAI,KAAK,gBAAgB;AACvB,WAAK,eAAe;AACpB,WAAK,iBAAiB;AAAA,IACxB;AACA,eAAW,QAAQ,KAAK,mBAAmB;AACzC,WAAK,QAAQ;AAAA,IACf;AACA,SAAK,kBAAkB,MAAM;AAC7B,SAAK,WAAW,EAAE,MAAM,MAAM;AAAA,IAAC,CAAC;AAAA,EAClC;AACF;AAEA,SAAS,YAAY,OAAoD;AACvE,UAAQ,OAAO;AAAA,IACb,KAAK;AACH,aAAO,SAAS;AAAA,IAClB,KAAK;AACH,aAAO,SAAS;AAAA,IAClB,KAAK;AACH,aAAO,SAAS;AAAA,IAClB,KAAK;AACH,aAAO,SAAS;AAAA,IAClB,KAAK;AAAA,IACL;AACE,aAAO,SAAS;AAAA,EACpB;AACF;","names":[]}
package/package.json CHANGED
@@ -1,6 +1,50 @@
1
1
  {
2
2
  "name": "@dotwire/client",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.2.1",
4
+ "description": "Browser/Node client SDK for dotwire: scoped room handles, SignalR subscription with sequence-based gap-fill, typing, presence, and history.",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "git+https://github.com/pasztor-mark/dotwire.git",
8
+ "directory": "packages/client"
9
+ },
10
+ "main": "./dist/index.cjs",
11
+ "module": "./dist/index.js",
12
+ "types": "./dist/index.d.ts",
13
+ "exports": {
14
+ ".": {
15
+ "types": "./dist/index.d.ts",
16
+ "import": "./dist/index.js",
17
+ "require": "./dist/index.cjs"
18
+ }
19
+ },
20
+ "files": [
21
+ "dist",
22
+ "README.md"
23
+ ],
24
+ "sideEffects": false,
25
+ "engines": {
26
+ "node": ">=20"
27
+ },
28
+ "scripts": {
29
+ "build": "tsup",
30
+ "dev": "tsup --watch",
31
+ "test": "tsc -p tsconfig.test.json && node --test dist-test/test/**/*.test.js"
32
+ },
33
+ "keywords": [
34
+ "dotwire",
35
+ "chat",
36
+ "sdk",
37
+ "client",
38
+ "signalr"
39
+ ],
40
+ "license": "Apache-2.0",
41
+ "type": "module",
42
+ "dependencies": {
43
+ "@microsoft/signalr": "^8.0.7"
44
+ },
45
+ "devDependencies": {
46
+ "@types/node": "^22.0.0",
47
+ "tsup": "^8.5.1",
48
+ "typescript": "^5.8.0"
49
+ }
50
+ }