@bootdesk/js-web-adapter-core 0.1.0 → 0.3.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/README.md CHANGED
@@ -14,12 +14,13 @@ npm install @bootdesk/js-web-adapter-core
14
14
  import { WebChatClient } from "@bootdesk/js-web-adapter-core";
15
15
 
16
16
  const client = new WebChatClient({
17
- baseUrl: "https://your-app.com/api/chat",
18
- token: "your-auth-token",
17
+ apiUrl: "https://your-app.com/api",
18
+ userId: "user-1",
19
+ userName: "Alice",
19
20
  });
20
21
 
21
- const unsub = client.onNewMessage((event) => {
22
- console.log("New message:", event.message.text);
22
+ const unsub = client.onMessagePosted((event) => {
23
+ console.log("New message:", event.text);
23
24
  });
24
25
 
25
26
  await client.connect();
@@ -33,19 +34,36 @@ await client.connect();
33
34
  |--------|-------------|
34
35
  | `connect()` | Initialize connection, start listening |
35
36
  | `disconnect()` | Cleanup, remove listeners |
36
- | `loadMessages(threadId, options?)` | Fetch paginated messages |
37
+ | `loadMessages(options?)` | Fetch paginated messages |
37
38
  | `sendMessage(text, attachments?)` | Send a new message |
39
+ | `sendAction(messageId, actionId, value)` | Send a button action |
38
40
  | `editMessage(messageId, text)` | Edit an existing message |
39
41
  | `deleteMessage(messageId)` | Delete a message |
40
42
  | `addReaction(messageId, emoji)` | Add a reaction |
41
43
  | `removeReaction(messageId, emoji)` | Remove a reaction |
42
- | `onNewMessage(cb)` | Subscribe to new messages |
44
+ | `onMessagePosted(cb)` | Subscribe to new messages |
43
45
  | `onMessageEdited(cb)` | Subscribe to edits |
44
46
  | `onMessageDeleted(cb)` | Subscribe to deletions |
45
47
  | `onReactionAdded(cb)` | Subscribe to reaction adds |
46
48
  | `onReactionRemoved(cb)` | Subscribe to reaction removes |
47
49
  | `onTypingStarted(cb)` | Subscribe to typing events |
48
50
  | `onStreamingChunk(cb)` | Subscribe to streaming chunks |
51
+ | `reconfigure(config)` | Update identity (userId, userName, verifyToken, conversationId, headers) after construction |
52
+
53
+ ### Reconfiguration
54
+
55
+ Use `reconfigure()` to update the client's identity after construction — useful for pre-entry flows where the user's info is collected first:
56
+
57
+ ```typescript
58
+ client.reconfigure({
59
+ userId: "user-abc",
60
+ userName: "Alice",
61
+ verifyToken: "encrypted-token",
62
+ conversationId: "conv-xyz",
63
+ });
64
+ ```
65
+
66
+ Updates HTTP headers (`X-User-Id`, `X-User-Name`, `X-Verify-Token`) and internal state.
49
67
 
50
68
  ### Broadcasting
51
69
 
@@ -57,20 +75,33 @@ const broadcast = new PusherBroadcastClient({
57
75
  cluster: "us2",
58
76
  });
59
77
 
60
- const client = new WebChatClient({ baseUrl, token, broadcast });
78
+ const client = new WebChatClient({
79
+ apiUrl: "https://your-app.com/api",
80
+ userId: "user-1",
81
+ userName: "Alice",
82
+ broadcastClient: broadcast,
83
+ });
61
84
  ```
62
85
 
63
86
  ### Push Notifications
64
87
 
65
88
  ```typescript
66
89
  import { PushManager, createPushSubscriptionHandlers } from "@bootdesk/js-web-adapter-core";
90
+ import { HttpClient } from "@bootdesk/js-web-adapter-core";
67
91
 
68
- const manager = new PushManager(
69
- "https://your-app.com/api/push",
70
- createPushSubscriptionHandlers(fetch),
71
- );
92
+ const httpClient = new HttpClient({ apiUrl: "https://your-app.com/api" });
93
+ const manager = new PushManager({
94
+ getVapidPublicKey: async () => "your-vapid-public-key",
95
+ onSubscribe: createPushSubscriptionHandlers(httpClient, "user-1").onSubscribe,
96
+ onUnsubscribe: createPushSubscriptionHandlers(httpClient, "user-1").onUnsubscribe,
97
+ });
72
98
 
99
+ await manager.initialize();
73
100
  await manager.subscribe();
101
+
102
+ manager.onMessage((data) => {
103
+ console.log("Push received:", data);
104
+ });
74
105
  ```
75
106
 
76
107
  ## License
@@ -0,0 +1,163 @@
1
+ // src/chat-service-worker.ts
2
+ var swLocales = {
3
+ en: { openChat: "Open Chat", dismiss: "Dismiss" },
4
+ pt: { openChat: "Abrir chat", dismiss: "Dispensar" },
5
+ es: { openChat: "Abrir chat", dismiss: "Descartar" },
6
+ da: { openChat: "\xC5bn Chat", dismiss: "Afvis" },
7
+ sv: { openChat: "\xD6ppna Chatt", dismiss: "Avf\xE4rda" },
8
+ nb: { openChat: "\xC5pne Chat", dismiss: "Avvis" },
9
+ fi: { openChat: "Avaa Chat", dismiss: "Hylk\xE4\xE4" },
10
+ fr: { openChat: "Ouvrir le Chat", dismiss: "Ignorer" },
11
+ de: { openChat: "Chat \xF6ffnen", dismiss: "Schlie\xDFen" },
12
+ it: { openChat: "Apri Chat", dismiss: "Ignora" },
13
+ nl: { openChat: "Open Chat", dismiss: "Sluiten" },
14
+ pl: { openChat: "Otw\xF3rz Czat", dismiss: "Odrzu\u0107" },
15
+ cs: { openChat: "Otev\u0159\xEDt Chat", dismiss: "Zav\u0159\xEDt" },
16
+ ro: { openChat: "Deschide Chat", dismiss: "Ignor\u0103" },
17
+ hu: { openChat: "Chat Megnyit\xE1sa", dismiss: "Elutas\xEDt\xE1s" },
18
+ uk: { openChat: "\u0412\u0456\u0434\u043A\u0440\u0438\u0442\u0438 \u0427\u0430\u0442", dismiss: "\u0412\u0456\u0434\u0445\u0438\u043B\u0438\u0442\u0438" },
19
+ ru: { openChat: "\u041E\u0442\u043A\u0440\u044B\u0442\u044C \u0427\u0430\u0442", dismiss: "\u0417\u0430\u043A\u0440\u044B\u0442\u044C" },
20
+ el: { openChat: "\u0386\u03BD\u03BF\u03B9\u03B3\u03BC\u03B1 \u03A3\u03C5\u03BD\u03BF\u03BC\u03B9\u03BB\u03AF\u03B1\u03C2", dismiss: "\u0391\u03C0\u03CC\u03C1\u03C1\u03B9\u03C8\u03B7" },
21
+ tr: { openChat: "Sohbeti A\xE7", dismiss: "Kapat" },
22
+ et: { openChat: "Ava Vestlus", dismiss: "Loobu" },
23
+ ja: { openChat: "\u30C1\u30E3\u30C3\u30C8\u3092\u958B\u304F", dismiss: "\u9589\u3058\u308B" },
24
+ "zh-CN": { openChat: "\u6253\u5F00\u804A\u5929", dismiss: "\u5173\u95ED" },
25
+ "zh-TW": { openChat: "\u958B\u555F\u804A\u5929", dismiss: "\u95DC\u9589" },
26
+ ko: { openChat: "\uCC44\uD305 \uC5F4\uAE30", dismiss: "\uB2EB\uAE30" },
27
+ vi: { openChat: "M\u1EDF Tr\xF2 chuy\u1EC7n", dismiss: "B\u1ECF qua" },
28
+ th: { openChat: "\u0E40\u0E1B\u0E34\u0E14\u0E41\u0E0A\u0E17", dismiss: "\u0E1B\u0E34\u0E14" },
29
+ id: { openChat: "Buka Obrolan", dismiss: "Tutup" },
30
+ hi: { openChat: "\u091A\u0948\u091F \u0916\u094B\u0932\u0947\u0902", dismiss: "\u0916\u093E\u0930\u093F\u091C \u0915\u0930\u0947\u0902" },
31
+ ar: { openChat: "\u0641\u062A\u062D \u0627\u0644\u062F\u0631\u062F\u0634\u0629", dismiss: "\u062A\u062C\u0627\u0647\u0644" }
32
+ };
33
+ function getFallbackChain(locale) {
34
+ const parts = locale.split("-");
35
+ if (parts.length === 1) return [parts[0], "en"];
36
+ return [locale, parts[0], "en"];
37
+ }
38
+ function resolveLocale(locale) {
39
+ if (!locale) return swLocales.en;
40
+ for (const tag of getFallbackChain(locale)) {
41
+ if (swLocales[tag]) return swLocales[tag];
42
+ }
43
+ return swLocales.en;
44
+ }
45
+ function getDefaultActions(locale) {
46
+ const strings = resolveLocale(locale);
47
+ return [
48
+ { action: "open", title: strings.openChat },
49
+ { action: "dismiss", title: strings.dismiss }
50
+ ];
51
+ }
52
+ var sw = self;
53
+ sw.addEventListener("push", (event) => {
54
+ if (!event.data) return;
55
+ let data;
56
+ try {
57
+ data = event.data.json();
58
+ } catch {
59
+ return;
60
+ }
61
+ const {
62
+ threadId,
63
+ messageId,
64
+ senderName,
65
+ preview,
66
+ timestamp,
67
+ deepLink,
68
+ type,
69
+ actions,
70
+ locale,
71
+ icon,
72
+ badge
73
+ } = data;
74
+ const notificationType = type ?? "chat";
75
+ event.waitUntil(
76
+ sw.clients.matchAll({ type: "window", includeUncontrolled: true }).then((clients) => {
77
+ clients.forEach((client) => {
78
+ client.postMessage({
79
+ type: "chat-widget:push-data",
80
+ data: { threadId, messageId, senderName, preview, timestamp, deepLink }
81
+ });
82
+ });
83
+ })
84
+ );
85
+ const notificationData = {
86
+ threadId,
87
+ messageId,
88
+ timestamp,
89
+ deepLink,
90
+ type: notificationType
91
+ };
92
+ const defaultIcon = "/chat-icon-192.png";
93
+ const defaultBadge = "/chat-badge-72.png";
94
+ if (notificationType === "generic") {
95
+ const notificationActions = actions ?? [];
96
+ event.waitUntil(
97
+ sw.registration.showNotification(senderName, {
98
+ body: preview,
99
+ icon: icon ?? defaultIcon,
100
+ badge: badge ?? defaultBadge,
101
+ tag: `generic-${threadId}`,
102
+ data: notificationData,
103
+ actions: notificationActions
104
+ })
105
+ );
106
+ return;
107
+ }
108
+ const chatActions = actions ?? getDefaultActions(locale);
109
+ event.waitUntil(
110
+ sw.registration.showNotification(senderName, {
111
+ body: preview,
112
+ icon: icon ?? defaultIcon,
113
+ badge: badge ?? defaultBadge,
114
+ tag: `message-${threadId}`,
115
+ renotify: true,
116
+ data: notificationData,
117
+ actions: chatActions
118
+ })
119
+ );
120
+ });
121
+ sw.addEventListener("notificationclick", (event) => {
122
+ event.notification.close();
123
+ if (event.action === "dismiss") return;
124
+ const clickData = event.notification.data;
125
+ const { deepLink, threadId, messageId, timestamp, type } = clickData;
126
+ if (type === "generic") {
127
+ if (deepLink) {
128
+ event.waitUntil(sw.clients.openWindow(deepLink));
129
+ }
130
+ return;
131
+ }
132
+ event.waitUntil(
133
+ sw.clients.matchAll({ type: "window", includeUncontrolled: true }).then((clientList) => {
134
+ const origin = sw.location.origin;
135
+ const sameOrigin = clientList.filter((c) => c.url.startsWith(origin));
136
+ for (const client of deepLink ? clientList : sameOrigin) {
137
+ if (!deepLink || client.url.includes(deepLink) || client.url.includes("chat-widget")) {
138
+ client.postMessage({
139
+ type: "chat-widget:notification-clicked",
140
+ threadId,
141
+ messageId,
142
+ timestamp,
143
+ instanceId: threadId
144
+ });
145
+ return client.focus();
146
+ }
147
+ }
148
+ return sw.clients.openWindow(deepLink || `/chat?thread=${threadId}`);
149
+ })
150
+ );
151
+ });
152
+ sw.addEventListener("message", (event) => {
153
+ const data = event.data;
154
+ if (data?.type === "chat-widget:notification-clicked") {
155
+ const source = event.source;
156
+ source?.postMessage({
157
+ type: "chat-widget:sync",
158
+ threadId: data.threadId,
159
+ after: data.timestamp
160
+ });
161
+ }
162
+ });
163
+ //# sourceMappingURL=chat-service-worker.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/chat-service-worker.ts"],"sourcesContent":["/// <reference lib=\"webworker\" />\n\ntype PushType = \"chat\" | \"generic\";\n\ninterface PushAction {\n action: string;\n title: string;\n}\n\ninterface PushNotificationData {\n threadId: string;\n messageId: string;\n senderName: string;\n preview: string;\n timestamp: number;\n deepLink?: string;\n type?: PushType;\n actions?: PushAction[];\n locale?: string;\n icon?: string;\n badge?: string;\n}\n\ninterface NotificationClickData {\n deepLink?: string;\n threadId: string;\n messageId: string;\n timestamp: number;\n type: PushType;\n}\n\ninterface SyncMessage {\n type: \"chat-widget:sync\";\n threadId: string;\n after: number;\n}\n\ninterface ClickedMessage {\n type: \"chat-widget:notification-clicked\";\n threadId: string;\n messageId: string;\n timestamp: number;\n instanceId: string;\n}\n\n// ── Locale strings for notification actions ──\n\ninterface NotificationLocaleStrings {\n openChat: string;\n dismiss: string;\n}\n\nconst swLocales: Record<string, NotificationLocaleStrings> = {\n en: { openChat: \"Open Chat\", dismiss: \"Dismiss\" },\n pt: { openChat: \"Abrir chat\", dismiss: \"Dispensar\" },\n es: { openChat: \"Abrir chat\", dismiss: \"Descartar\" },\n da: { openChat: \"Åbn Chat\", dismiss: \"Afvis\" },\n sv: { openChat: \"Öppna Chatt\", dismiss: \"Avfärda\" },\n nb: { openChat: \"Åpne Chat\", dismiss: \"Avvis\" },\n fi: { openChat: \"Avaa Chat\", dismiss: \"Hylkää\" },\n fr: { openChat: \"Ouvrir le Chat\", dismiss: \"Ignorer\" },\n de: { openChat: \"Chat öffnen\", dismiss: \"Schließen\" },\n it: { openChat: \"Apri Chat\", dismiss: \"Ignora\" },\n nl: { openChat: \"Open Chat\", dismiss: \"Sluiten\" },\n pl: { openChat: \"Otwórz Czat\", dismiss: \"Odrzuć\" },\n cs: { openChat: \"Otevřít Chat\", dismiss: \"Zavřít\" },\n ro: { openChat: \"Deschide Chat\", dismiss: \"Ignoră\" },\n hu: { openChat: \"Chat Megnyitása\", dismiss: \"Elutasítás\" },\n uk: { openChat: \"Відкрити Чат\", dismiss: \"Відхилити\" },\n ru: { openChat: \"Открыть Чат\", dismiss: \"Закрыть\" },\n el: { openChat: \"Άνοιγμα Συνομιλίας\", dismiss: \"Απόρριψη\" },\n tr: { openChat: \"Sohbeti Aç\", dismiss: \"Kapat\" },\n et: { openChat: \"Ava Vestlus\", dismiss: \"Loobu\" },\n ja: { openChat: \"チャットを開く\", dismiss: \"閉じる\" },\n \"zh-CN\": { openChat: \"打开聊天\", dismiss: \"关闭\" },\n \"zh-TW\": { openChat: \"開啟聊天\", dismiss: \"關閉\" },\n ko: { openChat: \"채팅 열기\", dismiss: \"닫기\" },\n vi: { openChat: \"Mở Trò chuyện\", dismiss: \"Bỏ qua\" },\n th: { openChat: \"เปิดแชท\", dismiss: \"ปิด\" },\n id: { openChat: \"Buka Obrolan\", dismiss: \"Tutup\" },\n hi: { openChat: \"चैट खोलें\", dismiss: \"खारिज करें\" },\n ar: { openChat: \"فتح الدردشة\", dismiss: \"تجاهل\" },\n};\n\nfunction getFallbackChain(locale: string): string[] {\n const parts = locale.split(\"-\");\n if (parts.length === 1) return [parts[0], \"en\"];\n return [locale, parts[0], \"en\"];\n}\n\nfunction resolveLocale(locale?: string): NotificationLocaleStrings {\n if (!locale) return swLocales.en;\n for (const tag of getFallbackChain(locale)) {\n if (swLocales[tag]) return swLocales[tag];\n }\n return swLocales.en;\n}\n\nfunction getDefaultActions(locale?: string): PushAction[] {\n const strings = resolveLocale(locale);\n return [\n { action: \"open\", title: strings.openChat },\n { action: \"dismiss\", title: strings.dismiss },\n ];\n}\n\n// ── Service Worker ──\n\nconst sw = self as unknown as ServiceWorkerGlobalScope;\n\nsw.addEventListener(\"push\", (event: PushEvent) => {\n if (!event.data) return;\n\n let data: PushNotificationData;\n try {\n data = event.data.json();\n } catch {\n return;\n }\n\n const {\n threadId,\n messageId,\n senderName,\n preview,\n timestamp,\n deepLink,\n type,\n actions,\n locale,\n icon,\n badge,\n } = data;\n\n const notificationType = type ?? \"chat\";\n\n // Forward push data to window clients for PushManager.onMessage to receive\n event.waitUntil(\n sw.clients.matchAll({ type: \"window\", includeUncontrolled: true }).then((clients) => {\n clients.forEach((client) => {\n client.postMessage({\n type: \"chat-widget:push-data\",\n data: { threadId, messageId, senderName, preview, timestamp, deepLink },\n });\n });\n }),\n );\n\n const notificationData: NotificationClickData = {\n threadId,\n messageId,\n timestamp,\n deepLink,\n type: notificationType,\n };\n\n const defaultIcon = \"/chat-icon-192.png\";\n const defaultBadge = \"/chat-badge-72.png\";\n\n if (notificationType === \"generic\") {\n const notificationActions = actions ?? [];\n\n event.waitUntil(\n sw.registration.showNotification(senderName, {\n body: preview,\n icon: icon ?? defaultIcon,\n badge: badge ?? defaultBadge,\n tag: `generic-${threadId}`,\n data: notificationData,\n actions: notificationActions,\n } as NotificationOptions),\n );\n return;\n }\n\n const chatActions = actions ?? getDefaultActions(locale);\n\n event.waitUntil(\n sw.registration.showNotification(senderName, {\n body: preview,\n icon: icon ?? defaultIcon,\n badge: badge ?? defaultBadge,\n tag: `message-${threadId}`,\n renotify: true,\n data: notificationData,\n actions: chatActions,\n } as NotificationOptions),\n );\n});\n\nsw.addEventListener(\"notificationclick\", (event: NotificationEvent) => {\n event.notification.close();\n if (event.action === \"dismiss\") return;\n\n const clickData = event.notification.data as unknown as NotificationClickData;\n const { deepLink, threadId, messageId, timestamp, type } = clickData;\n\n if (type === \"generic\") {\n if (deepLink) {\n event.waitUntil(sw.clients.openWindow(deepLink));\n }\n return;\n }\n\n event.waitUntil(\n sw.clients\n .matchAll({ type: \"window\", includeUncontrolled: true })\n .then((clientList: readonly WindowClient[]) => {\n const origin = sw.location.origin;\n const sameOrigin = clientList.filter((c: WindowClient) => c.url.startsWith(origin));\n\n for (const client of deepLink ? clientList : sameOrigin) {\n if (!deepLink || client.url.includes(deepLink) || client.url.includes(\"chat-widget\")) {\n client.postMessage({\n type: \"chat-widget:notification-clicked\",\n threadId,\n messageId,\n timestamp,\n instanceId: threadId,\n } satisfies ClickedMessage);\n return client.focus();\n }\n }\n\n return sw.clients.openWindow(deepLink || `/chat?thread=${threadId}`);\n }),\n );\n});\n\nsw.addEventListener(\"message\", (event: ExtendableMessageEvent) => {\n const data = event.data as ClickedMessage | null;\n if (data?.type === \"chat-widget:notification-clicked\") {\n const source = event.source as WindowClient | null;\n source?.postMessage({\n type: \"chat-widget:sync\",\n threadId: data.threadId,\n after: data.timestamp,\n } satisfies SyncMessage);\n }\n});\n"],"mappings":"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","names":[]}
package/dist/index.cjs CHANGED
@@ -129,6 +129,30 @@ var HttpClient = class {
129
129
  const url = this.expandTemplate(endpointTemplate, { id: messageId, emoji });
130
130
  await this.delete(url);
131
131
  }
132
+ async postFormData(url, formData, signal) {
133
+ const controller = new AbortController();
134
+ const timeoutId = setTimeout(() => controller.abort(), this.config.timeout);
135
+ try {
136
+ const fullUrl = this.resolve(url);
137
+ const combined = signal ? AbortSignal.any([controller.signal, signal]) : controller.signal;
138
+ const response = await fetch(fullUrl, {
139
+ method: "POST",
140
+ headers: this.config.headers,
141
+ signal: combined,
142
+ body: formData
143
+ });
144
+ if (!response.ok) throw new Error(`HTTP ${response.status}: ${response.statusText}`);
145
+ return response.json();
146
+ } finally {
147
+ clearTimeout(timeoutId);
148
+ }
149
+ }
150
+ setHeader(name, value) {
151
+ this.config.headers[name] = value;
152
+ }
153
+ removeHeader(name) {
154
+ delete this.config.headers[name];
155
+ }
132
156
  resolve(url) {
133
157
  return /^https?:\/\//.test(url) ? url : `${this.config.apiUrl}${url}`;
134
158
  }
@@ -329,6 +353,31 @@ var WebChatClient = class {
329
353
  this.conversationId = config.conversationId ?? generateConversationId();
330
354
  this.currentUserId = config.userId;
331
355
  }
356
+ reconfigure(config) {
357
+ if (config.userId) {
358
+ this.currentUserId = config.userId;
359
+ this.httpClient.setHeader("X-User-Id", config.userId);
360
+ }
361
+ if (config.userName) {
362
+ this.config = { ...this.config, userName: config.userName };
363
+ this.httpClient.setHeader("X-User-Name", config.userName);
364
+ }
365
+ if (config.verifyToken) {
366
+ this.config = { ...this.config, verifyToken: config.verifyToken };
367
+ this.httpClient.setHeader("X-Verify-Token", config.verifyToken);
368
+ }
369
+ if (config.conversationId) {
370
+ this.conversationId = config.conversationId;
371
+ }
372
+ if (config.headers) {
373
+ Object.entries(config.headers).forEach(([key, value]) => {
374
+ this.httpClient.setHeader(key, value);
375
+ });
376
+ }
377
+ }
378
+ setLocaleHeader(locale) {
379
+ this.httpClient.setHeader("X-Locale", locale);
380
+ }
332
381
  async connect() {
333
382
  if (this.broadcastClient) {
334
383
  this.broadcastClient.connect();
@@ -377,10 +426,14 @@ var WebChatClient = class {
377
426
  `${endpoint}?${params.toString()}`,
378
427
  signal
379
428
  );
380
- const messages = (response.messages || []).map((msg) => ({
429
+ const rawMessages = response.messages ?? [];
430
+ const messages = rawMessages.map((msg) => ({
381
431
  id: msg.id,
382
432
  threadId,
383
- content: { text: msg.text, cards: msg.card ? [msg.card] : void 0 },
433
+ content: {
434
+ text: msg.text,
435
+ cards: msg.card ? [msg.card] : void 0
436
+ },
384
437
  author: {
385
438
  id: msg.author.id,
386
439
  name: msg.author.name,
@@ -419,7 +472,7 @@ var WebChatClient = class {
419
472
  timestamp: Date.now(),
420
473
  attachments: attachments.length > 0 ? attachments.map((a, i) => ({
421
474
  id: `att-${messageId}-${i}`,
422
- name: a.name || "",
475
+ name: a.name ?? "",
423
476
  url: a.url,
424
477
  size: a.size,
425
478
  mimeType: a.mimeType
@@ -434,7 +487,7 @@ var WebChatClient = class {
434
487
  id: messageId,
435
488
  role: "user",
436
489
  text,
437
- attachments: attachments?.map((a) => ({
490
+ attachments: attachments.map((a) => ({
438
491
  url: a.url,
439
492
  name: a.name,
440
493
  mime_type: a.mimeType,
@@ -453,15 +506,15 @@ var WebChatClient = class {
453
506
  }
454
507
  if (response.text && !response.events?.some((e) => e.type === "message.posted")) {
455
508
  const assistantMessage = {
456
- id: response.id || generateId(),
509
+ id: response.id ?? generateId(),
457
510
  threadId: this.getThreadId(),
458
511
  content: { text: response.text },
459
512
  author: { id: "assistant", name: "Assistant", isBot: true },
460
513
  timestamp: Date.now(),
461
514
  attachments: response.attachments?.map((a, i) => ({
462
- id: `att-${response.id || "msg"}-${i}`,
463
- name: a.name || "",
464
- url: a.url || "",
515
+ id: `att-${response.id ?? "msg"}-${i}`,
516
+ name: a.name ?? "",
517
+ url: a.url ?? "",
465
518
  type: a.type,
466
519
  mimeType: a.mime_type,
467
520
  size: a.size
@@ -518,11 +571,23 @@ var WebChatClient = class {
518
571
  onMessagePosted(handler) {
519
572
  return this.addEventListener("message.posted", handler);
520
573
  }
574
+ onMessageEdited(handler) {
575
+ return this.addEventListener("message:edited", handler);
576
+ }
577
+ onMessageDeleted(handler) {
578
+ return this.addEventListener("message:deleted", handler);
579
+ }
580
+ onReactionAdded(handler) {
581
+ return this.addEventListener("reaction:added", handler);
582
+ }
583
+ onReactionRemoved(handler) {
584
+ return this.addEventListener("reaction:removed", handler);
585
+ }
521
586
  onStreamingChunk(handler) {
522
- return this.addEventListener("streaming.chunk", handler);
587
+ return this.addEventListener("streaming:chunk", handler);
523
588
  }
524
589
  onTypingStarted(handler) {
525
- return this.addEventListener("typing.started", handler);
590
+ return this.addEventListener("typing:started", handler);
526
591
  }
527
592
  getConversationId() {
528
593
  return this.conversationId;
@@ -567,11 +632,11 @@ var WebChatClient = class {
567
632
  timestamp: event.timestamp,
568
633
  attachments: event.attachments?.map((a) => ({
569
634
  id: `att-${event.messageId}-${Math.random().toString(36).slice(2, 8)}`,
570
- name: a.name || "",
571
- url: a.url || "",
635
+ name: a.name ?? "",
636
+ url: a.url ?? "",
572
637
  type: a.type,
573
638
  mimeType: a.mimeType,
574
- size: a.size
639
+ size: a.size ?? void 0
575
640
  }))
576
641
  };
577
642
  this.messages.push(message);
@@ -582,17 +647,14 @@ var WebChatClient = class {
582
647
  const message = this.messages.find((m) => m.id === event.messageId);
583
648
  if (message?.content) {
584
649
  message.content.text = event.newText;
585
- this.notifySubscribers("message:edited", {
586
- messageId: event.messageId,
587
- newText: event.newText
588
- });
650
+ this.notifySubscribers("message:edited", event);
589
651
  }
590
652
  }
591
653
  handleMessageDeleted(event) {
592
654
  const index = this.messages.findIndex((m) => m.id === event.messageId);
593
655
  if (index !== -1) {
594
656
  this.messages.splice(index, 1);
595
- this.notifySubscribers("message:deleted", { messageId: event.messageId });
657
+ this.notifySubscribers("message:deleted", event);
596
658
  }
597
659
  }
598
660
  handleReactionAdded(event) {
@@ -606,7 +668,7 @@ var WebChatClient = class {
606
668
  } else {
607
669
  message.reactions.push({ emoji: event.emoji, count: 1, users: [event.user.id] });
608
670
  }
609
- this.notifySubscribers("reaction:added", { messageId: event.messageId, emoji: event.emoji });
671
+ this.notifySubscribers("reaction:added", event);
610
672
  }
611
673
  handleReactionRemoved(event) {
612
674
  const message = this.messages.find((m) => m.id === event.messageId);
@@ -618,13 +680,13 @@ var WebChatClient = class {
618
680
  reaction.users = reaction.users.filter((id) => id !== event.user.id);
619
681
  if (reaction.count === 0) message.reactions.splice(index, 1);
620
682
  }
621
- this.notifySubscribers("reaction:removed", { messageId: event.messageId, emoji: event.emoji });
683
+ this.notifySubscribers("reaction:removed", event);
622
684
  }
623
685
  handleStreamingChunk(event) {
624
686
  const { messageId, chunk, isFinal } = event;
625
687
  if (!this.streamingMessages.has(messageId)) {
626
688
  this.streamingMessages.set(messageId, { messageId, accumulatedText: "", isComplete: false });
627
- this.notifySubscribers("streaming:started", { messageId });
689
+ this.notifySubscribers("streaming:started", event);
628
690
  }
629
691
  const state = this.streamingMessages.get(messageId);
630
692
  state.accumulatedText += chunk;
@@ -644,24 +706,18 @@ var WebChatClient = class {
644
706
  this.streamingMessages.delete(messageId);
645
707
  this.notifySubscribers("streaming:complete", { messageId, text: state.accumulatedText });
646
708
  } else {
647
- this.notifySubscribers("streaming:chunk", {
648
- messageId,
649
- chunk,
650
- fullText: state.accumulatedText
651
- });
709
+ this.notifySubscribers("streaming:chunk", event);
652
710
  }
653
- this.notifySubscribers("streaming.chunk", event);
654
711
  }
655
712
  handleTypingStarted(event) {
656
713
  if (this.pendingTyping) clearTimeout(this.pendingTyping);
657
- this.notifySubscribers("typing:started", { userId: event.userId });
714
+ this.notifySubscribers("typing:started", event);
658
715
  this.pendingTyping = setTimeout(() => {
659
716
  this.notifySubscribers("typing:stopped", { userId: event.userId });
660
717
  }, 3e3);
661
- this.notifySubscribers("typing.started", event);
662
718
  }
663
719
  handleDMRequested(event) {
664
- this.notifySubscribers("dm.requested", { userId: event.userId, threadId: event.threadId });
720
+ this.notifySubscribers("dm.requested", event);
665
721
  }
666
722
  notifySubscribers(eventType, data) {
667
723
  this.subscribers.get(eventType)?.forEach((handler) => handler(data));
@@ -939,9 +995,19 @@ var PushManager = class _PushManager {
939
995
  try {
940
996
  this.registration = await navigator.serviceWorker.register(
941
997
  this.config.serviceWorkerUrl || "/chat-service-worker.js",
942
- { scope: this.config.serviceWorkerScope || "/" }
998
+ {
999
+ scope: this.config.serviceWorkerScope || "/",
1000
+ type: this.config.serviceWorkerType
1001
+ }
943
1002
  );
944
1003
  await navigator.serviceWorker.ready;
1004
+ navigator.serviceWorker.addEventListener("message", (event) => {
1005
+ const msg = event.data;
1006
+ if (msg?.type === "chat-widget:push-data") {
1007
+ const pushData = msg.data;
1008
+ this.messageListeners.forEach((listener) => listener(pushData));
1009
+ }
1010
+ });
945
1011
  const subscription = await this.registration.pushManager.getSubscription();
946
1012
  this.setStatus(subscription ? "subscribed" : "default");
947
1013
  } catch {