@magicfeedback/popup-sdk 1.0.15 → 1.1.1
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 +0 -19
- package/dist/chunk-5VVCJBND.mjs +140 -0
- package/dist/chunk-5VVCJBND.mjs.map +1 -0
- package/dist/chunk-M6PEFLLQ.mjs +119 -0
- package/dist/chunk-M6PEFLLQ.mjs.map +1 -0
- package/dist/index.d.mts +400 -40
- package/dist/index.d.ts +400 -40
- package/dist/index.js +907 -108
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +762 -3413
- package/dist/index.mjs.map +1 -1
- package/dist/react-native.d.mts +470 -0
- package/dist/react-native.d.ts +470 -0
- package/dist/react-native.js +3202 -0
- package/dist/react-native.js.map +1 -0
- package/dist/react-native.mjs +1675 -0
- package/dist/react-native.mjs.map +1 -0
- package/dist/renderPopup-7GGKR5BY.mjs +1376 -0
- package/dist/renderPopup-7GGKR5BY.mjs.map +1 -0
- package/dist/renderPopup-Y4WG3575.mjs +3303 -0
- package/dist/renderPopup-Y4WG3575.mjs.map +1 -0
- package/package.json +36 -2
package/dist/index.js
CHANGED
|
@@ -5,6 +5,9 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
|
5
5
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
6
|
var __getProtoOf = Object.getPrototypeOf;
|
|
7
7
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __esm = (fn, res) => function __init() {
|
|
9
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
10
|
+
};
|
|
8
11
|
var __commonJS = (cb, mod) => function __require() {
|
|
9
12
|
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
|
|
10
13
|
};
|
|
@@ -30,6 +33,116 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
30
33
|
));
|
|
31
34
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
32
35
|
|
|
36
|
+
// src/tracking/tracking-manager.ts
|
|
37
|
+
function createDefaultStorage() {
|
|
38
|
+
try {
|
|
39
|
+
if (typeof window !== "undefined" && window.localStorage) {
|
|
40
|
+
const ls = window.localStorage;
|
|
41
|
+
const probe = "__deepdots_probe__";
|
|
42
|
+
ls.setItem(probe, "1");
|
|
43
|
+
ls.removeItem(probe);
|
|
44
|
+
return {
|
|
45
|
+
getItem: (k) => ls.getItem(k),
|
|
46
|
+
setItem: (k, v) => ls.setItem(k, v),
|
|
47
|
+
removeItem: (k) => ls.removeItem(k)
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
} catch {
|
|
51
|
+
}
|
|
52
|
+
return new InMemoryStorage();
|
|
53
|
+
}
|
|
54
|
+
function buildSurveyIdentity(userId, sessionId, miniService = null) {
|
|
55
|
+
const profile = userId ? [{ key: "external-user-id", value: [userId] }] : [];
|
|
56
|
+
const metadata = [];
|
|
57
|
+
if (sessionId) metadata.push({ key: "session_id", value: [sessionId] });
|
|
58
|
+
if (userId) metadata.push({ key: "user_id", value: [userId] });
|
|
59
|
+
if (miniService) metadata.push({ key: "mini_service", value: [miniService] });
|
|
60
|
+
return { profile, metadata };
|
|
61
|
+
}
|
|
62
|
+
function defaultUuid() {
|
|
63
|
+
const g = typeof globalThis !== "undefined" ? globalThis : {};
|
|
64
|
+
if (g.crypto?.randomUUID) return g.crypto.randomUUID();
|
|
65
|
+
const bytes = new Uint8Array(16);
|
|
66
|
+
if (g.crypto?.getRandomValues) {
|
|
67
|
+
g.crypto.getRandomValues(bytes);
|
|
68
|
+
} else {
|
|
69
|
+
for (let i = 0; i < 16; i++) bytes[i] = Math.floor(Math.random() * 256);
|
|
70
|
+
}
|
|
71
|
+
bytes[6] = bytes[6] & 15 | 64;
|
|
72
|
+
bytes[8] = bytes[8] & 63 | 128;
|
|
73
|
+
const hex = Array.from(bytes, (b) => b.toString(16).padStart(2, "0"));
|
|
74
|
+
return `${hex.slice(0, 4).join("")}-${hex.slice(4, 6).join("")}-${hex.slice(6, 8).join("")}-${hex.slice(8, 10).join("")}-${hex.slice(10, 16).join("")}`;
|
|
75
|
+
}
|
|
76
|
+
var InMemoryStorage, STORAGE_KEYS, TrackingManager;
|
|
77
|
+
var init_tracking_manager = __esm({
|
|
78
|
+
"src/tracking/tracking-manager.ts"() {
|
|
79
|
+
"use strict";
|
|
80
|
+
InMemoryStorage = class {
|
|
81
|
+
constructor() {
|
|
82
|
+
this.map = /* @__PURE__ */ new Map();
|
|
83
|
+
}
|
|
84
|
+
getItem(key) {
|
|
85
|
+
return this.map.has(key) ? this.map.get(key) : null;
|
|
86
|
+
}
|
|
87
|
+
setItem(key, value) {
|
|
88
|
+
this.map.set(key, value);
|
|
89
|
+
}
|
|
90
|
+
removeItem(key) {
|
|
91
|
+
this.map.delete(key);
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
STORAGE_KEYS = {
|
|
95
|
+
userId: "deepdots.user_id",
|
|
96
|
+
firstSeen: "deepdots.user.first_seen",
|
|
97
|
+
contactAttributes: "deepdots.contact.attributes"
|
|
98
|
+
};
|
|
99
|
+
TrackingManager = class {
|
|
100
|
+
constructor(options) {
|
|
101
|
+
/** session_id cacheado, provisto por el backend (no se genera ni se persiste). */
|
|
102
|
+
this.sessionId = null;
|
|
103
|
+
this.userWasJustCreated = false;
|
|
104
|
+
this.storage = options.storage;
|
|
105
|
+
this.clientUserId = options.clientUserId;
|
|
106
|
+
this.now = options.now ?? (() => Date.now());
|
|
107
|
+
this.uuid = options.uuid ?? defaultUuid;
|
|
108
|
+
this.enabled = options.enabled ?? true;
|
|
109
|
+
}
|
|
110
|
+
isTrackingEnabled() {
|
|
111
|
+
return this.enabled;
|
|
112
|
+
}
|
|
113
|
+
setTrackingEnabled(enabled) {
|
|
114
|
+
this.enabled = enabled;
|
|
115
|
+
}
|
|
116
|
+
/** Resuelve el user_id según la regla: cliente > persistido > generar+persistir. */
|
|
117
|
+
getUserId() {
|
|
118
|
+
if (!this.enabled) return null;
|
|
119
|
+
if (this.clientUserId) return this.clientUserId;
|
|
120
|
+
const persisted = this.storage.getItem(STORAGE_KEYS.userId);
|
|
121
|
+
if (persisted) return persisted;
|
|
122
|
+
const id = this.uuid();
|
|
123
|
+
this.storage.setItem(STORAGE_KEYS.userId, id);
|
|
124
|
+
this.storage.setItem(STORAGE_KEYS.firstSeen, String(this.now()));
|
|
125
|
+
this.userWasJustCreated = true;
|
|
126
|
+
return id;
|
|
127
|
+
}
|
|
128
|
+
/** `true` solo en la primera sesión tras crear el user_id propio del SDK. */
|
|
129
|
+
isNewUser() {
|
|
130
|
+
return this.userWasJustCreated;
|
|
131
|
+
}
|
|
132
|
+
/** session_id actual provisto por el backend, o null si aún no llegó. */
|
|
133
|
+
getSessionId() {
|
|
134
|
+
if (!this.enabled) return null;
|
|
135
|
+
return this.sessionId;
|
|
136
|
+
}
|
|
137
|
+
/** Cachea el session_id devuelto por el backend (respuesta de POST /sdk/popups). */
|
|
138
|
+
setSessionId(sessionId) {
|
|
139
|
+
if (!this.enabled) return;
|
|
140
|
+
this.sessionId = sessionId;
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
|
|
33
146
|
// node_modules/@magicfeedback/native/dist/magicfeedback-sdk.browser.js
|
|
34
147
|
var require_magicfeedback_sdk_browser = __commonJS({
|
|
35
148
|
"node_modules/@magicfeedback/native/dist/magicfeedback-sdk.browser.js"(exports, module2) {
|
|
@@ -1955,26 +2068,11 @@ var require_magicfeedback_sdk_browser = __commonJS({
|
|
|
1955
2068
|
}
|
|
1956
2069
|
});
|
|
1957
2070
|
|
|
1958
|
-
// src/index.ts
|
|
1959
|
-
var index_exports = {};
|
|
1960
|
-
__export(index_exports, {
|
|
1961
|
-
DeepdotsPopups: () => DeepdotsPopups,
|
|
1962
|
-
POPUP_TRIGGER_CONDITION_STATUSES: () => POPUP_TRIGGER_CONDITION_STATUSES
|
|
1963
|
-
});
|
|
1964
|
-
module.exports = __toCommonJS(index_exports);
|
|
1965
|
-
|
|
1966
|
-
// src/types/index.ts
|
|
1967
|
-
var POPUP_TRIGGER_CONDITION_STATUSES = [
|
|
1968
|
-
"SHOWED",
|
|
1969
|
-
"PARTIAL",
|
|
1970
|
-
"COMPLETED"
|
|
1971
|
-
];
|
|
1972
|
-
|
|
1973
|
-
// src/ui/renderPopup.ts
|
|
1974
|
-
var import_native = __toESM(require_magicfeedback_sdk_browser());
|
|
1975
|
-
|
|
1976
2071
|
// src/assets/style.css
|
|
1977
|
-
var style_default
|
|
2072
|
+
var style_default;
|
|
2073
|
+
var init_style = __esm({
|
|
2074
|
+
"src/assets/style.css"() {
|
|
2075
|
+
style_default = `.magicfeedback-container {
|
|
1978
2076
|
width: 100%;
|
|
1979
2077
|
}
|
|
1980
2078
|
|
|
@@ -2828,8 +2926,14 @@ var style_default = `.magicfeedback-container {
|
|
|
2828
2926
|
.magicfeedback-error{
|
|
2829
2927
|
margin-top: 5px;
|
|
2830
2928
|
}`;
|
|
2929
|
+
}
|
|
2930
|
+
});
|
|
2831
2931
|
|
|
2832
2932
|
// src/ui/renderPopup.ts
|
|
2933
|
+
var renderPopup_exports = {};
|
|
2934
|
+
__export(renderPopup_exports, {
|
|
2935
|
+
renderPopup: () => renderPopup
|
|
2936
|
+
});
|
|
2833
2937
|
function ensureMagicFeedbackStyles(_popup) {
|
|
2834
2938
|
const STYLE_ID = "magicfeedback-sdk-styles";
|
|
2835
2939
|
if (document.getElementById(STYLE_ID)) return;
|
|
@@ -2887,7 +2991,7 @@ function ensureResponsiveStyles(_popup) {
|
|
|
2887
2991
|
`;
|
|
2888
2992
|
document.head.appendChild(style);
|
|
2889
2993
|
}
|
|
2890
|
-
async function renderPopup(container, surveyId, productId, actions, emit, onClose, env = "production", userId, style) {
|
|
2994
|
+
async function renderPopup(container, surveyId, productId, actions, emit, onClose, env = "production", userId, style, sessionId, miniService) {
|
|
2891
2995
|
let surveyCompletedEmitted = false;
|
|
2892
2996
|
let stylesInjected = false;
|
|
2893
2997
|
const isDark = style?.theme === "dark";
|
|
@@ -3198,8 +3302,8 @@ async function renderPopup(container, surveyId, productId, actions, emit, onClos
|
|
|
3198
3302
|
env: env === "production" ? "prod" : "dev"
|
|
3199
3303
|
}
|
|
3200
3304
|
);
|
|
3201
|
-
const profile = userId
|
|
3202
|
-
formInstance = import_native.default.form(surveyId, productId, profile,
|
|
3305
|
+
const { profile, metadata } = buildSurveyIdentity(userId ?? null, sessionId ?? null, miniService ?? null);
|
|
3306
|
+
formInstance = import_native.default.form(surveyId, productId, profile, metadata);
|
|
3203
3307
|
const generateOptions = {
|
|
3204
3308
|
addButton: false,
|
|
3205
3309
|
getMetaData: true
|
|
@@ -3338,6 +3442,42 @@ async function renderPopup(container, surveyId, productId, actions, emit, onClos
|
|
|
3338
3442
|
setLoading(false);
|
|
3339
3443
|
}
|
|
3340
3444
|
}
|
|
3445
|
+
var import_native;
|
|
3446
|
+
var init_renderPopup = __esm({
|
|
3447
|
+
"src/ui/renderPopup.ts"() {
|
|
3448
|
+
"use strict";
|
|
3449
|
+
init_tracking_manager();
|
|
3450
|
+
import_native = __toESM(require_magicfeedback_sdk_browser());
|
|
3451
|
+
init_style();
|
|
3452
|
+
}
|
|
3453
|
+
});
|
|
3454
|
+
|
|
3455
|
+
// src/index.ts
|
|
3456
|
+
var index_exports = {};
|
|
3457
|
+
__export(index_exports, {
|
|
3458
|
+
BrowserPopupRenderer: () => BrowserPopupRenderer,
|
|
3459
|
+
ContactManager: () => ContactManager,
|
|
3460
|
+
DeepdotsPopups: () => DeepdotsPopups,
|
|
3461
|
+
InMemoryStorage: () => InMemoryStorage,
|
|
3462
|
+
NoopPopupRenderer: () => NoopPopupRenderer,
|
|
3463
|
+
POPUP_TRIGGER_CONDITION_STATUSES: () => POPUP_TRIGGER_CONDITION_STATUSES,
|
|
3464
|
+
ReactNativePopupRenderer: () => ReactNativePopupRenderer,
|
|
3465
|
+
buildAnalyticsFeedbackBody: () => buildAnalyticsFeedbackBody,
|
|
3466
|
+
buildSurveyHtml: () => buildSurveyHtml,
|
|
3467
|
+
collectRnDevice: () => collectRnDevice,
|
|
3468
|
+
createDefaultRenderer: () => createDefaultRenderer,
|
|
3469
|
+
createFeedbackSink: () => createFeedbackSink,
|
|
3470
|
+
mmkvStorage: () => mmkvStorage,
|
|
3471
|
+
setupReactNative: () => setupReactNative
|
|
3472
|
+
});
|
|
3473
|
+
module.exports = __toCommonJS(index_exports);
|
|
3474
|
+
|
|
3475
|
+
// src/types/index.ts
|
|
3476
|
+
var POPUP_TRIGGER_CONDITION_STATUSES = [
|
|
3477
|
+
"SHOWED",
|
|
3478
|
+
"PARTIAL",
|
|
3479
|
+
"COMPLETED"
|
|
3480
|
+
];
|
|
3341
3481
|
|
|
3342
3482
|
// src/triggers/index.ts
|
|
3343
3483
|
function setupTrigger(popups, trigger) {
|
|
@@ -3516,41 +3656,125 @@ function resolveEnvironment(nodeEnv) {
|
|
|
3516
3656
|
return ENVIRONMENT_MAP[nodeEnv || "production"];
|
|
3517
3657
|
}
|
|
3518
3658
|
|
|
3659
|
+
// src/platform/react-native-renderer.ts
|
|
3660
|
+
init_tracking_manager();
|
|
3661
|
+
|
|
3662
|
+
// src/ui/surveyHtml.ts
|
|
3663
|
+
function buildSurveyHtml(opts) {
|
|
3664
|
+
const env = opts.env === "development" ? "dev" : "prod";
|
|
3665
|
+
const version = opts.version ?? "2.2.4";
|
|
3666
|
+
const cdn = `https://cdn.jsdelivr.net/npm/@magicfeedback/native@${version}/dist/magicfeedback-sdk.browser.js`;
|
|
3667
|
+
const sid = JSON.stringify(opts.surveyId);
|
|
3668
|
+
const pid = JSON.stringify(opts.productId);
|
|
3669
|
+
const profileJson = JSON.stringify(opts.profile ?? []);
|
|
3670
|
+
const metaJson = JSON.stringify(opts.metadata ?? []);
|
|
3671
|
+
return `<!DOCTYPE html><html><head>
|
|
3672
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
|
3673
|
+
<meta name="color-scheme" content="light"/>
|
|
3674
|
+
<style>html,body{margin:0;padding:0;background:transparent;font-family:-apple-system,system-ui,sans-serif}#mf{width:100%;box-sizing:border-box}#mf *{max-width:100%;box-sizing:border-box}</style>
|
|
3675
|
+
</head><body>
|
|
3676
|
+
<div id="mf"></div>
|
|
3677
|
+
<script>
|
|
3678
|
+
(function(){
|
|
3679
|
+
function emit(s){ try{ if(window.ReactNativeWebView){ window.ReactNativeWebView.postMessage(s); } }catch(e){} }
|
|
3680
|
+
function emitJSON(n,p){ try{ emit(JSON.stringify({name:n,payload:p||{}})); }catch(e){} }
|
|
3681
|
+
window.DeepdotsClose = function(){ emitJSON('popup_close'); };
|
|
3682
|
+
var initialized=false;
|
|
3683
|
+
function initMF(){
|
|
3684
|
+
if(initialized || !window.magicfeedback) return initialized;
|
|
3685
|
+
initialized=true;
|
|
3686
|
+
try{
|
|
3687
|
+
window.magicfeedback.init({ debug:false, env:'${env}' });
|
|
3688
|
+
var form = window.magicfeedback.form(${sid}, ${pid}, ${profileJson}, ${metaJson});
|
|
3689
|
+
window.DeepdotsForm = form;
|
|
3690
|
+
form.generate('mf', {
|
|
3691
|
+
addButton:false,
|
|
3692
|
+
getMetaData:true,
|
|
3693
|
+
onLoadedEvent:function(){ emitJSON('loaded'); },
|
|
3694
|
+
beforeSubmitEvent:function(){ emitJSON('before_submit'); },
|
|
3695
|
+
afterSubmitEvent:function(p){
|
|
3696
|
+
try{ if(p && p.completed){ emitJSON('survey_completed', p); } else { emitJSON('after_submit', p); } }
|
|
3697
|
+
catch(e){ emitJSON('after_submit'); }
|
|
3698
|
+
},
|
|
3699
|
+
onBackEvent:function(p){ emitJSON('back', p); }
|
|
3700
|
+
}).catch(function(e){ emit('error:init'); });
|
|
3701
|
+
}catch(e){ emit('error:exception'); }
|
|
3702
|
+
return true;
|
|
3703
|
+
}
|
|
3704
|
+
var s=document.createElement('script'); s.src=${JSON.stringify(cdn)}; s.async=true;
|
|
3705
|
+
s.onload=function(){ initMF(); };
|
|
3706
|
+
s.onerror=function(){ emit('error:load'); };
|
|
3707
|
+
document.head.appendChild(s);
|
|
3708
|
+
var tries=0, t=setInterval(function(){ if(initMF()||++tries>40){ clearInterval(t); } },150);
|
|
3709
|
+
})();
|
|
3710
|
+
<\/script>
|
|
3711
|
+
</body></html>`;
|
|
3712
|
+
}
|
|
3713
|
+
|
|
3519
3714
|
// src/platform/react-native-renderer.ts
|
|
3520
3715
|
var ReactNativePopupRenderer = class {
|
|
3521
|
-
constructor() {
|
|
3522
|
-
this.
|
|
3523
|
-
this.lastSurveyId = null;
|
|
3716
|
+
constructor(options = {}) {
|
|
3717
|
+
this.options = options;
|
|
3524
3718
|
this.emitFn = null;
|
|
3525
3719
|
this.onCloseFn = null;
|
|
3526
|
-
this.
|
|
3720
|
+
this.currentSurveyId = null;
|
|
3721
|
+
this.partialEmitted = false;
|
|
3527
3722
|
}
|
|
3528
3723
|
init() {
|
|
3529
3724
|
}
|
|
3530
|
-
show(surveyId, productId,
|
|
3531
|
-
this.mounted = true;
|
|
3532
|
-
this.lastSurveyId = surveyId;
|
|
3725
|
+
show(surveyId, productId, _actions, emit, onClose, env = "production", userId, _style, sessionId, miniService) {
|
|
3533
3726
|
this.emitFn = emit;
|
|
3534
3727
|
this.onCloseFn = onClose;
|
|
3535
|
-
this.
|
|
3536
|
-
|
|
3537
|
-
|
|
3538
|
-
|
|
3539
|
-
|
|
3540
|
-
|
|
3541
|
-
|
|
3542
|
-
|
|
3543
|
-
|
|
3544
|
-
|
|
3545
|
-
|
|
3546
|
-
console.log("[ReactNativePopupRenderer] (stub) survey completed:", { surveyId: this.lastSurveyId, data });
|
|
3547
|
-
this.lastSurveyId = null;
|
|
3728
|
+
this.currentSurveyId = surveyId;
|
|
3729
|
+
this.partialEmitted = false;
|
|
3730
|
+
const { profile, metadata } = buildSurveyIdentity(userId ?? null, sessionId ?? null, miniService ?? null);
|
|
3731
|
+
const html = buildSurveyHtml({ surveyId, productId, env, profile, metadata });
|
|
3732
|
+
if (this.options.onShow) {
|
|
3733
|
+
this.options.onShow({ surveyId, productId, html });
|
|
3734
|
+
} else {
|
|
3735
|
+
console.warn(
|
|
3736
|
+
"[Deepdots] ReactNativePopupRenderer sin onShow: pasa { onShow } a new ReactNativePopupRenderer({...}) para montar el WebView del survey."
|
|
3737
|
+
);
|
|
3738
|
+
}
|
|
3548
3739
|
}
|
|
3549
3740
|
hide() {
|
|
3550
|
-
|
|
3551
|
-
this.
|
|
3552
|
-
|
|
3553
|
-
|
|
3741
|
+
this.options.onHide?.();
|
|
3742
|
+
this.currentSurveyId = null;
|
|
3743
|
+
}
|
|
3744
|
+
/**
|
|
3745
|
+
* El host la conecta al `onMessage` del WebView. Traduce los mensajes del survey a
|
|
3746
|
+
* eventos del SDK: primera interacción → `popup_clicked` (PARTIAL), completado →
|
|
3747
|
+
* `survey_completed` (COMPLETED) y cierra el popup.
|
|
3748
|
+
*/
|
|
3749
|
+
handleMessage(raw) {
|
|
3750
|
+
const surveyId = this.currentSurveyId;
|
|
3751
|
+
if (!surveyId || !this.emitFn) return;
|
|
3752
|
+
let name;
|
|
3753
|
+
try {
|
|
3754
|
+
name = JSON.parse(raw).name;
|
|
3755
|
+
} catch {
|
|
3756
|
+
name = raw;
|
|
3757
|
+
}
|
|
3758
|
+
switch (name) {
|
|
3759
|
+
case "loaded":
|
|
3760
|
+
case "before_submit":
|
|
3761
|
+
case "after_submit":
|
|
3762
|
+
case "back":
|
|
3763
|
+
if (!this.partialEmitted) {
|
|
3764
|
+
this.partialEmitted = true;
|
|
3765
|
+
this.emitFn("popup_clicked", surveyId, { action: "partial" });
|
|
3766
|
+
}
|
|
3767
|
+
break;
|
|
3768
|
+
case "survey_completed":
|
|
3769
|
+
this.emitFn("survey_completed", surveyId);
|
|
3770
|
+
this.onCloseFn?.();
|
|
3771
|
+
break;
|
|
3772
|
+
case "popup_close":
|
|
3773
|
+
this.onCloseFn?.();
|
|
3774
|
+
break;
|
|
3775
|
+
default:
|
|
3776
|
+
break;
|
|
3777
|
+
}
|
|
3554
3778
|
}
|
|
3555
3779
|
};
|
|
3556
3780
|
function isReactNativeEnv() {
|
|
@@ -3594,12 +3818,15 @@ var BrowserPopupRenderer = class {
|
|
|
3594
3818
|
document.body.appendChild(this.container);
|
|
3595
3819
|
}
|
|
3596
3820
|
}
|
|
3597
|
-
show(surveyId, productId, actions, emit, onClose, env = "production", userId, style) {
|
|
3821
|
+
show(surveyId, productId, actions, emit, onClose, env = "production", userId, style, sessionId, miniService) {
|
|
3598
3822
|
if (this.visible) return;
|
|
3599
3823
|
if (!this.container || !document.body.contains(this.container)) this.init();
|
|
3600
3824
|
if (!this.container) return;
|
|
3601
3825
|
this.visible = true;
|
|
3602
|
-
|
|
3826
|
+
const container = this.container;
|
|
3827
|
+
void Promise.resolve().then(() => (init_renderPopup(), renderPopup_exports)).then(({ renderPopup: renderPopup2 }) => {
|
|
3828
|
+
renderPopup2(container, surveyId, productId, actions, emit, onClose, env, userId, style, sessionId, miniService);
|
|
3829
|
+
});
|
|
3603
3830
|
}
|
|
3604
3831
|
hide() {
|
|
3605
3832
|
this.visible = false;
|
|
@@ -3618,6 +3845,350 @@ function createDefaultRenderer() {
|
|
|
3618
3845
|
return new BrowserPopupRenderer();
|
|
3619
3846
|
}
|
|
3620
3847
|
|
|
3848
|
+
// src/core/deepdots-popups.ts
|
|
3849
|
+
init_tracking_manager();
|
|
3850
|
+
|
|
3851
|
+
// src/analytics/analytics-manager.ts
|
|
3852
|
+
var dryRunSink = (payload) => {
|
|
3853
|
+
console.log(
|
|
3854
|
+
"[DeepdotsAnalytics] (dry-run \xB7 NO enviado \xB7 sin init.analytics) POST /sdk/feedback \u2192",
|
|
3855
|
+
JSON.stringify(payload, null, 2)
|
|
3856
|
+
);
|
|
3857
|
+
};
|
|
3858
|
+
var AnalyticsManager = class {
|
|
3859
|
+
constructor(options = {}) {
|
|
3860
|
+
this.events = [];
|
|
3861
|
+
this.attributes = {};
|
|
3862
|
+
this.miniService = null;
|
|
3863
|
+
this.miniServiceEnteredAt = 0;
|
|
3864
|
+
this.sink = options.sink ?? dryRunSink;
|
|
3865
|
+
this.now = options.now ?? (() => Date.now());
|
|
3866
|
+
this.publicKey = options.publicKey;
|
|
3867
|
+
this.language = options.language;
|
|
3868
|
+
this.device = options.device;
|
|
3869
|
+
this.platform = options.platform ?? "web";
|
|
3870
|
+
}
|
|
3871
|
+
/** Mezcla user attributes (se coercionan a string). Mutable en runtime. */
|
|
3872
|
+
setUserAttributes(attrs) {
|
|
3873
|
+
for (const [k, v] of Object.entries(attrs)) {
|
|
3874
|
+
if (!k) continue;
|
|
3875
|
+
this.attributes[k] = String(v);
|
|
3876
|
+
}
|
|
3877
|
+
}
|
|
3878
|
+
/** Marca el inicio de un mini-service; etiqueta los eventos siguientes con `mini_service`. */
|
|
3879
|
+
enterMiniService(name, entryPointType) {
|
|
3880
|
+
this.miniService = name;
|
|
3881
|
+
this.miniServiceEnteredAt = this.now();
|
|
3882
|
+
this.track("mini_service_enter", { entry_point_type: entryPointType ?? null });
|
|
3883
|
+
}
|
|
3884
|
+
/** Cierra el mini-service activo emitiendo `mini_service_exit` con su duración (#27). No-op si no hay ninguno. */
|
|
3885
|
+
exitMiniService() {
|
|
3886
|
+
const name = this.miniService;
|
|
3887
|
+
if (name == null) return;
|
|
3888
|
+
const durationSeconds = Math.max(0, Math.round((this.now() - this.miniServiceEnteredAt) / 1e3));
|
|
3889
|
+
this.miniService = null;
|
|
3890
|
+
this.track("mini_service_exit", { mini_service: name, duration_seconds: durationSeconds });
|
|
3891
|
+
}
|
|
3892
|
+
/** Registra un evento de analítica (modelo GA: nombre + params). */
|
|
3893
|
+
track(name, params) {
|
|
3894
|
+
const merged = {
|
|
3895
|
+
...this.miniService ? { mini_service: this.miniService } : {},
|
|
3896
|
+
...params ?? {}
|
|
3897
|
+
};
|
|
3898
|
+
this.events.push({
|
|
3899
|
+
name,
|
|
3900
|
+
timestamp: this.now(),
|
|
3901
|
+
params: Object.keys(merged).length ? merged : void 0
|
|
3902
|
+
});
|
|
3903
|
+
}
|
|
3904
|
+
/** Mini-service activo (para inyectarlo en la metadata del survey, #33). */
|
|
3905
|
+
getMiniService() {
|
|
3906
|
+
return this.miniService;
|
|
3907
|
+
}
|
|
3908
|
+
/** Nº de eventos pendientes de flush. */
|
|
3909
|
+
pending() {
|
|
3910
|
+
return this.events.length;
|
|
3911
|
+
}
|
|
3912
|
+
/** Construye el envelope que se enviaría al endpoint de analytics. */
|
|
3913
|
+
buildPayload(identity) {
|
|
3914
|
+
return {
|
|
3915
|
+
publicKey: this.publicKey,
|
|
3916
|
+
userId: identity.userId,
|
|
3917
|
+
sessionId: identity.sessionId,
|
|
3918
|
+
context: {
|
|
3919
|
+
platform: this.platform,
|
|
3920
|
+
language: this.language,
|
|
3921
|
+
device: this.device,
|
|
3922
|
+
attributes: { ...this.attributes }
|
|
3923
|
+
},
|
|
3924
|
+
events: [...this.events]
|
|
3925
|
+
};
|
|
3926
|
+
}
|
|
3927
|
+
/** Envía (vía sink) el lote acumulado y vacía el buffer. No-op si no hay eventos. */
|
|
3928
|
+
flush(identity) {
|
|
3929
|
+
if (this.events.length === 0) return null;
|
|
3930
|
+
const payload = this.buildPayload(identity);
|
|
3931
|
+
this.sink(payload);
|
|
3932
|
+
this.events = [];
|
|
3933
|
+
return payload;
|
|
3934
|
+
}
|
|
3935
|
+
};
|
|
3936
|
+
|
|
3937
|
+
// src/analytics/feedback-payload.ts
|
|
3938
|
+
function pushKV(list, key, value) {
|
|
3939
|
+
if (value === void 0 || value === null || value === "") return;
|
|
3940
|
+
list.push({ key, value: String(value) });
|
|
3941
|
+
}
|
|
3942
|
+
function buildAnalyticsFeedbackBody(envelope, keys, feedbackSessionId) {
|
|
3943
|
+
const { context } = envelope;
|
|
3944
|
+
const metrics = envelope.events.map((e) => ({
|
|
3945
|
+
key: e.name,
|
|
3946
|
+
value: JSON.stringify({ timestamp: e.timestamp, ...e.params ?? {} })
|
|
3947
|
+
}));
|
|
3948
|
+
const profile = [];
|
|
3949
|
+
pushKV(profile, "external-user-id", envelope.userId);
|
|
3950
|
+
const metadata = [];
|
|
3951
|
+
pushKV(metadata, "user_id", envelope.userId);
|
|
3952
|
+
pushKV(metadata, "session_id", envelope.sessionId);
|
|
3953
|
+
pushKV(metadata, "platform", context.platform);
|
|
3954
|
+
pushKV(metadata, "language", context.language);
|
|
3955
|
+
if (context.device) {
|
|
3956
|
+
pushKV(metadata, "device_type", context.device.device_type);
|
|
3957
|
+
pushKV(metadata, "os_version", context.device.os_version);
|
|
3958
|
+
pushKV(metadata, "device_model", context.device.device_model);
|
|
3959
|
+
pushKV(metadata, "app_version", context.device.app_version);
|
|
3960
|
+
pushKV(metadata, "user_agent", context.device.user_agent);
|
|
3961
|
+
}
|
|
3962
|
+
for (const [k, v] of Object.entries(context.attributes ?? {})) {
|
|
3963
|
+
pushKV(metadata, k, v);
|
|
3964
|
+
}
|
|
3965
|
+
return {
|
|
3966
|
+
feedback: {
|
|
3967
|
+
text: "",
|
|
3968
|
+
answers: [],
|
|
3969
|
+
metadata,
|
|
3970
|
+
metrics,
|
|
3971
|
+
profile,
|
|
3972
|
+
finished: false
|
|
3973
|
+
},
|
|
3974
|
+
publicKey: keys.publicKey,
|
|
3975
|
+
integration: keys.integration,
|
|
3976
|
+
completed: false,
|
|
3977
|
+
// finished: false,
|
|
3978
|
+
...feedbackSessionId ? { sessionId: feedbackSessionId } : {}
|
|
3979
|
+
};
|
|
3980
|
+
}
|
|
3981
|
+
function createFeedbackSink(options) {
|
|
3982
|
+
let feedbackSessionId;
|
|
3983
|
+
return (envelope) => {
|
|
3984
|
+
const body = buildAnalyticsFeedbackBody(envelope, options.keys, feedbackSessionId);
|
|
3985
|
+
const f = options.fetchImpl ?? (typeof fetch !== "undefined" ? fetch : void 0);
|
|
3986
|
+
if (!f) {
|
|
3987
|
+
options.log?.("[DeepdotsAnalytics] no fetch disponible; payload no enviado", body);
|
|
3988
|
+
return;
|
|
3989
|
+
}
|
|
3990
|
+
options.log?.("[DeepdotsAnalytics] POST /sdk/feedback \u2192", body);
|
|
3991
|
+
f(`${options.baseUrl}/sdk/feedback`, {
|
|
3992
|
+
method: "POST",
|
|
3993
|
+
headers: { "Content-Type": "application/json" },
|
|
3994
|
+
body: JSON.stringify(body)
|
|
3995
|
+
}).then(async (res) => {
|
|
3996
|
+
if (res.ok) {
|
|
3997
|
+
try {
|
|
3998
|
+
const data = await res.json();
|
|
3999
|
+
if (data?.sessionId && data.sessionId !== feedbackSessionId) {
|
|
4000
|
+
feedbackSessionId = data.sessionId;
|
|
4001
|
+
options.log?.("[DeepdotsAnalytics] feedbackSessionId cacheado:", feedbackSessionId);
|
|
4002
|
+
}
|
|
4003
|
+
} catch {
|
|
4004
|
+
}
|
|
4005
|
+
}
|
|
4006
|
+
}).catch((err) => options.log?.("[DeepdotsAnalytics] error enviando feedback", err));
|
|
4007
|
+
};
|
|
4008
|
+
}
|
|
4009
|
+
|
|
4010
|
+
// src/tracking/navigation-observer.ts
|
|
4011
|
+
var UUID_PREFIX = /^[0-9a-f]{8}-[0-9a-f]{4}-/i;
|
|
4012
|
+
function normalizeScreen(href) {
|
|
4013
|
+
let pathAndHash;
|
|
4014
|
+
try {
|
|
4015
|
+
const u = new URL(href, "http://deepdots.local");
|
|
4016
|
+
pathAndHash = u.pathname + (u.hash || "");
|
|
4017
|
+
} catch {
|
|
4018
|
+
pathAndHash = href;
|
|
4019
|
+
}
|
|
4020
|
+
pathAndHash = pathAndHash.replace(/\?[^#]*/g, "");
|
|
4021
|
+
const collapsed = pathAndHash.split("/").map((seg) => /^[0-9]+$/.test(seg) || UUID_PREFIX.test(seg) ? ":id" : seg).join("/");
|
|
4022
|
+
return collapsed || "/";
|
|
4023
|
+
}
|
|
4024
|
+
var NavigationObserver = class {
|
|
4025
|
+
constructor(options = {}) {
|
|
4026
|
+
this.listeners = /* @__PURE__ */ new Set();
|
|
4027
|
+
this.currentScreen = null;
|
|
4028
|
+
this.entryAt = 0;
|
|
4029
|
+
this.installed = false;
|
|
4030
|
+
this.domHandlers = [];
|
|
4031
|
+
this.now = options.now ?? (() => Date.now());
|
|
4032
|
+
}
|
|
4033
|
+
onVisit(listener) {
|
|
4034
|
+
this.listeners.add(listener);
|
|
4035
|
+
}
|
|
4036
|
+
/** Fija la pantalla inicial (sin emitir visita; su duración se cierra al salir). */
|
|
4037
|
+
begin(href) {
|
|
4038
|
+
this.currentScreen = normalizeScreen(href);
|
|
4039
|
+
this.entryAt = this.now();
|
|
4040
|
+
}
|
|
4041
|
+
/** Navega a una pantalla nueva: cierra la anterior (emite visita) y abre la nueva. */
|
|
4042
|
+
visit(href) {
|
|
4043
|
+
const next = normalizeScreen(href);
|
|
4044
|
+
if (next === this.currentScreen) return;
|
|
4045
|
+
this.closeCurrent();
|
|
4046
|
+
this.currentScreen = next;
|
|
4047
|
+
this.entryAt = this.now();
|
|
4048
|
+
}
|
|
4049
|
+
/** Cierra la pantalla actual emitiendo su visita (p. ej. al cerrar la app/pestaña). */
|
|
4050
|
+
stop() {
|
|
4051
|
+
this.closeCurrent();
|
|
4052
|
+
this.currentScreen = null;
|
|
4053
|
+
}
|
|
4054
|
+
closeCurrent() {
|
|
4055
|
+
if (this.currentScreen == null) return;
|
|
4056
|
+
const exit = this.now();
|
|
4057
|
+
const visit = {
|
|
4058
|
+
screen: this.currentScreen,
|
|
4059
|
+
entry: this.entryAt,
|
|
4060
|
+
exit,
|
|
4061
|
+
durationSeconds: Math.max(0, Math.round((exit - this.entryAt) / 1e3))
|
|
4062
|
+
};
|
|
4063
|
+
this.listeners.forEach((l) => {
|
|
4064
|
+
try {
|
|
4065
|
+
l(visit);
|
|
4066
|
+
} catch {
|
|
4067
|
+
}
|
|
4068
|
+
});
|
|
4069
|
+
}
|
|
4070
|
+
/** Instala los hooks de History en el navegador y registra la pantalla actual. */
|
|
4071
|
+
install() {
|
|
4072
|
+
if (this.installed || typeof window === "undefined" || typeof history === "undefined") return;
|
|
4073
|
+
this.installed = true;
|
|
4074
|
+
const handle = () => this.visit(window.location.href);
|
|
4075
|
+
const originalPush = history.pushState.bind(history);
|
|
4076
|
+
const originalReplace = history.replaceState.bind(history);
|
|
4077
|
+
history.pushState = (data, unused, url) => {
|
|
4078
|
+
const r = originalPush(data, unused, url);
|
|
4079
|
+
handle();
|
|
4080
|
+
return r;
|
|
4081
|
+
};
|
|
4082
|
+
history.replaceState = (data, unused, url) => {
|
|
4083
|
+
const r = originalReplace(data, unused, url);
|
|
4084
|
+
handle();
|
|
4085
|
+
return r;
|
|
4086
|
+
};
|
|
4087
|
+
window.addEventListener("popstate", handle);
|
|
4088
|
+
window.addEventListener("hashchange", handle);
|
|
4089
|
+
this.domHandlers.push(() => {
|
|
4090
|
+
history.pushState = originalPush;
|
|
4091
|
+
history.replaceState = originalReplace;
|
|
4092
|
+
window.removeEventListener("popstate", handle);
|
|
4093
|
+
window.removeEventListener("hashchange", handle);
|
|
4094
|
+
});
|
|
4095
|
+
this.begin(window.location.href);
|
|
4096
|
+
}
|
|
4097
|
+
uninstall() {
|
|
4098
|
+
this.domHandlers.forEach((off) => off());
|
|
4099
|
+
this.domHandlers = [];
|
|
4100
|
+
this.installed = false;
|
|
4101
|
+
}
|
|
4102
|
+
};
|
|
4103
|
+
|
|
4104
|
+
// src/analytics/device-info.ts
|
|
4105
|
+
function parseDeviceType(ua) {
|
|
4106
|
+
const s = (ua || "").toLowerCase();
|
|
4107
|
+
if (/ipad|tablet|playbook|silk/.test(s) || /android/.test(s) && !/mobile/.test(s)) {
|
|
4108
|
+
return "tablet";
|
|
4109
|
+
}
|
|
4110
|
+
if (/mobi|iphone|ipod|windows phone/.test(s) || /android/.test(s) && /mobile/.test(s)) {
|
|
4111
|
+
return "mobile";
|
|
4112
|
+
}
|
|
4113
|
+
return "desktop";
|
|
4114
|
+
}
|
|
4115
|
+
function collectDeviceInfo(appVersion) {
|
|
4116
|
+
const ua = typeof navigator !== "undefined" ? navigator.userAgent : "";
|
|
4117
|
+
return {
|
|
4118
|
+
device_type: parseDeviceType(ua),
|
|
4119
|
+
user_agent: ua || void 0,
|
|
4120
|
+
app_version: appVersion
|
|
4121
|
+
};
|
|
4122
|
+
}
|
|
4123
|
+
|
|
4124
|
+
// src/analytics/engagement-tracker.ts
|
|
4125
|
+
var EngagementTracker = class {
|
|
4126
|
+
constructor(now = () => Date.now()) {
|
|
4127
|
+
this.activeMs = 0;
|
|
4128
|
+
this.lastResumeAt = null;
|
|
4129
|
+
this.now = now;
|
|
4130
|
+
}
|
|
4131
|
+
/** Reanuda el conteo (pestaña visible / app en foreground). Idempotente. */
|
|
4132
|
+
resume() {
|
|
4133
|
+
if (this.lastResumeAt === null) this.lastResumeAt = this.now();
|
|
4134
|
+
}
|
|
4135
|
+
/** Pausa el conteo (pestaña oculta / app en background), acumulando el tramo activo. */
|
|
4136
|
+
pause() {
|
|
4137
|
+
if (this.lastResumeAt !== null) {
|
|
4138
|
+
this.activeMs += this.now() - this.lastResumeAt;
|
|
4139
|
+
this.lastResumeAt = null;
|
|
4140
|
+
}
|
|
4141
|
+
}
|
|
4142
|
+
/** Devuelve los ms activos acumulados desde la última lectura y reinicia (sin parar el timer). */
|
|
4143
|
+
consume() {
|
|
4144
|
+
if (this.lastResumeAt !== null) {
|
|
4145
|
+
const t = this.now();
|
|
4146
|
+
this.activeMs += t - this.lastResumeAt;
|
|
4147
|
+
this.lastResumeAt = t;
|
|
4148
|
+
}
|
|
4149
|
+
const ms = this.activeMs;
|
|
4150
|
+
this.activeMs = 0;
|
|
4151
|
+
return ms;
|
|
4152
|
+
}
|
|
4153
|
+
isActive() {
|
|
4154
|
+
return this.lastResumeAt !== null;
|
|
4155
|
+
}
|
|
4156
|
+
};
|
|
4157
|
+
|
|
4158
|
+
// src/contact/contact-manager.ts
|
|
4159
|
+
init_tracking_manager();
|
|
4160
|
+
function stableSerialize(attrs) {
|
|
4161
|
+
const sorted = {};
|
|
4162
|
+
for (const key of Object.keys(attrs).sort()) {
|
|
4163
|
+
sorted[key] = attrs[key];
|
|
4164
|
+
}
|
|
4165
|
+
return JSON.stringify(sorted);
|
|
4166
|
+
}
|
|
4167
|
+
var ContactManager = class {
|
|
4168
|
+
constructor(options) {
|
|
4169
|
+
this.storage = options.storage;
|
|
4170
|
+
this.publicKey = options.publicKey;
|
|
4171
|
+
this.userId = options.userId;
|
|
4172
|
+
this.post = options.post;
|
|
4173
|
+
}
|
|
4174
|
+
/**
|
|
4175
|
+
* Envía los atributos si cambiaron respecto a lo último enviado.
|
|
4176
|
+
* @returns `true` si se envió, `false` si no hubo cambios.
|
|
4177
|
+
*/
|
|
4178
|
+
async setAttributes(attributes) {
|
|
4179
|
+
const serialized = stableSerialize(attributes);
|
|
4180
|
+
const last = this.storage.getItem(STORAGE_KEYS.contactAttributes);
|
|
4181
|
+
if (last === serialized) return false;
|
|
4182
|
+
await this.post({
|
|
4183
|
+
publicKey: this.publicKey,
|
|
4184
|
+
userId: this.userId,
|
|
4185
|
+
userAttributes: attributes
|
|
4186
|
+
});
|
|
4187
|
+
this.storage.setItem(STORAGE_KEYS.contactAttributes, serialized);
|
|
4188
|
+
return true;
|
|
4189
|
+
}
|
|
4190
|
+
};
|
|
4191
|
+
|
|
3621
4192
|
// src/core/deepdots-popups.ts
|
|
3622
4193
|
var EXIT_QUEUE_STORAGE_KEY = "__deepdots_exit_popup_queue__";
|
|
3623
4194
|
var DAY_IN_MS = 24 * 60 * 60 * 1e3;
|
|
@@ -3642,6 +4213,18 @@ var DeepdotsPopups = class {
|
|
|
3642
4213
|
this.deferredExitTimers = [];
|
|
3643
4214
|
this.baseUrl = "";
|
|
3644
4215
|
this.env = "production";
|
|
4216
|
+
/** Identidad + sesión (Fase 1 tracking). Null hasta init(). */
|
|
4217
|
+
this.tracking = null;
|
|
4218
|
+
/** Capa de analytics (canal separado del feedback). Null hasta init(). */
|
|
4219
|
+
this.analytics = null;
|
|
4220
|
+
/** Observador de navegación (Fase 2): emite page_view por el canal de analytics. */
|
|
4221
|
+
this.navObserver = null;
|
|
4222
|
+
/** Tiempo activo (engagement time, #8). */
|
|
4223
|
+
this.engagement = null;
|
|
4224
|
+
/** Atributos internos del usuario identificado → POST /sdk/popups/contact. Null si no hay userId. */
|
|
4225
|
+
this.contact = null;
|
|
4226
|
+
/** Marca si ya se inició la navegación manual (setScreen) — para RN sin History API. */
|
|
4227
|
+
this.navStarted = false;
|
|
3645
4228
|
}
|
|
3646
4229
|
/** Initialize the SDK with configuration */
|
|
3647
4230
|
init(config) {
|
|
@@ -3654,33 +4237,196 @@ var DeepdotsPopups = class {
|
|
|
3654
4237
|
this.env = config.nodeEnv || "production";
|
|
3655
4238
|
this.config = {
|
|
3656
4239
|
apiKey: config.apiKey || void 0,
|
|
3657
|
-
mode: config.mode || "client",
|
|
3658
4240
|
debug: config.debug || false,
|
|
3659
|
-
popups: config.popups || [],
|
|
3660
4241
|
userId: config.userId || void 0
|
|
3661
4242
|
};
|
|
4243
|
+
const storage = config.storage ?? createDefaultStorage();
|
|
4244
|
+
this.tracking = new TrackingManager({
|
|
4245
|
+
storage,
|
|
4246
|
+
clientUserId: this.config.userId,
|
|
4247
|
+
enabled: config.trackingEnabled ?? true
|
|
4248
|
+
});
|
|
4249
|
+
if (this.config.userId) {
|
|
4250
|
+
const userId = this.config.userId;
|
|
4251
|
+
const publicKey = this.config.apiKey ?? "";
|
|
4252
|
+
this.contact = new ContactManager({
|
|
4253
|
+
storage,
|
|
4254
|
+
publicKey,
|
|
4255
|
+
userId,
|
|
4256
|
+
post: (body) => this.postContact(body)
|
|
4257
|
+
});
|
|
4258
|
+
if (config.contactAttributes) {
|
|
4259
|
+
void this.setContactAttributes(config.contactAttributes);
|
|
4260
|
+
}
|
|
4261
|
+
}
|
|
4262
|
+
this.log("tracking \xB7 user_id:", this.tracking.getUserId(), "\xB7 new_user:", this.tracking.isNewUser(), "\xB7 enabled:", this.tracking.isTrackingEnabled());
|
|
4263
|
+
const analyticsSink = config.analytics ? createFeedbackSink({
|
|
4264
|
+
baseUrl: this.baseUrl,
|
|
4265
|
+
keys: config.analytics,
|
|
4266
|
+
log: (...a) => this.log(...a)
|
|
4267
|
+
}) : void 0;
|
|
4268
|
+
this.analytics = new AnalyticsManager({
|
|
4269
|
+
sink: analyticsSink,
|
|
4270
|
+
publicKey: config.analytics?.publicKey ?? this.config.apiKey,
|
|
4271
|
+
language: typeof navigator !== "undefined" ? navigator.language : void 0,
|
|
4272
|
+
platform: config.platform ?? "web",
|
|
4273
|
+
device: config.device ?? collectDeviceInfo(config.appVersion)
|
|
4274
|
+
});
|
|
4275
|
+
this.navObserver = new NavigationObserver();
|
|
4276
|
+
this.navObserver.onVisit((v) => this.track("page_view", { screen: v.screen, duration_seconds: v.durationSeconds }));
|
|
4277
|
+
this.navObserver.install();
|
|
4278
|
+
this.engagement = new EngagementTracker();
|
|
4279
|
+
this.engagement.resume();
|
|
4280
|
+
this.setupAnalyticsFlush();
|
|
3662
4281
|
this.initialized = true;
|
|
3663
4282
|
this.log("SDK initialized", this.config);
|
|
3664
4283
|
if (this.renderer.init) this.renderer.init();
|
|
3665
4284
|
this.setupPopupContainer();
|
|
3666
|
-
|
|
3667
|
-
this.popupDefinitions = this.validatePopupDefinitions(
|
|
4285
|
+
this.fetchPopupsFromServer().then((defs) => {
|
|
4286
|
+
this.popupDefinitions = this.validatePopupDefinitions(defs);
|
|
3668
4287
|
this.popupsLoaded = true;
|
|
4288
|
+
this.log("Popups loaded from API", this.popupDefinitions);
|
|
3669
4289
|
this.configureTriggersFromDefinitions();
|
|
3670
4290
|
this.processDeferredExitQueue();
|
|
4291
|
+
if (this.pendingAutoLaunch) {
|
|
4292
|
+
this.startTriggers();
|
|
4293
|
+
}
|
|
4294
|
+
});
|
|
4295
|
+
}
|
|
4296
|
+
/** User id actual (generado por el SDK o provisto por el cliente). Null si tracking off. */
|
|
4297
|
+
getUserId() {
|
|
4298
|
+
return this.tracking?.getUserId() ?? null;
|
|
4299
|
+
}
|
|
4300
|
+
/** Session id de navegación actual. Null si tracking off. */
|
|
4301
|
+
getSessionId() {
|
|
4302
|
+
return this.tracking?.getSessionId() ?? null;
|
|
4303
|
+
}
|
|
4304
|
+
/** Activa/desactiva el tracking (identidad + sesión + analytics). Kill-switch del contrato §7bis. */
|
|
4305
|
+
setTrackingEnabled(enabled) {
|
|
4306
|
+
this.tracking?.setTrackingEnabled(enabled);
|
|
4307
|
+
this.log("tracking \xB7 setTrackingEnabled:", enabled, "\xB7 session_id:", this.tracking?.getSessionId() ?? null);
|
|
4308
|
+
}
|
|
4309
|
+
// ───────── Analytics (canal separado del feedback, vinculado por user_id) ─────────
|
|
4310
|
+
/** Registra un evento de analítica (modelo GA: nombre + params). No-op si tracking off. */
|
|
4311
|
+
track(name, params) {
|
|
4312
|
+
if (!this.tracking?.isTrackingEnabled()) return;
|
|
4313
|
+
this.analytics?.track(name, params);
|
|
4314
|
+
this.log("analytics \xB7 track:", name, params ?? {});
|
|
4315
|
+
}
|
|
4316
|
+
/** User attributes para breakdowns (registration_status, pass_type, sector, pass_status…). */
|
|
4317
|
+
setUserAttributes(attributes) {
|
|
4318
|
+
if (!this.tracking?.isTrackingEnabled()) return;
|
|
4319
|
+
this.analytics?.setUserAttributes(attributes);
|
|
4320
|
+
}
|
|
4321
|
+
/**
|
|
4322
|
+
* Info interna del usuario que solo conoce el host (idioma, edad, plan…), persistida en el
|
|
4323
|
+
* Contact del backend para segmentación/targeting de popups. Identificada por el `userId` del
|
|
4324
|
+
* init. Solo envía si los atributos cambiaron (diff en storage). No-op si tracking off o sin userId.
|
|
4325
|
+
* @returns `true` si se envió al backend, `false` si no hubo cambios o está deshabilitado.
|
|
4326
|
+
*/
|
|
4327
|
+
async setContactAttributes(attributes) {
|
|
4328
|
+
if (!this.tracking?.isTrackingEnabled()) return false;
|
|
4329
|
+
if (!this.contact) return false;
|
|
4330
|
+
return this.contact.setAttributes(attributes);
|
|
4331
|
+
}
|
|
4332
|
+
/** Marca el inicio de un mini-service; etiqueta los eventos siguientes. No-op si tracking off. */
|
|
4333
|
+
enterMiniService(name, entryPointType) {
|
|
4334
|
+
if (!this.tracking?.isTrackingEnabled()) return;
|
|
4335
|
+
this.analytics?.enterMiniService(name, entryPointType);
|
|
4336
|
+
this.log("analytics \xB7 enterMiniService:", name, entryPointType ?? "");
|
|
4337
|
+
}
|
|
4338
|
+
/** Cierra el mini-service activo (emite `mini_service_exit` con duración, #27). No-op si tracking off. */
|
|
4339
|
+
exitMiniService() {
|
|
4340
|
+
if (!this.tracking?.isTrackingEnabled()) return;
|
|
4341
|
+
this.analytics?.exitMiniService();
|
|
4342
|
+
this.log("analytics \xB7 exitMiniService");
|
|
4343
|
+
}
|
|
4344
|
+
/** Findability (#31/#35): registra una búsqueda. `has_results` se deriva de `resultsCount`. */
|
|
4345
|
+
trackSearch(query, resultsCount, params) {
|
|
4346
|
+
this.track("search", { query, results_count: resultsCount, has_results: resultsCount > 0, ...params ?? {} });
|
|
4347
|
+
}
|
|
4348
|
+
/** Findability friction (#34/#35): señal de fricción con su `friction_topic`. */
|
|
4349
|
+
trackFindabilityFriction(frictionTopic, params) {
|
|
4350
|
+
this.track("findability_friction", { friction_topic: frictionTopic, ...params ?? {} });
|
|
4351
|
+
}
|
|
4352
|
+
/** Funnel: un paso del embudo, correlacionado por `taskId`. El backend reconstruye conversión/drop-off/tiempo. */
|
|
4353
|
+
trackFunnelStep(funnel, step, taskId, params) {
|
|
4354
|
+
this.track("funnel_step", { funnel, step, task_id: taskId, ...params ?? {} });
|
|
4355
|
+
}
|
|
4356
|
+
/**
|
|
4357
|
+
* Navegación MANUAL (entornos sin History API, p. ej. React Native con React Navigation).
|
|
4358
|
+
* El host la llama en cada cambio de pantalla; emite `page_view` al salir de la anterior.
|
|
4359
|
+
*/
|
|
4360
|
+
setScreen(name) {
|
|
4361
|
+
if (!this.navObserver) return;
|
|
4362
|
+
if (!this.navStarted) {
|
|
4363
|
+
this.navObserver.begin(name);
|
|
4364
|
+
this.navStarted = true;
|
|
3671
4365
|
} else {
|
|
3672
|
-
this.
|
|
3673
|
-
this.popupDefinitions = this.validatePopupDefinitions(defs);
|
|
3674
|
-
this.popupsLoaded = true;
|
|
3675
|
-
this.log("Popups loaded from server (fake)", this.popupDefinitions);
|
|
3676
|
-
this.configureTriggersFromDefinitions();
|
|
3677
|
-
this.processDeferredExitQueue();
|
|
3678
|
-
if (this.pendingAutoLaunch) {
|
|
3679
|
-
this.startTriggers();
|
|
3680
|
-
}
|
|
3681
|
-
});
|
|
4366
|
+
this.navObserver.visit(name);
|
|
3682
4367
|
}
|
|
3683
4368
|
}
|
|
4369
|
+
/** App a foreground (RN: AppState 'active'): reanuda el engagement time. */
|
|
4370
|
+
onForeground() {
|
|
4371
|
+
this.engagement?.resume();
|
|
4372
|
+
}
|
|
4373
|
+
/**
|
|
4374
|
+
* App a background (RN: AppState 'background'): cierra pantalla actual (page_view),
|
|
4375
|
+
* mini-service (mini_service_exit), emite engagement y hace flush del lote.
|
|
4376
|
+
*/
|
|
4377
|
+
onBackground() {
|
|
4378
|
+
this.navObserver?.stop();
|
|
4379
|
+
this.navStarted = false;
|
|
4380
|
+
this.exitMiniService();
|
|
4381
|
+
this.flushEngagement();
|
|
4382
|
+
this.engagement?.pause();
|
|
4383
|
+
this.flushAnalytics();
|
|
4384
|
+
}
|
|
4385
|
+
/** Payload que se ENVIARÍA al endpoint de analytics (no envía ni vacía el buffer). */
|
|
4386
|
+
previewAnalytics() {
|
|
4387
|
+
return this.analytics?.buildPayload(this.analyticsIdentity()) ?? {
|
|
4388
|
+
userId: null,
|
|
4389
|
+
sessionId: null,
|
|
4390
|
+
context: { platform: "web", attributes: {} },
|
|
4391
|
+
events: []
|
|
4392
|
+
};
|
|
4393
|
+
}
|
|
4394
|
+
/** Envía (hoy dry-run → console.log) el lote acumulado de analytics y vacía el buffer. */
|
|
4395
|
+
flushAnalytics() {
|
|
4396
|
+
if (!this.tracking?.isTrackingEnabled()) return;
|
|
4397
|
+
this.analytics?.flush(this.analyticsIdentity());
|
|
4398
|
+
}
|
|
4399
|
+
/** Emite un evento `user_engagement` con el tiempo activo acumulado (#8). */
|
|
4400
|
+
flushEngagement() {
|
|
4401
|
+
if (!this.tracking?.isTrackingEnabled()) return;
|
|
4402
|
+
const ms = this.engagement?.consume() ?? 0;
|
|
4403
|
+
if (ms > 0) this.track("user_engagement", { engagement_time_msec: ms });
|
|
4404
|
+
}
|
|
4405
|
+
analyticsIdentity() {
|
|
4406
|
+
return {
|
|
4407
|
+
userId: this.tracking?.getUserId() ?? null,
|
|
4408
|
+
sessionId: this.tracking?.getSessionId() ?? null
|
|
4409
|
+
};
|
|
4410
|
+
}
|
|
4411
|
+
/** Flush automático al ocultar/cerrar la página (no perder el lote pendiente). */
|
|
4412
|
+
setupAnalyticsFlush() {
|
|
4413
|
+
if (typeof document === "undefined" || typeof window === "undefined") return;
|
|
4414
|
+
window.addEventListener("pagehide", () => {
|
|
4415
|
+
this.navObserver?.stop();
|
|
4416
|
+
this.exitMiniService();
|
|
4417
|
+
this.flushEngagement();
|
|
4418
|
+
this.flushAnalytics();
|
|
4419
|
+
});
|
|
4420
|
+
document.addEventListener("visibilitychange", () => {
|
|
4421
|
+
if (document.visibilityState === "hidden") {
|
|
4422
|
+
this.flushEngagement();
|
|
4423
|
+
this.engagement?.pause();
|
|
4424
|
+
this.flushAnalytics();
|
|
4425
|
+
} else if (document.visibilityState === "visible") {
|
|
4426
|
+
this.engagement?.resume();
|
|
4427
|
+
}
|
|
4428
|
+
});
|
|
4429
|
+
}
|
|
3684
4430
|
/** Enable auto-launch functionality with configured triggers */
|
|
3685
4431
|
autoLaunch() {
|
|
3686
4432
|
if (!this.initialized) {
|
|
@@ -3698,35 +4444,6 @@ var DeepdotsPopups = class {
|
|
|
3698
4444
|
this.log("Auto-launch enabled");
|
|
3699
4445
|
this.triggers.forEach((trigger) => setupTrigger(this, trigger));
|
|
3700
4446
|
}
|
|
3701
|
-
/** Show a popup immediately (supports legacy ShowOptions and new PopupDefinition) */
|
|
3702
|
-
show(options) {
|
|
3703
|
-
if (!this.initialized) {
|
|
3704
|
-
throw new Error("SDK not initialized. Call init() first.");
|
|
3705
|
-
}
|
|
3706
|
-
const isDefinition = options.id !== void 0;
|
|
3707
|
-
if (isDefinition) {
|
|
3708
|
-
const def = options;
|
|
3709
|
-
this.log("Showing popup (definition)", def);
|
|
3710
|
-
this.surveyToPopupId.set(def.surveyId, def.id);
|
|
3711
|
-
this.lastShown.set(def.id, Date.now());
|
|
3712
|
-
this.renderPopup(def.surveyId, def.productId, def.actions, def.style);
|
|
3713
|
-
this.emitEvent("popup_shown", def.surveyId, { popupId: def.id });
|
|
3714
|
-
} else {
|
|
3715
|
-
const opt = options;
|
|
3716
|
-
this.log("Showing popup (legacy options)", opt);
|
|
3717
|
-
this.renderPopup(opt.surveyId, opt.productId, void 0);
|
|
3718
|
-
this.emitEvent("popup_shown", opt.surveyId);
|
|
3719
|
-
}
|
|
3720
|
-
}
|
|
3721
|
-
/** Mostrar popup por id de definición (usa surveyId interno) */
|
|
3722
|
-
showByPopupId(popupId) {
|
|
3723
|
-
const def = this.popupDefinitions.find((popup) => popup.id === popupId);
|
|
3724
|
-
if (!def) {
|
|
3725
|
-
this.log("Popup definition not found", popupId);
|
|
3726
|
-
return;
|
|
3727
|
-
}
|
|
3728
|
-
this.show(def);
|
|
3729
|
-
}
|
|
3730
4447
|
/** Configure triggers for auto-launching popups (manual) */
|
|
3731
4448
|
configureTriggers(triggers) {
|
|
3732
4449
|
if (!this.initialized) {
|
|
@@ -3795,7 +4512,11 @@ var DeepdotsPopups = class {
|
|
|
3795
4512
|
this.showDefinition(matched);
|
|
3796
4513
|
}
|
|
3797
4514
|
showDefinition(def) {
|
|
3798
|
-
this.
|
|
4515
|
+
this.log("Showing popup (definition)", def);
|
|
4516
|
+
this.surveyToPopupId.set(def.surveyId, def.id);
|
|
4517
|
+
this.lastShown.set(def.id, Date.now());
|
|
4518
|
+
this.renderPopup(def.surveyId, def.productId, def.actions, def.style);
|
|
4519
|
+
this.emitEvent("popup_shown", def.surveyId, { popupId: def.id });
|
|
3799
4520
|
}
|
|
3800
4521
|
shouldShow(def, pathUrl, skipPathCheck = false) {
|
|
3801
4522
|
if (!skipPathCheck && !this.matchesSegmentsPath(def, pathUrl)) {
|
|
@@ -4039,6 +4760,26 @@ var DeepdotsPopups = class {
|
|
|
4039
4760
|
return [];
|
|
4040
4761
|
}
|
|
4041
4762
|
}
|
|
4763
|
+
/** Envía los atributos de contact a la API (`POST /sdk/popups/contact`). */
|
|
4764
|
+
async postContact(body) {
|
|
4765
|
+
const baseUrl = this.baseUrl;
|
|
4766
|
+
if (!baseUrl) return;
|
|
4767
|
+
const endpoint = `${baseUrl}/sdk/popups/contact`;
|
|
4768
|
+
try {
|
|
4769
|
+
const response = await fetch(endpoint, {
|
|
4770
|
+
method: "POST",
|
|
4771
|
+
headers: { "Content-Type": "application/json" },
|
|
4772
|
+
body: JSON.stringify(body)
|
|
4773
|
+
});
|
|
4774
|
+
if (!response.ok) {
|
|
4775
|
+
this.log("Failed to post contact", response.status, response.statusText);
|
|
4776
|
+
return;
|
|
4777
|
+
}
|
|
4778
|
+
this.log("contact \xB7 sent attributes for user:", body.userId);
|
|
4779
|
+
} catch (error) {
|
|
4780
|
+
this.log("Error posting contact", error);
|
|
4781
|
+
}
|
|
4782
|
+
}
|
|
4042
4783
|
/** Notifica eventos de popup a la API */
|
|
4043
4784
|
async postPopupEvent(status, popupId, userId) {
|
|
4044
4785
|
const apiKey = this.config?.apiKey;
|
|
@@ -4060,6 +4801,15 @@ var DeepdotsPopups = class {
|
|
|
4060
4801
|
});
|
|
4061
4802
|
if (!response.ok) {
|
|
4062
4803
|
this.log("Failed to post popup event", response.status, response.statusText);
|
|
4804
|
+
return;
|
|
4805
|
+
}
|
|
4806
|
+
try {
|
|
4807
|
+
const data = await response.json();
|
|
4808
|
+
if (data && typeof data.sessionId === "string") {
|
|
4809
|
+
this.tracking?.setSessionId(data.sessionId);
|
|
4810
|
+
this.log("tracking \xB7 session_id from backend:", data.sessionId);
|
|
4811
|
+
}
|
|
4812
|
+
} catch {
|
|
4063
4813
|
}
|
|
4064
4814
|
} catch (error) {
|
|
4065
4815
|
this.log("Error posting popup event", error);
|
|
@@ -4102,7 +4852,10 @@ var DeepdotsPopups = class {
|
|
|
4102
4852
|
}
|
|
4103
4853
|
/** Render the popup UI */
|
|
4104
4854
|
renderPopup(surveyId, productId, actions, style) {
|
|
4105
|
-
const userId = this.config?.userId;
|
|
4855
|
+
const userId = this.tracking?.getUserId() ?? this.config?.userId;
|
|
4856
|
+
const sessionId = this.tracking?.getSessionId() ?? void 0;
|
|
4857
|
+
const miniService = this.analytics?.getMiniService() ?? void 0;
|
|
4858
|
+
this.log("tracking \xB7 survey identity \u2192", { userId: userId ?? null, sessionId: sessionId ?? null, miniService: miniService ?? null });
|
|
4106
4859
|
if (this.renderer) {
|
|
4107
4860
|
this.renderer.show(
|
|
4108
4861
|
surveyId,
|
|
@@ -4112,22 +4865,29 @@ var DeepdotsPopups = class {
|
|
|
4112
4865
|
() => this.hidePopup(),
|
|
4113
4866
|
this.env,
|
|
4114
4867
|
userId,
|
|
4115
|
-
style
|
|
4868
|
+
style,
|
|
4869
|
+
sessionId,
|
|
4870
|
+
miniService
|
|
4116
4871
|
);
|
|
4117
4872
|
return;
|
|
4118
4873
|
}
|
|
4119
4874
|
if (!this.popupContainer) return;
|
|
4120
|
-
|
|
4121
|
-
|
|
4122
|
-
|
|
4123
|
-
|
|
4124
|
-
|
|
4125
|
-
|
|
4126
|
-
|
|
4127
|
-
|
|
4128
|
-
|
|
4129
|
-
|
|
4130
|
-
|
|
4875
|
+
const container = this.popupContainer;
|
|
4876
|
+
void Promise.resolve().then(() => (init_renderPopup(), renderPopup_exports)).then(({ renderPopup: renderPopup2 }) => {
|
|
4877
|
+
renderPopup2(
|
|
4878
|
+
container,
|
|
4879
|
+
surveyId,
|
|
4880
|
+
productId,
|
|
4881
|
+
actions,
|
|
4882
|
+
(type, id, payload) => this.emitEvent(type, id, payload),
|
|
4883
|
+
() => this.hidePopup(),
|
|
4884
|
+
this.env,
|
|
4885
|
+
userId,
|
|
4886
|
+
style,
|
|
4887
|
+
sessionId,
|
|
4888
|
+
miniService
|
|
4889
|
+
);
|
|
4890
|
+
});
|
|
4131
4891
|
}
|
|
4132
4892
|
/** Hide the popup */
|
|
4133
4893
|
hidePopup() {
|
|
@@ -4144,18 +4904,20 @@ var DeepdotsPopups = class {
|
|
|
4144
4904
|
emitEvent(type, surveyId, data) {
|
|
4145
4905
|
const event = { type, surveyId, timestamp: Date.now(), data };
|
|
4146
4906
|
this.log("Event emitted", event);
|
|
4147
|
-
|
|
4907
|
+
const isPartialClick = type === "popup_clicked" && data?.action === "partial";
|
|
4908
|
+
if (isPartialClick) {
|
|
4148
4909
|
this.markSurveyProgress(surveyId, "PARTIAL");
|
|
4149
4910
|
}
|
|
4150
4911
|
if (type === "survey_completed") {
|
|
4151
4912
|
this.markSurveyAnswered(surveyId);
|
|
4152
4913
|
}
|
|
4153
|
-
if (type === "popup_shown" || type === "survey_completed") {
|
|
4914
|
+
if (type === "popup_shown" || type === "survey_completed" || isPartialClick) {
|
|
4154
4915
|
const popupIdFromData = data?.popupId;
|
|
4155
4916
|
const popupId = popupIdFromData || this.surveyToPopupId.get(surveyId);
|
|
4156
4917
|
if (popupId) {
|
|
4157
4918
|
const userIdFromData = data?.userId;
|
|
4158
|
-
|
|
4919
|
+
const status = type === "popup_shown" ? "SHOWED" /* SHOWED */ : type === "survey_completed" ? "COMPLETED" /* COMPLETED */ : "PARTIAL" /* PARTIAL */;
|
|
4920
|
+
void this.postPopupEvent(status, popupId, userIdFromData || this.tracking?.getUserId() || this.config?.userId);
|
|
4159
4921
|
} else {
|
|
4160
4922
|
this.debug("No popupId available to post event", { type, surveyId });
|
|
4161
4923
|
}
|
|
@@ -4291,4 +5053,41 @@ var DeepdotsPopups = class {
|
|
|
4291
5053
|
return defs.map((def) => this.normalizePopupDefinition(def)).filter((def) => !!def && this.isPopupDefinition(def));
|
|
4292
5054
|
}
|
|
4293
5055
|
};
|
|
5056
|
+
|
|
5057
|
+
// src/index.ts
|
|
5058
|
+
init_tracking_manager();
|
|
5059
|
+
|
|
5060
|
+
// src/react-native/setup.ts
|
|
5061
|
+
function mmkvStorage(mmkv) {
|
|
5062
|
+
return {
|
|
5063
|
+
getItem: (k) => mmkv.getString(k) ?? null,
|
|
5064
|
+
setItem: (k, v) => mmkv.set(k, v),
|
|
5065
|
+
removeItem: (k) => mmkv.delete(k)
|
|
5066
|
+
};
|
|
5067
|
+
}
|
|
5068
|
+
function collectRnDevice(d) {
|
|
5069
|
+
return {
|
|
5070
|
+
device_type: d.isTablet?.() ? "tablet" : "mobile",
|
|
5071
|
+
os_version: d.getSystemVersion?.(),
|
|
5072
|
+
device_model: d.getDeviceId?.(),
|
|
5073
|
+
app_version: d.getVersion?.()
|
|
5074
|
+
};
|
|
5075
|
+
}
|
|
5076
|
+
function setupReactNative(sdk, config, deps = {}) {
|
|
5077
|
+
if (deps.renderer) sdk.setRenderer(deps.renderer);
|
|
5078
|
+
const storage = deps.mmkv ? mmkvStorage(deps.mmkv) : config.storage;
|
|
5079
|
+
const device = deps.deviceInfo ? collectRnDevice(deps.deviceInfo) : config.device;
|
|
5080
|
+
const platform = deps.platform ?? config.platform;
|
|
5081
|
+
sdk.init({ ...config, storage, device, platform });
|
|
5082
|
+
let subscription;
|
|
5083
|
+
if (deps.appState?.addEventListener) {
|
|
5084
|
+
subscription = deps.appState.addEventListener("change", (state) => {
|
|
5085
|
+
if (state === "active") sdk.onForeground();
|
|
5086
|
+
else sdk.onBackground();
|
|
5087
|
+
});
|
|
5088
|
+
}
|
|
5089
|
+
return () => {
|
|
5090
|
+
if (subscription && typeof subscription.remove === "function") subscription.remove();
|
|
5091
|
+
};
|
|
5092
|
+
}
|
|
4294
5093
|
//# sourceMappingURL=index.js.map
|