@brandfine/client 0.13.0 → 0.14.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/CHANGELOG.md +30 -0
- package/README.md +11 -0
- package/dist/index.cjs +196 -11
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +101 -7
- package/dist/index.d.ts +101 -7
- package/dist/index.js +196 -11
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -17,12 +17,23 @@ export { BrandfineWebhookEvent, BrandfineWebhookHandlerOptions, BrandfineWebhook
|
|
|
17
17
|
* who talks through the widget on one page and an inline panel on
|
|
18
18
|
* another continues the same thread.
|
|
19
19
|
*/
|
|
20
|
-
|
|
20
|
+
/**
|
|
21
|
+
* `BOT` = AI-assistant replies (arriving once a workspace enables the
|
|
22
|
+
* live-chat AI agent). Render `BOT` on the business/agent side, ideally
|
|
23
|
+
* with an "AI" label — and treat any FUTURE unknown sender value the
|
|
24
|
+
* same way (agent-side, generic label) rather than switching
|
|
25
|
+
* exhaustively: the server may add sender kinds before your copy of
|
|
26
|
+
* this SDK learns their names.
|
|
27
|
+
*/
|
|
28
|
+
type ChatMessageSender = 'VISITOR' | 'AGENT' | 'SYSTEM' | 'BOT';
|
|
21
29
|
type ChatMessage = {
|
|
22
30
|
id: string;
|
|
23
31
|
body: string;
|
|
24
32
|
sender: ChatMessageSender;
|
|
25
33
|
createdAt: string;
|
|
34
|
+
/** Monotonic per-conversation sequence — the realtime resume
|
|
35
|
+
* cursor. Optional: older APIs don't send it. */
|
|
36
|
+
eventId?: number;
|
|
26
37
|
};
|
|
27
38
|
type ConversationStatus = 'OPEN' | 'CLOSED';
|
|
28
39
|
type LiveChatSessionState = 'CONNECTING' | 'OPEN' | 'CLOSED' | 'ERROR';
|
|
@@ -38,6 +49,10 @@ type LiveChatRuntimeConfig = {
|
|
|
38
49
|
offlineMessage: string | null;
|
|
39
50
|
theme: Record<string, string> | null;
|
|
40
51
|
online?: boolean;
|
|
52
|
+
/** Realtime endpoint (wss://…). Absent = poll (older API, or
|
|
53
|
+
* the transport is off). The session upgrades automatically
|
|
54
|
+
* when present; consumers never touch it. */
|
|
55
|
+
realtimeUrl?: string;
|
|
41
56
|
};
|
|
42
57
|
type LiveChatSessionSnapshot = {
|
|
43
58
|
messages: ChatMessage[];
|
|
@@ -64,10 +79,12 @@ type StartConversationResult = {
|
|
|
64
79
|
};
|
|
65
80
|
type LiveChatCreateSessionOptions = {
|
|
66
81
|
/** The server-half bootstrap (same object `install()` takes) —
|
|
67
|
-
* supplies the publishable key
|
|
82
|
+
* supplies the publishable key (and, when the API advertises it,
|
|
83
|
+
* the realtime endpoint — passed through wholesale). */
|
|
68
84
|
config: {
|
|
69
85
|
enabled: boolean;
|
|
70
86
|
publishableKey?: string;
|
|
87
|
+
realtimeUrl?: string;
|
|
71
88
|
};
|
|
72
89
|
/** Signed identity — SAME shape and rules as `install()`. A bad
|
|
73
90
|
* signature downgrades to anonymous server-side; it is never a
|
|
@@ -259,6 +276,14 @@ type CreateAppointmentRequestInput = {
|
|
|
259
276
|
requestedAt: string;
|
|
260
277
|
/** Optional cookie-derived session id from the consumer site. */
|
|
261
278
|
visitorSessionId?: string;
|
|
279
|
+
/** Signed host-app identity — the SAME payload shape and signature
|
|
280
|
+
* Live Chat accepts, so one `liveChat.identityToken(externalId)`
|
|
281
|
+
* call signs for both plugins. When present and valid, the booking
|
|
282
|
+
* is attributed to the person (verified name/email take precedence
|
|
283
|
+
* over the free-text fields above, and the booking links to their
|
|
284
|
+
* chat threads via the shared externalId). Invalid or absent →
|
|
285
|
+
* the booking proceeds anonymously — it is never rejected. */
|
|
286
|
+
visitor?: VerifiedVisitor;
|
|
262
287
|
};
|
|
263
288
|
type CreatedAppointmentRequest = {
|
|
264
289
|
id: string;
|
|
@@ -266,11 +291,40 @@ type CreatedAppointmentRequest = {
|
|
|
266
291
|
requestedAt: string;
|
|
267
292
|
durationMinutes: number;
|
|
268
293
|
status: 'PENDING';
|
|
294
|
+
/** True iff `visitor.identityToken` HMAC-verified — your signal
|
|
295
|
+
* that the booking landed attributed rather than anonymous. */
|
|
296
|
+
identityVerified: boolean;
|
|
269
297
|
/** Visitor's self-cancel token. Embed it in confirmation
|
|
270
298
|
* emails / on-page UI so the visitor can cancel without an
|
|
271
299
|
* account. One-time use; revoked once any party acts. */
|
|
272
300
|
cancellationToken: string | null;
|
|
273
301
|
};
|
|
302
|
+
type AppointmentStatus = 'PENDING' | 'CONFIRMED' | 'REJECTED' | 'CANCELLED';
|
|
303
|
+
/** One appointment as the identity-scoped read-back returns it —
|
|
304
|
+
* the visitor-safe projection ("where does my request stand?"). */
|
|
305
|
+
type Appointment = {
|
|
306
|
+
id: string;
|
|
307
|
+
status: AppointmentStatus;
|
|
308
|
+
/** UTC ISO 8601 of the requested slot start (reflects the current
|
|
309
|
+
* slot after a reschedule). */
|
|
310
|
+
requestedAt: string;
|
|
311
|
+
/** The confirmed slot — non-null only once CONFIRMED. */
|
|
312
|
+
scheduledAt: string | null;
|
|
313
|
+
durationMinutes: number;
|
|
314
|
+
/** Workspace's IANA timezone for local rendering. */
|
|
315
|
+
timezone: string;
|
|
316
|
+
/** Customer's note — populated only on REJECTED. */
|
|
317
|
+
declineReason: string | null;
|
|
318
|
+
rescheduleCount: number;
|
|
319
|
+
respondedAt: string | null;
|
|
320
|
+
createdAt: string;
|
|
321
|
+
};
|
|
322
|
+
/** Proof of identity for read-back calls: the same externalId +
|
|
323
|
+
* `liveChat.identityToken(externalId)` pair used when booking. */
|
|
324
|
+
type AppointmentIdentity = {
|
|
325
|
+
externalId: string;
|
|
326
|
+
identityToken: string;
|
|
327
|
+
};
|
|
274
328
|
type AppointmentsApi = {
|
|
275
329
|
/**
|
|
276
330
|
* Available slots for the workspace's booking window.
|
|
@@ -286,12 +340,37 @@ type AppointmentsApi = {
|
|
|
286
340
|
* the slot is still bookable; if it isn't, throws
|
|
287
341
|
* `BrandfineApiError` with status 404 / 409.
|
|
288
342
|
*
|
|
289
|
-
*
|
|
290
|
-
*
|
|
291
|
-
*
|
|
292
|
-
*
|
|
343
|
+
* Pass `visitor` (signed with `liveChat.identityToken()`) to book
|
|
344
|
+
* as a known person — that unlocks `list`/`get` read-back below.
|
|
345
|
+
* Anonymous bookings remain fully supported; their status flow
|
|
346
|
+
* stays email-driven via the cancellation-token link.
|
|
293
347
|
*/
|
|
294
348
|
createRequest: (input: CreateAppointmentRequestInput) => Promise<CreatedAppointmentRequest>;
|
|
349
|
+
/**
|
|
350
|
+
* Every appointment belonging to the verified person, newest
|
|
351
|
+
* first. Requires a valid identity signature — throws
|
|
352
|
+
* `BrandfineApiError` 401 on a bad one (reads need proof; there
|
|
353
|
+
* is no anonymous downgrade for reading history). Server-side
|
|
354
|
+
* only, like `identityToken()` itself.
|
|
355
|
+
*/
|
|
356
|
+
list: (identity: AppointmentIdentity) => Promise<Appointment[]>;
|
|
357
|
+
/**
|
|
358
|
+
* One appointment by id, identity-scoped. 404s when the id does
|
|
359
|
+
* not exist OR belongs to someone else — indistinguishable by
|
|
360
|
+
* design.
|
|
361
|
+
*/
|
|
362
|
+
get: (id: string, identity: AppointmentIdentity) => Promise<Appointment>;
|
|
363
|
+
/**
|
|
364
|
+
* Spend the one-time `cancellationToken` from `createRequest` to
|
|
365
|
+
* cancel a still-PENDING request. The token IS the credential —
|
|
366
|
+
* no id or API key needed. Throws `BrandfineApiError` 400 when
|
|
367
|
+
* the request is no longer PENDING, 404 when the token is
|
|
368
|
+
* unknown/already spent.
|
|
369
|
+
*/
|
|
370
|
+
cancel: (cancellationToken: string) => Promise<{
|
|
371
|
+
id: string;
|
|
372
|
+
status: 'CANCELLED';
|
|
373
|
+
}>;
|
|
295
374
|
};
|
|
296
375
|
type AnalyticsConfig = {
|
|
297
376
|
enabled: false;
|
|
@@ -463,6 +542,12 @@ type LiveChatBootstrap = {
|
|
|
463
542
|
theme: Record<string, string> | null;
|
|
464
543
|
/** Widget bundle path relative to the API base URL. */
|
|
465
544
|
scriptPath: string;
|
|
545
|
+
/** Realtime endpoint (wss://…). OPTIONAL by design — this is
|
|
546
|
+
* what makes the upgrade non-breaking: an older SDK ignores
|
|
547
|
+
* it, a newer SDK against an older API sees it absent and
|
|
548
|
+
* polls. Consumers pass `config` through wholesale, so it
|
|
549
|
+
* reaches the browser with no change on their side. */
|
|
550
|
+
realtimeUrl?: string;
|
|
466
551
|
};
|
|
467
552
|
type LiveChatInstallResult = {
|
|
468
553
|
installed: false;
|
|
@@ -478,6 +563,13 @@ type LiveChatInstallResult = {
|
|
|
478
563
|
* the Brandfine API verifies the signature. An invalid or missing
|
|
479
564
|
* token silently downgrades the conversation to anonymous.
|
|
480
565
|
*/
|
|
566
|
+
/**
|
|
567
|
+
* A signed host-app identity payload. Named per-plugin below for
|
|
568
|
+
* discoverability, but it is ONE shape signed ONE way: a token from
|
|
569
|
+
* `liveChat.identityToken(externalId)` is accepted by Live Chat AND
|
|
570
|
+
* Appointments (`createRequest.visitor`, `list`/`get`).
|
|
571
|
+
*/
|
|
572
|
+
type VerifiedVisitor = LiveChatVisitor;
|
|
481
573
|
type LiveChatVisitor = {
|
|
482
574
|
/** Your app's stable id for this person (user id, lead reference…).
|
|
483
575
|
* Conversations sharing an externalId are the same person across
|
|
@@ -588,9 +680,11 @@ type LiveChatApi = {
|
|
|
588
680
|
}) => Promise<StartConversationResult>;
|
|
589
681
|
sendMessage: (conversationToken: string, input: {
|
|
590
682
|
body: string;
|
|
683
|
+
clientId?: string;
|
|
591
684
|
}) => Promise<ChatMessage>;
|
|
592
685
|
history: (conversationToken: string, opts?: {
|
|
593
686
|
after?: string;
|
|
687
|
+
afterEvent?: number;
|
|
594
688
|
}) => Promise<{
|
|
595
689
|
messages: ChatMessage[];
|
|
596
690
|
status: ConversationStatus;
|
|
@@ -612,4 +706,4 @@ declare function createBrandfineClient(config: BrandfineClientConfig): Brandfine
|
|
|
612
706
|
*/
|
|
613
707
|
declare const SDK_VERSION: "0.0.0";
|
|
614
708
|
|
|
615
|
-
export { type AnalyticsConfig, type AnalyticsInstallResult, type AnalyticsOverview, type AnalyticsOverviewRange, BrandfineApiError, BrandfineCategory, type BrandfineClient, type BrandfineClientConfig, BrandfineNavigation, BrandfinePost, BrandfineWorkspace, type ChatMessage, type ChatMessageSender, type ConversationStatus, type CreateSubmissionInput, type InstallOptions, ListCategoriesOptions, ListPostsOptions, type LiveChatBootstrap, type LiveChatCreateSessionOptions, type LiveChatInstallOptions, type LiveChatInstallResult, type LiveChatRuntimeConfig, type LiveChatSession, type LiveChatSessionSnapshot, type LiveChatSessionState, type LiveChatVisitor, SDK_VERSION, type StartConversationResult, type Submission, createBrandfineClient };
|
|
709
|
+
export { type AnalyticsConfig, type AnalyticsInstallResult, type AnalyticsOverview, type AnalyticsOverviewRange, type Appointment, type AppointmentAvailability, type AppointmentIdentity, type AppointmentSlot, type AppointmentStatus, BrandfineApiError, BrandfineCategory, type BrandfineClient, type BrandfineClientConfig, BrandfineNavigation, BrandfinePost, BrandfineWorkspace, type ChatMessage, type ChatMessageSender, type ConversationStatus, type CreateAppointmentRequestInput, type CreateSubmissionInput, type CreatedAppointmentRequest, type InstallOptions, ListCategoriesOptions, ListPostsOptions, type LiveChatBootstrap, type LiveChatCreateSessionOptions, type LiveChatInstallOptions, type LiveChatInstallResult, type LiveChatRuntimeConfig, type LiveChatSession, type LiveChatSessionSnapshot, type LiveChatSessionState, type LiveChatVisitor, SDK_VERSION, type StartConversationResult, type Submission, type VerifiedVisitor, createBrandfineClient };
|
package/dist/index.js
CHANGED
|
@@ -60,34 +60,63 @@ async function createLiveChatSession(wire, opts) {
|
|
|
60
60
|
const listeners = /* @__PURE__ */ new Set();
|
|
61
61
|
const seen = /* @__PURE__ */ new Set();
|
|
62
62
|
let cursor;
|
|
63
|
+
let lastEventId = 0;
|
|
63
64
|
let token;
|
|
64
65
|
let timer;
|
|
65
66
|
let closed = false;
|
|
66
67
|
let ticking = false;
|
|
67
68
|
const aborter = new AbortController();
|
|
69
|
+
let realtimeUrl = opts.config.realtimeUrl;
|
|
70
|
+
let ws;
|
|
71
|
+
let wsLive = false;
|
|
72
|
+
let wsFailures = 0;
|
|
73
|
+
let wsGivenUp = false;
|
|
74
|
+
let reconnectTimer;
|
|
75
|
+
let pingTimer;
|
|
68
76
|
function emit(patch) {
|
|
69
77
|
snapshot = { ...snapshot, ...patch };
|
|
70
78
|
for (const listener of listeners) listener(snapshot);
|
|
71
79
|
}
|
|
72
80
|
function appendMessages(incoming) {
|
|
73
81
|
const fresh = incoming.filter((m) => !seen.has(m.id));
|
|
74
|
-
if (fresh.length === 0)
|
|
75
|
-
|
|
82
|
+
if (fresh.length === 0) {
|
|
83
|
+
for (const m of incoming) {
|
|
84
|
+
if (typeof m.eventId === "number" && m.eventId > lastEventId) {
|
|
85
|
+
lastEventId = m.eventId;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
for (const m of fresh) {
|
|
91
|
+
seen.add(m.id);
|
|
92
|
+
if (typeof m.eventId === "number" && m.eventId > lastEventId) {
|
|
93
|
+
lastEventId = m.eventId;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
76
96
|
const messages = [...snapshot.messages, ...fresh];
|
|
77
97
|
cursor = messages[messages.length - 1].createdAt;
|
|
78
98
|
emit({ messages });
|
|
79
99
|
}
|
|
100
|
+
function markClosed() {
|
|
101
|
+
if (snapshot.status !== "CLOSED") emit({ status: "CLOSED" });
|
|
102
|
+
stopPolling();
|
|
103
|
+
teardownRealtime();
|
|
104
|
+
wsGivenUp = true;
|
|
105
|
+
}
|
|
80
106
|
async function tick() {
|
|
81
107
|
if (ticking || closed || !token) return;
|
|
82
108
|
ticking = true;
|
|
83
109
|
try {
|
|
84
|
-
const result = await wire.history(
|
|
110
|
+
const result = await wire.history(
|
|
111
|
+
token,
|
|
112
|
+
// The eventId cursor is exact (no equal-millisecond drop
|
|
113
|
+
// window); the timestamp cursor remains for old APIs that
|
|
114
|
+
// never sent eventIds.
|
|
115
|
+
lastEventId > 0 ? { afterEvent: lastEventId } : { after: cursor }
|
|
116
|
+
);
|
|
85
117
|
if (closed) return;
|
|
86
118
|
appendMessages(result.messages);
|
|
87
|
-
if (result.status === "CLOSED"
|
|
88
|
-
emit({ status: "CLOSED" });
|
|
89
|
-
stopPolling();
|
|
90
|
-
}
|
|
119
|
+
if (result.status === "CLOSED") markClosed();
|
|
91
120
|
} catch {
|
|
92
121
|
} finally {
|
|
93
122
|
ticking = false;
|
|
@@ -101,6 +130,119 @@ async function createLiveChatSession(wire, opts) {
|
|
|
101
130
|
if (timer !== void 0) clearInterval(timer);
|
|
102
131
|
timer = void 0;
|
|
103
132
|
}
|
|
133
|
+
function teardownRealtime() {
|
|
134
|
+
if (reconnectTimer !== void 0) clearTimeout(reconnectTimer);
|
|
135
|
+
reconnectTimer = void 0;
|
|
136
|
+
if (pingTimer !== void 0) clearInterval(pingTimer);
|
|
137
|
+
pingTimer = void 0;
|
|
138
|
+
wsLive = false;
|
|
139
|
+
if (ws) {
|
|
140
|
+
const socket = ws;
|
|
141
|
+
ws = void 0;
|
|
142
|
+
try {
|
|
143
|
+
socket.onopen = socket.onmessage = socket.onclose = socket.onerror = null;
|
|
144
|
+
socket.close();
|
|
145
|
+
} catch {
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
function scheduleReconnect() {
|
|
150
|
+
if (closed || wsGivenUp || reconnectTimer !== void 0) return;
|
|
151
|
+
if (wsFailures >= 5) {
|
|
152
|
+
wsGivenUp = true;
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
const base = Math.min(3e4, 1e3 * 2 ** wsFailures);
|
|
156
|
+
const delay = base / 2 + Math.random() * (base / 2);
|
|
157
|
+
reconnectTimer = setTimeout(() => {
|
|
158
|
+
reconnectTimer = void 0;
|
|
159
|
+
startRealtime();
|
|
160
|
+
}, delay);
|
|
161
|
+
}
|
|
162
|
+
function handleSocketDown() {
|
|
163
|
+
teardownRealtime();
|
|
164
|
+
if (closed || wsGivenUp || snapshot.status === "CLOSED") return;
|
|
165
|
+
startPolling();
|
|
166
|
+
wsFailures += 1;
|
|
167
|
+
scheduleReconnect();
|
|
168
|
+
}
|
|
169
|
+
function startRealtime() {
|
|
170
|
+
if (closed || wsGivenUp || wsLive || ws !== void 0 || !realtimeUrl || !token || !publishableKey || typeof WebSocket === "undefined" || snapshot.status === "CLOSED") {
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
let socket;
|
|
174
|
+
try {
|
|
175
|
+
socket = new WebSocket(realtimeUrl);
|
|
176
|
+
} catch {
|
|
177
|
+
wsFailures += 1;
|
|
178
|
+
scheduleReconnect();
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
ws = socket;
|
|
182
|
+
socket.onopen = () => {
|
|
183
|
+
try {
|
|
184
|
+
socket.send(
|
|
185
|
+
JSON.stringify({
|
|
186
|
+
type: "auth",
|
|
187
|
+
publishableKey,
|
|
188
|
+
conversationToken: token,
|
|
189
|
+
lastEventId
|
|
190
|
+
})
|
|
191
|
+
);
|
|
192
|
+
} catch {
|
|
193
|
+
handleSocketDown();
|
|
194
|
+
}
|
|
195
|
+
};
|
|
196
|
+
socket.onmessage = (event) => {
|
|
197
|
+
let frame;
|
|
198
|
+
try {
|
|
199
|
+
frame = JSON.parse(String(event.data));
|
|
200
|
+
} catch {
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
switch (frame.type) {
|
|
204
|
+
case "ready": {
|
|
205
|
+
wsLive = true;
|
|
206
|
+
wsFailures = 0;
|
|
207
|
+
stopPolling();
|
|
208
|
+
if (pingTimer === void 0) {
|
|
209
|
+
pingTimer = setInterval(() => {
|
|
210
|
+
try {
|
|
211
|
+
socket.send(JSON.stringify({ type: "ping" }));
|
|
212
|
+
} catch {
|
|
213
|
+
}
|
|
214
|
+
}, 3e4);
|
|
215
|
+
}
|
|
216
|
+
break;
|
|
217
|
+
}
|
|
218
|
+
case "message": {
|
|
219
|
+
const m = frame.message;
|
|
220
|
+
if (m && typeof m.id === "string") {
|
|
221
|
+
appendMessages([
|
|
222
|
+
{
|
|
223
|
+
...m,
|
|
224
|
+
eventId: typeof frame.eventId === "number" ? frame.eventId : m.eventId
|
|
225
|
+
}
|
|
226
|
+
]);
|
|
227
|
+
}
|
|
228
|
+
break;
|
|
229
|
+
}
|
|
230
|
+
case "status": {
|
|
231
|
+
if (frame.status === "CLOSED") markClosed();
|
|
232
|
+
break;
|
|
233
|
+
}
|
|
234
|
+
case "error": {
|
|
235
|
+
wsGivenUp = true;
|
|
236
|
+
teardownRealtime();
|
|
237
|
+
if (!closed && snapshot.status !== "CLOSED") startPolling();
|
|
238
|
+
break;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
};
|
|
242
|
+
socket.onclose = () => handleSocketDown();
|
|
243
|
+
socket.onerror = () => {
|
|
244
|
+
};
|
|
245
|
+
}
|
|
104
246
|
async function connect(resumeToken) {
|
|
105
247
|
if (!publishableKey) {
|
|
106
248
|
emit({ status: "CLOSED" });
|
|
@@ -118,6 +260,7 @@ async function createLiveChatSession(wire, opts) {
|
|
|
118
260
|
offlineMessage: cfg.offlineMessage,
|
|
119
261
|
online: cfg.online !== false
|
|
120
262
|
});
|
|
263
|
+
if (cfg.realtimeUrl !== void 0) realtimeUrl = cfg.realtimeUrl;
|
|
121
264
|
} catch (error) {
|
|
122
265
|
if (closed) return;
|
|
123
266
|
emit({ status: "ERROR", error });
|
|
@@ -142,7 +285,10 @@ async function createLiveChatSession(wire, opts) {
|
|
|
142
285
|
if (started.online !== void 0) emit({ online: started.online });
|
|
143
286
|
emit({ status: "OPEN" });
|
|
144
287
|
await tick();
|
|
145
|
-
if (!closed)
|
|
288
|
+
if (!closed) {
|
|
289
|
+
startPolling();
|
|
290
|
+
startRealtime();
|
|
291
|
+
}
|
|
146
292
|
} catch (error) {
|
|
147
293
|
if (closed) return;
|
|
148
294
|
emit({ status: "ERROR", error });
|
|
@@ -169,7 +315,11 @@ async function createLiveChatSession(wire, opts) {
|
|
|
169
315
|
}
|
|
170
316
|
emit({ sending: true });
|
|
171
317
|
try {
|
|
172
|
-
const
|
|
318
|
+
const clientId = typeof crypto !== "undefined" && "randomUUID" in crypto ? crypto.randomUUID() : `${Date.now().toString(36)}${Math.random().toString(36).slice(2, 12)}`;
|
|
319
|
+
const message = await wire.sendMessage(token, {
|
|
320
|
+
body: trimmed,
|
|
321
|
+
clientId
|
|
322
|
+
});
|
|
173
323
|
appendMessages([message]);
|
|
174
324
|
return message;
|
|
175
325
|
} finally {
|
|
@@ -180,13 +330,18 @@ async function createLiveChatSession(wire, opts) {
|
|
|
180
330
|
if (closed) return;
|
|
181
331
|
closed = true;
|
|
182
332
|
stopPolling();
|
|
333
|
+
teardownRealtime();
|
|
183
334
|
aborter.abort();
|
|
184
335
|
listeners.clear();
|
|
185
336
|
},
|
|
186
337
|
async reset() {
|
|
338
|
+
teardownRealtime();
|
|
339
|
+
wsFailures = 0;
|
|
340
|
+
wsGivenUp = false;
|
|
187
341
|
if (publishableKey) writeStoredToken(publishableKey, null);
|
|
188
342
|
token = void 0;
|
|
189
343
|
cursor = void 0;
|
|
344
|
+
lastEventId = 0;
|
|
190
345
|
seen.clear();
|
|
191
346
|
stopPolling();
|
|
192
347
|
emit({ messages: [], status: "CONNECTING", error: null });
|
|
@@ -462,7 +617,7 @@ function createBrandfineClient(config) {
|
|
|
462
617
|
history: (conversationToken, o = {}) => liveChatWireRequest(
|
|
463
618
|
void 0,
|
|
464
619
|
"GET",
|
|
465
|
-
`/external/live-chat/conversations/${encodeURIComponent(conversationToken)}/messages${o.after ? `?after=${encodeURIComponent(o.after)}` : ""}`
|
|
620
|
+
`/external/live-chat/conversations/${encodeURIComponent(conversationToken)}/messages${o.afterEvent !== void 0 ? `?afterEvent=${encodeURIComponent(String(o.afterEvent))}` : o.after ? `?after=${encodeURIComponent(o.after)}` : ""}`
|
|
466
621
|
)
|
|
467
622
|
};
|
|
468
623
|
return createLiveChatSession(wire, opts);
|
|
@@ -486,10 +641,11 @@ function createBrandfineClient(config) {
|
|
|
486
641
|
);
|
|
487
642
|
},
|
|
488
643
|
history(conversationToken, o = {}) {
|
|
644
|
+
const qs = o.afterEvent !== void 0 ? `?afterEvent=${encodeURIComponent(String(o.afterEvent))}` : o.after ? `?after=${encodeURIComponent(o.after)}` : "";
|
|
489
645
|
return liveChatWireRequest(
|
|
490
646
|
void 0,
|
|
491
647
|
"GET",
|
|
492
|
-
`/external/live-chat/conversations/${encodeURIComponent(conversationToken)}/messages${
|
|
648
|
+
`/external/live-chat/conversations/${encodeURIComponent(conversationToken)}/messages${qs}`
|
|
493
649
|
);
|
|
494
650
|
},
|
|
495
651
|
async identityToken(externalId, opts = {}) {
|
|
@@ -571,6 +727,35 @@ function createBrandfineClient(config) {
|
|
|
571
727
|
"/external/appointments/requests",
|
|
572
728
|
input
|
|
573
729
|
);
|
|
730
|
+
},
|
|
731
|
+
async list(identity) {
|
|
732
|
+
const res = await post(
|
|
733
|
+
"/external/appointments/requests/lookup",
|
|
734
|
+
identity
|
|
735
|
+
);
|
|
736
|
+
return res.appointments;
|
|
737
|
+
},
|
|
738
|
+
async get(id, identity) {
|
|
739
|
+
const res = await post(
|
|
740
|
+
"/external/appointments/requests/lookup",
|
|
741
|
+
{ ...identity, id }
|
|
742
|
+
);
|
|
743
|
+
const row = res.appointments[0];
|
|
744
|
+
if (!row) {
|
|
745
|
+
throw new BrandfineApiError({
|
|
746
|
+
status: 404,
|
|
747
|
+
statusText: "Not Found",
|
|
748
|
+
body: "Appointment not found.",
|
|
749
|
+
url: "/external/appointments/requests/lookup"
|
|
750
|
+
});
|
|
751
|
+
}
|
|
752
|
+
return row;
|
|
753
|
+
},
|
|
754
|
+
cancel(cancellationToken) {
|
|
755
|
+
return post(
|
|
756
|
+
`/external/appointments/requests/${encodeURIComponent(cancellationToken)}/cancel`,
|
|
757
|
+
{}
|
|
758
|
+
);
|
|
574
759
|
}
|
|
575
760
|
};
|
|
576
761
|
return {
|