@chativa/connector-directline 0.3.0-beta.3 → 0.4.0

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.js CHANGED
@@ -1,9 +1,301 @@
1
- import { DirectLine as r } from "botframework-directlinejs";
2
- function n() {
3
- return Math.random().toString(36).slice(2, 15) + Math.random().toString(36).slice(2, 15);
1
+ import { DirectLine as v, ConnectionStatus as l } from "botframework-directlinejs";
2
+ const f = /* @__PURE__ */ Symbol("typing"), k = "application/vnd.microsoft.card.hero", b = "application/vnd.microsoft.card.thumbnail", p = "application/vnd.microsoft.card.adaptive", S = "application/vnd.microsoft.card.signin", H = "application/vnd.microsoft.card.oauth", A = "application/vnd.microsoft.card.receipt", D = "application/vnd.microsoft.card.audio", E = "application/vnd.microsoft.card.video", $ = "application/vnd.microsoft.card.animation", T = "application/vnd.microsoft.card.flex";
3
+ function y(i, t) {
4
+ if (i.from.id === t) return null;
5
+ if (i.type === "typing") return f;
6
+ if (i.type !== "message") return null;
7
+ const e = i, n = e.id ?? `dl-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`, s = e.timestamp ? new Date(e.timestamp).getTime() : Date.now(), r = e.channelData, o = R(e);
8
+ if (e.attachments && e.attachments.length > 0) {
9
+ const a = L(e, n, s);
10
+ return a && (o.length > 0 && (a.actions = o), r && (a.data.channelData = r)), a;
11
+ }
12
+ return o.length > 0 ? {
13
+ id: n,
14
+ type: "quick-reply",
15
+ from: "bot",
16
+ data: { text: e.text ?? "", actions: o, ...r ? { channelData: r } : {} },
17
+ timestamp: s
18
+ } : e.text ? {
19
+ id: n,
20
+ type: "text",
21
+ from: "bot",
22
+ data: { text: e.text, ...r ? { channelData: r } : {} },
23
+ timestamp: s
24
+ } : null;
25
+ }
26
+ function L(i, t, e) {
27
+ const n = i.attachments, s = i.attachmentLayout, r = [k, b, T, p];
28
+ return n.every((a) => r.includes(a.contentType)) && (n.length > 1 || s === "carousel") ? {
29
+ id: t,
30
+ type: "carousel",
31
+ from: "bot",
32
+ data: {
33
+ cards: n.map((a) => a.contentType === p ? U(
34
+ a.content
35
+ ) : I(a.content))
36
+ },
37
+ timestamp: e
38
+ } : N(n[0], i, t, e);
39
+ }
40
+ function N(i, t, e, n) {
41
+ const s = i.contentType;
42
+ if (s === k || s === b || s === T) {
43
+ const r = i.content;
44
+ return {
45
+ id: e,
46
+ type: "card",
47
+ from: "bot",
48
+ data: I(r),
49
+ timestamp: n
50
+ };
51
+ }
52
+ if (s === p)
53
+ return _(i.content, e, n);
54
+ if (s === S || s === H) {
55
+ const r = i.content;
56
+ return {
57
+ id: e,
58
+ type: "buttons",
59
+ from: "bot",
60
+ data: {
61
+ text: r.text ?? "Please sign in",
62
+ buttons: C(r.buttons)
63
+ },
64
+ timestamp: n
65
+ };
66
+ }
67
+ if (s === A)
68
+ return O(i.content, e, n);
69
+ if (s === E) {
70
+ const r = i.content, o = r.media?.[0]?.url;
71
+ return o ? {
72
+ id: e,
73
+ type: "video",
74
+ from: "bot",
75
+ data: {
76
+ src: o,
77
+ poster: r.image?.url,
78
+ caption: r.title ?? t.text
79
+ },
80
+ timestamp: n
81
+ } : null;
82
+ }
83
+ if (s === D) {
84
+ const r = i.content, o = r.media?.[0]?.url;
85
+ return o ? {
86
+ id: e,
87
+ type: "file",
88
+ from: "bot",
89
+ data: {
90
+ url: o,
91
+ name: r.title ?? "audio",
92
+ mimeType: "audio/mpeg"
93
+ },
94
+ timestamp: n
95
+ } : null;
96
+ }
97
+ if (s === $) {
98
+ const r = i.content, o = r.media?.[0]?.url;
99
+ return o ? {
100
+ id: e,
101
+ type: "image",
102
+ from: "bot",
103
+ data: {
104
+ src: o,
105
+ caption: r.title ?? t.text
106
+ },
107
+ timestamp: n
108
+ } : null;
109
+ }
110
+ return s.startsWith("image/") && "contentUrl" in i ? {
111
+ id: e,
112
+ type: "image",
113
+ from: "bot",
114
+ data: {
115
+ src: i.contentUrl,
116
+ alt: i.name,
117
+ caption: t.text
118
+ },
119
+ timestamp: n
120
+ } : s.startsWith("video/") && "contentUrl" in i ? {
121
+ id: e,
122
+ type: "video",
123
+ from: "bot",
124
+ data: {
125
+ src: i.contentUrl,
126
+ caption: t.text
127
+ },
128
+ timestamp: n
129
+ } : "contentUrl" in i ? {
130
+ id: e,
131
+ type: "file",
132
+ from: "bot",
133
+ data: {
134
+ url: i.contentUrl,
135
+ name: i.name ?? "file",
136
+ mimeType: s
137
+ },
138
+ timestamp: n
139
+ } : null;
140
+ }
141
+ function I(i) {
142
+ return {
143
+ image: i.images?.[0]?.url,
144
+ title: i.title ?? "",
145
+ subtitle: i.subtitle ?? i.text,
146
+ buttons: C(i.buttons)
147
+ };
148
+ }
149
+ function C(i) {
150
+ return !i || i.length === 0 ? [] : i.map((t) => {
151
+ const e = ("title" in t ? t.title : void 0) ?? String(t.value ?? "");
152
+ switch (t.type) {
153
+ case "openUrl":
154
+ case "signin":
155
+ return { label: e, url: String(t.value ?? "") };
156
+ case "call":
157
+ return { label: e, url: String(t.value ?? "") };
158
+ default:
159
+ return { label: e, value: String(t.value ?? e) };
160
+ }
161
+ });
162
+ }
163
+ function R(i) {
164
+ const t = i.suggestedActions?.actions;
165
+ return !t || t.length === 0 ? [] : t.map((e) => {
166
+ const n = ("title" in e ? e.title : void 0) ?? String(e.value ?? "");
167
+ return e.type === "openUrl" ? { label: n, url: String(e.value ?? "") } : { label: n, value: String(e.value ?? n) };
168
+ });
169
+ }
170
+ function d(i, t, e, n) {
171
+ if (i)
172
+ for (const s of i)
173
+ switch (s.type) {
174
+ case "TextBlock":
175
+ s.text && t.push(s.text);
176
+ break;
177
+ case "Image":
178
+ s.url && n(s.url);
179
+ break;
180
+ case "ActionSet":
181
+ s.actions && x(s.actions, e);
182
+ break;
183
+ case "Container":
184
+ d(s.items, t, e, n);
185
+ break;
186
+ case "ColumnSet":
187
+ for (const r of s.columns ?? [])
188
+ d(r.items, t, e, n);
189
+ break;
190
+ case "Column":
191
+ d(s.items, t, e, n);
192
+ break;
193
+ }
194
+ }
195
+ function w(i) {
196
+ const t = [];
197
+ let e;
198
+ const n = [];
199
+ return d(
200
+ i.body,
201
+ t,
202
+ n,
203
+ (s) => {
204
+ e = e ?? s;
205
+ }
206
+ ), Array.isArray(i.actions) && x(i.actions, n), { texts: t, image: e, buttons: n };
207
+ }
208
+ function U(i) {
209
+ const { texts: t, image: e, buttons: n } = w(i);
210
+ return {
211
+ image: e,
212
+ title: t[0] ?? "",
213
+ subtitle: t.slice(1).join(`
214
+ `),
215
+ buttons: n
216
+ };
4
217
  }
5
- async function a(i) {
6
- const e = n(), t = await fetch(
218
+ function _(i, t, e) {
219
+ const { texts: n, image: s, buttons: r } = w(i), o = i.fallbackText ?? i.speak ?? n.join(`
220
+ `);
221
+ return s ? {
222
+ id: t,
223
+ type: "card",
224
+ from: "bot",
225
+ data: {
226
+ image: s,
227
+ title: n[0] ?? "",
228
+ subtitle: n.slice(1).join(`
229
+ `),
230
+ buttons: r
231
+ },
232
+ timestamp: e
233
+ } : r.length > 0 ? {
234
+ id: t,
235
+ type: "buttons",
236
+ from: "bot",
237
+ data: {
238
+ text: o || "Adaptive Card",
239
+ buttons: r
240
+ },
241
+ timestamp: e
242
+ } : {
243
+ id: t,
244
+ type: "text",
245
+ from: "bot",
246
+ data: { text: o || "[Adaptive Card]" },
247
+ timestamp: e
248
+ };
249
+ }
250
+ function x(i, t) {
251
+ for (const e of i) {
252
+ const n = e.title ?? "Action";
253
+ e.type === "Action.OpenUrl" && e.url ? t.push({ label: n, url: e.url }) : e.type === "Action.Submit" ? t.push({ label: n, value: typeof e.data == "string" ? e.data : n }) : t.push({ label: n, value: n });
254
+ }
255
+ }
256
+ function O(i, t, e) {
257
+ const n = [];
258
+ if (i.title && n.push(`**${i.title}**`), i.facts)
259
+ for (const s of i.facts)
260
+ n.push(`${s.key}: ${s.value}`);
261
+ if (i.items)
262
+ for (const s of i.items) {
263
+ const r = s.price ? ` — ${s.price}` : "";
264
+ n.push(`- ${s.title ?? "Item"}${r}`);
265
+ }
266
+ return i.tax && n.push(`Tax: ${i.tax}`), i.total && n.push(`**Total: ${i.total}**`), {
267
+ id: t,
268
+ type: "text",
269
+ from: "bot",
270
+ data: { text: n.join(`
271
+ `) },
272
+ timestamp: e
273
+ };
274
+ }
275
+ const g = "chativa_directline_userId", h = "chativa_directline_conversation";
276
+ function P() {
277
+ try {
278
+ const t = localStorage.getItem(g);
279
+ if (t) return t;
280
+ } catch {
281
+ }
282
+ const i = Math.random().toString(36).slice(2, 15) + Math.random().toString(36).slice(2, 15);
283
+ try {
284
+ localStorage.setItem(g, i);
285
+ } catch {
286
+ }
287
+ return i;
288
+ }
289
+ function B(i) {
290
+ try {
291
+ const t = JSON.parse(atob(i.split(".")[1]));
292
+ return typeof t.exp == "number" ? t.exp * 1e3 : null;
293
+ } catch {
294
+ return null;
295
+ }
296
+ }
297
+ async function j(i, t) {
298
+ const e = await fetch(
7
299
  "https://directline.botframework.com/v3/directline/tokens/generate",
8
300
  {
9
301
  method: "POST",
@@ -11,55 +303,424 @@ async function a(i) {
11
303
  Authorization: `Bearer ${i}`,
12
304
  "Content-Type": "application/json"
13
305
  },
14
- body: JSON.stringify({ user: { id: e, name: e } })
306
+ body: JSON.stringify({ user: { id: t, name: t } })
15
307
  }
16
308
  );
309
+ if (!e.ok)
310
+ throw new Error(`DirectLine token fetch failed: ${e.status}`);
311
+ const n = await e.json();
312
+ return { token: n.token, conversationId: n.conversationId };
313
+ }
314
+ async function F(i) {
315
+ const t = await fetch(i, { method: "POST" });
17
316
  if (!t.ok)
18
- throw new Error(`DirectLine token fetch failed: ${t.status}`);
19
- const { token: s, conversationId: o } = await t.json();
20
- return { token: s, conversationId: o, userId: e };
317
+ throw new Error(`Token generator failed: ${t.status}`);
318
+ return await t.json();
319
+ }
320
+ async function u(i, t) {
321
+ const n = await fetch(`${t ?? "https://directline.botframework.com/v3/directline"}/tokens/refresh`, {
322
+ method: "POST",
323
+ headers: { Authorization: `Bearer ${i}` }
324
+ });
325
+ if (!n.ok)
326
+ throw new Error(`Token refresh failed: ${n.status}`);
327
+ return await n.json();
21
328
  }
22
- class d {
23
- constructor(e) {
24
- this.name = "directline", this.addSentToHistory = !1, this.messageHandler = null, this.options = e;
329
+ class J {
330
+ constructor(t) {
331
+ this.name = "directline", this.addSentToHistory = !0, this.chativaCtx = null, this.messageHandler = null, this.connectHandler = null, this.disconnectHandler = null, this.typingHandler = null, this.messageStatusHandler = null, this.activitySub = null, this.connectionSub = null, this.typingTimeout = null, this.refreshTimer = null, this.pendingIds = [], this.hasConnectedBefore = !1, this.resolveReady = null, this._skipNextJoin = !1, this.options = t;
332
+ }
333
+ setContext(t) {
334
+ this.chativaCtx = t;
335
+ }
336
+ /**
337
+ * Register (or replace) an event handler for a bot-initiated event activity.
338
+ * Can be called before or after `connect()`.
339
+ *
340
+ * @example
341
+ * ```ts
342
+ * connector.addEventHandler("LocationRequest", (ctx) => {
343
+ * navigator.geolocation.getCurrentPosition((pos) => {
344
+ * ctx.postEvent("webchat/location", { latitude: pos.coords.latitude, ... });
345
+ * });
346
+ * });
347
+ * ```
348
+ */
349
+ addEventHandler(t, e) {
350
+ this.options.eventHandlers ??= {}, this.options.eventHandlers[t] = e;
351
+ }
352
+ /** Remove a previously registered event handler by name. */
353
+ removeEventHandler(t) {
354
+ return this.options.eventHandlers?.[t] ? (delete this.options.eventHandlers[t], !0) : !1;
355
+ }
356
+ /** Check whether an event handler is registered for the given name. */
357
+ hasEventHandler(t) {
358
+ return !!this.options.eventHandlers?.[t];
359
+ }
360
+ /** Return the names of all registered event handlers. */
361
+ getEventHandlerNames() {
362
+ return Object.keys(this.options.eventHandlers ?? {});
25
363
  }
26
364
  async connect() {
27
- let e;
28
- if (this.options.token)
29
- e = this.options.token, this.userId = n();
30
- else if (this.options.secret) {
31
- const t = await a(this.options.secret);
32
- e = t.token, this.conversationId = t.conversationId, this.userId = t.userId;
33
- } else
34
- throw new Error("DirectLineConnector: provide either token or secret.");
35
- this.directLine = new r({ token: e }), this.directLine.activity$.filter((t) => t.type === "message").subscribe((t) => {
36
- this.messageHandler?.({
37
- id: t.id ?? `dl-${Date.now()}`,
38
- type: "text",
39
- data: { text: t.text ?? "" },
40
- timestamp: Date.now()
41
- });
365
+ this.userId = this.options.userId ?? P(), this.userName = this.options.userName;
366
+ let t = !1;
367
+ if (this.options.resumeConversation) {
368
+ const n = this.loadPersistedConversation();
369
+ if (n)
370
+ try {
371
+ const s = await u(
372
+ n.token,
373
+ this.options.domain
374
+ );
375
+ this.token = s.token, this.conversationId = n.conversationId, this.watermark = n.watermark, this.userId = n.userId, t = !0;
376
+ } catch {
377
+ this.clearPersistedConversation();
378
+ }
379
+ }
380
+ if (!t)
381
+ if (this.options.tokenGeneratorUrl) {
382
+ const n = await F(
383
+ this.options.tokenGeneratorUrl
384
+ );
385
+ this.token = n.token, n.conversationId && (this.conversationId = n.conversationId), n.userId && (this.userId = n.userId);
386
+ } else if (this.options.token)
387
+ this.token = this.options.token;
388
+ else if (this.options.secret) {
389
+ const n = await j(
390
+ this.options.secret,
391
+ this.userId
392
+ );
393
+ this.token = n.token, this.conversationId = n.conversationId;
394
+ } else
395
+ throw new Error(
396
+ "DirectLineConnector: provide token, secret, or tokenGeneratorUrl."
397
+ );
398
+ this.scheduleTokenRefresh(), this.directLine = new v({
399
+ token: this.token,
400
+ domain: this.options.domain,
401
+ ...t ? {
402
+ conversationId: this.conversationId,
403
+ watermark: this.watermark
404
+ } : {}
405
+ }), t && (this.hasConnectedBefore = !0, this._skipNextJoin = !0);
406
+ const e = new Promise((n) => {
407
+ this.resolveReady = n;
42
408
  });
409
+ this.startListening(t), await e, this.connectHandler?.(), this.options.resumeConversation && this.persistConversation();
43
410
  }
44
411
  async disconnect() {
45
- this.messageHandler = null;
412
+ this.activitySub?.unsubscribe(), this.activitySub = null, this.connectionSub?.unsubscribe(), this.connectionSub = null, this.clearTypingTimeout(), this.clearRefreshTimer();
413
+ try {
414
+ this.directLine?.end();
415
+ } catch {
416
+ }
417
+ this.messageHandler = null, this.connectHandler = null, this.disconnectHandler = null, this.typingHandler = null, this.messageStatusHandler = null;
46
418
  }
47
- async sendMessage(e) {
419
+ /** Clear persisted conversation state and reset watermark. */
420
+ clearConversation() {
421
+ this.watermark = void 0, this.clearPersistedConversation();
422
+ }
423
+ async sendMessage(t) {
424
+ return this.pendingIds.push(t.id), this.messageStatusHandler?.(t.id, "sent"), new Promise((e, n) => {
425
+ this.directLine.postActivity({
426
+ type: "message",
427
+ from: {
428
+ id: this.userId,
429
+ name: this.userName ?? this.userId
430
+ },
431
+ text: t.data.text ?? "",
432
+ conversation: { id: this.conversationId },
433
+ channelId: "directline",
434
+ ...this.options.locale ? { locale: this.options.locale } : {},
435
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
436
+ id: t.id
437
+ }).subscribe({
438
+ next: () => e(),
439
+ error: (s) => n(s)
440
+ });
441
+ });
442
+ }
443
+ async sendFile(t) {
444
+ const n = `${this.options.domain ?? "https://directline.botframework.com/v3/directline"}/conversations/${this.conversationId}/upload?userId=${encodeURIComponent(this.userId)}`, s = new FormData();
445
+ s.append("file", t, t.name);
446
+ const r = await fetch(n, {
447
+ method: "POST",
448
+ headers: { Authorization: `Bearer ${this.token}` },
449
+ body: s
450
+ });
451
+ if (!r.ok)
452
+ throw new Error(`DirectLine file upload failed: ${r.status}`);
453
+ }
454
+ async sendFeedback(t, e) {
455
+ const s = this.chativaCtx?.messages.getAll().find((o) => o.id === t)?.data?.channelData?.correlationId;
456
+ if (!s) {
457
+ console.warn(
458
+ "[DirectLineConnector] sendFeedback: no correlationId found for message",
459
+ t
460
+ );
461
+ return;
462
+ }
463
+ const r = e === "like" ? 0 : 1;
464
+ return new Promise((o, a) => {
465
+ this.directLine.postActivity({
466
+ type: "event",
467
+ name: "webchat/messageFeedback",
468
+ from: { id: this.userId, name: this.userId },
469
+ value: { correlationId: s, feedbackType: r }
470
+ }).subscribe({
471
+ next: () => o(),
472
+ error: (c) => a(c)
473
+ });
474
+ });
475
+ }
476
+ async loadHistory(t) {
477
+ const e = this.options.domain ?? "https://directline.botframework.com/v3/directline", n = t ? `${e}/conversations/${this.conversationId}/activities?watermark=${encodeURIComponent(t)}` : `${e}/conversations/${this.conversationId}/activities`, s = await fetch(n, {
478
+ headers: { Authorization: `Bearer ${this.token}` }
479
+ });
480
+ if (!s.ok)
481
+ throw new Error(`DirectLine history fetch failed: ${s.status}`);
482
+ const r = await s.json(), o = [];
483
+ for (const a of r.activities) {
484
+ if (a.from.id === this.userId && a.type === "message") {
485
+ const m = a;
486
+ m.text && o.push({
487
+ id: a.id ?? `dl-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`,
488
+ type: "text",
489
+ from: "user",
490
+ data: { text: m.text },
491
+ timestamp: a.timestamp ? new Date(a.timestamp).getTime() : Date.now()
492
+ });
493
+ continue;
494
+ }
495
+ const c = y(a, this.userId);
496
+ c !== null && c !== f && o.push(c);
497
+ }
498
+ return {
499
+ messages: o,
500
+ hasMore: !1,
501
+ cursor: r.watermark
502
+ };
503
+ }
504
+ onMessage(t) {
505
+ this.messageHandler = t;
506
+ }
507
+ onConnect(t) {
508
+ this.connectHandler = t;
509
+ }
510
+ onDisconnect(t) {
511
+ this.disconnectHandler = t;
512
+ }
513
+ onTyping(t) {
514
+ this.typingHandler = t;
515
+ }
516
+ onMessageStatus(t) {
517
+ this.messageStatusHandler = t;
518
+ }
519
+ /* ── Private helpers ───────────────────────────────────────────── */
520
+ /**
521
+ * Subscribe to DirectLine connectionStatus$ and activity$ observables.
522
+ * Extracted so it can be re-used after an ExpiredToken reconnect.
523
+ */
524
+ startListening(t) {
525
+ this.connectionSub = this.directLine.connectionStatus$.subscribe(
526
+ (e) => {
527
+ switch (e) {
528
+ case l.Online:
529
+ this.sendJoinEvent(), t && this.resolveReady && (this.resolveReady(), this.resolveReady = null);
530
+ break;
531
+ case l.ExpiredToken:
532
+ this.handleExpiredToken();
533
+ break;
534
+ case l.FailedToConnect:
535
+ this.options.resumeConversation && this.clearPersistedConversation(), this.disconnectHandler?.("Failed to connect");
536
+ break;
537
+ case l.Ended:
538
+ this.disconnectHandler?.("Connection ended");
539
+ break;
540
+ }
541
+ }
542
+ ), this.activitySub = this.directLine.activity$.subscribe((e) => {
543
+ try {
544
+ if (e.id && (this.watermark = e.id), !this.conversationId && e.conversation?.id && (this.conversationId = e.conversation.id), e.from.id === this.userId) {
545
+ if (e.type === "message") {
546
+ const s = this.pendingIds.shift();
547
+ s && this.messageStatusHandler?.(s, "read");
548
+ }
549
+ return;
550
+ }
551
+ if (e.type === "event" && e.name) {
552
+ if (e.name === "DisableFeedbackButton" && e.value) {
553
+ const r = e.value;
554
+ r.CorrelationId && this.handleDisableFeedback(
555
+ r.CorrelationId,
556
+ r.FeedbackType
557
+ );
558
+ }
559
+ const s = this.options.eventHandlers?.[e.name];
560
+ s && s(this.createEventContext(e));
561
+ return;
562
+ }
563
+ const n = y(e, this.userId);
564
+ if (n === f) {
565
+ this.handleTyping();
566
+ return;
567
+ }
568
+ n !== null && (this.clearTypingTimeout(), this.typingHandler?.(!1), this.resolveReady && (this.resolveReady(), this.resolveReady = null), this.messageHandler?.(n), this.options.resumeConversation && this.persistConversation());
569
+ } catch (n) {
570
+ console.warn(
571
+ "[DirectLineConnector] Activity mapping error:",
572
+ n
573
+ );
574
+ }
575
+ });
576
+ }
577
+ /** Build the context object passed to custom event handlers. */
578
+ createEventContext(t) {
579
+ const e = { id: this.userId, name: this.userName ?? this.userId };
580
+ return {
581
+ activity: t,
582
+ userId: this.userId,
583
+ userName: this.userName ?? this.userId,
584
+ postEvent: (n, s) => {
585
+ this.directLine.postActivity({
586
+ type: "event",
587
+ name: n,
588
+ from: e,
589
+ ...this.options.locale ? { locale: this.options.locale } : {},
590
+ value: s
591
+ }).subscribe();
592
+ },
593
+ chativa: this.chativaCtx
594
+ };
595
+ }
596
+ /** Send webchat/join (first connect) or webchat/rejoin (reconnect) event. */
597
+ sendJoinEvent() {
598
+ const t = this.options.locale, e = { id: this.userId, name: this.userName ?? this.userId };
599
+ if (this._skipNextJoin) {
600
+ this._skipNextJoin = !1, this.hasConnectedBefore = !0, this.directLine.postActivity({
601
+ type: "event",
602
+ name: "webchat/rejoin",
603
+ from: e,
604
+ ...t ? { locale: t } : {},
605
+ value: { ...t ? { language: t } : {} }
606
+ }).subscribe();
607
+ return;
608
+ }
48
609
  this.directLine.postActivity({
49
- type: "message",
50
- from: { id: this.userId },
51
- text: e.data.text ?? "",
52
- conversation: { id: this.conversationId },
53
- channelId: "directline",
54
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
55
- id: e.id
610
+ type: "event",
611
+ name: "webchat/join",
612
+ from: e,
613
+ ...t ? { locale: t } : {},
614
+ value: {
615
+ ...t ? { language: t } : {},
616
+ ...this.options.joinParameters
617
+ }
56
618
  }).subscribe();
57
619
  }
58
- onMessage(e) {
59
- this.messageHandler = e;
620
+ /** Handle DisableFeedbackButton event — find the message by correlationId and patch it. */
621
+ handleDisableFeedback(t, e) {
622
+ const n = this.chativaCtx?.messages.getAll();
623
+ if (!n) return;
624
+ const s = n.find(
625
+ (r) => r.data?.channelData?.correlationId === t
626
+ );
627
+ s && this.chativaCtx.messages.update(s.id, {
628
+ data: { ...s.data, feedbackDisabled: !0, feedbackType: e }
629
+ });
630
+ }
631
+ handleTyping() {
632
+ this.clearTypingTimeout(), this.typingHandler?.(!0), this.typingTimeout = setTimeout(() => {
633
+ this.typingHandler?.(!1), this.typingTimeout = null;
634
+ }, 3e3);
635
+ }
636
+ clearTypingTimeout() {
637
+ this.typingTimeout !== null && (clearTimeout(this.typingTimeout), this.typingTimeout = null);
638
+ }
639
+ /* ── Token refresh ──────────────────────────────────────────────── */
640
+ /** Schedule a token refresh 60 seconds before expiry. */
641
+ scheduleTokenRefresh() {
642
+ this.clearRefreshTimer();
643
+ const t = B(this.token);
644
+ if (!t) return;
645
+ const e = t - Date.now() - 6e4;
646
+ if (e <= 0) {
647
+ this.refreshTokenNow();
648
+ return;
649
+ }
650
+ this.refreshTimer = setTimeout(() => this.refreshTokenNow(), e);
651
+ }
652
+ /** Pre-emptively refresh the token before it expires. */
653
+ async refreshTokenNow() {
654
+ try {
655
+ const t = await u(
656
+ this.token,
657
+ this.options.domain
658
+ );
659
+ this.token = t.token, this.scheduleTokenRefresh(), this.options.resumeConversation && this.persistConversation();
660
+ } catch (t) {
661
+ console.warn("[DirectLineConnector] Token refresh failed:", t);
662
+ }
663
+ }
664
+ /**
665
+ * Called when DirectLine emits ExpiredToken status.
666
+ * Refreshes the token and recreates the DirectLine connection.
667
+ */
668
+ async handleExpiredToken() {
669
+ try {
670
+ const t = await u(
671
+ this.token,
672
+ this.options.domain
673
+ );
674
+ this.token = t.token, this.scheduleTokenRefresh();
675
+ } catch {
676
+ this.options.resumeConversation && this.clearPersistedConversation(), this.disconnectHandler?.("Token expired and refresh failed");
677
+ return;
678
+ }
679
+ this.activitySub?.unsubscribe(), this.connectionSub?.unsubscribe(), this.clearTypingTimeout();
680
+ try {
681
+ this.directLine.end();
682
+ } catch {
683
+ }
684
+ this.directLine = new v({
685
+ token: this.token,
686
+ domain: this.options.domain,
687
+ conversationId: this.conversationId,
688
+ watermark: this.watermark
689
+ }), this.startListening(!0), this.options.resumeConversation && this.persistConversation();
690
+ }
691
+ clearRefreshTimer() {
692
+ this.refreshTimer !== null && (clearTimeout(this.refreshTimer), this.refreshTimer = null);
693
+ }
694
+ /* ── Conversation persistence ───────────────────────────────────── */
695
+ persistConversation() {
696
+ if (!(!this.conversationId || !this.token))
697
+ try {
698
+ const t = {
699
+ conversationId: this.conversationId,
700
+ token: this.token,
701
+ watermark: this.watermark,
702
+ userId: this.userId
703
+ };
704
+ localStorage.setItem(h, JSON.stringify(t));
705
+ } catch {
706
+ }
707
+ }
708
+ loadPersistedConversation() {
709
+ try {
710
+ const t = localStorage.getItem(h);
711
+ return t ? JSON.parse(t) : null;
712
+ } catch {
713
+ return null;
714
+ }
715
+ }
716
+ clearPersistedConversation() {
717
+ try {
718
+ localStorage.removeItem(h);
719
+ } catch {
720
+ }
60
721
  }
61
722
  }
62
723
  export {
63
- d as DirectLineConnector
724
+ J as DirectLineConnector
64
725
  };
65
726
  //# sourceMappingURL=index.js.map