@magicfeedback/popup-sdk 1.1.1 → 1.1.3
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/dist/{chunk-5VVCJBND.mjs → chunk-6WTOQGET.mjs} +3 -2
- package/dist/chunk-6WTOQGET.mjs.map +1 -0
- package/dist/{chunk-M6PEFLLQ.mjs → chunk-IOAZQPZE.mjs} +3 -2
- package/dist/chunk-IOAZQPZE.mjs.map +1 -0
- package/dist/index.d.mts +122 -18
- package/dist/index.d.ts +122 -18
- package/dist/index.js +320 -57
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +319 -57
- package/dist/index.mjs.map +1 -1
- package/dist/react-native.d.mts +60 -12
- package/dist/react-native.d.ts +60 -12
- package/dist/react-native.js +318 -57
- package/dist/react-native.js.map +1 -1
- package/dist/react-native.mjs +317 -57
- package/dist/react-native.mjs.map +1 -1
- package/dist/{renderPopup-7GGKR5BY.mjs → renderPopup-KBDWWSUU.mjs} +4 -4
- package/dist/renderPopup-KBDWWSUU.mjs.map +1 -0
- package/dist/{renderPopup-Y4WG3575.mjs → renderPopup-YLFRGPQN.mjs} +4 -4
- package/dist/renderPopup-YLFRGPQN.mjs.map +1 -0
- package/package.json +1 -1
- package/dist/chunk-5VVCJBND.mjs.map +0 -1
- package/dist/chunk-M6PEFLLQ.mjs.map +0 -1
- package/dist/renderPopup-7GGKR5BY.mjs.map +0 -1
- package/dist/renderPopup-Y4WG3575.mjs.map +0 -1
package/dist/index.js
CHANGED
|
@@ -51,12 +51,13 @@ function createDefaultStorage() {
|
|
|
51
51
|
}
|
|
52
52
|
return new InMemoryStorage();
|
|
53
53
|
}
|
|
54
|
-
function buildSurveyIdentity(userId, sessionId, miniService = null) {
|
|
54
|
+
function buildSurveyIdentity(userId, sessionId, miniService = null, analyticsFeedbackSessionId = null) {
|
|
55
55
|
const profile = userId ? [{ key: "external-user-id", value: [userId] }] : [];
|
|
56
56
|
const metadata = [];
|
|
57
57
|
if (sessionId) metadata.push({ key: "session_id", value: [sessionId] });
|
|
58
58
|
if (userId) metadata.push({ key: "user_id", value: [userId] });
|
|
59
59
|
if (miniService) metadata.push({ key: "mini_service", value: [miniService] });
|
|
60
|
+
if (analyticsFeedbackSessionId) metadata.push({ key: "deepdots_analytics_feedback_session_id", value: [analyticsFeedbackSessionId] });
|
|
60
61
|
return { profile, metadata };
|
|
61
62
|
}
|
|
62
63
|
function defaultUuid() {
|
|
@@ -2991,7 +2992,7 @@ function ensureResponsiveStyles(_popup) {
|
|
|
2991
2992
|
`;
|
|
2992
2993
|
document.head.appendChild(style);
|
|
2993
2994
|
}
|
|
2994
|
-
async function renderPopup(container, surveyId, productId, actions, emit, onClose, env = "production", userId, style, sessionId, miniService) {
|
|
2995
|
+
async function renderPopup(container, surveyId, productId, actions, emit, onClose, env = "production", userId, style, sessionId, miniService, analyticsFeedbackSessionId) {
|
|
2995
2996
|
let surveyCompletedEmitted = false;
|
|
2996
2997
|
let stylesInjected = false;
|
|
2997
2998
|
const isDark = style?.theme === "dark";
|
|
@@ -3302,7 +3303,7 @@ async function renderPopup(container, surveyId, productId, actions, emit, onClos
|
|
|
3302
3303
|
env: env === "production" ? "prod" : "dev"
|
|
3303
3304
|
}
|
|
3304
3305
|
);
|
|
3305
|
-
const { profile, metadata } = buildSurveyIdentity(userId ?? null, sessionId ?? null, miniService ?? null);
|
|
3306
|
+
const { profile, metadata } = buildSurveyIdentity(userId ?? null, sessionId ?? null, miniService ?? null, analyticsFeedbackSessionId ?? null);
|
|
3306
3307
|
formInstance = import_native.default.form(surveyId, productId, profile, metadata);
|
|
3307
3308
|
const generateOptions = {
|
|
3308
3309
|
addButton: false,
|
|
@@ -3457,6 +3458,7 @@ var index_exports = {};
|
|
|
3457
3458
|
__export(index_exports, {
|
|
3458
3459
|
BrowserPopupRenderer: () => BrowserPopupRenderer,
|
|
3459
3460
|
ContactManager: () => ContactManager,
|
|
3461
|
+
CrashReporter: () => CrashReporter,
|
|
3460
3462
|
DeepdotsPopups: () => DeepdotsPopups,
|
|
3461
3463
|
InMemoryStorage: () => InMemoryStorage,
|
|
3462
3464
|
NoopPopupRenderer: () => NoopPopupRenderer,
|
|
@@ -3465,6 +3467,7 @@ __export(index_exports, {
|
|
|
3465
3467
|
buildAnalyticsFeedbackBody: () => buildAnalyticsFeedbackBody,
|
|
3466
3468
|
buildSurveyHtml: () => buildSurveyHtml,
|
|
3467
3469
|
collectRnDevice: () => collectRnDevice,
|
|
3470
|
+
crashRecordToParams: () => crashRecordToParams,
|
|
3468
3471
|
createDefaultRenderer: () => createDefaultRenderer,
|
|
3469
3472
|
createFeedbackSink: () => createFeedbackSink,
|
|
3470
3473
|
mmkvStorage: () => mmkvStorage,
|
|
@@ -3722,12 +3725,12 @@ var ReactNativePopupRenderer = class {
|
|
|
3722
3725
|
}
|
|
3723
3726
|
init() {
|
|
3724
3727
|
}
|
|
3725
|
-
show(surveyId, productId, _actions, emit, onClose, env = "production", userId, _style, sessionId, miniService) {
|
|
3728
|
+
show(surveyId, productId, _actions, emit, onClose, env = "production", userId, _style, sessionId, miniService, analyticsFeedbackSessionId) {
|
|
3726
3729
|
this.emitFn = emit;
|
|
3727
3730
|
this.onCloseFn = onClose;
|
|
3728
3731
|
this.currentSurveyId = surveyId;
|
|
3729
3732
|
this.partialEmitted = false;
|
|
3730
|
-
const { profile, metadata } = buildSurveyIdentity(userId ?? null, sessionId ?? null, miniService ?? null);
|
|
3733
|
+
const { profile, metadata } = buildSurveyIdentity(userId ?? null, sessionId ?? null, miniService ?? null, analyticsFeedbackSessionId ?? null);
|
|
3731
3734
|
const html = buildSurveyHtml({ surveyId, productId, env, profile, metadata });
|
|
3732
3735
|
if (this.options.onShow) {
|
|
3733
3736
|
this.options.onShow({ surveyId, productId, html });
|
|
@@ -3818,14 +3821,14 @@ var BrowserPopupRenderer = class {
|
|
|
3818
3821
|
document.body.appendChild(this.container);
|
|
3819
3822
|
}
|
|
3820
3823
|
}
|
|
3821
|
-
show(surveyId, productId, actions, emit, onClose, env = "production", userId, style, sessionId, miniService) {
|
|
3824
|
+
show(surveyId, productId, actions, emit, onClose, env = "production", userId, style, sessionId, miniService, analyticsFeedbackSessionId) {
|
|
3822
3825
|
if (this.visible) return;
|
|
3823
3826
|
if (!this.container || !document.body.contains(this.container)) this.init();
|
|
3824
3827
|
if (!this.container) return;
|
|
3825
3828
|
this.visible = true;
|
|
3826
3829
|
const container = this.container;
|
|
3827
3830
|
void Promise.resolve().then(() => (init_renderPopup(), renderPopup_exports)).then(({ renderPopup: renderPopup2 }) => {
|
|
3828
|
-
renderPopup2(container, surveyId, productId, actions, emit, onClose, env, userId, style, sessionId, miniService);
|
|
3831
|
+
renderPopup2(container, surveyId, productId, actions, emit, onClose, env, userId, style, sessionId, miniService, analyticsFeedbackSessionId);
|
|
3829
3832
|
});
|
|
3830
3833
|
}
|
|
3831
3834
|
hide() {
|
|
@@ -3859,14 +3862,20 @@ var AnalyticsManager = class {
|
|
|
3859
3862
|
constructor(options = {}) {
|
|
3860
3863
|
this.events = [];
|
|
3861
3864
|
this.attributes = {};
|
|
3862
|
-
|
|
3863
|
-
this.
|
|
3865
|
+
/** Mini-services activos: nombre → timestamp de entrada. El orden de inserción marca el "más reciente". */
|
|
3866
|
+
this.activeMiniServices = /* @__PURE__ */ new Map();
|
|
3864
3867
|
this.sink = options.sink ?? dryRunSink;
|
|
3865
3868
|
this.now = options.now ?? (() => Date.now());
|
|
3866
3869
|
this.publicKey = options.publicKey;
|
|
3867
3870
|
this.language = options.language;
|
|
3868
3871
|
this.device = options.device;
|
|
3869
3872
|
this.platform = options.platform ?? "web";
|
|
3873
|
+
this.maxBatchSize = options.maxBatchSize ?? 20;
|
|
3874
|
+
this.onFlushNeeded = options.onFlushNeeded;
|
|
3875
|
+
}
|
|
3876
|
+
/** Actualiza campos de device en runtime (p.ej. country/city tras resolver geo). */
|
|
3877
|
+
updateDevice(partial) {
|
|
3878
|
+
this.device = { ...this.device, ...partial };
|
|
3870
3879
|
}
|
|
3871
3880
|
/** Mezcla user attributes (se coercionan a string). Mutable en runtime. */
|
|
3872
3881
|
setUserAttributes(attrs) {
|
|
@@ -3875,24 +3884,38 @@ var AnalyticsManager = class {
|
|
|
3875
3884
|
this.attributes[k] = String(v);
|
|
3876
3885
|
}
|
|
3877
3886
|
}
|
|
3878
|
-
/**
|
|
3887
|
+
/**
|
|
3888
|
+
* Marca el inicio de un mini-service; etiqueta los eventos siguientes con `mini_service`.
|
|
3889
|
+
* Admite varios activos a la vez (concurrencia); el "actual" para etiquetar es el más reciente.
|
|
3890
|
+
* Reentrar con un nombre ya activo refresca su orden y su tiempo de entrada.
|
|
3891
|
+
*/
|
|
3879
3892
|
enterMiniService(name, entryPointType) {
|
|
3880
|
-
this.
|
|
3881
|
-
this.
|
|
3882
|
-
this.track("
|
|
3883
|
-
}
|
|
3884
|
-
/**
|
|
3885
|
-
|
|
3886
|
-
|
|
3887
|
-
|
|
3888
|
-
|
|
3889
|
-
|
|
3890
|
-
|
|
3893
|
+
this.activeMiniServices.delete(name);
|
|
3894
|
+
this.activeMiniServices.set(name, this.now());
|
|
3895
|
+
this.track("deepdots_mini_service_enter", { entry_point_type: entryPointType ?? null });
|
|
3896
|
+
}
|
|
3897
|
+
/**
|
|
3898
|
+
* Cierra el mini-service `name` emitiendo `mini_service_exit` con su duración (#27).
|
|
3899
|
+
* No-op si ese nombre no está activo. Cierre coherente con concurrencia.
|
|
3900
|
+
*/
|
|
3901
|
+
exitMiniService(name) {
|
|
3902
|
+
const enteredAt = this.activeMiniServices.get(name);
|
|
3903
|
+
if (enteredAt === void 0) return;
|
|
3904
|
+
const durationSeconds = Math.max(0, Math.round((this.now() - enteredAt) / 1e3));
|
|
3905
|
+
this.activeMiniServices.delete(name);
|
|
3906
|
+
this.track("deepdots_mini_service_exit", { mini_service: name, duration_seconds: durationSeconds });
|
|
3907
|
+
}
|
|
3908
|
+
/** Cierra TODOS los mini-services activos (orden LIFO). Para el cierre por lifecycle (background/cierre). */
|
|
3909
|
+
exitAllMiniServices() {
|
|
3910
|
+
for (const name of Array.from(this.activeMiniServices.keys()).reverse()) {
|
|
3911
|
+
this.exitMiniService(name);
|
|
3912
|
+
}
|
|
3891
3913
|
}
|
|
3892
3914
|
/** Registra un evento de analítica (modelo GA: nombre + params). */
|
|
3893
3915
|
track(name, params) {
|
|
3916
|
+
const current = this.getMiniService();
|
|
3894
3917
|
const merged = {
|
|
3895
|
-
...
|
|
3918
|
+
...current ? { mini_service: current } : {},
|
|
3896
3919
|
...params ?? {}
|
|
3897
3920
|
};
|
|
3898
3921
|
this.events.push({
|
|
@@ -3900,10 +3923,15 @@ var AnalyticsManager = class {
|
|
|
3900
3923
|
timestamp: this.now(),
|
|
3901
3924
|
params: Object.keys(merged).length ? merged : void 0
|
|
3902
3925
|
});
|
|
3926
|
+
if (this.events.length >= this.maxBatchSize) {
|
|
3927
|
+
this.onFlushNeeded?.();
|
|
3928
|
+
}
|
|
3903
3929
|
}
|
|
3904
|
-
/** Mini-service activo
|
|
3930
|
+
/** Mini-service actual (el más reciente aún activo) para etiquetar eventos + metadata del survey (#33). */
|
|
3905
3931
|
getMiniService() {
|
|
3906
|
-
|
|
3932
|
+
let last = null;
|
|
3933
|
+
for (const name of this.activeMiniServices.keys()) last = name;
|
|
3934
|
+
return last;
|
|
3907
3935
|
}
|
|
3908
3936
|
/** Nº de eventos pendientes de flush. */
|
|
3909
3937
|
pending() {
|
|
@@ -3937,44 +3965,55 @@ var AnalyticsManager = class {
|
|
|
3937
3965
|
// src/analytics/feedback-payload.ts
|
|
3938
3966
|
function pushKV(list, key, value) {
|
|
3939
3967
|
if (value === void 0 || value === null || value === "") return;
|
|
3940
|
-
list.push({ key, value: String(value) });
|
|
3968
|
+
list.push({ key, value: [String(value)] });
|
|
3941
3969
|
}
|
|
3942
3970
|
function buildAnalyticsFeedbackBody(envelope, keys, feedbackSessionId) {
|
|
3943
3971
|
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
3972
|
const profile = [];
|
|
3949
3973
|
pushKV(profile, "external-user-id", envelope.userId);
|
|
3950
3974
|
const metadata = [];
|
|
3951
|
-
pushKV(metadata, "
|
|
3952
|
-
pushKV(metadata, "
|
|
3953
|
-
pushKV(metadata, "
|
|
3954
|
-
pushKV(metadata, "
|
|
3975
|
+
pushKV(metadata, "deepdots_user_id", envelope.userId);
|
|
3976
|
+
pushKV(metadata, "deepdots_session_id", envelope.sessionId);
|
|
3977
|
+
pushKV(metadata, "deepdots_platform", context.platform);
|
|
3978
|
+
pushKV(metadata, "deepdots_language", context.language);
|
|
3955
3979
|
if (context.device) {
|
|
3956
|
-
|
|
3957
|
-
pushKV(metadata, "
|
|
3958
|
-
pushKV(metadata, "
|
|
3959
|
-
pushKV(metadata, "
|
|
3960
|
-
pushKV(metadata, "
|
|
3980
|
+
const d = context.device;
|
|
3981
|
+
pushKV(metadata, "deepdots_device_type", d.device_type);
|
|
3982
|
+
pushKV(metadata, "deepdots_os_version", d.os_version);
|
|
3983
|
+
pushKV(metadata, "deepdots_device_model", d.device_model);
|
|
3984
|
+
pushKV(metadata, "deepdots_app_version", d.app_version);
|
|
3985
|
+
pushKV(metadata, "deepdots_user_agent", d.user_agent);
|
|
3986
|
+
pushKV(metadata, "deepdots_timezone", d.timezone);
|
|
3987
|
+
pushKV(metadata, "deepdots_referrer", d.referrer);
|
|
3988
|
+
pushKV(metadata, "deepdots_viewport_size", d.viewport_size);
|
|
3989
|
+
pushKV(metadata, "deepdots_screen_resolution", d.screen_resolution);
|
|
3990
|
+
pushKV(metadata, "deepdots_pixel_ratio", d.pixel_ratio);
|
|
3991
|
+
pushKV(metadata, "deepdots_entry_type", d.entry_type);
|
|
3992
|
+
pushKV(metadata, "deepdots_page_load_ms", d.page_load_ms);
|
|
3993
|
+
pushKV(metadata, "deepdots_connection_type", d.connection_type);
|
|
3994
|
+
pushKV(metadata, "deepdots_country", d.country);
|
|
3995
|
+
pushKV(metadata, "deepdots_city", d.city);
|
|
3961
3996
|
}
|
|
3962
3997
|
for (const [k, v] of Object.entries(context.attributes ?? {})) {
|
|
3963
3998
|
pushKV(metadata, k, v);
|
|
3964
3999
|
}
|
|
4000
|
+
for (const e of envelope.events) {
|
|
4001
|
+
metadata.push({
|
|
4002
|
+
key: e.name,
|
|
4003
|
+
value: [JSON.stringify({ timestamp: e.timestamp, ...e.params ?? {} })]
|
|
4004
|
+
});
|
|
4005
|
+
}
|
|
3965
4006
|
return {
|
|
3966
4007
|
feedback: {
|
|
3967
4008
|
text: "",
|
|
3968
4009
|
answers: [],
|
|
3969
4010
|
metadata,
|
|
3970
|
-
metrics,
|
|
3971
4011
|
profile,
|
|
3972
4012
|
finished: false
|
|
3973
4013
|
},
|
|
3974
4014
|
publicKey: keys.publicKey,
|
|
3975
4015
|
integration: keys.integration,
|
|
3976
4016
|
completed: false,
|
|
3977
|
-
// finished: false,
|
|
3978
4017
|
...feedbackSessionId ? { sessionId: feedbackSessionId } : {}
|
|
3979
4018
|
};
|
|
3980
4019
|
}
|
|
@@ -3999,6 +4038,7 @@ function createFeedbackSink(options) {
|
|
|
3999
4038
|
if (data?.sessionId && data.sessionId !== feedbackSessionId) {
|
|
4000
4039
|
feedbackSessionId = data.sessionId;
|
|
4001
4040
|
options.log?.("[DeepdotsAnalytics] feedbackSessionId cacheado:", feedbackSessionId);
|
|
4041
|
+
options.onSessionId?.(feedbackSessionId);
|
|
4002
4042
|
}
|
|
4003
4043
|
} catch {
|
|
4004
4044
|
}
|
|
@@ -4112,15 +4152,57 @@ function parseDeviceType(ua) {
|
|
|
4112
4152
|
}
|
|
4113
4153
|
return "desktop";
|
|
4114
4154
|
}
|
|
4155
|
+
function getEntryType() {
|
|
4156
|
+
if (typeof performance === "undefined") return void 0;
|
|
4157
|
+
const entries = performance.getEntriesByType("navigation");
|
|
4158
|
+
if (!entries.length) return void 0;
|
|
4159
|
+
return entries[0].type;
|
|
4160
|
+
}
|
|
4161
|
+
function getPageLoadMs() {
|
|
4162
|
+
if (typeof performance === "undefined") return void 0;
|
|
4163
|
+
const entries = performance.getEntriesByType("navigation");
|
|
4164
|
+
if (!entries.length) return void 0;
|
|
4165
|
+
const load = entries[0].loadEventEnd;
|
|
4166
|
+
return load > 0 ? Math.round(load) : void 0;
|
|
4167
|
+
}
|
|
4168
|
+
function getConnectionType() {
|
|
4169
|
+
if (typeof navigator === "undefined") return void 0;
|
|
4170
|
+
return navigator.connection?.effectiveType ?? void 0;
|
|
4171
|
+
}
|
|
4115
4172
|
function collectDeviceInfo(appVersion) {
|
|
4116
4173
|
const ua = typeof navigator !== "undefined" ? navigator.userAgent : "";
|
|
4117
4174
|
return {
|
|
4118
4175
|
device_type: parseDeviceType(ua),
|
|
4119
4176
|
user_agent: ua || void 0,
|
|
4120
|
-
app_version: appVersion
|
|
4177
|
+
app_version: appVersion,
|
|
4178
|
+
timezone: typeof Intl !== "undefined" ? Intl.DateTimeFormat().resolvedOptions().timeZone : void 0,
|
|
4179
|
+
referrer: typeof document !== "undefined" && document.referrer ? document.referrer : void 0,
|
|
4180
|
+
viewport_size: typeof window !== "undefined" ? `${window.innerWidth}x${window.innerHeight}` : void 0,
|
|
4181
|
+
screen_resolution: typeof screen !== "undefined" ? `${screen.width}x${screen.height}` : void 0,
|
|
4182
|
+
pixel_ratio: typeof window !== "undefined" ? window.devicePixelRatio : void 0,
|
|
4183
|
+
entry_type: getEntryType(),
|
|
4184
|
+
page_load_ms: getPageLoadMs(),
|
|
4185
|
+
connection_type: getConnectionType()
|
|
4121
4186
|
};
|
|
4122
4187
|
}
|
|
4123
4188
|
|
|
4189
|
+
// src/analytics/geo-info.ts
|
|
4190
|
+
async function collectGeoInfo(fetchImpl) {
|
|
4191
|
+
const f = fetchImpl ?? (typeof fetch !== "undefined" ? fetch : void 0);
|
|
4192
|
+
if (!f) return null;
|
|
4193
|
+
try {
|
|
4194
|
+
const res = await f("https://ipapi.co/json/");
|
|
4195
|
+
if (!res.ok) return null;
|
|
4196
|
+
const data = await res.json();
|
|
4197
|
+
const country = data.country_code || void 0;
|
|
4198
|
+
const city = data.city || void 0;
|
|
4199
|
+
if (!country && !city) return null;
|
|
4200
|
+
return { country, city };
|
|
4201
|
+
} catch {
|
|
4202
|
+
return null;
|
|
4203
|
+
}
|
|
4204
|
+
}
|
|
4205
|
+
|
|
4124
4206
|
// src/analytics/engagement-tracker.ts
|
|
4125
4207
|
var EngagementTracker = class {
|
|
4126
4208
|
constructor(now = () => Date.now()) {
|
|
@@ -4189,9 +4271,137 @@ var ContactManager = class {
|
|
|
4189
4271
|
}
|
|
4190
4272
|
};
|
|
4191
4273
|
|
|
4274
|
+
// src/analytics/crash-reporter.ts
|
|
4275
|
+
var STACK_MAX = 8e3;
|
|
4276
|
+
var QUEUE_KEY = "deepdots.crash.queue";
|
|
4277
|
+
var MAX_QUEUED = 20;
|
|
4278
|
+
function crashRecordToParams(r) {
|
|
4279
|
+
const params = {
|
|
4280
|
+
crashed_at: r.crashedAt,
|
|
4281
|
+
crash_type: r.crashType,
|
|
4282
|
+
message: r.message,
|
|
4283
|
+
stack: r.stack,
|
|
4284
|
+
fatal: r.fatal,
|
|
4285
|
+
handled: r.handled,
|
|
4286
|
+
severity: r.severity
|
|
4287
|
+
};
|
|
4288
|
+
if (r.sessionId) params.crashed_session_id = r.sessionId;
|
|
4289
|
+
if (r.appVersion) params.crashed_app_version = r.appVersion;
|
|
4290
|
+
if (r.osVersion) params.crashed_os_version = r.osVersion;
|
|
4291
|
+
if (r.deviceModel) params.crashed_device_model = r.deviceModel;
|
|
4292
|
+
if (r.context) {
|
|
4293
|
+
for (const [k, v] of Object.entries(r.context)) params[`ctx_${k}`] = v;
|
|
4294
|
+
}
|
|
4295
|
+
return params;
|
|
4296
|
+
}
|
|
4297
|
+
var CrashReporter = class {
|
|
4298
|
+
constructor(options) {
|
|
4299
|
+
this.options = options;
|
|
4300
|
+
this.now = options.now ?? (() => Date.now());
|
|
4301
|
+
}
|
|
4302
|
+
isEnabled() {
|
|
4303
|
+
return this.options.enabled ? this.options.enabled() : true;
|
|
4304
|
+
}
|
|
4305
|
+
/** Construye un CrashRecord capturando contexto en el momento del crash. */
|
|
4306
|
+
buildRecord(error, severity, handled, fatal, context) {
|
|
4307
|
+
const err = error;
|
|
4308
|
+
const isErr = error instanceof Error;
|
|
4309
|
+
const dev = this.options.device();
|
|
4310
|
+
const ctx = context ? Object.fromEntries(Object.entries(context).map(([k, v]) => [k, String(v)])) : void 0;
|
|
4311
|
+
return {
|
|
4312
|
+
crashedAt: this.now(),
|
|
4313
|
+
crashType: isErr && err.name || "Error",
|
|
4314
|
+
message: isErr ? String(err.message ?? "") : String(error),
|
|
4315
|
+
stack: (isErr && typeof err.stack === "string" ? err.stack : "").slice(0, STACK_MAX),
|
|
4316
|
+
fatal,
|
|
4317
|
+
handled,
|
|
4318
|
+
severity,
|
|
4319
|
+
sessionId: this.options.sessionId(),
|
|
4320
|
+
appVersion: dev.appVersion,
|
|
4321
|
+
osVersion: dev.osVersion,
|
|
4322
|
+
deviceModel: dev.deviceModel,
|
|
4323
|
+
context: ctx
|
|
4324
|
+
};
|
|
4325
|
+
}
|
|
4326
|
+
/** API pública del host: reporta un error (app viva) → emite el evento ya. */
|
|
4327
|
+
reportError(error, options = {}) {
|
|
4328
|
+
if (!this.isEnabled()) return;
|
|
4329
|
+
const severity = options.severity ?? "error";
|
|
4330
|
+
const handled = options.handled ?? true;
|
|
4331
|
+
const fatal = severity === "fatal";
|
|
4332
|
+
const record = this.buildRecord(error, severity, handled, fatal, options.context);
|
|
4333
|
+
this.options.emit(crashRecordToParams(record));
|
|
4334
|
+
}
|
|
4335
|
+
/** Captura un error no manejado (fatal) → persiste a disco para replay en el siguiente arranque. */
|
|
4336
|
+
captureUnhandled(error) {
|
|
4337
|
+
this.persist(this.buildRecord(error, "fatal", false, true));
|
|
4338
|
+
}
|
|
4339
|
+
/** Persiste un crash no capturado a disco para reenviarlo en el siguiente arranque. */
|
|
4340
|
+
persist(record) {
|
|
4341
|
+
let queue = [];
|
|
4342
|
+
try {
|
|
4343
|
+
const raw = this.options.storage.getItem(QUEUE_KEY);
|
|
4344
|
+
if (raw) queue = JSON.parse(raw);
|
|
4345
|
+
if (!Array.isArray(queue)) queue = [];
|
|
4346
|
+
} catch {
|
|
4347
|
+
queue = [];
|
|
4348
|
+
}
|
|
4349
|
+
queue.push(record);
|
|
4350
|
+
if (queue.length > MAX_QUEUED) queue = queue.slice(queue.length - MAX_QUEUED);
|
|
4351
|
+
try {
|
|
4352
|
+
this.options.storage.setItem(QUEUE_KEY, JSON.stringify(queue));
|
|
4353
|
+
} catch {
|
|
4354
|
+
}
|
|
4355
|
+
}
|
|
4356
|
+
/** Lee y vacía la cola de crashes pendientes (llamado en init para el replay). */
|
|
4357
|
+
drainPendingCrashes() {
|
|
4358
|
+
let queue = [];
|
|
4359
|
+
try {
|
|
4360
|
+
const raw = this.options.storage.getItem(QUEUE_KEY);
|
|
4361
|
+
if (raw) queue = JSON.parse(raw);
|
|
4362
|
+
if (!Array.isArray(queue)) queue = [];
|
|
4363
|
+
} catch {
|
|
4364
|
+
queue = [];
|
|
4365
|
+
}
|
|
4366
|
+
if (queue.length) {
|
|
4367
|
+
try {
|
|
4368
|
+
this.options.storage.removeItem(QUEUE_KEY);
|
|
4369
|
+
} catch {
|
|
4370
|
+
}
|
|
4371
|
+
}
|
|
4372
|
+
return queue;
|
|
4373
|
+
}
|
|
4374
|
+
/** Instala los handlers globales de errores no capturados (no-op sin `window`). */
|
|
4375
|
+
install() {
|
|
4376
|
+
if (typeof window === "undefined") return;
|
|
4377
|
+
window.addEventListener("error", (e) => {
|
|
4378
|
+
if (!this.isEnabled()) return;
|
|
4379
|
+
this.captureUnhandled(e.error ?? e.message ?? "error");
|
|
4380
|
+
});
|
|
4381
|
+
window.addEventListener("unhandledrejection", (e) => {
|
|
4382
|
+
if (!this.isEnabled()) return;
|
|
4383
|
+
this.captureUnhandled(e.reason ?? "unhandledrejection");
|
|
4384
|
+
});
|
|
4385
|
+
}
|
|
4386
|
+
/** Engancha `global.ErrorUtils` de RN (no hay `window`): persiste y encadena al handler previo. */
|
|
4387
|
+
installReactNative(errorUtils) {
|
|
4388
|
+
const previous = errorUtils.getGlobalHandler?.();
|
|
4389
|
+
errorUtils.setGlobalHandler((error, isFatal) => {
|
|
4390
|
+
if (this.isEnabled()) this.captureUnhandled(error);
|
|
4391
|
+
previous?.(error, isFatal);
|
|
4392
|
+
});
|
|
4393
|
+
}
|
|
4394
|
+
/** @internal Test seam: ejercita el camino de persistencia (window handlers no son testeables en vitest). */
|
|
4395
|
+
_persistForTest(error) {
|
|
4396
|
+
this.captureUnhandled(error);
|
|
4397
|
+
}
|
|
4398
|
+
};
|
|
4399
|
+
|
|
4192
4400
|
// src/core/deepdots-popups.ts
|
|
4193
4401
|
var EXIT_QUEUE_STORAGE_KEY = "__deepdots_exit_popup_queue__";
|
|
4194
4402
|
var DAY_IN_MS = 24 * 60 * 60 * 1e3;
|
|
4403
|
+
var ANALYTICS_MAX_BATCH_SIZE = 20;
|
|
4404
|
+
var ANALYTICS_FLUSH_INTERVAL_MS = 3e4;
|
|
4195
4405
|
var DeepdotsPopups = class {
|
|
4196
4406
|
constructor() {
|
|
4197
4407
|
this.config = null;
|
|
@@ -4217,6 +4427,12 @@ var DeepdotsPopups = class {
|
|
|
4217
4427
|
this.tracking = null;
|
|
4218
4428
|
/** Capa de analytics (canal separado del feedback). Null hasta init(). */
|
|
4219
4429
|
this.analytics = null;
|
|
4430
|
+
/** feedbackSessionId cacheado del canal de analytics (devuelto por POST /sdk/feedback). */
|
|
4431
|
+
this.analyticsFeedbackSessionId = void 0;
|
|
4432
|
+
/** Timer del flush periódico de analytics (cada ANALYTICS_FLUSH_INTERVAL_MS). */
|
|
4433
|
+
this.analyticsFlushTimer = void 0;
|
|
4434
|
+
/** Crash & error reporting (#14–17). Null hasta init(). */
|
|
4435
|
+
this.crashReporter = null;
|
|
4220
4436
|
/** Observador de navegación (Fase 2): emite page_view por el canal de analytics. */
|
|
4221
4437
|
this.navObserver = null;
|
|
4222
4438
|
/** Tiempo activo (engagement time, #8). */
|
|
@@ -4263,21 +4479,49 @@ var DeepdotsPopups = class {
|
|
|
4263
4479
|
const analyticsSink = config.analytics ? createFeedbackSink({
|
|
4264
4480
|
baseUrl: this.baseUrl,
|
|
4265
4481
|
keys: config.analytics,
|
|
4266
|
-
log: (...a) => this.log(...a)
|
|
4482
|
+
log: (...a) => this.log(...a),
|
|
4483
|
+
onSessionId: (id) => {
|
|
4484
|
+
this.analyticsFeedbackSessionId = id;
|
|
4485
|
+
}
|
|
4267
4486
|
}) : void 0;
|
|
4487
|
+
const device = config.device ?? collectDeviceInfo(config.appVersion);
|
|
4268
4488
|
this.analytics = new AnalyticsManager({
|
|
4269
4489
|
sink: analyticsSink,
|
|
4270
4490
|
publicKey: config.analytics?.publicKey ?? this.config.apiKey,
|
|
4271
4491
|
language: typeof navigator !== "undefined" ? navigator.language : void 0,
|
|
4272
4492
|
platform: config.platform ?? "web",
|
|
4273
|
-
device
|
|
4493
|
+
device,
|
|
4494
|
+
maxBatchSize: ANALYTICS_MAX_BATCH_SIZE,
|
|
4495
|
+
onFlushNeeded: () => this.flushAnalytics()
|
|
4496
|
+
});
|
|
4497
|
+
this.crashReporter = new CrashReporter({
|
|
4498
|
+
storage,
|
|
4499
|
+
emit: (params) => this.track("deepdots_app_crash", params),
|
|
4500
|
+
device: () => ({ appVersion: device.app_version, osVersion: device.os_version, deviceModel: device.device_model }),
|
|
4501
|
+
sessionId: () => this.tracking?.getSessionId() ?? null,
|
|
4502
|
+
now: () => Date.now(),
|
|
4503
|
+
enabled: () => this.tracking?.isTrackingEnabled() ?? false
|
|
4504
|
+
});
|
|
4505
|
+
if (this.tracking?.isTrackingEnabled()) {
|
|
4506
|
+
this.crashReporter.install();
|
|
4507
|
+
}
|
|
4508
|
+
collectGeoInfo().then((geo) => {
|
|
4509
|
+
if (geo) this.analytics?.updateDevice(geo);
|
|
4510
|
+
}).catch(() => {
|
|
4274
4511
|
});
|
|
4275
4512
|
this.navObserver = new NavigationObserver();
|
|
4276
|
-
this.navObserver.onVisit((v) => this.track("
|
|
4513
|
+
this.navObserver.onVisit((v) => this.track("deepdots_page_view", { screen: v.screen, duration_seconds: v.durationSeconds }));
|
|
4277
4514
|
this.navObserver.install();
|
|
4278
4515
|
this.engagement = new EngagementTracker();
|
|
4279
4516
|
this.engagement.resume();
|
|
4280
4517
|
this.setupAnalyticsFlush();
|
|
4518
|
+
this.track("deepdots_session_start", {});
|
|
4519
|
+
const pendingCrashes = this.crashReporter.drainPendingCrashes();
|
|
4520
|
+
if (this.tracking?.isTrackingEnabled()) {
|
|
4521
|
+
for (const rec of pendingCrashes) {
|
|
4522
|
+
this.track("deepdots_app_crash", crashRecordToParams(rec));
|
|
4523
|
+
}
|
|
4524
|
+
}
|
|
4281
4525
|
this.initialized = true;
|
|
4282
4526
|
this.log("SDK initialized", this.config);
|
|
4283
4527
|
if (this.renderer.init) this.renderer.init();
|
|
@@ -4318,6 +4562,16 @@ var DeepdotsPopups = class {
|
|
|
4318
4562
|
if (!this.tracking?.isTrackingEnabled()) return;
|
|
4319
4563
|
this.analytics?.setUserAttributes(attributes);
|
|
4320
4564
|
}
|
|
4565
|
+
/** Reporta un error del host (manejado o no) → evento `deepdots_app_crash`. No-op si tracking off. */
|
|
4566
|
+
reportError(error, options) {
|
|
4567
|
+
if (!this.tracking?.isTrackingEnabled()) return;
|
|
4568
|
+
this.crashReporter?.reportError(error, options);
|
|
4569
|
+
}
|
|
4570
|
+
/** Engancha `global.ErrorUtils` de RN para capturar errores JS no manejados. No-op si tracking off. */
|
|
4571
|
+
installReactNativeCrashHandler(errorUtils) {
|
|
4572
|
+
if (!this.tracking?.isTrackingEnabled()) return;
|
|
4573
|
+
this.crashReporter?.installReactNative(errorUtils);
|
|
4574
|
+
}
|
|
4321
4575
|
/**
|
|
4322
4576
|
* Info interna del usuario que solo conoce el host (idioma, edad, plan…), persistida en el
|
|
4323
4577
|
* Contact del backend para segmentación/targeting de popups. Identificada por el `userId` del
|
|
@@ -4335,23 +4589,23 @@ var DeepdotsPopups = class {
|
|
|
4335
4589
|
this.analytics?.enterMiniService(name, entryPointType);
|
|
4336
4590
|
this.log("analytics \xB7 enterMiniService:", name, entryPointType ?? "");
|
|
4337
4591
|
}
|
|
4338
|
-
/** Cierra el mini-service
|
|
4339
|
-
exitMiniService() {
|
|
4592
|
+
/** Cierra el mini-service `name` (emite `mini_service_exit` con duración, #27). No-op si tracking off o si ese no está activo. */
|
|
4593
|
+
exitMiniService(name) {
|
|
4340
4594
|
if (!this.tracking?.isTrackingEnabled()) return;
|
|
4341
|
-
this.analytics?.exitMiniService();
|
|
4342
|
-
this.log("analytics \xB7 exitMiniService");
|
|
4595
|
+
this.analytics?.exitMiniService(name);
|
|
4596
|
+
this.log("analytics \xB7 exitMiniService:", name);
|
|
4343
4597
|
}
|
|
4344
4598
|
/** Findability (#31/#35): registra una búsqueda. `has_results` se deriva de `resultsCount`. */
|
|
4345
4599
|
trackSearch(query, resultsCount, params) {
|
|
4346
|
-
this.track("
|
|
4600
|
+
this.track("deepdots_search", { query, results_count: resultsCount, has_results: resultsCount > 0, ...params ?? {} });
|
|
4347
4601
|
}
|
|
4348
4602
|
/** Findability friction (#34/#35): señal de fricción con su `friction_topic`. */
|
|
4349
4603
|
trackFindabilityFriction(frictionTopic, params) {
|
|
4350
|
-
this.track("
|
|
4604
|
+
this.track("deepdots_findability_friction", { friction_topic: frictionTopic, ...params ?? {} });
|
|
4351
4605
|
}
|
|
4352
4606
|
/** Funnel: un paso del embudo, correlacionado por `taskId`. El backend reconstruye conversión/drop-off/tiempo. */
|
|
4353
4607
|
trackFunnelStep(funnel, step, taskId, params) {
|
|
4354
|
-
this.track("
|
|
4608
|
+
this.track("deepdots_funnel_step", { funnel, step, task_id: taskId, ...params ?? {} });
|
|
4355
4609
|
}
|
|
4356
4610
|
/**
|
|
4357
4611
|
* Navegación MANUAL (entornos sin History API, p. ej. React Native con React Navigation).
|
|
@@ -4377,7 +4631,7 @@ var DeepdotsPopups = class {
|
|
|
4377
4631
|
onBackground() {
|
|
4378
4632
|
this.navObserver?.stop();
|
|
4379
4633
|
this.navStarted = false;
|
|
4380
|
-
this.
|
|
4634
|
+
if (this.tracking?.isTrackingEnabled()) this.analytics?.exitAllMiniServices();
|
|
4381
4635
|
this.flushEngagement();
|
|
4382
4636
|
this.engagement?.pause();
|
|
4383
4637
|
this.flushAnalytics();
|
|
@@ -4400,7 +4654,7 @@ var DeepdotsPopups = class {
|
|
|
4400
4654
|
flushEngagement() {
|
|
4401
4655
|
if (!this.tracking?.isTrackingEnabled()) return;
|
|
4402
4656
|
const ms = this.engagement?.consume() ?? 0;
|
|
4403
|
-
if (ms > 0) this.track("
|
|
4657
|
+
if (ms > 0) this.track("deepdots_user_engagement", { engagement_time_msec: ms });
|
|
4404
4658
|
}
|
|
4405
4659
|
analyticsIdentity() {
|
|
4406
4660
|
return {
|
|
@@ -4411,9 +4665,11 @@ var DeepdotsPopups = class {
|
|
|
4411
4665
|
/** Flush automático al ocultar/cerrar la página (no perder el lote pendiente). */
|
|
4412
4666
|
setupAnalyticsFlush() {
|
|
4413
4667
|
if (typeof document === "undefined" || typeof window === "undefined") return;
|
|
4668
|
+
this.analyticsFlushTimer = setInterval(() => this.flushAnalytics(), ANALYTICS_FLUSH_INTERVAL_MS);
|
|
4414
4669
|
window.addEventListener("pagehide", () => {
|
|
4670
|
+
clearInterval(this.analyticsFlushTimer);
|
|
4415
4671
|
this.navObserver?.stop();
|
|
4416
|
-
this.
|
|
4672
|
+
if (this.tracking?.isTrackingEnabled()) this.analytics?.exitAllMiniServices();
|
|
4417
4673
|
this.flushEngagement();
|
|
4418
4674
|
this.flushAnalytics();
|
|
4419
4675
|
});
|
|
@@ -4855,7 +5111,8 @@ var DeepdotsPopups = class {
|
|
|
4855
5111
|
const userId = this.tracking?.getUserId() ?? this.config?.userId;
|
|
4856
5112
|
const sessionId = this.tracking?.getSessionId() ?? void 0;
|
|
4857
5113
|
const miniService = this.analytics?.getMiniService() ?? void 0;
|
|
4858
|
-
|
|
5114
|
+
const analyticsFeedbackSessionId = this.analyticsFeedbackSessionId;
|
|
5115
|
+
this.log("tracking \xB7 survey identity \u2192", { userId: userId ?? null, sessionId: sessionId ?? null, miniService: miniService ?? null, analyticsFeedbackSessionId: analyticsFeedbackSessionId ?? null });
|
|
4859
5116
|
if (this.renderer) {
|
|
4860
5117
|
this.renderer.show(
|
|
4861
5118
|
surveyId,
|
|
@@ -4867,7 +5124,8 @@ var DeepdotsPopups = class {
|
|
|
4867
5124
|
userId,
|
|
4868
5125
|
style,
|
|
4869
5126
|
sessionId,
|
|
4870
|
-
miniService
|
|
5127
|
+
miniService,
|
|
5128
|
+
analyticsFeedbackSessionId
|
|
4871
5129
|
);
|
|
4872
5130
|
return;
|
|
4873
5131
|
}
|
|
@@ -4885,7 +5143,8 @@ var DeepdotsPopups = class {
|
|
|
4885
5143
|
userId,
|
|
4886
5144
|
style,
|
|
4887
5145
|
sessionId,
|
|
4888
|
-
miniService
|
|
5146
|
+
miniService,
|
|
5147
|
+
analyticsFeedbackSessionId
|
|
4889
5148
|
);
|
|
4890
5149
|
});
|
|
4891
5150
|
}
|
|
@@ -5079,6 +5338,10 @@ function setupReactNative(sdk, config, deps = {}) {
|
|
|
5079
5338
|
const device = deps.deviceInfo ? collectRnDevice(deps.deviceInfo) : config.device;
|
|
5080
5339
|
const platform = deps.platform ?? config.platform;
|
|
5081
5340
|
sdk.init({ ...config, storage, device, platform });
|
|
5341
|
+
const errorUtils = deps.errorUtils ?? globalThis.ErrorUtils;
|
|
5342
|
+
if (errorUtils && typeof errorUtils.setGlobalHandler === "function") {
|
|
5343
|
+
sdk.installReactNativeCrashHandler(errorUtils);
|
|
5344
|
+
}
|
|
5082
5345
|
let subscription;
|
|
5083
5346
|
if (deps.appState?.addEventListener) {
|
|
5084
5347
|
subscription = deps.appState.addEventListener("change", (state) => {
|