@magicfeedback/popup-sdk 1.0.10 → 1.0.12

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 CHANGED
@@ -1,8 +1,16 @@
1
1
  # MagicFeedback Popup SDK
2
2
 
3
- TypeScript SDK to render and manage survey popups for MagicFeedback.
3
+ TypeScript SDK for loading and displaying MagicFeedback surveys as popups in browser apps.
4
4
 
5
- > Current package name: `@magicfeedback/popup-sdk`
5
+ Package name: `@magicfeedback/popup-sdk`
6
+
7
+ ## What It Does
8
+
9
+ - Loads popup definitions from the Deepdots API (`server` mode) or from inline definitions (`client` mode)
10
+ - Triggers popups by time on page, scroll depth, click, route exit, or host-driven events
11
+ - Emits lifecycle events so the host app can track popup activity
12
+ - Supports route targeting through `segments.path`
13
+ - Falls back to a no-op renderer outside the browser
6
14
 
7
15
  ## Installation
8
16
 
@@ -10,187 +18,300 @@ TypeScript SDK to render and manage survey popups for MagicFeedback.
10
18
  npm install @magicfeedback/popup-sdk
11
19
  ```
12
20
 
13
- ## Quick Start
21
+ ## Recommended Setup
22
+
23
+ `server` mode is the recommended setup for real client integrations. Popup definitions are managed remotely and the SDK fetches them at runtime.
14
24
 
15
- ```typescript
16
- import {DeepdotsPopups} from '@magicfeedback/popup-sdk';
25
+ ```ts
26
+ import { DeepdotsPopups } from '@magicfeedback/popup-sdk';
17
27
 
18
- // Basic initialization
19
28
  const popups = new DeepdotsPopups();
29
+
20
30
  popups.init({
21
- debug: true, // enable verbose logging
22
- mode: 'client', // 'client' (preloaded definitions) | 'server' (fake async load for now)
23
- popups: [ // optional in client mode
24
- {
25
- id: 'popup-123',
26
- title: 'Help us improve?',
27
- message: '<p>Take a quick survey and share your thoughts.</p>',
28
- trigger: {type: 'time_on_page', value: 5, condition: [{answered: false, cooldownDays: 7}]},
29
- actions: {
30
- accept: {label: 'Take Survey', surveyId: 'survey-abc'},
31
- decline: {label: 'Not Now', cooldownDays: 7}
32
- },
33
- surveyId: 'survey-abc',
34
- productId: 'product-xyz',
35
- style: {theme: 'light', position: 'bottom-right', imageUrl: null},
36
- segments: {lang: ['en'], path: ['/checkout']}
37
- }
38
- ]
31
+ mode: 'server',
32
+ nodeEnv: 'production',
33
+ apiKey: 'YOUR_PUBLIC_API_KEY',
34
+ userId: 'customer-123',
35
+ debug: false,
39
36
  });
40
37
 
41
- // Show a popup immediately
42
- popups.show({
43
- surveyId: 'survey-abc',
44
- productId: 'product-xyz',
45
- data: {source: 'homepage'}
38
+ popups.on('popup_shown', (event) => {
39
+ console.log('Popup shown', event);
46
40
  });
47
41
 
48
- // Listen to events
49
- popups.on('popup_shown', (ev) => console.log('Shown:', ev));
50
- popups.on('popup_clicked', (ev) => console.log('Clicked:', ev));
51
- popups.on('survey_completed', (ev) => console.log('Completed:', ev));
42
+ popups.on('survey_completed', (event) => {
43
+ console.log('Survey completed', event);
44
+ });
45
+
46
+ popups.autoLaunch();
52
47
  ```
53
48
 
54
- ## Features
49
+ Notes:
50
+
51
+ - `mode: 'server'` fetches popup definitions from Deepdots.
52
+ - `nodeEnv: 'production'` uses `https://api.deepdots.com`.
53
+ - `nodeEnv: 'development'` uses `https://api-dev.deepdots.com`.
54
+ - `userId` is optional, but recommended when popups are targeted per user.
55
+ - `autoLaunch()` can be called immediately after `init()`. In `server` mode, the SDK waits until remote popups are loaded.
56
+
57
+ ## When To Use Each Mode
55
58
 
