@agentup-inksen/web-chat-core 0.0.2 → 0.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +27 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +46 -3
- package/dist/index.d.ts +46 -3
- package/dist/index.js +27 -2
- package/dist/index.js.map +1 -1
- package/package.json +6 -3
package/dist/index.cjs
CHANGED
|
@@ -80,14 +80,39 @@ var AgentUpChatClient = class {
|
|
|
80
80
|
}));
|
|
81
81
|
}
|
|
82
82
|
/**
|
|
83
|
-
* REST fallback for sending
|
|
84
|
-
*
|
|
83
|
+
* REST fallback for sending a plain text message (legacy / bubble widget path).
|
|
84
|
+
* The API binds `[FromForm] content` — multipart/form-data is required.
|
|
85
85
|
*/
|
|
86
86
|
sendMessage(conversationId, content) {
|
|
87
87
|
const form = new FormData();
|
|
88
88
|
form.append("content", content);
|
|
89
89
|
return this.postForm(`/api/webchat/messages/${conversationId}`, form);
|
|
90
90
|
}
|
|
91
|
+
/**
|
|
92
|
+
* Send a message with optional text caption and up to 5 file attachments via
|
|
93
|
+
* `POST /api/webchat/messages/{conversationId}/multi` (multipart/form-data).
|
|
94
|
+
*
|
|
95
|
+
* - `content` may be `null` when sending files without a caption.
|
|
96
|
+
* - Validation (file count, size, type) is enforced server-side and surfaced as
|
|
97
|
+
* structured `Error` messages if the request fails.
|
|
98
|
+
* - Returns one `ChatMessage` per persisted row (text caption + one per file).
|
|
99
|
+
*
|
|
100
|
+
* File-only sends (no caption) do NOT trigger the AI pipeline; the user must
|
|
101
|
+
* follow up with a text message to get a RAG-augmented reply.
|
|
102
|
+
*/
|
|
103
|
+
async sendMessageWithAttachments(conversationId, content, files) {
|
|
104
|
+
const form = new FormData();
|
|
105
|
+
if (content?.trim()) {
|
|
106
|
+
form.append("content", content.trim());
|
|
107
|
+
}
|
|
108
|
+
for (const file of files) {
|
|
109
|
+
form.append("files", file, file.name);
|
|
110
|
+
}
|
|
111
|
+
return this.postForm(
|
|
112
|
+
`/api/webchat/messages/${conversationId}/multi`,
|
|
113
|
+
form
|
|
114
|
+
);
|
|
115
|
+
}
|
|
91
116
|
closeConversation(conversationId) {
|
|
92
117
|
return this.post(`/api/webchat/conversations/${conversationId}/close`, {});
|
|
93
118
|
}
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/api-client.ts","../src/signalr-client.ts","../src/storage.ts"],"sourcesContent":["export * from './types.js';\r\nexport { AgentUpChatClient, WebChatAuthError } from './api-client.js';\r\nexport { AgentUpChatHub } from './signalr-client.js';\r\nexport type {\r\n AgentUpChatHubOptions,\r\n AgentUpChatHubHandlers,\r\n ChatConnectionState\r\n} from './signalr-client.js';\r\nexport * as storage from './storage.js';\r\n","import type {\r\n AgentUpChatClientConfig,\r\n ChatMessage,\r\n ConversationDetails,\r\n PublicAgent,\r\n VisitorConversationSummary\r\n} from './types.js';\r\n\r\n/** Thrown when the visitor token is expired/invalid; UIs should surface a re-auth flow. */\r\nexport class WebChatAuthError extends Error {\r\n constructor(message: string) {\r\n super(message);\r\n this.name = 'WebChatAuthError';\r\n }\r\n}\r\n\r\n/**\r\n * Minimal REST client for the embedded web chat app endpoints. Wraps fetch with\r\n * the visitor JWT and unwraps the AgentUp `ApiResponse<T>` envelope automatically.\r\n *\r\n * Endpoints used:\r\n * - `GET /api/webchat/agents`\r\n * - `GET /api/webchat/conversations`\r\n * - `POST /api/webchat/conversations`\r\n * - `GET /api/webchat/conversations/{id}` (includes `messages` history)\r\n * - `POST /api/webchat/messages/{conversationId}` (send)\r\n * - `POST /api/webchat/conversations/{id}/close`\r\n */\r\nexport class AgentUpChatClient {\r\n private readonly baseUrl: string;\r\n private readonly tenantId?: string;\r\n private readonly fetchImpl: typeof fetch;\r\n private visitorToken: string;\r\n\r\n constructor(config: AgentUpChatClientConfig) {\r\n if (!config.apiUrl) throw new Error('AgentUpChatClient: apiUrl is required');\r\n if (!config.visitorToken) throw new Error('AgentUpChatClient: visitorToken is required');\r\n this.baseUrl = config.apiUrl.replace(/\\/$/, '');\r\n this.visitorToken = config.visitorToken;\r\n this.tenantId = config.tenantId;\r\n this.fetchImpl = config.fetch ?? fetch.bind(globalThis);\r\n }\r\n\r\n /** Refresh the visitor token in place (e.g. after a silent re-auth round-trip). */\r\n setVisitorToken(token: string): void {\r\n if (!token) throw new Error('AgentUpChatClient.setVisitorToken: empty token');\r\n this.visitorToken = token;\r\n }\r\n\r\n listAgents(signal?: AbortSignal): Promise<PublicAgent[]> {\r\n return this.get<PublicAgent[]>('/api/webchat/agents', signal);\r\n }\r\n\r\n listConversations(signal?: AbortSignal): Promise<VisitorConversationSummary[]> {\r\n return this.get<VisitorConversationSummary[]>('/api/webchat/conversations', signal);\r\n }\r\n\r\n createConversation(agentCode: string): Promise<VisitorConversationSummary> {\r\n return this.post<VisitorConversationSummary>('/api/webchat/conversations', { agentCode });\r\n }\r\n\r\n getConversation(conversationId: string, signal?: AbortSignal): Promise<ConversationDetails> {\r\n return this.get<ConversationDetails>(`/api/webchat/conversations/${conversationId}`, signal);\r\n }\r\n\r\n /**\r\n * Message history is returned on `GET /api/webchat/conversations/{id}` (there is no GET on\r\n * `/api/webchat/messages/{id}` — that route is POST-only for sending).\r\n */\r\n async getMessages(conversationId: string, signal?: AbortSignal): Promise<ChatMessage[]> {\r\n const conv = await this.get<ConversationDetails>(`/api/webchat/conversations/${conversationId}`, signal);\r\n const raw = conv.messages;\r\n if (!raw?.length) return [];\r\n return raw.map((m) => ({\r\n ...m,\r\n conversationId: m.conversationId || conversationId\r\n }));\r\n }\r\n\r\n /**\r\n * REST fallback for sending. The API binds `[FromForm] content` (see `WebChatController`),\r\n * not JSON — multipart/form-data is required.\r\n */\r\n sendMessage(conversationId: string, content: string): Promise<ChatMessage> {\r\n const form = new FormData();\r\n form.append('content', content);\r\n return this.postForm<ChatMessage>(`/api/webchat/messages/${conversationId}`, form);\r\n }\r\n\r\n closeConversation(conversationId: string): Promise<void> {\r\n return this.post<void>(`/api/webchat/conversations/${conversationId}/close`, {});\r\n }\r\n\r\n // ---------------- internals ----------------\r\n\r\n private async get<T>(path: string, signal?: AbortSignal): Promise<T> {\r\n const res = await this.fetchImpl(`${this.baseUrl}${path}`, {\r\n method: 'GET',\r\n headers: this.headers(),\r\n signal\r\n });\r\n return this.unwrap<T>(res);\r\n }\r\n\r\n private async post<T>(path: string, body: unknown): Promise<T> {\r\n const res = await this.fetchImpl(`${this.baseUrl}${path}`, {\r\n method: 'POST',\r\n headers: { ...this.headers(), 'Content-Type': 'application/json' },\r\n body: JSON.stringify(body ?? {})\r\n });\r\n return this.unwrap<T>(res);\r\n }\r\n\r\n /** POST multipart/form-data (do not set Content-Type — boundary is set by the runtime). */\r\n private async postForm<T>(path: string, formData: FormData): Promise<T> {\r\n const res = await this.fetchImpl(`${this.baseUrl}${path}`, {\r\n method: 'POST',\r\n headers: this.headers(),\r\n body: formData\r\n });\r\n return this.unwrap<T>(res);\r\n }\r\n\r\n private headers(): Record<string, string> {\r\n const h: Record<string, string> = {\r\n Authorization: `Bearer ${this.visitorToken}`,\r\n Accept: 'application/json'\r\n };\r\n if (this.tenantId) h['X-Tenant-Id'] = this.tenantId;\r\n return h;\r\n }\r\n\r\n private async unwrap<T>(res: Response): Promise<T> {\r\n if (res.status === 401) {\r\n throw new WebChatAuthError('Visitor token expired or invalid');\r\n }\r\n if (!res.ok) {\r\n let detail = '';\r\n try {\r\n const text = await res.text();\r\n detail = text.length > 200 ? text.slice(0, 200) + '…' : text;\r\n } catch {\r\n // ignore body parse errors\r\n }\r\n throw new Error(`AgentUp request ${res.url} failed (${res.status}): ${detail}`);\r\n }\r\n if (res.status === 204) return undefined as T;\r\n\r\n const json = (await res.json()) as unknown;\r\n // Backend wraps responses as { success, result } except for a few skipped paths.\r\n if (\r\n json &&\r\n typeof json === 'object' &&\r\n 'success' in (json as Record<string, unknown>) &&\r\n 'result' in (json as Record<string, unknown>)\r\n ) {\r\n const env = json as { success: boolean; result: T; message?: string };\r\n if (!env.success) {\r\n throw new Error(env.message ?? 'AgentUp request failed');\r\n }\r\n return env.result;\r\n }\r\n return json as T;\r\n }\r\n}\r\n","import {\r\n HubConnection,\r\n HubConnectionBuilder,\r\n HubConnectionState,\r\n LogLevel\r\n} from '@microsoft/signalr';\r\n\r\nimport type { ChatMessage, ChatMessageChunk, UserTypingPayload } from './types.js';\r\n\r\n/** Connection lifecycle states surfaced to UI layers. */\r\nexport type ChatConnectionState = 'idle' | 'connecting' | 'connected' | 'disconnected' | 'reconnecting';\r\n\r\nexport interface AgentUpChatHubOptions {\r\n /** Base URL of the AgentUp API. */\r\n apiUrl: string;\r\n /** Provides the latest visitor JWT (called on initial connect AND on reconnect). */\r\n accessTokenFactory: () => string | Promise<string>;\r\n}\r\n\r\nexport interface AgentUpChatHubHandlers {\r\n onMessage?: (message: ChatMessage) => void;\r\n /** AI/agent streaming tokens before the final `ReceiveMessage`. */\r\n onMessageChunk?: (chunk: ChatMessageChunk) => void;\r\n /** Hub `UserTyping` — includes AI (`userId` `\"ai\"`) and human agents. */\r\n onUserTyping?: (payload: UserTypingPayload) => void;\r\n /** Hub `UserStoppedTyping` — argument is `userId` (e.g. `\"ai\"`). */\r\n onUserStoppedTyping?: (userId: string) => void;\r\n onConnectionStateChanged?: (state: ChatConnectionState) => void;\r\n onError?: (error: unknown) => void;\r\n}\r\n\r\n/**\r\n * Thin SignalR client for the AgentUp ChatHub. Intentionally narrow surface — the\r\n * UI layer owns active-conversation tracking and group join/leave logic.\r\n *\r\n * Server methods used:\r\n * - `JoinConversation(conversationId)` / `LeaveConversation(conversationId)`\r\n * - `SendMessage(conversationId, content)`\r\n * - `Typing(conversationId, isTyping)`\r\n *\r\n * Server callbacks consumed:\r\n * - `ReceiveMessage`, `ReceiveMessageChunk` (streaming), `UserTyping`, `UserStoppedTyping`, `Error`.\r\n */\r\nexport class AgentUpChatHub {\r\n private readonly options: AgentUpChatHubOptions;\r\n private readonly handlers: AgentUpChatHubHandlers;\r\n private connection: HubConnection | null = null;\r\n private state: ChatConnectionState = 'idle';\r\n\r\n constructor(options: AgentUpChatHubOptions, handlers: AgentUpChatHubHandlers = {}) {\r\n this.options = options;\r\n this.handlers = handlers;\r\n }\r\n\r\n get currentState(): ChatConnectionState {\r\n return this.state;\r\n }\r\n\r\n async start(): Promise<void> {\r\n if (this.connection && this.connection.state === HubConnectionState.Connected) {\r\n return;\r\n }\r\n this.setState('connecting');\r\n\r\n const base = this.options.apiUrl.replace(/\\/$/, '');\r\n /** Must match ASP.NET `MapHub<ChatHub>(\"/api/chatHub\")` (same path as legacy widget). */\r\n const url = `${base}/api/chatHub`;\r\n this.connection = new HubConnectionBuilder()\r\n .withUrl(url, {\r\n accessTokenFactory: () => Promise.resolve(this.options.accessTokenFactory())\r\n })\r\n .withAutomaticReconnect()\r\n .configureLogging(LogLevel.Warning)\r\n .build();\r\n\r\n this.connection.on('ReceiveMessage', (msg: ChatMessage) => this.handlers.onMessage?.(msg));\r\n this.connection.on('ReceiveMessageChunk', (chunk: ChatMessageChunk) =>\r\n this.handlers.onMessageChunk?.(chunk)\r\n );\r\n this.connection.on('UserTyping', (payload: UserTypingPayload) =>\r\n this.handlers.onUserTyping?.(payload)\r\n );\r\n this.connection.on('UserStoppedTyping', (userId: string) =>\r\n this.handlers.onUserStoppedTyping?.(userId)\r\n );\r\n this.connection.on('Error', (msg: string) => this.handlers.onError?.(new Error(msg)));\r\n\r\n this.connection.onreconnecting(() => this.setState('reconnecting'));\r\n this.connection.onreconnected(() => this.setState('connected'));\r\n this.connection.onclose(() => this.setState('disconnected'));\r\n\r\n await this.connection.start();\r\n this.setState('connected');\r\n }\r\n\r\n async stop(): Promise<void> {\r\n if (this.connection) {\r\n await this.connection.stop();\r\n this.connection = null;\r\n }\r\n this.setState('idle');\r\n }\r\n\r\n async joinConversation(conversationId: string): Promise<void> {\r\n await this.ensureConnected();\r\n await this.connection!.invoke('JoinConversation', conversationId);\r\n }\r\n\r\n async leaveConversation(conversationId: string): Promise<void> {\r\n if (this.connection?.state !== HubConnectionState.Connected) return;\r\n await this.connection.invoke('LeaveConversation', conversationId);\r\n }\r\n\r\n async sendMessage(conversationId: string, content: string): Promise<void> {\r\n await this.ensureConnected();\r\n await this.connection!.invoke('SendMessage', conversationId, content);\r\n }\r\n\r\n async sendTyping(conversationId: string, isTyping: boolean): Promise<void> {\r\n if (this.connection?.state !== HubConnectionState.Connected) return;\r\n await this.connection.invoke('Typing', conversationId, isTyping);\r\n }\r\n\r\n private async ensureConnected(): Promise<void> {\r\n if (!this.connection) {\r\n await this.start();\r\n return;\r\n }\r\n if (this.connection.state !== HubConnectionState.Connected) {\r\n await this.connection.start();\r\n this.setState('connected');\r\n }\r\n }\r\n\r\n private setState(next: ChatConnectionState): void {\r\n if (this.state === next) return;\r\n this.state = next;\r\n this.handlers.onConnectionStateChanged?.(next);\r\n }\r\n}\r\n","/**\r\n * Local storage helper for the embedded chat app. Keeps ephemeral UI state\r\n * (selected conversation id, sidebar collapsed, etc.) outside React-style\r\n * components so the choice of UI framework stays flexible.\r\n *\r\n * Storage is **not** used for the visitor token by default — tokens are short\r\n * lived and should be re-minted from the host backend on demand. Hosts can\r\n * still call {@link saveToken} if they want browser refresh persistence.\r\n */\r\nconst PREFIX = 'agentup.webchat:';\r\n\r\nexport function getItem(key: string): string | null {\r\n try {\r\n return localStorage.getItem(PREFIX + key);\r\n } catch {\r\n return null;\r\n }\r\n}\r\n\r\nexport function setItem(key: string, value: string): void {\r\n try {\r\n localStorage.setItem(PREFIX + key, value);\r\n } catch {\r\n // Storage may be disabled (private mode); silently degrade.\r\n }\r\n}\r\n\r\nexport function removeItem(key: string): void {\r\n try {\r\n localStorage.removeItem(PREFIX + key);\r\n } catch {\r\n // ignore\r\n }\r\n}\r\n\r\n/** Convenience wrapper for the visitor token (host-controlled persistence). */\r\nexport function saveToken(token: string): void {\r\n setItem('visitor_token', token);\r\n}\r\n\r\nexport function loadToken(): string | null {\r\n return getItem('visitor_token');\r\n}\r\n\r\nexport function clearToken(): void {\r\n removeItem('visitor_token');\r\n}\r\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/api-client.ts","../src/signalr-client.ts","../src/storage.ts"],"sourcesContent":["export * from './types.js';\r\nexport { AgentUpChatClient, WebChatAuthError } from './api-client.js';\r\nexport { AgentUpChatHub } from './signalr-client.js';\r\nexport type {\r\n AgentUpChatHubOptions,\r\n AgentUpChatHubHandlers,\r\n ChatConnectionState\r\n} from './signalr-client.js';\r\nexport * as storage from './storage.js';\r\n","import type {\r\n AgentUpChatClientConfig,\r\n ChatMessage,\r\n ConversationDetails,\r\n PublicAgent,\r\n VisitorConversationSummary\r\n} from './types.js';\r\n\r\n/** Thrown when the visitor token is expired/invalid; UIs should surface a re-auth flow. */\r\nexport class WebChatAuthError extends Error {\r\n constructor(message: string) {\r\n super(message);\r\n this.name = 'WebChatAuthError';\r\n }\r\n}\r\n\r\n/**\r\n * Minimal REST client for the embedded web chat app endpoints. Wraps fetch with\r\n * the visitor JWT and unwraps the AgentUp `ApiResponse<T>` envelope automatically.\r\n *\r\n * Endpoints used:\r\n * - `GET /api/webchat/agents`\r\n * - `GET /api/webchat/conversations`\r\n * - `POST /api/webchat/conversations`\r\n * - `GET /api/webchat/conversations/{id}` (includes `messages` history)\r\n * - `POST /api/webchat/messages/{conversationId}` (send)\r\n * - `POST /api/webchat/conversations/{id}/close`\r\n */\r\nexport class AgentUpChatClient {\r\n private readonly baseUrl: string;\r\n private readonly tenantId?: string;\r\n private readonly fetchImpl: typeof fetch;\r\n private visitorToken: string;\r\n\r\n constructor(config: AgentUpChatClientConfig) {\r\n if (!config.apiUrl) throw new Error('AgentUpChatClient: apiUrl is required');\r\n if (!config.visitorToken) throw new Error('AgentUpChatClient: visitorToken is required');\r\n this.baseUrl = config.apiUrl.replace(/\\/$/, '');\r\n this.visitorToken = config.visitorToken;\r\n this.tenantId = config.tenantId;\r\n this.fetchImpl = config.fetch ?? fetch.bind(globalThis);\r\n }\r\n\r\n /** Refresh the visitor token in place (e.g. after a silent re-auth round-trip). */\r\n setVisitorToken(token: string): void {\r\n if (!token) throw new Error('AgentUpChatClient.setVisitorToken: empty token');\r\n this.visitorToken = token;\r\n }\r\n\r\n listAgents(signal?: AbortSignal): Promise<PublicAgent[]> {\r\n return this.get<PublicAgent[]>('/api/webchat/agents', signal);\r\n }\r\n\r\n listConversations(signal?: AbortSignal): Promise<VisitorConversationSummary[]> {\r\n return this.get<VisitorConversationSummary[]>('/api/webchat/conversations', signal);\r\n }\r\n\r\n createConversation(agentCode: string): Promise<VisitorConversationSummary> {\r\n return this.post<VisitorConversationSummary>('/api/webchat/conversations', { agentCode });\r\n }\r\n\r\n getConversation(conversationId: string, signal?: AbortSignal): Promise<ConversationDetails> {\r\n return this.get<ConversationDetails>(`/api/webchat/conversations/${conversationId}`, signal);\r\n }\r\n\r\n /**\r\n * Message history is returned on `GET /api/webchat/conversations/{id}` (there is no GET on\r\n * `/api/webchat/messages/{id}` — that route is POST-only for sending).\r\n */\r\n async getMessages(conversationId: string, signal?: AbortSignal): Promise<ChatMessage[]> {\r\n const conv = await this.get<ConversationDetails>(`/api/webchat/conversations/${conversationId}`, signal);\r\n const raw = conv.messages;\r\n if (!raw?.length) return [];\r\n return raw.map((m) => ({\r\n ...m,\r\n conversationId: m.conversationId || conversationId\r\n }));\r\n }\r\n\r\n /**\r\n * REST fallback for sending a plain text message (legacy / bubble widget path).\r\n * The API binds `[FromForm] content` — multipart/form-data is required.\r\n */\r\n sendMessage(conversationId: string, content: string): Promise<ChatMessage> {\r\n const form = new FormData();\r\n form.append('content', content);\r\n return this.postForm<ChatMessage>(`/api/webchat/messages/${conversationId}`, form);\r\n }\r\n\r\n /**\r\n * Send a message with optional text caption and up to 5 file attachments via\r\n * `POST /api/webchat/messages/{conversationId}/multi` (multipart/form-data).\r\n *\r\n * - `content` may be `null` when sending files without a caption.\r\n * - Validation (file count, size, type) is enforced server-side and surfaced as\r\n * structured `Error` messages if the request fails.\r\n * - Returns one `ChatMessage` per persisted row (text caption + one per file).\r\n *\r\n * File-only sends (no caption) do NOT trigger the AI pipeline; the user must\r\n * follow up with a text message to get a RAG-augmented reply.\r\n */\r\n async sendMessageWithAttachments(\r\n conversationId: string,\r\n content: string | null,\r\n files: File[]\r\n ): Promise<ChatMessage[]> {\r\n const form = new FormData();\r\n if (content?.trim()) {\r\n form.append('content', content.trim());\r\n }\r\n for (const file of files) {\r\n form.append('files', file, file.name);\r\n }\r\n return this.postForm<ChatMessage[]>(\r\n `/api/webchat/messages/${conversationId}/multi`,\r\n form\r\n );\r\n }\r\n\r\n closeConversation(conversationId: string): Promise<void> {\r\n return this.post<void>(`/api/webchat/conversations/${conversationId}/close`, {});\r\n }\r\n\r\n // ---------------- internals ----------------\r\n\r\n private async get<T>(path: string, signal?: AbortSignal): Promise<T> {\r\n const res = await this.fetchImpl(`${this.baseUrl}${path}`, {\r\n method: 'GET',\r\n headers: this.headers(),\r\n signal\r\n });\r\n return this.unwrap<T>(res);\r\n }\r\n\r\n private async post<T>(path: string, body: unknown): Promise<T> {\r\n const res = await this.fetchImpl(`${this.baseUrl}${path}`, {\r\n method: 'POST',\r\n headers: { ...this.headers(), 'Content-Type': 'application/json' },\r\n body: JSON.stringify(body ?? {})\r\n });\r\n return this.unwrap<T>(res);\r\n }\r\n\r\n /** POST multipart/form-data (do not set Content-Type — boundary is set by the runtime). */\r\n private async postForm<T>(path: string, formData: FormData): Promise<T> {\r\n const res = await this.fetchImpl(`${this.baseUrl}${path}`, {\r\n method: 'POST',\r\n headers: this.headers(),\r\n body: formData\r\n });\r\n return this.unwrap<T>(res);\r\n }\r\n\r\n private headers(): Record<string, string> {\r\n const h: Record<string, string> = {\r\n Authorization: `Bearer ${this.visitorToken}`,\r\n Accept: 'application/json'\r\n };\r\n if (this.tenantId) h['X-Tenant-Id'] = this.tenantId;\r\n return h;\r\n }\r\n\r\n private async unwrap<T>(res: Response): Promise<T> {\r\n if (res.status === 401) {\r\n throw new WebChatAuthError('Visitor token expired or invalid');\r\n }\r\n if (!res.ok) {\r\n let detail = '';\r\n try {\r\n const text = await res.text();\r\n detail = text.length > 200 ? text.slice(0, 200) + '…' : text;\r\n } catch {\r\n // ignore body parse errors\r\n }\r\n throw new Error(`AgentUp request ${res.url} failed (${res.status}): ${detail}`);\r\n }\r\n if (res.status === 204) return undefined as T;\r\n\r\n const json = (await res.json()) as unknown;\r\n // Backend wraps responses as { success, result } except for a few skipped paths.\r\n if (\r\n json &&\r\n typeof json === 'object' &&\r\n 'success' in (json as Record<string, unknown>) &&\r\n 'result' in (json as Record<string, unknown>)\r\n ) {\r\n const env = json as { success: boolean; result: T; message?: string };\r\n if (!env.success) {\r\n throw new Error(env.message ?? 'AgentUp request failed');\r\n }\r\n return env.result;\r\n }\r\n return json as T;\r\n }\r\n}\r\n","import {\r\n HubConnection,\r\n HubConnectionBuilder,\r\n HubConnectionState,\r\n LogLevel\r\n} from '@microsoft/signalr';\r\n\r\nimport type { ChatMessage, ChatMessageChunk, UserTypingPayload } from './types.js';\r\n\r\n/** Connection lifecycle states surfaced to UI layers. */\r\nexport type ChatConnectionState = 'idle' | 'connecting' | 'connected' | 'disconnected' | 'reconnecting';\r\n\r\nexport interface AgentUpChatHubOptions {\r\n /** Base URL of the AgentUp API. */\r\n apiUrl: string;\r\n /** Provides the latest visitor JWT (called on initial connect AND on reconnect). */\r\n accessTokenFactory: () => string | Promise<string>;\r\n}\r\n\r\nexport interface AgentUpChatHubHandlers {\r\n onMessage?: (message: ChatMessage) => void;\r\n /** AI/agent streaming tokens before the final `ReceiveMessage`. */\r\n onMessageChunk?: (chunk: ChatMessageChunk) => void;\r\n /** Hub `UserTyping` — includes AI (`userId` `\"ai\"`) and human agents. */\r\n onUserTyping?: (payload: UserTypingPayload) => void;\r\n /** Hub `UserStoppedTyping` — argument is `userId` (e.g. `\"ai\"`). */\r\n onUserStoppedTyping?: (userId: string) => void;\r\n onConnectionStateChanged?: (state: ChatConnectionState) => void;\r\n onError?: (error: unknown) => void;\r\n}\r\n\r\n/**\r\n * Thin SignalR client for the AgentUp ChatHub. Intentionally narrow surface — the\r\n * UI layer owns active-conversation tracking and group join/leave logic.\r\n *\r\n * Server methods used:\r\n * - `JoinConversation(conversationId)` / `LeaveConversation(conversationId)`\r\n * - `SendMessage(conversationId, content)`\r\n * - `Typing(conversationId, isTyping)`\r\n *\r\n * Server callbacks consumed:\r\n * - `ReceiveMessage`, `ReceiveMessageChunk` (streaming), `UserTyping`, `UserStoppedTyping`, `Error`.\r\n */\r\nexport class AgentUpChatHub {\r\n private readonly options: AgentUpChatHubOptions;\r\n private readonly handlers: AgentUpChatHubHandlers;\r\n private connection: HubConnection | null = null;\r\n private state: ChatConnectionState = 'idle';\r\n\r\n constructor(options: AgentUpChatHubOptions, handlers: AgentUpChatHubHandlers = {}) {\r\n this.options = options;\r\n this.handlers = handlers;\r\n }\r\n\r\n get currentState(): ChatConnectionState {\r\n return this.state;\r\n }\r\n\r\n async start(): Promise<void> {\r\n if (this.connection && this.connection.state === HubConnectionState.Connected) {\r\n return;\r\n }\r\n this.setState('connecting');\r\n\r\n const base = this.options.apiUrl.replace(/\\/$/, '');\r\n /** Must match ASP.NET `MapHub<ChatHub>(\"/api/chatHub\")` (same path as legacy widget). */\r\n const url = `${base}/api/chatHub`;\r\n this.connection = new HubConnectionBuilder()\r\n .withUrl(url, {\r\n accessTokenFactory: () => Promise.resolve(this.options.accessTokenFactory())\r\n })\r\n .withAutomaticReconnect()\r\n .configureLogging(LogLevel.Warning)\r\n .build();\r\n\r\n this.connection.on('ReceiveMessage', (msg: ChatMessage) => this.handlers.onMessage?.(msg));\r\n this.connection.on('ReceiveMessageChunk', (chunk: ChatMessageChunk) =>\r\n this.handlers.onMessageChunk?.(chunk)\r\n );\r\n this.connection.on('UserTyping', (payload: UserTypingPayload) =>\r\n this.handlers.onUserTyping?.(payload)\r\n );\r\n this.connection.on('UserStoppedTyping', (userId: string) =>\r\n this.handlers.onUserStoppedTyping?.(userId)\r\n );\r\n this.connection.on('Error', (msg: string) => this.handlers.onError?.(new Error(msg)));\r\n\r\n this.connection.onreconnecting(() => this.setState('reconnecting'));\r\n this.connection.onreconnected(() => this.setState('connected'));\r\n this.connection.onclose(() => this.setState('disconnected'));\r\n\r\n await this.connection.start();\r\n this.setState('connected');\r\n }\r\n\r\n async stop(): Promise<void> {\r\n if (this.connection) {\r\n await this.connection.stop();\r\n this.connection = null;\r\n }\r\n this.setState('idle');\r\n }\r\n\r\n async joinConversation(conversationId: string): Promise<void> {\r\n await this.ensureConnected();\r\n await this.connection!.invoke('JoinConversation', conversationId);\r\n }\r\n\r\n async leaveConversation(conversationId: string): Promise<void> {\r\n if (this.connection?.state !== HubConnectionState.Connected) return;\r\n await this.connection.invoke('LeaveConversation', conversationId);\r\n }\r\n\r\n async sendMessage(conversationId: string, content: string): Promise<void> {\r\n await this.ensureConnected();\r\n await this.connection!.invoke('SendMessage', conversationId, content);\r\n }\r\n\r\n async sendTyping(conversationId: string, isTyping: boolean): Promise<void> {\r\n if (this.connection?.state !== HubConnectionState.Connected) return;\r\n await this.connection.invoke('Typing', conversationId, isTyping);\r\n }\r\n\r\n private async ensureConnected(): Promise<void> {\r\n if (!this.connection) {\r\n await this.start();\r\n return;\r\n }\r\n if (this.connection.state !== HubConnectionState.Connected) {\r\n await this.connection.start();\r\n this.setState('connected');\r\n }\r\n }\r\n\r\n private setState(next: ChatConnectionState): void {\r\n if (this.state === next) return;\r\n this.state = next;\r\n this.handlers.onConnectionStateChanged?.(next);\r\n }\r\n}\r\n","/**\r\n * Local storage helper for the embedded chat app. Keeps ephemeral UI state\r\n * (selected conversation id, sidebar collapsed, etc.) outside React-style\r\n * components so the choice of UI framework stays flexible.\r\n *\r\n * Storage is **not** used for the visitor token by default — tokens are short\r\n * lived and should be re-minted from the host backend on demand. Hosts can\r\n * still call {@link saveToken} if they want browser refresh persistence.\r\n */\r\nconst PREFIX = 'agentup.webchat:';\r\n\r\nexport function getItem(key: string): string | null {\r\n try {\r\n return localStorage.getItem(PREFIX + key);\r\n } catch {\r\n return null;\r\n }\r\n}\r\n\r\nexport function setItem(key: string, value: string): void {\r\n try {\r\n localStorage.setItem(PREFIX + key, value);\r\n } catch {\r\n // Storage may be disabled (private mode); silently degrade.\r\n }\r\n}\r\n\r\nexport function removeItem(key: string): void {\r\n try {\r\n localStorage.removeItem(PREFIX + key);\r\n } catch {\r\n // ignore\r\n }\r\n}\r\n\r\n/** Convenience wrapper for the visitor token (host-controlled persistence). */\r\nexport function saveToken(token: string): void {\r\n setItem('visitor_token', token);\r\n}\r\n\r\nexport function loadToken(): string | null {\r\n return getItem('visitor_token');\r\n}\r\n\r\nexport function clearToken(): void {\r\n removeItem('visitor_token');\r\n}\r\n"],"mappings":"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","names":[]}
|
package/dist/index.d.cts
CHANGED
|
@@ -34,6 +34,13 @@ interface VisitorTokenResponse {
|
|
|
34
34
|
}
|
|
35
35
|
/** Mirrors `Domain.Entities.SenderType`. */
|
|
36
36
|
type SenderType = 'Customer' | 'Agent' | 'AI' | 'System';
|
|
37
|
+
/** Mirrors `Domain.Entities.MessageType`. */
|
|
38
|
+
type MessageType = 'Text' | 'Image' | 'Document' | 'Audio' | 'Video' | string;
|
|
39
|
+
/** Client-only previews inside a single optimistic multi-file row (stripped when server echoes). */
|
|
40
|
+
interface OptimisticAttachmentPreview {
|
|
41
|
+
fileName: string;
|
|
42
|
+
mimeType: string;
|
|
43
|
+
}
|
|
37
44
|
interface ChatMessage {
|
|
38
45
|
id: string;
|
|
39
46
|
conversationId: string;
|
|
@@ -41,8 +48,31 @@ interface ChatMessage {
|
|
|
41
48
|
senderType: SenderType;
|
|
42
49
|
senderName?: string | null;
|
|
43
50
|
createdAt: string;
|
|
51
|
+
/** Present for file attachment messages; absolute or relative URL to the stored file. */
|
|
52
|
+
mediaUrl?: string | null;
|
|
53
|
+
/** MIME type of the attached file (e.g. `image/jpeg`, `application/pdf`). */
|
|
54
|
+
mediaMimeType?: string | null;
|
|
55
|
+
/** Message kind; `Text` for plain text, `Image` / `Document` for attachments. */
|
|
56
|
+
messageType?: MessageType;
|
|
44
57
|
/** Optional: AI tool calls / structured payload (passthrough). */
|
|
45
58
|
metadata?: Record<string, unknown> | null;
|
|
59
|
+
/**
|
|
60
|
+
* When set on a file row, render in the same bubble as the message with this id
|
|
61
|
+
* (caption from the same multi-part send). Mirrors `WebChatMessageDto.AttachmentParentMessageId`.
|
|
62
|
+
*/
|
|
63
|
+
attachmentParentMessageId?: string | null;
|
|
64
|
+
/**
|
|
65
|
+
* Client-only: one placeholder row for caption + N files until `sendMessageWithAttachments`
|
|
66
|
+
* resolves; then replaced by the server’s N (or N+1) persisted messages.
|
|
67
|
+
*/
|
|
68
|
+
optimisticAttachments?: OptimisticAttachmentPreview[] | null;
|
|
69
|
+
}
|
|
70
|
+
/** Static attachment limits; mirror `appsettings.json WebChat` section defaults. */
|
|
71
|
+
interface WebChatAttachmentLimits {
|
|
72
|
+
maxFilesPerMessage: number;
|
|
73
|
+
maxFileSizeMB: number;
|
|
74
|
+
/** Allowed file extensions including the dot, e.g. `[".pdf", ".jpg"]`. */
|
|
75
|
+
allowedExtensions: string[];
|
|
46
76
|
}
|
|
47
77
|
/** Matches `TypingIndicatorDto` on hub event `UserTyping` (camelCase JSON). */
|
|
48
78
|
interface UserTypingPayload {
|
|
@@ -118,10 +148,23 @@ declare class AgentUpChatClient {
|
|
|
118
148
|
*/
|
|
119
149
|
getMessages(conversationId: string, signal?: AbortSignal): Promise<ChatMessage[]>;
|
|
120
150
|
/**
|
|
121
|
-
* REST fallback for sending
|
|
122
|
-
*
|
|
151
|
+
* REST fallback for sending a plain text message (legacy / bubble widget path).
|
|
152
|
+
* The API binds `[FromForm] content` — multipart/form-data is required.
|
|
123
153
|
*/
|
|
124
154
|
sendMessage(conversationId: string, content: string): Promise<ChatMessage>;
|
|
155
|
+
/**
|
|
156
|
+
* Send a message with optional text caption and up to 5 file attachments via
|
|
157
|
+
* `POST /api/webchat/messages/{conversationId}/multi` (multipart/form-data).
|
|
158
|
+
*
|
|
159
|
+
* - `content` may be `null` when sending files without a caption.
|
|
160
|
+
* - Validation (file count, size, type) is enforced server-side and surfaced as
|
|
161
|
+
* structured `Error` messages if the request fails.
|
|
162
|
+
* - Returns one `ChatMessage` per persisted row (text caption + one per file).
|
|
163
|
+
*
|
|
164
|
+
* File-only sends (no caption) do NOT trigger the AI pipeline; the user must
|
|
165
|
+
* follow up with a text message to get a RAG-augmented reply.
|
|
166
|
+
*/
|
|
167
|
+
sendMessageWithAttachments(conversationId: string, content: string | null, files: File[]): Promise<ChatMessage[]>;
|
|
125
168
|
closeConversation(conversationId: string): Promise<void>;
|
|
126
169
|
private get;
|
|
127
170
|
private post;
|
|
@@ -197,4 +240,4 @@ declare namespace storage {
|
|
|
197
240
|
export { storage_clearToken as clearToken, storage_getItem as getItem, storage_loadToken as loadToken, storage_removeItem as removeItem, storage_saveToken as saveToken, storage_setItem as setItem };
|
|
198
241
|
}
|
|
199
242
|
|
|
200
|
-
export { AgentUpChatClient, type AgentUpChatClientConfig, AgentUpChatHub, type AgentUpChatHubHandlers, type AgentUpChatHubOptions, type ChatConnectionState, type ChatMessage, type ChatMessageChunk, type ConversationDetails, type ConversationStatus, type PublicAgent, type SenderType, type UserTypingPayload, type VisitorConversationSummary, type VisitorTokenResponse, WebChatAuthError, storage };
|
|
243
|
+
export { AgentUpChatClient, type AgentUpChatClientConfig, AgentUpChatHub, type AgentUpChatHubHandlers, type AgentUpChatHubOptions, type ChatConnectionState, type ChatMessage, type ChatMessageChunk, type ConversationDetails, type ConversationStatus, type MessageType, type OptimisticAttachmentPreview, type PublicAgent, type SenderType, type UserTypingPayload, type VisitorConversationSummary, type VisitorTokenResponse, type WebChatAttachmentLimits, WebChatAuthError, storage };
|
package/dist/index.d.ts
CHANGED
|
@@ -34,6 +34,13 @@ interface VisitorTokenResponse {
|
|
|
34
34
|
}
|
|
35
35
|
/** Mirrors `Domain.Entities.SenderType`. */
|
|
36
36
|
type SenderType = 'Customer' | 'Agent' | 'AI' | 'System';
|
|
37
|
+
/** Mirrors `Domain.Entities.MessageType`. */
|
|
38
|
+
type MessageType = 'Text' | 'Image' | 'Document' | 'Audio' | 'Video' | string;
|
|
39
|
+
/** Client-only previews inside a single optimistic multi-file row (stripped when server echoes). */
|
|
40
|
+
interface OptimisticAttachmentPreview {
|
|
41
|
+
fileName: string;
|
|
42
|
+
mimeType: string;
|
|
43
|
+
}
|
|
37
44
|
interface ChatMessage {
|
|
38
45
|
id: string;
|
|
39
46
|
conversationId: string;
|
|
@@ -41,8 +48,31 @@ interface ChatMessage {
|
|
|
41
48
|
senderType: SenderType;
|
|
42
49
|
senderName?: string | null;
|
|
43
50
|
createdAt: string;
|
|
51
|
+
/** Present for file attachment messages; absolute or relative URL to the stored file. */
|
|
52
|
+
mediaUrl?: string | null;
|
|
53
|
+
/** MIME type of the attached file (e.g. `image/jpeg`, `application/pdf`). */
|
|
54
|
+
mediaMimeType?: string | null;
|
|
55
|
+
/** Message kind; `Text` for plain text, `Image` / `Document` for attachments. */
|
|
56
|
+
messageType?: MessageType;
|
|
44
57
|
/** Optional: AI tool calls / structured payload (passthrough). */
|
|
45
58
|
metadata?: Record<string, unknown> | null;
|
|
59
|
+
/**
|
|
60
|
+
* When set on a file row, render in the same bubble as the message with this id
|
|
61
|
+
* (caption from the same multi-part send). Mirrors `WebChatMessageDto.AttachmentParentMessageId`.
|
|
62
|
+
*/
|
|
63
|
+
attachmentParentMessageId?: string | null;
|
|
64
|
+
/**
|
|
65
|
+
* Client-only: one placeholder row for caption + N files until `sendMessageWithAttachments`
|
|
66
|
+
* resolves; then replaced by the server’s N (or N+1) persisted messages.
|
|
67
|
+
*/
|
|
68
|
+
optimisticAttachments?: OptimisticAttachmentPreview[] | null;
|
|
69
|
+
}
|
|
70
|
+
/** Static attachment limits; mirror `appsettings.json WebChat` section defaults. */
|
|
71
|
+
interface WebChatAttachmentLimits {
|
|
72
|
+
maxFilesPerMessage: number;
|
|
73
|
+
maxFileSizeMB: number;
|
|
74
|
+
/** Allowed file extensions including the dot, e.g. `[".pdf", ".jpg"]`. */
|
|
75
|
+
allowedExtensions: string[];
|
|
46
76
|
}
|
|
47
77
|
/** Matches `TypingIndicatorDto` on hub event `UserTyping` (camelCase JSON). */
|
|
48
78
|
interface UserTypingPayload {
|
|
@@ -118,10 +148,23 @@ declare class AgentUpChatClient {
|
|
|
118
148
|
*/
|
|
119
149
|
getMessages(conversationId: string, signal?: AbortSignal): Promise<ChatMessage[]>;
|
|
120
150
|
/**
|
|
121
|
-
* REST fallback for sending
|
|
122
|
-
*
|
|
151
|
+
* REST fallback for sending a plain text message (legacy / bubble widget path).
|
|
152
|
+
* The API binds `[FromForm] content` — multipart/form-data is required.
|
|
123
153
|
*/
|
|
124
154
|
sendMessage(conversationId: string, content: string): Promise<ChatMessage>;
|
|
155
|
+
/**
|
|
156
|
+
* Send a message with optional text caption and up to 5 file attachments via
|
|
157
|
+
* `POST /api/webchat/messages/{conversationId}/multi` (multipart/form-data).
|
|
158
|
+
*
|
|
159
|
+
* - `content` may be `null` when sending files without a caption.
|
|
160
|
+
* - Validation (file count, size, type) is enforced server-side and surfaced as
|
|
161
|
+
* structured `Error` messages if the request fails.
|
|
162
|
+
* - Returns one `ChatMessage` per persisted row (text caption + one per file).
|
|
163
|
+
*
|
|
164
|
+
* File-only sends (no caption) do NOT trigger the AI pipeline; the user must
|
|
165
|
+
* follow up with a text message to get a RAG-augmented reply.
|
|
166
|
+
*/
|
|
167
|
+
sendMessageWithAttachments(conversationId: string, content: string | null, files: File[]): Promise<ChatMessage[]>;
|
|
125
168
|
closeConversation(conversationId: string): Promise<void>;
|
|
126
169
|
private get;
|
|
127
170
|
private post;
|
|
@@ -197,4 +240,4 @@ declare namespace storage {
|
|
|
197
240
|
export { storage_clearToken as clearToken, storage_getItem as getItem, storage_loadToken as loadToken, storage_removeItem as removeItem, storage_saveToken as saveToken, storage_setItem as setItem };
|
|
198
241
|
}
|
|
199
242
|
|
|
200
|
-
export { AgentUpChatClient, type AgentUpChatClientConfig, AgentUpChatHub, type AgentUpChatHubHandlers, type AgentUpChatHubOptions, type ChatConnectionState, type ChatMessage, type ChatMessageChunk, type ConversationDetails, type ConversationStatus, type PublicAgent, type SenderType, type UserTypingPayload, type VisitorConversationSummary, type VisitorTokenResponse, WebChatAuthError, storage };
|
|
243
|
+
export { AgentUpChatClient, type AgentUpChatClientConfig, AgentUpChatHub, type AgentUpChatHubHandlers, type AgentUpChatHubOptions, type ChatConnectionState, type ChatMessage, type ChatMessageChunk, type ConversationDetails, type ConversationStatus, type MessageType, type OptimisticAttachmentPreview, type PublicAgent, type SenderType, type UserTypingPayload, type VisitorConversationSummary, type VisitorTokenResponse, type WebChatAttachmentLimits, WebChatAuthError, storage };
|
package/dist/index.js
CHANGED
|
@@ -57,14 +57,39 @@ var AgentUpChatClient = class {
|
|
|
57
57
|
}));
|
|
58
58
|
}
|
|
59
59
|
/**
|
|
60
|
-
* REST fallback for sending
|
|
61
|
-
*
|
|
60
|
+
* REST fallback for sending a plain text message (legacy / bubble widget path).
|
|
61
|
+
* The API binds `[FromForm] content` — multipart/form-data is required.
|
|
62
62
|
*/
|
|
63
63
|
sendMessage(conversationId, content) {
|
|
64
64
|
const form = new FormData();
|
|
65
65
|
form.append("content", content);
|
|
66
66
|
return this.postForm(`/api/webchat/messages/${conversationId}`, form);
|
|
67
67
|
}
|
|
68
|
+
/**
|
|
69
|
+
* Send a message with optional text caption and up to 5 file attachments via
|
|
70
|
+
* `POST /api/webchat/messages/{conversationId}/multi` (multipart/form-data).
|
|
71
|
+
*
|
|
72
|
+
* - `content` may be `null` when sending files without a caption.
|
|
73
|
+
* - Validation (file count, size, type) is enforced server-side and surfaced as
|
|
74
|
+
* structured `Error` messages if the request fails.
|
|
75
|
+
* - Returns one `ChatMessage` per persisted row (text caption + one per file).
|
|
76
|
+
*
|
|
77
|
+
* File-only sends (no caption) do NOT trigger the AI pipeline; the user must
|
|
78
|
+
* follow up with a text message to get a RAG-augmented reply.
|
|
79
|
+
*/
|
|
80
|
+
async sendMessageWithAttachments(conversationId, content, files) {
|
|
81
|
+
const form = new FormData();
|
|
82
|
+
if (content?.trim()) {
|
|
83
|
+
form.append("content", content.trim());
|
|
84
|
+
}
|
|
85
|
+
for (const file of files) {
|
|
86
|
+
form.append("files", file, file.name);
|
|
87
|
+
}
|
|
88
|
+
return this.postForm(
|
|
89
|
+
`/api/webchat/messages/${conversationId}/multi`,
|
|
90
|
+
form
|
|
91
|
+
);
|
|
92
|
+
}
|
|
68
93
|
closeConversation(conversationId) {
|
|
69
94
|
return this.post(`/api/webchat/conversations/${conversationId}/close`, {});
|
|
70
95
|
}
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/api-client.ts","../src/signalr-client.ts","../src/storage.ts"],"sourcesContent":["import type {\r\n AgentUpChatClientConfig,\r\n ChatMessage,\r\n ConversationDetails,\r\n PublicAgent,\r\n VisitorConversationSummary\r\n} from './types.js';\r\n\r\n/** Thrown when the visitor token is expired/invalid; UIs should surface a re-auth flow. */\r\nexport class WebChatAuthError extends Error {\r\n constructor(message: string) {\r\n super(message);\r\n this.name = 'WebChatAuthError';\r\n }\r\n}\r\n\r\n/**\r\n * Minimal REST client for the embedded web chat app endpoints. Wraps fetch with\r\n * the visitor JWT and unwraps the AgentUp `ApiResponse<T>` envelope automatically.\r\n *\r\n * Endpoints used:\r\n * - `GET /api/webchat/agents`\r\n * - `GET /api/webchat/conversations`\r\n * - `POST /api/webchat/conversations`\r\n * - `GET /api/webchat/conversations/{id}` (includes `messages` history)\r\n * - `POST /api/webchat/messages/{conversationId}` (send)\r\n * - `POST /api/webchat/conversations/{id}/close`\r\n */\r\nexport class AgentUpChatClient {\r\n private readonly baseUrl: string;\r\n private readonly tenantId?: string;\r\n private readonly fetchImpl: typeof fetch;\r\n private visitorToken: string;\r\n\r\n constructor(config: AgentUpChatClientConfig) {\r\n if (!config.apiUrl) throw new Error('AgentUpChatClient: apiUrl is required');\r\n if (!config.visitorToken) throw new Error('AgentUpChatClient: visitorToken is required');\r\n this.baseUrl = config.apiUrl.replace(/\\/$/, '');\r\n this.visitorToken = config.visitorToken;\r\n this.tenantId = config.tenantId;\r\n this.fetchImpl = config.fetch ?? fetch.bind(globalThis);\r\n }\r\n\r\n /** Refresh the visitor token in place (e.g. after a silent re-auth round-trip). */\r\n setVisitorToken(token: string): void {\r\n if (!token) throw new Error('AgentUpChatClient.setVisitorToken: empty token');\r\n this.visitorToken = token;\r\n }\r\n\r\n listAgents(signal?: AbortSignal): Promise<PublicAgent[]> {\r\n return this.get<PublicAgent[]>('/api/webchat/agents', signal);\r\n }\r\n\r\n listConversations(signal?: AbortSignal): Promise<VisitorConversationSummary[]> {\r\n return this.get<VisitorConversationSummary[]>('/api/webchat/conversations', signal);\r\n }\r\n\r\n createConversation(agentCode: string): Promise<VisitorConversationSummary> {\r\n return this.post<VisitorConversationSummary>('/api/webchat/conversations', { agentCode });\r\n }\r\n\r\n getConversation(conversationId: string, signal?: AbortSignal): Promise<ConversationDetails> {\r\n return this.get<ConversationDetails>(`/api/webchat/conversations/${conversationId}`, signal);\r\n }\r\n\r\n /**\r\n * Message history is returned on `GET /api/webchat/conversations/{id}` (there is no GET on\r\n * `/api/webchat/messages/{id}` — that route is POST-only for sending).\r\n */\r\n async getMessages(conversationId: string, signal?: AbortSignal): Promise<ChatMessage[]> {\r\n const conv = await this.get<ConversationDetails>(`/api/webchat/conversations/${conversationId}`, signal);\r\n const raw = conv.messages;\r\n if (!raw?.length) return [];\r\n return raw.map((m) => ({\r\n ...m,\r\n conversationId: m.conversationId || conversationId\r\n }));\r\n }\r\n\r\n /**\r\n * REST fallback for sending. The API binds `[FromForm] content` (see `WebChatController`),\r\n * not JSON — multipart/form-data is required.\r\n */\r\n sendMessage(conversationId: string, content: string): Promise<ChatMessage> {\r\n const form = new FormData();\r\n form.append('content', content);\r\n return this.postForm<ChatMessage>(`/api/webchat/messages/${conversationId}`, form);\r\n }\r\n\r\n closeConversation(conversationId: string): Promise<void> {\r\n return this.post<void>(`/api/webchat/conversations/${conversationId}/close`, {});\r\n }\r\n\r\n // ---------------- internals ----------------\r\n\r\n private async get<T>(path: string, signal?: AbortSignal): Promise<T> {\r\n const res = await this.fetchImpl(`${this.baseUrl}${path}`, {\r\n method: 'GET',\r\n headers: this.headers(),\r\n signal\r\n });\r\n return this.unwrap<T>(res);\r\n }\r\n\r\n private async post<T>(path: string, body: unknown): Promise<T> {\r\n const res = await this.fetchImpl(`${this.baseUrl}${path}`, {\r\n method: 'POST',\r\n headers: { ...this.headers(), 'Content-Type': 'application/json' },\r\n body: JSON.stringify(body ?? {})\r\n });\r\n return this.unwrap<T>(res);\r\n }\r\n\r\n /** POST multipart/form-data (do not set Content-Type — boundary is set by the runtime). */\r\n private async postForm<T>(path: string, formData: FormData): Promise<T> {\r\n const res = await this.fetchImpl(`${this.baseUrl}${path}`, {\r\n method: 'POST',\r\n headers: this.headers(),\r\n body: formData\r\n });\r\n return this.unwrap<T>(res);\r\n }\r\n\r\n private headers(): Record<string, string> {\r\n const h: Record<string, string> = {\r\n Authorization: `Bearer ${this.visitorToken}`,\r\n Accept: 'application/json'\r\n };\r\n if (this.tenantId) h['X-Tenant-Id'] = this.tenantId;\r\n return h;\r\n }\r\n\r\n private async unwrap<T>(res: Response): Promise<T> {\r\n if (res.status === 401) {\r\n throw new WebChatAuthError('Visitor token expired or invalid');\r\n }\r\n if (!res.ok) {\r\n let detail = '';\r\n try {\r\n const text = await res.text();\r\n detail = text.length > 200 ? text.slice(0, 200) + '…' : text;\r\n } catch {\r\n // ignore body parse errors\r\n }\r\n throw new Error(`AgentUp request ${res.url} failed (${res.status}): ${detail}`);\r\n }\r\n if (res.status === 204) return undefined as T;\r\n\r\n const json = (await res.json()) as unknown;\r\n // Backend wraps responses as { success, result } except for a few skipped paths.\r\n if (\r\n json &&\r\n typeof json === 'object' &&\r\n 'success' in (json as Record<string, unknown>) &&\r\n 'result' in (json as Record<string, unknown>)\r\n ) {\r\n const env = json as { success: boolean; result: T; message?: string };\r\n if (!env.success) {\r\n throw new Error(env.message ?? 'AgentUp request failed');\r\n }\r\n return env.result;\r\n }\r\n return json as T;\r\n }\r\n}\r\n","import {\r\n HubConnection,\r\n HubConnectionBuilder,\r\n HubConnectionState,\r\n LogLevel\r\n} from '@microsoft/signalr';\r\n\r\nimport type { ChatMessage, ChatMessageChunk, UserTypingPayload } from './types.js';\r\n\r\n/** Connection lifecycle states surfaced to UI layers. */\r\nexport type ChatConnectionState = 'idle' | 'connecting' | 'connected' | 'disconnected' | 'reconnecting';\r\n\r\nexport interface AgentUpChatHubOptions {\r\n /** Base URL of the AgentUp API. */\r\n apiUrl: string;\r\n /** Provides the latest visitor JWT (called on initial connect AND on reconnect). */\r\n accessTokenFactory: () => string | Promise<string>;\r\n}\r\n\r\nexport interface AgentUpChatHubHandlers {\r\n onMessage?: (message: ChatMessage) => void;\r\n /** AI/agent streaming tokens before the final `ReceiveMessage`. */\r\n onMessageChunk?: (chunk: ChatMessageChunk) => void;\r\n /** Hub `UserTyping` — includes AI (`userId` `\"ai\"`) and human agents. */\r\n onUserTyping?: (payload: UserTypingPayload) => void;\r\n /** Hub `UserStoppedTyping` — argument is `userId` (e.g. `\"ai\"`). */\r\n onUserStoppedTyping?: (userId: string) => void;\r\n onConnectionStateChanged?: (state: ChatConnectionState) => void;\r\n onError?: (error: unknown) => void;\r\n}\r\n\r\n/**\r\n * Thin SignalR client for the AgentUp ChatHub. Intentionally narrow surface — the\r\n * UI layer owns active-conversation tracking and group join/leave logic.\r\n *\r\n * Server methods used:\r\n * - `JoinConversation(conversationId)` / `LeaveConversation(conversationId)`\r\n * - `SendMessage(conversationId, content)`\r\n * - `Typing(conversationId, isTyping)`\r\n *\r\n * Server callbacks consumed:\r\n * - `ReceiveMessage`, `ReceiveMessageChunk` (streaming), `UserTyping`, `UserStoppedTyping`, `Error`.\r\n */\r\nexport class AgentUpChatHub {\r\n private readonly options: AgentUpChatHubOptions;\r\n private readonly handlers: AgentUpChatHubHandlers;\r\n private connection: HubConnection | null = null;\r\n private state: ChatConnectionState = 'idle';\r\n\r\n constructor(options: AgentUpChatHubOptions, handlers: AgentUpChatHubHandlers = {}) {\r\n this.options = options;\r\n this.handlers = handlers;\r\n }\r\n\r\n get currentState(): ChatConnectionState {\r\n return this.state;\r\n }\r\n\r\n async start(): Promise<void> {\r\n if (this.connection && this.connection.state === HubConnectionState.Connected) {\r\n return;\r\n }\r\n this.setState('connecting');\r\n\r\n const base = this.options.apiUrl.replace(/\\/$/, '');\r\n /** Must match ASP.NET `MapHub<ChatHub>(\"/api/chatHub\")` (same path as legacy widget). */\r\n const url = `${base}/api/chatHub`;\r\n this.connection = new HubConnectionBuilder()\r\n .withUrl(url, {\r\n accessTokenFactory: () => Promise.resolve(this.options.accessTokenFactory())\r\n })\r\n .withAutomaticReconnect()\r\n .configureLogging(LogLevel.Warning)\r\n .build();\r\n\r\n this.connection.on('ReceiveMessage', (msg: ChatMessage) => this.handlers.onMessage?.(msg));\r\n this.connection.on('ReceiveMessageChunk', (chunk: ChatMessageChunk) =>\r\n this.handlers.onMessageChunk?.(chunk)\r\n );\r\n this.connection.on('UserTyping', (payload: UserTypingPayload) =>\r\n this.handlers.onUserTyping?.(payload)\r\n );\r\n this.connection.on('UserStoppedTyping', (userId: string) =>\r\n this.handlers.onUserStoppedTyping?.(userId)\r\n );\r\n this.connection.on('Error', (msg: string) => this.handlers.onError?.(new Error(msg)));\r\n\r\n this.connection.onreconnecting(() => this.setState('reconnecting'));\r\n this.connection.onreconnected(() => this.setState('connected'));\r\n this.connection.onclose(() => this.setState('disconnected'));\r\n\r\n await this.connection.start();\r\n this.setState('connected');\r\n }\r\n\r\n async stop(): Promise<void> {\r\n if (this.connection) {\r\n await this.connection.stop();\r\n this.connection = null;\r\n }\r\n this.setState('idle');\r\n }\r\n\r\n async joinConversation(conversationId: string): Promise<void> {\r\n await this.ensureConnected();\r\n await this.connection!.invoke('JoinConversation', conversationId);\r\n }\r\n\r\n async leaveConversation(conversationId: string): Promise<void> {\r\n if (this.connection?.state !== HubConnectionState.Connected) return;\r\n await this.connection.invoke('LeaveConversation', conversationId);\r\n }\r\n\r\n async sendMessage(conversationId: string, content: string): Promise<void> {\r\n await this.ensureConnected();\r\n await this.connection!.invoke('SendMessage', conversationId, content);\r\n }\r\n\r\n async sendTyping(conversationId: string, isTyping: boolean): Promise<void> {\r\n if (this.connection?.state !== HubConnectionState.Connected) return;\r\n await this.connection.invoke('Typing', conversationId, isTyping);\r\n }\r\n\r\n private async ensureConnected(): Promise<void> {\r\n if (!this.connection) {\r\n await this.start();\r\n return;\r\n }\r\n if (this.connection.state !== HubConnectionState.Connected) {\r\n await this.connection.start();\r\n this.setState('connected');\r\n }\r\n }\r\n\r\n private setState(next: ChatConnectionState): void {\r\n if (this.state === next) return;\r\n this.state = next;\r\n this.handlers.onConnectionStateChanged?.(next);\r\n }\r\n}\r\n","/**\r\n * Local storage helper for the embedded chat app. Keeps ephemeral UI state\r\n * (selected conversation id, sidebar collapsed, etc.) outside React-style\r\n * components so the choice of UI framework stays flexible.\r\n *\r\n * Storage is **not** used for the visitor token by default — tokens are short\r\n * lived and should be re-minted from the host backend on demand. Hosts can\r\n * still call {@link saveToken} if they want browser refresh persistence.\r\n */\r\nconst PREFIX = 'agentup.webchat:';\r\n\r\nexport function getItem(key: string): string | null {\r\n try {\r\n return localStorage.getItem(PREFIX + key);\r\n } catch {\r\n return null;\r\n }\r\n}\r\n\r\nexport function setItem(key: string, value: string): void {\r\n try {\r\n localStorage.setItem(PREFIX + key, value);\r\n } catch {\r\n // Storage may be disabled (private mode); silently degrade.\r\n }\r\n}\r\n\r\nexport function removeItem(key: string): void {\r\n try {\r\n localStorage.removeItem(PREFIX + key);\r\n } catch {\r\n // ignore\r\n }\r\n}\r\n\r\n/** Convenience wrapper for the visitor token (host-controlled persistence). */\r\nexport function saveToken(token: string): void {\r\n setItem('visitor_token', token);\r\n}\r\n\r\nexport function loadToken(): string | null {\r\n return getItem('visitor_token');\r\n}\r\n\r\nexport function clearToken(): void {\r\n removeItem('visitor_token');\r\n}\r\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/api-client.ts","../src/signalr-client.ts","../src/storage.ts"],"sourcesContent":["import type {\r\n AgentUpChatClientConfig,\r\n ChatMessage,\r\n ConversationDetails,\r\n PublicAgent,\r\n VisitorConversationSummary\r\n} from './types.js';\r\n\r\n/** Thrown when the visitor token is expired/invalid; UIs should surface a re-auth flow. */\r\nexport class WebChatAuthError extends Error {\r\n constructor(message: string) {\r\n super(message);\r\n this.name = 'WebChatAuthError';\r\n }\r\n}\r\n\r\n/**\r\n * Minimal REST client for the embedded web chat app endpoints. Wraps fetch with\r\n * the visitor JWT and unwraps the AgentUp `ApiResponse<T>` envelope automatically.\r\n *\r\n * Endpoints used:\r\n * - `GET /api/webchat/agents`\r\n * - `GET /api/webchat/conversations`\r\n * - `POST /api/webchat/conversations`\r\n * - `GET /api/webchat/conversations/{id}` (includes `messages` history)\r\n * - `POST /api/webchat/messages/{conversationId}` (send)\r\n * - `POST /api/webchat/conversations/{id}/close`\r\n */\r\nexport class AgentUpChatClient {\r\n private readonly baseUrl: string;\r\n private readonly tenantId?: string;\r\n private readonly fetchImpl: typeof fetch;\r\n private visitorToken: string;\r\n\r\n constructor(config: AgentUpChatClientConfig) {\r\n if (!config.apiUrl) throw new Error('AgentUpChatClient: apiUrl is required');\r\n if (!config.visitorToken) throw new Error('AgentUpChatClient: visitorToken is required');\r\n this.baseUrl = config.apiUrl.replace(/\\/$/, '');\r\n this.visitorToken = config.visitorToken;\r\n this.tenantId = config.tenantId;\r\n this.fetchImpl = config.fetch ?? fetch.bind(globalThis);\r\n }\r\n\r\n /** Refresh the visitor token in place (e.g. after a silent re-auth round-trip). */\r\n setVisitorToken(token: string): void {\r\n if (!token) throw new Error('AgentUpChatClient.setVisitorToken: empty token');\r\n this.visitorToken = token;\r\n }\r\n\r\n listAgents(signal?: AbortSignal): Promise<PublicAgent[]> {\r\n return this.get<PublicAgent[]>('/api/webchat/agents', signal);\r\n }\r\n\r\n listConversations(signal?: AbortSignal): Promise<VisitorConversationSummary[]> {\r\n return this.get<VisitorConversationSummary[]>('/api/webchat/conversations', signal);\r\n }\r\n\r\n createConversation(agentCode: string): Promise<VisitorConversationSummary> {\r\n return this.post<VisitorConversationSummary>('/api/webchat/conversations', { agentCode });\r\n }\r\n\r\n getConversation(conversationId: string, signal?: AbortSignal): Promise<ConversationDetails> {\r\n return this.get<ConversationDetails>(`/api/webchat/conversations/${conversationId}`, signal);\r\n }\r\n\r\n /**\r\n * Message history is returned on `GET /api/webchat/conversations/{id}` (there is no GET on\r\n * `/api/webchat/messages/{id}` — that route is POST-only for sending).\r\n */\r\n async getMessages(conversationId: string, signal?: AbortSignal): Promise<ChatMessage[]> {\r\n const conv = await this.get<ConversationDetails>(`/api/webchat/conversations/${conversationId}`, signal);\r\n const raw = conv.messages;\r\n if (!raw?.length) return [];\r\n return raw.map((m) => ({\r\n ...m,\r\n conversationId: m.conversationId || conversationId\r\n }));\r\n }\r\n\r\n /**\r\n * REST fallback for sending a plain text message (legacy / bubble widget path).\r\n * The API binds `[FromForm] content` — multipart/form-data is required.\r\n */\r\n sendMessage(conversationId: string, content: string): Promise<ChatMessage> {\r\n const form = new FormData();\r\n form.append('content', content);\r\n return this.postForm<ChatMessage>(`/api/webchat/messages/${conversationId}`, form);\r\n }\r\n\r\n /**\r\n * Send a message with optional text caption and up to 5 file attachments via\r\n * `POST /api/webchat/messages/{conversationId}/multi` (multipart/form-data).\r\n *\r\n * - `content` may be `null` when sending files without a caption.\r\n * - Validation (file count, size, type) is enforced server-side and surfaced as\r\n * structured `Error` messages if the request fails.\r\n * - Returns one `ChatMessage` per persisted row (text caption + one per file).\r\n *\r\n * File-only sends (no caption) do NOT trigger the AI pipeline; the user must\r\n * follow up with a text message to get a RAG-augmented reply.\r\n */\r\n async sendMessageWithAttachments(\r\n conversationId: string,\r\n content: string | null,\r\n files: File[]\r\n ): Promise<ChatMessage[]> {\r\n const form = new FormData();\r\n if (content?.trim()) {\r\n form.append('content', content.trim());\r\n }\r\n for (const file of files) {\r\n form.append('files', file, file.name);\r\n }\r\n return this.postForm<ChatMessage[]>(\r\n `/api/webchat/messages/${conversationId}/multi`,\r\n form\r\n );\r\n }\r\n\r\n closeConversation(conversationId: string): Promise<void> {\r\n return this.post<void>(`/api/webchat/conversations/${conversationId}/close`, {});\r\n }\r\n\r\n // ---------------- internals ----------------\r\n\r\n private async get<T>(path: string, signal?: AbortSignal): Promise<T> {\r\n const res = await this.fetchImpl(`${this.baseUrl}${path}`, {\r\n method: 'GET',\r\n headers: this.headers(),\r\n signal\r\n });\r\n return this.unwrap<T>(res);\r\n }\r\n\r\n private async post<T>(path: string, body: unknown): Promise<T> {\r\n const res = await this.fetchImpl(`${this.baseUrl}${path}`, {\r\n method: 'POST',\r\n headers: { ...this.headers(), 'Content-Type': 'application/json' },\r\n body: JSON.stringify(body ?? {})\r\n });\r\n return this.unwrap<T>(res);\r\n }\r\n\r\n /** POST multipart/form-data (do not set Content-Type — boundary is set by the runtime). */\r\n private async postForm<T>(path: string, formData: FormData): Promise<T> {\r\n const res = await this.fetchImpl(`${this.baseUrl}${path}`, {\r\n method: 'POST',\r\n headers: this.headers(),\r\n body: formData\r\n });\r\n return this.unwrap<T>(res);\r\n }\r\n\r\n private headers(): Record<string, string> {\r\n const h: Record<string, string> = {\r\n Authorization: `Bearer ${this.visitorToken}`,\r\n Accept: 'application/json'\r\n };\r\n if (this.tenantId) h['X-Tenant-Id'] = this.tenantId;\r\n return h;\r\n }\r\n\r\n private async unwrap<T>(res: Response): Promise<T> {\r\n if (res.status === 401) {\r\n throw new WebChatAuthError('Visitor token expired or invalid');\r\n }\r\n if (!res.ok) {\r\n let detail = '';\r\n try {\r\n const text = await res.text();\r\n detail = text.length > 200 ? text.slice(0, 200) + '…' : text;\r\n } catch {\r\n // ignore body parse errors\r\n }\r\n throw new Error(`AgentUp request ${res.url} failed (${res.status}): ${detail}`);\r\n }\r\n if (res.status === 204) return undefined as T;\r\n\r\n const json = (await res.json()) as unknown;\r\n // Backend wraps responses as { success, result } except for a few skipped paths.\r\n if (\r\n json &&\r\n typeof json === 'object' &&\r\n 'success' in (json as Record<string, unknown>) &&\r\n 'result' in (json as Record<string, unknown>)\r\n ) {\r\n const env = json as { success: boolean; result: T; message?: string };\r\n if (!env.success) {\r\n throw new Error(env.message ?? 'AgentUp request failed');\r\n }\r\n return env.result;\r\n }\r\n return json as T;\r\n }\r\n}\r\n","import {\r\n HubConnection,\r\n HubConnectionBuilder,\r\n HubConnectionState,\r\n LogLevel\r\n} from '@microsoft/signalr';\r\n\r\nimport type { ChatMessage, ChatMessageChunk, UserTypingPayload } from './types.js';\r\n\r\n/** Connection lifecycle states surfaced to UI layers. */\r\nexport type ChatConnectionState = 'idle' | 'connecting' | 'connected' | 'disconnected' | 'reconnecting';\r\n\r\nexport interface AgentUpChatHubOptions {\r\n /** Base URL of the AgentUp API. */\r\n apiUrl: string;\r\n /** Provides the latest visitor JWT (called on initial connect AND on reconnect). */\r\n accessTokenFactory: () => string | Promise<string>;\r\n}\r\n\r\nexport interface AgentUpChatHubHandlers {\r\n onMessage?: (message: ChatMessage) => void;\r\n /** AI/agent streaming tokens before the final `ReceiveMessage`. */\r\n onMessageChunk?: (chunk: ChatMessageChunk) => void;\r\n /** Hub `UserTyping` — includes AI (`userId` `\"ai\"`) and human agents. */\r\n onUserTyping?: (payload: UserTypingPayload) => void;\r\n /** Hub `UserStoppedTyping` — argument is `userId` (e.g. `\"ai\"`). */\r\n onUserStoppedTyping?: (userId: string) => void;\r\n onConnectionStateChanged?: (state: ChatConnectionState) => void;\r\n onError?: (error: unknown) => void;\r\n}\r\n\r\n/**\r\n * Thin SignalR client for the AgentUp ChatHub. Intentionally narrow surface — the\r\n * UI layer owns active-conversation tracking and group join/leave logic.\r\n *\r\n * Server methods used:\r\n * - `JoinConversation(conversationId)` / `LeaveConversation(conversationId)`\r\n * - `SendMessage(conversationId, content)`\r\n * - `Typing(conversationId, isTyping)`\r\n *\r\n * Server callbacks consumed:\r\n * - `ReceiveMessage`, `ReceiveMessageChunk` (streaming), `UserTyping`, `UserStoppedTyping`, `Error`.\r\n */\r\nexport class AgentUpChatHub {\r\n private readonly options: AgentUpChatHubOptions;\r\n private readonly handlers: AgentUpChatHubHandlers;\r\n private connection: HubConnection | null = null;\r\n private state: ChatConnectionState = 'idle';\r\n\r\n constructor(options: AgentUpChatHubOptions, handlers: AgentUpChatHubHandlers = {}) {\r\n this.options = options;\r\n this.handlers = handlers;\r\n }\r\n\r\n get currentState(): ChatConnectionState {\r\n return this.state;\r\n }\r\n\r\n async start(): Promise<void> {\r\n if (this.connection && this.connection.state === HubConnectionState.Connected) {\r\n return;\r\n }\r\n this.setState('connecting');\r\n\r\n const base = this.options.apiUrl.replace(/\\/$/, '');\r\n /** Must match ASP.NET `MapHub<ChatHub>(\"/api/chatHub\")` (same path as legacy widget). */\r\n const url = `${base}/api/chatHub`;\r\n this.connection = new HubConnectionBuilder()\r\n .withUrl(url, {\r\n accessTokenFactory: () => Promise.resolve(this.options.accessTokenFactory())\r\n })\r\n .withAutomaticReconnect()\r\n .configureLogging(LogLevel.Warning)\r\n .build();\r\n\r\n this.connection.on('ReceiveMessage', (msg: ChatMessage) => this.handlers.onMessage?.(msg));\r\n this.connection.on('ReceiveMessageChunk', (chunk: ChatMessageChunk) =>\r\n this.handlers.onMessageChunk?.(chunk)\r\n );\r\n this.connection.on('UserTyping', (payload: UserTypingPayload) =>\r\n this.handlers.onUserTyping?.(payload)\r\n );\r\n this.connection.on('UserStoppedTyping', (userId: string) =>\r\n this.handlers.onUserStoppedTyping?.(userId)\r\n );\r\n this.connection.on('Error', (msg: string) => this.handlers.onError?.(new Error(msg)));\r\n\r\n this.connection.onreconnecting(() => this.setState('reconnecting'));\r\n this.connection.onreconnected(() => this.setState('connected'));\r\n this.connection.onclose(() => this.setState('disconnected'));\r\n\r\n await this.connection.start();\r\n this.setState('connected');\r\n }\r\n\r\n async stop(): Promise<void> {\r\n if (this.connection) {\r\n await this.connection.stop();\r\n this.connection = null;\r\n }\r\n this.setState('idle');\r\n }\r\n\r\n async joinConversation(conversationId: string): Promise<void> {\r\n await this.ensureConnected();\r\n await this.connection!.invoke('JoinConversation', conversationId);\r\n }\r\n\r\n async leaveConversation(conversationId: string): Promise<void> {\r\n if (this.connection?.state !== HubConnectionState.Connected) return;\r\n await this.connection.invoke('LeaveConversation', conversationId);\r\n }\r\n\r\n async sendMessage(conversationId: string, content: string): Promise<void> {\r\n await this.ensureConnected();\r\n await this.connection!.invoke('SendMessage', conversationId, content);\r\n }\r\n\r\n async sendTyping(conversationId: string, isTyping: boolean): Promise<void> {\r\n if (this.connection?.state !== HubConnectionState.Connected) return;\r\n await this.connection.invoke('Typing', conversationId, isTyping);\r\n }\r\n\r\n private async ensureConnected(): Promise<void> {\r\n if (!this.connection) {\r\n await this.start();\r\n return;\r\n }\r\n if (this.connection.state !== HubConnectionState.Connected) {\r\n await this.connection.start();\r\n this.setState('connected');\r\n }\r\n }\r\n\r\n private setState(next: ChatConnectionState): void {\r\n if (this.state === next) return;\r\n this.state = next;\r\n this.handlers.onConnectionStateChanged?.(next);\r\n }\r\n}\r\n","/**\r\n * Local storage helper for the embedded chat app. Keeps ephemeral UI state\r\n * (selected conversation id, sidebar collapsed, etc.) outside React-style\r\n * components so the choice of UI framework stays flexible.\r\n *\r\n * Storage is **not** used for the visitor token by default — tokens are short\r\n * lived and should be re-minted from the host backend on demand. Hosts can\r\n * still call {@link saveToken} if they want browser refresh persistence.\r\n */\r\nconst PREFIX = 'agentup.webchat:';\r\n\r\nexport function getItem(key: string): string | null {\r\n try {\r\n return localStorage.getItem(PREFIX + key);\r\n } catch {\r\n return null;\r\n }\r\n}\r\n\r\nexport function setItem(key: string, value: string): void {\r\n try {\r\n localStorage.setItem(PREFIX + key, value);\r\n } catch {\r\n // Storage may be disabled (private mode); silently degrade.\r\n }\r\n}\r\n\r\nexport function removeItem(key: string): void {\r\n try {\r\n localStorage.removeItem(PREFIX + key);\r\n } catch {\r\n // ignore\r\n }\r\n}\r\n\r\n/** Convenience wrapper for the visitor token (host-controlled persistence). */\r\nexport function saveToken(token: string): void {\r\n setItem('visitor_token', token);\r\n}\r\n\r\nexport function loadToken(): string | null {\r\n return getItem('visitor_token');\r\n}\r\n\r\nexport function clearToken(): void {\r\n removeItem('visitor_token');\r\n}\r\n"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agentup-inksen/web-chat-core",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.4",
|
|
4
4
|
"description": "Shared SDK for AgentUp web chat clients (REST + SignalR + types)",
|
|
5
5
|
"license": "PROPRIETARY",
|
|
6
6
|
"type": "module",
|
|
@@ -22,7 +22,9 @@
|
|
|
22
22
|
"build": "tsup src/index.ts --format esm,cjs --dts --clean --sourcemap",
|
|
23
23
|
"dev": "tsup src/index.ts --format esm,cjs --dts --watch --sourcemap",
|
|
24
24
|
"clean": "rimraf dist",
|
|
25
|
-
"lint": "tsc --noEmit"
|
|
25
|
+
"lint": "tsc --noEmit",
|
|
26
|
+
"test": "vitest run",
|
|
27
|
+
"test:watch": "vitest"
|
|
26
28
|
},
|
|
27
29
|
"dependencies": {
|
|
28
30
|
"@microsoft/signalr": "^8.0.0"
|
|
@@ -30,7 +32,8 @@
|
|
|
30
32
|
"devDependencies": {
|
|
31
33
|
"rimraf": "^5.0.5",
|
|
32
34
|
"tsup": "^8.0.0",
|
|
33
|
-
"typescript": "^5.3.0"
|
|
35
|
+
"typescript": "^5.3.0",
|
|
36
|
+
"vitest": "^4.1.6"
|
|
34
37
|
},
|
|
35
38
|
"publishConfig": {
|
|
36
39
|
"access": "restricted"
|