@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.mjs
CHANGED
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
TrackingManager,
|
|
5
5
|
buildSurveyIdentity,
|
|
6
6
|
createDefaultStorage
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-6WTOQGET.mjs";
|
|
8
8
|
|
|
9
9
|
// src/types/index.ts
|
|
10
10
|
var POPUP_TRIGGER_CONDITION_STATUSES = [
|
|
@@ -253,12 +253,12 @@ var ReactNativePopupRenderer = class {
|
|
|
253
253
|
}
|
|
254
254
|
init() {
|
|
255
255
|
}
|
|
256
|
-
show(surveyId, productId, _actions, emit, onClose, env = "production", userId, _style, sessionId, miniService) {
|
|
256
|
+
show(surveyId, productId, _actions, emit, onClose, env = "production", userId, _style, sessionId, miniService, analyticsFeedbackSessionId) {
|
|
257
257
|
this.emitFn = emit;
|
|
258
258
|
this.onCloseFn = onClose;
|
|
259
259
|
this.currentSurveyId = surveyId;
|
|
260
260
|
this.partialEmitted = false;
|
|
261
|
-
const { profile, metadata } = buildSurveyIdentity(userId ?? null, sessionId ?? null, miniService ?? null);
|
|
261
|
+
const { profile, metadata } = buildSurveyIdentity(userId ?? null, sessionId ?? null, miniService ?? null, analyticsFeedbackSessionId ?? null);
|
|
262
262
|
const html = buildSurveyHtml({ surveyId, productId, env, profile, metadata });
|
|
263
263
|
if (this.options.onShow) {
|
|
264
264
|
this.options.onShow({ surveyId, productId, html });
|
|
@@ -349,14 +349,14 @@ var BrowserPopupRenderer = class {
|
|
|
349
349
|
document.body.appendChild(this.container);
|
|
350
350
|
}
|
|
351
351
|
}
|
|
352
|
-
show(surveyId, productId, actions, emit, onClose, env = "production", userId, style, sessionId, miniService) {
|
|
352
|
+
show(surveyId, productId, actions, emit, onClose, env = "production", userId, style, sessionId, miniService, analyticsFeedbackSessionId) {
|
|
353
353
|
if (this.visible) return;
|
|
354
354
|
if (!this.container || !document.body.contains(this.container)) this.init();
|
|
355
355
|
if (!this.container) return;
|
|
356
356
|
this.visible = true;
|
|
357
357
|
const container = this.container;
|
|
358
|
-
void import("./renderPopup-
|
|
359
|
-
renderPopup(container, surveyId, productId, actions, emit, onClose, env, userId, style, sessionId, miniService);
|
|
358
|
+
void import("./renderPopup-YLFRGPQN.mjs").then(({ renderPopup }) => {
|
|
359
|
+
renderPopup(container, surveyId, productId, actions, emit, onClose, env, userId, style, sessionId, miniService, analyticsFeedbackSessionId);
|
|
360
360
|
});
|
|
361
361
|
}
|
|
362
362
|
hide() {
|
|
@@ -387,14 +387,20 @@ var AnalyticsManager = class {
|
|
|
387
387
|
constructor(options = {}) {
|
|
388
388
|
this.events = [];
|
|
389
389
|
this.attributes = {};
|
|
390
|
-
|
|
391
|
-
this.
|
|
390
|
+
/** Mini-services activos: nombre → timestamp de entrada. El orden de inserción marca el "más reciente". */
|
|
391
|
+
this.activeMiniServices = /* @__PURE__ */ new Map();
|
|
392
392
|
this.sink = options.sink ?? dryRunSink;
|
|
393
393
|
this.now = options.now ?? (() => Date.now());
|
|
394
394
|
this.publicKey = options.publicKey;
|
|
395
395
|
this.language = options.language;
|
|
396
396
|
this.device = options.device;
|
|
397
397
|
this.platform = options.platform ?? "web";
|
|
398
|
+
this.maxBatchSize = options.maxBatchSize ?? 20;
|
|
399
|
+
this.onFlushNeeded = options.onFlushNeeded;
|
|
400
|
+
}
|
|
401
|
+
/** Actualiza campos de device en runtime (p.ej. country/city tras resolver geo). */
|
|
402
|
+
updateDevice(partial) {
|
|
403
|
+
this.device = { ...this.device, ...partial };
|
|
398
404
|
}
|
|
399
405
|
/** Mezcla user attributes (se coercionan a string). Mutable en runtime. */
|
|
400
406
|
setUserAttributes(attrs) {
|
|
@@ -403,24 +409,38 @@ var AnalyticsManager = class {
|
|
|
403
409
|
this.attributes[k] = String(v);
|
|
404
410
|
}
|
|
405
411
|
}
|
|
406
|
-
/**
|
|
412
|
+
/**
|
|
413
|
+
* Marca el inicio de un mini-service; etiqueta los eventos siguientes con `mini_service`.
|
|
414
|
+
* Admite varios activos a la vez (concurrencia); el "actual" para etiquetar es el más reciente.
|
|
415
|
+
* Reentrar con un nombre ya activo refresca su orden y su tiempo de entrada.
|
|
416
|
+
*/
|
|
407
417
|
enterMiniService(name, entryPointType) {
|
|
408
|
-
this.
|
|
409
|
-
this.
|
|
410
|
-
this.track("
|
|
411
|
-
}
|
|
412
|
-
/**
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
418
|
+
this.activeMiniServices.delete(name);
|
|
419
|
+
this.activeMiniServices.set(name, this.now());
|
|
420
|
+
this.track("deepdots_mini_service_enter", { entry_point_type: entryPointType ?? null });
|
|
421
|
+
}
|
|
422
|
+
/**
|
|
423
|
+
* Cierra el mini-service `name` emitiendo `mini_service_exit` con su duración (#27).
|
|
424
|
+
* No-op si ese nombre no está activo. Cierre coherente con concurrencia.
|
|
425
|
+
*/
|
|
426
|
+
exitMiniService(name) {
|
|
427
|
+
const enteredAt = this.activeMiniServices.get(name);
|
|
428
|
+
if (enteredAt === void 0) return;
|
|
429
|
+
const durationSeconds = Math.max(0, Math.round((this.now() - enteredAt) / 1e3));
|
|
430
|
+
this.activeMiniServices.delete(name);
|
|
431
|
+
this.track("deepdots_mini_service_exit", { mini_service: name, duration_seconds: durationSeconds });
|
|
432
|
+
}
|
|
433
|
+
/** Cierra TODOS los mini-services activos (orden LIFO). Para el cierre por lifecycle (background/cierre). */
|
|
434
|
+
exitAllMiniServices() {
|
|
435
|
+
for (const name of Array.from(this.activeMiniServices.keys()).reverse()) {
|
|
436
|
+
this.exitMiniService(name);
|
|
437
|
+
}
|
|
419
438
|
}
|
|
420
439
|
/** Registra un evento de analítica (modelo GA: nombre + params). */
|
|
421
440
|
track(name, params) {
|
|
441
|
+
const current = this.getMiniService();
|
|
422
442
|
const merged = {
|
|
423
|
-
...
|
|
443
|
+
...current ? { mini_service: current } : {},
|
|
424
444
|
...params ?? {}
|
|
425
445
|
};
|
|
426
446
|
this.events.push({
|
|
@@ -428,10 +448,15 @@ var AnalyticsManager = class {
|
|
|
428
448
|
timestamp: this.now(),
|
|
429
449
|
params: Object.keys(merged).length ? merged : void 0
|
|
430
450
|
});
|
|
451
|
+
if (this.events.length >= this.maxBatchSize) {
|
|
452
|
+
this.onFlushNeeded?.();
|
|
453
|
+
}
|
|
431
454
|
}
|
|
432
|
-
/** Mini-service activo
|
|
455
|
+
/** Mini-service actual (el más reciente aún activo) para etiquetar eventos + metadata del survey (#33). */
|
|
433
456
|
getMiniService() {
|
|
434
|
-
|
|
457
|
+
let last = null;
|
|
458
|
+
for (const name of this.activeMiniServices.keys()) last = name;
|
|
459
|
+
return last;
|
|
435
460
|
}
|
|
436
461
|
/** Nº de eventos pendientes de flush. */
|
|
437
462
|
pending() {
|
|
@@ -465,44 +490,55 @@ var AnalyticsManager = class {
|
|
|
465
490
|
// src/analytics/feedback-payload.ts
|
|
466
491
|
function pushKV(list, key, value) {
|
|
467
492
|
if (value === void 0 || value === null || value === "") return;
|
|
468
|
-
list.push({ key, value: String(value) });
|
|
493
|
+
list.push({ key, value: [String(value)] });
|
|
469
494
|
}
|
|
470
495
|
function buildAnalyticsFeedbackBody(envelope, keys, feedbackSessionId) {
|
|
471
496
|
const { context } = envelope;
|
|
472
|
-
const metrics = envelope.events.map((e) => ({
|
|
473
|
-
key: e.name,
|
|
474
|
-
value: JSON.stringify({ timestamp: e.timestamp, ...e.params ?? {} })
|
|
475
|
-
}));
|
|
476
497
|
const profile = [];
|
|
477
498
|
pushKV(profile, "external-user-id", envelope.userId);
|
|
478
499
|
const metadata = [];
|
|
479
|
-
pushKV(metadata, "
|
|
480
|
-
pushKV(metadata, "
|
|
481
|
-
pushKV(metadata, "
|
|
482
|
-
pushKV(metadata, "
|
|
500
|
+
pushKV(metadata, "deepdots_user_id", envelope.userId);
|
|
501
|
+
pushKV(metadata, "deepdots_session_id", envelope.sessionId);
|
|
502
|
+
pushKV(metadata, "deepdots_platform", context.platform);
|
|
503
|
+
pushKV(metadata, "deepdots_language", context.language);
|
|
483
504
|
if (context.device) {
|
|
484
|
-
|
|
485
|
-
pushKV(metadata, "
|
|
486
|
-
pushKV(metadata, "
|
|
487
|
-
pushKV(metadata, "
|
|
488
|
-
pushKV(metadata, "
|
|
505
|
+
const d = context.device;
|
|
506
|
+
pushKV(metadata, "deepdots_device_type", d.device_type);
|
|
507
|
+
pushKV(metadata, "deepdots_os_version", d.os_version);
|
|
508
|
+
pushKV(metadata, "deepdots_device_model", d.device_model);
|
|
509
|
+
pushKV(metadata, "deepdots_app_version", d.app_version);
|
|
510
|
+
pushKV(metadata, "deepdots_user_agent", d.user_agent);
|
|
511
|
+
pushKV(metadata, "deepdots_timezone", d.timezone);
|
|
512
|
+
pushKV(metadata, "deepdots_referrer", d.referrer);
|
|
513
|
+
pushKV(metadata, "deepdots_viewport_size", d.viewport_size);
|
|
514
|
+
pushKV(metadata, "deepdots_screen_resolution", d.screen_resolution);
|
|
515
|
+
pushKV(metadata, "deepdots_pixel_ratio", d.pixel_ratio);
|
|
516
|
+
pushKV(metadata, "deepdots_entry_type", d.entry_type);
|
|
517
|
+
pushKV(metadata, "deepdots_page_load_ms", d.page_load_ms);
|
|
518
|
+
pushKV(metadata, "deepdots_connection_type", d.connection_type);
|
|
519
|
+
pushKV(metadata, "deepdots_country", d.country);
|
|
520
|
+
pushKV(metadata, "deepdots_city", d.city);
|
|
489
521
|
}
|
|
490
522
|
for (const [k, v] of Object.entries(context.attributes ?? {})) {
|
|
491
523
|
pushKV(metadata, k, v);
|
|
492
524
|
}
|
|
525
|
+
for (const e of envelope.events) {
|
|
526
|
+
metadata.push({
|
|
527
|
+
key: e.name,
|
|
528
|
+
value: [JSON.stringify({ timestamp: e.timestamp, ...e.params ?? {} })]
|
|
529
|
+
});
|
|
530
|
+
}
|
|
493
531
|
return {
|
|
494
532
|
feedback: {
|
|
495
533
|
text: "",
|
|
496
534
|
answers: [],
|
|
497
535
|
metadata,
|
|
498
|
-
metrics,
|
|
499
536
|
profile,
|
|
500
537
|
finished: false
|
|
501
538
|
},
|
|
502
539
|
publicKey: keys.publicKey,
|
|
503
540
|
integration: keys.integration,
|
|
504
541
|
completed: false,
|
|
505
|
-
// finished: false,
|
|
506
542
|
...feedbackSessionId ? { sessionId: feedbackSessionId } : {}
|
|
507
543
|
};
|
|
508
544
|
}
|
|
@@ -527,6 +563,7 @@ function createFeedbackSink(options) {
|
|
|
527
563
|
if (data?.sessionId && data.sessionId !== feedbackSessionId) {
|
|
528
564
|
feedbackSessionId = data.sessionId;
|
|
529
565
|
options.log?.("[DeepdotsAnalytics] feedbackSessionId cacheado:", feedbackSessionId);
|
|
566
|
+
options.onSessionId?.(feedbackSessionId);
|
|
530
567
|
}
|
|
531
568
|
} catch {
|
|
532
569
|
}
|
|
@@ -640,15 +677,57 @@ function parseDeviceType(ua) {
|
|
|
640
677
|
}
|
|
641
678
|
return "desktop";
|
|
642
679
|
}
|
|
680
|
+
function getEntryType() {
|
|
681
|
+
if (typeof performance === "undefined") return void 0;
|
|
682
|
+
const entries = performance.getEntriesByType("navigation");
|
|
683
|
+
if (!entries.length) return void 0;
|
|
684
|
+
return entries[0].type;
|
|
685
|
+
}
|
|
686
|
+
function getPageLoadMs() {
|
|
687
|
+
if (typeof performance === "undefined") return void 0;
|
|
688
|
+
const entries = performance.getEntriesByType("navigation");
|
|
689
|
+
if (!entries.length) return void 0;
|
|
690
|
+
const load = entries[0].loadEventEnd;
|
|
691
|
+
return load > 0 ? Math.round(load) : void 0;
|
|
692
|
+
}
|
|
693
|
+
function getConnectionType() {
|
|
694
|
+
if (typeof navigator === "undefined") return void 0;
|
|
695
|
+
return navigator.connection?.effectiveType ?? void 0;
|
|
696
|
+
}
|
|
643
697
|
function collectDeviceInfo(appVersion) {
|
|
644
698
|
const ua = typeof navigator !== "undefined" ? navigator.userAgent : "";
|
|
645
699
|
return {
|
|
646
700
|
device_type: parseDeviceType(ua),
|
|
647
701
|
user_agent: ua || void 0,
|
|
648
|
-
app_version: appVersion
|
|
702
|
+
app_version: appVersion,
|
|
703
|
+
timezone: typeof Intl !== "undefined" ? Intl.DateTimeFormat().resolvedOptions().timeZone : void 0,
|
|
704
|
+
referrer: typeof document !== "undefined" && document.referrer ? document.referrer : void 0,
|
|
705
|
+
viewport_size: typeof window !== "undefined" ? `${window.innerWidth}x${window.innerHeight}` : void 0,
|
|
706
|
+
screen_resolution: typeof screen !== "undefined" ? `${screen.width}x${screen.height}` : void 0,
|
|
707
|
+
pixel_ratio: typeof window !== "undefined" ? window.devicePixelRatio : void 0,
|
|
708
|
+
entry_type: getEntryType(),
|
|
709
|
+
page_load_ms: getPageLoadMs(),
|
|
710
|
+
connection_type: getConnectionType()
|
|
649
711
|
};
|
|
650
712
|
}
|
|
651
713
|
|
|
714
|
+
// src/analytics/geo-info.ts
|
|
715
|
+
async function collectGeoInfo(fetchImpl) {
|
|
716
|
+
const f = fetchImpl ?? (typeof fetch !== "undefined" ? fetch : void 0);
|
|
717
|
+
if (!f) return null;
|
|
718
|
+
try {
|
|
719
|
+
const res = await f("https://ipapi.co/json/");
|
|
720
|
+
if (!res.ok) return null;
|
|
721
|
+
const data = await res.json();
|
|
722
|
+
const country = data.country_code || void 0;
|
|
723
|
+
const city = data.city || void 0;
|
|
724
|
+
if (!country && !city) return null;
|
|
725
|
+
return { country, city };
|
|
726
|
+
} catch {
|
|
727
|
+
return null;
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
|
|
652
731
|
// src/analytics/engagement-tracker.ts
|
|
653
732
|
var EngagementTracker = class {
|
|
654
733
|
constructor(now = () => Date.now()) {
|
|
@@ -716,9 +795,137 @@ var ContactManager = class {
|
|
|
716
795
|
}
|
|
717
796
|
};
|
|
718
797
|
|
|
798
|
+
// src/analytics/crash-reporter.ts
|
|
799
|
+
var STACK_MAX = 8e3;
|
|
800
|
+
var QUEUE_KEY = "deepdots.crash.queue";
|
|
801
|
+
var MAX_QUEUED = 20;
|
|
802
|
+
function crashRecordToParams(r) {
|
|
803
|
+
const params = {
|
|
804
|
+
crashed_at: r.crashedAt,
|
|
805
|
+
crash_type: r.crashType,
|
|
806
|
+
message: r.message,
|
|
807
|
+
stack: r.stack,
|
|
808
|
+
fatal: r.fatal,
|
|
809
|
+
handled: r.handled,
|
|
810
|
+
severity: r.severity
|
|
811
|
+
};
|
|
812
|
+
if (r.sessionId) params.crashed_session_id = r.sessionId;
|
|
813
|
+
if (r.appVersion) params.crashed_app_version = r.appVersion;
|
|
814
|
+
if (r.osVersion) params.crashed_os_version = r.osVersion;
|
|
815
|
+
if (r.deviceModel) params.crashed_device_model = r.deviceModel;
|
|
816
|
+
if (r.context) {
|
|
817
|
+
for (const [k, v] of Object.entries(r.context)) params[`ctx_${k}`] = v;
|
|
818
|
+
}
|
|
819
|
+
return params;
|
|
820
|
+
}
|
|
821
|
+
var CrashReporter = class {
|
|
822
|
+
constructor(options) {
|
|
823
|
+
this.options = options;
|
|
824
|
+
this.now = options.now ?? (() => Date.now());
|
|
825
|
+
}
|
|
826
|
+
isEnabled() {
|
|
827
|
+
return this.options.enabled ? this.options.enabled() : true;
|
|
828
|
+
}
|
|
829
|
+
/** Construye un CrashRecord capturando contexto en el momento del crash. */
|
|
830
|
+
buildRecord(error, severity, handled, fatal, context) {
|
|
831
|
+
const err = error;
|
|
832
|
+
const isErr = error instanceof Error;
|
|
833
|
+
const dev = this.options.device();
|
|
834
|
+
const ctx = context ? Object.fromEntries(Object.entries(context).map(([k, v]) => [k, String(v)])) : void 0;
|
|
835
|
+
return {
|
|
836
|
+
crashedAt: this.now(),
|
|
837
|
+
crashType: isErr && err.name || "Error",
|
|
838
|
+
message: isErr ? String(err.message ?? "") : String(error),
|
|
839
|
+
stack: (isErr && typeof err.stack === "string" ? err.stack : "").slice(0, STACK_MAX),
|
|
840
|
+
fatal,
|
|
841
|
+
handled,
|
|
842
|
+
severity,
|
|
843
|
+
sessionId: this.options.sessionId(),
|
|
844
|
+
appVersion: dev.appVersion,
|
|
845
|
+
osVersion: dev.osVersion,
|
|
846
|
+
deviceModel: dev.deviceModel,
|
|
847
|
+
context: ctx
|
|
848
|
+
};
|
|
849
|
+
}
|
|
850
|
+
/** API pública del host: reporta un error (app viva) → emite el evento ya. */
|
|
851
|
+
reportError(error, options = {}) {
|
|
852
|
+
if (!this.isEnabled()) return;
|
|
853
|
+
const severity = options.severity ?? "error";
|
|
854
|
+
const handled = options.handled ?? true;
|
|
855
|
+
const fatal = severity === "fatal";
|
|
856
|
+
const record = this.buildRecord(error, severity, handled, fatal, options.context);
|
|
857
|
+
this.options.emit(crashRecordToParams(record));
|
|
858
|
+
}
|
|
859
|
+
/** Captura un error no manejado (fatal) → persiste a disco para replay en el siguiente arranque. */
|
|
860
|
+
captureUnhandled(error) {
|
|
861
|
+
this.persist(this.buildRecord(error, "fatal", false, true));
|
|
862
|
+
}
|
|
863
|
+
/** Persiste un crash no capturado a disco para reenviarlo en el siguiente arranque. */
|
|
864
|
+
persist(record) {
|
|
865
|
+
let queue = [];
|
|
866
|
+
try {
|
|
867
|
+
const raw = this.options.storage.getItem(QUEUE_KEY);
|
|
868
|
+
if (raw) queue = JSON.parse(raw);
|
|
869
|
+
if (!Array.isArray(queue)) queue = [];
|
|
870
|
+
} catch {
|
|
871
|
+
queue = [];
|
|
872
|
+
}
|
|
873
|
+
queue.push(record);
|
|
874
|
+
if (queue.length > MAX_QUEUED) queue = queue.slice(queue.length - MAX_QUEUED);
|
|
875
|
+
try {
|
|
876
|
+
this.options.storage.setItem(QUEUE_KEY, JSON.stringify(queue));
|
|
877
|
+
} catch {
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
/** Lee y vacía la cola de crashes pendientes (llamado en init para el replay). */
|
|
881
|
+
drainPendingCrashes() {
|
|
882
|
+
let queue = [];
|
|
883
|
+
try {
|
|
884
|
+
const raw = this.options.storage.getItem(QUEUE_KEY);
|
|
885
|
+
if (raw) queue = JSON.parse(raw);
|
|
886
|
+
if (!Array.isArray(queue)) queue = [];
|
|
887
|
+
} catch {
|
|
888
|
+
queue = [];
|
|
889
|
+
}
|
|
890
|
+
if (queue.length) {
|
|
891
|
+
try {
|
|
892
|
+
this.options.storage.removeItem(QUEUE_KEY);
|
|
893
|
+
} catch {
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
return queue;
|
|
897
|
+
}
|
|
898
|
+
/** Instala los handlers globales de errores no capturados (no-op sin `window`). */
|
|
899
|
+
install() {
|
|
900
|
+
if (typeof window === "undefined") return;
|
|
901
|
+
window.addEventListener("error", (e) => {
|
|
902
|
+
if (!this.isEnabled()) return;
|
|
903
|
+
this.captureUnhandled(e.error ?? e.message ?? "error");
|
|
904
|
+
});
|
|
905
|
+
window.addEventListener("unhandledrejection", (e) => {
|
|
906
|
+
if (!this.isEnabled()) return;
|
|
907
|
+
this.captureUnhandled(e.reason ?? "unhandledrejection");
|
|
908
|
+
});
|
|
909
|
+
}
|
|
910
|
+
/** Engancha `global.ErrorUtils` de RN (no hay `window`): persiste y encadena al handler previo. */
|
|
911
|
+
installReactNative(errorUtils) {
|
|
912
|
+
const previous = errorUtils.getGlobalHandler?.();
|
|
913
|
+
errorUtils.setGlobalHandler((error, isFatal) => {
|
|
914
|
+
if (this.isEnabled()) this.captureUnhandled(error);
|
|
915
|
+
previous?.(error, isFatal);
|
|
916
|
+
});
|
|
917
|
+
}
|
|
918
|
+
/** @internal Test seam: ejercita el camino de persistencia (window handlers no son testeables en vitest). */
|
|
919
|
+
_persistForTest(error) {
|
|
920
|
+
this.captureUnhandled(error);
|
|
921
|
+
}
|
|
922
|
+
};
|
|
923
|
+
|
|
719
924
|
// src/core/deepdots-popups.ts
|
|
720
925
|
var EXIT_QUEUE_STORAGE_KEY = "__deepdots_exit_popup_queue__";
|
|
721
926
|
var DAY_IN_MS = 24 * 60 * 60 * 1e3;
|
|
927
|
+
var ANALYTICS_MAX_BATCH_SIZE = 20;
|
|
928
|
+
var ANALYTICS_FLUSH_INTERVAL_MS = 3e4;
|
|
722
929
|
var DeepdotsPopups = class {
|
|
723
930
|
constructor() {
|
|
724
931
|
this.config = null;
|
|
@@ -744,6 +951,12 @@ var DeepdotsPopups = class {
|
|
|
744
951
|
this.tracking = null;
|
|
745
952
|
/** Capa de analytics (canal separado del feedback). Null hasta init(). */
|
|
746
953
|
this.analytics = null;
|
|
954
|
+
/** feedbackSessionId cacheado del canal de analytics (devuelto por POST /sdk/feedback). */
|
|
955
|
+
this.analyticsFeedbackSessionId = void 0;
|
|
956
|
+
/** Timer del flush periódico de analytics (cada ANALYTICS_FLUSH_INTERVAL_MS). */
|
|
957
|
+
this.analyticsFlushTimer = void 0;
|
|
958
|
+
/** Crash & error reporting (#14–17). Null hasta init(). */
|
|
959
|
+
this.crashReporter = null;
|
|
747
960
|
/** Observador de navegación (Fase 2): emite page_view por el canal de analytics. */
|
|
748
961
|
this.navObserver = null;
|
|
749
962
|
/** Tiempo activo (engagement time, #8). */
|
|
@@ -790,21 +1003,49 @@ var DeepdotsPopups = class {
|
|
|
790
1003
|
const analyticsSink = config.analytics ? createFeedbackSink({
|
|
791
1004
|
baseUrl: this.baseUrl,
|
|
792
1005
|
keys: config.analytics,
|
|
793
|
-
log: (...a) => this.log(...a)
|
|
1006
|
+
log: (...a) => this.log(...a),
|
|
1007
|
+
onSessionId: (id) => {
|
|
1008
|
+
this.analyticsFeedbackSessionId = id;
|
|
1009
|
+
}
|
|
794
1010
|
}) : void 0;
|
|
1011
|
+
const device = config.device ?? collectDeviceInfo(config.appVersion);
|
|
795
1012
|
this.analytics = new AnalyticsManager({
|
|
796
1013
|
sink: analyticsSink,
|
|
797
1014
|
publicKey: config.analytics?.publicKey ?? this.config.apiKey,
|
|
798
1015
|
language: typeof navigator !== "undefined" ? navigator.language : void 0,
|
|
799
1016
|
platform: config.platform ?? "web",
|
|
800
|
-
device
|
|
1017
|
+
device,
|
|
1018
|
+
maxBatchSize: ANALYTICS_MAX_BATCH_SIZE,
|
|
1019
|
+
onFlushNeeded: () => this.flushAnalytics()
|
|
1020
|
+
});
|
|
1021
|
+
this.crashReporter = new CrashReporter({
|
|
1022
|
+
storage,
|
|
1023
|
+
emit: (params) => this.track("deepdots_app_crash", params),
|
|
1024
|
+
device: () => ({ appVersion: device.app_version, osVersion: device.os_version, deviceModel: device.device_model }),
|
|
1025
|
+
sessionId: () => this.tracking?.getSessionId() ?? null,
|
|
1026
|
+
now: () => Date.now(),
|
|
1027
|
+
enabled: () => this.tracking?.isTrackingEnabled() ?? false
|
|
1028
|
+
});
|
|
1029
|
+
if (this.tracking?.isTrackingEnabled()) {
|
|
1030
|
+
this.crashReporter.install();
|
|
1031
|
+
}
|
|
1032
|
+
collectGeoInfo().then((geo) => {
|
|
1033
|
+
if (geo) this.analytics?.updateDevice(geo);
|
|
1034
|
+
}).catch(() => {
|
|
801
1035
|
});
|
|
802
1036
|
this.navObserver = new NavigationObserver();
|
|
803
|
-
this.navObserver.onVisit((v) => this.track("
|
|
1037
|
+
this.navObserver.onVisit((v) => this.track("deepdots_page_view", { screen: v.screen, duration_seconds: v.durationSeconds }));
|
|
804
1038
|
this.navObserver.install();
|
|
805
1039
|
this.engagement = new EngagementTracker();
|
|
806
1040
|
this.engagement.resume();
|
|
807
1041
|
this.setupAnalyticsFlush();
|
|
1042
|
+
this.track("deepdots_session_start", {});
|
|
1043
|
+
const pendingCrashes = this.crashReporter.drainPendingCrashes();
|
|
1044
|
+
if (this.tracking?.isTrackingEnabled()) {
|
|
1045
|
+
for (const rec of pendingCrashes) {
|
|
1046
|
+
this.track("deepdots_app_crash", crashRecordToParams(rec));
|
|
1047
|
+
}
|
|
1048
|
+
}
|
|
808
1049
|
this.initialized = true;
|
|
809
1050
|
this.log("SDK initialized", this.config);
|
|
810
1051
|
if (this.renderer.init) this.renderer.init();
|
|
@@ -845,6 +1086,16 @@ var DeepdotsPopups = class {
|
|
|
845
1086
|
if (!this.tracking?.isTrackingEnabled()) return;
|
|
846
1087
|
this.analytics?.setUserAttributes(attributes);
|
|
847
1088
|
}
|
|
1089
|
+
/** Reporta un error del host (manejado o no) → evento `deepdots_app_crash`. No-op si tracking off. */
|
|
1090
|
+
reportError(error, options) {
|
|
1091
|
+
if (!this.tracking?.isTrackingEnabled()) return;
|
|
1092
|
+
this.crashReporter?.reportError(error, options);
|
|
1093
|
+
}
|
|
1094
|
+
/** Engancha `global.ErrorUtils` de RN para capturar errores JS no manejados. No-op si tracking off. */
|
|
1095
|
+
installReactNativeCrashHandler(errorUtils) {
|
|
1096
|
+
if (!this.tracking?.isTrackingEnabled()) return;
|
|
1097
|
+
this.crashReporter?.installReactNative(errorUtils);
|
|
1098
|
+
}
|
|
848
1099
|
/**
|
|
849
1100
|
* Info interna del usuario que solo conoce el host (idioma, edad, plan…), persistida en el
|
|
850
1101
|
* Contact del backend para segmentación/targeting de popups. Identificada por el `userId` del
|
|
@@ -862,23 +1113,23 @@ var DeepdotsPopups = class {
|
|
|
862
1113
|
this.analytics?.enterMiniService(name, entryPointType);
|
|
863
1114
|
this.log("analytics \xB7 enterMiniService:", name, entryPointType ?? "");
|
|
864
1115
|
}
|
|
865
|
-
/** Cierra el mini-service
|
|
866
|
-
exitMiniService() {
|
|
1116
|
+
/** Cierra el mini-service `name` (emite `mini_service_exit` con duración, #27). No-op si tracking off o si ese no está activo. */
|
|
1117
|
+
exitMiniService(name) {
|
|
867
1118
|
if (!this.tracking?.isTrackingEnabled()) return;
|
|
868
|
-
this.analytics?.exitMiniService();
|
|
869
|
-
this.log("analytics \xB7 exitMiniService");
|
|
1119
|
+
this.analytics?.exitMiniService(name);
|
|
1120
|
+
this.log("analytics \xB7 exitMiniService:", name);
|
|
870
1121
|
}
|
|
871
1122
|
/** Findability (#31/#35): registra una búsqueda. `has_results` se deriva de `resultsCount`. */
|
|
872
1123
|
trackSearch(query, resultsCount, params) {
|
|
873
|
-
this.track("
|
|
1124
|
+
this.track("deepdots_search", { query, results_count: resultsCount, has_results: resultsCount > 0, ...params ?? {} });
|
|
874
1125
|
}
|
|
875
1126
|
/** Findability friction (#34/#35): señal de fricción con su `friction_topic`. */
|
|
876
1127
|
trackFindabilityFriction(frictionTopic, params) {
|
|
877
|
-
this.track("
|
|
1128
|
+
this.track("deepdots_findability_friction", { friction_topic: frictionTopic, ...params ?? {} });
|
|
878
1129
|
}
|
|
879
1130
|
/** Funnel: un paso del embudo, correlacionado por `taskId`. El backend reconstruye conversión/drop-off/tiempo. */
|
|
880
1131
|
trackFunnelStep(funnel, step, taskId, params) {
|
|
881
|
-
this.track("
|
|
1132
|
+
this.track("deepdots_funnel_step", { funnel, step, task_id: taskId, ...params ?? {} });
|
|
882
1133
|
}
|
|
883
1134
|
/**
|
|
884
1135
|
* Navegación MANUAL (entornos sin History API, p. ej. React Native con React Navigation).
|
|
@@ -904,7 +1155,7 @@ var DeepdotsPopups = class {
|
|
|
904
1155
|
onBackground() {
|
|
905
1156
|
this.navObserver?.stop();
|
|
906
1157
|
this.navStarted = false;
|
|
907
|
-
this.
|
|
1158
|
+
if (this.tracking?.isTrackingEnabled()) this.analytics?.exitAllMiniServices();
|
|
908
1159
|
this.flushEngagement();
|
|
909
1160
|
this.engagement?.pause();
|
|
910
1161
|
this.flushAnalytics();
|
|
@@ -927,7 +1178,7 @@ var DeepdotsPopups = class {
|
|
|
927
1178
|
flushEngagement() {
|
|
928
1179
|
if (!this.tracking?.isTrackingEnabled()) return;
|
|
929
1180
|
const ms = this.engagement?.consume() ?? 0;
|
|
930
|
-
if (ms > 0) this.track("
|
|
1181
|
+
if (ms > 0) this.track("deepdots_user_engagement", { engagement_time_msec: ms });
|
|
931
1182
|
}
|
|
932
1183
|
analyticsIdentity() {
|
|
933
1184
|
return {
|
|
@@ -938,9 +1189,11 @@ var DeepdotsPopups = class {
|
|
|
938
1189
|
/** Flush automático al ocultar/cerrar la página (no perder el lote pendiente). */
|
|
939
1190
|
setupAnalyticsFlush() {
|
|
940
1191
|
if (typeof document === "undefined" || typeof window === "undefined") return;
|
|
1192
|
+
this.analyticsFlushTimer = setInterval(() => this.flushAnalytics(), ANALYTICS_FLUSH_INTERVAL_MS);
|
|
941
1193
|
window.addEventListener("pagehide", () => {
|
|
1194
|
+
clearInterval(this.analyticsFlushTimer);
|
|
942
1195
|
this.navObserver?.stop();
|
|
943
|
-
this.
|
|
1196
|
+
if (this.tracking?.isTrackingEnabled()) this.analytics?.exitAllMiniServices();
|
|
944
1197
|
this.flushEngagement();
|
|
945
1198
|
this.flushAnalytics();
|
|
946
1199
|
});
|
|
@@ -1382,7 +1635,8 @@ var DeepdotsPopups = class {
|
|
|
1382
1635
|
const userId = this.tracking?.getUserId() ?? this.config?.userId;
|
|
1383
1636
|
const sessionId = this.tracking?.getSessionId() ?? void 0;
|
|
1384
1637
|
const miniService = this.analytics?.getMiniService() ?? void 0;
|
|
1385
|
-
|
|
1638
|
+
const analyticsFeedbackSessionId = this.analyticsFeedbackSessionId;
|
|
1639
|
+
this.log("tracking \xB7 survey identity \u2192", { userId: userId ?? null, sessionId: sessionId ?? null, miniService: miniService ?? null, analyticsFeedbackSessionId: analyticsFeedbackSessionId ?? null });
|
|
1386
1640
|
if (this.renderer) {
|
|
1387
1641
|
this.renderer.show(
|
|
1388
1642
|
surveyId,
|
|
@@ -1394,13 +1648,14 @@ var DeepdotsPopups = class {
|
|
|
1394
1648
|
userId,
|
|
1395
1649
|
style,
|
|
1396
1650
|
sessionId,
|
|
1397
|
-
miniService
|
|
1651
|
+
miniService,
|
|
1652
|
+
analyticsFeedbackSessionId
|
|
1398
1653
|
);
|
|
1399
1654
|
return;
|
|
1400
1655
|
}
|
|
1401
1656
|
if (!this.popupContainer) return;
|
|
1402
1657
|
const container = this.popupContainer;
|
|
1403
|
-
void import("./renderPopup-
|
|
1658
|
+
void import("./renderPopup-YLFRGPQN.mjs").then(({ renderPopup }) => {
|
|
1404
1659
|
renderPopup(
|
|
1405
1660
|
container,
|
|
1406
1661
|
surveyId,
|
|
@@ -1412,7 +1667,8 @@ var DeepdotsPopups = class {
|
|
|
1412
1667
|
userId,
|
|
1413
1668
|
style,
|
|
1414
1669
|
sessionId,
|
|
1415
|
-
miniService
|
|
1670
|
+
miniService,
|
|
1671
|
+
analyticsFeedbackSessionId
|
|
1416
1672
|
);
|
|
1417
1673
|
});
|
|
1418
1674
|
}
|
|
@@ -1603,6 +1859,10 @@ function setupReactNative(sdk, config, deps = {}) {
|
|
|
1603
1859
|
const device = deps.deviceInfo ? collectRnDevice(deps.deviceInfo) : config.device;
|
|
1604
1860
|
const platform = deps.platform ?? config.platform;
|
|
1605
1861
|
sdk.init({ ...config, storage, device, platform });
|
|
1862
|
+
const errorUtils = deps.errorUtils ?? globalThis.ErrorUtils;
|
|
1863
|
+
if (errorUtils && typeof errorUtils.setGlobalHandler === "function") {
|
|
1864
|
+
sdk.installReactNativeCrashHandler(errorUtils);
|
|
1865
|
+
}
|
|
1606
1866
|
let subscription;
|
|
1607
1867
|
if (deps.appState?.addEventListener) {
|
|
1608
1868
|
subscription = deps.appState.addEventListener("change", (state) => {
|
|
@@ -1617,6 +1877,7 @@ function setupReactNative(sdk, config, deps = {}) {
|
|
|
1617
1877
|
export {
|
|
1618
1878
|
BrowserPopupRenderer,
|
|
1619
1879
|
ContactManager,
|
|
1880
|
+
CrashReporter,
|
|
1620
1881
|
DeepdotsPopups,
|
|
1621
1882
|
InMemoryStorage,
|
|
1622
1883
|
NoopPopupRenderer,
|
|
@@ -1625,6 +1886,7 @@ export {
|
|
|
1625
1886
|
buildAnalyticsFeedbackBody,
|
|
1626
1887
|
buildSurveyHtml,
|
|
1627
1888
|
collectRnDevice,
|
|
1889
|
+
crashRecordToParams,
|
|
1628
1890
|
createDefaultRenderer,
|
|
1629
1891
|
createFeedbackSink,
|
|
1630
1892
|
mmkvStorage,
|