56
- - ✅ Simple API: initialize and show popups with minimal code
57
- - ✅ Auto-launch triggers: time, scroll, exit intent
58
- - ✅ Automatic trigger derivation from popup definitions (`time_on_page` → internal `time` in ms)
59
- - ✅ Conditions & cooldowns: prevent display if already answered or within cooldown window
60
- - ✅ Event system: `popup_shown`, `popup_clicked`, `survey_completed`
61
- - ✅ Renderer abstraction: browser, React Native stub, SSR-friendly no-op
62
- - ✅ TypeScript: full type definitions
63
- - ✅ Lightweight: zero runtime dependencies for core logic
64
- - ✅ Framework agnostic: works in vanilla JS or any framework
59
+ - `server`: production integrations where popup definitions are managed remotely.
60
+ - `client`: local demos, QA, hardcoded fallback flows, and integration testing without API calls.
65
61
 
66
- ## API Reference
62
+ ## Real Use Cases From `examples/`
63
+
64
+ ### 1. Remote website integration
65
+
66
+ Files:
67
+
68
+ - `examples/index.html`
69
+ - `examples/product.html`
70
+ - `examples/demo-sdk.js`
71
+
72
+ This flow initializes the SDK in `server` mode, auto-launches popups on page load, and logs popup events in the page UI. It is the closest example to a standard website integration.
73
+
74
+ ### 2. Host-driven business event trigger
75
+
76
+ Files:
77
+
78
+ - `examples/clients/casino/sdk.js`
79
+ - `examples/clients/casino/sdk-trigger-event-example.js`
80
+
81
+ This flow tracks search behavior in the host app and calls:
82
+
83
+ ```ts
84
+ sdk.triggerEvent('search');
85
+ ```
67
86
 
68
- ### Class: `DeepdotsPopups`
69
- Main class for configuring and displaying survey popups.
87
+ Use this pattern when popup logic depends on product behavior instead of plain DOM triggers. In the casino demo, the popup is shown after repeated searches with low intent signals.
70
88
 
71
- #### `init(config: DeepdotsInitParams): void`
72
- Initializes the SDK. Must be called before other methods.
89
+ ### 3. Route-exit popup after navigation
90
+
91
+ Files:
92
+
93
+ - `examples/index.html`
94
+ - `examples/product.html`
95
+
96
+ Exit triggers are queued when the user leaves a matching route and rendered on the next route after the configured delay. This is useful for "before you go" feedback flows that should appear after navigation, not before it.
97
+
98
+ ### 4. Inline client-mode sandbox
99
+
100
+ File:
101
+
102
+ - `examples/demo.html`
103
+
104
+ This example uses local popup definitions and is useful for validating trigger behavior without hitting the API.
105
+
106
+ ## Client Mode Example
107
+
108
+ Use `client` mode when you want to preload popup definitions yourself.
109
+
110
+ ```ts
111
+ import { DeepdotsPopups } from '@magicfeedback/popup-sdk';
112
+
113
+ const popupDefinitions = [
114
+ {
115
+ id: 'popup-home-5s',
116
+ title: 'Help us improve',
117
+ message: '<p>Thanks for visiting our homepage.</p>',
118
+ triggers: [
119
+ { type: 'time_on_page', value: 5 },
120
+ ],
121
+ cooldown: [
122
+ { answered: 'SHOWED', cooldownDays: 7 },
123
+ { answered: 'COMPLETED', cooldownDays: 30 },
124
+ ],
125
+ actions: {
126
+ accept: {
127
+ label: 'Open survey',
128
+ surveyId: 'survey-home-001',
129
+ },
130
+ },
131
+ surveyId: 'survey-home-001',
132
+ productId: 'product-main',
133
+ segments: {
134
+ path: ['/', '/pricing', '/#/home'],
135
+ },
136
+ },
137
+ ];
138
+
139
+ const popups = new DeepdotsPopups();
73
140
 
74
- ```typescript
75
141
  popups.init({
76
- apiKey: 'your-key', // optional
77
- nodeEnv: 'production', // optional: influences internal baseUrl
78
- mode: 'client', // 'client' or 'server'
79
- debug: false, // verbose logging
80
- popups: [ /* PopupDefinition[] (client mode only) */ ]
142
+ mode: 'client',
143
+ debug: true,
144
+ popups: popupDefinitions,
81
145
  });
