@marketrix.ai/widget 4.0.134 → 4.0.136
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/README.md +28 -9
- package/dist/src/components/views/ChatView.d.ts +5 -1
- package/dist/src/context/ChatContext.d.ts +23 -8
- package/dist/src/hooks/useWidget.d.ts +1 -1
- package/dist/src/sdk/contracts/entities.d.ts +18 -0
- package/dist/src/services/StorageService.d.ts +17 -3
- package/dist/src/services/StreamClient.d.ts +4 -1
- package/dist/src/types/index.d.ts +4 -1
- package/dist/src/utils/bootstrap.d.ts +1 -1
- package/dist/widget.mjs +64 -64
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -71,14 +71,15 @@ All modes also accept the common options below.
|
|
|
71
71
|
|
|
72
72
|
These apply to every mode (script attribute → config key):
|
|
73
73
|
|
|
74
|
-
| Config key | Script attribute | Type | Description
|
|
75
|
-
| ------------------------- | --------------------- | ------------- |
|
|
76
|
-
| `mtxApiHost` | `mtx-api-host` | string | API server URL, e.g. `https://api.marketrix.ai`. The widget has no baked-in API host — you must supply it.
|
|
77
|
-
| `container` | — | `HTMLElement` | Element to mount inside (programmatic only). Defaults to a container appended to `<body>`.
|
|
78
|
-
| `userId` | — | number | Associates widget activity with one of your users.
|
|
79
|
-
| `widget_position_z_index` | — | number | `z-index` floor for the launcher and panel. Raised to the widget's own layer token if you pass a lower value.
|
|
80
|
-
| `show_widget` | — | boolean | When `false`, the widget initializes fully but its UI stays hidden. Default `true`.
|
|
81
|
-
| `use_screenshare` | `mtx-use-screenshare` | boolean | When `false`, screen-share requests are auto-denied and the Share Screen button is hidden. Default `true`. Disable via `mtx-use-screenshare="false"`.
|
|
74
|
+
| Config key | Script attribute | Type | Description |
|
|
75
|
+
| ------------------------- | --------------------- | ------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
76
|
+
| `mtxApiHost` | `mtx-api-host` | string | API server URL, e.g. `https://api.marketrix.ai`. The widget has no baked-in API host — you must supply it. |
|
|
77
|
+
| `container` | — | `HTMLElement` | Element to mount inside (programmatic only). Defaults to a container appended to `<body>`. |
|
|
78
|
+
| `userId` | — | number | Associates widget activity with one of your users. |
|
|
79
|
+
| `widget_position_z_index` | — | number | `z-index` floor for the launcher and panel. Raised to the widget's own layer token if you pass a lower value. |
|
|
80
|
+
| `show_widget` | — | boolean | When `false`, the widget initializes fully but its UI stays hidden. Default `true`. |
|
|
81
|
+
| `use_screenshare` | `mtx-use-screenshare` | boolean | When `false`, screen-share requests are auto-denied and the Share Screen button is hidden. Default `true`. Disable via `mtx-use-screenshare="false"`. |
|
|
82
|
+
| `styleNonce` | `mtx-style-nonce` | string | A CSP nonce applied to the widget's injected `<style>` element. Required only if your `style-src` policy has no `'unsafe-inline'` — see [Content Security Policy](#content-security-policy). |
|
|
82
83
|
|
|
83
84
|
Widget **appearance and behavior** (position, colors, sizing, border radius, animation, enabled Tell/Show/Do features, visibility, greeting toast, optional session recording, header/body/greeting text, and quick-action chips) are configured in the Marketrix dashboard and fetched from the API at init. A hidden widget stays visible in the dashboard preview.
|
|
84
85
|
|
|
@@ -216,7 +217,7 @@ TypeScript types are bundled with the package:
|
|
|
216
217
|
|
|
217
218
|
- `MarketrixConfig` — full config for `initWidget` / `updateMarketrixConfig` (`mtxId`, `mtxKey`, `mtxApiHost`, `userId`, `show_widget`, `use_screenshare`, plus all widget appearance settings, optional).
|
|
218
219
|
- `AddWidgetConfig` — discriminated config for `mountWidget` (production / preview variants + common options).
|
|
219
|
-
- `ClientOwnedConfig` — the host-supplied options the API never sends (`mtxApiHost`, `userId`, `widget_position_z_index`, `show_widget`, `use_screenshare`).
|
|
220
|
+
- `ClientOwnedConfig` — the host-supplied options the API never sends (`mtxApiHost`, `userId`, `widget_position_z_index`, `show_widget`, `use_screenshare`, `styleNonce`).
|
|
220
221
|
- `MarketrixWidgetPreviewProps` — props for the `MarketrixWidgetPreview` component.
|
|
221
222
|
- `ChatMessage`, `WidgetState`, `InstructionType` (`'tell' | 'show' | 'do'`).
|
|
222
223
|
|
|
@@ -230,6 +231,24 @@ TypeScript types are bundled with the package:
|
|
|
230
231
|
|
|
231
232
|
---
|
|
232
233
|
|
|
234
|
+
## Content Security Policy
|
|
235
|
+
|
|
236
|
+
The widget mounts into a closed Shadow DOM and injects its own stylesheet as an inline `<style>`
|
|
237
|
+
element inside it (there is no external stylesheet to point a `<link>` at). A host page enforcing a
|
|
238
|
+
`style-src` policy with no `'unsafe-inline'` blocks that element, leaving the widget mounted but
|
|
239
|
+
entirely unstyled. If your policy is that strict, either:
|
|
240
|
+
|
|
241
|
+
- add `'unsafe-inline'` to `style-src` (simplest, and scoped to styles only), or
|
|
242
|
+
- generate a per-request nonce, add it to your `style-src` policy (`style-src 'nonce-<value>'`), and
|
|
243
|
+
pass the same value as `styleNonce` (programmatic) or `mtx-style-nonce` (script tag) — the widget
|
|
244
|
+
applies it to its injected `<style>` element.
|
|
245
|
+
|
|
246
|
+
The widget makes network requests only to the configured `mtxApiHost`, with credentials explicitly
|
|
247
|
+
omitted on every request (it authenticates via `mtxId`/`mtxKey`, never a cookie) — no `connect-src`
|
|
248
|
+
entry beyond your own API host is required, and no third-party origin is ever contacted.
|
|
249
|
+
|
|
250
|
+
---
|
|
251
|
+
|
|
233
252
|
## License
|
|
234
253
|
|
|
235
254
|
Apache License 2.0 — see the `LICENSE` file.
|
|
@@ -6,7 +6,11 @@
|
|
|
6
6
|
* shell's toolbar button fills with a share toggle, and the composer textarea ref. `MODES` pairs the
|
|
7
7
|
* mode chips' display order with the tenant setting enabling each, so the composer offers only what
|
|
8
8
|
* the workspace turned on. `ChatView` owns the draft text; `handleSendMessage` posts the turn and
|
|
9
|
-
* `handleModeChange` announces a mode switch in the transcript before flipping state.
|
|
9
|
+
* `handleModeChange` announces a mode switch in the transcript before flipping state. A POST failure
|
|
10
|
+
* restores the composed text to the (now-cleared) composer — `messageDispatch`'s resolved `false` —
|
|
11
|
+
* unless the visitor already started typing a new message in the meantime, so a resend is one tap on
|
|
12
|
+
* Send rather than a retype, and the failed turn's own bubble (added optimistically, unconditionally)
|
|
13
|
+
* still shows what was said either way.
|
|
10
14
|
*
|
|
11
15
|
* The composer is locked both while a reply is outstanding and while a screen-access request is open,
|
|
12
16
|
* since a second turn queued behind an unanswered permission card has nowhere to land. `use_screenshare`
|
|
@@ -8,15 +8,30 @@
|
|
|
8
8
|
* defers updaters and loses the effects captured in them, so tool calls never execute. `messageDispatch`
|
|
9
9
|
* POSTs fire-and-forget: the reply arrives over SSE, so only a POST failure resolves the placeholder
|
|
10
10
|
* locally, and a stale-reply watchdog keyed on id AND part count re-arms on every progress line.
|
|
11
|
+
* `messageDispatch` resolves `false` on that same POST failure (`true` otherwise, including preview
|
|
12
|
+
* mode) so a caller holding the composed text — `ChatView`'s composer — can restore it instead of the
|
|
13
|
+
* visitor having to retype a message the widget already dropped from the input box.
|
|
11
14
|
*
|
|
12
15
|
* The stream effect subscribes to the `StreamClient` singleton: `handleMessage` does the bookkeeping the
|
|
13
|
-
* pure reducer cannot hold (`tool_call_id` dedupe in a bounded set, cleared on a terminal status
|
|
14
|
-
* each effect runs its browser tool, stamps progress,
|
|
15
|
-
*
|
|
16
|
-
* retriable blip settles when the reply lands on the
|
|
17
|
-
* which carries no task id. Every failure on the tool
|
|
18
|
-
* as the console: an undelivered `tool/response`
|
|
19
|
-
* the
|
|
16
|
+
* pure reducer cannot hold (`tool_call_id` dedupe in a bounded set, cleared on a terminal status; the
|
|
17
|
+
* mirror-shaped `respondedRequestIds` below), then each effect runs its browser tool, stamps progress,
|
|
18
|
+
* replies `tool/response` and only then fires `afterResponseAttempt`. `handleError` converts only a
|
|
19
|
+
* `StreamGaveUpError` into a transport failure, as a retriable blip settles when the reply lands on the
|
|
20
|
+
* reconnected stream. `stopTask` sends `chat/stop`, which carries no task id. Every failure on the tool
|
|
21
|
+
* and stop paths reaches `uiActions.setError` as well as the console: an undelivered `tool/response`
|
|
22
|
+
* leaves the agent waiting on a reply that never comes, so the run stalls with nothing on screen unless
|
|
23
|
+
* the visitor is told, and `do` mode may still be clicking.
|
|
24
|
+
*
|
|
25
|
+
* `respondedRequestIds` closes the other half of the reconnect-replay contract `StreamClient.ts`
|
|
26
|
+
* documents (the api replays a chat_id's whole turn history, not just the tail a client missed):
|
|
27
|
+
* `reduceText`'s own exact-repeat guard (`sseReducer.ts`) only catches an identical FINAL
|
|
28
|
+
* `chat/response` replayed after the answer already closed, because it only ever compares against the
|
|
29
|
+
* message's LAST part. A replayed `chat/delta` fails that same-string check (it's a partial fragment,
|
|
30
|
+
* not the full closed text) and would otherwise be appended as a brand-new, visibly duplicated text
|
|
31
|
+
* segment ahead of the closing response that then only overwrites the one it just added — leaving two
|
|
32
|
+
* copies of the same answer on screen. Scoped by `request_id`, not globally, so an unrelated later
|
|
33
|
+
* turn's genuinely new `chat/delta`/`chat/response` is untouched; marked closed only once, on the
|
|
34
|
+
* request's own terminal `chat/response`, since a delta is by definition still open.
|
|
20
35
|
*
|
|
21
36
|
* `lastStreamErrorRef`/`currentErrorRef` close the loop `handleError` alone leaves open: `StreamClient`
|
|
22
37
|
* calls `onError` on every failed dial, not only a terminal give-up, so the visitor sees "Stream
|
|
@@ -43,7 +58,7 @@ interface ChatActions {
|
|
|
43
58
|
removeMessage: (messageId: string) => void;
|
|
44
59
|
setMessages: (messages: ChatMessage[]) => void;
|
|
45
60
|
clearMessages: () => void;
|
|
46
|
-
messageDispatch: (content: string, mode?: InstructionType, skipUserMessage?: boolean) => Promise<
|
|
61
|
+
messageDispatch: (content: string, mode?: InstructionType, skipUserMessage?: boolean) => Promise<boolean>;
|
|
47
62
|
}
|
|
48
63
|
interface TaskActions {
|
|
49
64
|
resetTask: () => void;
|
|
@@ -21,7 +21,7 @@ export declare const useWidget: () => {
|
|
|
21
21
|
removeMessage: (messageId: string) => void;
|
|
22
22
|
setMessages: (messages: import("..").ChatMessage[]) => void;
|
|
23
23
|
clearMessages: () => void;
|
|
24
|
-
messageDispatch: (content: string, mode?: import("..").InstructionType, skipUserMessage?: boolean) => Promise<
|
|
24
|
+
messageDispatch: (content: string, mode?: import("..").InstructionType, skipUserMessage?: boolean) => Promise<boolean>;
|
|
25
25
|
resetTask: () => void;
|
|
26
26
|
stopTask: () => Promise<void>;
|
|
27
27
|
setActiveView: (view: import("../types").WidgetView) => void;
|
|
@@ -135,6 +135,12 @@ export declare const WorkspaceSummarySchema: z.ZodObject<{
|
|
|
135
135
|
notify_all_members_on_question: z.ZodBoolean;
|
|
136
136
|
}, z.core.$strip>;
|
|
137
137
|
export type WorkspaceSummary = z.infer<typeof WorkspaceSummarySchema>;
|
|
138
|
+
export declare const ApplicationSkillDistillationStatusSchema: z.ZodEnum<{
|
|
139
|
+
failed: "failed";
|
|
140
|
+
idle: "idle";
|
|
141
|
+
pending: "pending";
|
|
142
|
+
}>;
|
|
143
|
+
export type ApplicationSkillDistillationStatus = z.infer<typeof ApplicationSkillDistillationStatusSchema>;
|
|
138
144
|
export declare const ApplicationEntitySchema: z.ZodObject<{
|
|
139
145
|
id: z.ZodNumber;
|
|
140
146
|
created_at: z.ZodCoercedDate<unknown>;
|
|
@@ -150,6 +156,12 @@ export declare const ApplicationEntitySchema: z.ZodObject<{
|
|
|
150
156
|
username: z.ZodNullable<z.ZodString>;
|
|
151
157
|
password: z.ZodNullable<z.ZodString>;
|
|
152
158
|
allowed_domains: z.ZodArray<z.ZodString>;
|
|
159
|
+
skill_distillation_status: z.ZodEnum<{
|
|
160
|
+
failed: "failed";
|
|
161
|
+
idle: "idle";
|
|
162
|
+
pending: "pending";
|
|
163
|
+
}>;
|
|
164
|
+
skill_distillation_error: z.ZodNullable<z.ZodString>;
|
|
153
165
|
}, z.core.$strip>;
|
|
154
166
|
export type ApplicationData = z.infer<typeof ApplicationEntitySchema>;
|
|
155
167
|
/** Used for all API responses; password is write-only and never returned to clients. */
|
|
@@ -167,6 +179,12 @@ export declare const ApplicationReadSchema: z.ZodObject<{
|
|
|
167
179
|
workspace_id: z.ZodNumber;
|
|
168
180
|
username: z.ZodNullable<z.ZodString>;
|
|
169
181
|
allowed_domains: z.ZodArray<z.ZodString>;
|
|
182
|
+
skill_distillation_status: z.ZodEnum<{
|
|
183
|
+
failed: "failed";
|
|
184
|
+
idle: "idle";
|
|
185
|
+
pending: "pending";
|
|
186
|
+
}>;
|
|
187
|
+
skill_distillation_error: z.ZodNullable<z.ZodString>;
|
|
170
188
|
}, z.core.$strip>;
|
|
171
189
|
export type ApplicationReadData = z.infer<typeof ApplicationReadSchema>;
|
|
172
190
|
export declare const WidgetChipSchema: z.ZodObject<{
|
|
@@ -5,7 +5,12 @@
|
|
|
5
5
|
* browser-local key shares via `scopedKey`, so the chat-context, drag-position and resize keys all
|
|
6
6
|
* partition by tenant identically. `readLocal`/`writeLocal` are the only `localStorage` access in `src/`;
|
|
7
7
|
* a host page can deny storage outright (third-party cookies off, sandboxed iframe), so both degrade to
|
|
8
|
-
*
|
|
8
|
+
* memory and the widget keeps working unpersisted. `updateContext` calls `writeLocal` on every UI-state
|
|
9
|
+
* change (a drag, a resize, every chat message), so without `warnOnce` a denied host page would spam one
|
|
10
|
+
* console line per write for the visitor's whole session; a single warn on first denial says everything
|
|
11
|
+
* a customer's console needs. `warnOnce` is keyed by read vs. write since `readLocal` also fires once on
|
|
12
|
+
* its own (`loadContext` at module init, before any write) and both should still surface if a page
|
|
13
|
+
* somehow denies one and not the other.
|
|
9
14
|
*
|
|
10
15
|
* `loadContext` merges one parsed key over `DEFAULT_CONTEXT`, so an older widget version's payload reads
|
|
11
16
|
* as incomplete rather than corrupt, and discards anything past `CONTEXT_EXPIRY_MS` (7 days).
|
|
@@ -14,8 +19,13 @@
|
|
|
14
19
|
* from it, since without that two applications on one origin would leak one tenant's transcript into
|
|
15
20
|
* another's.
|
|
16
21
|
*
|
|
17
|
-
*
|
|
18
|
-
* excluded
|
|
22
|
+
* `writeChatSnapshot`'s own parameter is `{messages, currentMode, isOpen}` — chat_id, config and
|
|
23
|
+
* timestamp are deliberately excluded from THAT function's input, though `config` (via `setConfig`)
|
|
24
|
+
* and `chat_id`/`timestamp` (via `updateContext`) are still part of the one persisted record on disk;
|
|
25
|
+
* `config` there is the tenant's own `mtxId`/`mtxKey`/`mtxApiHost`/`userId`/rendered settings — no
|
|
26
|
+
* broader PII, and `mtxId`/`mtxKey` are already fully public in the host page's own script-tag markup,
|
|
27
|
+
* not a secret this adds exposure to. `StoredMessage` is the ONE place `content` still exists as a
|
|
28
|
+
* field: a live `ChatMessage` has
|
|
19
29
|
* none (every reader derives `messageText(parts)` instead), but a transcript written before `parts`
|
|
20
30
|
* existed has only `content` on disk, so `readChatSnapshot` backfills a text part from it and
|
|
21
31
|
* `writeChatSnapshot` derives `content` back from `parts` on the way out, keeping the persisted shape
|
|
@@ -50,6 +60,10 @@ type MarketrixChatContext = Omit<ChatSnapshot, 'messages'> & {
|
|
|
50
60
|
};
|
|
51
61
|
export declare function tenantScope(config: MarketrixConfig): string;
|
|
52
62
|
export declare function scopedKey(name: string, config: MarketrixConfig): string;
|
|
63
|
+
/** Test-only: `warned` is module-level so a "denies storage, keeps working" proof isn't the last test in
|
|
64
|
+
* the file to touch a denied `Storage.prototype`, and a later test in the same `bun test` process (one
|
|
65
|
+
* process per file, not per test) would otherwise see zero warns instead of one. Never called from `src/`. */
|
|
66
|
+
export declare function resetStorageWarningsForTests(): void;
|
|
53
67
|
export declare function readLocal(key: string): string | null;
|
|
54
68
|
export declare function writeLocal(key: string, value: string): void;
|
|
55
69
|
declare class StorageService {
|
|
@@ -6,7 +6,10 @@
|
|
|
6
6
|
*
|
|
7
7
|
* `open` is the transport, `registered` is the chat: only the latter can carry a reply, so `isConnected` reads
|
|
8
8
|
* `registered` and nothing waits on `open`. Backoff counters reset only on `registered` — resetting at `open` would
|
|
9
|
-
* defeat the max-attempts cap if registration never lands and the stream flaps open→closed.
|
|
9
|
+
* defeat the max-attempts cap if registration never lands and the stream flaps open→closed. `scheduleReconnect`'s
|
|
10
|
+
* EQUAL JITTER (the doubling delay's own second half, chosen uniformly) keeps every dial within the documented
|
|
11
|
+
* schedule's bound while stopping every tab across every open customer page from redialing on the exact same
|
|
12
|
+
* clock tick after a shared outage — a thundering herd the deterministic schedule alone cannot prevent. Tabs share the
|
|
10
13
|
* localStorage chat id, so the server keys SSE by (chat_id, tab_id) and `tabId` stops tabs evicting each other's
|
|
11
14
|
* stream. Credentials are read at connect time, not captured at init, so a reconnect after `updateMarketrixConfig`
|
|
12
15
|
* dials with the current ones.
|
|
@@ -4,7 +4,9 @@
|
|
|
4
4
|
*
|
|
5
5
|
* `ClientOwnedConfig` holds host-supplied knobs the api never sends (`show_widget: false` still
|
|
6
6
|
* initializes the widget fully and only hides its UI; `use_screenshare: false` auto-denies
|
|
7
|
-
* screen-access requests and hides the Share Screen button; both default true
|
|
7
|
+
* screen-access requests and hides the Share Screen button; both default true; `styleNonce` reaches
|
|
8
|
+
* the injected shadow-root `<style>` element for a host running a strict `style-src` CSP with no
|
|
9
|
+
* `'unsafe-inline'`). `MarketrixConfig` is
|
|
8
10
|
* deliberately FLAT so api settings spread straight in — `mtxId`+`mtxKey` is the credential, while
|
|
9
11
|
* `mtxApp` is stamped internally after validation and never an input, since an application id is
|
|
10
12
|
* guessable and authenticates nothing. `ValidWidgetConfig` is a `MarketrixConfig` run through
|
|
@@ -29,6 +31,7 @@ export interface ClientOwnedConfig {
|
|
|
29
31
|
widget_position_z_index?: number;
|
|
30
32
|
show_widget?: boolean;
|
|
31
33
|
use_screenshare?: boolean;
|
|
34
|
+
styleNonce?: string;
|
|
32
35
|
}
|
|
33
36
|
export type MarketrixConfig = Partial<WidgetRenderedSettings> & ClientOwnedConfig & {
|
|
34
37
|
mtxId?: string;
|
|
@@ -11,7 +11,7 @@ interface WidgetMount {
|
|
|
11
11
|
export declare const widgetState: {
|
|
12
12
|
mount: WidgetMount | null;
|
|
13
13
|
};
|
|
14
|
-
export declare const createWidgetContainer: (parentContainer?: HTMLElement) => {
|
|
14
|
+
export declare const createWidgetContainer: (parentContainer?: HTMLElement, styleNonce?: string) => {
|
|
15
15
|
container: HTMLElement;
|
|
16
16
|
shadowRoot: ShadowRoot;
|
|
17
17
|
mountEl: HTMLElement;
|