@magicfeedback/popup-sdk 1.1.6 → 1.1.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +16 -1
- package/dist/index.d.ts +16 -1
- package/dist/index.js +123 -14
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +121 -12
- package/dist/index.mjs.map +1 -1
- package/dist/react-native.d.mts +15 -1
- package/dist/react-native.d.ts +15 -1
- package/dist/react-native.js +123 -14
- package/dist/react-native.js.map +1 -1
- package/dist/react-native.mjs +121 -12
- package/dist/react-native.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.d.mts
CHANGED
|
@@ -86,6 +86,8 @@ interface AnalyticsEnvelope {
|
|
|
86
86
|
device?: DeviceInfo;
|
|
87
87
|
/** User attributes del cliente, usados para breakdowns (registration_status, pass_type, …). */
|
|
88
88
|
attributes: Record<string, string>;
|
|
89
|
+
/** Métricas del host: valores medibles persistentes → van en `feedback.metrics` del body. */
|
|
90
|
+
metrics?: Record<string, string>;
|
|
89
91
|
};
|
|
90
92
|
events: AnalyticsEvent[];
|
|
91
93
|
}
|
|
@@ -103,7 +105,8 @@ type AnalyticsSink = (payload: AnalyticsEnvelope) => void;
|
|
|
103
105
|
* - cada entrada usa `value: string[]` (array de un elemento)
|
|
104
106
|
* - eventos: {key: nombre_evento, value: [JSON(timestamp + params)]}
|
|
105
107
|
* - identidad (user_id) → `profile` como `external-user-id`
|
|
106
|
-
* -
|
|
108
|
+
* - métricas del host (setMetric) → `feedback.metrics` (mismo shape que metadata, sin prefijo)
|
|
109
|
+
* - `answers` vacío
|
|
107
110
|
* - `text` vacío
|
|
108
111
|
*/
|
|
109
112
|
|
|
@@ -119,6 +122,7 @@ interface AnalyticsFeedbackBody {
|
|
|
119
122
|
feedback: {
|
|
120
123
|
text: string;
|
|
121
124
|
answers: FeedbackKV[];
|
|
125
|
+
metrics: FeedbackKV[];
|
|
122
126
|
metadata: FeedbackKV[];
|
|
123
127
|
profile: FeedbackKV[];
|
|
124
128
|
finished: boolean;
|
|
@@ -185,6 +189,12 @@ interface DeepdotsInitParams {
|
|
|
185
189
|
storage?: KeyValueStorage;
|
|
186
190
|
/** Platform used in the analytics envelope. Default is 'web'; in RN pass 'android'/'ios'. */
|
|
187
191
|
platform?: 'web' | 'android' | 'ios';
|
|
192
|
+
/**
|
|
193
|
+
* BCP-47 language tag for the analytics context (e.g. 'es-ES'). If omitted, the SDK
|
|
194
|
+
* auto-detects: `navigator.language` on web, or the `Intl` locale as a fallback (works in
|
|
195
|
+
* React Native with Hermes, where `navigator.language` is absent).
|
|
196
|
+
*/
|
|
197
|
+
language?: string;
|
|
188
198
|
/** Injectable device info (RN: from react-native-device-info). If missing, it is derived from the browser. */
|
|
189
199
|
device?: DeviceInfo;
|
|
190
200
|
/**
|
|
@@ -529,6 +539,11 @@ declare class DeepdotsPopups {
|
|
|
529
539
|
track(name: string, params?: Record<string, unknown>): void;
|
|
530
540
|
/** User attributes para breakdowns (registration_status, pass_type, sector, pass_status…). */
|
|
531
541
|
setUserAttributes(attributes: Record<string, string | number | boolean>): void;
|
|
542
|
+
/**
|
|
543
|
+
* Registra/actualiza una métrica (valor medible) → `feedback.metrics` del body.
|
|
544
|
+
* Persistente y sobrescribe por key. No-op si tracking off.
|
|
545
|
+
*/
|
|
546
|
+
setMetric(key: string, value: string | number | boolean): void;
|
|
532
547
|
/** Reporta un error del host (manejado o no) → evento `deepdots_app_crash`. No-op si tracking off. */
|
|
533
548
|
reportError(error: unknown, options?: ReportErrorOptions): void;
|
|
534
549
|
/** Engancha `global.ErrorUtils` de RN para capturar errores JS no manejados. No-op si tracking off. */
|
package/dist/index.d.ts
CHANGED
|
@@ -86,6 +86,8 @@ interface AnalyticsEnvelope {
|
|
|
86
86
|
device?: DeviceInfo;
|
|
87
87
|
/** User attributes del cliente, usados para breakdowns (registration_status, pass_type, …). */
|
|
88
88
|
attributes: Record<string, string>;
|
|
89
|
+
/** Métricas del host: valores medibles persistentes → van en `feedback.metrics` del body. */
|
|
90
|
+
metrics?: Record<string, string>;
|
|
89
91
|
};
|
|
90
92
|
events: AnalyticsEvent[];
|
|
91
93
|
}
|
|
@@ -103,7 +105,8 @@ type AnalyticsSink = (payload: AnalyticsEnvelope) => void;
|
|
|
103
105
|
* - cada entrada usa `value: string[]` (array de un elemento)
|
|
104
106
|
* - eventos: {key: nombre_evento, value: [JSON(timestamp + params)]}
|
|
105
107
|
* - identidad (user_id) → `profile` como `external-user-id`
|
|
106
|
-
* -
|
|
108
|
+
* - métricas del host (setMetric) → `feedback.metrics` (mismo shape que metadata, sin prefijo)
|
|
109
|
+
* - `answers` vacío
|
|
107
110
|
* - `text` vacío
|
|
108
111
|
*/
|
|
109
112
|
|
|
@@ -119,6 +122,7 @@ interface AnalyticsFeedbackBody {
|
|
|
119
122
|
feedback: {
|
|
120
123
|
text: string;
|
|
121
124
|
answers: FeedbackKV[];
|
|
125
|
+
metrics: FeedbackKV[];
|
|
122
126
|
metadata: FeedbackKV[];
|
|
123
127
|
profile: FeedbackKV[];
|
|
124
128
|
finished: boolean;
|
|
@@ -185,6 +189,12 @@ interface DeepdotsInitParams {
|
|
|
185
189
|
storage?: KeyValueStorage;
|
|
186
190
|
/** Platform used in the analytics envelope. Default is 'web'; in RN pass 'android'/'ios'. */
|
|
187
191
|
platform?: 'web' | 'android' | 'ios';
|
|
192
|
+
/**
|
|
193
|
+
* BCP-47 language tag for the analytics context (e.g. 'es-ES'). If omitted, the SDK
|
|
194
|
+
* auto-detects: `navigator.language` on web, or the `Intl` locale as a fallback (works in
|
|
195
|
+
* React Native with Hermes, where `navigator.language` is absent).
|
|
196
|
+
*/
|
|
197
|
+
language?: string;
|
|
188
198
|
/** Injectable device info (RN: from react-native-device-info). If missing, it is derived from the browser. */
|
|
189
199
|
device?: DeviceInfo;
|
|
190
200
|
/**
|
|
@@ -529,6 +539,11 @@ declare class DeepdotsPopups {
|
|
|
529
539
|
track(name: string, params?: Record<string, unknown>): void;
|
|
530
540
|
/** User attributes para breakdowns (registration_status, pass_type, sector, pass_status…). */
|
|
531
541
|
setUserAttributes(attributes: Record<string, string | number | boolean>): void;
|
|
542
|
+
/**
|
|
543
|
+
* Registra/actualiza una métrica (valor medible) → `feedback.metrics` del body.
|
|
544
|
+
* Persistente y sobrescribe por key. No-op si tracking off.
|
|
545
|
+
*/
|
|
546
|
+
setMetric(key: string, value: string | number | boolean): void;
|
|
532
547
|
/** Reporta un error del host (manejado o no) → evento `deepdots_app_crash`. No-op si tracking off. */
|
|
533
548
|
reportError(error: unknown, options?: ReportErrorOptions): void;
|
|
534
549
|
/** Engancha `global.ErrorUtils` de RN para capturar errores JS no manejados. No-op si tracking off. */
|
package/dist/index.js
CHANGED
|
@@ -146,8 +146,8 @@ var init_tracking_manager = __esm({
|
|
|
146
146
|
|
|
147
147
|
// src/ui/font.ts
|
|
148
148
|
function fontFormatFromUrl(url) {
|
|
149
|
-
const
|
|
150
|
-
const ext =
|
|
149
|
+
const clean2 = url.split("?")[0].split("#")[0];
|
|
150
|
+
const ext = clean2.slice(clean2.lastIndexOf(".") + 1).toLowerCase();
|
|
151
151
|
return FORMAT_BY_EXT[ext];
|
|
152
152
|
}
|
|
153
153
|
function sanitizeFamily(family) {
|
|
@@ -3957,6 +3957,8 @@ var AnalyticsManager = class {
|
|
|
3957
3957
|
constructor(options = {}) {
|
|
3958
3958
|
this.events = [];
|
|
3959
3959
|
this.attributes = {};
|
|
3960
|
+
/** Métricas del host (valores medibles persistentes). Sobrescriben por key; se reenvían en cada flush. */
|
|
3961
|
+
this.metrics = {};
|
|
3960
3962
|
/** Mini-services activos: nombre → timestamp de entrada. El orden de inserción marca el "más reciente". */
|
|
3961
3963
|
this.activeMiniServices = /* @__PURE__ */ new Map();
|
|
3962
3964
|
this.sink = options.sink ?? dryRunSink;
|
|
@@ -3979,6 +3981,14 @@ var AnalyticsManager = class {
|
|
|
3979
3981
|
this.attributes[k] = String(v);
|
|
3980
3982
|
}
|
|
3981
3983
|
}
|
|
3984
|
+
/**
|
|
3985
|
+
* Registra/actualiza una métrica (valor medible). Persistente: se reenvía en cada flush.
|
|
3986
|
+
* Repetir la misma key SOBRESCRIBE; el valor se coerciona a string; ignora key vacía.
|
|
3987
|
+
*/
|
|
3988
|
+
setMetric(key, value) {
|
|
3989
|
+
if (!key) return;
|
|
3990
|
+
this.metrics[key] = String(value);
|
|
3991
|
+
}
|
|
3982
3992
|
/**
|
|
3983
3993
|
* Marca el inicio de un mini-service; etiqueta los eventos siguientes con `mini_service`.
|
|
3984
3994
|
* Admite varios activos a la vez (concurrencia); el "actual" para etiquetar es el más reciente.
|
|
@@ -4042,7 +4052,8 @@ var AnalyticsManager = class {
|
|
|
4042
4052
|
platform: this.platform,
|
|
4043
4053
|
language: this.language,
|
|
4044
4054
|
device: this.device,
|
|
4045
|
-
attributes: { ...this.attributes }
|
|
4055
|
+
attributes: { ...this.attributes },
|
|
4056
|
+
metrics: { ...this.metrics }
|
|
4046
4057
|
},
|
|
4047
4058
|
events: [...this.events]
|
|
4048
4059
|
};
|
|
@@ -4098,10 +4109,15 @@ function buildAnalyticsFeedbackBody(envelope, keys, feedbackSessionId) {
|
|
|
4098
4109
|
value: [JSON.stringify({ timestamp: e.timestamp, ...e.params ?? {} })]
|
|
4099
4110
|
});
|
|
4100
4111
|
}
|
|
4112
|
+
const metrics = [];
|
|
4113
|
+
for (const [k, v] of Object.entries(context.metrics ?? {})) {
|
|
4114
|
+
pushKV(metrics, k, v);
|
|
4115
|
+
}
|
|
4101
4116
|
return {
|
|
4102
4117
|
feedback: {
|
|
4103
4118
|
text: "",
|
|
4104
4119
|
answers: [],
|
|
4120
|
+
metrics,
|
|
4105
4121
|
metadata,
|
|
4106
4122
|
profile,
|
|
4107
4123
|
finished: false
|
|
@@ -4282,20 +4298,95 @@ function collectDeviceInfo(appVersion) {
|
|
|
4282
4298
|
}
|
|
4283
4299
|
|
|
4284
4300
|
// src/analytics/geo-info.ts
|
|
4285
|
-
|
|
4286
|
-
|
|
4287
|
-
|
|
4301
|
+
var GEO_STORAGE_KEY = "deepdots.geo";
|
|
4302
|
+
var GEO_TTL_MS = 30 * 24 * 60 * 60 * 1e3;
|
|
4303
|
+
var GEO_TIMEOUT_MS = 3e3;
|
|
4304
|
+
function readCachedGeo(storage, now) {
|
|
4305
|
+
try {
|
|
4306
|
+
const raw = storage.getItem(GEO_STORAGE_KEY);
|
|
4307
|
+
if (!raw) return null;
|
|
4308
|
+
const cached = JSON.parse(raw);
|
|
4309
|
+
if (typeof cached.ts !== "number" || now - cached.ts > GEO_TTL_MS) return null;
|
|
4310
|
+
const geo = {};
|
|
4311
|
+
if (cached.country) geo.country = cached.country;
|
|
4312
|
+
if (cached.city) geo.city = cached.city;
|
|
4313
|
+
return geo.country || geo.city ? geo : null;
|
|
4314
|
+
} catch {
|
|
4315
|
+
return null;
|
|
4316
|
+
}
|
|
4317
|
+
}
|
|
4318
|
+
function writeCachedGeo(storage, geo, now) {
|
|
4319
|
+
try {
|
|
4320
|
+
const payload = { ...geo, ts: now };
|
|
4321
|
+
storage.setItem(GEO_STORAGE_KEY, JSON.stringify(payload));
|
|
4322
|
+
} catch {
|
|
4323
|
+
}
|
|
4324
|
+
}
|
|
4325
|
+
var DEFAULT_GEO_PROVIDERS = [
|
|
4326
|
+
{
|
|
4327
|
+
// https://ipapi.co/json/
|
|
4328
|
+
url: "https://ipapi.co/json/",
|
|
4329
|
+
parse: (d) => geoFrom(d.country_code, d.city)
|
|
4330
|
+
},
|
|
4331
|
+
{
|
|
4332
|
+
// https://ipwho.is/ (devuelve success:false con 200 en errores)
|
|
4333
|
+
url: "https://ipwho.is/",
|
|
4334
|
+
parse: (d) => d.success === false ? null : geoFrom(d.country_code, d.city)
|
|
4335
|
+
},
|
|
4336
|
+
{
|
|
4337
|
+
// https://ipinfo.io/json (country = código ISO)
|
|
4338
|
+
url: "https://ipinfo.io/json",
|
|
4339
|
+
parse: (d) => geoFrom(d.country, d.city)
|
|
4340
|
+
}
|
|
4341
|
+
];
|
|
4342
|
+
function geoFrom(country, city) {
|
|
4343
|
+
const c = typeof country === "string" && country ? country : void 0;
|
|
4344
|
+
const t = typeof city === "string" && city ? city : void 0;
|
|
4345
|
+
if (!c && !t) return null;
|
|
4346
|
+
return { country: c, city: t };
|
|
4347
|
+
}
|
|
4348
|
+
async function fetchOne(provider, f, timeoutMs) {
|
|
4349
|
+
const controller = new AbortController();
|
|
4350
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
4288
4351
|
try {
|
|
4289
|
-
const res = await f(
|
|
4352
|
+
const res = await f(provider.url, { signal: controller.signal });
|
|
4290
4353
|
if (!res.ok) return null;
|
|
4291
4354
|
const data = await res.json();
|
|
4292
|
-
|
|
4293
|
-
const city = data.city || void 0;
|
|
4294
|
-
if (!country && !city) return null;
|
|
4295
|
-
return { country, city };
|
|
4355
|
+
return provider.parse(data);
|
|
4296
4356
|
} catch {
|
|
4297
4357
|
return null;
|
|
4358
|
+
} finally {
|
|
4359
|
+
clearTimeout(timer);
|
|
4360
|
+
}
|
|
4361
|
+
}
|
|
4362
|
+
async function fetchGeo(options = {}) {
|
|
4363
|
+
const f = options.fetchImpl ?? (typeof fetch !== "undefined" ? fetch : void 0);
|
|
4364
|
+
if (!f) return null;
|
|
4365
|
+
const providers = options.providers ?? DEFAULT_GEO_PROVIDERS;
|
|
4366
|
+
const timeoutMs = options.timeoutMs ?? GEO_TIMEOUT_MS;
|
|
4367
|
+
for (const provider of providers) {
|
|
4368
|
+
const geo = await fetchOne(provider, f, timeoutMs);
|
|
4369
|
+
if (geo) return geo;
|
|
4370
|
+
}
|
|
4371
|
+
return null;
|
|
4372
|
+
}
|
|
4373
|
+
|
|
4374
|
+
// src/analytics/language.ts
|
|
4375
|
+
function clean(value) {
|
|
4376
|
+
const trimmed = value?.trim();
|
|
4377
|
+
return trimmed ? trimmed : void 0;
|
|
4378
|
+
}
|
|
4379
|
+
function resolveLanguage(sources) {
|
|
4380
|
+
const explicit = clean(sources.explicit);
|
|
4381
|
+
if (explicit) return explicit;
|
|
4382
|
+
const navLang = clean(sources.navigator?.language);
|
|
4383
|
+
if (navLang) return navLang;
|
|
4384
|
+
try {
|
|
4385
|
+
const locale = clean(sources.intl?.DateTimeFormat().resolvedOptions().locale);
|
|
4386
|
+
if (locale) return locale;
|
|
4387
|
+
} catch {
|
|
4298
4388
|
}
|
|
4389
|
+
return void 0;
|
|
4299
4390
|
}
|
|
4300
4391
|
|
|
4301
4392
|
// src/analytics/engagement-tracker.ts
|
|
@@ -4606,7 +4697,11 @@ var DeepdotsPopups = class {
|
|
|
4606
4697
|
this.analytics = new AnalyticsManager({
|
|
4607
4698
|
sink: analyticsSink,
|
|
4608
4699
|
publicKey: config.analytics?.publicKey ?? this.config.apiKey,
|
|
4609
|
-
language:
|
|
4700
|
+
language: resolveLanguage({
|
|
4701
|
+
explicit: config.language,
|
|
4702
|
+
navigator: typeof navigator !== "undefined" ? navigator : void 0,
|
|
4703
|
+
intl: typeof Intl !== "undefined" ? Intl : void 0
|
|
4704
|
+
}),
|
|
4610
4705
|
platform: config.platform ?? "web",
|
|
4611
4706
|
device,
|
|
4612
4707
|
maxBatchSize: ANALYTICS_MAX_BATCH_SIZE,
|
|
@@ -4623,8 +4718,13 @@ var DeepdotsPopups = class {
|
|
|
4623
4718
|
if (this.tracking?.isTrackingEnabled()) {
|
|
4624
4719
|
this.crashReporter.install();
|
|
4625
4720
|
}
|
|
4626
|
-
|
|
4627
|
-
|
|
4721
|
+
const cachedGeo = readCachedGeo(storage, Date.now());
|
|
4722
|
+
if (cachedGeo) this.analytics.updateDevice(cachedGeo);
|
|
4723
|
+
fetchGeo().then((geo) => {
|
|
4724
|
+
if (geo) {
|
|
4725
|
+
this.analytics?.updateDevice(geo);
|
|
4726
|
+
writeCachedGeo(storage, geo, Date.now());
|
|
4727
|
+
}
|
|
4628
4728
|
}).catch(() => {
|
|
4629
4729
|
});
|
|
4630
4730
|
this.navObserver = new NavigationObserver();
|
|
@@ -4681,6 +4781,15 @@ var DeepdotsPopups = class {
|
|
|
4681
4781
|
if (!this.tracking?.isTrackingEnabled()) return;
|
|
4682
4782
|
this.analytics?.setUserAttributes(attributes);
|
|
4683
4783
|
}
|
|
4784
|
+
/**
|
|
4785
|
+
* Registra/actualiza una métrica (valor medible) → `feedback.metrics` del body.
|
|
4786
|
+
* Persistente y sobrescribe por key. No-op si tracking off.
|
|
4787
|
+
*/
|
|
4788
|
+
setMetric(key, value) {
|
|
4789
|
+
if (!this.tracking?.isTrackingEnabled()) return;
|
|
4790
|
+
this.analytics?.setMetric(key, value);
|
|
4791
|
+
this.log("analytics \xB7 setMetric:", key, value);
|
|
4792
|
+
}
|
|
4684
4793
|
/** Reporta un error del host (manejado o no) → evento `deepdots_app_crash`. No-op si tracking off. */
|
|
4685
4794
|
reportError(error, options) {
|
|
4686
4795
|
if (!this.tracking?.isTrackingEnabled()) return;
|