146
+
147
+ popups.autoLaunch();
82
148
  ```
83
149
 
84
- In `server` mode popups are (currently) loaded via a simulated async fetch; triggers derived after load will start when `autoLaunch()` runs or once loading completes.
150
+ Important:
85
151
 
86
- #### `show(options: ShowOptions): void`
87
- Displays a popup for a survey immediately. `productId` is required.
152
+ - The property name is `triggers`, and it is now an array.
153
+ - `time_on_page` values are defined in seconds.
154
+ - `segments.path` can contain full URLs, path fragments such as `/pricing`, or hash routes such as `/#/home`.
88
155
 
89
- ```typescript
90
- popups.show({ surveyId: 'survey-xyz', productId: 'product-123', data: { plan: 'pro' } });
156
+ ## API Essentials
157
+
158
+ ### `init(config)`
159
+
160
+ Initializes the SDK.
161
+
162
+ ```ts
163
+ popups.init({
164
+ mode: 'server',
165
+ apiKey: 'YOUR_PUBLIC_API_KEY',
166
+ userId: 'customer-123',
167
+ nodeEnv: 'production',
168
+ debug: false,
169
+ });
91
170
  ```
92
171
 
93
- #### `showByPopupId(popupId: string): void`
94
- Displays a popup using its definition `id` (internally maps to `surveyId` + `productId`).
172
+ Config fields:
173
+
174
+ - `apiKey?: string`
175
+ - `userId?: string`
176
+ - `nodeEnv?: 'development' | 'production'`
177
+ - `mode?: 'server' | 'client'`
178
+ - `debug?: boolean`
179
+ - `popups?: PopupDefinition[]`
180
+
181
+ ### `autoLaunch()`
95
182
 
96
- ```typescript
97
- popups.showByPopupId('popup-123');
183
+ Starts the triggers derived from the popup definitions loaded during `init()`.
184
+
185
+ ```ts
186
+ popups.autoLaunch();
98
187
  ```
99
188
 
100
- #### `configureTriggers(triggers: TriggerConfig[]): void`
101
- Registers manual triggers.
189
+ ### `triggerEvent(eventName)`
102
190
 
103
- ```typescript
104
- popups.configureTriggers([
105
- { type: 'time', value: 5000, surveyId: 'survey-1' },
106
- { type: 'scroll', value: 60, surveyId: 'survey-2' },
107
- { type: 'exit', surveyId: 'survey-3' }
108
- ]);
191
+ Shows the first eligible popup whose definition contains:
192
+
193
+ ```ts
194
+ triggers: [{ type: 'event', value: eventName }]
109
195
  ```
110
196
 
111
- #### `autoLaunch(): void`
112
- Starts automatic trigger evaluation. If popups are not yet loaded (server mode) call is deferred until load completes.
197
+ Example:
113
198
 
114
- ```typescript
115
- popups.autoLaunch();
199
+ ```ts
200
+ popups.triggerEvent('search');
116
201
  ```
117
202
 
118
- #### `triggerSurvey(surveyId: string): void`
119
- Evaluates conditions and, if allowed, shows a popup for the specified survey. Used internally by trigger handlers; you normally don’t call this directly.
203
+ ### `show({ surveyId, productId })`
120
204
 
121
- #### `markSurveyAnswered(surveyId: string): void`
122
- Marks a survey as answered to satisfy conditions like `{ answered: false }`.
205
+ Shows a popup immediately without waiting for a trigger.
123
206
 
