@magicfeedback/popup-sdk 1.0.11 → 1.0.13

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
- # MagicFeedback Popup SDK
1
+ # Deepdots Popup SDK
2
2
 
3
- TypeScript SDK to render and manage survey popups for MagicFeedback.
3
+ TypeScript SDK for loading and displaying Deepdots 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,185 +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
- triggers: {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,
36
+ });
37
+
38
+ popups.on('popup_shown', (event) => {
39
+ console.log('Popup shown', event);
40
+ });
41
+
42
+ popups.on('survey_completed', (event) => {
43
+ console.log('Survey completed', event);
39
44
  });
40
45
 
41
- // Start triggers
42
46
  popups.autoLaunch();
47
+ ```
48
+
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
58
+
59
+ - `server`: production integrations where popup definitions are managed remotely.
60
+ - `client`: local demos, QA, hardcoded fallback flows, and integration testing without API calls.
61
+
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
+ ```
86
+
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.
88
+
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();
43
140
 
44
- // Listen to events
45
- popups.on('popup_shown', (ev) => console.log('Shown:', ev));
46
- popups.on('popup_clicked', (ev) => console.log('Clicked:', ev));
47
- popups.on('survey_completed', (ev) => console.log('Completed:', ev));
141
+ popups.init({
142
+ mode: 'client',
143
+ debug: true,
144
+ popups: popupDefinitions,
145
+ });
146
+
147
+ popups.autoLaunch();
48
148
  ```
49
149
 
50
- ## Features
150
+ Important:
51
151
 
52
- - ✅ Simple API: initialize and launch popups with minimal code
53
- - ✅ Auto-launch triggers: time, scroll, exit intent, click
54
- - ✅ Automatic trigger derivation from popup definitions (`time_on_page` → internal `time` in ms)
55
- - ✅ Conditions & cooldowns: prevent display if already answered or within cooldown window
56
- - ✅ Event system: `popup_shown`, `popup_clicked`, `survey_completed`
57
- - ✅ Renderer abstraction: browser, React Native stub, SSR-friendly no-op
58
- - ✅ TypeScript: full type definitions
59
- - ✅ Lightweight: zero runtime dependencies for core logic
60
- - ✅ Framework agnostic: works in vanilla JS or any framework
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`.
61
155
 
62
- ## API Reference
156
+ ## API Essentials
63
157
 
64
- ### Class: `DeepdotsPopups`
65
- Main class for configuring and displaying survey popups.
158
+ ### `init(config)`
66
159
 
67
- #### `init(config: DeepdotsInitParams): void`
68
- Initializes the SDK. Must be called before other methods.
160
+ Initializes the SDK.
69
161
 
70
- ```typescript
162
+ ```ts
71
163
  popups.init({
72
- apiKey: 'your-key', // optional
73
- nodeEnv: 'production', // optional: influences internal baseUrl
74
- mode: 'client', // 'client' or 'server'
75
- debug: false, // verbose logging
76
- popups: [ /* PopupDefinition[] (client mode only) */ ]
164
+ mode: 'server',
165
+ apiKey: 'YOUR_PUBLIC_API_KEY',
166
+ userId: 'customer-123',
167
+ nodeEnv: 'production',
168
+ debug: false,
77
169
  });
78
170
  ```
79
171
 
80
- 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.
172
+ Config fields:
81
173
 
82
- #### `show(options: ShowOptions): void`
83
- Displays a popup for a survey immediately. `productId` is required.
174
+ - `apiKey?: string`
175
+ - `userId?: string`
176
+ - `nodeEnv?: 'development' | 'production'`
177
+ - `mode?: 'server' | 'client'`
178
+ - `debug?: boolean`
179
+ - `popups?: PopupDefinition[]`
84
180
 
85
- ```typescript
86
- popups.show({ surveyId: 'survey-xyz', productId: 'product-123', data: { plan: 'pro' } });
181
+ ### `autoLaunch()`
182
+
183
+ Starts the triggers derived from the popup definitions loaded during `init()`.
184
+
185
+ ```ts
186
+ popups.autoLaunch();
87
187
  ```
88
188
 
89
- #### `showByPopupId(popupId: string): void`
90
- Displays a popup using its definition `id` (internally maps to `surveyId` + `productId`).
189
+ ### `triggerEvent(eventName)`
91
190
 
92
- ```typescript
93
- popups.showByPopupId('popup-123');
191
+ Shows the first eligible popup whose definition contains:
192
+
193
+ ```ts
194
+ triggers: [{ type: 'event', value: eventName }]
94
195
  ```
95
196
 
96
- #### `configureTriggers(triggers: TriggerConfig[]): void`
97
- Registers manual triggers. Most apps should use `autoLaunch()` and popup definitions instead.
197
+ Example:
98
198
 
99
- ```typescript
100
- popups.configureTriggers([
101
- { type: 'time', value: 5000, surveyId: 'survey-1' },
102
- { type: 'scroll', value: 60, surveyId: 'survey-2' },
103
- { type: 'exit', surveyId: 'survey-3' },
104
- { type: 'click', value: 'cta-button', surveyId: 'survey-4' }
105
- ]);
199
+ ```ts
200
+ popups.triggerEvent('search');
106
201
  ```
