@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.js
CHANGED
|
@@ -48,12 +48,13 @@ function createDefaultStorage() {
|
|
|
48
48
|
}
|
|
49
49
|
return new InMemoryStorage();
|
|
50
50
|
}
|
|
51
|
-
function buildSurveyIdentity(userId, sessionId, miniService = null) {
|
|
51
|
+
function buildSurveyIdentity(userId, sessionId, miniService = null, analyticsFeedbackSessionId = null) {
|
|
52
52
|
const profile = userId ? [{ key: "external-user-id", value: [userId] }] : [];
|
|
53
53
|
const metadata = [];
|
|
54
54
|
if (sessionId) metadata.push({ key: "session_id", value: [sessionId] });
|
|
55
55
|
if (userId) metadata.push({ key: "user_id", value: [userId] });
|
|
56
56
|
if (miniService) metadata.push({ key: "mini_service", value: [miniService] });
|
|
57
|
+
if (analyticsFeedbackSessionId) metadata.push({ key: "deepdots_analytics_feedback_session_id", value: [analyticsFeedbackSessionId] });
|
|
57
58
|
return { profile, metadata };
|
|
58
59
|
}
|
|
59
60
|
function defaultUuid() {
|
|
@@ -1063,7 +1064,7 @@ function ensureResponsiveStyles(_popup) {
|
|
|
1063
1064
|
`;
|
|
1064
1065
|
document.head.appendChild(style);
|
|
1065
1066
|
}
|
|
1066
|
-
async function renderPopup(container, surveyId, productId, actions, emit, onClose, env = "production", userId, style, sessionId, miniService) {
|
|
1067
|
+
async function renderPopup(container, surveyId, productId, actions, emit, onClose, env = "production", userId, style, sessionId, miniService, analyticsFeedbackSessionId) {
|
|
1067
1068
|
let surveyCompletedEmitted = false;
|
|
1068
1069
|
let stylesInjected = false;
|
|
1069
1070
|
const isDark = style?.theme === "dark";
|
|
@@ -1374,7 +1375,7 @@ async function renderPopup(container, surveyId, productId, actions, emit, onClos
|
|
|
1374
1375
|
env: env === "production" ? "prod" : "dev"
|
|
1375
1376
|
}
|
|
1376
1377
|
);
|
|
1377
|
-
const { profile, metadata } = buildSurveyIdentity(userId ?? null, sessionId ?? null, miniService ?? null);
|
|
1378
|
+
const { profile, metadata } = buildSurveyIdentity(userId ?? null, sessionId ?? null, miniService ?? null, analyticsFeedbackSessionId ?? null);
|
|
1378
1379
|
formInstance = import_native.default.form(surveyId, productId, profile, metadata);
|
|
1379
1380
|
const generateOptions = {
|
|
1380
1381
|
addButton: false,
|
|
@@ -1787,12 +1788,12 @@ var ReactNativePopupRenderer = class {
|
|
|
1787
1788
|
}
|
|
1788
1789
|
init() {
|
|
1789
1790
|
}
|
|
1790
|
-
show(surveyId, productId, _actions, emit, onClose, env = "production", userId, _style, sessionId, miniService) {
|
|
1791
|
+
show(surveyId, productId, _actions, emit, onClose, env = "production", userId, _style, sessionId, miniService, analyticsFeedbackSessionId) {
|
|
1791
1792
|
this.emitFn = emit;
|
|
1792
1793
|
this.onCloseFn = onClose;
|
|
1793
1794
|
this.currentSurveyId = surveyId;
|
|
1794
1795
|
this.partialEmitted = false;
|
|
1795
|
-
const { profile, metadata } = buildSurveyIdentity(userId ?? null, sessionId ?? null, miniService ?? null);
|
|
1796
|
+
const { profile, metadata } = buildSurveyIdentity(userId ?? null, sessionId ?? null, miniService ?? null, analyticsFeedbackSessionId ?? null);
|
|
1796
1797
|
const html = buildSurveyHtml({ surveyId, productId, env, profile, metadata });
|
|
1797
1798
|
if (this.options.onShow) {
|
|
1798
1799
|
this.options.onShow({ surveyId, productId, html });
|
|
@@ -1883,14 +1884,14 @@ var BrowserPopupRenderer = class {
|
|
|
1883
1884
|
document.body.appendChild(this.container);
|
|
1884
1885
|
}
|
|
1885
1886
|
}
|
|
1886
|
-
show(surveyId, productId, actions, emit, onClose, env = "production", userId, style, sessionId, miniService) {
|
|
1887
|
+
show(surveyId, productId, actions, emit, onClose, env = "production", userId, style, sessionId, miniService, analyticsFeedbackSessionId) {
|
|
1887
1888
|
if (this.visible) return;
|
|
1888
1889
|
if (!this.container || !document.body.contains(this.container)) this.init();
|
|
1889
1890
|
if (!this.container) return;
|
|
1890
1891
|
this.visible = true;
|
|
1891
1892
|
const container = this.container;
|
|
1892
1893
|
void Promise.resolve().then(() => (init_renderPopup(), renderPopup_exports)).then(({ renderPopup: renderPopup2 }) => {
|
|
1893
|
-
renderPopup2(container, surveyId, productId, actions, emit, onClose, env, userId, style, sessionId, miniService);
|
|
1894
|
+
renderPopup2(container, surveyId, productId, actions, emit, onClose, env, userId, style, sessionId, miniService, analyticsFeedbackSessionId);
|
|
1894
1895
|
});
|
|
1895
1896
|
}
|
|
1896
1897
|
hide() {
|
|
@@ -1924,14 +1925,20 @@ var AnalyticsManager = class {
|
|
|
1924
1925
|
constructor(options = {}) {
|
|
1925
1926
|
this.events = [];
|
|
1926
1927
|
this.attributes = {};
|
|
1927
|
-
|
|
1928
|
-
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();
|
|
1929
1930
|
this.sink = options.sink ?? dryRunSink;
|
|
1930
1931
|
this.now = options.now ?? (() => Date.now());
|
|
1931
1932
|
this.publicKey = options.publicKey;
|
|
1932
1933
|
this.language = options.language;
|
|
1933
1934
|
this.device = options.device;
|
|
1934
1935
|
this.platform = options.platform ?? "web";
|
|
1936
|
+
this.maxBatchSize = options.maxBatchSize ?? 20;
|
|
1937
|
+
this.onFlushNeeded = options.onFlushNeeded;
|
|
1938
|
+
}
|
|
1939
|
+
/** Actualiza campos de device en runtime (p.ej. country/city tras resolver geo). */
|
|
1940
|
+
updateDevice(partial) {
|
|
1941
|
+
this.device = { ...this.device, ...partial };
|
|
1935
1942
|
}
|
|
1936
1943
|
/** Mezcla user attributes (se coercionan a string). Mutable en runtime. */
|
|
1937
1944
|
setUserAttributes(attrs) {
|
|
@@ -1940,24 +1947,38 @@ var AnalyticsManager = class {
|
|
|
1940
1947
|
this.attributes[k] = String(v);
|
|
1941
1948
|
}
|
|
1942
1949
|
}
|
|
1943
|
-
/**
|
|
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
|
+
*/
|
|
1944
1955
|
enterMiniService(name, entryPointType) {
|
|
1945
|
-
this.
|
|
1946
|
-
this.
|
|
1947
|
-
this.track("
|
|
1948
|
-
}
|
|
1949
|
-
/**
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
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
|
+
}
|
|
1956
1976
|
}
|
|
1957
1977
|
/** Registra un evento de analítica (modelo GA: nombre + params). */
|
|
1958
1978
|
track(name, params) {
|
|
1979
|
+
const current = this.getMiniService();
|
|
1959
1980
|
const merged = {
|
|
1960
|
-
...
|
|
1981
|
+
...current ? { mini_service: current } : {},
|
|
1961
1982
|
...params ?? {}
|
|
1962
1983
|
};
|
|
1963
1984
|
this.events.push({
|
|
@@ -1965,10 +1986,15 @@ var AnalyticsManager = class {
|
|
|
1965
1986
|
timestamp: this.now(),
|
|
1966
1987
|
params: Object.keys(merged).length ? merged : void 0
|
|
1967
1988
|
});
|
|
1989
|
+
if (this.events.length >= this.maxBatchSize) {
|
|
1990
|
+
this.onFlushNeeded?.();
|
|
1991
|
+
}
|
|
1968
1992
|
}
|
|
1969
|
-
/** Mini-service activo
|
|
1993
|
+
/** Mini-service actual (el más reciente aún activo) para etiquetar eventos + metadata del survey (#33). */
|
|
1970
1994
|
getMiniService() {
|
|
1971
|
-
|
|
1995
|
+
let last = null;
|
|
1996
|
+
for (const name of this.activeMiniServices.keys()) last = name;
|
|
1997
|
+
return last;
|
|
1972
1998
|
}
|
|
1973
1999
|
/** Nº de eventos pendientes de flush. */
|
|
1974
2000
|
pending() {
|
|
@@ -2002,44 +2028,55 @@ var AnalyticsManager = class {
|
|
|
2002
2028
|
// src/analytics/feedback-payload.ts
|
|
2003
2029
|
function pushKV(list, key, value) {
|
|
2004
2030
|
if (value === void 0 || value === null || value === "") return;
|
|
2005
|
-
list.push({ key, value: String(value) });
|
|
2031
|
+
list.push({ key, value: [String(value)] });
|
|
2006
2032
|
}
|
|
2007
2033
|
function buildAnalyticsFeedbackBody(envelope, keys, feedbackSessionId) {
|
|
2008
2034
|
const { context } = envelope;
|
|
2009
|
-
const metrics = envelope.events.map((e) => ({
|
|
2010
|
-
key: e.name,
|
|
2011
|
-
value: JSON.stringify({ timestamp: e.timestamp, ...e.params ?? {} })
|
|
2012
|
-
}));
|
|
2013
2035
|
const profile = [];
|
|
2014
2036
|
pushKV(profile, "external-user-id", envelope.userId);
|
|
2015
2037
|
const metadata = [];
|
|
2016
|
-
pushKV(metadata, "
|
|
2017
|
-
pushKV(metadata, "
|
|
2018
|
-
pushKV(metadata, "
|
|
2019
|
-
pushKV(metadata, "
|
|
2038
|
+
pushKV(metadata, "deepdots_user_id", envelope.userId);
|
|
2039
|
+
pushKV(metadata, "deepdots_session_id", envelope.sessionId);
|
|
2040
|
+
pushKV(metadata, "deepdots_platform", context.platform);
|
|
2041
|
+
pushKV(metadata, "deepdots_language", context.language);
|
|
2020
2042
|
if (context.device) {
|
|
2021
|
-
|
|
2022
|
-
pushKV(metadata, "
|
|
2023
|
-
pushKV(metadata, "
|
|
2024
|
-
pushKV(metadata, "
|
|
2025
|
-
pushKV(metadata, "
|
|
2043
|
+
const d = context.device;
|
|
2044
|
+
pushKV(metadata, "deepdots_device_type", d.device_type);
|
|
2045
|
+
pushKV(metadata, "deepdots_os_version", d.os_version);
|
|
2046
|
+
pushKV(metadata, "deepdots_device_model", d.device_model);
|
|
2047
|
+
pushKV(metadata, "deepdots_app_version", d.app_version);
|
|
2048
|
+
pushKV(metadata, "deepdots_user_agent", d.user_agent);
|
|
2049
|
+
pushKV(metadata, "deepdots_timezone", d.timezone);
|
|
2050
|
+
pushKV(metadata, "deepdots_referrer", d.referrer);
|
|
2051
|
+
pushKV(metadata, "deepdots_viewport_size", d.viewport_size);
|
|
2052
|
+
pushKV(metadata, "deepdots_screen_resolution", d.screen_resolution);
|
|
2053
|
+
pushKV(metadata, "deepdots_pixel_ratio", d.pixel_ratio);
|
|
2054
|
+
pushKV(metadata, "deepdots_entry_type", d.entry_type);
|
|
2055
|
+
pushKV(metadata, "deepdots_page_load_ms", d.page_load_ms);
|
|
2056
|
+
pushKV(metadata, "deepdots_connection_type", d.connection_type);
|
|
2057
|
+
pushKV(metadata, "deepdots_country", d.country);
|
|
2058
|
+
pushKV(metadata, "deepdots_city", d.city);
|
|
2026
2059
|
}
|
|
2027
2060
|
for (const [k, v] of Object.entries(context.attributes ?? {})) {
|
|
2028
2061
|
pushKV(metadata, k, v);
|
|
2029
2062
|
}
|
|
2063
|
+
for (const e of envelope.events) {
|
|
2064
|
+
metadata.push({
|
|
2065
|
+
key: e.name,
|
|
2066
|
+
value: [JSON.stringify({ timestamp: e.timestamp, ...e.params ?? {} })]
|
|
2067
|
+
});
|
|
2068
|
+
}
|
|
2030
2069
|
return {
|
|
2031
2070
|
feedback: {
|
|
2032
2071
|
text: "",
|
|
2033
2072
|
answers: [],
|
|
2034
2073
|
metadata,
|
|
2035
|
-
metrics,
|
|
2036
2074
|
profile,
|
|
2037
2075
|
finished: false
|
|
2038
2076
|
},
|
|
2039
2077
|
publicKey: keys.publicKey,
|
|
2040
2078
|
integration: keys.integration,
|
|
2041
2079
|
completed: false,
|
|
2042
|
-
// finished: false,
|
|
2043
2080
|
...feedbackSessionId ? { sessionId: feedbackSessionId } : {}
|
|
2044
2081
|
};
|
|
2045
2082
|
}
|
|
@@ -2064,6 +2101,7 @@ function createFeedbackSink(options) {
|
|
|
2064
2101
|
if (data?.sessionId && data.sessionId !== feedbackSessionId) {
|
|
2065
2102
|
feedbackSessionId = data.sessionId;
|
|
2066
2103
|
options.log?.("[DeepdotsAnalytics] feedbackSessionId cacheado:", feedbackSessionId);
|
|
2104
|
+
options.onSessionId?.(feedbackSessionId);
|
|
2067
2105
|
}
|
|
2068
2106
|
} catch {
|
|
2069
2107
|
}
|
|
@@ -2177,15 +2215,57 @@ function parseDeviceType(ua) {
|
|
|
2177
2215
|
}
|
|
2178
2216
|
return "desktop";
|
|
2179
2217
|
}
|
|
2218
|
+
function getEntryType() {
|
|
2219
|
+
if (typeof performance === "undefined") return void 0;
|
|
2220
|
+
const entries = performance.getEntriesByType("navigation");
|
|
2221
|
+
if (!entries.length) return void 0;
|
|
2222
|
+
return entries[0].type;
|
|
2223
|
+
}
|
|
2224
|
+
function getPageLoadMs() {
|
|
2225
|
+
if (typeof performance === "undefined") return void 0;
|
|
2226
|
+
const entries = performance.getEntriesByType("navigation");
|
|
2227
|
+
if (!entries.length) return void 0;
|
|
2228
|
+
const load = entries[0].loadEventEnd;
|
|
2229
|
+
return load > 0 ? Math.round(load) : void 0;
|
|
2230
|
+
}
|
|
2231
|
+
function getConnectionType() {
|
|
2232
|
+
if (typeof navigator === "undefined") return void 0;
|
|
2233
|
+
return navigator.connection?.effectiveType ?? void 0;
|
|
2234
|
+
}
|
|
2180
2235
|
function collectDeviceInfo(appVersion) {
|
|
2181
2236
|
const ua = typeof navigator !== "undefined" ? navigator.userAgent : "";
|
|
2182
2237
|
return {
|
|
2183
2238
|
device_type: parseDeviceType(ua),
|
|
2184
2239
|
user_agent: ua || void 0,
|
|
2185
|
-
app_version: appVersion
|
|
2240
|
+
app_version: appVersion,
|
|
2241
|
+
timezone: typeof Intl !== "undefined" ? Intl.DateTimeFormat().resolvedOptions().timeZone : void 0,
|
|
2242
|
+
referrer: typeof document !== "undefined" && document.referrer ? document.referrer : void 0,
|
|
2243
|
+
viewport_size: typeof window !== "undefined" ? `${window.innerWidth}x${window.innerHeight}` : void 0,
|
|
2244
|
+
screen_resolution: typeof screen !== "undefined" ? `${screen.width}x${screen.height}` : void 0,
|
|
2245
|
+
pixel_ratio: typeof window !== "undefined" ? window.devicePixelRatio : void 0,
|
|
2246
|
+
entry_type: getEntryType(),
|
|
2247
|
+
page_load_ms: getPageLoadMs(),
|
|
2248
|
+
connection_type: getConnectionType()
|
|
2186
2249
|
};
|
|
2187
2250
|
}
|
|
2188
2251
|
|
|
2252
|
+
// src/analytics/geo-info.ts
|
|
2253
|
+
async function collectGeoInfo(fetchImpl) {
|
|
2254
|
+
const f = fetchImpl ?? (typeof fetch !== "undefined" ? fetch : void 0);
|
|
2255
|
+
if (!f) return null;
|
|
2256
|
+
try {
|
|
2257
|
+
const res = await f("https://ipapi.co/json/");
|
|
2258
|
+
if (!res.ok) return null;
|
|
2259
|
+
const data = await res.json();
|
|
2260
|
+
const country = data.country_code || void 0;
|
|
2261
|
+
const city = data.city || void 0;
|
|
2262
|
+
if (!country && !city) return null;
|
|
2263
|
+
return { country, city };
|
|
2264
|
+
} catch {
|
|
2265
|
+
return null;
|
|
2266
|
+
}
|
|
2267
|
+
}
|
|
2268
|
+
|
|
2189
2269
|
// src/analytics/engagement-tracker.ts
|
|
2190
2270
|
var EngagementTracker = class {
|
|
2191
2271
|
constructor(now = () => Date.now()) {
|
|
@@ -2254,9 +2334,137 @@ var ContactManager = class {
|
|
|
2254
2334
|
}
|
|
2255
2335
|
};
|
|
2256
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
|
+
|
|
2257
2463
|
// src/core/deepdots-popups.ts
|
|
2258
2464
|
var EXIT_QUEUE_STORAGE_KEY = "__deepdots_exit_popup_queue__";
|
|
2259
2465
|
var DAY_IN_MS = 24 * 60 * 60 * 1e3;
|
|
2466
|
+
var ANALYTICS_MAX_BATCH_SIZE = 20;
|
|
2467
|
+
var ANALYTICS_FLUSH_INTERVAL_MS = 3e4;
|
|
2260
2468
|
var DeepdotsPopups = class {
|
|
2261
2469
|
constructor() {
|
|
2262
2470
|
this.config = null;
|
|
@@ -2282,6 +2490,12 @@ var DeepdotsPopups = class {
|
|
|
2282
2490
|
this.tracking = null;
|
|
2283
2491
|
/** Capa de analytics (canal separado del feedback). Null hasta init(). */
|
|
2284
2492
|
this.analytics = null;
|
|
2493
|
+
/** feedbackSessionId cacheado del canal de analytics (devuelto por POST /sdk/feedback). */
|
|
2494
|
+
this.analyticsFeedbackSessionId = void 0;
|
|
2495
|
+
/** Timer del flush periódico de analytics (cada ANALYTICS_FLUSH_INTERVAL_MS). */
|
|
2496
|
+
this.analyticsFlushTimer = void 0;
|
|
2497
|
+
/** Crash & error reporting (#14–17). Null hasta init(). */
|
|
2498
|
+
this.crashReporter = null;
|
|
2285
2499
|
/** Observador de navegación (Fase 2): emite page_view por el canal de analytics. */
|
|
2286
2500
|
this.navObserver = null;
|
|
2287
2501
|
/** Tiempo activo (engagement time, #8). */
|
|
@@ -2328,21 +2542,49 @@ var DeepdotsPopups = class {
|
|
|
2328
2542
|
const analyticsSink = config.analytics ? createFeedbackSink({
|
|
2329
2543
|
baseUrl: this.baseUrl,
|
|
2330
2544
|
keys: config.analytics,
|
|
2331
|
-
log: (...a) => this.log(...a)
|
|
2545
|
+
log: (...a) => this.log(...a),
|
|
2546
|
+
onSessionId: (id) => {
|
|
2547
|
+
this.analyticsFeedbackSessionId = id;
|
|
2548
|
+
}
|
|
2332
2549
|
}) : void 0;
|
|
2550
|
+
const device = config.device ?? collectDeviceInfo(config.appVersion);
|
|
2333
2551
|
this.analytics = new AnalyticsManager({
|
|
2334
2552
|
sink: analyticsSink,
|
|
2335
2553
|
publicKey: config.analytics?.publicKey ?? this.config.apiKey,
|
|
2336
2554
|
language: typeof navigator !== "undefined" ? navigator.language : void 0,
|
|
2337
2555
|
platform: config.platform ?? "web",
|
|
2338
|
-
device
|
|
2556
|
+
device,
|
|
2557
|
+
maxBatchSize: ANALYTICS_MAX_BATCH_SIZE,
|
|
2558
|
+
onFlushNeeded: () => this.flushAnalytics()
|
|
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
|
+
}
|
|
2571
|
+
collectGeoInfo().then((geo) => {
|
|
2572
|
+
if (geo) this.analytics?.updateDevice(geo);
|
|
2573
|
+
}).catch(() => {
|
|
2339
2574
|
});
|
|
2340
2575
|
this.navObserver = new NavigationObserver();
|
|
2341
|
-
this.navObserver.onVisit((v) => this.track("
|
|
2576
|
+
this.navObserver.onVisit((v) => this.track("deepdots_page_view", { screen: v.screen, duration_seconds: v.durationSeconds }));
|
|
2342
2577
|
this.navObserver.install();
|
|
2343
2578
|
this.engagement = new EngagementTracker();
|
|
2344
2579
|
this.engagement.resume();
|
|
2345
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
|
+
}
|
|
2346
2588
|
this.initialized = true;
|
|
2347
2589
|
this.log("SDK initialized", this.config);
|
|
2348
2590
|
if (this.renderer.init) this.renderer.init();
|
|
@@ -2383,6 +2625,16 @@ var DeepdotsPopups = class {
|
|
|
2383
2625
|
if (!this.tracking?.isTrackingEnabled()) return;
|
|
2384
2626
|
this.analytics?.setUserAttributes(attributes);
|
|
2385
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
|
+
}
|
|
2386
2638
|
/**
|
|
2387
2639
|
* Info interna del usuario que solo conoce el host (idioma, edad, plan…), persistida en el
|
|
2388
2640
|
* Contact del backend para segmentación/targeting de popups. Identificada por el `userId` del
|
|
@@ -2400,23 +2652,23 @@ var DeepdotsPopups = class {
|
|
|
2400
2652
|
this.analytics?.enterMiniService(name, entryPointType);
|
|
2401
2653
|
this.log("analytics \xB7 enterMiniService:", name, entryPointType ?? "");
|
|
2402
2654
|
}
|
|
2403
|
-
/** Cierra el mini-service
|
|
2404
|
-
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) {
|
|
2405
2657
|
if (!this.tracking?.isTrackingEnabled()) return;
|
|
2406
|
-
this.analytics?.exitMiniService();
|
|
2407
|
-
this.log("analytics \xB7 exitMiniService");
|
|
2658
|
+
this.analytics?.exitMiniService(name);
|
|
2659
|
+
this.log("analytics \xB7 exitMiniService:", name);
|
|
2408
2660
|
}
|
|
2409
2661
|
/** Findability (#31/#35): registra una búsqueda. `has_results` se deriva de `resultsCount`. */
|
|
2410
2662
|
trackSearch(query, resultsCount, params) {
|
|
2411
|
-
this.track("
|
|
2663
|
+
this.track("deepdots_search", { query, results_count: resultsCount, has_results: resultsCount > 0, ...params ?? {} });
|
|
2412
2664
|
}
|
|
2413
2665
|
/** Findability friction (#34/#35): señal de fricción con su `friction_topic`. */
|
|
2414
2666
|
trackFindabilityFriction(frictionTopic, params) {
|
|
2415
|
-
this.track("
|
|
2667
|
+
this.track("deepdots_findability_friction", { friction_topic: frictionTopic, ...params ?? {} });
|
|
2416
2668
|
}
|
|
2417
2669
|
/** Funnel: un paso del embudo, correlacionado por `taskId`. El backend reconstruye conversión/drop-off/tiempo. */
|
|
2418
2670
|
trackFunnelStep(funnel, step, taskId, params) {
|
|
2419
|
-
this.track("
|
|
2671
|
+
this.track("deepdots_funnel_step", { funnel, step, task_id: taskId, ...params ?? {} });
|
|
2420
2672
|
}
|
|
2421
2673
|
/**
|
|
2422
2674
|
* Navegación MANUAL (entornos sin History API, p. ej. React Native con React Navigation).
|
|
@@ -2442,7 +2694,7 @@ var DeepdotsPopups = class {
|
|
|
2442
2694
|
onBackground() {
|
|
2443
2695
|
this.navObserver?.stop();
|
|
2444
2696
|
this.navStarted = false;
|
|
2445
|
-
this.
|
|
2697
|
+
if (this.tracking?.isTrackingEnabled()) this.analytics?.exitAllMiniServices();
|
|
2446
2698
|
this.flushEngagement();
|
|
2447
2699
|
this.engagement?.pause();
|
|
2448
2700
|
this.flushAnalytics();
|
|
@@ -2465,7 +2717,7 @@ var DeepdotsPopups = class {
|
|
|
2465
2717
|
flushEngagement() {
|
|
2466
2718
|
if (!this.tracking?.isTrackingEnabled()) return;
|
|
2467
2719
|
const ms = this.engagement?.consume() ?? 0;
|
|
2468
|
-
if (ms > 0) this.track("
|
|
2720
|
+
if (ms > 0) this.track("deepdots_user_engagement", { engagement_time_msec: ms });
|
|
2469
2721
|
}
|
|
2470
2722
|
analyticsIdentity() {
|
|
2471
2723
|
return {
|
|
@@ -2476,9 +2728,11 @@ var DeepdotsPopups = class {
|
|
|
2476
2728
|
/** Flush automático al ocultar/cerrar la página (no perder el lote pendiente). */
|
|
2477
2729
|
setupAnalyticsFlush() {
|
|
2478
2730
|
if (typeof document === "undefined" || typeof window === "undefined") return;
|
|
2731
|
+
this.analyticsFlushTimer = setInterval(() => this.flushAnalytics(), ANALYTICS_FLUSH_INTERVAL_MS);
|
|
2479
2732
|
window.addEventListener("pagehide", () => {
|
|
2733
|
+
clearInterval(this.analyticsFlushTimer);
|
|
2480
2734
|
this.navObserver?.stop();
|
|
2481
|
-
this.
|
|
2735
|
+
if (this.tracking?.isTrackingEnabled()) this.analytics?.exitAllMiniServices();
|
|
2482
2736
|
this.flushEngagement();
|
|
2483
2737
|
this.flushAnalytics();
|
|
2484
2738
|
});
|
|
@@ -2920,7 +3174,8 @@ var DeepdotsPopups = class {
|
|
|
2920
3174
|
const userId = this.tracking?.getUserId() ?? this.config?.userId;
|
|
2921
3175
|
const sessionId = this.tracking?.getSessionId() ?? void 0;
|
|
2922
3176
|
const miniService = this.analytics?.getMiniService() ?? void 0;
|
|
2923
|
-
|
|
3177
|
+
const analyticsFeedbackSessionId = this.analyticsFeedbackSessionId;
|
|
3178
|
+
this.log("tracking \xB7 survey identity \u2192", { userId: userId ?? null, sessionId: sessionId ?? null, miniService: miniService ?? null, analyticsFeedbackSessionId: analyticsFeedbackSessionId ?? null });
|
|
2924
3179
|
if (this.renderer) {
|
|
2925
3180
|
this.renderer.show(
|
|
2926
3181
|
surveyId,
|
|
@@ -2932,7 +3187,8 @@ var DeepdotsPopups = class {
|
|
|
2932
3187
|
userId,
|
|
2933
3188
|
style,
|
|
2934
3189
|
sessionId,
|
|
2935
|
-
miniService
|
|
3190
|
+
miniService,
|
|
3191
|
+
analyticsFeedbackSessionId
|
|
2936
3192
|
);
|
|
2937
3193
|
return;
|
|
2938
3194
|
}
|
|
@@ -2950,7 +3206,8 @@ var DeepdotsPopups = class {
|
|
|
2950
3206
|
userId,
|
|
2951
3207
|
style,
|
|
2952
3208
|
sessionId,
|
|
2953
|
-
miniService
|
|
3209
|
+
miniService,
|
|
3210
|
+
analyticsFeedbackSessionId
|
|
2954
3211
|
);
|
|
2955
3212
|
});
|
|
2956
3213
|
}
|
|
@@ -3141,6 +3398,10 @@ function setupReactNative(sdk2, config, deps = {}) {
|
|
|
3141
3398
|
const device = deps.deviceInfo ? collectRnDevice(deps.deviceInfo) : config.device;
|
|
3142
3399
|
const platform = deps.platform ?? config.platform;
|
|
3143
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
|
+
}
|
|
3144
3405
|
let subscription;
|
|
3145
3406
|
if (deps.appState?.addEventListener) {
|
|
3146
3407
|
subscription = deps.appState.addEventListener("change", (state) => {
|