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