107
202
 
108
- #### `autoLaunch(): void`
109
- Starts automatic trigger evaluation. If popups are not yet loaded (server mode) call is deferred until load completes.
203
+ ### `show({ surveyId, productId })`
110
204
 
111
- ```typescript
112
- popups.autoLaunch();
205
+ Shows a popup immediately without waiting for a trigger.
206
+
207
+ ```ts
208
+ popups.show({
209
+ surveyId: 'survey-home-001',
210
+ productId: 'product-main',
211
+ });
113
212
  ```
114
213
 
115
- #### `triggerSurvey(surveyId: string): void`
116
- Evaluates conditions and, if allowed, shows a popup for the specified survey. Used internally by trigger handlers; you normally don’t call this directly.
214
+ ### `showByPopupId(popupId)`
117
215
 
118
- #### `markSurveyAnswered(surveyId: string): void`
119
- Marks a survey as answered to satisfy conditions like `{ answered: false }`.
216
+ Shows a popup definition by id. This is useful when multiple popups reuse the same `surveyId`.
120
217
 
121
- ```typescript
122
- popups.markSurveyAnswered('survey-xyz');
218
+ ```ts
219
+ popups.showByPopupId('popup-home-5s');
123
220
  ```
124
221
 
125
- #### `on(type, listener)` / `off(type, listener)`
126
- Add or remove event listeners.
222
+ ### `markSurveyAnswered(surveyId)`
223
+
224
+ Marks a survey as answered so cooldown rules like `{ answered: 'COMPLETED', cooldownDays: 30 }` start applying.
127
225
 
128
- ```typescript
129
- const handler = (ev) => console.log(ev);
130
- popups.on('popup_shown', handler);
131
- popups.off('popup_shown', handler);
226
+ ```ts
227
+ popups.markSurveyAnswered('survey-home-001');
132
228
  ```
133
229
 
134
- #### `setRenderer(renderer: PopupRenderer): void`
135
- Inject a custom renderer (e.g. native mobile). If called after `init` and the renderer has `init()`, it's invoked automatically.
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);
136
236
 
137
- ```typescript
138
- import { ReactNativePopupRenderer } from './my-native-renderer';
139
- const sdk = new DeepdotsPopups();
140
- sdk.setRenderer(new ReactNativePopupRenderer());
141
- sdk.init({ mode: 'client', popups: [...] });
237
+ popups.on('popup_shown', onShown);
238
+ popups.off('popup_shown', onShown);
142
239
  ```
143
240
 
144
- ## Popup Definitions
241
+ ## Supported Trigger Types
145
242
 
146
- When using `client` mode you can preload definitions. In `server` mode a fake fetch creates sample definitions. Trigger types in definitions:
147
- - `time_on_page` (seconds) → mapped automatically to internal `time` trigger (milliseconds)
148
- - `scroll` (percentage scrolled)
149
- - `exit` (route/navigation exit intent)
150
- - `click` (element id, string)
243
+ Popup definitions use these trigger types:
151
244
 
152
- Structure:
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)`
153
250
 
154
- ```typescript
251
+ ## Popup Definition Shape
252
+
253
+ `client` mode accepts popup definitions with this structure:
254
+
255
+ ```ts
155
256
  interface PopupDefinition {
156
257
  id: string;
157
258
  title: string;
158
- message: string; // HTML string
159
- triggers: {
160
- type: 'time_on_page' | 'scroll' | 'exit' | 'click';
161
- value: number | string; // seconds, percentage, or element id for click
162
- condition?: { answered: boolean; cooldownDays: number }[];
163
- };
164
- actions: {
165
- accept: { label: string; surveyId: string };
166
- 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
+ };
167
289
  };
168
290
  surveyId: string;
169
291
  productId: string;
170
- style: {
292
+ style?: {
171
293
  theme: 'light' | 'dark';
172
294
  position: 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left' | 'center';
173
295
  imageUrl: string | null;
174
296
  };
175
- segments?: { lang?: string[]; path?: string[]; [key: string]: unknown };
297
+ segments?: {
298
+ path?: string[];
299
+ [key: string]: unknown;
300
+ };
176
301
  }
177
302
  ```
178
303
 
179
- ### Conditions
180
- - `answered: false` → show only if survey not yet completed
181
- - `cooldownDays: N` → do not show again until N days have passed since last display (per popup definition `id`)
182
-
183
304
  ## Events
184
305
 
185
- Emitted events:
306
+ The SDK emits:
307
+
186
308
  - `popup_shown`
187
- - `popup_clicked` (first interaction / initial rendering)
309
+ - `popup_clicked`
188
310
  - `survey_completed`
189
311
 
190
312
  Payload:
191
- ```typescript
313
+
314
+ ```ts
192
315
  interface DeepdotsEvent {
193
316
  type: 'popup_shown' | 'popup_clicked' | 'survey_completed';
194
317
  surveyId: string;
@@ -197,111 +320,50 @@ interface DeepdotsEvent {
197
320
  }
198
321
  ```
