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