open-chat-studio-widget 0.8.0 → 0.9.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.
- package/README.md +1 -0
- package/dist/cjs/{index-Cf6K60f1.js → index-fFSp-Z_h.js} +3 -3
- package/dist/cjs/{index-Cf6K60f1.js.map → index-fFSp-Z_h.js.map} +1 -1
- package/dist/cjs/loader.cjs.js +2 -2
- package/dist/cjs/open-chat-studio-widget.cjs.entry.js +179 -24
- package/dist/cjs/open-chat-studio-widget.cjs.entry.js.map +1 -1
- package/dist/cjs/open-chat-studio-widget.cjs.js +2 -2
- package/dist/cjs/open-chat-studio-widget.entry.cjs.js.map +1 -1
- package/dist/collection/components/ocs-chat/ocs-chat.js +82 -4
- package/dist/collection/components/ocs-chat/ocs-chat.js.map +1 -1
- package/dist/collection/services/chat-session-service.js +95 -20
- package/dist/collection/services/chat-session-service.js.map +1 -1
- package/dist/collection/services/file-attachment-manager.js +3 -0
- package/dist/collection/services/file-attachment-manager.js.map +1 -1
- package/dist/components/open-chat-studio-widget.js +179 -23
- package/dist/components/open-chat-studio-widget.js.map +1 -1
- package/dist/esm/{index-DXf2dIht.js → index-ythTKHg-.js} +3 -3
- package/dist/esm/{index-DXf2dIht.js.map → index-ythTKHg-.js.map} +1 -1
- package/dist/esm/loader.js +3 -3
- package/dist/esm/open-chat-studio-widget.entry.js +179 -24
- package/dist/esm/open-chat-studio-widget.entry.js.map +1 -1
- package/dist/esm/open-chat-studio-widget.js +3 -3
- package/dist/open-chat-studio-widget/open-chat-studio-widget.entry.esm.js.map +1 -1
- package/dist/open-chat-studio-widget/open-chat-studio-widget.esm.js +1 -1
- package/dist/open-chat-studio-widget/p-2d31a15c.entry.js +4 -0
- package/dist/open-chat-studio-widget/p-2d31a15c.entry.js.map +1 -0
- package/dist/open-chat-studio-widget/{p-DXf2dIht.js → p-ythTKHg-.js} +2 -2
- package/dist/open-chat-studio-widget/{p-DXf2dIht.js.map → p-ythTKHg-.js.map} +1 -1
- package/dist/types/components/ocs-chat/ocs-chat.d.ts +14 -0
- package/dist/types/components.d.ts +8 -0
- package/dist/types/services/chat-session-service.d.ts +23 -0
- package/dist/types/services/file-attachment-manager.d.ts +3 -0
- package/package.json +1 -1
- package/dist/open-chat-studio-widget/p-ff47dabf.entry.js +0 -4
- package/dist/open-chat-studio-widget/p-ff47dabf.entry.js.map +0 -1
|
@@ -1,53 +1,52 @@
|
|
|
1
1
|
import { getCSRFToken } from "../utils/cookies";
|
|
2
|
+
export class SessionAccessError extends Error {
|
|
3
|
+
constructor(status, code, message) {
|
|
4
|
+
super(message);
|
|
5
|
+
this.name = 'SessionAccessError';
|
|
6
|
+
this.status = status;
|
|
7
|
+
this.code = code;
|
|
8
|
+
}
|
|
9
|
+
}
|
|
2
10
|
export class ChatSessionService {
|
|
3
11
|
constructor(options) {
|
|
4
12
|
var _a, _b, _c, _d;
|
|
5
13
|
this.apiBaseUrl = options.apiBaseUrl;
|
|
6
14
|
this.embedKey = options.embedKey;
|
|
7
15
|
this.widgetVersion = options.widgetVersion;
|
|
16
|
+
this.sessionToken = options.sessionToken;
|
|
8
17
|
this.csrfTokenProvider = (_a = options.csrfTokenProvider) !== null && _a !== void 0 ? _a : getCSRFToken;
|
|
9
18
|
this.taskPollingIntervalMs = (_b = options.taskPollingIntervalMs) !== null && _b !== void 0 ? _b : 1000;
|
|
10
19
|
this.taskPollingMaxAttempts = (_c = options.taskPollingMaxAttempts) !== null && _c !== void 0 ? _c : 120;
|
|
11
20
|
this.messagePollingIntervalMs = (_d = options.messagePollingIntervalMs) !== null && _d !== void 0 ? _d : 30000;
|
|
12
21
|
}
|
|
13
22
|
async startSession(requestBody) {
|
|
14
|
-
const response = await
|
|
23
|
+
const response = await this.request(`${this.apiBaseUrl}/api/chat/start/`, {
|
|
15
24
|
method: 'POST',
|
|
16
25
|
headers: this.getJsonHeaders(),
|
|
17
26
|
body: JSON.stringify(requestBody),
|
|
18
27
|
});
|
|
19
28
|
if (!response.ok) {
|
|
20
|
-
|
|
29
|
+
await this.raiseForStatus(response, 'Failed to start session');
|
|
21
30
|
}
|
|
22
31
|
return response.json();
|
|
23
32
|
}
|
|
24
33
|
async sendMessage(sessionId, payload) {
|
|
25
|
-
const response = await
|
|
34
|
+
const response = await this.request(`${this.apiBaseUrl}/api/chat/${sessionId}/message/`, {
|
|
26
35
|
method: 'POST',
|
|
27
36
|
headers: this.getJsonHeaders(),
|
|
28
37
|
body: JSON.stringify(payload),
|
|
29
38
|
});
|
|
30
39
|
if (!response.ok) {
|
|
31
|
-
|
|
40
|
+
await this.raiseForStatus(response, 'Failed to send message');
|
|
32
41
|
}
|
|
33
42
|
return response.json();
|
|
34
43
|
}
|
|
35
44
|
async pollTaskOnce(sessionId, taskId) {
|
|
36
|
-
const response = await
|
|
45
|
+
const response = await this.request(`${this.apiBaseUrl}/api/chat/${sessionId}/${taskId}/poll/`, {
|
|
37
46
|
headers: this.getCommonHeaders(),
|
|
38
47
|
});
|
|
39
48
|
if (!response.ok) {
|
|
40
|
-
|
|
41
|
-
try {
|
|
42
|
-
const data = (await response.json());
|
|
43
|
-
if (data === null || data === void 0 ? void 0 : data.error) {
|
|
44
|
-
errorMessage = data.error;
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
catch (_a) {
|
|
48
|
-
// non-JSON body; keep statusText fallback
|
|
49
|
-
}
|
|
50
|
-
throw new Error(errorMessage);
|
|
49
|
+
await this.raiseForStatus(response, 'Failed to poll task');
|
|
51
50
|
}
|
|
52
51
|
return response.json();
|
|
53
52
|
}
|
|
@@ -107,11 +106,11 @@ export class ChatSessionService {
|
|
|
107
106
|
if (since) {
|
|
108
107
|
url.searchParams.set('since', since);
|
|
109
108
|
}
|
|
110
|
-
const response = await
|
|
109
|
+
const response = await this.request(url.toString(), {
|
|
111
110
|
headers: this.getCommonHeaders(),
|
|
112
111
|
});
|
|
113
112
|
if (!response.ok) {
|
|
114
|
-
|
|
113
|
+
await this.raiseForStatus(response, 'Failed to poll messages');
|
|
115
114
|
}
|
|
116
115
|
return response.json();
|
|
117
116
|
}
|
|
@@ -168,15 +167,88 @@ export class ChatSessionService {
|
|
|
168
167
|
this.messagePollingTimer = undefined;
|
|
169
168
|
}
|
|
170
169
|
}
|
|
171
|
-
|
|
170
|
+
setSessionToken(token) {
|
|
171
|
+
this.sessionToken = token;
|
|
172
|
+
}
|
|
173
|
+
async request(input, init) {
|
|
174
|
+
const response = await fetch(input, init);
|
|
175
|
+
this.checkSunsetHeaders(response);
|
|
176
|
+
return response;
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Log a deprecation warning (RFC 8594 `Deprecation`/`Sunset`/`Link` headers)
|
|
180
|
+
* when the server reports that this widget version is deprecated. Warns
|
|
181
|
+
* during the deprecation window and errors once the sunset date has passed.
|
|
182
|
+
* Logs at most once per level so polling does not flood the console.
|
|
183
|
+
*/
|
|
184
|
+
checkSunsetHeaders(response) {
|
|
185
|
+
const headers = response === null || response === void 0 ? void 0 : response.headers;
|
|
186
|
+
if (!headers || typeof headers.get !== 'function') {
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
if (headers.get('Deprecation') !== 'true') {
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
const sunsetAt = this.parseSunsetDate(headers.get('Sunset'));
|
|
193
|
+
const pastSunset = sunsetAt !== null && Date.now() >= sunsetAt.getTime();
|
|
194
|
+
const level = pastSunset ? 'error' : 'warn';
|
|
195
|
+
if (this.loggedSunsetLevel === level) {
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
this.loggedSunsetLevel = level;
|
|
199
|
+
const upgradeUrl = this.parseSuccessorUrl(headers.get('Link'));
|
|
200
|
+
const upgradeSuffix = upgradeUrl ? ` Upgrade: ${upgradeUrl}` : '';
|
|
201
|
+
const sunsetText = sunsetAt ? sunsetAt.toUTCString() : 'an upcoming date';
|
|
202
|
+
if (level === 'error') {
|
|
203
|
+
console.error(`[open-chat-studio-widget] Widget version ${this.widgetVersion} is past its sunset date ` + `(${sunsetText}) and may stop working.${upgradeSuffix}`);
|
|
204
|
+
}
|
|
205
|
+
else {
|
|
206
|
+
console.warn(`[open-chat-studio-widget] Widget version ${this.widgetVersion} is deprecated and will stop ` + `working after ${sunsetText}.${upgradeSuffix}`);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
parseSunsetDate(sunset) {
|
|
210
|
+
if (!sunset) {
|
|
211
|
+
return null;
|
|
212
|
+
}
|
|
213
|
+
const parsed = new Date(sunset);
|
|
214
|
+
return Number.isNaN(parsed.getTime()) ? null : parsed;
|
|
215
|
+
}
|
|
216
|
+
parseSuccessorUrl(link) {
|
|
217
|
+
const match = link === null || link === void 0 ? void 0 : link.match(/<([^>]+)>\s*;\s*rel="?successor-version"?/);
|
|
218
|
+
return match === null || match === void 0 ? void 0 : match[1];
|
|
219
|
+
}
|
|
220
|
+
async raiseForStatus(response, fallbackPrefix) {
|
|
221
|
+
let message = `${fallbackPrefix}: ${response.statusText}`;
|
|
222
|
+
let code;
|
|
223
|
+
try {
|
|
224
|
+
const data = (await response.json());
|
|
225
|
+
if (data === null || data === void 0 ? void 0 : data.error) {
|
|
226
|
+
message = data.error;
|
|
227
|
+
}
|
|
228
|
+
code = data === null || data === void 0 ? void 0 : data.code;
|
|
229
|
+
}
|
|
230
|
+
catch (_a) {
|
|
231
|
+
// non-JSON body; keep statusText fallback
|
|
232
|
+
}
|
|
233
|
+
if (response.status === 403) {
|
|
234
|
+
throw new SessionAccessError(response.status, code, message);
|
|
235
|
+
}
|
|
236
|
+
throw new Error(message);
|
|
237
|
+
}
|
|
238
|
+
/** Headers for multipart requests (no Content-Type — fetch sets the boundary). */
|
|
239
|
+
getUploadHeaders() {
|
|
172
240
|
const headers = this.getCommonHeaders();
|
|
173
|
-
headers['Content-Type'] = 'application/json';
|
|
174
241
|
const csrfToken = this.csrfTokenProvider(this.apiBaseUrl);
|
|
175
242
|
if (csrfToken) {
|
|
176
243
|
headers['X-CSRFToken'] = csrfToken;
|
|
177
244
|
}
|
|
178
245
|
return headers;
|
|
179
246
|
}
|
|
247
|
+
getJsonHeaders() {
|
|
248
|
+
const headers = this.getUploadHeaders();
|
|
249
|
+
headers['Content-Type'] = 'application/json';
|
|
250
|
+
return headers;
|
|
251
|
+
}
|
|
180
252
|
getCommonHeaders() {
|
|
181
253
|
const headers = {
|
|
182
254
|
'x-ocs-widget-version': this.widgetVersion,
|
|
@@ -184,6 +256,9 @@ export class ChatSessionService {
|
|
|
184
256
|
if (this.embedKey) {
|
|
185
257
|
headers['X-Embed-Key'] = this.embedKey;
|
|
186
258
|
}
|
|
259
|
+
if (this.sessionToken) {
|
|
260
|
+
headers['X-Session-Token'] = this.sessionToken;
|
|
261
|
+
}
|
|
187
262
|
return headers;
|
|
188
263
|
}
|
|
189
264
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"chat-session-service.js","sourceRoot":"","sources":["../../src/services/chat-session-service.ts"],"names":[],"mappings":"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","sourcesContent":["import { getCSRFToken } from '../utils/cookies';\n\nexport type ChatRole = 'system' | 'user' | 'assistant';\n\nexport interface ChatAttachment {\n name: string;\n content_type: string;\n size: number;\n}\n\nexport interface ChatMessage {\n created_at: string;\n role: ChatRole;\n content: string;\n metadata?: unknown;\n attachments?: ChatAttachment[];\n}\n\nexport interface ChatStartSessionResponse {\n session_id: string;\n chatbot: unknown;\n participant: unknown;\n}\n\nexport interface ChatSendMessageResponse {\n task_id: string;\n status: 'processing' | 'completed' | 'error';\n error?: string;\n}\n\nexport interface ChatTaskPollResponse {\n message?: ChatMessage;\n status: 'processing' | 'complete';\n error?: string;\n}\n\nexport interface ChatPollResponse {\n messages: ChatMessage[];\n has_more: boolean;\n session_status: 'active' | 'ended';\n}\n\nexport interface ChatSessionServiceOptions {\n apiBaseUrl: string;\n embedKey?: string;\n widgetVersion: string;\n csrfTokenProvider?: (apiBaseUrl: string) => string | undefined;\n taskPollingIntervalMs?: number;\n taskPollingMaxAttempts?: number;\n messagePollingIntervalMs?: number;\n}\n\nexport interface TaskPollingCallbacks {\n onMessage: (message: ChatMessage) => void;\n onProgress?: (message: string) => void;\n onTimeout?: () => void;\n onError?: (error: Error) => void;\n}\n\nexport interface TaskPollingHandle {\n cancel: () => void;\n}\n\nexport interface MessagePollingCallbacks {\n getSince: () => string | undefined;\n onMessages: (messages: ChatMessage[]) => void;\n onError?: (error: Error) => void;\n}\n\nexport interface MessagePollingHandle {\n stop: () => void;\n}\n\nexport class ChatSessionService {\n private readonly apiBaseUrl: string;\n private readonly embedKey?: string;\n private readonly widgetVersion: string;\n private readonly csrfTokenProvider: (apiBaseUrl: string) => string | undefined;\n private readonly taskPollingIntervalMs: number;\n private readonly taskPollingMaxAttempts: number;\n private readonly messagePollingIntervalMs: number;\n private messagePollingTimer?: ReturnType<typeof setInterval>;\n private static readonly MAX_HISTORY_PAGES = 40;\n\n constructor(options: ChatSessionServiceOptions) {\n this.apiBaseUrl = options.apiBaseUrl;\n this.embedKey = options.embedKey;\n this.widgetVersion = options.widgetVersion;\n this.csrfTokenProvider = options.csrfTokenProvider ?? getCSRFToken;\n this.taskPollingIntervalMs = options.taskPollingIntervalMs ?? 1000;\n this.taskPollingMaxAttempts = options.taskPollingMaxAttempts ?? 120;\n this.messagePollingIntervalMs = options.messagePollingIntervalMs ?? 30000;\n }\n\n async startSession(requestBody: Record<string, unknown>): Promise<ChatStartSessionResponse> {\n const response = await fetch(`${this.apiBaseUrl}/api/chat/start/`, {\n method: 'POST',\n headers: this.getJsonHeaders(),\n body: JSON.stringify(requestBody),\n });\n\n if (!response.ok) {\n throw new Error(`Failed to start session: ${response.statusText}`);\n }\n\n return response.json() as Promise<ChatStartSessionResponse>;\n }\n\n async sendMessage(sessionId: string, payload: Record<string, unknown>): Promise<ChatSendMessageResponse> {\n const response = await fetch(`${this.apiBaseUrl}/api/chat/${sessionId}/message/`, {\n method: 'POST',\n headers: this.getJsonHeaders(),\n body: JSON.stringify(payload),\n });\n\n if (!response.ok) {\n throw new Error(`Failed to send message: ${response.statusText}`);\n }\n\n return response.json() as Promise<ChatSendMessageResponse>;\n }\n\n async pollTaskOnce(sessionId: string, taskId: string): Promise<ChatTaskPollResponse> {\n const response = await fetch(`${this.apiBaseUrl}/api/chat/${sessionId}/${taskId}/poll/`, {\n headers: this.getCommonHeaders(),\n });\n\n if (!response.ok) {\n let errorMessage = `Failed to poll task: ${response.statusText}`;\n try {\n const data = (await response.json()) as { error?: string };\n if (data?.error) {\n errorMessage = data.error;\n }\n } catch {\n // non-JSON body; keep statusText fallback\n }\n\n throw new Error(errorMessage);\n }\n\n return response.json() as Promise<ChatTaskPollResponse>;\n }\n\n pollTask(sessionId: string, taskId: string, callbacks: TaskPollingCallbacks): TaskPollingHandle {\n let attempts = 0;\n let cancelled = false;\n let timeoutId: ReturnType<typeof setTimeout> | undefined;\n\n const scheduleNextPoll = () => {\n timeoutId = setTimeout(() => {\n void poll();\n }, this.taskPollingIntervalMs);\n };\n\n const poll = async () => {\n if (cancelled) {\n return;\n }\n\n try {\n const data = await this.pollTaskOnce(sessionId, taskId);\n\n if (data.error) {\n throw new Error(data.error);\n }\n\n if (data.status === 'complete' && data.message) {\n callbacks.onMessage(data.message);\n return;\n }\n\n if (data.status === 'processing' && data.message?.content && callbacks.onProgress) {\n callbacks.onProgress(data.message.content);\n }\n\n attempts += 1;\n if (attempts >= this.taskPollingMaxAttempts) {\n if (callbacks.onTimeout) {\n callbacks.onTimeout();\n }\n return;\n }\n\n scheduleNextPoll();\n } catch (error) {\n if (callbacks.onError) {\n callbacks.onError(error instanceof Error ? error : new Error('Failed to get response'));\n }\n }\n };\n\n void poll();\n\n return {\n cancel: () => {\n cancelled = true;\n if (timeoutId) {\n clearTimeout(timeoutId);\n }\n },\n };\n }\n\n async fetchMessages(sessionId: string, since?: string): Promise<ChatPollResponse> {\n const url = new URL(`${this.apiBaseUrl}/api/chat/${sessionId}/poll/`);\n if (since) {\n url.searchParams.set('since', since);\n }\n\n const response = await fetch(url.toString(), {\n headers: this.getCommonHeaders(),\n });\n if (!response.ok) {\n throw new Error(`Failed to poll messages: ${response.statusText}`);\n }\n\n return response.json() as Promise<ChatPollResponse>;\n }\n\n /**\n * Fetch the complete message history for a session by paging through the\n * poll endpoint until no more messages remain.\n */\n async fetchAllMessages(sessionId: string): Promise<ChatMessage[]> {\n const allMessages: ChatMessage[] = [];\n let since: string | undefined;\n let hasMore = true;\n\n for (let page = 0; hasMore && page < ChatSessionService.MAX_HISTORY_PAGES; page++) {\n const data = await this.fetchMessages(sessionId, since);\n allMessages.push(...data.messages);\n hasMore = data.has_more && data.messages.length > 0;\n // The server returns pages in ascending created_at order and `since` is\n // exclusive (created_at > since), so the last message's timestamp is the\n // next page cursor.\n since = data.messages.at(-1)?.created_at;\n }\n\n if (hasMore) {\n console.warn('Chat history truncated after', ChatSessionService.MAX_HISTORY_PAGES, 'pages');\n }\n\n return allMessages;\n }\n\n startMessagePolling(sessionId: string, callbacks: MessagePollingCallbacks): MessagePollingHandle {\n const poll = async () => {\n try {\n const since = callbacks.getSince();\n const data = await this.fetchMessages(sessionId, since);\n if (data.messages.length > 0) {\n callbacks.onMessages(data.messages);\n }\n } catch (error) {\n if (callbacks.onError) {\n callbacks.onError(error instanceof Error ? error : new Error('Failed to poll messages'));\n }\n }\n };\n\n // perform an initial poll immediately\n void poll();\n\n this.messagePollingTimer = setInterval(() => {\n void poll();\n }, this.messagePollingIntervalMs);\n\n return {\n stop: () => this.stopMessagePolling(),\n };\n }\n\n stopMessagePolling(): void {\n if (this.messagePollingTimer) {\n clearInterval(this.messagePollingTimer);\n this.messagePollingTimer = undefined;\n }\n }\n\n private getJsonHeaders(): Record<string, string> {\n const headers = this.getCommonHeaders();\n headers['Content-Type'] = 'application/json';\n\n const csrfToken = this.csrfTokenProvider(this.apiBaseUrl);\n if (csrfToken) {\n headers['X-CSRFToken'] = csrfToken;\n }\n\n return headers;\n }\n\n private getCommonHeaders(): Record<string, string> {\n const headers: Record<string, string> = {\n 'x-ocs-widget-version': this.widgetVersion,\n };\n\n if (this.embedKey) {\n headers['X-Embed-Key'] = this.embedKey;\n }\n\n return headers;\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"chat-session-service.js","sourceRoot":"","sources":["../../src/services/chat-session-service.ts"],"names":[],"mappings":"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","sourcesContent":["import { getCSRFToken } from '../utils/cookies';\n\nexport class SessionAccessError extends Error {\n readonly status: number;\n readonly code?: string;\n\n constructor(status: number, code: string | undefined, message: string) {\n super(message);\n this.name = 'SessionAccessError';\n this.status = status;\n this.code = code;\n }\n}\n\nexport type ChatRole = 'system' | 'user' | 'assistant';\n\nexport interface ChatAttachment {\n name: string;\n content_type: string;\n size: number;\n}\n\nexport interface ChatMessage {\n created_at: string;\n role: ChatRole;\n content: string;\n metadata?: unknown;\n attachments?: ChatAttachment[];\n}\n\nexport interface ChatStartSessionResponse {\n session_id: string;\n session_token?: string | null;\n chatbot: unknown;\n participant: unknown;\n}\n\nexport interface ChatSendMessageResponse {\n task_id: string;\n status: 'processing' | 'completed' | 'error';\n error?: string;\n}\n\nexport interface ChatTaskPollResponse {\n message?: ChatMessage;\n status: 'processing' | 'complete';\n error?: string;\n}\n\nexport interface ChatPollResponse {\n messages: ChatMessage[];\n has_more: boolean;\n session_status: 'active' | 'ended';\n}\n\nexport interface ChatSessionServiceOptions {\n apiBaseUrl: string;\n embedKey?: string;\n widgetVersion: string;\n sessionToken?: string;\n csrfTokenProvider?: (apiBaseUrl: string) => string | undefined;\n taskPollingIntervalMs?: number;\n taskPollingMaxAttempts?: number;\n messagePollingIntervalMs?: number;\n}\n\nexport interface TaskPollingCallbacks {\n onMessage: (message: ChatMessage) => void;\n onProgress?: (message: string) => void;\n onTimeout?: () => void;\n onError?: (error: Error) => void;\n}\n\nexport interface TaskPollingHandle {\n cancel: () => void;\n}\n\nexport interface MessagePollingCallbacks {\n getSince: () => string | undefined;\n onMessages: (messages: ChatMessage[]) => void;\n onError?: (error: Error) => void;\n}\n\nexport interface MessagePollingHandle {\n stop: () => void;\n}\n\nexport class ChatSessionService {\n private readonly apiBaseUrl: string;\n private readonly embedKey?: string;\n private readonly widgetVersion: string;\n private sessionToken?: string;\n private readonly csrfTokenProvider: (apiBaseUrl: string) => string | undefined;\n private readonly taskPollingIntervalMs: number;\n private readonly taskPollingMaxAttempts: number;\n private readonly messagePollingIntervalMs: number;\n private messagePollingTimer?: ReturnType<typeof setInterval>;\n private loggedSunsetLevel?: 'warn' | 'error';\n private static readonly MAX_HISTORY_PAGES = 40;\n\n constructor(options: ChatSessionServiceOptions) {\n this.apiBaseUrl = options.apiBaseUrl;\n this.embedKey = options.embedKey;\n this.widgetVersion = options.widgetVersion;\n this.sessionToken = options.sessionToken;\n this.csrfTokenProvider = options.csrfTokenProvider ?? getCSRFToken;\n this.taskPollingIntervalMs = options.taskPollingIntervalMs ?? 1000;\n this.taskPollingMaxAttempts = options.taskPollingMaxAttempts ?? 120;\n this.messagePollingIntervalMs = options.messagePollingIntervalMs ?? 30000;\n }\n\n async startSession(requestBody: Record<string, unknown>): Promise<ChatStartSessionResponse> {\n const response = await this.request(`${this.apiBaseUrl}/api/chat/start/`, {\n method: 'POST',\n headers: this.getJsonHeaders(),\n body: JSON.stringify(requestBody),\n });\n\n if (!response.ok) {\n await this.raiseForStatus(response, 'Failed to start session');\n }\n\n return response.json() as Promise<ChatStartSessionResponse>;\n }\n\n async sendMessage(sessionId: string, payload: Record<string, unknown>): Promise<ChatSendMessageResponse> {\n const response = await this.request(`${this.apiBaseUrl}/api/chat/${sessionId}/message/`, {\n method: 'POST',\n headers: this.getJsonHeaders(),\n body: JSON.stringify(payload),\n });\n\n if (!response.ok) {\n await this.raiseForStatus(response, 'Failed to send message');\n }\n\n return response.json() as Promise<ChatSendMessageResponse>;\n }\n\n async pollTaskOnce(sessionId: string, taskId: string): Promise<ChatTaskPollResponse> {\n const response = await this.request(`${this.apiBaseUrl}/api/chat/${sessionId}/${taskId}/poll/`, {\n headers: this.getCommonHeaders(),\n });\n\n if (!response.ok) {\n await this.raiseForStatus(response, 'Failed to poll task');\n }\n\n return response.json() as Promise<ChatTaskPollResponse>;\n }\n\n pollTask(sessionId: string, taskId: string, callbacks: TaskPollingCallbacks): TaskPollingHandle {\n let attempts = 0;\n let cancelled = false;\n let timeoutId: ReturnType<typeof setTimeout> | undefined;\n\n const scheduleNextPoll = () => {\n timeoutId = setTimeout(() => {\n void poll();\n }, this.taskPollingIntervalMs);\n };\n\n const poll = async () => {\n if (cancelled) {\n return;\n }\n\n try {\n const data = await this.pollTaskOnce(sessionId, taskId);\n\n if (data.error) {\n throw new Error(data.error);\n }\n\n if (data.status === 'complete' && data.message) {\n callbacks.onMessage(data.message);\n return;\n }\n\n if (data.status === 'processing' && data.message?.content && callbacks.onProgress) {\n callbacks.onProgress(data.message.content);\n }\n\n attempts += 1;\n if (attempts >= this.taskPollingMaxAttempts) {\n if (callbacks.onTimeout) {\n callbacks.onTimeout();\n }\n return;\n }\n\n scheduleNextPoll();\n } catch (error) {\n if (callbacks.onError) {\n callbacks.onError(error instanceof Error ? error : new Error('Failed to get response'));\n }\n }\n };\n\n void poll();\n\n return {\n cancel: () => {\n cancelled = true;\n if (timeoutId) {\n clearTimeout(timeoutId);\n }\n },\n };\n }\n\n async fetchMessages(sessionId: string, since?: string): Promise<ChatPollResponse> {\n const url = new URL(`${this.apiBaseUrl}/api/chat/${sessionId}/poll/`);\n if (since) {\n url.searchParams.set('since', since);\n }\n\n const response = await this.request(url.toString(), {\n headers: this.getCommonHeaders(),\n });\n if (!response.ok) {\n await this.raiseForStatus(response, 'Failed to poll messages');\n }\n\n return response.json() as Promise<ChatPollResponse>;\n }\n\n /**\n * Fetch the complete message history for a session by paging through the\n * poll endpoint until no more messages remain.\n */\n async fetchAllMessages(sessionId: string): Promise<ChatMessage[]> {\n const allMessages: ChatMessage[] = [];\n let since: string | undefined;\n let hasMore = true;\n\n for (let page = 0; hasMore && page < ChatSessionService.MAX_HISTORY_PAGES; page++) {\n const data = await this.fetchMessages(sessionId, since);\n allMessages.push(...data.messages);\n hasMore = data.has_more && data.messages.length > 0;\n // The server returns pages in ascending created_at order and `since` is\n // exclusive (created_at > since), so the last message's timestamp is the\n // next page cursor.\n since = data.messages.at(-1)?.created_at;\n }\n\n if (hasMore) {\n console.warn('Chat history truncated after', ChatSessionService.MAX_HISTORY_PAGES, 'pages');\n }\n\n return allMessages;\n }\n\n startMessagePolling(sessionId: string, callbacks: MessagePollingCallbacks): MessagePollingHandle {\n const poll = async () => {\n try {\n const since = callbacks.getSince();\n const data = await this.fetchMessages(sessionId, since);\n if (data.messages.length > 0) {\n callbacks.onMessages(data.messages);\n }\n } catch (error) {\n if (callbacks.onError) {\n callbacks.onError(error instanceof Error ? error : new Error('Failed to poll messages'));\n }\n }\n };\n\n // perform an initial poll immediately\n void poll();\n\n this.messagePollingTimer = setInterval(() => {\n void poll();\n }, this.messagePollingIntervalMs);\n\n return {\n stop: () => this.stopMessagePolling(),\n };\n }\n\n stopMessagePolling(): void {\n if (this.messagePollingTimer) {\n clearInterval(this.messagePollingTimer);\n this.messagePollingTimer = undefined;\n }\n }\n\n setSessionToken(token?: string): void {\n this.sessionToken = token;\n }\n\n private async request(input: string, init?: RequestInit): Promise<Response> {\n const response = await fetch(input, init);\n this.checkSunsetHeaders(response);\n return response;\n }\n\n /**\n * Log a deprecation warning (RFC 8594 `Deprecation`/`Sunset`/`Link` headers)\n * when the server reports that this widget version is deprecated. Warns\n * during the deprecation window and errors once the sunset date has passed.\n * Logs at most once per level so polling does not flood the console.\n */\n private checkSunsetHeaders(response: Response): void {\n const headers = response?.headers;\n if (!headers || typeof headers.get !== 'function') {\n return;\n }\n if (headers.get('Deprecation') !== 'true') {\n return;\n }\n\n const sunsetAt = this.parseSunsetDate(headers.get('Sunset'));\n const pastSunset = sunsetAt !== null && Date.now() >= sunsetAt.getTime();\n const level: 'warn' | 'error' = pastSunset ? 'error' : 'warn';\n if (this.loggedSunsetLevel === level) {\n return;\n }\n this.loggedSunsetLevel = level;\n\n const upgradeUrl = this.parseSuccessorUrl(headers.get('Link'));\n const upgradeSuffix = upgradeUrl ? ` Upgrade: ${upgradeUrl}` : '';\n const sunsetText = sunsetAt ? sunsetAt.toUTCString() : 'an upcoming date';\n if (level === 'error') {\n console.error(`[open-chat-studio-widget] Widget version ${this.widgetVersion} is past its sunset date ` + `(${sunsetText}) and may stop working.${upgradeSuffix}`);\n } else {\n console.warn(`[open-chat-studio-widget] Widget version ${this.widgetVersion} is deprecated and will stop ` + `working after ${sunsetText}.${upgradeSuffix}`);\n }\n }\n\n private parseSunsetDate(sunset: string | null): Date | null {\n if (!sunset) {\n return null;\n }\n const parsed = new Date(sunset);\n return Number.isNaN(parsed.getTime()) ? null : parsed;\n }\n\n private parseSuccessorUrl(link: string | null): string | undefined {\n const match = link?.match(/<([^>]+)>\\s*;\\s*rel=\"?successor-version\"?/);\n return match?.[1];\n }\n\n private async raiseForStatus(response: Response, fallbackPrefix: string): Promise<never> {\n let message = `${fallbackPrefix}: ${response.statusText}`;\n let code: string | undefined;\n try {\n const data = (await response.json()) as { error?: string; code?: string };\n if (data?.error) {\n message = data.error;\n }\n code = data?.code;\n } catch {\n // non-JSON body; keep statusText fallback\n }\n if (response.status === 403) {\n throw new SessionAccessError(response.status, code, message);\n }\n throw new Error(message);\n }\n\n /** Headers for multipart requests (no Content-Type — fetch sets the boundary). */\n getUploadHeaders(): Record<string, string> {\n const headers = this.getCommonHeaders();\n\n const csrfToken = this.csrfTokenProvider(this.apiBaseUrl);\n if (csrfToken) {\n headers['X-CSRFToken'] = csrfToken;\n }\n\n return headers;\n }\n\n private getJsonHeaders(): Record<string, string> {\n const headers = this.getUploadHeaders();\n headers['Content-Type'] = 'application/json';\n return headers;\n }\n\n private getCommonHeaders(): Record<string, string> {\n const headers: Record<string, string> = {\n 'x-ocs-widget-version': this.widgetVersion,\n };\n\n if (this.embedKey) {\n headers['X-Embed-Key'] = this.embedKey;\n }\n\n if (this.sessionToken) {\n headers['X-Session-Token'] = this.sessionToken;\n }\n\n return headers;\n }\n}\n"]}
|
|
@@ -42,6 +42,7 @@ export class FileAttachmentManager {
|
|
|
42
42
|
});
|
|
43
43
|
}
|
|
44
44
|
async uploadPendingFiles(existingFiles, context) {
|
|
45
|
+
var _a;
|
|
45
46
|
if (existingFiles.length === 0) {
|
|
46
47
|
return { selectedFiles: existingFiles, uploadedIds: [] };
|
|
47
48
|
}
|
|
@@ -61,6 +62,7 @@ export class FileAttachmentManager {
|
|
|
61
62
|
try {
|
|
62
63
|
const response = await fetch(`${context.apiBaseUrl}/api/chat/${context.sessionId}/upload/`, {
|
|
63
64
|
method: 'POST',
|
|
65
|
+
headers: (_a = context.headers) !== null && _a !== void 0 ? _a : {},
|
|
64
66
|
body: formData,
|
|
65
67
|
});
|
|
66
68
|
if (!response.ok) {
|
|
@@ -70,6 +72,7 @@ export class FileAttachmentManager {
|
|
|
70
72
|
selectedFiles: this.markPendingFilesWithError(existingFiles, errorMessage),
|
|
71
73
|
uploadedIds,
|
|
72
74
|
errorMessage,
|
|
75
|
+
tokenRejected: response.status === 403,
|
|
73
76
|
};
|
|
74
77
|
}
|
|
75
78
|
const data = await this.safeJson(response);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"file-attachment-manager.js","sourceRoot":"","sources":["../../src/services/file-attachment-manager.ts"],"names":[],"mappings":"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","sourcesContent":["export interface UploadedFile {\n id: number;\n name: string;\n size: number;\n content_type: string;\n}\n\nexport interface SelectedFile {\n file: File;\n uploaded?: UploadedFile;\n error?: string;\n}\n\nexport interface AttachmentValidationConfig {\n supportedExtensions: string[];\n maxFileSizeMb: number;\n maxTotalSizeMb: number;\n}\n\nexport interface UploadContext {\n apiBaseUrl: string;\n sessionId: string;\n participantId: string;\n participantName?: string;\n}\n\nexport interface UploadResult {\n selectedFiles: SelectedFile[];\n uploadedIds: number[];\n errorMessage?: string;\n}\n\nexport class FileAttachmentManager {\n private readonly supportedExtensions: string[];\n private readonly maxFileSizeMb: number;\n private readonly maxTotalSizeMb: number;\n\n constructor(config: AttachmentValidationConfig) {\n this.supportedExtensions = config.supportedExtensions;\n this.maxFileSizeMb = config.maxFileSizeMb;\n this.maxTotalSizeMb = config.maxTotalSizeMb;\n }\n\n addFiles(existingFiles: SelectedFile[], files: FileList | File[]): SelectedFile[] {\n const newSelected: SelectedFile[] = [];\n const fileArray = Array.from(files instanceof FileList ? Array.from(files) : files);\n let totalSize = existingFiles.reduce((sum, f) => sum + f.file.size, 0);\n\n for (const file of fileArray) {\n const extension = this.getFileExtension(file.name);\n const contentType = file.type.split('/')[0];\n if (contentType != 'text' && !this.supportedExtensions.includes(extension)) {\n newSelected.push({ file, error: `File type ${extension} not supported` });\n continue;\n }\n\n const fileSizeMb = this.bytesToMb(file.size);\n if (fileSizeMb > this.maxFileSizeMb) {\n newSelected.push({ file, error: `File exceeds ${this.maxFileSizeMb}MB limit` });\n continue;\n }\n\n totalSize += file.size;\n const totalSizeMb = this.bytesToMb(totalSize);\n if (totalSizeMb > this.maxTotalSizeMb) {\n newSelected.push({ file, error: `Total size exceeds ${this.maxTotalSizeMb}MB limit` });\n continue;\n }\n\n newSelected.push({ file });\n }\n\n return [...existingFiles, ...newSelected];\n }\n\n removeFile(existingFiles: SelectedFile[], index: number): SelectedFile[] {\n return existingFiles.filter((_, i) => i !== index);\n }\n\n markPendingFilesWithError(existingFiles: SelectedFile[], errorMessage: string): SelectedFile[] {\n return existingFiles.map(file => {\n if (!file.error && !file.uploaded) {\n return { ...file, error: errorMessage };\n }\n return file;\n });\n }\n\n async uploadPendingFiles(existingFiles: SelectedFile[], context: UploadContext): Promise<UploadResult> {\n if (existingFiles.length === 0) {\n return { selectedFiles: existingFiles, uploadedIds: [] };\n }\n\n const uploadCandidates = existingFiles.filter(file => !file.error && !file.uploaded);\n const uploadedIds: number[] = existingFiles.filter(file => file.uploaded).map(file => file.uploaded!.id);\n\n if (uploadCandidates.length === 0) {\n return { selectedFiles: existingFiles, uploadedIds };\n }\n\n const formData = new FormData();\n for (const file of uploadCandidates) {\n formData.append('files', file.file);\n }\n formData.append('participant_remote_id', context.participantId);\n if (context.participantName) {\n formData.append('participant_name', context.participantName);\n }\n\n try {\n const response = await fetch(`${context.apiBaseUrl}/api/chat/${context.sessionId}/upload/`, {\n method: 'POST',\n body: formData,\n });\n\n if (!response.ok) {\n const errorData = await this.safeJson(response);\n const errorMessage = (errorData && typeof errorData === 'object' && 'error' in errorData && (errorData as { error?: string }).error) || 'Failed to upload files';\n return {\n selectedFiles: this.markPendingFilesWithError(existingFiles, errorMessage),\n uploadedIds,\n errorMessage,\n };\n }\n\n const data = await this.safeJson(response);\n if (!data || typeof data !== 'object' || !Array.isArray((data as { files?: unknown }).files)) {\n const errorMessage = 'Unexpected upload response shape';\n return {\n selectedFiles: this.markPendingFilesWithError(existingFiles, errorMessage),\n uploadedIds,\n errorMessage,\n };\n }\n\n const uploadedFiles = (data as { files: UploadedFile[] }).files;\n let index = 0;\n const updatedSelected = existingFiles.map(file => {\n if (!file.error && !file.uploaded) {\n const uploaded = uploadedFiles[index];\n index += 1;\n return { ...file, uploaded };\n }\n return file;\n });\n uploadedIds.push(...uploadedFiles.map(file => file.id));\n\n return { selectedFiles: updatedSelected, uploadedIds };\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : 'Failed to upload files';\n return {\n selectedFiles: this.markPendingFilesWithError(existingFiles, errorMessage),\n uploadedIds,\n errorMessage,\n };\n }\n }\n\n private bytesToMb(bytes: number): number {\n return bytes / (1024 * 1024);\n }\n\n private getFileExtension(filename: string): string {\n const parts = filename.split('.');\n const ext = parts.pop();\n return ext ? `.${ext.toLowerCase()}` : '';\n }\n\n private async safeJson(response: Response): Promise<unknown> {\n try {\n return await response.json();\n } catch {\n return undefined;\n }\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"file-attachment-manager.js","sourceRoot":"","sources":["../../src/services/file-attachment-manager.ts"],"names":[],"mappings":"AAmCA,MAAM,OAAO,qBAAqB;IAKhC,YAAY,MAAkC;QAC5C,IAAI,CAAC,mBAAmB,GAAG,MAAM,CAAC,mBAAmB,CAAC;QACtD,IAAI,CAAC,aAAa,GAAG,MAAM,CAAC,aAAa,CAAC;QAC1C,IAAI,CAAC,cAAc,GAAG,MAAM,CAAC,cAAc,CAAC;IAC9C,CAAC;IAED,QAAQ,CAAC,aAA6B,EAAE,KAAwB;QAC9D,MAAM,WAAW,GAAmB,EAAE,CAAC;QACvC,MAAM,SAAS,GAAG,KAAK,CAAC,IAAI,CAAC,KAAK,YAAY,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;QACpF,IAAI,SAAS,GAAG,aAAa,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC,GAAG,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;QAEvE,KAAK,MAAM,IAAI,IAAI,SAAS,EAAE,CAAC;YAC7B,MAAM,SAAS,GAAG,IAAI,CAAC,gBAAgB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACnD,MAAM,WAAW,GAAG,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;YAC5C,IAAI,WAAW,IAAI,MAAM,IAAI,CAAC,IAAI,CAAC,mBAAmB,CAAC,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC;gBAC3E,WAAW,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,aAAa,SAAS,gBAAgB,EAAE,CAAC,CAAC;gBAC1E,SAAS;YACX,CAAC;YAED,MAAM,UAAU,GAAG,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YAC7C,IAAI,UAAU,GAAG,IAAI,CAAC,aAAa,EAAE,CAAC;gBACpC,WAAW,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,gBAAgB,IAAI,CAAC,aAAa,UAAU,EAAE,CAAC,CAAC;gBAChF,SAAS;YACX,CAAC;YAED,SAAS,IAAI,IAAI,CAAC,IAAI,CAAC;YACvB,MAAM,WAAW,GAAG,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC;YAC9C,IAAI,WAAW,GAAG,IAAI,CAAC,cAAc,EAAE,CAAC;gBACtC,WAAW,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,sBAAsB,IAAI,CAAC,cAAc,UAAU,EAAE,CAAC,CAAC;gBACvF,SAAS;YACX,CAAC;YAED,WAAW,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC;QAC7B,CAAC;QAED,OAAO,CAAC,GAAG,aAAa,EAAE,GAAG,WAAW,CAAC,CAAC;IAC5C,CAAC;IAED,UAAU,CAAC,aAA6B,EAAE,KAAa;QACrD,OAAO,aAAa,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,KAAK,CAAC,CAAC;IACrD,CAAC;IAED,yBAAyB,CAAC,aAA6B,EAAE,YAAoB;QAC3E,OAAO,aAAa,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE;YAC9B,IAAI,CAAC,IAAI,CAAC,KAAK,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC;gBAClC,uCAAY,IAAI,KAAE,KAAK,EAAE,YAAY,IAAG;YAC1C,CAAC;YACD,OAAO,IAAI,CAAC;QACd,CAAC,CAAC,CAAC;IACL,CAAC;IAED,KAAK,CAAC,kBAAkB,CAAC,aAA6B,EAAE,OAAsB;;QAC5E,IAAI,aAAa,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC/B,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,WAAW,EAAE,EAAE,EAAE,CAAC;QAC3D,CAAC;QAED,MAAM,gBAAgB,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,KAAK,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QACrF,MAAM,WAAW,GAAa,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,QAAS,CAAC,EAAE,CAAC,CAAC;QAEzG,IAAI,gBAAgB,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAClC,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,WAAW,EAAE,CAAC;QACvD,CAAC;QAED,MAAM,QAAQ,GAAG,IAAI,QAAQ,EAAE,CAAC;QAChC,KAAK,MAAM,IAAI,IAAI,gBAAgB,EAAE,CAAC;YACpC,QAAQ,CAAC,MAAM,CAAC,OAAO,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC;QACtC,CAAC;QACD,QAAQ,CAAC,MAAM,CAAC,uBAAuB,EAAE,OAAO,CAAC,aAAa,CAAC,CAAC;QAChE,IAAI,OAAO,CAAC,eAAe,EAAE,CAAC;YAC5B,QAAQ,CAAC,MAAM,CAAC,kBAAkB,EAAE,OAAO,CAAC,eAAe,CAAC,CAAC;QAC/D,CAAC;QAED,IAAI,CAAC;YACH,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,OAAO,CAAC,UAAU,aAAa,OAAO,CAAC,SAAS,UAAU,EAAE;gBAC1F,MAAM,EAAE,MAAM;gBACd,OAAO,EAAE,MAAA,OAAO,CAAC,OAAO,mCAAI,EAAE;gBAC9B,IAAI,EAAE,QAAQ;aACf,CAAC,CAAC;YAEH,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;gBACjB,MAAM,SAAS,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;gBAChD,MAAM,YAAY,GAAG,CAAC,SAAS,IAAI,OAAO,SAAS,KAAK,QAAQ,IAAI,OAAO,IAAI,SAAS,IAAK,SAAgC,CAAC,KAAK,CAAC,IAAI,wBAAwB,CAAC;gBACjK,OAAO;oBACL,aAAa,EAAE,IAAI,CAAC,yBAAyB,CAAC,aAAa,EAAE,YAAY,CAAC;oBAC1E,WAAW;oBACX,YAAY;oBACZ,aAAa,EAAE,QAAQ,CAAC,MAAM,KAAK,GAAG;iBACvC,CAAC;YACJ,CAAC;YAED,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;YAC3C,IAAI,CAAC,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,CAAC,KAAK,CAAC,OAAO,CAAE,IAA4B,CAAC,KAAK,CAAC,EAAE,CAAC;gBAC7F,MAAM,YAAY,GAAG,kCAAkC,CAAC;gBACxD,OAAO;oBACL,aAAa,EAAE,IAAI,CAAC,yBAAyB,CAAC,aAAa,EAAE,YAAY,CAAC;oBAC1E,WAAW;oBACX,YAAY;iBACb,CAAC;YACJ,CAAC;YAED,MAAM,aAAa,GAAI,IAAkC,CAAC,KAAK,CAAC;YAChE,IAAI,KAAK,GAAG,CAAC,CAAC;YACd,MAAM,eAAe,GAAG,aAAa,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE;gBAC/C,IAAI,CAAC,IAAI,CAAC,KAAK,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC;oBAClC,MAAM,QAAQ,GAAG,aAAa,CAAC,KAAK,CAAC,CAAC;oBACtC,KAAK,IAAI,CAAC,CAAC;oBACX,uCAAY,IAAI,KAAE,QAAQ,IAAG;gBAC/B,CAAC;gBACD,OAAO,IAAI,CAAC;YACd,CAAC,CAAC,CAAC;YACH,WAAW,CAAC,IAAI,CAAC,GAAG,aAAa,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC;YAExD,OAAO,EAAE,aAAa,EAAE,eAAe,EAAE,WAAW,EAAE,CAAC;QACzD,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,YAAY,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,wBAAwB,CAAC;YACvF,OAAO;gBACL,aAAa,EAAE,IAAI,CAAC,yBAAyB,CAAC,aAAa,EAAE,YAAY,CAAC;gBAC1E,WAAW;gBACX,YAAY;aACb,CAAC;QACJ,CAAC;IACH,CAAC;IAEO,SAAS,CAAC,KAAa;QAC7B,OAAO,KAAK,GAAG,CAAC,IAAI,GAAG,IAAI,CAAC,CAAC;IAC/B,CAAC;IAEO,gBAAgB,CAAC,QAAgB;QACvC,MAAM,KAAK,GAAG,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QAClC,MAAM,GAAG,GAAG,KAAK,CAAC,GAAG,EAAE,CAAC;QACxB,OAAO,GAAG,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,WAAW,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;IAC5C,CAAC;IAEO,KAAK,CAAC,QAAQ,CAAC,QAAkB;QACvC,IAAI,CAAC;YACH,OAAO,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC;QAC/B,CAAC;QAAC,WAAM,CAAC;YACP,OAAO,SAAS,CAAC;QACnB,CAAC;IACH,CAAC;CACF","sourcesContent":["export interface UploadedFile {\n id: number;\n name: string;\n size: number;\n content_type: string;\n}\n\nexport interface SelectedFile {\n file: File;\n uploaded?: UploadedFile;\n error?: string;\n}\n\nexport interface AttachmentValidationConfig {\n supportedExtensions: string[];\n maxFileSizeMb: number;\n maxTotalSizeMb: number;\n}\n\nexport interface UploadContext {\n apiBaseUrl: string;\n sessionId: string;\n participantId: string;\n participantName?: string;\n /** Auth headers (session token, CSRF, widget version) — see ChatSessionService.getUploadHeaders. */\n headers?: Record<string, string>;\n}\n\nexport interface UploadResult {\n selectedFiles: SelectedFile[];\n uploadedIds: number[];\n errorMessage?: string;\n tokenRejected?: boolean;\n}\n\nexport class FileAttachmentManager {\n private readonly supportedExtensions: string[];\n private readonly maxFileSizeMb: number;\n private readonly maxTotalSizeMb: number;\n\n constructor(config: AttachmentValidationConfig) {\n this.supportedExtensions = config.supportedExtensions;\n this.maxFileSizeMb = config.maxFileSizeMb;\n this.maxTotalSizeMb = config.maxTotalSizeMb;\n }\n\n addFiles(existingFiles: SelectedFile[], files: FileList | File[]): SelectedFile[] {\n const newSelected: SelectedFile[] = [];\n const fileArray = Array.from(files instanceof FileList ? Array.from(files) : files);\n let totalSize = existingFiles.reduce((sum, f) => sum + f.file.size, 0);\n\n for (const file of fileArray) {\n const extension = this.getFileExtension(file.name);\n const contentType = file.type.split('/')[0];\n if (contentType != 'text' && !this.supportedExtensions.includes(extension)) {\n newSelected.push({ file, error: `File type ${extension} not supported` });\n continue;\n }\n\n const fileSizeMb = this.bytesToMb(file.size);\n if (fileSizeMb > this.maxFileSizeMb) {\n newSelected.push({ file, error: `File exceeds ${this.maxFileSizeMb}MB limit` });\n continue;\n }\n\n totalSize += file.size;\n const totalSizeMb = this.bytesToMb(totalSize);\n if (totalSizeMb > this.maxTotalSizeMb) {\n newSelected.push({ file, error: `Total size exceeds ${this.maxTotalSizeMb}MB limit` });\n continue;\n }\n\n newSelected.push({ file });\n }\n\n return [...existingFiles, ...newSelected];\n }\n\n removeFile(existingFiles: SelectedFile[], index: number): SelectedFile[] {\n return existingFiles.filter((_, i) => i !== index);\n }\n\n markPendingFilesWithError(existingFiles: SelectedFile[], errorMessage: string): SelectedFile[] {\n return existingFiles.map(file => {\n if (!file.error && !file.uploaded) {\n return { ...file, error: errorMessage };\n }\n return file;\n });\n }\n\n async uploadPendingFiles(existingFiles: SelectedFile[], context: UploadContext): Promise<UploadResult> {\n if (existingFiles.length === 0) {\n return { selectedFiles: existingFiles, uploadedIds: [] };\n }\n\n const uploadCandidates = existingFiles.filter(file => !file.error && !file.uploaded);\n const uploadedIds: number[] = existingFiles.filter(file => file.uploaded).map(file => file.uploaded!.id);\n\n if (uploadCandidates.length === 0) {\n return { selectedFiles: existingFiles, uploadedIds };\n }\n\n const formData = new FormData();\n for (const file of uploadCandidates) {\n formData.append('files', file.file);\n }\n formData.append('participant_remote_id', context.participantId);\n if (context.participantName) {\n formData.append('participant_name', context.participantName);\n }\n\n try {\n const response = await fetch(`${context.apiBaseUrl}/api/chat/${context.sessionId}/upload/`, {\n method: 'POST',\n headers: context.headers ?? {},\n body: formData,\n });\n\n if (!response.ok) {\n const errorData = await this.safeJson(response);\n const errorMessage = (errorData && typeof errorData === 'object' && 'error' in errorData && (errorData as { error?: string }).error) || 'Failed to upload files';\n return {\n selectedFiles: this.markPendingFilesWithError(existingFiles, errorMessage),\n uploadedIds,\n errorMessage,\n tokenRejected: response.status === 403,\n };\n }\n\n const data = await this.safeJson(response);\n if (!data || typeof data !== 'object' || !Array.isArray((data as { files?: unknown }).files)) {\n const errorMessage = 'Unexpected upload response shape';\n return {\n selectedFiles: this.markPendingFilesWithError(existingFiles, errorMessage),\n uploadedIds,\n errorMessage,\n };\n }\n\n const uploadedFiles = (data as { files: UploadedFile[] }).files;\n let index = 0;\n const updatedSelected = existingFiles.map(file => {\n if (!file.error && !file.uploaded) {\n const uploaded = uploadedFiles[index];\n index += 1;\n return { ...file, uploaded };\n }\n return file;\n });\n uploadedIds.push(...uploadedFiles.map(file => file.id));\n\n return { selectedFiles: updatedSelected, uploadedIds };\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : 'Failed to upload files';\n return {\n selectedFiles: this.markPendingFilesWithError(existingFiles, errorMessage),\n uploadedIds,\n errorMessage,\n };\n }\n }\n\n private bytesToMb(bytes: number): number {\n return bytes / (1024 * 1024);\n }\n\n private getFileExtension(filename: string): string {\n const parts = filename.split('.');\n const ext = parts.pop();\n return ext ? `.${ext.toLowerCase()}` : '';\n }\n\n private async safeJson(response: Response): Promise<unknown> {\n try {\n return await response.json();\n } catch {\n return undefined;\n }\n }\n}\n"]}
|