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