@magicfeedback/popup-sdk 1.16.0 → 1.17.0
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 +66 -24
- package/dist/index.d.ts +66 -24
- package/dist/index.js +192 -82
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +192 -82
- package/dist/index.mjs.map +1 -1
- package/dist/react-native.d.mts +21 -5
- package/dist/react-native.d.ts +21 -5
- package/dist/react-native.js +192 -82
- package/dist/react-native.js.map +1 -1
- package/dist/react-native.mjs +192 -82
- package/dist/react-native.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -1277,97 +1277,166 @@ async function safeBodyText(res) {
|
|
|
1277
1277
|
return "";
|
|
1278
1278
|
}
|
|
1279
1279
|
}
|
|
1280
|
+
var PENDING_MAX = 50;
|
|
1281
|
+
var PENDING_MAX_AGE_MS = 24 * 60 * 60 * 1e3;
|
|
1282
|
+
var POST_TIMEOUT_MS = 2e4;
|
|
1283
|
+
function pendingKey(integration) {
|
|
1284
|
+
return `__deepdots_analytics_pending__:${integration}`;
|
|
1285
|
+
}
|
|
1286
|
+
function generateSessionId() {
|
|
1287
|
+
const c = globalThis.crypto;
|
|
1288
|
+
if (typeof c?.randomUUID === "function") return c.randomUUID();
|
|
1289
|
+
const b = new Uint8Array(16);
|
|
1290
|
+
if (typeof c?.getRandomValues === "function") c.getRandomValues(b);
|
|
1291
|
+
else for (let i = 0; i < 16; i++) b[i] = Math.floor(Math.random() * 256);
|
|
1292
|
+
b[6] = b[6] & 15 | 64;
|
|
1293
|
+
b[8] = b[8] & 63 | 128;
|
|
1294
|
+
const h = Array.from(b, (x) => x.toString(16).padStart(2, "0")).join("");
|
|
1295
|
+
return `${h.slice(0, 8)}-${h.slice(8, 12)}-${h.slice(12, 16)}-${h.slice(16, 20)}-${h.slice(20)}`;
|
|
1296
|
+
}
|
|
1280
1297
|
function createFeedbackSink(options) {
|
|
1281
|
-
let feedbackSessionId = options.initialSessionId || void 0;
|
|
1282
|
-
let firstPostInFlight = null;
|
|
1283
|
-
let generation = 0;
|
|
1284
1298
|
const url = `${options.baseUrl}/sdk/feedback`;
|
|
1285
|
-
const
|
|
1286
|
-
|
|
1299
|
+
const newSessionId = options.newSessionId ?? generateSessionId;
|
|
1300
|
+
const now = options.now ?? (() => Date.now());
|
|
1301
|
+
const storageKey = pendingKey(options.keys.integration);
|
|
1302
|
+
let feedbackSessionId = options.initialSessionId || void 0;
|
|
1303
|
+
let announcedId = feedbackSessionId;
|
|
1304
|
+
const pending2 = loadPending();
|
|
1305
|
+
let draining = null;
|
|
1306
|
+
function loadPending() {
|
|
1307
|
+
if (!options.storage) return [];
|
|
1308
|
+
try {
|
|
1309
|
+
const raw = options.storage.getItem(storageKey);
|
|
1310
|
+
const list = raw ? JSON.parse(raw) : [];
|
|
1311
|
+
return Array.isArray(list) ? list.filter((p) => p?.body && now() - p.at <= PENDING_MAX_AGE_MS) : [];
|
|
1312
|
+
} catch {
|
|
1313
|
+
return [];
|
|
1314
|
+
}
|
|
1315
|
+
}
|
|
1316
|
+
function savePending() {
|
|
1317
|
+
if (!options.storage) return;
|
|
1318
|
+
try {
|
|
1319
|
+
if (pending2.length) options.storage.setItem(storageKey, JSON.stringify(pending2));
|
|
1320
|
+
else options.storage.removeItem(storageKey);
|
|
1321
|
+
} catch {
|
|
1322
|
+
}
|
|
1323
|
+
}
|
|
1324
|
+
function addPending(batch) {
|
|
1325
|
+
pending2.push(batch);
|
|
1326
|
+
if (pending2.length > PENDING_MAX) pending2.splice(0, pending2.length - PENDING_MAX);
|
|
1327
|
+
savePending();
|
|
1328
|
+
}
|
|
1329
|
+
function removePending(batch) {
|
|
1330
|
+
const i = pending2.indexOf(batch);
|
|
1331
|
+
if (i >= 0) pending2.splice(i, 1);
|
|
1332
|
+
savePending();
|
|
1333
|
+
}
|
|
1334
|
+
async function post(body, final) {
|
|
1287
1335
|
const json = JSON.stringify(body);
|
|
1288
1336
|
const small = json.length <= KEEPALIVE_MAX_BYTES;
|
|
1289
1337
|
if (final) options.log?.("[DeepdotsAnalytics] final flush via fetch keepalive");
|
|
1290
1338
|
const f = options.fetchImpl ?? (typeof fetch !== "undefined" ? fetch : void 0);
|
|
1291
1339
|
if (!f) {
|
|
1292
1340
|
options.log?.("[DeepdotsAnalytics] no fetch available; payload not sent", body);
|
|
1293
|
-
return;
|
|
1341
|
+
return "drop";
|
|
1294
1342
|
}
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1343
|
+
let timer;
|
|
1344
|
+
try {
|
|
1345
|
+
const request = f(url, {
|
|
1346
|
+
method: "POST",
|
|
1347
|
+
headers: { "Content-Type": "application/json" },
|
|
1348
|
+
body: json,
|
|
1349
|
+
// La API autentica por `publicKey` en el body y responde `Allow-Origin: *`: una
|
|
1350
|
+
// petición con credenciales fallaría el CORS. Explícito para no depender del default.
|
|
1351
|
+
credentials: "omit",
|
|
1352
|
+
// keepalive: el POST sobrevive a la navegación mientras el body sea pequeño.
|
|
1353
|
+
...small ? { keepalive: true } : {}
|
|
1354
|
+
});
|
|
1355
|
+
const timeout = new Promise((_, reject) => {
|
|
1356
|
+
timer = setTimeout(() => reject(new Error("timeout")), POST_TIMEOUT_MS);
|
|
1357
|
+
});
|
|
1358
|
+
const res = await Promise.race([request, timeout]);
|
|
1359
|
+
if (res.ok) return "ok";
|
|
1306
1360
|
const detail = await safeBodyText(res);
|
|
1307
1361
|
if (isRetryableStatus(res.status)) {
|
|
1308
|
-
|
|
1362
|
+
options.log?.(`[DeepdotsAnalytics] POST /sdk/feedback ${res.status} (retryable): ${detail}`);
|
|
1363
|
+
return "retry";
|
|
1309
1364
|
}
|
|
1310
1365
|
options.log?.(
|
|
1311
1366
|
`[DeepdotsAnalytics] POST /sdk/feedback rejected with ${res.status}; batch DISCARDED:`,
|
|
1312
1367
|
detail
|
|
1313
1368
|
);
|
|
1314
|
-
return;
|
|
1315
|
-
}
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
closedId = data?.sessionId ?? closedId;
|
|
1321
|
-
} catch {
|
|
1322
|
-
}
|
|
1323
|
-
try {
|
|
1324
|
-
if (closedId) options.onSessionClosed?.(closedId);
|
|
1325
|
-
} catch {
|
|
1326
|
-
}
|
|
1327
|
-
return;
|
|
1369
|
+
return "drop";
|
|
1370
|
+
} catch (error) {
|
|
1371
|
+
options.log?.("[DeepdotsAnalytics] POST /sdk/feedback failed (will retry):", error);
|
|
1372
|
+
return "retry";
|
|
1373
|
+
} finally {
|
|
1374
|
+
clearTimeout(timer);
|
|
1328
1375
|
}
|
|
1376
|
+
}
|
|
1377
|
+
function delivered(body) {
|
|
1378
|
+
const id = body.sessionId;
|
|
1379
|
+
if (!id) return;
|
|
1329
1380
|
try {
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
feedbackSessionId = data.sessionId;
|
|
1337
|
-
options.log?.("[DeepdotsAnalytics] feedbackSessionId cached:", feedbackSessionId);
|
|
1338
|
-
options.onSessionId?.(feedbackSessionId);
|
|
1381
|
+
if (body.completed) {
|
|
1382
|
+
options.onSessionClosed?.(id);
|
|
1383
|
+
} else if (id === feedbackSessionId && id !== announcedId) {
|
|
1384
|
+
announcedId = id;
|
|
1385
|
+
options.log?.("[DeepdotsAnalytics] feedback session open:", id);
|
|
1386
|
+
options.onSessionId?.(id);
|
|
1339
1387
|
}
|
|
1340
1388
|
} catch {
|
|
1341
1389
|
}
|
|
1342
|
-
}
|
|
1390
|
+
}
|
|
1391
|
+
async function send(batch, final, alreadyPending) {
|
|
1392
|
+
const result = await post(batch.body, final);
|
|
1393
|
+
if (result === "retry") {
|
|
1394
|
+
if (!alreadyPending) addPending(batch);
|
|
1395
|
+
return;
|
|
1396
|
+
}
|
|
1397
|
+
if (alreadyPending) removePending(batch);
|
|
1398
|
+
if (result === "ok") delivered(batch.body);
|
|
1399
|
+
}
|
|
1400
|
+
function drain() {
|
|
1401
|
+
const run = async () => {
|
|
1402
|
+
for (let i = 0; i < pending2.length; ) {
|
|
1403
|
+
const batch = pending2[i];
|
|
1404
|
+
await send(batch, false, true);
|
|
1405
|
+
if (pending2[i] === batch) return;
|
|
1406
|
+
}
|
|
1407
|
+
};
|
|
1408
|
+
const next = draining ? draining.then(run) : run();
|
|
1409
|
+
draining = next;
|
|
1410
|
+
void next.finally(() => {
|
|
1411
|
+
if (draining === next) draining = null;
|
|
1412
|
+
});
|
|
1413
|
+
return next;
|
|
1414
|
+
}
|
|
1415
|
+
if (pending2.length) {
|
|
1416
|
+
savePending();
|
|
1417
|
+
setTimeout(() => void drain(), 0);
|
|
1418
|
+
}
|
|
1343
1419
|
return async (envelope, meta) => {
|
|
1344
1420
|
if (meta?.resetSession) {
|
|
1345
1421
|
feedbackSessionId = void 0;
|
|
1346
|
-
firstPostInFlight = null;
|
|
1347
|
-
generation++;
|
|
1348
1422
|
options.onSessionReset?.();
|
|
1349
1423
|
return;
|
|
1350
1424
|
}
|
|
1351
1425
|
const final = meta?.final === true;
|
|
1352
1426
|
const sessionEnd = meta?.sessionEnd === true;
|
|
1353
|
-
if (!feedbackSessionId
|
|
1354
|
-
await firstPostInFlight;
|
|
1355
|
-
}
|
|
1427
|
+
if (!feedbackSessionId) feedbackSessionId = newSessionId();
|
|
1356
1428
|
const body = buildAnalyticsFeedbackBody(envelope, options.keys, feedbackSessionId, { sessionEnd });
|
|
1357
1429
|
options.log?.("[DeepdotsAnalytics] POST /sdk/feedback \u2192", body);
|
|
1358
|
-
const
|
|
1430
|
+
const batch = { body, at: now() };
|
|
1359
1431
|
if (sessionEnd) {
|
|
1360
1432
|
feedbackSessionId = void 0;
|
|
1361
|
-
firstPostInFlight = null;
|
|
1362
|
-
generation++;
|
|
1363
1433
|
options.onSessionReset?.();
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
() => void 0
|
|
1368
|
-
);
|
|
1434
|
+
addPending(batch);
|
|
1435
|
+
await drain();
|
|
1436
|
+
return;
|
|
1369
1437
|
}
|
|
1370
|
-
|
|
1438
|
+
const retries = pending2.length ? drain() : null;
|
|
1439
|
+
await Promise.all([retries, send(batch, final, false)]);
|
|
1371
1440
|
};
|
|
1372
1441
|
}
|
|
1373
1442
|
|
|
@@ -1724,12 +1793,12 @@ var EngagementTracker = class {
|
|
|
1724
1793
|
};
|
|
1725
1794
|
|
|
1726
1795
|
// src/contact/contact-manager.ts
|
|
1727
|
-
function stableSerialize(attrs) {
|
|
1796
|
+
function stableSerialize(userId, attrs) {
|
|
1728
1797
|
const sorted = {};
|
|
1729
1798
|
for (const key of Object.keys(attrs).sort()) {
|
|
1730
1799
|
sorted[key] = attrs[key];
|
|
1731
1800
|
}
|
|
1732
|
-
return JSON.stringify(sorted);
|
|
1801
|
+
return JSON.stringify({ userId, attributes: sorted });
|
|
1733
1802
|
}
|
|
1734
1803
|
var ContactManager = class {
|
|
1735
1804
|
constructor(options) {
|
|
@@ -1739,18 +1808,23 @@ var ContactManager = class {
|
|
|
1739
1808
|
this.post = options.post;
|
|
1740
1809
|
}
|
|
1741
1810
|
/**
|
|
1742
|
-
* Envía los atributos si cambiaron respecto a lo último enviado.
|
|
1743
|
-
*
|
|
1811
|
+
* Envía los atributos si cambiaron respecto a lo último enviado. La caché solo se guarda
|
|
1812
|
+
* cuando la API acepta el envío; si falla, la llamada siguiente reintenta. Nunca rechaza.
|
|
1813
|
+
* @returns `true` si se envió, `false` si no hubo cambios o el envío falló.
|
|
1744
1814
|
*/
|
|
1745
1815
|
async setAttributes(attributes) {
|
|
1746
|
-
const serialized = stableSerialize(attributes);
|
|
1816
|
+
const serialized = stableSerialize(this.userId, attributes);
|
|
1747
1817
|
const last = this.storage.getItem(STORAGE_KEYS.contactAttributes);
|
|
1748
1818
|
if (last === serialized) return false;
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1819
|
+
try {
|
|
1820
|
+
await this.post({
|
|
1821
|
+
publicKey: this.publicKey,
|
|
1822
|
+
userId: this.userId,
|
|
1823
|
+
userAttributes: attributes
|
|
1824
|
+
});
|
|
1825
|
+
} catch {
|
|
1826
|
+
return false;
|
|
1827
|
+
}
|
|
1754
1828
|
this.storage.setItem(STORAGE_KEYS.contactAttributes, serialized);
|
|
1755
1829
|
return true;
|
|
1756
1830
|
}
|
|
@@ -1781,6 +1855,10 @@ function crashRecordToParams(r) {
|
|
|
1781
1855
|
}
|
|
1782
1856
|
var CrashReporter = class {
|
|
1783
1857
|
constructor(options) {
|
|
1858
|
+
/** Los handlers de `window` ya están puestos: `install()` es idempotente. */
|
|
1859
|
+
this.windowInstalled = false;
|
|
1860
|
+
/** ErrorUtils ya enganchados: engancharlos dos veces capturaría cada error dos veces. */
|
|
1861
|
+
this.hookedErrorUtils = /* @__PURE__ */ new Set();
|
|
1784
1862
|
this.options = options;
|
|
1785
1863
|
this.now = options.now ?? (() => Date.now());
|
|
1786
1864
|
}
|
|
@@ -1856,9 +1934,11 @@ var CrashReporter = class {
|
|
|
1856
1934
|
}
|
|
1857
1935
|
return queue;
|
|
1858
1936
|
}
|
|
1859
|
-
/** Instala los handlers globales de errores no capturados (no-op sin `window`). */
|
|
1937
|
+
/** Instala los handlers globales de errores no capturados (no-op sin `window`). Idempotente. */
|
|
1860
1938
|
install() {
|
|
1939
|
+
if (this.windowInstalled) return;
|
|
1861
1940
|
if (typeof window === "undefined" || typeof window.addEventListener !== "function") return;
|
|
1941
|
+
this.windowInstalled = true;
|
|
1862
1942
|
window.addEventListener("error", (e) => {
|
|
1863
1943
|
if (!this.isEnabled()) return;
|
|
1864
1944
|
this.captureUnhandled(e.error ?? e.message ?? "error");
|
|
@@ -1868,8 +1948,10 @@ var CrashReporter = class {
|
|
|
1868
1948
|
this.captureUnhandled(e.reason ?? "unhandledrejection");
|
|
1869
1949
|
});
|
|
1870
1950
|
}
|
|
1871
|
-
/** Engancha `global.ErrorUtils` de RN (no hay `window`): persiste y encadena al handler previo. */
|
|
1951
|
+
/** Engancha `global.ErrorUtils` de RN (no hay `window`): persiste y encadena al handler previo. Idempotente por instancia de ErrorUtils. */
|
|
1872
1952
|
installReactNative(errorUtils) {
|
|
1953
|
+
if (this.hookedErrorUtils.has(errorUtils)) return;
|
|
1954
|
+
this.hookedErrorUtils.add(errorUtils);
|
|
1873
1955
|
const previous = errorUtils.getGlobalHandler?.();
|
|
1874
1956
|
errorUtils.setGlobalHandler((error, isFatal) => {
|
|
1875
1957
|
if (this.isEnabled()) this.captureUnhandled(error);
|
|
@@ -2082,6 +2164,8 @@ var DeepdotsPopups = class {
|
|
|
2082
2164
|
this.analyticsFlushTimer = void 0;
|
|
2083
2165
|
/** Crash & error reporting (#14–17). Null hasta init(). */
|
|
2084
2166
|
this.crashReporter = null;
|
|
2167
|
+
/** ErrorUtils de RN recibido del host (o del Provider); se engancha en cuanto haya consentimiento. */
|
|
2168
|
+
this.rnErrorUtils = null;
|
|
2085
2169
|
/** Protecciones del funnel de Messaging (#18–22). Vigencia de sesión: se reinicia en init(). */
|
|
2086
2170
|
this.messageGuard = new MessageGuard();
|
|
2087
2171
|
/** Observador de navegación (Fase 2): emite page_view por el canal de analytics. */
|
|
@@ -2182,6 +2266,10 @@ var DeepdotsPopups = class {
|
|
|
2182
2266
|
keys: config.analytics,
|
|
2183
2267
|
log: (...a) => this.log(...a),
|
|
2184
2268
|
initialSessionId: resumable?.sessionId,
|
|
2269
|
+
// En móvil (RN) la app puede morir con el lote de cierre sin entregar: se
|
|
2270
|
+
// persiste y se reenvía en el arranque siguiente. En web el cierre sale con
|
|
2271
|
+
// `keepalive` y varias pestañas compartirían el mismo storage.
|
|
2272
|
+
storage: (config.platform ?? "web") !== "web" ? storage : void 0,
|
|
2185
2273
|
onSessionId: (id) => {
|
|
2186
2274
|
this.analyticsFeedbackSessionId = id;
|
|
2187
2275
|
this.notifyFeedbackSession(id, "open");
|
|
@@ -2217,9 +2305,7 @@ var DeepdotsPopups = class {
|
|
|
2217
2305
|
now: () => Date.now(),
|
|
2218
2306
|
enabled: () => this.tracking?.isTrackingEnabled() ?? false
|
|
2219
2307
|
});
|
|
2220
|
-
|
|
2221
|
-
this.crashReporter.install();
|
|
2222
|
-
}
|
|
2308
|
+
this.installCrashHandlers();
|
|
2223
2309
|
this.geoEnabled = config.geolocation ?? true;
|
|
2224
2310
|
this.geoWanted = this.geoEnabled && !!config.analytics;
|
|
2225
2311
|
this.resolveGeo();
|
|
@@ -2288,6 +2374,7 @@ var DeepdotsPopups = class {
|
|
|
2288
2374
|
if (!enabled) this.closeSession("tracking_disabled");
|
|
2289
2375
|
this.tracking?.setTrackingEnabled(enabled);
|
|
2290
2376
|
if (enabled) this.openSession("tracking_enabled");
|
|
2377
|
+
if (enabled) this.installCrashHandlers();
|
|
2291
2378
|
if (enabled) this.resolveGeo();
|
|
2292
2379
|
this.log("tracking \xB7 setTrackingEnabled:", enabled, "\xB7 session_id:", this.tracking?.getSessionId() ?? null);
|
|
2293
2380
|
}
|
|
@@ -2318,10 +2405,27 @@ var DeepdotsPopups = class {
|
|
|
2318
2405
|
if (!this.tracking?.isTrackingEnabled()) return;
|
|
2319
2406
|
this.crashReporter?.reportError(error, options);
|
|
2320
2407
|
}
|
|
2321
|
-
/**
|
|
2408
|
+
/**
|
|
2409
|
+
* Engancha `global.ErrorUtils` de RN para capturar errores JS no manejados. Con tracking off
|
|
2410
|
+
* se recuerda y se engancha cuando se conceda el consentimiento (`setTrackingEnabled(true)`).
|
|
2411
|
+
*/
|
|
2322
2412
|
installReactNativeCrashHandler(errorUtils) {
|
|
2323
|
-
|
|
2324
|
-
this.
|
|
2413
|
+
this.rnErrorUtils = errorUtils;
|
|
2414
|
+
this.installCrashHandlers();
|
|
2415
|
+
}
|
|
2416
|
+
/**
|
|
2417
|
+
* Pone los handlers de errores no capturados: `window` en web y `ErrorUtils` en RN (el que
|
|
2418
|
+
* pasó el host o, si no pasó ninguno, `globalThis.ErrorUtils`, para que un `init()` sin
|
|
2419
|
+
* `setupReactNative` también capture). Solo con consentimiento; idempotente, así que se
|
|
2420
|
+
* llama en `init()` y cada vez que se activa el tracking.
|
|
2421
|
+
*/
|
|
2422
|
+
installCrashHandlers() {
|
|
2423
|
+
if (!this.crashReporter || !this.tracking?.isTrackingEnabled()) return;
|
|
2424
|
+
this.crashReporter.install();
|
|
2425
|
+
const errorUtils = this.rnErrorUtils ?? globalThis.ErrorUtils;
|
|
2426
|
+
if (errorUtils && typeof errorUtils.setGlobalHandler === "function") {
|
|
2427
|
+
this.crashReporter.installReactNative(errorUtils);
|
|
2428
|
+
}
|
|
2325
2429
|
}
|
|
2326
2430
|
/**
|
|
2327
2431
|
* Info interna del usuario que solo conoce el host (idioma, edad, plan…), persistida en el
|
|
@@ -3144,25 +3248,31 @@ var DeepdotsPopups = class {
|
|
|
3144
3248
|
return null;
|
|
3145
3249
|
}
|
|
3146
3250
|
}
|
|
3147
|
-
/**
|
|
3251
|
+
/**
|
|
3252
|
+
* Envía los atributos de contact a la API (`POST /sdk/popups/contact`). Rechaza si no se
|
|
3253
|
+
* pudo enviar (sin red o respuesta no-2xx), para que `ContactManager` no guarde la caché
|
|
3254
|
+
* y la llamada siguiente reintente.
|
|
3255
|
+
*/
|
|
3148
3256
|
async postContact(body) {
|
|
3149
3257
|
const baseUrl = this.baseUrl;
|
|
3150
|
-
if (!baseUrl)
|
|
3258
|
+
if (!baseUrl) throw new Error("no base URL");
|
|
3151
3259
|
const endpoint = `${baseUrl}/sdk/popups/contact`;
|
|
3260
|
+
let response;
|
|
3152
3261
|
try {
|
|
3153
|
-
|
|
3262
|
+
response = await fetch(endpoint, {
|
|
3154
3263
|
method: "POST",
|
|
3155
3264
|
headers: { "Content-Type": "application/json" },
|
|
3156
3265
|
body: JSON.stringify(body)
|
|
3157
3266
|
});
|
|
3158
|
-
if (!response.ok) {
|
|
3159
|
-
this.log("Failed to post contact", response.status, response.statusText);
|
|
3160
|
-
return;
|
|
3161
|
-
}
|
|
3162
|
-
this.log("contact \xB7 sent attributes for user:", body.userId);
|
|
3163
3267
|
} catch (error) {
|
|
3164
3268
|
this.log("Error posting contact", error);
|
|
3269
|
+
throw error;
|
|
3270
|
+
}
|
|
3271
|
+
if (!response.ok) {
|
|
3272
|
+
this.log("Failed to post contact", response.status, response.statusText);
|
|
3273
|
+
throw new Error(`HTTP ${response.status}`);
|
|
3165
3274
|
}
|
|
3275
|
+
this.log("contact \xB7 sent attributes for user:", body.userId);
|
|
3166
3276
|
}
|
|
3167
3277
|
/** Notifica eventos de popup a la API */
|
|
3168
3278
|
async postPopupEvent(status, popupId, userId) {
|