@usereq/widget 0.2.2 → 0.2.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/dist/widget.js +212 -212
- package/package.json +1 -1
- package/src/custom-element/agent-widget-element.tsx +41 -42
- package/src/index.ts +0 -1
- package/src/renderer/widget-runtime.tsx +0 -15
- package/src/runtime/api.ts +0 -28
- package/src/shared/widget-config.ts +16 -0
- package/src/types.ts +5 -8
- package/src/runtime/analytics.ts +0 -431
- package/src/runtime/token-renewal.ts +0 -13
- package/src/shared/analytics.ts +0 -54
package/package.json
CHANGED
|
@@ -19,7 +19,6 @@ import { widgetTriggerRuleKey } from "../shared/widget-config";
|
|
|
19
19
|
import { WidgetRuntime, type WidgetRuntimeStatus } from "../renderer/index";
|
|
20
20
|
import { buildAssistantMessageRevealSteps } from "../chat-widget";
|
|
21
21
|
import { DEFAULT_STOP_CONVERSATION_PROMPT } from "../shared/stop-confirmation";
|
|
22
|
-
import type { WidgetTriggerRuleType } from "../shared/analytics";
|
|
23
22
|
import {
|
|
24
23
|
bootstrapWidget,
|
|
25
24
|
resetWidgetSession,
|
|
@@ -27,10 +26,6 @@ import {
|
|
|
27
26
|
startWidgetConversation,
|
|
28
27
|
submitWidgetMessage,
|
|
29
28
|
} from "../runtime/bootstrap";
|
|
30
|
-
import {
|
|
31
|
-
createWidgetAnalyticsTracker,
|
|
32
|
-
type WidgetAnalyticsTracker,
|
|
33
|
-
} from "../runtime/analytics";
|
|
34
29
|
import { getWidgetShadowCss } from "../shared/shadow-css";
|
|
35
30
|
import { WIDGET_SHADOW_THEME_CSS } from "../shared/shadow-theme";
|
|
36
31
|
|
|
@@ -72,8 +67,7 @@ export class AgentWidgetElement extends HTMLElement {
|
|
|
72
67
|
private sendVersion = 0;
|
|
73
68
|
private confirmationVersion = 0;
|
|
74
69
|
private bootstrapPhase: "idle" | "booting" | "ready" | "blocked" = "idle";
|
|
75
|
-
private
|
|
76
|
-
private analyticsTracker: WidgetAnalyticsTracker | null = null;
|
|
70
|
+
private autoStartTriggered = false;
|
|
77
71
|
|
|
78
72
|
constructor() {
|
|
79
73
|
super();
|
|
@@ -109,7 +103,6 @@ export class AgentWidgetElement extends HTMLElement {
|
|
|
109
103
|
|
|
110
104
|
connectedCallback() {
|
|
111
105
|
this.syncAttributes();
|
|
112
|
-
this.recreateAnalyticsTracker();
|
|
113
106
|
this.renderWidget();
|
|
114
107
|
if (this.agentId) {
|
|
115
108
|
void this.ensureBootstrap();
|
|
@@ -118,9 +111,6 @@ export class AgentWidgetElement extends HTMLElement {
|
|
|
118
111
|
|
|
119
112
|
disconnectedCallback() {
|
|
120
113
|
this.bootstrapPromise = null;
|
|
121
|
-
this.analyticsTracker?.dispose();
|
|
122
|
-
this.analyticsTracker = null;
|
|
123
|
-
this.analyticsTrackerKey = "";
|
|
124
114
|
this.root.unmount();
|
|
125
115
|
}
|
|
126
116
|
|
|
@@ -130,7 +120,6 @@ export class AgentWidgetElement extends HTMLElement {
|
|
|
130
120
|
newValue?: string | null,
|
|
131
121
|
) {
|
|
132
122
|
this.syncAttributes();
|
|
133
|
-
this.recreateAnalyticsTracker();
|
|
134
123
|
if (name === "agent-id" && oldValue !== newValue) {
|
|
135
124
|
this.clearAgentState();
|
|
136
125
|
}
|
|
@@ -216,22 +205,6 @@ export class AgentWidgetElement extends HTMLElement {
|
|
|
216
205
|
this.status = "idle";
|
|
217
206
|
}
|
|
218
207
|
|
|
219
|
-
private recreateAnalyticsTracker() {
|
|
220
|
-
const nextKey = `${this.mode}:${this.agentId}`;
|
|
221
|
-
if (nextKey === this.analyticsTrackerKey) {
|
|
222
|
-
return;
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
this.analyticsTracker?.dispose();
|
|
226
|
-
this.analyticsTracker = createWidgetAnalyticsTracker({
|
|
227
|
-
mode: this.mode,
|
|
228
|
-
agentId: this.agentId,
|
|
229
|
-
getSession: () => this.sessionState,
|
|
230
|
-
getConversationId: () => this.sessionState?.conversationId,
|
|
231
|
-
});
|
|
232
|
-
this.analyticsTrackerKey = nextKey;
|
|
233
|
-
}
|
|
234
|
-
|
|
235
208
|
private renderWidget() {
|
|
236
209
|
if (
|
|
237
210
|
this.bootstrapPhase !== "ready" ||
|
|
@@ -246,14 +219,31 @@ export class AgentWidgetElement extends HTMLElement {
|
|
|
246
219
|
const placement = this.getResolvedPlacement();
|
|
247
220
|
const variant = this.getResolvedVariant();
|
|
248
221
|
const preset = this.getResolvedPreset();
|
|
249
|
-
const
|
|
222
|
+
const autoStartEnabled =
|
|
223
|
+
this.sessionConfig?.widgetConfig.widgetBehavior?.autoStart === true;
|
|
224
|
+
const awaitingConversation =
|
|
250
225
|
this.status === "booting" ||
|
|
251
|
-
(this.sessionState != null && !this.sessionState.conversationId)
|
|
226
|
+
(this.sessionState != null && !this.sessionState.conversationId);
|
|
227
|
+
// When auto-start is on we never render the manual CTA — except as a
|
|
228
|
+
// fallback if the auto-start attempt errored, so the visitor isn't
|
|
229
|
+
// stranded on a blank panel.
|
|
230
|
+
const showFallbackCta = autoStartEnabled && this.status === "error";
|
|
231
|
+
const emptyAction =
|
|
232
|
+
!autoStartEnabled && awaitingConversation
|
|
252
233
|
? {
|
|
253
234
|
label: DEFAULT_START_CONVERSATION_LABEL,
|
|
254
235
|
onClick: () => void this.handleStartConversation(),
|
|
255
236
|
disabled: this.status !== "ready",
|
|
256
237
|
}
|
|
238
|
+
: showFallbackCta
|
|
239
|
+
? {
|
|
240
|
+
label: DEFAULT_START_CONVERSATION_LABEL,
|
|
241
|
+
onClick: () => {
|
|
242
|
+
this.autoStartTriggered = false;
|
|
243
|
+
void this.handleStartConversation();
|
|
244
|
+
},
|
|
245
|
+
disabled: false,
|
|
246
|
+
}
|
|
257
247
|
: undefined;
|
|
258
248
|
|
|
259
249
|
this.root.render(
|
|
@@ -287,10 +277,6 @@ export class AgentWidgetElement extends HTMLElement {
|
|
|
287
277
|
onSendMessage: (content: string) => void this.sendMessage(content),
|
|
288
278
|
onConfirmationDecision: (confirmationId: string, accepted: boolean) =>
|
|
289
279
|
void this.handleConfirmationDecision(confirmationId, accepted),
|
|
290
|
-
onWidgetVisible: (triggerRuleType: WidgetTriggerRuleType | null) =>
|
|
291
|
-
this.analyticsTracker?.trackView(triggerRuleType),
|
|
292
|
-
onLinkClick: (linkUrl: string) =>
|
|
293
|
-
this.analyticsTracker?.trackLinkClicked(linkUrl),
|
|
294
280
|
portalContainer: this.shadowRootRef,
|
|
295
281
|
}),
|
|
296
282
|
);
|
|
@@ -337,10 +323,6 @@ export class AgentWidgetElement extends HTMLElement {
|
|
|
337
323
|
this.welcomeMessage = state.session.widgetConfig.welcomeMessage;
|
|
338
324
|
this.status = "ready";
|
|
339
325
|
this.bootstrapPhase = "ready";
|
|
340
|
-
if (this.mode === "embed") {
|
|
341
|
-
this.analyticsTracker?.trackScriptLoaded();
|
|
342
|
-
this.analyticsTracker?.trackUniqueScriptLoaded();
|
|
343
|
-
}
|
|
344
326
|
this.renderWidget();
|
|
345
327
|
} catch (error) {
|
|
346
328
|
if (runVersion !== this.bootstrapVersion) {
|
|
@@ -443,16 +425,33 @@ export class AgentWidgetElement extends HTMLElement {
|
|
|
443
425
|
}
|
|
444
426
|
|
|
445
427
|
private async handleOpenChange(nextOpen: boolean) {
|
|
446
|
-
const wasOpen = this.open;
|
|
447
428
|
this.open = nextOpen;
|
|
448
429
|
this.renderWidget();
|
|
449
|
-
if (!wasOpen && nextOpen && this.mode === "embed") {
|
|
450
|
-
this.analyticsTracker?.trackClick();
|
|
451
|
-
}
|
|
452
430
|
|
|
453
431
|
if (this.open && (this.status === "idle" || !this.sessionState)) {
|
|
454
432
|
await this.ensureBootstrap();
|
|
455
433
|
}
|
|
434
|
+
|
|
435
|
+
this.maybeAutoStartConversation();
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
/**
|
|
439
|
+
* Auto-start opt-in: when the agent has `widgetBehavior.autoStart`
|
|
440
|
+
* enabled, the first time the visitor opens the launcher we transparently
|
|
441
|
+
* call /conversations/public/start so they skip the empty state. Fires
|
|
442
|
+
* once per element lifetime; subsequent opens reuse the existing
|
|
443
|
+
* conversation via the standard sessionStorage rehydrate.
|
|
444
|
+
*/
|
|
445
|
+
private maybeAutoStartConversation() {
|
|
446
|
+
if (this.autoStartTriggered) return;
|
|
447
|
+
if (!this.open) return;
|
|
448
|
+
if (this.status !== "ready") return;
|
|
449
|
+
if (!this.sessionConfig) return;
|
|
450
|
+
if (!this.sessionConfig.widgetConfig.widgetBehavior?.autoStart) return;
|
|
451
|
+
if (this.sessionState?.conversationId) return;
|
|
452
|
+
|
|
453
|
+
this.autoStartTriggered = true;
|
|
454
|
+
void this.handleStartConversation();
|
|
456
455
|
}
|
|
457
456
|
|
|
458
457
|
private async handleStartConversation() {
|
package/src/index.ts
CHANGED
|
@@ -27,7 +27,6 @@ import {
|
|
|
27
27
|
} from "../shared/stop-confirmation";
|
|
28
28
|
import { widgetCss } from "../styles/widget.css";
|
|
29
29
|
import { useWidgetTriggerGate } from "../runtime/trigger-rule";
|
|
30
|
-
import type { WidgetTriggerRuleType } from "../shared/analytics";
|
|
31
30
|
|
|
32
31
|
export type WidgetRuntimeMode = "embed" | "preview";
|
|
33
32
|
|
|
@@ -64,8 +63,6 @@ export type WidgetRuntimeProps = {
|
|
|
64
63
|
accepted: boolean,
|
|
65
64
|
) => void | Promise<void>;
|
|
66
65
|
portalContainer?: HTMLElement | DocumentFragment | null;
|
|
67
|
-
onWidgetVisible?: (triggerRuleType: WidgetTriggerRuleType | null) => void;
|
|
68
|
-
onLinkClick?: (linkUrl: string) => void;
|
|
69
66
|
onLauncherClick?: () => void;
|
|
70
67
|
};
|
|
71
68
|
|
|
@@ -100,21 +97,10 @@ export function WidgetRuntime({
|
|
|
100
97
|
onStopConversation,
|
|
101
98
|
onConfirmationDecision,
|
|
102
99
|
portalContainer,
|
|
103
|
-
onWidgetVisible,
|
|
104
|
-
onLinkClick,
|
|
105
100
|
onLauncherClick,
|
|
106
101
|
}: WidgetRuntimeProps) {
|
|
107
102
|
const { ready } = useWidgetTriggerGate(triggerRule);
|
|
108
103
|
const { horizontal, vertical } = splitWidgetPlacement(placement);
|
|
109
|
-
const viewTrackedRef = React.useRef(false);
|
|
110
|
-
|
|
111
|
-
React.useEffect(() => {
|
|
112
|
-
if (mode !== "embed" || !ready || viewTrackedRef.current) {
|
|
113
|
-
return;
|
|
114
|
-
}
|
|
115
|
-
viewTrackedRef.current = true;
|
|
116
|
-
onWidgetVisible?.(triggerRule?.triggerRuleType ?? null);
|
|
117
|
-
}, [mode, onWidgetVisible, ready, triggerRule]);
|
|
118
104
|
|
|
119
105
|
const { panelMessages, confirmation } = React.useMemo(
|
|
120
106
|
() => splitConfirmationFromMessages(messages, onConfirmationDecision),
|
|
@@ -219,7 +205,6 @@ export function WidgetRuntime({
|
|
|
219
205
|
onStopConversation ? handleStop : undefined
|
|
220
206
|
}
|
|
221
207
|
onLauncherClick={onLauncherClick}
|
|
222
|
-
onLinkClick={onLinkClick}
|
|
223
208
|
emptyAction={wrappedEmptyAction}
|
|
224
209
|
confirmation={confirmation}
|
|
225
210
|
portalContainer={portalEl}
|
package/src/runtime/api.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import type {
|
|
2
|
-
WidgetAnalyticsBatchRequest,
|
|
3
2
|
WidgetConversation,
|
|
4
3
|
WidgetMessage,
|
|
5
4
|
WidgetPublicChatSendChatResponse,
|
|
@@ -91,33 +90,6 @@ export async function sendPublicChatConfirmation(input: {
|
|
|
91
90
|
);
|
|
92
91
|
}
|
|
93
92
|
|
|
94
|
-
export async function sendWidgetAnalyticsBatch(
|
|
95
|
-
input: WidgetAnalyticsBatchRequest,
|
|
96
|
-
): Promise<void> {
|
|
97
|
-
const response = await fetch(
|
|
98
|
-
new URL("/api/widget/events/batch", `${getWidgetApiBase()}/`).toString(),
|
|
99
|
-
{
|
|
100
|
-
method: "POST",
|
|
101
|
-
headers: input.sessionToken
|
|
102
|
-
? { Authorization: `Bearer ${input.sessionToken}` }
|
|
103
|
-
: undefined,
|
|
104
|
-
body: JSON.stringify(input),
|
|
105
|
-
credentials: "omit",
|
|
106
|
-
keepalive: true,
|
|
107
|
-
},
|
|
108
|
-
);
|
|
109
|
-
|
|
110
|
-
if (!response.ok) {
|
|
111
|
-
const text = await response.text();
|
|
112
|
-
const payload = text ? JSON.parse(text) : null;
|
|
113
|
-
throw new Error(
|
|
114
|
-
typeof payload === "object" && payload && "error" in payload
|
|
115
|
-
? String((payload as { error?: string }).error ?? response.statusText)
|
|
116
|
-
: response.statusText,
|
|
117
|
-
);
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
|
|
121
93
|
export async function getCurrentConversation(sessionToken: string): Promise<{
|
|
122
94
|
conversation: WidgetConversation | null;
|
|
123
95
|
}> {
|
|
@@ -29,6 +29,22 @@ export type WidgetTriggerRule =
|
|
|
29
29
|
| { triggerRuleType: "element_clicked"; elementClicked: string }
|
|
30
30
|
| { triggerRuleType: "element_displayed"; elementDisplayed: string };
|
|
31
31
|
|
|
32
|
+
export type WidgetBehavior = {
|
|
33
|
+
autoStart: boolean;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export const DEFAULT_WIDGET_BEHAVIOR: WidgetBehavior = {
|
|
37
|
+
autoStart: false,
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export function normalizeWidgetBehavior(
|
|
41
|
+
behavior: Partial<WidgetBehavior> | null | undefined,
|
|
42
|
+
): WidgetBehavior {
|
|
43
|
+
return {
|
|
44
|
+
autoStart: Boolean(behavior?.autoStart),
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
32
48
|
export function widgetTriggerRuleKey(
|
|
33
49
|
rule: WidgetTriggerRule | null | undefined,
|
|
34
50
|
): string {
|
package/src/types.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type {
|
|
2
2
|
WidgetAppearance,
|
|
3
|
+
WidgetBehavior,
|
|
3
4
|
WidgetPlacement,
|
|
4
5
|
WidgetPreset,
|
|
5
6
|
WidgetVariant,
|
|
@@ -8,12 +9,15 @@ import type {
|
|
|
8
9
|
|
|
9
10
|
export {
|
|
10
11
|
DEFAULT_WIDGET_APPEARANCE,
|
|
12
|
+
DEFAULT_WIDGET_BEHAVIOR,
|
|
11
13
|
DEFAULT_WIDGET_PRESET,
|
|
12
14
|
type WidgetAppearance,
|
|
15
|
+
type WidgetBehavior,
|
|
13
16
|
type WidgetPlacement,
|
|
14
17
|
type WidgetPreset,
|
|
15
18
|
type WidgetVariant,
|
|
16
19
|
normalizeWidgetAppearance,
|
|
20
|
+
normalizeWidgetBehavior,
|
|
17
21
|
normalizeWidgetPlacement,
|
|
18
22
|
normalizeWidgetPreset,
|
|
19
23
|
normalizeWidgetVariant,
|
|
@@ -21,14 +25,6 @@ export {
|
|
|
21
25
|
widgetAppearanceToAttributes,
|
|
22
26
|
type WidgetTriggerRule,
|
|
23
27
|
} from "./shared/widget-config";
|
|
24
|
-
export type {
|
|
25
|
-
WidgetAnalyticsBatchEvent,
|
|
26
|
-
WidgetAnalyticsBatchRequest,
|
|
27
|
-
WidgetAnalyticsBatchResponse,
|
|
28
|
-
WidgetAnalyticsEventType,
|
|
29
|
-
WidgetClientAnalyticsEventType,
|
|
30
|
-
WidgetTriggerRuleType,
|
|
31
|
-
} from "./shared/analytics";
|
|
32
28
|
|
|
33
29
|
export type WidgetSessionConfig = {
|
|
34
30
|
sessionToken: string;
|
|
@@ -40,6 +36,7 @@ export type WidgetSessionConfig = {
|
|
|
40
36
|
welcomeMessage: string | null;
|
|
41
37
|
spotlightMessages: string[];
|
|
42
38
|
widgetAppearance: WidgetAppearance;
|
|
39
|
+
widgetBehavior: WidgetBehavior;
|
|
43
40
|
widgetVariant: WidgetVariant;
|
|
44
41
|
widgetPreset: WidgetPreset;
|
|
45
42
|
widgetPlacement: WidgetPlacement;
|