124
- ```typescript
125
- popups.markSurveyAnswered('survey-xyz');
207
+ ```ts
208
+ popups.show({
209
+ surveyId: 'survey-home-001',
210
+ productId: 'product-main',
211
+ });
126
212
  ```
127
213
 
128
- #### `on(type, listener)` / `off(type, listener)`
129
- Add or remove event listeners.
214
+ ### `showByPopupId(popupId)`
215
+
216
+ Shows a popup definition by id. This is useful when multiple popups reuse the same `surveyId`.
130
217
 
131
- ```typescript
132
- const handler = (ev) => console.log(ev);
133
- popups.on('popup_shown', handler);
134
- popups.off('popup_shown', handler);
218
+ ```ts
219
+ popups.showByPopupId('popup-home-5s');
135
220
  ```
136
221
 
137
- #### `setRenderer(renderer: PopupRenderer): void`
138
- Inject a custom renderer (e.g. native mobile). If called after `init` and the renderer has `init()`, it's invoked automatically.
222
+ ### `markSurveyAnswered(surveyId)`
139
223
 
140
- ```typescript
141
- import { ReactNativePopupRenderer } from './my-native-renderer';
142
- const sdk = new DeepdotsPopups();
143
- sdk.setRenderer(new ReactNativePopupRenderer());
144
- sdk.init({ mode: 'client', popups: [...] });
224
+ Marks a survey as answered so cooldown rules like `{ answered: 'COMPLETED', cooldownDays: 30 }` start applying.
225
+
226
+ ```ts
227
+ popups.markSurveyAnswered('survey-home-001');
145
228
  ```
146
229
 
147
- ## Popup Definitions
230
+ ### `on(eventType, listener)` / `off(eventType, listener)`
231
+
232
+ Subscribe or unsubscribe from SDK events.
233
+
234
+ ```ts
235
+ const onShown = (event) => console.log(event);
236
+
237
+ popups.on('popup_shown', onShown);
238
+ popups.off('popup_shown', onShown);
239
+ ```
148
240
 
149
- When using `client` mode you can preload definitions. In `server` mode a fake fetch creates sample definitions. Trigger types in definitions:
150
- - `time_on_page` (seconds) → mapped automatically to internal `time` trigger (milliseconds)
151
- - `scroll` (percentage scrolled)
152
- - `exit` (exit intent)
241
+ ## Supported Trigger Types
153
242
 
154
- Structure:
243
+ Popup definitions use these trigger types:
155
244
 
156
- ```typescript
245
+ - `time_on_page`: shows after N seconds on the page
246
+ - `scroll`: shows after reaching a scroll percentage
247
+ - `click`: shows after clicking the element with the given DOM id
248
+ - `exit`: shows after leaving a matching route and waiting N seconds on the destination route
249
+ - `event`: shows when the host app calls `triggerEvent(name)`
250
+
251
+ ## Popup Definition Shape
252
+
253
+ `client` mode accepts popup definitions with this structure:
254
+
255
+ ```ts
157
256
  interface PopupDefinition {
158
257
  id: string;
159
258
  title: string;
160
- message: string; // HTML string
161
- trigger: {
162
- type: 'time_on_page' | 'scroll' | 'exit';
163
- value: number; // seconds (time_on_page) or percentage (scroll)
164
- condition?: { answered: boolean; cooldownDays: number }[];
165
- };
166
- actions: {
167
- accept: { label: string; surveyId: string };
168
- decline: { label: string; cooldownDays?: number };
259
+ message: string;
260
+ triggers: Array<{
261
+ type: 'time_on_page' | 'scroll' | 'exit' | 'click' | 'event';
262
+ value: number | string;
263
+ }>;
264
+ cooldown?: Array<{
265
+ answered: 'SHOWED' | 'PARTIAL' | 'COMPLETED';
266
+ cooldownDays: number;
267
+ }>;
268
+ actions?: {
269
+ accept?: {
270
+ label: string;
271
+ surveyId: string;
272
+ };
273
+ start?: {
274
+ label: string;
275
+ };
276
+ back?: {
277
+ label: string;
278
+ };
279
+ complete?: {
280
+ label: string;
281
+ surveyId: string;
282
+ autoCompleteParams: Record<string, unknown>;
283
+ cooldownDays?: number;
284
+ };
285
+ decline?: {
286
+ label: string;
287
+ cooldownDays?: number;
288
+ };
169
289
  };
170
290
  surveyId: string;
171
291
  productId: string;
172
- style: {
292
+ style?: {
173
293
  theme: 'light' | 'dark';
174
294
  position: 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left' | 'center';
175
295
  imageUrl: string | null;
176
296
  };
177
- segments?: { lang?: string[]; path?: string[]; [key: string]: unknown };
297
+ segments?: {
298
+ path?: string[];
299
+ [key: string]: unknown;
300
+ };
178
301
  }
179
302
  ```
