@smooai/chat-widget 0.5.2 → 0.7.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/chat-widget.global.js +1633 -71
- package/dist/chat-widget.global.js.map +1 -1
- package/dist/index.d.ts +381 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1478 -72
- package/dist/index.js.map +1 -1
- package/package.json +3 -2
- package/src/config.ts +31 -1
- package/src/conversation.ts +531 -18
- package/src/element.ts +455 -57
- package/src/fingerprint.ts +122 -0
- package/src/index.ts +14 -0
- package/src/markdown.ts +368 -0
- package/src/persistence.ts +271 -0
- package/src/styles.ts +110 -0
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Citation } from "@smooai/smooth-operator";
|
|
2
|
+
import { StoreApi } from "zustand/vanilla";
|
|
2
3
|
|
|
3
4
|
//#region src/config.d.ts
|
|
4
5
|
/**
|
|
@@ -68,6 +69,17 @@ interface ChatWidgetConfig {
|
|
|
68
69
|
userEmail?: string;
|
|
69
70
|
/** Optional phone number for the user participant (passed via session metadata). */
|
|
70
71
|
userPhone?: string;
|
|
72
|
+
/**
|
|
73
|
+
* Optional pre-auth HMAC context. When the host page has a shared secret with
|
|
74
|
+
* the agent, it can sign `{ userId, signature, timestamp }` so the chat-ws
|
|
75
|
+
* wrapper's `/internal/*` identity routes (and the WS create path) verify the
|
|
76
|
+
* caller without an OTP round-trip (ADR-046/ADR-048). Passed through verbatim.
|
|
77
|
+
*/
|
|
78
|
+
authContext?: {
|
|
79
|
+
userId: string;
|
|
80
|
+
signature: string;
|
|
81
|
+
timestamp: number;
|
|
82
|
+
};
|
|
71
83
|
/** Placeholder text for the message input. */
|
|
72
84
|
placeholder?: string;
|
|
73
85
|
/** Greeting rendered when the conversation opens (before any messages). */
|
|
@@ -87,6 +99,24 @@ interface ChatWidgetConfig {
|
|
|
87
99
|
requireEmail?: boolean;
|
|
88
100
|
/** Require the visitor's phone before chatting. */
|
|
89
101
|
requirePhone?: boolean;
|
|
102
|
+
/**
|
|
103
|
+
* Show the phone field on the pre-chat form (optional unless {@link requirePhone}).
|
|
104
|
+
* Defaults to `true` for this widget — phone rides the session metadata as
|
|
105
|
+
* `userPhone` so the agent can follow up by SMS. Set `false` to hide it.
|
|
106
|
+
*/
|
|
107
|
+
collectPhone?: boolean;
|
|
108
|
+
/**
|
|
109
|
+
* Show the email + SMS marketing-consent checkboxes on the pre-chat form.
|
|
110
|
+
* Explicit opt-in, default UNCHECKED; a `consentAt` timestamp is stamped when
|
|
111
|
+
* a box is ticked. Defaults to `true`. The consent record is threaded into the
|
|
112
|
+
* session metadata (ADR-048).
|
|
113
|
+
*/
|
|
114
|
+
collectConsent?: boolean;
|
|
115
|
+
/**
|
|
116
|
+
* Offer the cross-device "Restore my chats" affordance — an explicit link that
|
|
117
|
+
* runs the identity-OTP → resolve → replay flow. Defaults to `true`.
|
|
118
|
+
*/
|
|
119
|
+
allowChatRestore?: boolean;
|
|
90
120
|
/**
|
|
91
121
|
* Let visitors chat without providing any identity. When `true`, the
|
|
92
122
|
* `require*` flags are ignored and the pre-chat form is skipped.
|
|
@@ -113,9 +143,17 @@ declare class SmoothAgentChatElement extends HTMLElement {
|
|
|
113
143
|
private hasSent;
|
|
114
144
|
/** Starter prompts shown as chips in the empty state. */
|
|
115
145
|
private examplePrompts;
|
|
146
|
+
/** Resolved greeting text, cached so async (rAF) renders can reuse it. */
|
|
147
|
+
private greeting;
|
|
116
148
|
/** Current mid-turn interrupt (OTP / tool-confirmation), or null. */
|
|
117
149
|
private interrupt;
|
|
118
150
|
private interruptEl;
|
|
151
|
+
/** Cross-device "restore my chats" flow state (ADR-048 §c). */
|
|
152
|
+
private identityRestore;
|
|
153
|
+
/** Whether the cross-device restore affordance is offered (config). */
|
|
154
|
+
private allowChatRestore;
|
|
155
|
+
/** True while the pre-chat identity gate is showing (blocks premature connect). */
|
|
156
|
+
private gating;
|
|
119
157
|
private panelEl;
|
|
120
158
|
private launcherEl;
|
|
121
159
|
private messagesEl;
|
|
@@ -123,6 +161,15 @@ declare class SmoothAgentChatElement extends HTMLElement {
|
|
|
123
161
|
private dotEl;
|
|
124
162
|
private inputEl;
|
|
125
163
|
private sendBtn;
|
|
164
|
+
/** The live streaming assistant bubble whose textContent the rAF loop drives. */
|
|
165
|
+
private streamBubbleEl;
|
|
166
|
+
/** Message id the reveal is bound to (guards against stale frames after rebuilds). */
|
|
167
|
+
private streamMsgId;
|
|
168
|
+
/** Full buffered target text for the streaming message (grows as tokens arrive). */
|
|
169
|
+
private streamTarget;
|
|
170
|
+
/** How many chars of {@link streamTarget} are currently shown. */
|
|
171
|
+
private displayedLength;
|
|
172
|
+
private rafId;
|
|
126
173
|
constructor();
|
|
127
174
|
connectedCallback(): void;
|
|
128
175
|
disconnectedCallback(): void;
|
|
@@ -144,14 +191,63 @@ declare class SmoothAgentChatElement extends HTMLElement {
|
|
|
144
191
|
* set via `textContent` (never innerHTML); only static icons use innerHTML.
|
|
145
192
|
*/
|
|
146
193
|
private renderInterrupt;
|
|
147
|
-
/**
|
|
194
|
+
/**
|
|
195
|
+
* Build the cross-device "Restore my chats" card (ADR-048 §c). Reuses the
|
|
196
|
+
* same overlay slot + visual language as the OTP interrupt. All server-supplied
|
|
197
|
+
* strings (masked destination, conversation previews) are set via `textContent`
|
|
198
|
+
* — never innerHTML — so they can't inject markup; only the static lock icon
|
|
199
|
+
* uses innerHTML.
|
|
200
|
+
*/
|
|
201
|
+
private buildRestoreCard;
|
|
202
|
+
/** Format an ISO timestamp as a short, locale-aware label (best-effort). */
|
|
203
|
+
private formatWhen;
|
|
204
|
+
/** Collect identity + consent from the pre-chat form, then drop into the chat view. */
|
|
148
205
|
private handlePrechatSubmit;
|
|
149
206
|
/** Send a starter prompt (from a chip click). */
|
|
150
207
|
private submitPrompt;
|
|
151
208
|
private syncOpenState;
|
|
152
209
|
/** Grow the textarea with its content, up to the CSS max-height. */
|
|
153
210
|
private autosize;
|
|
211
|
+
/**
|
|
212
|
+
* Receive a new message snapshot from the controller and update the view.
|
|
213
|
+
*
|
|
214
|
+
* `onMessages` fires on *every* `stream_token` delta. Rebuilding the whole
|
|
215
|
+
* list each frame is wasteful and fights the smooth reveal, so we take the
|
|
216
|
+
* cheap path when the only change is the trailing streaming assistant message
|
|
217
|
+
* growing: just bump the reveal *target* (the rAF loop drains it). Any
|
|
218
|
+
* structural change (new message, finalize, citations) triggers a full
|
|
219
|
+
* rebuild via {@link renderMessages}.
|
|
220
|
+
*/
|
|
221
|
+
private handleMessages;
|
|
154
222
|
private renderMessages;
|
|
223
|
+
/** True when the host/user prefers reduced motion (snap, no typewriter). */
|
|
224
|
+
private prefersReducedMotion;
|
|
225
|
+
/**
|
|
226
|
+
* Bind the rAF typewriter to a freshly built streaming bubble. Preserves the
|
|
227
|
+
* already-revealed prefix across rebuilds (so a structural rebuild mid-stream
|
|
228
|
+
* doesn't restart the reveal from zero), then resumes the loop.
|
|
229
|
+
*/
|
|
230
|
+
private bindReveal;
|
|
231
|
+
/** Start the rAF loop if it isn't already running. */
|
|
232
|
+
private ensureRevealLoop;
|
|
233
|
+
/**
|
|
234
|
+
* One animation frame of the reveal. Advances `displayedLength` toward the
|
|
235
|
+
* buffered target at an ADAPTIVE rate — the deeper the backlog, the more
|
|
236
|
+
* chars per frame — so the reveal stays smooth yet never falls behind the
|
|
237
|
+
* network. Updates ONLY the bound bubble's textContent (no list rebuild).
|
|
238
|
+
*/
|
|
239
|
+
private tickReveal;
|
|
240
|
+
/** Snap the bound bubble to the full buffered text immediately (reduced-motion / no-rAF). */
|
|
241
|
+
private snapReveal;
|
|
242
|
+
/** Cancel any in-flight reveal loop and clear its state (called on full rebuild). */
|
|
243
|
+
private resetReveal;
|
|
244
|
+
/**
|
|
245
|
+
* Auto-scroll the message list to the bottom — but don't fight a visitor who
|
|
246
|
+
* has scrolled up to read history. When `force` (a structural rebuild) we
|
|
247
|
+
* always pin to bottom; during the streaming reveal we only follow if the
|
|
248
|
+
* viewport is already near the bottom.
|
|
249
|
+
*/
|
|
250
|
+
private scrollToBottom;
|
|
155
251
|
/** Wrap a bubble in a `.row`, prefixing assistant rows with a mini avatar. */
|
|
156
252
|
private buildRow;
|
|
157
253
|
private greetingBubble;
|
|
@@ -229,7 +325,86 @@ declare function mountFullPageChat(config: Omit<ChatWidgetConfig, 'mode'>, targe
|
|
|
229
325
|
*/
|
|
230
326
|
declare function initChatWidgetLoader(): void;
|
|
231
327
|
//#endregion
|
|
328
|
+
//#region src/persistence.d.ts
|
|
329
|
+
/** Current persisted-shape version. Bump when the shape below changes incompatibly. */
|
|
330
|
+
declare const PERSIST_VERSION: 1;
|
|
331
|
+
/** Marketing-consent record captured at the pre-chat form. */
|
|
332
|
+
interface ConsentState {
|
|
333
|
+
emailOptIn: boolean;
|
|
334
|
+
smsOptIn: boolean;
|
|
335
|
+
/** Where the consent was captured. The widget always stamps `chat-widget-prechat`. */
|
|
336
|
+
consentSource?: string;
|
|
337
|
+
/** ISO 8601 timestamp stamped when the visitor ticked a consent box. */
|
|
338
|
+
consentAt?: string;
|
|
339
|
+
}
|
|
340
|
+
/** Visitor identity collected from config or the pre-chat form. */
|
|
341
|
+
interface IdentityState {
|
|
342
|
+
name?: string;
|
|
343
|
+
email?: string;
|
|
344
|
+
phone?: string;
|
|
345
|
+
}
|
|
346
|
+
/**
|
|
347
|
+
* The exact persisted shape (ADR-048 §d). Only the pointer + identity + consent
|
|
348
|
+
* persist — never the transcript.
|
|
349
|
+
*/
|
|
350
|
+
interface PersistedWidgetState {
|
|
351
|
+
version: typeof PERSIST_VERSION;
|
|
352
|
+
/** Pointer to the active conversation session; cleared on ended/404. */
|
|
353
|
+
sessionId: string | null;
|
|
354
|
+
identity: IdentityState;
|
|
355
|
+
consent: ConsentState;
|
|
356
|
+
/**
|
|
357
|
+
* Email proven via OTP for the CURRENT session. Session-scoped: cleared on
|
|
358
|
+
* `clearSession()` so it can't leak across visitors on a shared browser, and
|
|
359
|
+
* only threaded into session metadata when {@link verifiedEmailSessionId}
|
|
360
|
+
* matches the live session (i.e. on resume of the verified session) — never
|
|
361
|
+
* auto-stamped onto a brand-new session.
|
|
362
|
+
*/
|
|
363
|
+
verifiedEmail: string | null;
|
|
364
|
+
/** The sessionId the {@link verifiedEmail} was proven against (binds the proof to one session). */
|
|
365
|
+
verifiedEmailSessionId: string | null;
|
|
366
|
+
/** Stable per-browser correlation token (see fingerprint.ts). */
|
|
367
|
+
browserFingerprint: string | null;
|
|
368
|
+
}
|
|
369
|
+
/** Store actions layered on top of the persisted state. */
|
|
370
|
+
interface WidgetStoreActions {
|
|
371
|
+
setSessionId: (sessionId: string | null) => void;
|
|
372
|
+
/** Merge identity fields (undefined values don't clobber existing ones). */
|
|
373
|
+
mergeIdentity: (identity: IdentityState) => void;
|
|
374
|
+
/** Replace consent wholesale (the pre-chat form computes the full record). */
|
|
375
|
+
setConsent: (consent: ConsentState) => void;
|
|
376
|
+
/** Record an OTP-proven email bound to a specific session. */
|
|
377
|
+
setVerifiedEmail: (email: string | null, sessionId?: string | null) => void;
|
|
378
|
+
setBrowserFingerprint: (fp: string) => void;
|
|
379
|
+
/**
|
|
380
|
+
* Clear the session pointer (and the session-scoped `verifiedEmail` proof) but
|
|
381
|
+
* KEEP identity / consent / fingerprint — used when a persisted session has
|
|
382
|
+
* ended or 404s so the next turn starts a fresh session for a known visitor.
|
|
383
|
+
* `verifiedEmail` is deliberately cleared here: it is a per-session OTP proof,
|
|
384
|
+
* not a durable identity, so it must NOT survive onto a new session (which on a
|
|
385
|
+
* shared browser could be a different visitor).
|
|
386
|
+
*/
|
|
387
|
+
clearSession: () => void;
|
|
388
|
+
}
|
|
389
|
+
type WidgetStore = PersistedWidgetState & WidgetStoreActions;
|
|
390
|
+
/** localStorage key for an agent's persisted widget state. */
|
|
391
|
+
declare function storageKey(agentId: string): string;
|
|
392
|
+
/**
|
|
393
|
+
* Create the per-agent persisted Zustand store. The `partialize` step ensures
|
|
394
|
+
* only the persisted shape (never any future transient action/UI state) is
|
|
395
|
+
* written to localStorage.
|
|
396
|
+
*/
|
|
397
|
+
declare function createWidgetStore(agentId: string): StoreApi<WidgetStore>;
|
|
398
|
+
//#endregion
|
|
232
399
|
//#region src/conversation.d.ts
|
|
400
|
+
/**
|
|
401
|
+
* Derive the HTTP base for the chat-ws wrapper's `/internal/*` REST routes from
|
|
402
|
+
* the WS endpoint: `wss://ai.smoo.ai/ws` → `https://ai.smoo.ai`. The engine
|
|
403
|
+
* (smooth-operator 1.8.0) owns the `/ws` dispatch and REJECTS unknown verbs, so
|
|
404
|
+
* the cross-device identity flow + fingerprint resume are HTTP POST routes on the
|
|
405
|
+
* wrapper (origin-allowlisted + authContext, per ADR-046/ADR-048) — NOT WS frames.
|
|
406
|
+
*/
|
|
407
|
+
declare function httpBaseFromWsEndpoint(endpoint: string): string | null;
|
|
233
408
|
type Role = 'user' | 'assistant';
|
|
234
409
|
interface ChatMessage {
|
|
235
410
|
id: string;
|
|
@@ -275,7 +450,56 @@ interface UserInfo {
|
|
|
275
450
|
name?: string;
|
|
276
451
|
email?: string;
|
|
277
452
|
phone?: string;
|
|
453
|
+
/** Marketing-consent opt-ins captured at the pre-chat form (ADR-048). */
|
|
454
|
+
consent?: {
|
|
455
|
+
emailOptIn: boolean;
|
|
456
|
+
smsOptIn: boolean;
|
|
457
|
+
};
|
|
458
|
+
}
|
|
459
|
+
/** One conversation surfaced by `resolve_identity` for the cross-device picker. */
|
|
460
|
+
interface RestorableConversation {
|
|
461
|
+
conversationId: string;
|
|
462
|
+
sessionId: string;
|
|
463
|
+
lastActivityAt?: string;
|
|
464
|
+
preview?: string;
|
|
278
465
|
}
|
|
466
|
+
/**
|
|
467
|
+
* State machine for the cross-device "restore my chats" flow. Driven by the three
|
|
468
|
+
* HTTP POST routes on the chat-ws wrapper — `/internal/identity/request-otp` →
|
|
469
|
+
* `/internal/identity/verify-otp` → `/internal/identity/resolve` (ADR-048 §c).
|
|
470
|
+
* The view renders a panel off this.
|
|
471
|
+
*/
|
|
472
|
+
type IdentityRestore = {
|
|
473
|
+
phase: 'idle';
|
|
474
|
+
} /** UI-local: the email-entry step before any request is sent. */ | {
|
|
475
|
+
phase: 'awaiting_email';
|
|
476
|
+
error?: string;
|
|
477
|
+
} | {
|
|
478
|
+
phase: 'requesting';
|
|
479
|
+
email: string;
|
|
480
|
+
channel: 'email' | 'sms';
|
|
481
|
+
} | {
|
|
482
|
+
phase: 'awaiting_code';
|
|
483
|
+
email: string;
|
|
484
|
+
channel: 'email' | 'sms';
|
|
485
|
+
maskedDestination?: string;
|
|
486
|
+
error?: string;
|
|
487
|
+
attemptsRemaining?: number;
|
|
488
|
+
} | {
|
|
489
|
+
phase: 'verifying';
|
|
490
|
+
email: string;
|
|
491
|
+
channel: 'email' | 'sms';
|
|
492
|
+
} | {
|
|
493
|
+
phase: 'resolving';
|
|
494
|
+
email: string;
|
|
495
|
+
} | {
|
|
496
|
+
phase: 'resolved';
|
|
497
|
+
email: string;
|
|
498
|
+
conversations: RestorableConversation[];
|
|
499
|
+
} | {
|
|
500
|
+
phase: 'error';
|
|
501
|
+
message: string;
|
|
502
|
+
};
|
|
279
503
|
interface ConversationEvents {
|
|
280
504
|
/** Fired whenever the message list changes (append, token delta, finalize). */
|
|
281
505
|
onMessages: (messages: ChatMessage[]) => void;
|
|
@@ -283,25 +507,51 @@ interface ConversationEvents {
|
|
|
283
507
|
onStatus: (status: ConnectionStatus, detail?: string) => void;
|
|
284
508
|
/** Fired when a turn pauses for OTP / tool-confirmation, and `null` when it clears. */
|
|
285
509
|
onInterrupt?: (interrupt: Interrupt | null) => void;
|
|
510
|
+
/** Fired on cross-device identity-restore state transitions. */
|
|
511
|
+
onIdentityRestore?: (state: IdentityRestore) => void;
|
|
286
512
|
}
|
|
287
513
|
declare class ConversationController {
|
|
288
514
|
private readonly config;
|
|
289
515
|
private readonly events;
|
|
516
|
+
private readonly store;
|
|
290
517
|
private client;
|
|
291
518
|
private sessionId;
|
|
292
519
|
private readonly messages;
|
|
293
520
|
private status;
|
|
294
521
|
private seq;
|
|
295
|
-
/** Visitor identity, seeded from config and updated by the pre-chat form. */
|
|
296
|
-
private identity;
|
|
297
522
|
/** requestId of the in-flight turn — used to resume OTP / tool confirmations. */
|
|
298
523
|
private activeRequestId;
|
|
299
524
|
private interrupt;
|
|
300
|
-
|
|
525
|
+
private identityRestore;
|
|
526
|
+
/**
|
|
527
|
+
* True once the resume probe (persisted-pointer get_session OR the
|
|
528
|
+
* `/internal/resume-by-fingerprint` POST) has run for this controller. Makes
|
|
529
|
+
* `connect()` idempotent: re-entering after a transient `error` status (e.g. a
|
|
530
|
+
* retried `send()`) creates a fresh session rather than re-running the resume
|
|
531
|
+
* probe — which would fire another `resumeByFingerprint` POST and could adopt a
|
|
532
|
+
* session we already decided not to resume.
|
|
533
|
+
*/
|
|
534
|
+
private resumeAttempted;
|
|
535
|
+
/**
|
|
536
|
+
* HTTP base for the chat-ws wrapper's `/internal/*` REST routes. `null` when
|
|
537
|
+
* the configured WS endpoint could not be parsed into an absolute origin — in
|
|
538
|
+
* that case the `/internal/*` routes are refused (rather than mis-targeted at
|
|
539
|
+
* the host page origin). See {@link httpBaseFromWsEndpoint}.
|
|
540
|
+
*/
|
|
541
|
+
private readonly httpBase;
|
|
542
|
+
constructor(config: ChatWidgetConfig, events: ConversationEvents, store?: StoreApi<WidgetStore>);
|
|
301
543
|
get connectionStatus(): ConnectionStatus;
|
|
302
|
-
/**
|
|
544
|
+
/** The persisted store, exposed so the view can read identity for the pre-chat gate. */
|
|
545
|
+
getStore(): StoreApi<WidgetStore>;
|
|
546
|
+
/** True when a persisted session pointer exists (drives the resume path). */
|
|
547
|
+
hasPersistedSession(): boolean;
|
|
548
|
+
/** True when persisted identity exists (lets the view skip the pre-chat form). */
|
|
549
|
+
hasPersistedIdentity(): boolean;
|
|
550
|
+
/** Merge in visitor identity + consent (from the pre-chat form). Applied on next connect. */
|
|
303
551
|
setUserInfo(info: UserInfo): void;
|
|
304
552
|
private setInterrupt;
|
|
553
|
+
private setIdentityRestore;
|
|
554
|
+
get currentIdentityRestore(): IdentityRestore;
|
|
305
555
|
/** Submit an OTP code to resume the paused turn. No-op if not awaiting OTP. */
|
|
306
556
|
verifyOtp(code: string): void;
|
|
307
557
|
/** Approve or reject a pending tool write to resume the paused turn. */
|
|
@@ -309,8 +559,68 @@ declare class ConversationController {
|
|
|
309
559
|
private nextId;
|
|
310
560
|
private setStatus;
|
|
311
561
|
private emitMessages;
|
|
312
|
-
/**
|
|
562
|
+
/** Compute (once) + return the persisted browser fingerprint. */
|
|
563
|
+
private fingerprint;
|
|
564
|
+
/**
|
|
565
|
+
* Build the `metadata` payload threaded into `create_conversation_session`:
|
|
566
|
+
* phone (no first-class engine field) and consent.
|
|
567
|
+
*
|
|
568
|
+
* NOTE: `verifiedEmail` is deliberately NOT stamped here. It is a per-session
|
|
569
|
+
* OTP proof bound to the session it was verified against
|
|
570
|
+
* (`verifiedEmailSessionId`), and the server only treats an actual OTP `verify`
|
|
571
|
+
* as proof — metadata `verifiedEmail` is just a hint. Auto-stamping it onto
|
|
572
|
+
* every brand-new `create_conversation_session` would mislabel a fresh
|
|
573
|
+
* visitor's session with a prior (possibly different) visitor's email on a
|
|
574
|
+
* shared browser. The verified email is only used when RESUMING the exact
|
|
575
|
+
* session it was proven for — see {@link verifiedEmailForSession}.
|
|
576
|
+
*/
|
|
577
|
+
private sessionMetadata;
|
|
578
|
+
/**
|
|
579
|
+
* The verified-email hint, but ONLY when the OTP proof is bound to the session
|
|
580
|
+
* being resumed (`verifiedEmailSessionId === sessionId`). Returns null
|
|
581
|
+
* otherwise so a stale/cross-visitor proof is never threaded onto a session it
|
|
582
|
+
* wasn't proven for.
|
|
583
|
+
*/
|
|
584
|
+
private verifiedEmailForSession;
|
|
585
|
+
/** Lazily open the WS client (default transport). Idempotent within a connect. */
|
|
586
|
+
private ensureClient;
|
|
587
|
+
/**
|
|
588
|
+
* Open the connection and either RESUME or create a session.
|
|
589
|
+
*
|
|
590
|
+
* 1. Persisted pointer (ADR-048 §b): `get_session` → if not `ended`, reuse +
|
|
591
|
+
* hydrate from `get_messages` (newest-first, reversed). On ended/404 clear
|
|
592
|
+
* ONLY the pointer (identity/consent survive).
|
|
593
|
+
* 2. No persisted pointer: POST `/internal/resume-by-fingerprint` FIRST; if
|
|
594
|
+
* `resumable`, adopt the returned session (the wrapper has primed the
|
|
595
|
+
* operator registry), reuse the sessionId, and hydrate via get_session/
|
|
596
|
+
* get_messages — rather than relying on createConversationSession to resume.
|
|
597
|
+
* 3. Otherwise create a fresh session.
|
|
598
|
+
*/
|
|
313
599
|
connect(): Promise<void>;
|
|
600
|
+
/**
|
|
601
|
+
* Build the auth fields every `/internal/*` route shares: `agentId` (required
|
|
602
|
+
* for the agent-policy lookup), `agentName` (used as the OTP email sender), and
|
|
603
|
+
* the optional pre-auth `authContext` the host page may have configured. The
|
|
604
|
+
* `Origin` header is sent automatically by the browser and checked server-side.
|
|
605
|
+
*/
|
|
606
|
+
private authBody;
|
|
607
|
+
/** POST JSON to a `/internal/*` route; returns the parsed body (or throws). */
|
|
608
|
+
private postInternal;
|
|
609
|
+
/**
|
|
610
|
+
* POST `/internal/resume-by-fingerprint`. Returns the resumable sessionId when
|
|
611
|
+
* the wrapper found (and primed) a recent session for this fingerprint, else
|
|
612
|
+
* null. Network/route failures are swallowed → null (fall through to create).
|
|
613
|
+
*/
|
|
614
|
+
private resumeByFingerprint;
|
|
615
|
+
/** `create_conversation_session` with fingerprint + identity + consent metadata. */
|
|
616
|
+
private createSession;
|
|
617
|
+
/**
|
|
618
|
+
* Attempt to resume `sessionId`: returns true and hydrates the transcript when
|
|
619
|
+
* the session is live, false when it has ended / can't be fetched.
|
|
620
|
+
*/
|
|
621
|
+
private tryResume;
|
|
622
|
+
/** Page recent history (newest-first), reverse to chronological, and render. */
|
|
623
|
+
private hydrateHistory;
|
|
314
624
|
/**
|
|
315
625
|
* Submit a user message. Appends the user bubble immediately, then streams the
|
|
316
626
|
* assistant reply token-by-token, finalizing on `eventual_response`.
|
|
@@ -318,9 +628,73 @@ declare class ConversationController {
|
|
|
318
628
|
send(text: string): Promise<void>;
|
|
319
629
|
/** Map a non-token turn event (OTP / tool-confirmation lifecycle) to interrupt state. */
|
|
320
630
|
private handleTurnEvent;
|
|
631
|
+
/**
|
|
632
|
+
* Begin the cross-device restore: POST `/internal/identity/request-otp` for
|
|
633
|
+
* `email` over `channel`. The view collects the email via an explicit affordance.
|
|
634
|
+
*/
|
|
635
|
+
requestIdentityOtp(email: string, channel?: 'email' | 'sms'): Promise<void>;
|
|
636
|
+
/** Submit the code: POST `/internal/identity/verify-otp`, then resolve on success. */
|
|
637
|
+
verifyIdentityOtp(code: string): Promise<void>;
|
|
638
|
+
/** Resolve the verified identity → restorable conversations via POST `/internal/identity/resolve`. */
|
|
639
|
+
private resolveIdentity;
|
|
640
|
+
/**
|
|
641
|
+
* Replay a chosen restorable conversation: point the live session at its
|
|
642
|
+
* sessionId, hydrate its transcript (get_session + get_messages), and persist
|
|
643
|
+
* the new pointer so the next `sendMessage` continues it.
|
|
644
|
+
*/
|
|
645
|
+
restoreConversation(sessionId: string): Promise<void>;
|
|
646
|
+
/** Dismiss the cross-device restore panel. */
|
|
647
|
+
cancelIdentityRestore(): void;
|
|
321
648
|
/** Tear down the underlying client. */
|
|
322
649
|
disconnect(): void;
|
|
323
650
|
}
|
|
324
651
|
//#endregion
|
|
325
|
-
|
|
652
|
+
//#region src/fingerprint.d.ts
|
|
653
|
+
/**
|
|
654
|
+
* Browser fingerprint — a stable, privacy-light identifier used by the
|
|
655
|
+
* smooth-operator server to correlate an anonymous visitor's sessions across
|
|
656
|
+
* page loads (and, server-side, to match an anonymous fingerprint to a known
|
|
657
|
+
* CRM contact). It rides every `create_conversation_session` as
|
|
658
|
+
* `browserFingerprint` (see ADR-048).
|
|
659
|
+
*
|
|
660
|
+
* ## Why not ThumbmarkJS
|
|
661
|
+
*
|
|
662
|
+
* The ADR floats ThumbmarkJS as the reference implementation. For an *embed*
|
|
663
|
+
* that is injected onto arbitrary host pages, ThumbmarkJS is too heavy: its
|
|
664
|
+
* full build pulls in extensive device-detection tables and async
|
|
665
|
+
* component collection, adding tens of kilobytes (and async surface) to a
|
|
666
|
+
* bundle whose whole selling point is staying out of the host page's
|
|
667
|
+
* LCP/TBT budget. The fingerprint here is a few hundred bytes.
|
|
668
|
+
*
|
|
669
|
+
* ## What this is (and the tradeoff)
|
|
670
|
+
*
|
|
671
|
+
* The correlation that actually matters — "is this the same browser as last
|
|
672
|
+
* time?" — is carried by a **persisted random UUID** (the `browserFingerprint`
|
|
673
|
+
* field in the persisted store). That UUID is generated once and reused for
|
|
674
|
+
* the life of the localStorage entry, so same-browser resume is exact and
|
|
675
|
+
* deterministic. The signal hash below is a best-effort entropy *supplement*
|
|
676
|
+
* mixed into that UUID's derivation seed on first generation — it gives the
|
|
677
|
+
* server-side resolver a soft signal for the (rare) cross-storage case where
|
|
678
|
+
* the UUID was cleared but the device is unchanged. It is intentionally NOT a
|
|
679
|
+
* high-entropy device fingerprint: no canvas/WebGL/audio probes, no font
|
|
680
|
+
* enumeration, nothing that reads as invasive tracking. The tradeoff is weaker
|
|
681
|
+
* cross-storage matching in exchange for a tiny, transparent, no-network,
|
|
682
|
+
* XSS-safe implementation. The server's resolver (SMOODEV-2129d) is the source
|
|
683
|
+
* of truth for any fuzzy matching; the client just supplies a stable token.
|
|
684
|
+
*/
|
|
685
|
+
/**
|
|
686
|
+
* Compute a fresh fingerprint token: a stable random UUID, suffixed with the
|
|
687
|
+
* FNV hash of the device signals so the server can recover a soft device
|
|
688
|
+
* signal even if it only has the token. Called ONCE per browser (the result is
|
|
689
|
+
* persisted and reused) — see {@link getOrCreateFingerprint}.
|
|
690
|
+
*/
|
|
691
|
+
declare function computeFingerprint(): string;
|
|
692
|
+
/**
|
|
693
|
+
* Return the cached fingerprint if one was already computed for this browser,
|
|
694
|
+
* otherwise compute + persist a new one via the provided accessors. The
|
|
695
|
+
* persistence layer owns storage; this function owns the "compute once" policy.
|
|
696
|
+
*/
|
|
697
|
+
declare function getOrCreateFingerprint(get: () => string | null, set: (fp: string) => void): string;
|
|
698
|
+
//#endregion
|
|
699
|
+
export { type ChatMessage, type ChatWidgetConfig, type ChatWidgetMode, type ChatWidgetTheme, type Citation, type ConnectionStatus, type ConsentState, ConversationController, type ConversationEvents, ELEMENT_TAG, type IdentityRestore, type IdentityState, PERSIST_VERSION, type PersistedWidgetState, type RestorableConversation, type Role, SmoothAgentChatElement, type UserInfo, type WidgetStore, computeFingerprint, createWidgetStore, defineChatWidget, getOrCreateFingerprint, httpBaseFromWsEndpoint, initChatWidgetLoader, mountChatWidget, mountFullPageChat, storageKey };
|
|
326
700
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/config.ts","../src/element.ts","../src/loader-core.ts","../src/conversation.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/config.ts","../src/element.ts","../src/loader-core.ts","../src/persistence.ts","../src/conversation.ts","../src/fingerprint.ts"],"mappings":"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"}
|