@magicfeedback/popup-sdk 1.1.4 → 1.1.5
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/assets/assets/style.css +10 -1
- package/dist/{chunk-6WTOQGET.mjs → chunk-7TOLYAFL.mjs} +35 -2
- package/dist/chunk-7TOLYAFL.mjs.map +1 -0
- package/dist/{chunk-IOAZQPZE.mjs → chunk-LSTKGFU7.mjs} +35 -2
- package/dist/chunk-LSTKGFU7.mjs.map +1 -0
- package/dist/index.d.mts +11 -1
- package/dist/index.d.ts +11 -1
- package/dist/index.js +74 -4
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +10 -6
- package/dist/index.mjs.map +1 -1
- package/dist/react-native.d.mts +10 -1
- package/dist/react-native.d.ts +10 -1
- package/dist/react-native.js +74 -4
- package/dist/react-native.js.map +1 -1
- package/dist/react-native.mjs +10 -6
- package/dist/react-native.mjs.map +1 -1
- package/dist/{renderPopup-YLFRGPQN.mjs → renderPopup-MES7IINW.mjs} +34 -3
- package/dist/renderPopup-MES7IINW.mjs.map +1 -0
- package/dist/{renderPopup-KBDWWSUU.mjs → renderPopup-YNH2TD3B.mjs} +34 -3
- package/dist/renderPopup-YNH2TD3B.mjs.map +1 -0
- package/package.json +1 -1
- package/dist/chunk-6WTOQGET.mjs.map +0 -1
- package/dist/chunk-IOAZQPZE.mjs.map +0 -1
- package/dist/renderPopup-KBDWWSUU.mjs.map +0 -1
- package/dist/renderPopup-YLFRGPQN.mjs.map +0 -1
|
@@ -138,13 +138,22 @@
|
|
|
138
138
|
padding: 0;
|
|
139
139
|
font-weight: 900;
|
|
140
140
|
color: #222;
|
|
141
|
-
font-family: 'Montserrat';
|
|
141
|
+
font-family: var(--deepdots-font, 'Montserrat');
|
|
142
142
|
font-size: 18px;
|
|
143
143
|
text-transform: uppercase;
|
|
144
144
|
text-align: center;
|
|
145
145
|
margin-bottom: 40px;
|
|
146
146
|
}
|
|
147
147
|
|
|
148
|
+
/* Los controles de formulario no heredan font-family por defecto: cuando hay
|
|
149
|
+
fuente custom (clase deepdots-has-font), los forzamos a usar la del contenedor. */
|
|
150
|
+
.deepdots-popup.deepdots-has-font button,
|
|
151
|
+
.deepdots-popup.deepdots-has-font input,
|
|
152
|
+
.deepdots-popup.deepdots-has-font textarea,
|
|
153
|
+
.deepdots-popup.deepdots-has-font select {
|
|
154
|
+
font-family: var(--deepdots-font, inherit);
|
|
155
|
+
}
|
|
156
|
+
|
|
148
157
|
.deepdots-popup h6 {
|
|
149
158
|
margin-bottom: 1rem;
|
|
150
159
|
}
|
|
@@ -129,6 +129,37 @@ var TrackingManager = class {
|
|
|
129
129
|
}
|
|
130
130
|
};
|
|
131
131
|
|
|
132
|
+
// src/ui/font.ts
|
|
133
|
+
var FORMAT_BY_EXT = {
|
|
134
|
+
woff2: "woff2",
|
|
135
|
+
woff: "woff",
|
|
136
|
+
ttf: "truetype",
|
|
137
|
+
otf: "opentype"
|
|
138
|
+
};
|
|
139
|
+
function fontFormatFromUrl(url) {
|
|
140
|
+
const clean = url.split("?")[0].split("#")[0];
|
|
141
|
+
const ext = clean.slice(clean.lastIndexOf(".") + 1).toLowerCase();
|
|
142
|
+
return FORMAT_BY_EXT[ext];
|
|
143
|
+
}
|
|
144
|
+
function sanitizeFamily(family) {
|
|
145
|
+
return family.replace(/[^A-Za-z0-9 ._-]/g, "").trim();
|
|
146
|
+
}
|
|
147
|
+
function isSafeFontUrl(url) {
|
|
148
|
+
if (!/^(https?:|data:)/i.test(url)) return false;
|
|
149
|
+
return !/[\x00-\x20"<>\\]/.test(url);
|
|
150
|
+
}
|
|
151
|
+
function buildFontFamilyValue(family) {
|
|
152
|
+
const safe = sanitizeFamily(family);
|
|
153
|
+
return `"${safe}", -apple-system, system-ui, sans-serif`;
|
|
154
|
+
}
|
|
155
|
+
function buildFontFaceCss(family, url) {
|
|
156
|
+
if (!url || !isSafeFontUrl(url)) return "";
|
|
157
|
+
const safe = sanitizeFamily(family);
|
|
158
|
+
const fmt = fontFormatFromUrl(url);
|
|
159
|
+
const src = fmt ? `url("${url}") format("${fmt}")` : `url("${url}")`;
|
|
160
|
+
return `@font-face{font-family:"${safe}";src:${src};font-display:swap;}`;
|
|
161
|
+
}
|
|
162
|
+
|
|
132
163
|
export {
|
|
133
164
|
__commonJS,
|
|
134
165
|
__toESM,
|
|
@@ -136,6 +167,8 @@ export {
|
|
|
136
167
|
createDefaultStorage,
|
|
137
168
|
STORAGE_KEYS,
|
|
138
169
|
buildSurveyIdentity,
|
|
139
|
-
TrackingManager
|
|
170
|
+
TrackingManager,
|
|
171
|
+
buildFontFamilyValue,
|
|
172
|
+
buildFontFaceCss
|
|
140
173
|
};
|
|
141
|
-
//# sourceMappingURL=chunk-
|
|
174
|
+
//# sourceMappingURL=chunk-7TOLYAFL.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/tracking/tracking-manager.ts","../src/ui/font.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","/**\n * Helpers puros (sin DOM) para la fuente personalizable del popup/survey.\n * Espejo exacto en KMP: `ui/Font.kt`. Cualquier cambio aquí se replica allí.\n */\n\nconst FORMAT_BY_EXT: Record<string, string> = {\n woff2: 'woff2',\n woff: 'woff',\n ttf: 'truetype',\n otf: 'opentype',\n};\n\n/** Deriva el `format()` del `@font-face` a partir de la extensión de la URL. */\nexport function fontFormatFromUrl(url: string): string | undefined {\n const clean = url.split('?')[0].split('#')[0];\n const ext = clean.slice(clean.lastIndexOf('.') + 1).toLowerCase();\n return FORMAT_BY_EXT[ext];\n}\n\n// family/url provienen de la API y se interpolan en CSS y en el <style> del HTML del WebView.\n// Saneamos para evitar romper el string/rule (CSS injection) o cerrar </style> (script injection).\nfunction sanitizeFamily(family: string): string {\n return family.replace(/[^A-Za-z0-9 ._-]/g, '').trim();\n}\n\nfunction isSafeFontUrl(url: string): boolean {\n // Solo http(s)/data y sin caracteres que rompan url(\"...\") o el <style>:\n // comillas, <>, backslash, y cualquier whitespace o carácter de control.\n if (!/^(https?:|data:)/i.test(url)) return false;\n // eslint-disable-next-line no-control-regex\n return !/[\\x00-\\x20\"<>\\\\]/.test(url);\n}\n\n/** Valor a aplicar en `font-family`: nombre custom + fallback de sistema. */\nexport function buildFontFamilyValue(family: string): string {\n const safe = sanitizeFamily(family);\n return `\"${safe}\", -apple-system, system-ui, sans-serif`;\n}\n\n/** CSS del `@font-face`. Devuelve \"\" si no hay url (o no es segura). */\nexport function buildFontFaceCss(family: string, url?: string): string {\n if (!url || !isSafeFontUrl(url)) return '';\n const safe = sanitizeFamily(family);\n const fmt = fontFormatFromUrl(url);\n const src = fmt ? `url(\"${url}\") format(\"${fmt}\")` : `url(\"${url}\")`;\n return `@font-face{font-family:\"${safe}\";src:${src};font-display:swap;}`;\n}\n"],"mappings":"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","names":[]}
|
|
@@ -110,11 +110,44 @@ var TrackingManager = class {
|
|
|
110
110
|
}
|
|
111
111
|
};
|
|
112
112
|
|
|
113
|
+
// src/ui/font.ts
|
|
114
|
+
var FORMAT_BY_EXT = {
|
|
115
|
+
woff2: "woff2",
|
|
116
|
+
woff: "woff",
|
|
117
|
+
ttf: "truetype",
|
|
118
|
+
otf: "opentype"
|
|
119
|
+
};
|
|
120
|
+
function fontFormatFromUrl(url) {
|
|
121
|
+
const clean = url.split("?")[0].split("#")[0];
|
|
122
|
+
const ext = clean.slice(clean.lastIndexOf(".") + 1).toLowerCase();
|
|
123
|
+
return FORMAT_BY_EXT[ext];
|
|
124
|
+
}
|
|
125
|
+
function sanitizeFamily(family) {
|
|
126
|
+
return family.replace(/[^A-Za-z0-9 ._-]/g, "").trim();
|
|
127
|
+
}
|
|
128
|
+
function isSafeFontUrl(url) {
|
|
129
|
+
if (!/^(https?:|data:)/i.test(url)) return false;
|
|
130
|
+
return !/[\x00-\x20"<>\\]/.test(url);
|
|
131
|
+
}
|
|
132
|
+
function buildFontFamilyValue(family) {
|
|
133
|
+
const safe = sanitizeFamily(family);
|
|
134
|
+
return `"${safe}", -apple-system, system-ui, sans-serif`;
|
|
135
|
+
}
|
|
136
|
+
function buildFontFaceCss(family, url) {
|
|
137
|
+
if (!url || !isSafeFontUrl(url)) return "";
|
|
138
|
+
const safe = sanitizeFamily(family);
|
|
139
|
+
const fmt = fontFormatFromUrl(url);
|
|
140
|
+
const src = fmt ? `url("${url}") format("${fmt}")` : `url("${url}")`;
|
|
141
|
+
return `@font-face{font-family:"${safe}";src:${src};font-display:swap;}`;
|
|
142
|
+
}
|
|
143
|
+
|
|
113
144
|
export {
|
|
114
145
|
__require,
|
|
115
146
|
createDefaultStorage,
|
|
116
147
|
STORAGE_KEYS,
|
|
117
148
|
buildSurveyIdentity,
|
|
118
|
-
TrackingManager
|
|
149
|
+
TrackingManager,
|
|
150
|
+
buildFontFamilyValue,
|
|
151
|
+
buildFontFaceCss
|
|
119
152
|
};
|
|
120
|
-
//# sourceMappingURL=chunk-
|
|
153
|
+
//# sourceMappingURL=chunk-LSTKGFU7.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/tracking/tracking-manager.ts","../src/ui/font.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","/**\n * Helpers puros (sin DOM) para la fuente personalizable del popup/survey.\n * Espejo exacto en KMP: `ui/Font.kt`. Cualquier cambio aquí se replica allí.\n */\n\nconst FORMAT_BY_EXT: Record<string, string> = {\n woff2: 'woff2',\n woff: 'woff',\n ttf: 'truetype',\n otf: 'opentype',\n};\n\n/** Deriva el `format()` del `@font-face` a partir de la extensión de la URL. */\nexport function fontFormatFromUrl(url: string): string | undefined {\n const clean = url.split('?')[0].split('#')[0];\n const ext = clean.slice(clean.lastIndexOf('.') + 1).toLowerCase();\n return FORMAT_BY_EXT[ext];\n}\n\n// family/url provienen de la API y se interpolan en CSS y en el <style> del HTML del WebView.\n// Saneamos para evitar romper el string/rule (CSS injection) o cerrar </style> (script injection).\nfunction sanitizeFamily(family: string): string {\n return family.replace(/[^A-Za-z0-9 ._-]/g, '').trim();\n}\n\nfunction isSafeFontUrl(url: string): boolean {\n // Solo http(s)/data y sin caracteres que rompan url(\"...\") o el <style>:\n // comillas, <>, backslash, y cualquier whitespace o carácter de control.\n if (!/^(https?:|data:)/i.test(url)) return false;\n // eslint-disable-next-line no-control-regex\n return !/[\\x00-\\x20\"<>\\\\]/.test(url);\n}\n\n/** Valor a aplicar en `font-family`: nombre custom + fallback de sistema. */\nexport function buildFontFamilyValue(family: string): string {\n const safe = sanitizeFamily(family);\n return `\"${safe}\", -apple-system, system-ui, sans-serif`;\n}\n\n/** CSS del `@font-face`. Devuelve \"\" si no hay url (o no es segura). */\nexport function buildFontFaceCss(family: string, url?: string): string {\n if (!url || !isSafeFontUrl(url)) return '';\n const safe = sanitizeFamily(family);\n const fmt = fontFormatFromUrl(url);\n const src = fmt ? `url(\"${url}\") format(\"${fmt}\")` : `url(\"${url}\")`;\n return `@font-face{font-family:\"${safe}\";src:${src};font-display:swap;}`;\n}\n"],"mappings":"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","names":[]}
|
package/dist/index.d.mts
CHANGED
|
@@ -267,12 +267,21 @@ interface PopupActions {
|
|
|
267
267
|
start?: PopupActionStart;
|
|
268
268
|
back?: PopupActionDecline;
|
|
269
269
|
}
|
|
270
|
+
/** Fuente personalizable del popup + survey (viene de la API por popup) */
|
|
271
|
+
interface PopupFont {
|
|
272
|
+
/** Nombre de la familia. Ej: "Inter" (nombre limpio, NO un stack). */
|
|
273
|
+
family: string;
|
|
274
|
+
/** Opcional: URL a un archivo de fuente (woff2/ttf/otf) para armar un @font-face. */
|
|
275
|
+
url?: string;
|
|
276
|
+
}
|
|
270
277
|
/** Configurable popup styles */
|
|
271
278
|
interface PopupStyle {
|
|
272
279
|
theme: 'light' | 'dark';
|
|
273
280
|
position: 'bottom' | 'bottom-right' | 'bottom-left' | 'top' | 'top-right' | 'top-left' | 'center';
|
|
274
281
|
/** @deprecated Not used by the SDK; kept only for API compatibility */
|
|
275
282
|
imageUrl?: string | null;
|
|
283
|
+
/** Fuente personalizada. Si falta, se mantiene el comportamiento actual. */
|
|
284
|
+
font?: PopupFont;
|
|
276
285
|
}
|
|
277
286
|
/** Segments/targeting rules for showing the popup */
|
|
278
287
|
interface PopupSegments {
|
|
@@ -658,7 +667,7 @@ declare class ReactNativePopupRenderer implements PopupRenderer {
|
|
|
658
667
|
private partialEmitted;
|
|
659
668
|
constructor(options?: ReactNativeRendererOptions);
|
|
660
669
|
init(): void;
|
|
661
|
-
show(surveyId: string, productId: string, _actions: PopupActions | undefined, emit: EmitFn, onClose: () => void, env?: string, userId?: string,
|
|
670
|
+
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;
|
|
662
671
|
hide(): void;
|
|
663
672
|
/**
|
|
664
673
|
* El host la conecta al `onMessage` del WebView. Traduce los mensajes del survey a
|
|
@@ -684,6 +693,7 @@ interface BuildSurveyHtmlOptions {
|
|
|
684
693
|
profile?: IdentityAnswer[];
|
|
685
694
|
metadata?: IdentityAnswer[];
|
|
686
695
|
version?: string;
|
|
696
|
+
font?: PopupFont;
|
|
687
697
|
}
|
|
688
698
|
declare function buildSurveyHtml(opts: BuildSurveyHtmlOptions): string;
|
|
689
699
|
|
package/dist/index.d.ts
CHANGED
|
@@ -267,12 +267,21 @@ interface PopupActions {
|
|
|
267
267
|
start?: PopupActionStart;
|
|
268
268
|
back?: PopupActionDecline;
|
|
269
269
|
}
|
|
270
|
+
/** Fuente personalizable del popup + survey (viene de la API por popup) */
|
|
271
|
+
interface PopupFont {
|
|
272
|
+
/** Nombre de la familia. Ej: "Inter" (nombre limpio, NO un stack). */
|
|
273
|
+
family: string;
|
|
274
|
+
/** Opcional: URL a un archivo de fuente (woff2/ttf/otf) para armar un @font-face. */
|
|
275
|
+
url?: string;
|
|
276
|
+
}
|
|
270
277
|
/** Configurable popup styles */
|
|
271
278
|
interface PopupStyle {
|
|
272
279
|
theme: 'light' | 'dark';
|
|
273
280
|
position: 'bottom' | 'bottom-right' | 'bottom-left' | 'top' | 'top-right' | 'top-left' | 'center';
|
|
274
281
|
/** @deprecated Not used by the SDK; kept only for API compatibility */
|
|
275
282
|
imageUrl?: string | null;
|
|
283
|
+
/** Fuente personalizada. Si falta, se mantiene el comportamiento actual. */
|
|
284
|
+
font?: PopupFont;
|
|
276
285
|
}
|
|
277
286
|
/** Segments/targeting rules for showing the popup */
|
|
278
287
|
interface PopupSegments {
|
|
@@ -658,7 +667,7 @@ declare class ReactNativePopupRenderer implements PopupRenderer {
|
|
|
658
667
|
private partialEmitted;
|
|
659
668
|
constructor(options?: ReactNativeRendererOptions);
|
|
660
669
|
init(): void;
|
|
661
|
-
show(surveyId: string, productId: string, _actions: PopupActions | undefined, emit: EmitFn, onClose: () => void, env?: string, userId?: string,
|
|
670
|
+
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;
|
|
662
671
|
hide(): void;
|
|
663
672
|
/**
|
|
664
673
|
* El host la conecta al `onMessage` del WebView. Traduce los mensajes del survey a
|
|
@@ -684,6 +693,7 @@ interface BuildSurveyHtmlOptions {
|
|
|
684
693
|
profile?: IdentityAnswer[];
|
|
685
694
|
metadata?: IdentityAnswer[];
|
|
686
695
|
version?: string;
|
|
696
|
+
font?: PopupFont;
|
|
687
697
|
}
|
|
688
698
|
declare function buildSurveyHtml(opts: BuildSurveyHtmlOptions): string;
|
|
689
699
|
|
package/dist/index.js
CHANGED
|
@@ -144,6 +144,43 @@ var init_tracking_manager = __esm({
|
|
|
144
144
|
}
|
|
145
145
|
});
|
|
146
146
|
|
|
147
|
+
// src/ui/font.ts
|
|
148
|
+
function fontFormatFromUrl(url) {
|
|
149
|
+
const clean = url.split("?")[0].split("#")[0];
|
|
150
|
+
const ext = clean.slice(clean.lastIndexOf(".") + 1).toLowerCase();
|
|
151
|
+
return FORMAT_BY_EXT[ext];
|
|
152
|
+
}
|
|
153
|
+
function sanitizeFamily(family) {
|
|
154
|
+
return family.replace(/[^A-Za-z0-9 ._-]/g, "").trim();
|
|
155
|
+
}
|
|
156
|
+
function isSafeFontUrl(url) {
|
|
157
|
+
if (!/^(https?:|data:)/i.test(url)) return false;
|
|
158
|
+
return !/[\x00-\x20"<>\\]/.test(url);
|
|
159
|
+
}
|
|
160
|
+
function buildFontFamilyValue(family) {
|
|
161
|
+
const safe = sanitizeFamily(family);
|
|
162
|
+
return `"${safe}", -apple-system, system-ui, sans-serif`;
|
|
163
|
+
}
|
|
164
|
+
function buildFontFaceCss(family, url) {
|
|
165
|
+
if (!url || !isSafeFontUrl(url)) return "";
|
|
166
|
+
const safe = sanitizeFamily(family);
|
|
167
|
+
const fmt = fontFormatFromUrl(url);
|
|
168
|
+
const src = fmt ? `url("${url}") format("${fmt}")` : `url("${url}")`;
|
|
169
|
+
return `@font-face{font-family:"${safe}";src:${src};font-display:swap;}`;
|
|
170
|
+
}
|
|
171
|
+
var FORMAT_BY_EXT;
|
|
172
|
+
var init_font = __esm({
|
|
173
|
+
"src/ui/font.ts"() {
|
|
174
|
+
"use strict";
|
|
175
|
+
FORMAT_BY_EXT = {
|
|
176
|
+
woff2: "woff2",
|
|
177
|
+
woff: "woff",
|
|
178
|
+
ttf: "truetype",
|
|
179
|
+
otf: "opentype"
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
});
|
|
183
|
+
|
|
147
184
|
// node_modules/@magicfeedback/native/dist/magicfeedback-sdk.browser.js
|
|
148
185
|
var require_magicfeedback_sdk_browser = __commonJS({
|
|
149
186
|
"node_modules/@magicfeedback/native/dist/magicfeedback-sdk.browser.js"(exports, module2) {
|
|
@@ -2213,13 +2250,22 @@ var init_style = __esm({
|
|
|
2213
2250
|
padding: 0;
|
|
2214
2251
|
font-weight: 900;
|
|
2215
2252
|
color: #222;
|
|
2216
|
-
font-family: 'Montserrat';
|
|
2253
|
+
font-family: var(--deepdots-font, 'Montserrat');
|
|
2217
2254
|
font-size: 18px;
|
|
2218
2255
|
text-transform: uppercase;
|
|
2219
2256
|
text-align: center;
|
|
2220
2257
|
margin-bottom: 40px;
|
|
2221
2258
|
}
|
|
2222
2259
|
|
|
2260
|
+
/* Los controles de formulario no heredan font-family por defecto: cuando hay
|
|
2261
|
+
fuente custom (clase deepdots-has-font), los forzamos a usar la del contenedor. */
|
|
2262
|
+
.deepdots-popup.deepdots-has-font button,
|
|
2263
|
+
.deepdots-popup.deepdots-has-font input,
|
|
2264
|
+
.deepdots-popup.deepdots-has-font textarea,
|
|
2265
|
+
.deepdots-popup.deepdots-has-font select {
|
|
2266
|
+
font-family: var(--deepdots-font, inherit);
|
|
2267
|
+
}
|
|
2268
|
+
|
|
2223
2269
|
.deepdots-popup h6 {
|
|
2224
2270
|
margin-bottom: 1rem;
|
|
2225
2271
|
}
|
|
@@ -2992,6 +3038,18 @@ function ensureResponsiveStyles(_popup) {
|
|
|
2992
3038
|
`;
|
|
2993
3039
|
document.head.appendChild(style);
|
|
2994
3040
|
}
|
|
3041
|
+
function ensureFontFace(family, url) {
|
|
3042
|
+
const css = buildFontFaceCss(family, url);
|
|
3043
|
+
if (!css) return;
|
|
3044
|
+
const STYLE_ID = "deepdots-font-face";
|
|
3045
|
+
let el = document.getElementById(STYLE_ID);
|
|
3046
|
+
if (!el) {
|
|
3047
|
+
el = document.createElement("style");
|
|
3048
|
+
el.id = STYLE_ID;
|
|
3049
|
+
document.head.appendChild(el);
|
|
3050
|
+
}
|
|
3051
|
+
if (el.textContent !== css) el.textContent = css;
|
|
3052
|
+
}
|
|
2995
3053
|
async function renderPopup(container, surveyId, productId, actions, emit, onClose, env = "production", userId, style, sessionId, miniService, analyticsFeedbackSessionId) {
|
|
2996
3054
|
let surveyCompletedEmitted = false;
|
|
2997
3055
|
let stylesInjected = false;
|
|
@@ -3027,6 +3085,14 @@ async function renderPopup(container, surveyId, productId, actions, emit, onClos
|
|
|
3027
3085
|
width: 90%;
|
|
3028
3086
|
min-height: 200px;
|
|
3029
3087
|
`;
|
|
3088
|
+
const font = style?.font;
|
|
3089
|
+
if (font?.family) {
|
|
3090
|
+
const familyValue = buildFontFamilyValue(font.family);
|
|
3091
|
+
popup.style.fontFamily = familyValue;
|
|
3092
|
+
popup.style.setProperty("--deepdots-font", familyValue);
|
|
3093
|
+
popup.classList.add("deepdots-has-font");
|
|
3094
|
+
if (font.url) ensureFontFace(font.family, font.url);
|
|
3095
|
+
}
|
|
3030
3096
|
const header = document.createElement("div");
|
|
3031
3097
|
header.className = "deepdots-popup-header";
|
|
3032
3098
|
header.style.cssText = "display:flex; justify-content:flex-end; align-items:center; width:100%;";
|
|
@@ -3448,6 +3514,7 @@ var init_renderPopup = __esm({
|
|
|
3448
3514
|
"src/ui/renderPopup.ts"() {
|
|
3449
3515
|
"use strict";
|
|
3450
3516
|
init_tracking_manager();
|
|
3517
|
+
init_font();
|
|
3451
3518
|
import_native = __toESM(require_magicfeedback_sdk_browser());
|
|
3452
3519
|
init_style();
|
|
3453
3520
|
}
|
|
@@ -3664,6 +3731,7 @@ function resolveEnvironment(nodeEnv) {
|
|
|
3664
3731
|
init_tracking_manager();
|
|
3665
3732
|
|
|
3666
3733
|
// src/ui/surveyHtml.ts
|
|
3734
|
+
init_font();
|
|
3667
3735
|
function buildSurveyHtml(opts) {
|
|
3668
3736
|
const env = opts.env === "development" ? "dev" : "prod";
|
|
3669
3737
|
const version = opts.version ?? "2.2.4";
|
|
@@ -3672,10 +3740,12 @@ function buildSurveyHtml(opts) {
|
|
|
3672
3740
|
const pid = JSON.stringify(opts.productId);
|
|
3673
3741
|
const profileJson = JSON.stringify(opts.profile ?? []);
|
|
3674
3742
|
const metaJson = JSON.stringify(opts.metadata ?? []);
|
|
3743
|
+
const fontFaceCss = opts.font ? buildFontFaceCss(opts.font.family, opts.font.url) : "";
|
|
3744
|
+
const fontFamilyValue = opts.font ? buildFontFamilyValue(opts.font.family) : "-apple-system,system-ui,sans-serif";
|
|
3675
3745
|
return `<!DOCTYPE html><html><head>
|
|
3676
3746
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
|
3677
3747
|
<meta name="color-scheme" content="light"/>
|
|
3678
|
-
<style
|
|
3748
|
+
<style>${fontFaceCss}html,body{margin:0;padding:0;background:transparent;font-family:${fontFamilyValue}}#mf{width:100%;box-sizing:border-box}#mf *{max-width:100%;box-sizing:border-box}</style>
|
|
3679
3749
|
</head><body>
|
|
3680
3750
|
<div id="mf"></div>
|
|
3681
3751
|
<script>
|
|
@@ -3726,13 +3796,13 @@ var ReactNativePopupRenderer = class {
|
|
|
3726
3796
|
}
|
|
3727
3797
|
init() {
|
|
3728
3798
|
}
|
|
3729
|
-
show(surveyId, productId, _actions, emit, onClose, env = "production", userId,
|
|
3799
|
+
show(surveyId, productId, _actions, emit, onClose, env = "production", userId, style, sessionId, miniService, analyticsFeedbackSessionId) {
|
|
3730
3800
|
this.emitFn = emit;
|
|
3731
3801
|
this.onCloseFn = onClose;
|
|
3732
3802
|
this.currentSurveyId = surveyId;
|
|
3733
3803
|
this.partialEmitted = false;
|
|
3734
3804
|
const { profile, metadata } = buildSurveyIdentity(userId ?? null, sessionId ?? null, miniService ?? null, analyticsFeedbackSessionId ?? null);
|
|
3735
|
-
const html = buildSurveyHtml({ surveyId, productId, env, profile, metadata });
|
|
3805
|
+
const html = buildSurveyHtml({ surveyId, productId, env, profile, metadata, font: style?.font });
|
|
3736
3806
|
if (this.options.onShow) {
|
|
3737
3807
|
this.options.onShow({ surveyId, productId, html });
|
|
3738
3808
|
} else {
|