@magicfeedback/popup-sdk 1.1.1 → 1.1.2
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 +32 -15
- package/dist/index.d.ts +32 -15
- package/dist/index.js +113 -29
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +112 -29
- package/dist/index.mjs.map +1 -1
- package/dist/react-native.d.mts +26 -10
- package/dist/react-native.d.ts +26 -10
- package/dist/react-native.js +113 -29
- package/dist/react-native.js.map +1 -1
- package/dist/react-native.mjs +112 -29
- 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
|
@@ -61,12 +61,13 @@ var STORAGE_KEYS = {
|
|
|
61
61
|
firstSeen: "deepdots.user.first_seen",
|
|
62
62
|
contactAttributes: "deepdots.contact.attributes"
|
|
63
63
|
};
|
|
64
|
-
function buildSurveyIdentity(userId, sessionId, miniService = null) {
|
|
64
|
+
function buildSurveyIdentity(userId, sessionId, miniService = null, analyticsFeedbackSessionId = null) {
|
|
65
65
|
const profile = userId ? [{ key: "external-user-id", value: [userId] }] : [];
|
|
66
66
|
const metadata = [];
|
|
67
67
|
if (sessionId) metadata.push({ key: "session_id", value: [sessionId] });
|
|
68
68
|
if (userId) metadata.push({ key: "user_id", value: [userId] });
|
|
69
69
|
if (miniService) metadata.push({ key: "mini_service", value: [miniService] });
|
|
70
|
+
if (analyticsFeedbackSessionId) metadata.push({ key: "deepdots_analytics_feedback_session_id", value: [analyticsFeedbackSessionId] });
|
|
70
71
|
return { profile, metadata };
|
|
71
72
|
}
|
|
72
73
|
function defaultUuid() {
|
|
@@ -137,4 +138,4 @@ export {
|
|
|
137
138
|
buildSurveyIdentity,
|
|
138
139
|
TrackingManager
|
|
139
140
|
};
|
|
140
|
-
//# sourceMappingURL=chunk-
|
|
141
|
+
//# sourceMappingURL=chunk-6WTOQGET.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/tracking/tracking-manager.ts"],"sourcesContent":["/**\n * Identidad + sesión para el tracking de navegación (Fase 1).\n * Implementa el \"Contrato común Web+KMP\".\n *\n * Modelo: el SDK gestiona el `user_id` (persistente). El `session_id` es propiedad\n * del BACKEND: llega en la respuesta de `POST /sdk/popups` y se cachea; el backend\n * cose sesiones por `user_id` + ventana. El SDK NO genera ni expira sesiones.\n *\n * Sin dependencias de DOM: recibe el storage y (opcionalmente) el reloj y el\n * generador de uuid por inyección. El binding a `localStorage` vive en `createDefaultStorage`.\n */\n\n/** Store clave/valor mínimo (espejo conceptual del `KeyValueStorage` de KMP). */\nexport interface KeyValueStorage {\n getItem(key: string): string | null;\n setItem(key: string, value: string): void;\n removeItem(key: string): void;\n}\n\n/** Impl en memoria — usada en tests y como fallback sin `localStorage`. */\nexport class InMemoryStorage implements KeyValueStorage {\n private map = new Map<string, string>();\n getItem(key: string): string | null {\n return this.map.has(key) ? (this.map.get(key) as string) : null;\n }\n setItem(key: string, value: string): void {\n this.map.set(key, value);\n }\n removeItem(key: string): void {\n this.map.delete(key);\n }\n}\n\n/** Storage por defecto en Web: `localStorage` si está disponible, si no in-memory. */\nexport function createDefaultStorage(): KeyValueStorage {\n try {\n if (typeof window !== 'undefined' && window.localStorage) {\n const ls = window.localStorage;\n const probe = '__deepdots_probe__';\n ls.setItem(probe, '1');\n ls.removeItem(probe);\n return {\n getItem: (k) => ls.getItem(k),\n setItem: (k, v) => ls.setItem(k, v),\n removeItem: (k) => ls.removeItem(k),\n };\n }\n } catch {\n // cae a in-memory\n }\n return new InMemoryStorage();\n}\n\n/** Claves de storage — namespace `deepdots.*`. Solo identidad (la sesión no se persiste). */\nexport const STORAGE_KEYS = {\n userId: 'deepdots.user_id',\n firstSeen: 'deepdots.user.first_seen',\n contactAttributes: 'deepdots.contact.attributes',\n} as const;\n\n/** Par clave/valor con el formato `NativeAnswer` que espera `@magicfeedback/native`. */\nexport interface IdentityAnswer {\n key: string;\n value: string[];\n}\n\n/**\n * Construye el `profile` y el `metadata` de identidad para inyectar en el survey\n * (`magicfeedback.form(appId, publicKey, profile, metadata)`), según el contrato §5.\n * `session_id` solo se incluye si el backend ya lo proveyó.\n */\nexport function buildSurveyIdentity(\n userId: string | null,\n sessionId: string | null,\n miniService: string | null = null,\n analyticsFeedbackSessionId: string | null = null,\n): { profile: IdentityAnswer[]; metadata: IdentityAnswer[] } {\n const profile: IdentityAnswer[] = userId ? [{ key: 'external-user-id', value: [userId] }] : [];\n const metadata: IdentityAnswer[] = [];\n if (sessionId) metadata.push({ key: 'session_id', value: [sessionId] });\n if (userId) metadata.push({ key: 'user_id', value: [userId] });\n if (miniService) metadata.push({ key: 'mini_service', value: [miniService] });\n if (analyticsFeedbackSessionId) metadata.push({ key: 'deepdots_analytics_feedback_session_id', value: [analyticsFeedbackSessionId] });\n return { profile, metadata };\n}\n\nexport interface TrackingManagerOptions {\n storage: KeyValueStorage;\n /** Id provisto por el cliente; si existe se usa y NO se persiste. */\n clientUserId?: string;\n /** Reloj inyectable (default `Date.now`). */\n now?: () => number;\n /** Generador de uuid inyectable (default uuid v4). */\n uuid?: () => string;\n /** Estado inicial del tracking (default `true`). */\n enabled?: boolean;\n}\n\nfunction defaultUuid(): string {\n const g = (typeof globalThis !== 'undefined' ? globalThis : {}) as {\n crypto?: { randomUUID?: () => string; getRandomValues?: (a: Uint8Array) => Uint8Array };\n };\n if (g.crypto?.randomUUID) return g.crypto.randomUUID();\n const bytes = new Uint8Array(16);\n if (g.crypto?.getRandomValues) {\n g.crypto.getRandomValues(bytes);\n } else {\n for (let i = 0; i < 16; i++) bytes[i] = Math.floor(Math.random() * 256);\n }\n bytes[6] = (bytes[6] & 0x0f) | 0x40;\n bytes[8] = (bytes[8] & 0x3f) | 0x80;\n const hex = Array.from(bytes, (b) => b.toString(16).padStart(2, '0'));\n return `${hex.slice(0, 4).join('')}-${hex.slice(4, 6).join('')}-${hex\n .slice(6, 8)\n .join('')}-${hex.slice(8, 10).join('')}-${hex.slice(10, 16).join('')}`;\n}\n\nexport class TrackingManager {\n private storage: KeyValueStorage;\n private clientUserId?: string;\n private now: () => number;\n private uuid: () => string;\n private enabled: boolean;\n\n /** session_id cacheado, provisto por el backend (no se genera ni se persiste). */\n private sessionId: string | null = null;\n private userWasJustCreated = false;\n\n constructor(options: TrackingManagerOptions) {\n this.storage = options.storage;\n this.clientUserId = options.clientUserId;\n this.now = options.now ?? (() => Date.now());\n this.uuid = options.uuid ?? defaultUuid;\n this.enabled = options.enabled ?? true;\n }\n\n isTrackingEnabled(): boolean {\n return this.enabled;\n }\n\n setTrackingEnabled(enabled: boolean): void {\n this.enabled = enabled;\n }\n\n /** Resuelve el user_id según la regla: cliente > persistido > generar+persistir. */\n getUserId(): string | null {\n if (!this.enabled) return null;\n if (this.clientUserId) return this.clientUserId;\n\n const persisted = this.storage.getItem(STORAGE_KEYS.userId);\n if (persisted) return persisted;\n\n const id = this.uuid();\n this.storage.setItem(STORAGE_KEYS.userId, id);\n this.storage.setItem(STORAGE_KEYS.firstSeen, String(this.now()));\n this.userWasJustCreated = true;\n return id;\n }\n\n /** `true` solo en la primera sesión tras crear el user_id propio del SDK. */\n isNewUser(): boolean {\n return this.userWasJustCreated;\n }\n\n /** session_id actual provisto por el backend, o null si aún no llegó. */\n getSessionId(): string | null {\n if (!this.enabled) return null;\n return this.sessionId;\n }\n\n /** Cachea el session_id devuelto por el backend (respuesta de POST /sdk/popups). */\n setSessionId(sessionId: string | null): void {\n if (!this.enabled) return;\n this.sessionId = sessionId;\n }\n}\n"],"mappings":"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","names":[]}
|
|
@@ -42,12 +42,13 @@ var STORAGE_KEYS = {
|
|
|
42
42
|
firstSeen: "deepdots.user.first_seen",
|
|
43
43
|
contactAttributes: "deepdots.contact.attributes"
|
|
44
44
|
};
|
|
45
|
-
function buildSurveyIdentity(userId, sessionId, miniService = null) {
|
|
45
|
+
function buildSurveyIdentity(userId, sessionId, miniService = null, analyticsFeedbackSessionId = null) {
|
|
46
46
|
const profile = userId ? [{ key: "external-user-id", value: [userId] }] : [];
|
|
47
47
|
const metadata = [];
|
|
48
48
|
if (sessionId) metadata.push({ key: "session_id", value: [sessionId] });
|
|
49
49
|
if (userId) metadata.push({ key: "user_id", value: [userId] });
|
|
50
50
|
if (miniService) metadata.push({ key: "mini_service", value: [miniService] });
|
|
51
|
+
if (analyticsFeedbackSessionId) metadata.push({ key: "deepdots_analytics_feedback_session_id", value: [analyticsFeedbackSessionId] });
|
|
51
52
|
return { profile, metadata };
|
|
52
53
|
}
|
|
53
54
|
function defaultUuid() {
|
|
@@ -116,4 +117,4 @@ export {
|
|
|
116
117
|
buildSurveyIdentity,
|
|
117
118
|
TrackingManager
|
|
118
119
|
};
|
|
119
|
-
//# sourceMappingURL=chunk-
|
|
120
|
+
//# sourceMappingURL=chunk-IOAZQPZE.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/tracking/tracking-manager.ts"],"sourcesContent":["/**\n * Identidad + sesión para el tracking de navegación (Fase 1).\n * Implementa el \"Contrato común Web+KMP\".\n *\n * Modelo: el SDK gestiona el `user_id` (persistente). El `session_id` es propiedad\n * del BACKEND: llega en la respuesta de `POST /sdk/popups` y se cachea; el backend\n * cose sesiones por `user_id` + ventana. El SDK NO genera ni expira sesiones.\n *\n * Sin dependencias de DOM: recibe el storage y (opcionalmente) el reloj y el\n * generador de uuid por inyección. El binding a `localStorage` vive en `createDefaultStorage`.\n */\n\n/** Store clave/valor mínimo (espejo conceptual del `KeyValueStorage` de KMP). */\nexport interface KeyValueStorage {\n getItem(key: string): string | null;\n setItem(key: string, value: string): void;\n removeItem(key: string): void;\n}\n\n/** Impl en memoria — usada en tests y como fallback sin `localStorage`. */\nexport class InMemoryStorage implements KeyValueStorage {\n private map = new Map<string, string>();\n getItem(key: string): string | null {\n return this.map.has(key) ? (this.map.get(key) as string) : null;\n }\n setItem(key: string, value: string): void {\n this.map.set(key, value);\n }\n removeItem(key: string): void {\n this.map.delete(key);\n }\n}\n\n/** Storage por defecto en Web: `localStorage` si está disponible, si no in-memory. */\nexport function createDefaultStorage(): KeyValueStorage {\n try {\n if (typeof window !== 'undefined' && window.localStorage) {\n const ls = window.localStorage;\n const probe = '__deepdots_probe__';\n ls.setItem(probe, '1');\n ls.removeItem(probe);\n return {\n getItem: (k) => ls.getItem(k),\n setItem: (k, v) => ls.setItem(k, v),\n removeItem: (k) => ls.removeItem(k),\n };\n }\n } catch {\n // cae a in-memory\n }\n return new InMemoryStorage();\n}\n\n/** Claves de storage — namespace `deepdots.*`. Solo identidad (la sesión no se persiste). */\nexport const STORAGE_KEYS = {\n userId: 'deepdots.user_id',\n firstSeen: 'deepdots.user.first_seen',\n contactAttributes: 'deepdots.contact.attributes',\n} as const;\n\n/** Par clave/valor con el formato `NativeAnswer` que espera `@magicfeedback/native`. */\nexport interface IdentityAnswer {\n key: string;\n value: string[];\n}\n\n/**\n * Construye el `profile` y el `metadata` de identidad para inyectar en el survey\n * (`magicfeedback.form(appId, publicKey, profile, metadata)`), según el contrato §5.\n * `session_id` solo se incluye si el backend ya lo proveyó.\n */\nexport function buildSurveyIdentity(\n userId: string | null,\n sessionId: string | null,\n miniService: string | null = null,\n analyticsFeedbackSessionId: string | null = null,\n): { profile: IdentityAnswer[]; metadata: IdentityAnswer[] } {\n const profile: IdentityAnswer[] = userId ? [{ key: 'external-user-id', value: [userId] }] : [];\n const metadata: IdentityAnswer[] = [];\n if (sessionId) metadata.push({ key: 'session_id', value: [sessionId] });\n if (userId) metadata.push({ key: 'user_id', value: [userId] });\n if (miniService) metadata.push({ key: 'mini_service', value: [miniService] });\n if (analyticsFeedbackSessionId) metadata.push({ key: 'deepdots_analytics_feedback_session_id', value: [analyticsFeedbackSessionId] });\n return { profile, metadata };\n}\n\nexport interface TrackingManagerOptions {\n storage: KeyValueStorage;\n /** Id provisto por el cliente; si existe se usa y NO se persiste. */\n clientUserId?: string;\n /** Reloj inyectable (default `Date.now`). */\n now?: () => number;\n /** Generador de uuid inyectable (default uuid v4). */\n uuid?: () => string;\n /** Estado inicial del tracking (default `true`). */\n enabled?: boolean;\n}\n\nfunction defaultUuid(): string {\n const g = (typeof globalThis !== 'undefined' ? globalThis : {}) as {\n crypto?: { randomUUID?: () => string; getRandomValues?: (a: Uint8Array) => Uint8Array };\n };\n if (g.crypto?.randomUUID) return g.crypto.randomUUID();\n const bytes = new Uint8Array(16);\n if (g.crypto?.getRandomValues) {\n g.crypto.getRandomValues(bytes);\n } else {\n for (let i = 0; i < 16; i++) bytes[i] = Math.floor(Math.random() * 256);\n }\n bytes[6] = (bytes[6] & 0x0f) | 0x40;\n bytes[8] = (bytes[8] & 0x3f) | 0x80;\n const hex = Array.from(bytes, (b) => b.toString(16).padStart(2, '0'));\n return `${hex.slice(0, 4).join('')}-${hex.slice(4, 6).join('')}-${hex\n .slice(6, 8)\n .join('')}-${hex.slice(8, 10).join('')}-${hex.slice(10, 16).join('')}`;\n}\n\nexport class TrackingManager {\n private storage: KeyValueStorage;\n private clientUserId?: string;\n private now: () => number;\n private uuid: () => string;\n private enabled: boolean;\n\n /** session_id cacheado, provisto por el backend (no se genera ni se persiste). */\n private sessionId: string | null = null;\n private userWasJustCreated = false;\n\n constructor(options: TrackingManagerOptions) {\n this.storage = options.storage;\n this.clientUserId = options.clientUserId;\n this.now = options.now ?? (() => Date.now());\n this.uuid = options.uuid ?? defaultUuid;\n this.enabled = options.enabled ?? true;\n }\n\n isTrackingEnabled(): boolean {\n return this.enabled;\n }\n\n setTrackingEnabled(enabled: boolean): void {\n this.enabled = enabled;\n }\n\n /** Resuelve el user_id según la regla: cliente > persistido > generar+persistir. */\n getUserId(): string | null {\n if (!this.enabled) return null;\n if (this.clientUserId) return this.clientUserId;\n\n const persisted = this.storage.getItem(STORAGE_KEYS.userId);\n if (persisted) return persisted;\n\n const id = this.uuid();\n this.storage.setItem(STORAGE_KEYS.userId, id);\n this.storage.setItem(STORAGE_KEYS.firstSeen, String(this.now()));\n this.userWasJustCreated = true;\n return id;\n }\n\n /** `true` solo en la primera sesión tras crear el user_id propio del SDK. */\n isNewUser(): boolean {\n return this.userWasJustCreated;\n }\n\n /** session_id actual provisto por el backend, o null si aún no llegó. */\n getSessionId(): string | null {\n if (!this.enabled) return null;\n return this.sessionId;\n }\n\n /** Cachea el session_id devuelto por el backend (respuesta de POST /sdk/popups). */\n setSessionId(sessionId: string | null): void {\n if (!this.enabled) return;\n this.sessionId = sessionId;\n }\n}\n"],"mappings":"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","names":[]}
|
package/dist/index.d.mts
CHANGED
|
@@ -31,10 +31,11 @@ interface IdentityAnswer {
|
|
|
31
31
|
/**
|
|
32
32
|
* Device info para el context de analytics (Technology #11–13).
|
|
33
33
|
*
|
|
34
|
-
* En Web enviamos `device_type`
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
34
|
+
* En Web enviamos `device_type` + `user_agent` crudo y dejamos que el backend
|
|
35
|
+
* derive OS/navegador del UA (como hace GA). Los campos adicionales (timezone,
|
|
36
|
+
* referrer, viewport, etc.) se recogen de las APIs del navegador sin permisos.
|
|
37
|
+
* `country`/`city` se rellenan de forma asíncrona (ver geo-info.ts).
|
|
38
|
+
* En KMP las APIs nativas dan os_version/device_model reales.
|
|
38
39
|
*/
|
|
39
40
|
interface DeviceInfo {
|
|
40
41
|
device_type: 'mobile' | 'tablet' | 'desktop';
|
|
@@ -42,6 +43,16 @@ interface DeviceInfo {
|
|
|
42
43
|
device_model?: string;
|
|
43
44
|
app_version?: string;
|
|
44
45
|
user_agent?: string;
|
|
46
|
+
timezone?: string;
|
|
47
|
+
referrer?: string;
|
|
48
|
+
viewport_size?: string;
|
|
49
|
+
screen_resolution?: string;
|
|
50
|
+
pixel_ratio?: number;
|
|
51
|
+
entry_type?: string;
|
|
52
|
+
page_load_ms?: number;
|
|
53
|
+
connection_type?: string;
|
|
54
|
+
country?: string;
|
|
55
|
+
city?: string;
|
|
45
56
|
}
|
|
46
57
|
|
|
47
58
|
/**
|
|
@@ -87,11 +98,12 @@ type AnalyticsSink = (payload: AnalyticsEnvelope) => void;
|
|
|
87
98
|
* INTEGRACIÓN creada en la plataforma. Se manda en streaming con `completed:false` y
|
|
88
99
|
* `finished:false` (nunca se "cierra"); el backend cose por `sessionId` + `user_id`.
|
|
89
100
|
*
|
|
90
|
-
* Encoding
|
|
91
|
-
* -
|
|
101
|
+
* Encoding:
|
|
102
|
+
* - todo va en `feedback.metadata`: contexto (user_id, session_id, platform…) + eventos
|
|
103
|
+
* - cada entrada usa `value: string[]` (array de un elemento)
|
|
104
|
+
* - eventos: {key: nombre_evento, value: [JSON(timestamp + params)]}
|
|
92
105
|
* - identidad (user_id) → `profile` como `external-user-id`
|
|
93
|
-
* -
|
|
94
|
-
* - `answers` vacío
|
|
106
|
+
* - `answers` vacío, `metrics` no se usa
|
|
95
107
|
* - `text` vacío
|
|
96
108
|
*/
|
|
97
109
|
|
|
@@ -101,15 +113,14 @@ interface AnalyticsKeys {
|
|
|
101
113
|
}
|
|
102
114
|
interface FeedbackKV {
|
|
103
115
|
key: string;
|
|
104
|
-
value: string;
|
|
116
|
+
value: string[];
|
|
105
117
|
}
|
|
106
118
|
interface AnalyticsFeedbackBody {
|
|
107
119
|
feedback: {
|
|
108
120
|
text: string;
|
|
109
|
-
answers
|
|
121
|
+
answers: FeedbackKV[];
|
|
110
122
|
metadata: FeedbackKV[];
|
|
111
|
-
|
|
112
|
-
profile?: FeedbackKV[];
|
|
123
|
+
profile: FeedbackKV[];
|
|
113
124
|
finished: boolean;
|
|
114
125
|
};
|
|
115
126
|
publicKey: string;
|
|
@@ -128,6 +139,8 @@ interface FeedbackSinkOptions {
|
|
|
128
139
|
log?: (...args: unknown[]) => void;
|
|
129
140
|
/** fetch inyectable (tests / RN). */
|
|
130
141
|
fetchImpl?: typeof fetch;
|
|
142
|
+
/** Callback invocado cuando el backend devuelve un nuevo sessionId (primer POST). */
|
|
143
|
+
onSessionId?: (id: string) => void;
|
|
131
144
|
}
|
|
132
145
|
/**
|
|
133
146
|
* Sink real: transforma el envelope y hace `POST {baseUrl}/sdk/feedback`.
|
|
@@ -285,7 +298,7 @@ interface PopupRenderer {
|
|
|
285
298
|
/** Preparar recursos si aplica */
|
|
286
299
|
init?(): void;
|
|
287
300
|
/** Mostrar popup */
|
|
288
|
-
show(surveyId: string, productId: string, actions: PopupActions | undefined, emit: (type: DeepdotsEventType, surveyId: string, data?: Record<string, unknown>) => void, onClose: () => void, env?: string, userId?: string, style?: PopupStyle, sessionId?: string, miniService?: string): void;
|
|
301
|
+
show(surveyId: string, productId: string, actions: PopupActions | undefined, emit: (type: DeepdotsEventType, surveyId: string, data?: Record<string, unknown>) => void, onClose: () => void, env?: string, userId?: string, style?: PopupStyle, sessionId?: string, miniService?: string, analyticsFeedbackSessionId?: string): void;
|
|
289
302
|
/** Ocultar popup */
|
|
290
303
|
hide(): void;
|
|
291
304
|
}
|
|
@@ -299,7 +312,7 @@ declare class BrowserPopupRenderer implements PopupRenderer {
|
|
|
299
312
|
private container;
|
|
300
313
|
private visible;
|
|
301
314
|
init(): void;
|
|
302
|
-
show(surveyId: string, productId: string, actions: PopupActions | undefined, emit: (type: DeepdotsEventType, surveyId: string, data?: Record<string, unknown>) => void, onClose: () => void, env?: string, userId?: string, style?: PopupStyle, sessionId?: string, miniService?: string): void;
|
|
315
|
+
show(surveyId: string, productId: string, actions: PopupActions | undefined, emit: (type: DeepdotsEventType, surveyId: string, data?: Record<string, unknown>) => void, onClose: () => void, env?: string, userId?: string, style?: PopupStyle, sessionId?: string, miniService?: string, analyticsFeedbackSessionId?: string): void;
|
|
303
316
|
hide(): void;
|
|
304
317
|
}
|
|
305
318
|
/** Factoría para obtener renderer por defecto */
|
|
@@ -363,6 +376,10 @@ declare class DeepdotsPopups {
|
|
|
363
376
|
private tracking;
|
|
364
377
|
/** Capa de analytics (canal separado del feedback). Null hasta init(). */
|
|
365
378
|
private analytics;
|
|
379
|
+
/** feedbackSessionId cacheado del canal de analytics (devuelto por POST /sdk/feedback). */
|
|
380
|
+
private analyticsFeedbackSessionId;
|
|
381
|
+
/** Timer del flush periódico de analytics (cada ANALYTICS_FLUSH_INTERVAL_MS). */
|
|
382
|
+
private analyticsFlushTimer;
|
|
366
383
|
/** Observador de navegación (Fase 2): emite page_view por el canal de analytics. */
|
|
367
384
|
private navObserver;
|
|
368
385
|
/** Tiempo activo (engagement time, #8). */
|
|
@@ -530,7 +547,7 @@ declare class ReactNativePopupRenderer implements PopupRenderer {
|
|
|
530
547
|
private partialEmitted;
|
|
531
548
|
constructor(options?: ReactNativeRendererOptions);
|
|
532
549
|
init(): void;
|
|
533
|
-
show(surveyId: string, productId: string, _actions: PopupActions | undefined, emit: EmitFn, onClose: () => void, env?: string, userId?: string, _style?: PopupStyle, sessionId?: string, miniService?: string): void;
|
|
550
|
+
show(surveyId: string, productId: string, _actions: PopupActions | undefined, emit: EmitFn, onClose: () => void, env?: string, userId?: string, _style?: PopupStyle, sessionId?: string, miniService?: string, analyticsFeedbackSessionId?: string): void;
|
|
534
551
|
hide(): void;
|
|
535
552
|
/**
|
|
536
553
|
* El host la conecta al `onMessage` del WebView. Traduce los mensajes del survey a
|
package/dist/index.d.ts
CHANGED
|
@@ -31,10 +31,11 @@ interface IdentityAnswer {
|
|
|
31
31
|
/**
|
|
32
32
|
* Device info para el context de analytics (Technology #11–13).
|
|
33
33
|
*
|
|
34
|
-
* En Web enviamos `device_type`
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
34
|
+
* En Web enviamos `device_type` + `user_agent` crudo y dejamos que el backend
|
|
35
|
+
* derive OS/navegador del UA (como hace GA). Los campos adicionales (timezone,
|
|
36
|
+
* referrer, viewport, etc.) se recogen de las APIs del navegador sin permisos.
|
|
37
|
+
* `country`/`city` se rellenan de forma asíncrona (ver geo-info.ts).
|
|
38
|
+
* En KMP las APIs nativas dan os_version/device_model reales.
|
|
38
39
|
*/
|
|
39
40
|
interface DeviceInfo {
|
|
40
41
|
device_type: 'mobile' | 'tablet' | 'desktop';
|
|
@@ -42,6 +43,16 @@ interface DeviceInfo {
|
|
|
42
43
|
device_model?: string;
|
|
43
44
|
app_version?: string;
|
|
44
45
|
user_agent?: string;
|
|
46
|
+
timezone?: string;
|
|
47
|
+
referrer?: string;
|
|
48
|
+
viewport_size?: string;
|
|
49
|
+
screen_resolution?: string;
|
|
50
|
+
pixel_ratio?: number;
|
|
51
|
+
entry_type?: string;
|
|
52
|
+
page_load_ms?: number;
|
|
53
|
+
connection_type?: string;
|
|
54
|
+
country?: string;
|
|
55
|
+
city?: string;
|
|
45
56
|
}
|
|
46
57
|
|
|
47
58
|
/**
|
|
@@ -87,11 +98,12 @@ type AnalyticsSink = (payload: AnalyticsEnvelope) => void;
|
|
|
87
98
|
* INTEGRACIÓN creada en la plataforma. Se manda en streaming con `completed:false` y
|
|
88
99
|
* `finished:false` (nunca se "cierra"); el backend cose por `sessionId` + `user_id`.
|
|
89
100
|
*
|
|
90
|
-
* Encoding
|
|
91
|
-
* -
|
|
101
|
+
* Encoding:
|
|
102
|
+
* - todo va en `feedback.metadata`: contexto (user_id, session_id, platform…) + eventos
|
|
103
|
+
* - cada entrada usa `value: string[]` (array de un elemento)
|
|
104
|
+
* - eventos: {key: nombre_evento, value: [JSON(timestamp + params)]}
|
|
92
105
|
* - identidad (user_id) → `profile` como `external-user-id`
|
|
93
|
-
* -
|
|
94
|
-
* - `answers` vacío
|
|
106
|
+
* - `answers` vacío, `metrics` no se usa
|
|
95
107
|
* - `text` vacío
|
|
96
108
|
*/
|
|
97
109
|
|
|
@@ -101,15 +113,14 @@ interface AnalyticsKeys {
|
|
|
101
113
|
}
|
|
102
114
|
interface FeedbackKV {
|
|
103
115
|
key: string;
|
|
104
|
-
value: string;
|
|
116
|
+
value: string[];
|
|
105
117
|
}
|
|
106
118
|
interface AnalyticsFeedbackBody {
|
|
107
119
|
feedback: {
|
|
108
120
|
text: string;
|
|
109
|
-
answers
|
|
121
|
+
answers: FeedbackKV[];
|
|
110
122
|
metadata: FeedbackKV[];
|
|
111
|
-
|
|
112
|
-
profile?: FeedbackKV[];
|
|
123
|
+
profile: FeedbackKV[];
|
|
113
124
|
finished: boolean;
|
|
114
125
|
};
|
|
115
126
|
publicKey: string;
|
|
@@ -128,6 +139,8 @@ interface FeedbackSinkOptions {
|
|
|
128
139
|
log?: (...args: unknown[]) => void;
|
|
129
140
|
/** fetch inyectable (tests / RN). */
|
|
130
141
|
fetchImpl?: typeof fetch;
|
|
142
|
+
/** Callback invocado cuando el backend devuelve un nuevo sessionId (primer POST). */
|
|
143
|
+
onSessionId?: (id: string) => void;
|
|
131
144
|
}
|
|
132
145
|
/**
|
|
133
146
|
* Sink real: transforma el envelope y hace `POST {baseUrl}/sdk/feedback`.
|
|
@@ -285,7 +298,7 @@ interface PopupRenderer {
|
|
|
285
298
|
/** Preparar recursos si aplica */
|
|
286
299
|
init?(): void;
|
|
287
300
|
/** Mostrar popup */
|
|
288
|
-
show(surveyId: string, productId: string, actions: PopupActions | undefined, emit: (type: DeepdotsEventType, surveyId: string, data?: Record<string, unknown>) => void, onClose: () => void, env?: string, userId?: string, style?: PopupStyle, sessionId?: string, miniService?: string): void;
|
|
301
|
+
show(surveyId: string, productId: string, actions: PopupActions | undefined, emit: (type: DeepdotsEventType, surveyId: string, data?: Record<string, unknown>) => void, onClose: () => void, env?: string, userId?: string, style?: PopupStyle, sessionId?: string, miniService?: string, analyticsFeedbackSessionId?: string): void;
|
|
289
302
|
/** Ocultar popup */
|
|
290
303
|
hide(): void;
|
|
291
304
|
}
|
|
@@ -299,7 +312,7 @@ declare class BrowserPopupRenderer implements PopupRenderer {
|
|
|
299
312
|
private container;
|
|
300
313
|
private visible;
|
|
301
314
|
init(): void;
|
|
302
|
-
show(surveyId: string, productId: string, actions: PopupActions | undefined, emit: (type: DeepdotsEventType, surveyId: string, data?: Record<string, unknown>) => void, onClose: () => void, env?: string, userId?: string, style?: PopupStyle, sessionId?: string, miniService?: string): void;
|
|
315
|
+
show(surveyId: string, productId: string, actions: PopupActions | undefined, emit: (type: DeepdotsEventType, surveyId: string, data?: Record<string, unknown>) => void, onClose: () => void, env?: string, userId?: string, style?: PopupStyle, sessionId?: string, miniService?: string, analyticsFeedbackSessionId?: string): void;
|
|
303
316
|
hide(): void;
|
|
304
317
|
}
|
|
305
318
|
/** Factoría para obtener renderer por defecto */
|
|
@@ -363,6 +376,10 @@ declare class DeepdotsPopups {
|
|
|
363
376
|
private tracking;
|
|
364
377
|
/** Capa de analytics (canal separado del feedback). Null hasta init(). */
|
|
365
378
|
private analytics;
|
|
379
|
+
/** feedbackSessionId cacheado del canal de analytics (devuelto por POST /sdk/feedback). */
|
|
380
|
+
private analyticsFeedbackSessionId;
|
|
381
|
+
/** Timer del flush periódico de analytics (cada ANALYTICS_FLUSH_INTERVAL_MS). */
|
|
382
|
+
private analyticsFlushTimer;
|
|
366
383
|
/** Observador de navegación (Fase 2): emite page_view por el canal de analytics. */
|
|
367
384
|
private navObserver;
|
|
368
385
|
/** Tiempo activo (engagement time, #8). */
|
|
@@ -530,7 +547,7 @@ declare class ReactNativePopupRenderer implements PopupRenderer {
|
|
|
530
547
|
private partialEmitted;
|
|
531
548
|
constructor(options?: ReactNativeRendererOptions);
|
|
532
549
|
init(): void;
|
|
533
|
-
show(surveyId: string, productId: string, _actions: PopupActions | undefined, emit: EmitFn, onClose: () => void, env?: string, userId?: string, _style?: PopupStyle, sessionId?: string, miniService?: string): void;
|
|
550
|
+
show(surveyId: string, productId: string, _actions: PopupActions | undefined, emit: EmitFn, onClose: () => void, env?: string, userId?: string, _style?: PopupStyle, sessionId?: string, miniService?: string, analyticsFeedbackSessionId?: string): void;
|
|
534
551
|
hide(): void;
|
|
535
552
|
/**
|
|
536
553
|
* El host la conecta al `onMessage` del WebView. Traduce los mensajes del survey a
|
package/dist/index.js
CHANGED
|
@@ -51,12 +51,13 @@ function createDefaultStorage() {
|
|
|
51
51
|
}
|
|
52
52
|
return new InMemoryStorage();
|
|
53
53
|
}
|
|
54
|
-
function buildSurveyIdentity(userId, sessionId, miniService = null) {
|
|
54
|
+
function buildSurveyIdentity(userId, sessionId, miniService = null, analyticsFeedbackSessionId = null) {
|
|
55
55
|
const profile = userId ? [{ key: "external-user-id", value: [userId] }] : [];
|
|
56
56
|
const metadata = [];
|
|
57
57
|
if (sessionId) metadata.push({ key: "session_id", value: [sessionId] });
|
|
58
58
|
if (userId) metadata.push({ key: "user_id", value: [userId] });
|
|
59
59
|
if (miniService) metadata.push({ key: "mini_service", value: [miniService] });
|
|
60
|
+
if (analyticsFeedbackSessionId) metadata.push({ key: "deepdots_analytics_feedback_session_id", value: [analyticsFeedbackSessionId] });
|
|
60
61
|
return { profile, metadata };
|
|
61
62
|
}
|
|
62
63
|
function defaultUuid() {
|
|
@@ -2991,7 +2992,7 @@ function ensureResponsiveStyles(_popup) {
|
|
|
2991
2992
|
`;
|
|
2992
2993
|
document.head.appendChild(style);
|
|
2993
2994
|
}
|
|
2994
|
-
async function renderPopup(container, surveyId, productId, actions, emit, onClose, env = "production", userId, style, sessionId, miniService) {
|
|
2995
|
+
async function renderPopup(container, surveyId, productId, actions, emit, onClose, env = "production", userId, style, sessionId, miniService, analyticsFeedbackSessionId) {
|
|
2995
2996
|
let surveyCompletedEmitted = false;
|
|
2996
2997
|
let stylesInjected = false;
|
|
2997
2998
|
const isDark = style?.theme === "dark";
|
|
@@ -3302,7 +3303,7 @@ async function renderPopup(container, surveyId, productId, actions, emit, onClos
|
|
|
3302
3303
|
env: env === "production" ? "prod" : "dev"
|
|
3303
3304
|
}
|
|
3304
3305
|
);
|
|
3305
|
-
const { profile, metadata } = buildSurveyIdentity(userId ?? null, sessionId ?? null, miniService ?? null);
|
|
3306
|
+
const { profile, metadata } = buildSurveyIdentity(userId ?? null, sessionId ?? null, miniService ?? null, analyticsFeedbackSessionId ?? null);
|
|
3306
3307
|
formInstance = import_native.default.form(surveyId, productId, profile, metadata);
|
|
3307
3308
|
const generateOptions = {
|
|
3308
3309
|
addButton: false,
|
|
@@ -3722,12 +3723,12 @@ var ReactNativePopupRenderer = class {
|
|
|
3722
3723
|
}
|
|
3723
3724
|
init() {
|
|
3724
3725
|
}
|
|
3725
|
-
show(surveyId, productId, _actions, emit, onClose, env = "production", userId, _style, sessionId, miniService) {
|
|
3726
|
+
show(surveyId, productId, _actions, emit, onClose, env = "production", userId, _style, sessionId, miniService, analyticsFeedbackSessionId) {
|
|
3726
3727
|
this.emitFn = emit;
|
|
3727
3728
|
this.onCloseFn = onClose;
|
|
3728
3729
|
this.currentSurveyId = surveyId;
|
|
3729
3730
|
this.partialEmitted = false;
|
|
3730
|
-
const { profile, metadata } = buildSurveyIdentity(userId ?? null, sessionId ?? null, miniService ?? null);
|
|
3731
|
+
const { profile, metadata } = buildSurveyIdentity(userId ?? null, sessionId ?? null, miniService ?? null, analyticsFeedbackSessionId ?? null);
|
|
3731
3732
|
const html = buildSurveyHtml({ surveyId, productId, env, profile, metadata });
|
|
3732
3733
|
if (this.options.onShow) {
|
|
3733
3734
|
this.options.onShow({ surveyId, productId, html });
|
|
@@ -3818,14 +3819,14 @@ var BrowserPopupRenderer = class {
|
|
|
3818
3819
|
document.body.appendChild(this.container);
|
|
3819
3820
|
}
|
|
3820
3821
|
}
|
|
3821
|
-
show(surveyId, productId, actions, emit, onClose, env = "production", userId, style, sessionId, miniService) {
|
|
3822
|
+
show(surveyId, productId, actions, emit, onClose, env = "production", userId, style, sessionId, miniService, analyticsFeedbackSessionId) {
|
|
3822
3823
|
if (this.visible) return;
|
|
3823
3824
|
if (!this.container || !document.body.contains(this.container)) this.init();
|
|
3824
3825
|
if (!this.container) return;
|
|
3825
3826
|
this.visible = true;
|
|
3826
3827
|
const container = this.container;
|
|
3827
3828
|
void Promise.resolve().then(() => (init_renderPopup(), renderPopup_exports)).then(({ renderPopup: renderPopup2 }) => {
|
|
3828
|
-
renderPopup2(container, surveyId, productId, actions, emit, onClose, env, userId, style, sessionId, miniService);
|
|
3829
|
+
renderPopup2(container, surveyId, productId, actions, emit, onClose, env, userId, style, sessionId, miniService, analyticsFeedbackSessionId);
|
|
3829
3830
|
});
|
|
3830
3831
|
}
|
|
3831
3832
|
hide() {
|
|
@@ -3867,6 +3868,12 @@ var AnalyticsManager = class {
|
|
|
3867
3868
|
this.language = options.language;
|
|
3868
3869
|
this.device = options.device;
|
|
3869
3870
|
this.platform = options.platform ?? "web";
|
|
3871
|
+
this.maxBatchSize = options.maxBatchSize ?? 20;
|
|
3872
|
+
this.onFlushNeeded = options.onFlushNeeded;
|
|
3873
|
+
}
|
|
3874
|
+
/** Actualiza campos de device en runtime (p.ej. country/city tras resolver geo). */
|
|
3875
|
+
updateDevice(partial) {
|
|
3876
|
+
this.device = { ...this.device, ...partial };
|
|
3870
3877
|
}
|
|
3871
3878
|
/** Mezcla user attributes (se coercionan a string). Mutable en runtime. */
|
|
3872
3879
|
setUserAttributes(attrs) {
|
|
@@ -3900,6 +3907,9 @@ var AnalyticsManager = class {
|
|
|
3900
3907
|
timestamp: this.now(),
|
|
3901
3908
|
params: Object.keys(merged).length ? merged : void 0
|
|
3902
3909
|
});
|
|
3910
|
+
if (this.events.length >= this.maxBatchSize) {
|
|
3911
|
+
this.onFlushNeeded?.();
|
|
3912
|
+
}
|
|
3903
3913
|
}
|
|
3904
3914
|
/** Mini-service activo (para inyectarlo en la metadata del survey, #33). */
|
|
3905
3915
|
getMiniService() {
|
|
@@ -3937,44 +3947,55 @@ var AnalyticsManager = class {
|
|
|
3937
3947
|
// src/analytics/feedback-payload.ts
|
|
3938
3948
|
function pushKV(list, key, value) {
|
|
3939
3949
|
if (value === void 0 || value === null || value === "") return;
|
|
3940
|
-
list.push({ key, value: String(value) });
|
|
3950
|
+
list.push({ key, value: [String(value)] });
|
|
3941
3951
|
}
|
|
3942
3952
|
function buildAnalyticsFeedbackBody(envelope, keys, feedbackSessionId) {
|
|
3943
3953
|
const { context } = envelope;
|
|
3944
|
-
const metrics = envelope.events.map((e) => ({
|
|
3945
|
-
key: e.name,
|
|
3946
|
-
value: JSON.stringify({ timestamp: e.timestamp, ...e.params ?? {} })
|
|
3947
|
-
}));
|
|
3948
3954
|
const profile = [];
|
|
3949
3955
|
pushKV(profile, "external-user-id", envelope.userId);
|
|
3950
3956
|
const metadata = [];
|
|
3951
|
-
pushKV(metadata, "
|
|
3952
|
-
pushKV(metadata, "
|
|
3953
|
-
pushKV(metadata, "
|
|
3954
|
-
pushKV(metadata, "
|
|
3957
|
+
pushKV(metadata, "deepdots_user_id", envelope.userId);
|
|
3958
|
+
pushKV(metadata, "deepdots_session_id", envelope.sessionId);
|
|
3959
|
+
pushKV(metadata, "deepdots_platform", context.platform);
|
|
3960
|
+
pushKV(metadata, "deepdots_language", context.language);
|
|
3955
3961
|
if (context.device) {
|
|
3956
|
-
|
|
3957
|
-
pushKV(metadata, "
|
|
3958
|
-
pushKV(metadata, "
|
|
3959
|
-
pushKV(metadata, "
|
|
3960
|
-
pushKV(metadata, "
|
|
3962
|
+
const d = context.device;
|
|
3963
|
+
pushKV(metadata, "deepdots_device_type", d.device_type);
|
|
3964
|
+
pushKV(metadata, "deepdots_os_version", d.os_version);
|
|
3965
|
+
pushKV(metadata, "deepdots_device_model", d.device_model);
|
|
3966
|
+
pushKV(metadata, "deepdots_app_version", d.app_version);
|
|
3967
|
+
pushKV(metadata, "deepdots_user_agent", d.user_agent);
|
|
3968
|
+
pushKV(metadata, "deepdots_timezone", d.timezone);
|
|
3969
|
+
pushKV(metadata, "deepdots_referrer", d.referrer);
|
|
3970
|
+
pushKV(metadata, "deepdots_viewport_size", d.viewport_size);
|
|
3971
|
+
pushKV(metadata, "deepdots_screen_resolution", d.screen_resolution);
|
|
3972
|
+
pushKV(metadata, "deepdots_pixel_ratio", d.pixel_ratio);
|
|
3973
|
+
pushKV(metadata, "deepdots_entry_type", d.entry_type);
|
|
3974
|
+
pushKV(metadata, "deepdots_page_load_ms", d.page_load_ms);
|
|
3975
|
+
pushKV(metadata, "deepdots_connection_type", d.connection_type);
|
|
3976
|
+
pushKV(metadata, "deepdots_country", d.country);
|
|
3977
|
+
pushKV(metadata, "deepdots_city", d.city);
|
|
3961
3978
|
}
|
|
3962
3979
|
for (const [k, v] of Object.entries(context.attributes ?? {})) {
|
|
3963
3980
|
pushKV(metadata, k, v);
|
|
3964
3981
|
}
|
|
3982
|
+
for (const e of envelope.events) {
|
|
3983
|
+
metadata.push({
|
|
3984
|
+
key: e.name,
|
|
3985
|
+
value: [JSON.stringify({ timestamp: e.timestamp, ...e.params ?? {} })]
|
|
3986
|
+
});
|
|
3987
|
+
}
|
|
3965
3988
|
return {
|
|
3966
3989
|
feedback: {
|
|
3967
3990
|
text: "",
|
|
3968
3991
|
answers: [],
|
|
3969
3992
|
metadata,
|
|
3970
|
-
metrics,
|
|
3971
3993
|
profile,
|
|
3972
3994
|
finished: false
|
|
3973
3995
|
},
|
|
3974
3996
|
publicKey: keys.publicKey,
|
|
3975
3997
|
integration: keys.integration,
|
|
3976
3998
|
completed: false,
|
|
3977
|
-
// finished: false,
|
|
3978
3999
|
...feedbackSessionId ? { sessionId: feedbackSessionId } : {}
|
|
3979
4000
|
};
|
|
3980
4001
|
}
|
|
@@ -3999,6 +4020,7 @@ function createFeedbackSink(options) {
|
|
|
3999
4020
|
if (data?.sessionId && data.sessionId !== feedbackSessionId) {
|
|
4000
4021
|
feedbackSessionId = data.sessionId;
|
|
4001
4022
|
options.log?.("[DeepdotsAnalytics] feedbackSessionId cacheado:", feedbackSessionId);
|
|
4023
|
+
options.onSessionId?.(feedbackSessionId);
|
|
4002
4024
|
}
|
|
4003
4025
|
} catch {
|
|
4004
4026
|
}
|
|
@@ -4112,15 +4134,57 @@ function parseDeviceType(ua) {
|
|
|
4112
4134
|
}
|
|
4113
4135
|
return "desktop";
|
|
4114
4136
|
}
|
|
4137
|
+
function getEntryType() {
|
|
4138
|
+
if (typeof performance === "undefined") return void 0;
|
|
4139
|
+
const entries = performance.getEntriesByType("navigation");
|
|
4140
|
+
if (!entries.length) return void 0;
|
|
4141
|
+
return entries[0].type;
|
|
4142
|
+
}
|
|
4143
|
+
function getPageLoadMs() {
|
|
4144
|
+
if (typeof performance === "undefined") return void 0;
|
|
4145
|
+
const entries = performance.getEntriesByType("navigation");
|
|
4146
|
+
if (!entries.length) return void 0;
|
|
4147
|
+
const load = entries[0].loadEventEnd;
|
|
4148
|
+
return load > 0 ? Math.round(load) : void 0;
|
|
4149
|
+
}
|
|
4150
|
+
function getConnectionType() {
|
|
4151
|
+
if (typeof navigator === "undefined") return void 0;
|
|
4152
|
+
return navigator.connection?.effectiveType ?? void 0;
|
|
4153
|
+
}
|
|
4115
4154
|
function collectDeviceInfo(appVersion) {
|
|
4116
4155
|
const ua = typeof navigator !== "undefined" ? navigator.userAgent : "";
|
|
4117
4156
|
return {
|
|
4118
4157
|
device_type: parseDeviceType(ua),
|
|
4119
4158
|
user_agent: ua || void 0,
|
|
4120
|
-
app_version: appVersion
|
|
4159
|
+
app_version: appVersion,
|
|
4160
|
+
timezone: typeof Intl !== "undefined" ? Intl.DateTimeFormat().resolvedOptions().timeZone : void 0,
|
|
4161
|
+
referrer: typeof document !== "undefined" && document.referrer ? document.referrer : void 0,
|
|
4162
|
+
viewport_size: typeof window !== "undefined" ? `${window.innerWidth}x${window.innerHeight}` : void 0,
|
|
4163
|
+
screen_resolution: typeof screen !== "undefined" ? `${screen.width}x${screen.height}` : void 0,
|
|
4164
|
+
pixel_ratio: typeof window !== "undefined" ? window.devicePixelRatio : void 0,
|
|
4165
|
+
entry_type: getEntryType(),
|
|
4166
|
+
page_load_ms: getPageLoadMs(),
|
|
4167
|
+
connection_type: getConnectionType()
|
|
4121
4168
|
};
|
|
4122
4169
|
}
|
|
4123
4170
|
|
|
4171
|
+
// src/analytics/geo-info.ts
|
|
4172
|
+
async function collectGeoInfo(fetchImpl) {
|
|
4173
|
+
const f = fetchImpl ?? (typeof fetch !== "undefined" ? fetch : void 0);
|
|
4174
|
+
if (!f) return null;
|
|
4175
|
+
try {
|
|
4176
|
+
const res = await f("https://ipapi.co/json/");
|
|
4177
|
+
if (!res.ok) return null;
|
|
4178
|
+
const data = await res.json();
|
|
4179
|
+
const country = data.country_code || void 0;
|
|
4180
|
+
const city = data.city || void 0;
|
|
4181
|
+
if (!country && !city) return null;
|
|
4182
|
+
return { country, city };
|
|
4183
|
+
} catch {
|
|
4184
|
+
return null;
|
|
4185
|
+
}
|
|
4186
|
+
}
|
|
4187
|
+
|
|
4124
4188
|
// src/analytics/engagement-tracker.ts
|
|
4125
4189
|
var EngagementTracker = class {
|
|
4126
4190
|
constructor(now = () => Date.now()) {
|
|
@@ -4192,6 +4256,8 @@ var ContactManager = class {
|
|
|
4192
4256
|
// src/core/deepdots-popups.ts
|
|
4193
4257
|
var EXIT_QUEUE_STORAGE_KEY = "__deepdots_exit_popup_queue__";
|
|
4194
4258
|
var DAY_IN_MS = 24 * 60 * 60 * 1e3;
|
|
4259
|
+
var ANALYTICS_MAX_BATCH_SIZE = 20;
|
|
4260
|
+
var ANALYTICS_FLUSH_INTERVAL_MS = 3e4;
|
|
4195
4261
|
var DeepdotsPopups = class {
|
|
4196
4262
|
constructor() {
|
|
4197
4263
|
this.config = null;
|
|
@@ -4217,6 +4283,10 @@ var DeepdotsPopups = class {
|
|
|
4217
4283
|
this.tracking = null;
|
|
4218
4284
|
/** Capa de analytics (canal separado del feedback). Null hasta init(). */
|
|
4219
4285
|
this.analytics = null;
|
|
4286
|
+
/** feedbackSessionId cacheado del canal de analytics (devuelto por POST /sdk/feedback). */
|
|
4287
|
+
this.analyticsFeedbackSessionId = void 0;
|
|
4288
|
+
/** Timer del flush periódico de analytics (cada ANALYTICS_FLUSH_INTERVAL_MS). */
|
|
4289
|
+
this.analyticsFlushTimer = void 0;
|
|
4220
4290
|
/** Observador de navegación (Fase 2): emite page_view por el canal de analytics. */
|
|
4221
4291
|
this.navObserver = null;
|
|
4222
4292
|
/** Tiempo activo (engagement time, #8). */
|
|
@@ -4263,14 +4333,23 @@ var DeepdotsPopups = class {
|
|
|
4263
4333
|
const analyticsSink = config.analytics ? createFeedbackSink({
|
|
4264
4334
|
baseUrl: this.baseUrl,
|
|
4265
4335
|
keys: config.analytics,
|
|
4266
|
-
log: (...a) => this.log(...a)
|
|
4336
|
+
log: (...a) => this.log(...a),
|
|
4337
|
+
onSessionId: (id) => {
|
|
4338
|
+
this.analyticsFeedbackSessionId = id;
|
|
4339
|
+
}
|
|
4267
4340
|
}) : void 0;
|
|
4268
4341
|
this.analytics = new AnalyticsManager({
|
|
4269
4342
|
sink: analyticsSink,
|
|
4270
4343
|
publicKey: config.analytics?.publicKey ?? this.config.apiKey,
|
|
4271
4344
|
language: typeof navigator !== "undefined" ? navigator.language : void 0,
|
|
4272
4345
|
platform: config.platform ?? "web",
|
|
4273
|
-
device: config.device ?? collectDeviceInfo(config.appVersion)
|
|
4346
|
+
device: config.device ?? collectDeviceInfo(config.appVersion),
|
|
4347
|
+
maxBatchSize: ANALYTICS_MAX_BATCH_SIZE,
|
|
4348
|
+
onFlushNeeded: () => this.flushAnalytics()
|
|
4349
|
+
});
|
|
4350
|
+
collectGeoInfo().then((geo) => {
|
|
4351
|
+
if (geo) this.analytics?.updateDevice(geo);
|
|
4352
|
+
}).catch(() => {
|
|
4274
4353
|
});
|
|
4275
4354
|
this.navObserver = new NavigationObserver();
|
|
4276
4355
|
this.navObserver.onVisit((v) => this.track("page_view", { screen: v.screen, duration_seconds: v.durationSeconds }));
|
|
@@ -4411,7 +4490,9 @@ var DeepdotsPopups = class {
|
|
|
4411
4490
|
/** Flush automático al ocultar/cerrar la página (no perder el lote pendiente). */
|
|
4412
4491
|
setupAnalyticsFlush() {
|
|
4413
4492
|
if (typeof document === "undefined" || typeof window === "undefined") return;
|
|
4493
|
+
this.analyticsFlushTimer = setInterval(() => this.flushAnalytics(), ANALYTICS_FLUSH_INTERVAL_MS);
|
|
4414
4494
|
window.addEventListener("pagehide", () => {
|
|
4495
|
+
clearInterval(this.analyticsFlushTimer);
|
|
4415
4496
|
this.navObserver?.stop();
|
|
4416
4497
|
this.exitMiniService();
|
|
4417
4498
|
this.flushEngagement();
|
|
@@ -4855,7 +4936,8 @@ var DeepdotsPopups = class {
|
|
|
4855
4936
|
const userId = this.tracking?.getUserId() ?? this.config?.userId;
|
|
4856
4937
|
const sessionId = this.tracking?.getSessionId() ?? void 0;
|
|
4857
4938
|
const miniService = this.analytics?.getMiniService() ?? void 0;
|
|
4858
|
-
|
|
4939
|
+
const analyticsFeedbackSessionId = this.analyticsFeedbackSessionId;
|
|
4940
|
+
this.log("tracking \xB7 survey identity \u2192", { userId: userId ?? null, sessionId: sessionId ?? null, miniService: miniService ?? null, analyticsFeedbackSessionId: analyticsFeedbackSessionId ?? null });
|
|
4859
4941
|
if (this.renderer) {
|
|
4860
4942
|
this.renderer.show(
|
|
4861
4943
|
surveyId,
|
|
@@ -4867,7 +4949,8 @@ var DeepdotsPopups = class {
|
|
|
4867
4949
|
userId,
|
|
4868
4950
|
style,
|
|
4869
4951
|
sessionId,
|
|
4870
|
-
miniService
|
|
4952
|
+
miniService,
|
|
4953
|
+
analyticsFeedbackSessionId
|
|
4871
4954
|
);
|
|
4872
4955
|
return;
|
|
4873
4956
|
}
|
|
@@ -4885,7 +4968,8 @@ var DeepdotsPopups = class {
|
|
|
4885
4968
|
userId,
|
|
4886
4969
|
style,
|
|
4887
4970
|
sessionId,
|
|
4888
|
-
miniService
|
|
4971
|
+
miniService,
|
|
4972
|
+
analyticsFeedbackSessionId
|
|
4889
4973
|
);
|
|
4890
4974
|
});
|
|
4891
4975
|
}
|