@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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@usereq/widget",
3
- "version": "0.2.2",
3
+ "version": "0.2.4",
4
4
  "type": "module",
5
5
  "main": "./src/index.ts",
6
6
  "types": "./src/index.ts",
@@ -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 analyticsTrackerKey = "";
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 emptyAction =
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
@@ -1,5 +1,4 @@
1
1
  export * from "./shared/widget-config";
2
- export * from "./shared/analytics";
3
2
  export * from "./renderer/index";
4
3
  export * from "./types";
5
4
  export * from "./shared/shadow-css";
@@ -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}
@@ -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;