@magicfeedback/popup-sdk
v1.1.0
Published
TypeScript SDK to render and manage survey popups for MagicFeedback
Downloads
296
Readme
Deepdots Popup SDK
TypeScript SDK for loading and displaying Deepdots surveys as popups in browser apps.
Package name: @magicfeedback/popup-sdk
What It Does
- Loads popup definitions from the Deepdots API (
servermode) or from inline definitions (clientmode) - Triggers popups by time on page, scroll depth, click, route exit, or host-driven events
- Emits lifecycle events so the host app can track popup activity
- Supports route targeting through
segments.path - Falls back to a no-op renderer outside the browser
Installation
npm install @magicfeedback/popup-sdkRecommended Setup
server mode is the recommended setup for real client integrations. Popup definitions are managed remotely and the SDK fetches them at runtime.
import { DeepdotsPopups } from '@magicfeedback/popup-sdk';
const popups = new DeepdotsPopups();
popups.init({
mode: 'server',
nodeEnv: 'production',
apiKey: 'YOUR_PUBLIC_API_KEY',
userId: 'customer-123',
debug: false,
});
popups.on('popup_shown', (event) => {
console.log('Popup shown', event);
});
popups.on('survey_completed', (event) => {
console.log('Survey completed', event);
});
popups.autoLaunch();Notes:
mode: 'server'fetches popup definitions from Deepdots.nodeEnv: 'production'useshttps://api.deepdots.com.nodeEnv: 'development'useshttps://api-dev.deepdots.com.userIdis optional, but recommended when popups are targeted per user.autoLaunch()can be called immediately afterinit(). Inservermode, the SDK waits until remote popups are loaded.
When To Use Each Mode
server: production integrations where popup definitions are managed remotely.client: local demos, QA, hardcoded fallback flows, and integration testing without API calls.
Real Use Cases From examples/
1. Remote website integration
Files:
examples/index.htmlexamples/product.htmlexamples/demo-sdk.js
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.
2. Host-driven business event trigger
Files:
examples/clients/casino/sdk.jsexamples/clients/casino/sdk-trigger-event-example.js
This flow tracks search behavior in the host app and calls:
sdk.triggerEvent('search');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.
3. Route-exit popup after navigation
Files:
examples/index.htmlexamples/product.html
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.
4. Inline client-mode sandbox
File:
examples/demo.html
This example uses local popup definitions and is useful for validating trigger behavior without hitting the API.
Client Mode Example
Use client mode when you want to preload popup definitions yourself.
import { DeepdotsPopups } from '@magicfeedback/popup-sdk';
const popupDefinitions = [
{
id: 'popup-home-5s',
title: 'Help us improve',
message: '<p>Thanks for visiting our homepage.</p>',
triggers: [
{ type: 'time_on_page', value: 5 },
],
cooldown: [
{ answered: 'SHOWED', cooldownDays: 7 },
{ answered: 'COMPLETED', cooldownDays: 30 },
],
actions: {
accept: {
label: 'Open survey',
surveyId: 'survey-home-001',
},
},
surveyId: 'survey-home-001',
productId: 'product-main',
segments: {
path: ['/', '/pricing', '/#/home'],
},
},
];
const popups = new DeepdotsPopups();
popups.init({
mode: 'client',
debug: true,
popups: popupDefinitions,
});
popups.autoLaunch();Important:
- The property name is
triggers, and it is now an array. time_on_pagevalues are defined in seconds.segments.pathcan contain full URLs, path fragments such as/pricing, or hash routes such as/#/home.
API Essentials
init(config)
Initializes the SDK.
popups.init({
mode: 'server',
apiKey: 'YOUR_PUBLIC_API_KEY',
userId: 'customer-123',
nodeEnv: 'production',
debug: false,
});Config fields:
apiKey?: stringuserId?: stringnodeEnv?: 'development' | 'production'mode?: 'server' | 'client'debug?: booleanpopups?: PopupDefinition[]
autoLaunch()
Starts the triggers derived from the popup definitions loaded during init().
popups.autoLaunch();triggerEvent(eventName)
Shows the first eligible popup whose definition contains:
triggers: [{ type: 'event', value: eventName }]Example:
popups.triggerEvent('search');show({ surveyId, productId })
Shows a popup immediately without waiting for a trigger.
popups.show({
surveyId: 'survey-home-001',
productId: 'product-main',
});showByPopupId(popupId)
Shows a popup definition by id. This is useful when multiple popups reuse the same surveyId.
popups.showByPopupId('popup-home-5s');markSurveyAnswered(surveyId)
Marks a survey as answered so cooldown rules like { answered: 'COMPLETED', cooldownDays: 30 } start applying.
popups.markSurveyAnswered('survey-home-001');on(eventType, listener) / off(eventType, listener)
Subscribe or unsubscribe from SDK events.
const onShown = (event) => console.log(event);
popups.on('popup_shown', onShown);
popups.off('popup_shown', onShown);Supported Trigger Types
Popup definitions use these trigger types:
time_on_page: shows after N seconds on the pagescroll: shows after reaching a scroll percentageclick: shows after clicking the element with the given DOM idexit: shows after leaving a matching route and waiting N seconds on the destination routeevent: shows when the host app callstriggerEvent(name)
Popup Definition Shape
client mode accepts popup definitions with this structure:
interface PopupDefinition {
id: string;
title: string;
message: string;
triggers: Array<{
type: 'time_on_page' | 'scroll' | 'exit' | 'click' | 'event';
value: number | string;
}>;
cooldown?: Array<{
answered: 'SHOWED' | 'PARTIAL' | 'COMPLETED';
cooldownDays: number;
}>;
actions?: {
accept?: {
label: string;
surveyId: string;
};
start?: {
label: string;
};
back?: {
label: string;
};
complete?: {
label: string;
surveyId: string;
autoCompleteParams: Record<string, unknown>;
cooldownDays?: number;
};
decline?: {
label: string;
cooldownDays?: number;
};
};
surveyId: string;
productId: string;
style?: {
theme: 'light' | 'dark';
position: 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left' | 'center';
imageUrl: string | null;
};
segments?: {
path?: string[];
[key: string]: unknown;
};
}Events
The SDK emits:
popup_shownpopup_clickedsurvey_completed
Payload:
interface DeepdotsEvent {
type: 'popup_shown' | 'popup_clicked' | 'survey_completed';
surveyId: string;
timestamp: number;
data?: Record<string, unknown>;
}data often contains values such as:
popupIdactionuserId
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.
Important Behavior Notes
segments.pathis the only segment currently evaluated by the SDK runtime.exittriggers work across anchor navigation, hash navigation,history.pushState(), andhistory.replaceState().- Pending
exitpopups are stored insessionStorageuntil they are shown or discarded. - The default browser renderer uses Deepdots forms and applies button labels from
actions.accept,actions.start,actions.back, andactions.complete. title,message, andstylebelong 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.actions.declineis accepted in the definition shape, but the current browser renderer does not render a dedicated decline button or enforcedecline.cooldownDays.- In browser mode, the SDK creates
#deepdots-popup-containerand injects the shared popup stylesheet from jsDelivr.
Running The Examples Locally
npm install
npm run build
python3 -m http.server 4173Then open:
http://localhost:4173/examples/index.htmlhttp://localhost:4173/examples/product.htmlhttp://localhost:4173/examples/demo.htmlhttp://localhost:4173/examples/clients/casino/index.html
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://.
Development
npm run build
npm run build:watch
npm run lint
npm testLicense
MIT
