@magicfeedback/popup-sdk 1.1.2 → 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/index.d.mts +90 -3
- package/dist/index.d.ts +90 -3
- package/dist/index.js +208 -29
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +208 -29
- package/dist/index.mjs.map +1 -1
- package/dist/react-native.d.mts +34 -2
- package/dist/react-native.d.ts +34 -2
- package/dist/react-native.js +206 -29
- package/dist/react-native.js.map +1 -1
- package/dist/react-native.mjs +206 -29
- package/dist/react-native.mjs.map +1 -1
- package/package.json +1 -1
package/dist/react-native.d.mts
CHANGED
|
@@ -216,6 +216,30 @@ interface PopupRenderer {
|
|
|
216
216
|
type ContactAttributeValue = string | number | boolean;
|
|
217
217
|
type ContactAttributes = Record<string, ContactAttributeValue>;
|
|
218
218
|
|
|
219
|
+
/**
|
|
220
|
+
* Crash & error reporting (Stability #14–17) — captura de errores GESTIONADOS.
|
|
221
|
+
*
|
|
222
|
+
* - `reportError()`: API pública para el host (app viva) → emite `deepdots_app_crash` ya.
|
|
223
|
+
* - handlers de `window` (errores no capturados): persisten a disco y se reenvían
|
|
224
|
+
* en el siguiente arranque (el proceso puede morir antes del flush normal).
|
|
225
|
+
*
|
|
226
|
+
* El contexto de device/sesión se captura EN EL MOMENTO del crash y se guarda en el
|
|
227
|
+
* record — no se lee del envelope en el replay (el replay puede ocurrir en otra
|
|
228
|
+
* app_version/OS). Las dims OS/device en Web suelen ir vacías (se derivan del UA en backend).
|
|
229
|
+
*/
|
|
230
|
+
|
|
231
|
+
type CrashSeverity = 'fatal' | 'error' | 'warning';
|
|
232
|
+
interface ReportErrorOptions {
|
|
233
|
+
severity?: CrashSeverity;
|
|
234
|
+
handled?: boolean;
|
|
235
|
+
context?: Record<string, unknown>;
|
|
236
|
+
}
|
|
237
|
+
/** Forma mínima de `global.ErrorUtils` de React Native. */
|
|
238
|
+
interface ReactNativeErrorUtils {
|
|
239
|
+
getGlobalHandler?(): ((error: unknown, isFatal?: boolean) => void) | undefined;
|
|
240
|
+
setGlobalHandler(handler: (error: unknown, isFatal?: boolean) => void): void;
|
|
241
|
+
}
|
|
242
|
+
|
|
219
243
|
/**
|
|
220
244
|
* Main class for managing survey popups
|
|
221
245
|
*/
|
|
@@ -244,6 +268,8 @@ declare class DeepdotsPopups {
|
|
|
244
268
|
private analyticsFeedbackSessionId;
|
|
245
269
|
/** Timer del flush periódico de analytics (cada ANALYTICS_FLUSH_INTERVAL_MS). */
|
|
246
270
|
private analyticsFlushTimer;
|
|
271
|
+
/** Crash & error reporting (#14–17). Null hasta init(). */
|
|
272
|
+
private crashReporter;
|
|
247
273
|
/** Observador de navegación (Fase 2): emite page_view por el canal de analytics. */
|
|
248
274
|
private navObserver;
|
|
249
275
|
/** Tiempo activo (engagement time, #8). */
|
|
@@ -264,6 +290,10 @@ declare class DeepdotsPopups {
|
|
|
264
290
|
track(name: string, params?: Record<string, unknown>): void;
|
|
265
291
|
/** User attributes para breakdowns (registration_status, pass_type, sector, pass_status…). */
|
|
266
292
|
setUserAttributes(attributes: Record<string, string | number | boolean>): void;
|
|
293
|
+
/** Reporta un error del host (manejado o no) → evento `deepdots_app_crash`. No-op si tracking off. */
|
|
294
|
+
reportError(error: unknown, options?: ReportErrorOptions): void;
|
|
295
|
+
/** Engancha `global.ErrorUtils` de RN para capturar errores JS no manejados. No-op si tracking off. */
|
|
296
|
+
installReactNativeCrashHandler(errorUtils: ReactNativeErrorUtils): void;
|
|
267
297
|
/**
|
|
268
298
|
* Info interna del usuario que solo conoce el host (idioma, edad, plan…), persistida en el
|
|
269
299
|
* Contact del backend para segmentación/targeting de popups. Identificada por el `userId` del
|
|
@@ -273,8 +303,8 @@ declare class DeepdotsPopups {
|
|
|
273
303
|
setContactAttributes(attributes: ContactAttributes): Promise<boolean>;
|
|
274
304
|
/** Marca el inicio de un mini-service; etiqueta los eventos siguientes. No-op si tracking off. */
|
|
275
305
|
enterMiniService(name: string, entryPointType?: string): void;
|
|
276
|
-
/** Cierra el mini-service
|
|
277
|
-
exitMiniService(): void;
|
|
306
|
+
/** Cierra el mini-service `name` (emite `mini_service_exit` con duración, #27). No-op si tracking off o si ese no está activo. */
|
|
307
|
+
exitMiniService(name: string): void;
|
|
278
308
|
/** Findability (#31/#35): registra una búsqueda. `has_results` se deriva de `resultsCount`. */
|
|
279
309
|
trackSearch(query: string, resultsCount: number, params?: Record<string, unknown>): void;
|
|
280
310
|
/** Findability friction (#34/#35): señal de fricción con su `friction_topic`. */
|
|
@@ -406,6 +436,8 @@ interface ReactNativeSetupDeps {
|
|
|
406
436
|
appState?: AppStateLike | null;
|
|
407
437
|
platform?: 'web' | 'android' | 'ios';
|
|
408
438
|
renderer?: PopupRenderer;
|
|
439
|
+
/** `global.ErrorUtils` de RN (para capturar errores JS no manejados). Default: `globalThis.ErrorUtils`. */
|
|
440
|
+
errorUtils?: ReactNativeErrorUtils | null;
|
|
409
441
|
}
|
|
410
442
|
/**
|
|
411
443
|
* Configura el SDK para React Native en una sola llamada: storage persistente (MMKV),
|
package/dist/react-native.d.ts
CHANGED
|
@@ -216,6 +216,30 @@ interface PopupRenderer {
|
|
|
216
216
|
type ContactAttributeValue = string | number | boolean;
|
|
217
217
|
type ContactAttributes = Record<string, ContactAttributeValue>;
|
|
218
218
|
|
|
219
|
+
/**
|
|
220
|
+
* Crash & error reporting (Stability #14–17) — captura de errores GESTIONADOS.
|
|
221
|
+
*
|
|
222
|
+
* - `reportError()`: API pública para el host (app viva) → emite `deepdots_app_crash` ya.
|
|
223
|
+
* - handlers de `window` (errores no capturados): persisten a disco y se reenvían
|
|
224
|
+
* en el siguiente arranque (el proceso puede morir antes del flush normal).
|
|
225
|
+
*
|
|
226
|
+
* El contexto de device/sesión se captura EN EL MOMENTO del crash y se guarda en el
|
|
227
|
+
* record — no se lee del envelope en el replay (el replay puede ocurrir en otra
|
|
228
|
+
* app_version/OS). Las dims OS/device en Web suelen ir vacías (se derivan del UA en backend).
|
|
229
|
+
*/
|
|
230
|
+
|
|
231
|
+
type CrashSeverity = 'fatal' | 'error' | 'warning';
|
|
232
|
+
interface ReportErrorOptions {
|
|
233
|
+
severity?: CrashSeverity;
|
|
234
|
+
handled?: boolean;
|
|
235
|
+
context?: Record<string, unknown>;
|
|
236
|
+
}
|
|
237
|
+
/** Forma mínima de `global.ErrorUtils` de React Native. */
|
|
238
|
+
interface ReactNativeErrorUtils {
|
|
239
|
+
getGlobalHandler?(): ((error: unknown, isFatal?: boolean) => void) | undefined;
|
|
240
|
+
setGlobalHandler(handler: (error: unknown, isFatal?: boolean) => void): void;
|
|
241
|
+
}
|
|
242
|
+
|
|
219
243
|
/**
|
|
220
244
|
* Main class for managing survey popups
|
|
221
245
|
*/
|
|
@@ -244,6 +268,8 @@ declare class DeepdotsPopups {
|
|
|
244
268
|
private analyticsFeedbackSessionId;
|
|
245
269
|
/** Timer del flush periódico de analytics (cada ANALYTICS_FLUSH_INTERVAL_MS). */
|
|
246
270
|
private analyticsFlushTimer;
|
|
271
|
+
/** Crash & error reporting (#14–17). Null hasta init(). */
|
|
272
|
+
private crashReporter;
|
|
247
273
|
/** Observador de navegación (Fase 2): emite page_view por el canal de analytics. */
|
|
248
274
|
private navObserver;
|
|
249
275
|
/** Tiempo activo (engagement time, #8). */
|
|
@@ -264,6 +290,10 @@ declare class DeepdotsPopups {
|
|
|
264
290
|
track(name: string, params?: Record<string, unknown>): void;
|
|
265
291
|
/** User attributes para breakdowns (registration_status, pass_type, sector, pass_status…). */
|
|
266
292
|
setUserAttributes(attributes: Record<string, string | number | boolean>): void;
|
|
293
|
+
/** Reporta un error del host (manejado o no) → evento `deepdots_app_crash`. No-op si tracking off. */
|
|
294
|
+
reportError(error: unknown, options?: ReportErrorOptions): void;
|
|
295
|
+
/** Engancha `global.ErrorUtils` de RN para capturar errores JS no manejados. No-op si tracking off. */
|
|
296
|
+
installReactNativeCrashHandler(errorUtils: ReactNativeErrorUtils): void;
|
|
267
297
|
/**
|
|
268
298
|
* Info interna del usuario que solo conoce el host (idioma, edad, plan…), persistida en el
|
|
269
299
|
* Contact del backend para segmentación/targeting de popups. Identificada por el `userId` del
|
|
@@ -273,8 +303,8 @@ declare class DeepdotsPopups {
|
|
|
273
303
|
setContactAttributes(attributes: ContactAttributes): Promise<boolean>;
|
|
274
304
|
/** Marca el inicio de un mini-service; etiqueta los eventos siguientes. No-op si tracking off. */
|
|
275
305
|
enterMiniService(name: string, entryPointType?: string): void;
|
|
276
|
-
/** Cierra el mini-service
|
|
277
|
-
exitMiniService(): void;
|
|
306
|
+
/** Cierra el mini-service `name` (emite `mini_service_exit` con duración, #27). No-op si tracking off o si ese no está activo. */
|
|
307
|
+
exitMiniService(name: string): void;
|
|
278
308
|
/** Findability (#31/#35): registra una búsqueda. `has_results` se deriva de `resultsCount`. */
|
|
279
309
|
trackSearch(query: string, resultsCount: number, params?: Record<string, unknown>): void;
|
|
280
310
|
/** Findability friction (#34/#35): señal de fricción con su `friction_topic`. */
|
|
@@ -406,6 +436,8 @@ interface ReactNativeSetupDeps {
|
|
|
406
436
|
appState?: AppStateLike | null;
|
|
407
437
|
platform?: 'web' | 'android' | 'ios';
|
|
408
438
|
renderer?: PopupRenderer;
|
|
439
|
+
/** `global.ErrorUtils` de RN (para capturar errores JS no manejados). Default: `globalThis.ErrorUtils`. */
|
|
440
|
+
errorUtils?: ReactNativeErrorUtils | null;
|
|
409
441
|
}
|
|
410
442
|
/**
|
|
411
443
|
* Configura el SDK para React Native en una sola llamada: storage persistente (MMKV),
|
package/dist/react-native.js
CHANGED
|
@@ -1925,8 +1925,8 @@ var AnalyticsManager = class {
|
|
|
1925
1925
|
constructor(options = {}) {
|
|
1926
1926
|
this.events = [];
|
|
1927
1927
|
this.attributes = {};
|
|
1928
|
-
|
|
1929
|
-
this.
|
|
1928
|
+
/** Mini-services activos: nombre → timestamp de entrada. El orden de inserción marca el "más reciente". */
|
|
1929
|
+
this.activeMiniServices = /* @__PURE__ */ new Map();
|
|
1930
1930
|
this.sink = options.sink ?? dryRunSink;
|
|
1931
1931
|
this.now = options.now ?? (() => Date.now());
|
|
1932
1932
|
this.publicKey = options.publicKey;
|
|
@@ -1947,24 +1947,38 @@ var AnalyticsManager = class {
|
|
|
1947
1947
|
this.attributes[k] = String(v);
|
|
1948
1948
|
}
|
|
1949
1949
|
}
|
|
1950
|
-
/**
|
|
1950
|
+
/**
|
|
1951
|
+
* Marca el inicio de un mini-service; etiqueta los eventos siguientes con `mini_service`.
|
|
1952
|
+
* Admite varios activos a la vez (concurrencia); el "actual" para etiquetar es el más reciente.
|
|
1953
|
+
* Reentrar con un nombre ya activo refresca su orden y su tiempo de entrada.
|
|
1954
|
+
*/
|
|
1951
1955
|
enterMiniService(name, entryPointType) {
|
|
1952
|
-
this.
|
|
1953
|
-
this.
|
|
1954
|
-
this.track("
|
|
1955
|
-
}
|
|
1956
|
-
/**
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1956
|
+
this.activeMiniServices.delete(name);
|
|
1957
|
+
this.activeMiniServices.set(name, this.now());
|
|
1958
|
+
this.track("deepdots_mini_service_enter", { entry_point_type: entryPointType ?? null });
|
|
1959
|
+
}
|
|
1960
|
+
/**
|
|
1961
|
+
* Cierra el mini-service `name` emitiendo `mini_service_exit` con su duración (#27).
|
|
1962
|
+
* No-op si ese nombre no está activo. Cierre coherente con concurrencia.
|
|
1963
|
+
*/
|
|
1964
|
+
exitMiniService(name) {
|
|
1965
|
+
const enteredAt = this.activeMiniServices.get(name);
|
|
1966
|
+
if (enteredAt === void 0) return;
|
|
1967
|
+
const durationSeconds = Math.max(0, Math.round((this.now() - enteredAt) / 1e3));
|
|
1968
|
+
this.activeMiniServices.delete(name);
|
|
1969
|
+
this.track("deepdots_mini_service_exit", { mini_service: name, duration_seconds: durationSeconds });
|
|
1970
|
+
}
|
|
1971
|
+
/** Cierra TODOS los mini-services activos (orden LIFO). Para el cierre por lifecycle (background/cierre). */
|
|
1972
|
+
exitAllMiniServices() {
|
|
1973
|
+
for (const name of Array.from(this.activeMiniServices.keys()).reverse()) {
|
|
1974
|
+
this.exitMiniService(name);
|
|
1975
|
+
}
|
|
1963
1976
|
}
|
|
1964
1977
|
/** Registra un evento de analítica (modelo GA: nombre + params). */
|
|
1965
1978
|
track(name, params) {
|
|
1979
|
+
const current = this.getMiniService();
|
|
1966
1980
|
const merged = {
|
|
1967
|
-
...
|
|
1981
|
+
...current ? { mini_service: current } : {},
|
|
1968
1982
|
...params ?? {}
|
|
1969
1983
|
};
|
|
1970
1984
|
this.events.push({
|
|
@@ -1976,9 +1990,11 @@ var AnalyticsManager = class {
|
|
|
1976
1990
|
this.onFlushNeeded?.();
|
|
1977
1991
|
}
|
|
1978
1992
|
}
|
|
1979
|
-
/** Mini-service activo
|
|
1993
|
+
/** Mini-service actual (el más reciente aún activo) para etiquetar eventos + metadata del survey (#33). */
|
|
1980
1994
|
getMiniService() {
|
|
1981
|
-
|
|
1995
|
+
let last = null;
|
|
1996
|
+
for (const name of this.activeMiniServices.keys()) last = name;
|
|
1997
|
+
return last;
|
|
1982
1998
|
}
|
|
1983
1999
|
/** Nº de eventos pendientes de flush. */
|
|
1984
2000
|
pending() {
|
|
@@ -2318,6 +2334,132 @@ var ContactManager = class {
|
|
|
2318
2334
|
}
|
|
2319
2335
|
};
|
|
2320
2336
|
|
|
2337
|
+
// src/analytics/crash-reporter.ts
|
|
2338
|
+
var STACK_MAX = 8e3;
|
|
2339
|
+
var QUEUE_KEY = "deepdots.crash.queue";
|
|
2340
|
+
var MAX_QUEUED = 20;
|
|
2341
|
+
function crashRecordToParams(r) {
|
|
2342
|
+
const params = {
|
|
2343
|
+
crashed_at: r.crashedAt,
|
|
2344
|
+
crash_type: r.crashType,
|
|
2345
|
+
message: r.message,
|
|
2346
|
+
stack: r.stack,
|
|
2347
|
+
fatal: r.fatal,
|
|
2348
|
+
handled: r.handled,
|
|
2349
|
+
severity: r.severity
|
|
2350
|
+
};
|
|
2351
|
+
if (r.sessionId) params.crashed_session_id = r.sessionId;
|
|
2352
|
+
if (r.appVersion) params.crashed_app_version = r.appVersion;
|
|
2353
|
+
if (r.osVersion) params.crashed_os_version = r.osVersion;
|
|
2354
|
+
if (r.deviceModel) params.crashed_device_model = r.deviceModel;
|
|
2355
|
+
if (r.context) {
|
|
2356
|
+
for (const [k, v] of Object.entries(r.context)) params[`ctx_${k}`] = v;
|
|
2357
|
+
}
|
|
2358
|
+
return params;
|
|
2359
|
+
}
|
|
2360
|
+
var CrashReporter = class {
|
|
2361
|
+
constructor(options) {
|
|
2362
|
+
this.options = options;
|
|
2363
|
+
this.now = options.now ?? (() => Date.now());
|
|
2364
|
+
}
|
|
2365
|
+
isEnabled() {
|
|
2366
|
+
return this.options.enabled ? this.options.enabled() : true;
|
|
2367
|
+
}
|
|
2368
|
+
/** Construye un CrashRecord capturando contexto en el momento del crash. */
|
|
2369
|
+
buildRecord(error, severity, handled, fatal, context) {
|
|
2370
|
+
const err = error;
|
|
2371
|
+
const isErr = error instanceof Error;
|
|
2372
|
+
const dev = this.options.device();
|
|
2373
|
+
const ctx = context ? Object.fromEntries(Object.entries(context).map(([k, v]) => [k, String(v)])) : void 0;
|
|
2374
|
+
return {
|
|
2375
|
+
crashedAt: this.now(),
|
|
2376
|
+
crashType: isErr && err.name || "Error",
|
|
2377
|
+
message: isErr ? String(err.message ?? "") : String(error),
|
|
2378
|
+
stack: (isErr && typeof err.stack === "string" ? err.stack : "").slice(0, STACK_MAX),
|
|
2379
|
+
fatal,
|
|
2380
|
+
handled,
|
|
2381
|
+
severity,
|
|
2382
|
+
sessionId: this.options.sessionId(),
|
|
2383
|
+
appVersion: dev.appVersion,
|
|
2384
|
+
osVersion: dev.osVersion,
|
|
2385
|
+
deviceModel: dev.deviceModel,
|
|
2386
|
+
context: ctx
|
|
2387
|
+
};
|
|
2388
|
+
}
|
|
2389
|
+
/** API pública del host: reporta un error (app viva) → emite el evento ya. */
|
|
2390
|
+
reportError(error, options = {}) {
|
|
2391
|
+
if (!this.isEnabled()) return;
|
|
2392
|
+
const severity = options.severity ?? "error";
|
|
2393
|
+
const handled = options.handled ?? true;
|
|
2394
|
+
const fatal = severity === "fatal";
|
|
2395
|
+
const record = this.buildRecord(error, severity, handled, fatal, options.context);
|
|
2396
|
+
this.options.emit(crashRecordToParams(record));
|
|
2397
|
+
}
|
|
2398
|
+
/** Captura un error no manejado (fatal) → persiste a disco para replay en el siguiente arranque. */
|
|
2399
|
+
captureUnhandled(error) {
|
|
2400
|
+
this.persist(this.buildRecord(error, "fatal", false, true));
|
|
2401
|
+
}
|
|
2402
|
+
/** Persiste un crash no capturado a disco para reenviarlo en el siguiente arranque. */
|
|
2403
|
+
persist(record) {
|
|
2404
|
+
let queue = [];
|
|
2405
|
+
try {
|
|
2406
|
+
const raw = this.options.storage.getItem(QUEUE_KEY);
|
|
2407
|
+
if (raw) queue = JSON.parse(raw);
|
|
2408
|
+
if (!Array.isArray(queue)) queue = [];
|
|
2409
|
+
} catch {
|
|
2410
|
+
queue = [];
|
|
2411
|
+
}
|
|
2412
|
+
queue.push(record);
|
|
2413
|
+
if (queue.length > MAX_QUEUED) queue = queue.slice(queue.length - MAX_QUEUED);
|
|
2414
|
+
try {
|
|
2415
|
+
this.options.storage.setItem(QUEUE_KEY, JSON.stringify(queue));
|
|
2416
|
+
} catch {
|
|
2417
|
+
}
|
|
2418
|
+
}
|
|
2419
|
+
/** Lee y vacía la cola de crashes pendientes (llamado en init para el replay). */
|
|
2420
|
+
drainPendingCrashes() {
|
|
2421
|
+
let queue = [];
|
|
2422
|
+
try {
|
|
2423
|
+
const raw = this.options.storage.getItem(QUEUE_KEY);
|
|
2424
|
+
if (raw) queue = JSON.parse(raw);
|
|
2425
|
+
if (!Array.isArray(queue)) queue = [];
|
|
2426
|
+
} catch {
|
|
2427
|
+
queue = [];
|
|
2428
|
+
}
|
|
2429
|
+
if (queue.length) {
|
|
2430
|
+
try {
|
|
2431
|
+
this.options.storage.removeItem(QUEUE_KEY);
|
|
2432
|
+
} catch {
|
|
2433
|
+
}
|
|
2434
|
+
}
|
|
2435
|
+
return queue;
|
|
2436
|
+
}
|
|
2437
|
+
/** Instala los handlers globales de errores no capturados (no-op sin `window`). */
|
|
2438
|
+
install() {
|
|
2439
|
+
if (typeof window === "undefined") return;
|
|
2440
|
+
window.addEventListener("error", (e) => {
|
|
2441
|
+
if (!this.isEnabled()) return;
|
|
2442
|
+
this.captureUnhandled(e.error ?? e.message ?? "error");
|
|
2443
|
+
});
|
|
2444
|
+
window.addEventListener("unhandledrejection", (e) => {
|
|
2445
|
+
if (!this.isEnabled()) return;
|
|
2446
|
+
this.captureUnhandled(e.reason ?? "unhandledrejection");
|
|
2447
|
+
});
|
|
2448
|
+
}
|
|
2449
|
+
/** Engancha `global.ErrorUtils` de RN (no hay `window`): persiste y encadena al handler previo. */
|
|
2450
|
+
installReactNative(errorUtils) {
|
|
2451
|
+
const previous = errorUtils.getGlobalHandler?.();
|
|
2452
|
+
errorUtils.setGlobalHandler((error, isFatal) => {
|
|
2453
|
+
if (this.isEnabled()) this.captureUnhandled(error);
|
|
2454
|
+
previous?.(error, isFatal);
|
|
2455
|
+
});
|
|
2456
|
+
}
|
|
2457
|
+
/** @internal Test seam: ejercita el camino de persistencia (window handlers no son testeables en vitest). */
|
|
2458
|
+
_persistForTest(error) {
|
|
2459
|
+
this.captureUnhandled(error);
|
|
2460
|
+
}
|
|
2461
|
+
};
|
|
2462
|
+
|
|
2321
2463
|
// src/core/deepdots-popups.ts
|
|
2322
2464
|
var EXIT_QUEUE_STORAGE_KEY = "__deepdots_exit_popup_queue__";
|
|
2323
2465
|
var DAY_IN_MS = 24 * 60 * 60 * 1e3;
|
|
@@ -2352,6 +2494,8 @@ var DeepdotsPopups = class {
|
|
|
2352
2494
|
this.analyticsFeedbackSessionId = void 0;
|
|
2353
2495
|
/** Timer del flush periódico de analytics (cada ANALYTICS_FLUSH_INTERVAL_MS). */
|
|
2354
2496
|
this.analyticsFlushTimer = void 0;
|
|
2497
|
+
/** Crash & error reporting (#14–17). Null hasta init(). */
|
|
2498
|
+
this.crashReporter = null;
|
|
2355
2499
|
/** Observador de navegación (Fase 2): emite page_view por el canal de analytics. */
|
|
2356
2500
|
this.navObserver = null;
|
|
2357
2501
|
/** Tiempo activo (engagement time, #8). */
|
|
@@ -2403,25 +2547,44 @@ var DeepdotsPopups = class {
|
|
|
2403
2547
|
this.analyticsFeedbackSessionId = id;
|
|
2404
2548
|
}
|
|
2405
2549
|
}) : void 0;
|
|
2550
|
+
const device = config.device ?? collectDeviceInfo(config.appVersion);
|
|
2406
2551
|
this.analytics = new AnalyticsManager({
|
|
2407
2552
|
sink: analyticsSink,
|
|
2408
2553
|
publicKey: config.analytics?.publicKey ?? this.config.apiKey,
|
|
2409
2554
|
language: typeof navigator !== "undefined" ? navigator.language : void 0,
|
|
2410
2555
|
platform: config.platform ?? "web",
|
|
2411
|
-
device
|
|
2556
|
+
device,
|
|
2412
2557
|
maxBatchSize: ANALYTICS_MAX_BATCH_SIZE,
|
|
2413
2558
|
onFlushNeeded: () => this.flushAnalytics()
|
|
2414
2559
|
});
|
|
2560
|
+
this.crashReporter = new CrashReporter({
|
|
2561
|
+
storage,
|
|
2562
|
+
emit: (params) => this.track("deepdots_app_crash", params),
|
|
2563
|
+
device: () => ({ appVersion: device.app_version, osVersion: device.os_version, deviceModel: device.device_model }),
|
|
2564
|
+
sessionId: () => this.tracking?.getSessionId() ?? null,
|
|
2565
|
+
now: () => Date.now(),
|
|
2566
|
+
enabled: () => this.tracking?.isTrackingEnabled() ?? false
|
|
2567
|
+
});
|
|
2568
|
+
if (this.tracking?.isTrackingEnabled()) {
|
|
2569
|
+
this.crashReporter.install();
|
|
2570
|
+
}
|
|
2415
2571
|
collectGeoInfo().then((geo) => {
|
|
2416
2572
|
if (geo) this.analytics?.updateDevice(geo);
|
|
2417
2573
|
}).catch(() => {
|
|
2418
2574
|
});
|
|
2419
2575
|
this.navObserver = new NavigationObserver();
|
|
2420
|
-
this.navObserver.onVisit((v) => this.track("
|
|
2576
|
+
this.navObserver.onVisit((v) => this.track("deepdots_page_view", { screen: v.screen, duration_seconds: v.durationSeconds }));
|
|
2421
2577
|
this.navObserver.install();
|
|
2422
2578
|
this.engagement = new EngagementTracker();
|
|
2423
2579
|
this.engagement.resume();
|
|
2424
2580
|
this.setupAnalyticsFlush();
|
|
2581
|
+
this.track("deepdots_session_start", {});
|
|
2582
|
+
const pendingCrashes = this.crashReporter.drainPendingCrashes();
|
|
2583
|
+
if (this.tracking?.isTrackingEnabled()) {
|
|
2584
|
+
for (const rec of pendingCrashes) {
|
|
2585
|
+
this.track("deepdots_app_crash", crashRecordToParams(rec));
|
|
2586
|
+
}
|
|
2587
|
+
}
|
|
2425
2588
|
this.initialized = true;
|
|
2426
2589
|
this.log("SDK initialized", this.config);
|
|
2427
2590
|
if (this.renderer.init) this.renderer.init();
|
|
@@ -2462,6 +2625,16 @@ var DeepdotsPopups = class {
|
|
|
2462
2625
|
if (!this.tracking?.isTrackingEnabled()) return;
|
|
2463
2626
|
this.analytics?.setUserAttributes(attributes);
|
|
2464
2627
|
}
|
|
2628
|
+
/** Reporta un error del host (manejado o no) → evento `deepdots_app_crash`. No-op si tracking off. */
|
|
2629
|
+
reportError(error, options) {
|
|
2630
|
+
if (!this.tracking?.isTrackingEnabled()) return;
|
|
2631
|
+
this.crashReporter?.reportError(error, options);
|
|
2632
|
+
}
|
|
2633
|
+
/** Engancha `global.ErrorUtils` de RN para capturar errores JS no manejados. No-op si tracking off. */
|
|
2634
|
+
installReactNativeCrashHandler(errorUtils) {
|
|
2635
|
+
if (!this.tracking?.isTrackingEnabled()) return;
|
|
2636
|
+
this.crashReporter?.installReactNative(errorUtils);
|
|
2637
|
+
}
|
|
2465
2638
|
/**
|
|
2466
2639
|
* Info interna del usuario que solo conoce el host (idioma, edad, plan…), persistida en el
|
|
2467
2640
|
* Contact del backend para segmentación/targeting de popups. Identificada por el `userId` del
|
|
@@ -2479,23 +2652,23 @@ var DeepdotsPopups = class {
|
|
|
2479
2652
|
this.analytics?.enterMiniService(name, entryPointType);
|
|
2480
2653
|
this.log("analytics \xB7 enterMiniService:", name, entryPointType ?? "");
|
|
2481
2654
|
}
|
|
2482
|
-
/** Cierra el mini-service
|
|
2483
|
-
exitMiniService() {
|
|
2655
|
+
/** Cierra el mini-service `name` (emite `mini_service_exit` con duración, #27). No-op si tracking off o si ese no está activo. */
|
|
2656
|
+
exitMiniService(name) {
|
|
2484
2657
|
if (!this.tracking?.isTrackingEnabled()) return;
|
|
2485
|
-
this.analytics?.exitMiniService();
|
|
2486
|
-
this.log("analytics \xB7 exitMiniService");
|
|
2658
|
+
this.analytics?.exitMiniService(name);
|
|
2659
|
+
this.log("analytics \xB7 exitMiniService:", name);
|
|
2487
2660
|
}
|
|
2488
2661
|
/** Findability (#31/#35): registra una búsqueda. `has_results` se deriva de `resultsCount`. */
|
|
2489
2662
|
trackSearch(query, resultsCount, params) {
|
|
2490
|
-
this.track("
|
|
2663
|
+
this.track("deepdots_search", { query, results_count: resultsCount, has_results: resultsCount > 0, ...params ?? {} });
|
|
2491
2664
|
}
|
|
2492
2665
|
/** Findability friction (#34/#35): señal de fricción con su `friction_topic`. */
|
|
2493
2666
|
trackFindabilityFriction(frictionTopic, params) {
|
|
2494
|
-
this.track("
|
|
2667
|
+
this.track("deepdots_findability_friction", { friction_topic: frictionTopic, ...params ?? {} });
|
|
2495
2668
|
}
|
|
2496
2669
|
/** Funnel: un paso del embudo, correlacionado por `taskId`. El backend reconstruye conversión/drop-off/tiempo. */
|
|
2497
2670
|
trackFunnelStep(funnel, step, taskId, params) {
|
|
2498
|
-
this.track("
|
|
2671
|
+
this.track("deepdots_funnel_step", { funnel, step, task_id: taskId, ...params ?? {} });
|
|
2499
2672
|
}
|
|
2500
2673
|
/**
|
|
2501
2674
|
* Navegación MANUAL (entornos sin History API, p. ej. React Native con React Navigation).
|
|
@@ -2521,7 +2694,7 @@ var DeepdotsPopups = class {
|
|
|
2521
2694
|
onBackground() {
|
|
2522
2695
|
this.navObserver?.stop();
|
|
2523
2696
|
this.navStarted = false;
|
|
2524
|
-
this.
|
|
2697
|
+
if (this.tracking?.isTrackingEnabled()) this.analytics?.exitAllMiniServices();
|
|
2525
2698
|
this.flushEngagement();
|
|
2526
2699
|
this.engagement?.pause();
|
|
2527
2700
|
this.flushAnalytics();
|
|
@@ -2544,7 +2717,7 @@ var DeepdotsPopups = class {
|
|
|
2544
2717
|
flushEngagement() {
|
|
2545
2718
|
if (!this.tracking?.isTrackingEnabled()) return;
|
|
2546
2719
|
const ms = this.engagement?.consume() ?? 0;
|
|
2547
|
-
if (ms > 0) this.track("
|
|
2720
|
+
if (ms > 0) this.track("deepdots_user_engagement", { engagement_time_msec: ms });
|
|
2548
2721
|
}
|
|
2549
2722
|
analyticsIdentity() {
|
|
2550
2723
|
return {
|
|
@@ -2559,7 +2732,7 @@ var DeepdotsPopups = class {
|
|
|
2559
2732
|
window.addEventListener("pagehide", () => {
|
|
2560
2733
|
clearInterval(this.analyticsFlushTimer);
|
|
2561
2734
|
this.navObserver?.stop();
|
|
2562
|
-
this.
|
|
2735
|
+
if (this.tracking?.isTrackingEnabled()) this.analytics?.exitAllMiniServices();
|
|
2563
2736
|
this.flushEngagement();
|
|
2564
2737
|
this.flushAnalytics();
|
|
2565
2738
|
});
|
|
@@ -3225,6 +3398,10 @@ function setupReactNative(sdk2, config, deps = {}) {
|
|
|
3225
3398
|
const device = deps.deviceInfo ? collectRnDevice(deps.deviceInfo) : config.device;
|
|
3226
3399
|
const platform = deps.platform ?? config.platform;
|
|
3227
3400
|
sdk2.init({ ...config, storage, device, platform });
|
|
3401
|
+
const errorUtils = deps.errorUtils ?? globalThis.ErrorUtils;
|
|
3402
|
+
if (errorUtils && typeof errorUtils.setGlobalHandler === "function") {
|
|
3403
|
+
sdk2.installReactNativeCrashHandler(errorUtils);
|
|
3404
|
+
}
|
|
3228
3405
|
let subscription;
|
|
3229
3406
|
if (deps.appState?.addEventListener) {
|
|
3230
3407
|
subscription = deps.appState.addEventListener("change", (state) => {
|