@magicfeedback/popup-sdk 1.1.6 → 1.1.8
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/README.md +5 -0
- package/dist/index.d.mts +63 -8
- package/dist/index.d.ts +63 -8
- package/dist/index.js +244 -43
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +242 -41
- package/dist/index.mjs.map +1 -1
- package/dist/react-native.d.mts +30 -3
- package/dist/react-native.d.ts +30 -3
- package/dist/react-native.js +244 -43
- package/dist/react-native.js.map +1 -1
- package/dist/react-native.mjs +242 -41
- package/dist/react-native.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -394,6 +394,8 @@ var AnalyticsManager = class {
|
|
|
394
394
|
constructor(options = {}) {
|
|
395
395
|
this.events = [];
|
|
396
396
|
this.attributes = {};
|
|
397
|
+
/** Métricas del host (valores medibles persistentes). Sobrescriben por key; se reenvían en cada flush. */
|
|
398
|
+
this.metrics = {};
|
|
397
399
|
/** Mini-services activos: nombre → timestamp de entrada. El orden de inserción marca el "más reciente". */
|
|
398
400
|
this.activeMiniServices = /* @__PURE__ */ new Map();
|
|
399
401
|
this.sink = options.sink ?? dryRunSink;
|
|
@@ -403,6 +405,7 @@ var AnalyticsManager = class {
|
|
|
403
405
|
this.device = options.device;
|
|
404
406
|
this.platform = options.platform ?? "web";
|
|
405
407
|
this.maxBatchSize = options.maxBatchSize ?? 20;
|
|
408
|
+
this.maxBufferedEvents = options.maxBufferedEvents ?? 200;
|
|
406
409
|
this.onFlushNeeded = options.onFlushNeeded;
|
|
407
410
|
}
|
|
408
411
|
/** Actualiza campos de device en runtime (p.ej. country/city tras resolver geo). */
|
|
@@ -416,6 +419,14 @@ var AnalyticsManager = class {
|
|
|
416
419
|
this.attributes[k] = String(v);
|
|
417
420
|
}
|
|
418
421
|
}
|
|
422
|
+
/**
|
|
423
|
+
* Registra/actualiza una métrica (valor medible). Persistente: se reenvía en cada flush.
|
|
424
|
+
* Repetir la misma key SOBRESCRIBE; el valor se coerciona a string; ignora key vacía.
|
|
425
|
+
*/
|
|
426
|
+
setMetric(key, value) {
|
|
427
|
+
if (!key) return;
|
|
428
|
+
this.metrics[key] = String(value);
|
|
429
|
+
}
|
|
419
430
|
/**
|
|
420
431
|
* Marca el inicio de un mini-service; etiqueta los eventos siguientes con `mini_service`.
|
|
421
432
|
* Admite varios activos a la vez (concurrencia); el "actual" para etiquetar es el más reciente.
|
|
@@ -455,6 +466,7 @@ var AnalyticsManager = class {
|
|
|
455
466
|
timestamp: this.now(),
|
|
456
467
|
params: Object.keys(merged).length ? merged : void 0
|
|
457
468
|
});
|
|
469
|
+
this.enforceBufferCap();
|
|
458
470
|
if (this.events.length >= this.maxBatchSize) {
|
|
459
471
|
this.onFlushNeeded?.();
|
|
460
472
|
}
|
|
@@ -479,19 +491,49 @@ var AnalyticsManager = class {
|
|
|
479
491
|
platform: this.platform,
|
|
480
492
|
language: this.language,
|
|
481
493
|
device: this.device,
|
|
482
|
-
attributes: { ...this.attributes }
|
|
494
|
+
attributes: { ...this.attributes },
|
|
495
|
+
metrics: { ...this.metrics }
|
|
483
496
|
},
|
|
484
497
|
events: [...this.events]
|
|
485
498
|
};
|
|
486
499
|
}
|
|
487
|
-
/**
|
|
488
|
-
|
|
500
|
+
/**
|
|
501
|
+
* Envía (vía sink) el lote acumulado y vacía el buffer. No-op si no hay eventos.
|
|
502
|
+
*
|
|
503
|
+
* Si el sink falla de forma transitoria (promesa rechazada o excepción síncrona) el lote
|
|
504
|
+
* se **re-encola** al principio del buffer y se reintenta en el siguiente flush: un error
|
|
505
|
+
* de red o un 5xx no debe hacer desaparecer los eventos.
|
|
506
|
+
*/
|
|
507
|
+
flush(identity, meta) {
|
|
489
508
|
if (this.events.length === 0) return null;
|
|
509
|
+
const batch = this.events;
|
|
490
510
|
const payload = this.buildPayload(identity);
|
|
491
|
-
this.sink(payload);
|
|
492
511
|
this.events = [];
|
|
512
|
+
let result;
|
|
513
|
+
try {
|
|
514
|
+
result = meta ? this.sink(payload, meta) : this.sink(payload);
|
|
515
|
+
} catch {
|
|
516
|
+
this.requeue(batch);
|
|
517
|
+
return payload;
|
|
518
|
+
}
|
|
519
|
+
if (result && typeof result.then === "function") {
|
|
520
|
+
result.catch(() => this.requeue(batch));
|
|
521
|
+
}
|
|
493
522
|
return payload;
|
|
494
523
|
}
|
|
524
|
+
/**
|
|
525
|
+
* Devuelve un lote fallido al buffer, delante de lo que haya llegado mientras se enviaba
|
|
526
|
+
* (orden cronológico). Con el buffer lleno se descartan los eventos más antiguos.
|
|
527
|
+
*/
|
|
528
|
+
requeue(batch) {
|
|
529
|
+
this.events = [...batch, ...this.events];
|
|
530
|
+
this.enforceBufferCap();
|
|
531
|
+
}
|
|
532
|
+
/** Techo del buffer: si el envío lleva rato fallando, se sacrifica lo más antiguo. */
|
|
533
|
+
enforceBufferCap() {
|
|
534
|
+
const overflow = this.events.length - this.maxBufferedEvents;
|
|
535
|
+
if (overflow > 0) this.events.splice(0, overflow);
|
|
536
|
+
}
|
|
495
537
|
};
|
|
496
538
|
|
|
497
539
|
// src/analytics/feedback-payload.ts
|
|
@@ -535,10 +577,15 @@ function buildAnalyticsFeedbackBody(envelope, keys, feedbackSessionId) {
|
|
|
535
577
|
value: [JSON.stringify({ timestamp: e.timestamp, ...e.params ?? {} })]
|
|
536
578
|
});
|
|
537
579
|
}
|
|
580
|
+
const metrics = [];
|
|
581
|
+
for (const [k, v] of Object.entries(context.metrics ?? {})) {
|
|
582
|
+
pushKV(metrics, k, v);
|
|
583
|
+
}
|
|
538
584
|
return {
|
|
539
585
|
feedback: {
|
|
540
586
|
text: "",
|
|
541
587
|
answers: [],
|
|
588
|
+
metrics,
|
|
542
589
|
metadata,
|
|
543
590
|
profile,
|
|
544
591
|
finished: false
|
|
@@ -549,33 +596,80 @@ function buildAnalyticsFeedbackBody(envelope, keys, feedbackSessionId) {
|
|
|
549
596
|
...feedbackSessionId ? { sessionId: feedbackSessionId } : {}
|
|
550
597
|
};
|
|
551
598
|
}
|
|
599
|
+
var KEEPALIVE_MAX_BYTES = 6e4;
|
|
600
|
+
function isRetryableStatus(status) {
|
|
601
|
+
return status >= 500 || status === 408 || status === 429;
|
|
602
|
+
}
|
|
603
|
+
async function safeBodyText(res) {
|
|
604
|
+
try {
|
|
605
|
+
return (await res.text()).slice(0, 500);
|
|
606
|
+
} catch {
|
|
607
|
+
return "";
|
|
608
|
+
}
|
|
609
|
+
}
|
|
552
610
|
function createFeedbackSink(options) {
|
|
553
611
|
let feedbackSessionId;
|
|
554
|
-
|
|
555
|
-
|
|
612
|
+
let firstPostInFlight = null;
|
|
613
|
+
const url = `${options.baseUrl}/sdk/feedback`;
|
|
614
|
+
const post = async (body, final) => {
|
|
615
|
+
const json = JSON.stringify(body);
|
|
616
|
+
const small = json.length <= KEEPALIVE_MAX_BYTES;
|
|
617
|
+
if (final && options.sendBeaconImpl && small) {
|
|
618
|
+
const payload = typeof Blob !== "undefined" ? new Blob([json], { type: "application/json" }) : json;
|
|
619
|
+
if (options.sendBeaconImpl(url, payload)) {
|
|
620
|
+
options.log?.("[DeepdotsAnalytics] flush final v\xEDa sendBeacon");
|
|
621
|
+
return;
|
|
622
|
+
}
|
|
623
|
+
options.log?.("[DeepdotsAnalytics] sendBeacon rechaz\xF3 el lote; fallback a fetch");
|
|
624
|
+
}
|
|
556
625
|
const f = options.fetchImpl ?? (typeof fetch !== "undefined" ? fetch : void 0);
|
|
557
626
|
if (!f) {
|
|
558
627
|
options.log?.("[DeepdotsAnalytics] no fetch disponible; payload no enviado", body);
|
|
559
628
|
return;
|
|
560
629
|
}
|
|
561
|
-
|
|
562
|
-
f(`${options.baseUrl}/sdk/feedback`, {
|
|
630
|
+
const res = await f(url, {
|
|
563
631
|
method: "POST",
|
|
564
632
|
headers: { "Content-Type": "application/json" },
|
|
565
|
-
body:
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
options.onSessionId?.(feedbackSessionId);
|
|
574
|
-
}
|
|
575
|
-
} catch {
|
|
576
|
-
}
|
|
633
|
+
body: json,
|
|
634
|
+
// keepalive: el POST sobrevive a la navegación mientras el body sea pequeño.
|
|
635
|
+
...small ? { keepalive: true } : {}
|
|
636
|
+
});
|
|
637
|
+
if (!res.ok) {
|
|
638
|
+
const detail = await safeBodyText(res);
|
|
639
|
+
if (isRetryableStatus(res.status)) {
|
|
640
|
+
throw new Error(`POST /sdk/feedback ${res.status} (reintentable): ${detail}`);
|
|
577
641
|
}
|
|
578
|
-
|
|
642
|
+
options.log?.(
|
|
643
|
+
`[DeepdotsAnalytics] POST /sdk/feedback rechazado con ${res.status}; lote DESCARTADO:`,
|
|
644
|
+
detail
|
|
645
|
+
);
|
|
646
|
+
return;
|
|
647
|
+
}
|
|
648
|
+
try {
|
|
649
|
+
const data = await res.json();
|
|
650
|
+
if (data?.sessionId && data.sessionId !== feedbackSessionId) {
|
|
651
|
+
feedbackSessionId = data.sessionId;
|
|
652
|
+
options.log?.("[DeepdotsAnalytics] feedbackSessionId cacheado:", feedbackSessionId);
|
|
653
|
+
options.onSessionId?.(feedbackSessionId);
|
|
654
|
+
}
|
|
655
|
+
} catch {
|
|
656
|
+
}
|
|
657
|
+
};
|
|
658
|
+
return async (envelope, meta) => {
|
|
659
|
+
const final = meta?.final === true;
|
|
660
|
+
if (!feedbackSessionId && firstPostInFlight && !final) {
|
|
661
|
+
await firstPostInFlight;
|
|
662
|
+
}
|
|
663
|
+
const body = buildAnalyticsFeedbackBody(envelope, options.keys, feedbackSessionId);
|
|
664
|
+
options.log?.("[DeepdotsAnalytics] POST /sdk/feedback \u2192", body);
|
|
665
|
+
const sent = post(body, final);
|
|
666
|
+
if (!feedbackSessionId) {
|
|
667
|
+
firstPostInFlight = sent.then(
|
|
668
|
+
() => void 0,
|
|
669
|
+
() => void 0
|
|
670
|
+
);
|
|
671
|
+
}
|
|
672
|
+
await sent;
|
|
579
673
|
};
|
|
580
674
|
}
|
|
581
675
|
|
|
@@ -719,20 +813,95 @@ function collectDeviceInfo(appVersion) {
|
|
|
719
813
|
}
|
|
720
814
|
|
|
721
815
|
// src/analytics/geo-info.ts
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
816
|
+
var GEO_STORAGE_KEY = "deepdots.geo";
|
|
817
|
+
var GEO_TTL_MS = 30 * 24 * 60 * 60 * 1e3;
|
|
818
|
+
var GEO_TIMEOUT_MS = 3e3;
|
|
819
|
+
function readCachedGeo(storage, now) {
|
|
820
|
+
try {
|
|
821
|
+
const raw = storage.getItem(GEO_STORAGE_KEY);
|
|
822
|
+
if (!raw) return null;
|
|
823
|
+
const cached = JSON.parse(raw);
|
|
824
|
+
if (typeof cached.ts !== "number" || now - cached.ts > GEO_TTL_MS) return null;
|
|
825
|
+
const geo = {};
|
|
826
|
+
if (cached.country) geo.country = cached.country;
|
|
827
|
+
if (cached.city) geo.city = cached.city;
|
|
828
|
+
return geo.country || geo.city ? geo : null;
|
|
829
|
+
} catch {
|
|
830
|
+
return null;
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
function writeCachedGeo(storage, geo, now) {
|
|
834
|
+
try {
|
|
835
|
+
const payload = { ...geo, ts: now };
|
|
836
|
+
storage.setItem(GEO_STORAGE_KEY, JSON.stringify(payload));
|
|
837
|
+
} catch {
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
var DEFAULT_GEO_PROVIDERS = [
|
|
841
|
+
{
|
|
842
|
+
// https://ipapi.co/json/
|
|
843
|
+
url: "https://ipapi.co/json/",
|
|
844
|
+
parse: (d) => geoFrom(d.country_code, d.city)
|
|
845
|
+
},
|
|
846
|
+
{
|
|
847
|
+
// https://ipwho.is/ (devuelve success:false con 200 en errores)
|
|
848
|
+
url: "https://ipwho.is/",
|
|
849
|
+
parse: (d) => d.success === false ? null : geoFrom(d.country_code, d.city)
|
|
850
|
+
},
|
|
851
|
+
{
|
|
852
|
+
// https://ipinfo.io/json (country = código ISO)
|
|
853
|
+
url: "https://ipinfo.io/json",
|
|
854
|
+
parse: (d) => geoFrom(d.country, d.city)
|
|
855
|
+
}
|
|
856
|
+
];
|
|
857
|
+
function geoFrom(country, city) {
|
|
858
|
+
const c = typeof country === "string" && country ? country : void 0;
|
|
859
|
+
const t = typeof city === "string" && city ? city : void 0;
|
|
860
|
+
if (!c && !t) return null;
|
|
861
|
+
return { country: c, city: t };
|
|
862
|
+
}
|
|
863
|
+
async function fetchOne(provider, f, timeoutMs) {
|
|
864
|
+
const controller = new AbortController();
|
|
865
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
725
866
|
try {
|
|
726
|
-
const res = await f(
|
|
867
|
+
const res = await f(provider.url, { signal: controller.signal });
|
|
727
868
|
if (!res.ok) return null;
|
|
728
869
|
const data = await res.json();
|
|
729
|
-
|
|
730
|
-
const city = data.city || void 0;
|
|
731
|
-
if (!country && !city) return null;
|
|
732
|
-
return { country, city };
|
|
870
|
+
return provider.parse(data);
|
|
733
871
|
} catch {
|
|
734
872
|
return null;
|
|
873
|
+
} finally {
|
|
874
|
+
clearTimeout(timer);
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
async function fetchGeo(options = {}) {
|
|
878
|
+
const f = options.fetchImpl ?? (typeof fetch !== "undefined" ? fetch : void 0);
|
|
879
|
+
if (!f) return null;
|
|
880
|
+
const providers = options.providers ?? DEFAULT_GEO_PROVIDERS;
|
|
881
|
+
const timeoutMs = options.timeoutMs ?? GEO_TIMEOUT_MS;
|
|
882
|
+
for (const provider of providers) {
|
|
883
|
+
const geo = await fetchOne(provider, f, timeoutMs);
|
|
884
|
+
if (geo) return geo;
|
|
885
|
+
}
|
|
886
|
+
return null;
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
// src/analytics/language.ts
|
|
890
|
+
function clean(value) {
|
|
891
|
+
const trimmed = value?.trim();
|
|
892
|
+
return trimmed ? trimmed : void 0;
|
|
893
|
+
}
|
|
894
|
+
function resolveLanguage(sources) {
|
|
895
|
+
const explicit = clean(sources.explicit);
|
|
896
|
+
if (explicit) return explicit;
|
|
897
|
+
const navLang = clean(sources.navigator?.language);
|
|
898
|
+
if (navLang) return navLang;
|
|
899
|
+
try {
|
|
900
|
+
const locale = clean(sources.intl?.DateTimeFormat().resolvedOptions().locale);
|
|
901
|
+
if (locale) return locale;
|
|
902
|
+
} catch {
|
|
735
903
|
}
|
|
904
|
+
return void 0;
|
|
736
905
|
}
|
|
737
906
|
|
|
738
907
|
// src/analytics/engagement-tracker.ts
|
|
@@ -990,6 +1159,11 @@ var DeepdotsPopups = class {
|
|
|
990
1159
|
this.contact = null;
|
|
991
1160
|
/** Marca si ya se inició la navegación manual (setScreen) — para RN sin History API. */
|
|
992
1161
|
this.navStarted = false;
|
|
1162
|
+
/**
|
|
1163
|
+
* Idioma resuelto en init() (explícito > navigator.language > Intl). Única fuente de
|
|
1164
|
+
* verdad para el context de analytics Y para la segmentación por idioma de los popups.
|
|
1165
|
+
*/
|
|
1166
|
+
this.language = void 0;
|
|
993
1167
|
}
|
|
994
1168
|
/** Initialize the SDK with configuration */
|
|
995
1169
|
init(config) {
|
|
@@ -1031,15 +1205,22 @@ var DeepdotsPopups = class {
|
|
|
1031
1205
|
baseUrl: this.baseUrl,
|
|
1032
1206
|
keys: config.analytics,
|
|
1033
1207
|
log: (...a) => this.log(...a),
|
|
1208
|
+
// sendBeacon: único transporte que sobrevive al cierre de la página.
|
|
1209
|
+
sendBeaconImpl: typeof navigator !== "undefined" && typeof navigator.sendBeacon === "function" ? (url, body) => navigator.sendBeacon(url, body) : void 0,
|
|
1034
1210
|
onSessionId: (id) => {
|
|
1035
1211
|
this.analyticsFeedbackSessionId = id;
|
|
1036
1212
|
}
|
|
1037
1213
|
}) : createDryRunSink((...a) => this.logger.log(...a));
|
|
1038
1214
|
const device = config.device ?? collectDeviceInfo(config.appVersion);
|
|
1215
|
+
this.language = resolveLanguage({
|
|
1216
|
+
explicit: config.language,
|
|
1217
|
+
navigator: typeof navigator !== "undefined" ? navigator : void 0,
|
|
1218
|
+
intl: typeof Intl !== "undefined" ? Intl : void 0
|
|
1219
|
+
});
|
|
1039
1220
|
this.analytics = new AnalyticsManager({
|
|
1040
1221
|
sink: analyticsSink,
|
|
1041
1222
|
publicKey: config.analytics?.publicKey ?? this.config.apiKey,
|
|
1042
|
-
language:
|
|
1223
|
+
language: this.language,
|
|
1043
1224
|
platform: config.platform ?? "web",
|
|
1044
1225
|
device,
|
|
1045
1226
|
maxBatchSize: ANALYTICS_MAX_BATCH_SIZE,
|
|
@@ -1056,8 +1237,13 @@ var DeepdotsPopups = class {
|
|
|
1056
1237
|
if (this.tracking?.isTrackingEnabled()) {
|
|
1057
1238
|
this.crashReporter.install();
|
|
1058
1239
|
}
|
|
1059
|
-
|
|
1060
|
-
|
|
1240
|
+
const cachedGeo = readCachedGeo(storage, Date.now());
|
|
1241
|
+
if (cachedGeo) this.analytics.updateDevice(cachedGeo);
|
|
1242
|
+
fetchGeo().then((geo) => {
|
|
1243
|
+
if (geo) {
|
|
1244
|
+
this.analytics?.updateDevice(geo);
|
|
1245
|
+
writeCachedGeo(storage, geo, Date.now());
|
|
1246
|
+
}
|
|
1061
1247
|
}).catch(() => {
|
|
1062
1248
|
});
|
|
1063
1249
|
this.navObserver = new NavigationObserver();
|
|
@@ -1114,6 +1300,15 @@ var DeepdotsPopups = class {
|
|
|
1114
1300
|
if (!this.tracking?.isTrackingEnabled()) return;
|
|
1115
1301
|
this.analytics?.setUserAttributes(attributes);
|
|
1116
1302
|
}
|
|
1303
|
+
/**
|
|
1304
|
+
* Registra/actualiza una métrica (valor medible) → `feedback.metrics` del body.
|
|
1305
|
+
* Persistente y sobrescribe por key. No-op si tracking off.
|
|
1306
|
+
*/
|
|
1307
|
+
setMetric(key, value) {
|
|
1308
|
+
if (!this.tracking?.isTrackingEnabled()) return;
|
|
1309
|
+
this.analytics?.setMetric(key, value);
|
|
1310
|
+
this.log("analytics \xB7 setMetric:", key, value);
|
|
1311
|
+
}
|
|
1117
1312
|
/** Reporta un error del host (manejado o no) → evento `deepdots_app_crash`. No-op si tracking off. */
|
|
1118
1313
|
reportError(error, options) {
|
|
1119
1314
|
if (!this.tracking?.isTrackingEnabled()) return;
|
|
@@ -1201,10 +1396,14 @@ var DeepdotsPopups = class {
|
|
|
1201
1396
|
events: []
|
|
1202
1397
|
};
|
|
1203
1398
|
}
|
|
1204
|
-
/**
|
|
1205
|
-
|
|
1399
|
+
/**
|
|
1400
|
+
* Envía el lote acumulado de analytics y vacía el buffer.
|
|
1401
|
+
* `final: true` (cierre de página/app) cambia el transporte a `sendBeacon`, que sobrevive
|
|
1402
|
+
* al unload; un lote que falle por red o 5xx se re-encola para el siguiente flush.
|
|
1403
|
+
*/
|
|
1404
|
+
flushAnalytics(options) {
|
|
1206
1405
|
if (!this.tracking?.isTrackingEnabled()) return;
|
|
1207
|
-
this.analytics?.flush(this.analyticsIdentity());
|
|
1406
|
+
this.analytics?.flush(this.analyticsIdentity(), options);
|
|
1208
1407
|
}
|
|
1209
1408
|
/** Emite un evento `user_engagement` con el tiempo activo acumulado (#8). */
|
|
1210
1409
|
flushEngagement() {
|
|
@@ -1227,7 +1426,7 @@ var DeepdotsPopups = class {
|
|
|
1227
1426
|
this.navObserver?.stop();
|
|
1228
1427
|
if (this.tracking?.isTrackingEnabled()) this.analytics?.exitAllMiniServices();
|
|
1229
1428
|
this.flushEngagement();
|
|
1230
|
-
this.flushAnalytics();
|
|
1429
|
+
this.flushAnalytics({ final: true });
|
|
1231
1430
|
});
|
|
1232
1431
|
document.addEventListener("visibilitychange", () => {
|
|
1233
1432
|
if (document.visibilityState === "hidden") {
|
|
@@ -1348,14 +1547,16 @@ var DeepdotsPopups = class {
|
|
|
1348
1547
|
matchesSegmentsLang(def) {
|
|
1349
1548
|
const langs = def.segments?.lang;
|
|
1350
1549
|
if (!langs || langs.length === 0) return true;
|
|
1351
|
-
const
|
|
1352
|
-
if (!
|
|
1353
|
-
this.debug("No
|
|
1550
|
+
const currentLang = (this.language ?? "").toLowerCase();
|
|
1551
|
+
if (!currentLang) {
|
|
1552
|
+
this.debug("No language available for lang comparison", { popupId: def.id, langs });
|
|
1354
1553
|
return true;
|
|
1355
1554
|
}
|
|
1356
|
-
const matches = langs.some(
|
|
1555
|
+
const matches = langs.some(
|
|
1556
|
+
(lang) => typeof lang === "string" && currentLang.startsWith(lang.toLowerCase())
|
|
1557
|
+
);
|
|
1357
1558
|
if (!matches) {
|
|
1358
|
-
this.debug("Lang mismatch for popup", { popupId: def.id, langs,
|
|
1559
|
+
this.debug("Lang mismatch for popup", { popupId: def.id, langs, currentLang });
|
|
1359
1560
|
}
|
|
1360
1561
|
return matches;
|
|
1361
1562
|
}
|