180
303
 
181
- ### Conditions
182
- - `answered: false` → show only if survey not yet completed
183
- - `cooldownDays: N` → do not show again until N days have passed since last display (per popup definition `id`)
184
-
185
304
  ## Events
186
305
 
187
- Emitted events:
306
+ The SDK emits:
307
+
188
308
  - `popup_shown`
189
- - `popup_clicked` (first interaction / initial rendering)
309
+ - `popup_clicked`
190
310
  - `survey_completed`
191
311
 
192
312
  Payload:
193
- ```typescript
313
+
314
+ ```ts
194
315
  interface DeepdotsEvent {
195
316
  type: 'popup_shown' | 'popup_clicked' | 'survey_completed';
196
317
  surveyId: string;
@@ -199,106 +320,50 @@ interface DeepdotsEvent {
199
320
  }
200
321
  ```
201
322
 
202
- ## Trigger Types (Manual Mode)
203
-
204
- ```typescript
205
- { type: 'time', value: 5000, surveyId: 'survey-1' } // after 5s
206
- { type: 'scroll', value: 50, surveyId: 'survey-2' } // at 50% scroll
207
- { type: 'exit', surveyId: 'survey-3' } // exit intent
208
- ```
209
-
210
- ## Automatic Trigger Derivation
211
- When definitions are loaded, each `time_on_page` trigger is converted to `{ type: 'time', value: seconds * 1000 }`. You just call `autoLaunch()` and derived triggers start.
323
+ `data` often contains values such as:
212
324
 
213
- ## Styling & Container
214
- Browser renderer creates a container `#deepdots-popup-container` with an overlay. Override styles via global CSS (e.g. `.deepdots-popup`, `#deepdots-popup-container`). The layout is intentionally minimal.
325
+ - `popupId`
326
+ - `action`
327
+ - `userId`
215
328
 
216
- ## Renderer Architecture
329
+ `popup_clicked` is a general interaction event. Depending on the flow, `data.action` can contain values such as `loaded`, `start_survey`, `manual_send`, `back`, `complete`, or `close_icon`.
217
330
 
218
- The SDK decouples core logic (triggers, conditions, events) from UI rendering.
331
+ ## Important Behavior Notes
219
332
 
220
- - `BrowserPopupRenderer`: default when DOM is available.
221
- - `ReactNativePopupRenderer` (stub): demonstrates how to bridge events in RN (does not render real UI here).
222
- - `NoopPopupRenderer`: used in SSR / test environments without a DOM.
333
+ - `segments.path` is the only segment currently evaluated by the SDK runtime.
334
+ - `exit` triggers work across anchor navigation, hash navigation, `history.pushState()`, and `history.replaceState()`.
335
+ - Pending `exit` popups are stored in `sessionStorage` until they are shown or discarded.
336
+ - The default browser renderer uses MagicFeedback forms and applies button labels from `actions.accept`, `actions.start`, `actions.back`, and `actions.complete`.
337
+ - `title`, `message`, and `style` belong to the popup definition contract, but the current browser renderer focuses on the embedded form and does not render those fields as standalone popup copy/layout controls.
338
+ - `actions.decline` is accepted in the definition shape, but the current browser renderer does not render a dedicated decline button or enforce `decline.cooldownDays`.
339
+ - In browser mode, the SDK creates `#deepdots-popup-container` and injects the shared popup stylesheet from jsDelivr.
223
340
 
224
- Detection for React Native uses `navigator.product === 'ReactNative'`.
341
+ ## Running The Examples Locally
225
342
 
226
- ### Creating a Native Renderer
227
- ```typescript
228
- class MyNativeRenderer implements PopupRenderer {
229
- init() { /* prepare modal/store */ }
230
- show(surveyId, productId, data, emit, onClose) {
231
- // 1. Display native modal
232
- // 2. Render equivalent UI
233
- // 3. Call emit('popup_clicked', surveyId, data) on first interaction
234
- }
235
- hide() { /* close modal */ }
236
- }
237
-
238
- const sdk = new DeepdotsPopups();
239
- sdk.setRenderer(new MyNativeRenderer());
240
- sdk.init({ mode: 'client', popups: [...] });
343
+ ```bash
344
+ npm install
345
+ npm run build
346
+ python3 -m http.server 4173
241
347
  ```
242
348
 
243
- ### WebView (Cordova / Capacitor / RN / Flutter)
244
- 1. Build the bundle: `npm run build`.
245
- 2. Load `dist/` in a WebView.
246
- 3. Bridge events: `window.ReactNativeWebView.postMessage(JSON.stringify(ev))`.
247
- 4. Initialize: `popups.init({...}); popups.autoLaunch();`.
349
+ Then open:
248
350
 
249
- Example event forwarding:
250
- ```javascript
251
- function forward(ev) {
252
- window.ReactNativeWebView?.postMessage(JSON.stringify(ev));
253
- }
254
- popups.on('popup_shown', forward);
255
- popups.on('survey_completed', forward);
256
- ```
351
+ - `http://localhost:4173/examples/index.html`
352
+ - `http://localhost:4173/examples/product.html`
353
+ - `http://localhost:4173/examples/demo.html`
354
+ - `http://localhost:4173/examples/clients/casino/index.html`
257
355
 
258
- ## Types
259
- Import types directly:
260
-
261
- ```typescript
262
- import type {
263
- DeepdotsConfig,
264
- TriggerConfig,
265
- ShowOptions,
266
- DeepdotsEvent,
267
- DeepdotsEventType,
268
- EventListener,
269
- } from '@magicfeedback/popup-sdk';
270
- ```
271
-
272
- ## Example Demo
273
- See `examples/demo.html` for a runnable browser demo (uses client mode + fake definitions). To try it:
274
- 1. Build the project (`npm run build`).
275
- 2. Open `examples/demo.html` in a browser.
276
- 3. Use buttons to show popups and observe console + event log.
356
+ Using a local HTTP server is recommended. Some browser module imports and navigation flows do not behave correctly when opening the files directly with `file://`.
277
357
 
278
358
  ## Development
279
359
 
280
360
  ```bash
281
- npm install # install dependencies
282
- npm run build # build (CJS + ESM + d.ts)
283
- npm run build:watch # watch build
284
- npm run lint # eslint
285
- npm test # run vitest
286
- npm run test:watch # watch tests
361
+ npm run build
362
+ npm run build:watch
363
+ npm run lint
364
+ npm test
287
365
  ```
288
366
 
289
- Publishing uses `prepublishOnly` to ensure a fresh build.
290
-
291
- ## Notes & Limitations
292
- - `server` mode currently simulates fetch; real network integration pending.
293
- - `apiKey` is reserved for future authenticated endpoints.
294
- - React Native renderer provided is a stub (example only).
295
- - Sanitization of `message` HTML should be handled upstream if untrusted.
296
-
297
- ## Contributing
298
- Issues & PRs welcome: https://github.com/MagicFeedback/deepdots-popup-sdk
299
-
300
367
  ## License
301
- MIT
302
368
 
303
- ## Support
304
- Open issues on the GitHub repository or contact maintainers.
369
+ MIT