@dolard.eu/versiq-widget 0.1.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/LICENSE +55 -0
- package/README.md +401 -0
- package/dist/config.d.ts +6 -0
- package/dist/index.d.ts +64 -0
- package/dist/loader.d.ts +106 -0
- package/dist/logger.d.ts +26 -0
- package/dist/postmessage.d.ts +47 -0
- package/dist/types.d.ts +185 -0
- package/dist/widget.es.js +3352 -0
- package/dist/widget.umd.js +39 -0
- package/package.json +84 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
Versiq Widget SDK — Commercial Source-available License
|
|
2
|
+
========================================================
|
|
3
|
+
|
|
4
|
+
Copyright (c) 2024-2026 Sébastien Dolard
|
|
5
|
+
|
|
6
|
+
This software is made available for inspection. Source code is published only
|
|
7
|
+
so integrators can verify what runs on their visitors' browsers. Publication
|
|
8
|
+
is NOT an open-source release and does NOT grant rights normally associated
|
|
9
|
+
with such releases.
|
|
10
|
+
|
|
11
|
+
1. Permitted use
|
|
12
|
+
You may, without separate written agreement:
|
|
13
|
+
(a) Include `dist/widget.umd.js` or `dist/widget.es.js` on websites you
|
|
14
|
+
operate, for the sole purpose of consuming the Versiq backend API
|
|
15
|
+
using a publishable key you have legitimately obtained.
|
|
16
|
+
(b) Hot-link the bundle from public CDNs that mirror npm (e.g. unpkg,
|
|
17
|
+
jsDelivr), provided you do not strip or alter the file.
|
|
18
|
+
(c) Configure the widget at runtime via the public TypeScript surface
|
|
19
|
+
(theme, position, lifecycle handlers, identity hooks, etc.) as
|
|
20
|
+
documented in the README.
|
|
21
|
+
|
|
22
|
+
2. Prohibited without prior written agreement
|
|
23
|
+
You may not, in whole or in part:
|
|
24
|
+
(a) Modify, fork, or create derivative works of the source or bundle for
|
|
25
|
+
redistribution under any name.
|
|
26
|
+
(b) Reverse-engineer, decompile, or de-obfuscate the bundle, in whole or
|
|
27
|
+
in part, for the purpose of creating a competing SDK, widget, or
|
|
28
|
+
backend.
|
|
29
|
+
(c) Redistribute the bundle, or any derivative, under a different package
|
|
30
|
+
name, namespace, or scope.
|
|
31
|
+
(d) Remove, alter, or obscure copyright notices, attribution, or this
|
|
32
|
+
license file from any copy.
|
|
33
|
+
|
|
34
|
+
3. Production use
|
|
35
|
+
Use of this software against the Versiq backend in production is subject
|
|
36
|
+
to the Versiq Commercial Terms of Service. The license granted here does
|
|
37
|
+
not by itself authorize production use; a valid commercial agreement
|
|
38
|
+
(typically reflected by an issued publishable key bound to an
|
|
39
|
+
Application) is required.
|
|
40
|
+
|
|
41
|
+
4. No warranty
|
|
42
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
|
43
|
+
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
44
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT.
|
|
45
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
|
46
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
|
47
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
|
48
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
49
|
+
|
|
50
|
+
5. Termination
|
|
51
|
+
Any breach of section 2 terminates your rights under section 1
|
|
52
|
+
immediately, without notice, and you must cease all use of the software
|
|
53
|
+
and destroy all copies in your possession or control.
|
|
54
|
+
|
|
55
|
+
For commercial licensing or written-agreement inquiries: admin@dolard.eu
|
package/README.md
ADDED
|
@@ -0,0 +1,401 @@
|
|
|
1
|
+
# @dolard.eu/versiq-widget
|
|
2
|
+
|
|
3
|
+
Versiq Widget SDK - Embed conversational qualification into your website.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @dolard.eu/versiq-widget
|
|
9
|
+
# or
|
|
10
|
+
pnpm add @dolard.eu/versiq-widget
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Quick Start
|
|
14
|
+
|
|
15
|
+
### Script Tag (CDN)
|
|
16
|
+
|
|
17
|
+
Hot-link from any public npm CDN, pinned to a version:
|
|
18
|
+
|
|
19
|
+
```html
|
|
20
|
+
<script
|
|
21
|
+
src="https://unpkg.com/@dolard.eu/versiq-widget@0.1.0/dist/widget.umd.js"
|
|
22
|
+
data-api-key="pk_your_key"
|
|
23
|
+
data-theme='{"primaryColor":"#3B82F6"}'
|
|
24
|
+
></script>
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Equivalent via jsDelivr:
|
|
28
|
+
|
|
29
|
+
```html
|
|
30
|
+
<script
|
|
31
|
+
src="https://cdn.jsdelivr.net/npm/@dolard.eu/versiq-widget@0.1.0/dist/widget.umd.js"
|
|
32
|
+
data-api-key="pk_your_key"
|
|
33
|
+
></script>
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Pinning a version avoids breaking changes from later releases. To always follow
|
|
37
|
+
the latest, drop `@0.1.0`.
|
|
38
|
+
|
|
39
|
+
### Programmatic API
|
|
40
|
+
|
|
41
|
+
```typescript
|
|
42
|
+
import { createWidget, type VersiqWidget } from "@dolard.eu/versiq-widget";
|
|
43
|
+
|
|
44
|
+
const widget = createWidget({
|
|
45
|
+
apiKey: "pk_your_key",
|
|
46
|
+
position: "bottom-right",
|
|
47
|
+
theme: { primaryColor: "#3B82F6" },
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
// Control the widget
|
|
51
|
+
widget.open();
|
|
52
|
+
widget.close();
|
|
53
|
+
widget.reset();
|
|
54
|
+
|
|
55
|
+
// Cleanup
|
|
56
|
+
widget.destroy();
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Configuration
|
|
60
|
+
|
|
61
|
+
### WidgetConfig
|
|
62
|
+
|
|
63
|
+
| Option | Type | Default | Description |
|
|
64
|
+
| ----------- | --------------------------------------------- | ---------------- | -------------------------------------------- |
|
|
65
|
+
| `apiKey` | `string` | **required** | Publishable API key (`pk_*`) for the app |
|
|
66
|
+
| `position` | `"bottom-right" \| "bottom-left" \| "inline"` | `"bottom-right"` | Widget position on the page |
|
|
67
|
+
| `open` | `boolean` | `false` | Initial open state |
|
|
68
|
+
| `container` | `HTMLElement \| string` | - | Container element for inline mode |
|
|
69
|
+
| `baseUrl` | `string` | Production URL | Base URL for the widget embed |
|
|
70
|
+
| `theme` | `ThemeConfig` | - | Custom theme (merged with server-side theme) |
|
|
71
|
+
| `debug` | `boolean` | `false` | Enable debug logging |
|
|
72
|
+
| `email` | `string` | - | Pre-identified user email (HMAC) |
|
|
73
|
+
| `userId` | `string` | - | Host-side user identifier (HMAC) |
|
|
74
|
+
| `userHash` | `string` | - | HMAC-SHA256 of email/userId |
|
|
75
|
+
|
|
76
|
+
### ThemeConfig
|
|
77
|
+
|
|
78
|
+
| Option | Type | Description |
|
|
79
|
+
| ----------------- | ----------------------------- | ---------------------------------------------------------------------------------------------- |
|
|
80
|
+
| `primaryColor` | `string` | Primary brand color (hex, e.g., `#3B82F6`) |
|
|
81
|
+
| `backgroundColor` | `string` | Background color for the widget container |
|
|
82
|
+
| `textColor` | `string` | Text color |
|
|
83
|
+
| `borderRadius` | `number` | Border radius in pixels |
|
|
84
|
+
| `fontFamily` | `string` | Font family |
|
|
85
|
+
| `colorScheme` | `"light" \| "dark" \| "auto"` | Widget color scheme. `"auto"` follows the visitor's `prefers-color-scheme`. Defaults to light. |
|
|
86
|
+
|
|
87
|
+
## API Reference
|
|
88
|
+
|
|
89
|
+
### createWidget(config)
|
|
90
|
+
|
|
91
|
+
Creates a new widget instance.
|
|
92
|
+
|
|
93
|
+
```typescript
|
|
94
|
+
const widget = createWidget({
|
|
95
|
+
apiKey: "pk_your_key",
|
|
96
|
+
position: "inline",
|
|
97
|
+
container: document.getElementById("widget-container"),
|
|
98
|
+
open: true,
|
|
99
|
+
});
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### VersiqWidget Methods
|
|
103
|
+
|
|
104
|
+
| Method | Description |
|
|
105
|
+
| ----------------------------------------------------- | -------------------------------------------------------- |
|
|
106
|
+
| `open()` | Open the widget |
|
|
107
|
+
| `close()` | Close the widget |
|
|
108
|
+
| `reset()` | Reset the conversation |
|
|
109
|
+
| `setTheme(theme: ThemeConfig)` | Update theme dynamically |
|
|
110
|
+
| `setColorScheme(scheme: "light" \| "dark" \| "auto")` | Sugar over `setTheme({ colorScheme })` for dark-mode UIs |
|
|
111
|
+
| `identify(params)` | Set host-attested identity (HMAC) |
|
|
112
|
+
| `destroy()` | Remove widget and cleanup |
|
|
113
|
+
| `on(event, handler)` | Subscribe to events |
|
|
114
|
+
| `off(event, handler)` | Unsubscribe from events |
|
|
115
|
+
|
|
116
|
+
### Window API (Script Tag)
|
|
117
|
+
|
|
118
|
+
When using the script tag, the API is available on `window.Versiq`:
|
|
119
|
+
|
|
120
|
+
```javascript
|
|
121
|
+
window.Versiq.open();
|
|
122
|
+
window.Versiq.close();
|
|
123
|
+
window.Versiq.setTheme({ primaryColor: "#10B981" });
|
|
124
|
+
|
|
125
|
+
// Wire your own dark-mode toggle to the widget:
|
|
126
|
+
window.Versiq.setColorScheme("dark"); // or "light", or "auto"
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
## Events
|
|
130
|
+
|
|
131
|
+
Subscribe to widget events to react to user interactions.
|
|
132
|
+
|
|
133
|
+
```typescript
|
|
134
|
+
widget.on("ready", () => {
|
|
135
|
+
console.log("Widget is ready");
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
widget.on("profile-update", (data) => {
|
|
139
|
+
console.log("Profile updated:", data.profile);
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
widget.on("qualified", (data) => {
|
|
143
|
+
console.log("Lead qualified:", data.profile, "Score:", data.score);
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
widget.on("message", (data) => {
|
|
147
|
+
console.log("New message:", data.message);
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
widget.on("error", (data) => {
|
|
151
|
+
console.error("Widget error:", data.code, data.message);
|
|
152
|
+
});
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
### Event Types
|
|
156
|
+
|
|
157
|
+
| Event | Payload | Description |
|
|
158
|
+
| ------------------- | ------------------------------------------- | ----------------------------- |
|
|
159
|
+
| `ready` | - | Widget is loaded and ready |
|
|
160
|
+
| `open` | - | Widget was opened |
|
|
161
|
+
| `close` | - | Widget was closed |
|
|
162
|
+
| `message` | `{ message: WidgetMessage }` | New chat message |
|
|
163
|
+
| `profile-update` | `{ profile: WidgetProfile }` | Profile data was updated |
|
|
164
|
+
| `qualified` | `{ profile: WidgetProfile, score: number }` | Lead qualification complete |
|
|
165
|
+
| `error` | `{ code: string, message: string }` | An error occurred |
|
|
166
|
+
| `quota-warning` | `{ remaining: number, limit: number }` | Lead quota running low |
|
|
167
|
+
| `quota-exceeded` | - | Lead quota exceeded |
|
|
168
|
+
| `identity-verified` | `{ email: string, userId?: string }` | Host identity verified (HMAC) |
|
|
169
|
+
|
|
170
|
+
## TypeScript
|
|
171
|
+
|
|
172
|
+
The package includes full TypeScript support with exported types:
|
|
173
|
+
|
|
174
|
+
```typescript
|
|
175
|
+
import type {
|
|
176
|
+
WidgetConfig,
|
|
177
|
+
ThemeConfig,
|
|
178
|
+
VersiqWidget,
|
|
179
|
+
WidgetEventType,
|
|
180
|
+
WidgetProfile,
|
|
181
|
+
WidgetMessage,
|
|
182
|
+
B2BProfile, // B2B-specific (backward compat)
|
|
183
|
+
} from "@dolard.eu/versiq-widget";
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
### Profile Types
|
|
187
|
+
|
|
188
|
+
- **`WidgetProfile`** (`Record<string, unknown>`) — Generic profile used in
|
|
189
|
+
events. Shape depends on the vertical (`BuyerProfile` for real-estate,
|
|
190
|
+
`B2BProfile` for b2b-qualification).
|
|
191
|
+
- **`B2BProfile`** — Typed B2B qualification profile (sector, companySize,
|
|
192
|
+
etc.). Kept for backward compatibility.
|
|
193
|
+
|
|
194
|
+
## Widget Mode (Real-Estate)
|
|
195
|
+
|
|
196
|
+
When using `vertical: "real-estate"`, the widget runs in **qualification mode**:
|
|
197
|
+
Versiq qualifies the buyer through conversation and transmits the profile — no
|
|
198
|
+
property data is needed from the integrator.
|
|
199
|
+
|
|
200
|
+
### Flow
|
|
201
|
+
|
|
202
|
+
1. Versiq detects the user type (buyer, seller, etc.)
|
|
203
|
+
2. Collects criteria: location, budget, property type, surface...
|
|
204
|
+
3. Emits `profile-update` at each turn with the current profile
|
|
205
|
+
4. When qualification is complete, emits `versiq:qualified` with the full
|
|
206
|
+
profile
|
|
207
|
+
|
|
208
|
+
### Integration Example
|
|
209
|
+
|
|
210
|
+
```typescript
|
|
211
|
+
const widget = createWidget({
|
|
212
|
+
apiKey: "pk_your_real_estate_key",
|
|
213
|
+
position: "inline",
|
|
214
|
+
container: "#chat",
|
|
215
|
+
open: true,
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
// Track profile updates in real-time
|
|
219
|
+
widget.on("profile-update", (data) => {
|
|
220
|
+
console.log("Criteria so far:", data.profile);
|
|
221
|
+
// data.profile: { userType, location, budget, propertyType, ... }
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
// Lead is fully qualified — trigger your own search/CRM
|
|
225
|
+
widget.on("qualified", (data) => {
|
|
226
|
+
console.log("Qualified lead:", data.profile);
|
|
227
|
+
// data.score: 1 (V1: binary — 1 = qualified)
|
|
228
|
+
triggerPropertySearch(data.profile);
|
|
229
|
+
});
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
### Data Tools
|
|
233
|
+
|
|
234
|
+
In widget mode, data-dependent tools (searchProperties, getCityStats,
|
|
235
|
+
estimateProperty, etc.) are **automatically excluded**. The integrator's
|
|
236
|
+
platform provides the data; Versiq handles qualification only.
|
|
237
|
+
|
|
238
|
+
## Display Modes
|
|
239
|
+
|
|
240
|
+
### Floating (Default)
|
|
241
|
+
|
|
242
|
+
Widget appears as a floating button in the corner of the page.
|
|
243
|
+
|
|
244
|
+
```typescript
|
|
245
|
+
createWidget({
|
|
246
|
+
apiKey: "pk_your_key",
|
|
247
|
+
position: "bottom-right", // or "bottom-left"
|
|
248
|
+
});
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
### Inline
|
|
252
|
+
|
|
253
|
+
Widget is embedded directly into a container element.
|
|
254
|
+
|
|
255
|
+
```typescript
|
|
256
|
+
createWidget({
|
|
257
|
+
apiKey: "pk_your_key",
|
|
258
|
+
position: "inline",
|
|
259
|
+
container: document.getElementById("chat-container"),
|
|
260
|
+
open: true,
|
|
261
|
+
});
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
## React Integration
|
|
265
|
+
|
|
266
|
+
```tsx
|
|
267
|
+
"use client";
|
|
268
|
+
|
|
269
|
+
import { useEffect, useRef } from "react";
|
|
270
|
+
import { createWidget, type VersiqWidget } from "@dolard.eu/versiq-widget";
|
|
271
|
+
|
|
272
|
+
export function ContactWidget() {
|
|
273
|
+
const widgetRef = useRef<VersiqWidget | null>(null);
|
|
274
|
+
const containerRef = useRef<HTMLDivElement>(null);
|
|
275
|
+
|
|
276
|
+
useEffect(() => {
|
|
277
|
+
if (!containerRef.current || widgetRef.current) return;
|
|
278
|
+
|
|
279
|
+
const widget = createWidget({
|
|
280
|
+
apiKey: "pk_your_key",
|
|
281
|
+
position: "inline",
|
|
282
|
+
container: containerRef.current,
|
|
283
|
+
open: true,
|
|
284
|
+
theme: { primaryColor: "#3B82F6" },
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
widgetRef.current = widget;
|
|
288
|
+
|
|
289
|
+
return () => {
|
|
290
|
+
widgetRef.current?.destroy();
|
|
291
|
+
widgetRef.current = null;
|
|
292
|
+
};
|
|
293
|
+
}, []);
|
|
294
|
+
|
|
295
|
+
return <div ref={containerRef} className="h-[600px]" />;
|
|
296
|
+
}
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
## Data Attributes (Script Tag)
|
|
300
|
+
|
|
301
|
+
| Attribute | Maps to | Example |
|
|
302
|
+
| ---------------- | ---------- | --------------------------------------- |
|
|
303
|
+
| `data-api-key` | `apiKey` | `data-api-key="pk_live_abc123"` |
|
|
304
|
+
| `data-position` | `position` | `data-position="bottom-left"` |
|
|
305
|
+
| `data-open` | `open` | `data-open="true"` |
|
|
306
|
+
| `data-theme` | `theme` | `data-theme='{"primaryColor":"#F00"}'` |
|
|
307
|
+
| `data-base-url` | `baseUrl` | `data-base-url="https://app.versiq.io"` |
|
|
308
|
+
| `data-debug` | `debug` | `data-debug="true"` |
|
|
309
|
+
| `data-email` | `email` | `data-email="user@example.com"` |
|
|
310
|
+
| `data-user-id` | `userId` | `data-user-id="usr_123"` |
|
|
311
|
+
| `data-user-hash` | `userHash` | `data-user-hash="<hmac>"` |
|
|
312
|
+
|
|
313
|
+
## E2E Test Selectors (data-testid)
|
|
314
|
+
|
|
315
|
+
The widget exposes a **stable contract** of `data-testid` attributes for
|
|
316
|
+
end-to-end testing (Playwright, Cypress, etc.). These selectors are guaranteed
|
|
317
|
+
not to change without a major version bump.
|
|
318
|
+
|
|
319
|
+
| Selector | Element | Additional attributes |
|
|
320
|
+
| ------------------- | --------------------- | ---------------------------------- |
|
|
321
|
+
| `widget-root` | Chat container (open) | — |
|
|
322
|
+
| `widget-input` | Message input field | — |
|
|
323
|
+
| `widget-send` | Send button | — |
|
|
324
|
+
| `widget-message` | Message bubble | `data-role="user" \| "assistant"` |
|
|
325
|
+
| `widget-suggestion` | Quick reply chip | `data-index="<N>"` (position) |
|
|
326
|
+
| `widget-cta-button` | Call-to-action button | `data-objective="<objectiveType>"` |
|
|
327
|
+
| `widget-avatar` | Header avatar | — |
|
|
328
|
+
|
|
329
|
+
> Contract source: `apps/app/src/app/widget/embed/components/`. Property card
|
|
330
|
+
> selectors (`widget-property-card`) are not yet exposed (pending #727).
|
|
331
|
+
|
|
332
|
+
### Example (Playwright)
|
|
333
|
+
|
|
334
|
+
```ts
|
|
335
|
+
await page.getByTestId("widget-input").fill("Looking for a 2-bedroom in Lyon");
|
|
336
|
+
await page.getByTestId("widget-send").click();
|
|
337
|
+
await expect(
|
|
338
|
+
page.getByTestId("widget-message").filter({ hasText: "Lyon" }),
|
|
339
|
+
).toBeVisible();
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
## Development
|
|
343
|
+
|
|
344
|
+
```bash
|
|
345
|
+
# Install dependencies
|
|
346
|
+
pnpm install
|
|
347
|
+
|
|
348
|
+
# Development build with watch
|
|
349
|
+
pnpm dev
|
|
350
|
+
|
|
351
|
+
# Production build
|
|
352
|
+
pnpm build
|
|
353
|
+
|
|
354
|
+
# Run tests
|
|
355
|
+
pnpm test
|
|
356
|
+
|
|
357
|
+
# Local testing (serves test.html via HTTP - required for iframe CSP)
|
|
358
|
+
pnpm serve
|
|
359
|
+
# Then open http://localhost:5500/test.html
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
## Performance
|
|
363
|
+
|
|
364
|
+
### Bundle Size
|
|
365
|
+
|
|
366
|
+
| Format | Size (minified) | Size (gzipped) | Limit |
|
|
367
|
+
| ------ | --------------- | -------------- | ----- |
|
|
368
|
+
| UMD | ~63 KB | ~16 KB | 50 KB |
|
|
369
|
+
| ESM | ~89 KB | ~19 KB | - |
|
|
370
|
+
|
|
371
|
+
CI enforces the 50 KB gzipped limit via `size-limit`.
|
|
372
|
+
|
|
373
|
+
### Largest Contentful Paint (LCP)
|
|
374
|
+
|
|
375
|
+
**Target**: < 1.5s
|
|
376
|
+
|
|
377
|
+
The widget loads as an iframe, so LCP depends on:
|
|
378
|
+
|
|
379
|
+
1. SDK download (~16 KB gzipped) - typically < 100ms
|
|
380
|
+
2. Iframe creation - instant
|
|
381
|
+
3. Embed page load - varies by network
|
|
382
|
+
|
|
383
|
+
**Measured baseline**: 0.14s (localhost, no throttling)
|
|
384
|
+
|
|
385
|
+
### Integration Time
|
|
386
|
+
|
|
387
|
+
**Target**: < 15 minutes from docs to working widget
|
|
388
|
+
|
|
389
|
+
The Quick Start section provides copy-paste integration in under 5 minutes.
|
|
390
|
+
|
|
391
|
+
## License
|
|
392
|
+
|
|
393
|
+
Commercial Source-available — see [LICENSE](./LICENSE).
|
|
394
|
+
|
|
395
|
+
This is **not** an open-source release: the source is published so integrators
|
|
396
|
+
can audit what runs in their visitors' browsers, but fork / redistribution /
|
|
397
|
+
competing-SDK use are prohibited without a written agreement. Production use
|
|
398
|
+
against the Versiq backend is governed by the Versiq Commercial Terms of
|
|
399
|
+
Service.
|
|
400
|
+
|
|
401
|
+
For commercial licensing inquiries: admin@dolard.eu
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { type ApplicationConfigResponse } from "./types";
|
|
2
|
+
export declare class ConfigFetchError extends Error {
|
|
3
|
+
readonly code: string;
|
|
4
|
+
constructor(message: string, code: string);
|
|
5
|
+
}
|
|
6
|
+
export declare function fetchApplicationConfig(apiKey: string, baseUrl: string, externalSignal?: AbortSignal): Promise<ApplicationConfigResponse>;
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Versiq Widget SDK
|
|
3
|
+
*
|
|
4
|
+
* Entry point for the widget SDK. Supports both auto-initialization from
|
|
5
|
+
* script tags and programmatic usage.
|
|
6
|
+
*
|
|
7
|
+
* Auto-init usage (canonical integrator surface — #726):
|
|
8
|
+
* ```html
|
|
9
|
+
* <script
|
|
10
|
+
* src="https://cdn.versiq.io/widget.js"
|
|
11
|
+
* data-api-key="pk_live_xxx">
|
|
12
|
+
* </script>
|
|
13
|
+
* ```
|
|
14
|
+
*
|
|
15
|
+
* The integrator's HTML carries only the publishable key (plus optional
|
|
16
|
+
* host-context attributes like `data-container` / `data-debug` / identity
|
|
17
|
+
* fields). Theme, position, language, showProfile, and the default open
|
|
18
|
+
* state are configured from the admin portal (`/portal/widget`) and
|
|
19
|
+
* resolved at load time via the publishable key — editing them from
|
|
20
|
+
* HTML is no longer supported. The legacy `data-theme` / `data-position` /
|
|
21
|
+
* `data-language` / `data-show-profile` / `data-open` attributes are
|
|
22
|
+
* silently ignored.
|
|
23
|
+
*
|
|
24
|
+
* Programmatic usage (internal dev surface — admin preview, marketing
|
|
25
|
+
* demo — not recommended for partner integrations):
|
|
26
|
+
* ```javascript
|
|
27
|
+
* import { createWidget } from '@dolard.eu/versiq-widget';
|
|
28
|
+
*
|
|
29
|
+
* const widget = createWidget({ apiKey: 'pk_live_xxx' });
|
|
30
|
+
* widget.on('qualified', (data) => sendToAnalytics(data));
|
|
31
|
+
* ```
|
|
32
|
+
*
|
|
33
|
+
* Programmatic `theme` / `position` / ... overrides are preserved for the
|
|
34
|
+
* admin sandbox's live-preview use case (client-wins merge). Partner
|
|
35
|
+
* integrators should configure those fields from the admin portal.
|
|
36
|
+
*/
|
|
37
|
+
import { VersiqWidget, createWidget } from "./loader";
|
|
38
|
+
import type { WidgetConfig } from "./types";
|
|
39
|
+
export { createWidget, VersiqWidget };
|
|
40
|
+
export { parseScriptAttributes as __parseScriptAttributes };
|
|
41
|
+
export { getCurrentScript as __getCurrentScript };
|
|
42
|
+
export { autoInit as __autoInit };
|
|
43
|
+
export { themeConfigSchema, widgetPositionSchema, widgetConfigSchema, b2bProfileSchema, widgetMessageSchema, applicationConfigResponseSchema, } from "./types";
|
|
44
|
+
export type { WidgetConfig, WidgetPosition, ColorScheme, ThemeConfig, B2BProfile, WidgetProfile, WidgetMessage, WidgetEvent, WidgetEventType, WidgetEventHandler, VersiqAPI, ApplicationConfigResponse, } from "./types";
|
|
45
|
+
export { fetchApplicationConfig, ConfigFetchError } from "./config";
|
|
46
|
+
/**
|
|
47
|
+
* Parse script tag data attributes to extract widget config (#726).
|
|
48
|
+
*
|
|
49
|
+
* Only host-context attributes are read here — fields that cannot live
|
|
50
|
+
* server-side because they are tied to the integrator's page (DOM
|
|
51
|
+
* selectors, dev flags, HMAC-verified identity). Theme, position,
|
|
52
|
+
* language, showProfile, and the default open state are resolved from
|
|
53
|
+
* the admin portal via `fetchApplicationConfig` in `loader.ts`.
|
|
54
|
+
*/
|
|
55
|
+
declare function parseScriptAttributes(script: HTMLScriptElement): WidgetConfig | null;
|
|
56
|
+
/**
|
|
57
|
+
* Find the current script tag.
|
|
58
|
+
*/
|
|
59
|
+
declare function getCurrentScript(): HTMLScriptElement | null;
|
|
60
|
+
/**
|
|
61
|
+
* Initialize widgets from script tags with data-api-key attributes.
|
|
62
|
+
* Supports multiple widgets on the same page (multi-instance).
|
|
63
|
+
*/
|
|
64
|
+
declare function autoInit(): void;
|
package/dist/loader.d.ts
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Widget Loader
|
|
3
|
+
*
|
|
4
|
+
* Creates and manages the widget iframe, handling:
|
|
5
|
+
* - iFrame injection and positioning
|
|
6
|
+
* - postMessage communication
|
|
7
|
+
* - Public API exposure
|
|
8
|
+
*/
|
|
9
|
+
import type { WidgetConfig, WidgetEventType, WidgetEventHandler, VersiqAPI, ColorScheme, ThemeConfig } from "./types";
|
|
10
|
+
export declare function __resetInstanceCounter(): void;
|
|
11
|
+
export declare class VersiqWidget implements VersiqAPI {
|
|
12
|
+
readonly version = "0.1.0";
|
|
13
|
+
/**
|
|
14
|
+
* Original config as passed by the caller — preserved separately from
|
|
15
|
+
* `this.config` (which has defaults merged on top) so the server-config
|
|
16
|
+
* merge in `resolveConfig()` can tell "field not set by the caller,
|
|
17
|
+
* use the server value" from "caller explicitly passed a default".
|
|
18
|
+
*/
|
|
19
|
+
private originalConfig;
|
|
20
|
+
private config;
|
|
21
|
+
private iframe;
|
|
22
|
+
private container;
|
|
23
|
+
private skeletonButton;
|
|
24
|
+
private isOpen;
|
|
25
|
+
private isReady;
|
|
26
|
+
private isInitialized;
|
|
27
|
+
private isDestroyed;
|
|
28
|
+
private fetchAbortController;
|
|
29
|
+
private _applicationId;
|
|
30
|
+
private vertical;
|
|
31
|
+
private instanceId;
|
|
32
|
+
private pendingCommands;
|
|
33
|
+
private eventHandlers;
|
|
34
|
+
private cleanupListener;
|
|
35
|
+
private debugLog;
|
|
36
|
+
private currentViewportMode;
|
|
37
|
+
private mobileMediaQuery;
|
|
38
|
+
private tabletMediaQuery;
|
|
39
|
+
private debounceTimer;
|
|
40
|
+
private viewportChangeHandler;
|
|
41
|
+
get applicationId(): string;
|
|
42
|
+
constructor(config: WidgetConfig);
|
|
43
|
+
/**
|
|
44
|
+
* Minimal placeholder launcher rendered synchronously before the server
|
|
45
|
+
* config arrives. Uses the default position (bottom-right) unless the
|
|
46
|
+
* caller explicitly provided one. Replaced by the real iframe in
|
|
47
|
+
* `init()` after `resolveConfig()` succeeds.
|
|
48
|
+
*/
|
|
49
|
+
private renderSkeleton;
|
|
50
|
+
/**
|
|
51
|
+
* Resolve the widget runtime config from the server (#726). Retries once
|
|
52
|
+
* with a 1s backoff on *transient* failures (network, timeout, 5xx) —
|
|
53
|
+
* 4xx and parse errors are permanent config/setup problems that a
|
|
54
|
+
* retry cannot fix, so we surface them immediately. If both attempts
|
|
55
|
+
* fail, the skeleton stays visible and the widget emits `error` —
|
|
56
|
+
* visible degraded mode rather than a silently absent widget.
|
|
57
|
+
*/
|
|
58
|
+
private resolveConfig;
|
|
59
|
+
private isTransientFetchError;
|
|
60
|
+
/**
|
|
61
|
+
* Merge the server-resolved application config into the widget state
|
|
62
|
+
* and promote the skeleton to a fully-initialised iframe.
|
|
63
|
+
*
|
|
64
|
+
* Merge semantics (#726):
|
|
65
|
+
* 1. Fields explicitly passed to `createWidget(...)` win (`originalConfig`).
|
|
66
|
+
* This keeps the admin sandbox's live preview working: unsaved edits
|
|
67
|
+
* stay visible on the preview widget.
|
|
68
|
+
* 2. Otherwise the server's `widget` block (from the publishable key
|
|
69
|
+
* lookup) provides theme/position/language/showProfile/open.
|
|
70
|
+
* 3. Legacy `server.theme` is still merged underneath `server.widget.theme`
|
|
71
|
+
* for backward compatibility with pre-#726 data shapes — it will
|
|
72
|
+
* be removed once all consumers land the new contract.
|
|
73
|
+
*/
|
|
74
|
+
private applyServerConfig;
|
|
75
|
+
private flushPendingCommands;
|
|
76
|
+
private init;
|
|
77
|
+
private createContainer;
|
|
78
|
+
private createIframe;
|
|
79
|
+
private buildIframeUrl;
|
|
80
|
+
private setupViewportListener;
|
|
81
|
+
private handleViewportChange;
|
|
82
|
+
private getViewportMode;
|
|
83
|
+
private updateContainerForViewport;
|
|
84
|
+
private handleMessage;
|
|
85
|
+
private sendConfig;
|
|
86
|
+
private updateVisibility;
|
|
87
|
+
private emit;
|
|
88
|
+
open(): void;
|
|
89
|
+
close(): void;
|
|
90
|
+
reset(): void;
|
|
91
|
+
identify(params: {
|
|
92
|
+
email: string;
|
|
93
|
+
userId?: string;
|
|
94
|
+
userHash: string;
|
|
95
|
+
}): void;
|
|
96
|
+
setTheme(theme: ThemeConfig): void;
|
|
97
|
+
setColorScheme(scheme: ColorScheme): void;
|
|
98
|
+
on<T = unknown>(event: WidgetEventType, handler: WidgetEventHandler<T>): void;
|
|
99
|
+
off<T = unknown>(event: WidgetEventType, handler: WidgetEventHandler<T>): void;
|
|
100
|
+
destroy(): void;
|
|
101
|
+
private log;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Create a new widget instance.
|
|
105
|
+
*/
|
|
106
|
+
export declare function createWidget(config: WidgetConfig): VersiqWidget;
|
package/dist/logger.d.ts
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Widget Logger
|
|
3
|
+
*
|
|
4
|
+
* Lightweight logger for the widget SDK with consistent prefix.
|
|
5
|
+
* Provides a centralized logging interface for debug, info, warn, and error levels.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Log an error message.
|
|
9
|
+
*/
|
|
10
|
+
export declare function logError(msg: string, ...args: unknown[]): void;
|
|
11
|
+
/**
|
|
12
|
+
* Log a warning message.
|
|
13
|
+
*/
|
|
14
|
+
export declare function logWarn(msg: string, ...args: unknown[]): void;
|
|
15
|
+
/**
|
|
16
|
+
* Log an info message.
|
|
17
|
+
*/
|
|
18
|
+
export declare function logInfo(msg: string, ...args: unknown[]): void;
|
|
19
|
+
/**
|
|
20
|
+
* Log a debug message.
|
|
21
|
+
*/
|
|
22
|
+
export declare function logDebug(msg: string, ...args: unknown[]): void;
|
|
23
|
+
/**
|
|
24
|
+
* Create a conditional debug logger based on debug flag.
|
|
25
|
+
*/
|
|
26
|
+
export declare function createDebugLogger(enabled: boolean): typeof logDebug;
|