199
322
 
200
- ## Trigger Types (Manual Mode)
201
-
202
- ```typescript
203
- { type: 'time', value: 5000, surveyId: 'survey-1' } // after 5s
204
- { type: 'scroll', value: 50, surveyId: 'survey-2' } // at 50% scroll
205
- { type: 'exit', surveyId: 'survey-3' } // navigation exit intent
206
- { type: 'click', value: 'cta-button', surveyId: 'survey-4' } // click on element id
207
- ```
208
-
209
- ## Automatic Trigger Derivation
210
- 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.
211
-
212
- ## Trigger Behavior Notes
213
- - `exit` fires on navigation intent (link clicks or SPA route changes). It does not depend on mouse leaving the viewport.
214
- - `click` expects a DOM element id in `value`. The handler attaches on `DOMContentLoaded` if the element isn't available immediately.
323
+ `data` often contains values such as:
215
324
 
216
- ## Styling & Container
217
- 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`
218
328
 
219
- ## 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`.
220
330
 
221
- The SDK decouples core logic (triggers, conditions, events) from UI rendering.
331
+ ## Important Behavior Notes
222
332
 
223
- - `BrowserPopupRenderer`: default when DOM is available.
224
- - `ReactNativePopupRenderer` (stub): demonstrates how to bridge events in RN (does not render real UI here).
225
- - `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 Deepdots 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.
226
340
 
227
- Detection for React Native uses `navigator.product === 'ReactNative'`.
341
+ ## Running The Examples Locally
228
342
 
229
- ### Creating a Native Renderer
230
- ```typescript
231
- class MyNativeRenderer implements PopupRenderer {
232
- init() { /* prepare modal/store */ }
233
- show(surveyId, productId, data, emit, onClose) {
234
- // 1. Display native modal
235
- // 2. Render equivalent UI
236
- // 3. Call emit('popup_clicked', surveyId, data) on first interaction
237
- }
238
- hide() { /* close modal */ }
239
- }
240
-
241
- const sdk = new DeepdotsPopups();
242
- sdk.setRenderer(new MyNativeRenderer());
243
- sdk.init({ mode: 'client', popups: [...] });
343
+ ```bash
344
+ npm install
345
+ npm run build
346
+ python3 -m http.server 4173
244
347
  ```
245
348
 
246
- ### WebView (Cordova / Capacitor / RN / Flutter)
247
- 1. Build the bundle: `npm run build`.
248
- 2. Load `dist/` in a WebView.
249
- 3. Bridge events: `window.ReactNativeWebView.postMessage(JSON.stringify(ev))`.
250
- 4. Initialize: `popups.init({...}); popups.autoLaunch();`.
349
+ Then open:
251
350
 
252
- Example event forwarding:
253
- ```javascript
254
- function forward(ev) {
255
- window.ReactNativeWebView?.postMessage(JSON.stringify(ev));
256
- }
257
- popups.on('popup_shown', forward);
258
- popups.on('survey_completed', forward);
259
- ```
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`
260
355
 
261
- ## Types
262
- Import types directly:
263
-
264
- ```typescript
265
- import type {
266
- DeepdotsConfig,
267
- TriggerConfig,
268
- ShowOptions,
269
- DeepdotsEvent,
270
- DeepdotsEventType,
271
- EventListener,
272
- } from '@magicfeedback/popup-sdk';
273
- ```
274
-
275
- ## Example Demo
276
- See `examples/demo.html` for a runnable browser demo (uses client mode + fake definitions). To try it:
277
- 1. Build the project (`npm run build`).
278
- 2. Open `examples/demo.html` in a browser.
279
- 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://`.
280
357
 
281
358
  ## Development
282
359
 
283
360
  ```bash
284
- npm install # install dependencies
285
- npm run build # build (CJS + ESM + d.ts)
286
- npm run build:watch # watch build
287
- npm run lint # eslint
288
- npm test # run vitest
289
- npm run test:watch # watch tests
361
+ npm run build
362
+ npm run build:watch
363
+ npm run lint
364
+ npm test
290
365
  ```
291
366
 
292
- Publishing uses `prepublishOnly` to ensure a fresh build.
293
-
294
- ## Notes & Limitations
295
- - `server` mode currently simulates fetch; real network integration pending.
296
- - `apiKey` is reserved for future authenticated endpoints.
297
- - React Native renderer provided is a stub (example only).
298
- - Sanitization of `message` HTML should be handled upstream if untrusted.
299
-
300
- ## Contributing
301
- Issues & PRs welcome: https://github.com/MagicFeedback/deepdots-popup-sdk
302
-
303
367
  ## License
304
- MIT
305
368
 
306
- ## Support
307
- Open issues on the GitHub repository or contact maintainers.
369
+ MIT