@funnelfox/billing 0.7.1 → 0.8.0-beta.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/chunk-index.cjs.js +249 -18
- package/dist/chunk-index.es.js +249 -18
- package/dist/funnelfox-billing.js +249 -18
- package/dist/funnelfox-billing.min.js +1 -1
- package/package.json +2 -2
- package/src/types.d.ts +1 -0
package/dist/chunk-index.cjs.js
CHANGED
|
@@ -370,6 +370,9 @@ function generateUUID() {
|
|
|
370
370
|
return v.toString(16);
|
|
371
371
|
});
|
|
372
372
|
}
|
|
373
|
+
function isBrowser() {
|
|
374
|
+
return typeof window !== 'undefined' && typeof document !== 'undefined';
|
|
375
|
+
}
|
|
373
376
|
function sleep(ms) {
|
|
374
377
|
return new Promise(resolve => setTimeout(resolve, ms));
|
|
375
378
|
}
|
|
@@ -488,7 +491,7 @@ exports.PaymentMethod = void 0;
|
|
|
488
491
|
/**
|
|
489
492
|
* @fileoverview Constants for Funnefox SDK
|
|
490
493
|
*/
|
|
491
|
-
const SDK_VERSION = '0.
|
|
494
|
+
const SDK_VERSION = '0.8.0-beta.0';
|
|
492
495
|
const DEFAULTS = {
|
|
493
496
|
BASE_URL: 'https://billing.funnelfox.com',
|
|
494
497
|
REGION: 'default',
|
|
@@ -1495,6 +1498,219 @@ async function loadAirwallexDeviceFingerprint(sessionId, isLivemode = true) {
|
|
|
1495
1498
|
});
|
|
1496
1499
|
}
|
|
1497
1500
|
|
|
1501
|
+
const MAX_QUERY_LENGTH = 1800;
|
|
1502
|
+
function getErrorImage(orgId, options) {
|
|
1503
|
+
if (typeof document === 'undefined') {
|
|
1504
|
+
return;
|
|
1505
|
+
}
|
|
1506
|
+
const params = new URLSearchParams({
|
|
1507
|
+
message: truncate(options.message, 500),
|
|
1508
|
+
code: options.code || 'SDK_ERROR',
|
|
1509
|
+
timestamp: Date.now().toString(),
|
|
1510
|
+
sdk_version: SDK_VERSION,
|
|
1511
|
+
});
|
|
1512
|
+
if (options.req_id) {
|
|
1513
|
+
appendIfFits(params, 'req_id', options.req_id, MAX_QUERY_LENGTH);
|
|
1514
|
+
}
|
|
1515
|
+
if (options.context) {
|
|
1516
|
+
Object.entries(options.context).forEach(([key, value]) => {
|
|
1517
|
+
if (value !== undefined && value !== null) {
|
|
1518
|
+
appendIfFits(params, key, truncate(String(value), 1000), MAX_QUERY_LENGTH);
|
|
1519
|
+
}
|
|
1520
|
+
});
|
|
1521
|
+
}
|
|
1522
|
+
const url = `https://billing.funnelfox.com/sdk_report/${encodeURIComponent(orgId)}/crash?${params.toString()}`;
|
|
1523
|
+
const img = new Image();
|
|
1524
|
+
img.src = url;
|
|
1525
|
+
img.style.display = 'none';
|
|
1526
|
+
img.onload = () => {
|
|
1527
|
+
img.remove();
|
|
1528
|
+
};
|
|
1529
|
+
img.onerror = () => {
|
|
1530
|
+
img.remove();
|
|
1531
|
+
};
|
|
1532
|
+
(document.body || document.documentElement)?.appendChild(img);
|
|
1533
|
+
}
|
|
1534
|
+
function truncate(value, maxLength) {
|
|
1535
|
+
return value.length > maxLength ? value.slice(0, maxLength) : value;
|
|
1536
|
+
}
|
|
1537
|
+
function appendIfFits(params, key, value, maxLength) {
|
|
1538
|
+
const nextParams = new URLSearchParams(params);
|
|
1539
|
+
nextParams.append(key, value);
|
|
1540
|
+
if (nextParams.toString().length > maxLength) {
|
|
1541
|
+
return false;
|
|
1542
|
+
}
|
|
1543
|
+
params.append(key, value);
|
|
1544
|
+
return true;
|
|
1545
|
+
}
|
|
1546
|
+
|
|
1547
|
+
let activeScope = null;
|
|
1548
|
+
const recentSignatures = new Map();
|
|
1549
|
+
const DEDUPE_WINDOW_MS = 3000;
|
|
1550
|
+
let isListening = false;
|
|
1551
|
+
function startUnhandledErrorTelemetry(scope) {
|
|
1552
|
+
if (!scope.enabled || !isBrowser()) {
|
|
1553
|
+
return () => { };
|
|
1554
|
+
}
|
|
1555
|
+
activeScope = scope;
|
|
1556
|
+
attachListeners();
|
|
1557
|
+
return () => {
|
|
1558
|
+
stopUnhandledErrorTelemetry(scope.id);
|
|
1559
|
+
};
|
|
1560
|
+
}
|
|
1561
|
+
function stopUnhandledErrorTelemetry(scopeId) {
|
|
1562
|
+
if (activeScope?.id === scopeId) {
|
|
1563
|
+
activeScope = null;
|
|
1564
|
+
recentSignatures.clear();
|
|
1565
|
+
detachListeners();
|
|
1566
|
+
}
|
|
1567
|
+
}
|
|
1568
|
+
function attachListeners() {
|
|
1569
|
+
if (isListening || typeof window === 'undefined') {
|
|
1570
|
+
return;
|
|
1571
|
+
}
|
|
1572
|
+
window.addEventListener('error', handleWindowError);
|
|
1573
|
+
window.addEventListener('unhandledrejection', handleUnhandledRejection);
|
|
1574
|
+
isListening = true;
|
|
1575
|
+
}
|
|
1576
|
+
function detachListeners() {
|
|
1577
|
+
if (!isListening || typeof window === 'undefined') {
|
|
1578
|
+
return;
|
|
1579
|
+
}
|
|
1580
|
+
window.removeEventListener('error', handleWindowError);
|
|
1581
|
+
window.removeEventListener('unhandledrejection', handleUnhandledRejection);
|
|
1582
|
+
isListening = false;
|
|
1583
|
+
}
|
|
1584
|
+
function handleWindowError(event) {
|
|
1585
|
+
const normalized = normalizeError(event.error || event.message || 'Unhandled browser error', 'UNHANDLED_ERROR');
|
|
1586
|
+
reportToActiveScopes(normalized, {
|
|
1587
|
+
event_type: 'error',
|
|
1588
|
+
filename: event.filename,
|
|
1589
|
+
lineno: event.lineno,
|
|
1590
|
+
colno: event.colno,
|
|
1591
|
+
});
|
|
1592
|
+
}
|
|
1593
|
+
function handleUnhandledRejection(event) {
|
|
1594
|
+
const normalized = normalizeError(event.reason || 'Unhandled promise rejection', 'UNHANDLED_REJECTION');
|
|
1595
|
+
reportToActiveScopes(normalized, {
|
|
1596
|
+
event_type: 'unhandledrejection',
|
|
1597
|
+
});
|
|
1598
|
+
}
|
|
1599
|
+
function reportToActiveScopes(error, eventContext) {
|
|
1600
|
+
if (!activeScope) {
|
|
1601
|
+
return;
|
|
1602
|
+
}
|
|
1603
|
+
if (!canReport(error, eventContext.event_type)) {
|
|
1604
|
+
return;
|
|
1605
|
+
}
|
|
1606
|
+
try {
|
|
1607
|
+
const context = activeScope.getContext();
|
|
1608
|
+
getErrorImage(activeScope.orgId, {
|
|
1609
|
+
message: `${error.name}: ${error.message}`,
|
|
1610
|
+
code: error.code,
|
|
1611
|
+
req_id: context.reqId,
|
|
1612
|
+
context: {
|
|
1613
|
+
...eventContext,
|
|
1614
|
+
checkout_id: context.checkoutId,
|
|
1615
|
+
order_id: context.orderId,
|
|
1616
|
+
price_id: context.priceId,
|
|
1617
|
+
checkout_state: context.state,
|
|
1618
|
+
payment_method: context.paymentMethod,
|
|
1619
|
+
stack: error.stack,
|
|
1620
|
+
page_url: getPageUrl(),
|
|
1621
|
+
},
|
|
1622
|
+
});
|
|
1623
|
+
}
|
|
1624
|
+
catch {
|
|
1625
|
+
// Telemetry must never affect checkout behavior.
|
|
1626
|
+
}
|
|
1627
|
+
}
|
|
1628
|
+
function canReport(error, eventType) {
|
|
1629
|
+
const now = Date.now();
|
|
1630
|
+
pruneExpiredSignatures(now);
|
|
1631
|
+
const signature = buildSignature(error, eventType);
|
|
1632
|
+
const previousReportTime = recentSignatures.get(signature);
|
|
1633
|
+
if (typeof previousReportTime === 'number' &&
|
|
1634
|
+
now - previousReportTime < DEDUPE_WINDOW_MS) {
|
|
1635
|
+
return false;
|
|
1636
|
+
}
|
|
1637
|
+
recentSignatures.set(signature, now);
|
|
1638
|
+
return true;
|
|
1639
|
+
}
|
|
1640
|
+
function pruneExpiredSignatures(now) {
|
|
1641
|
+
recentSignatures.forEach((timestamp, signature) => {
|
|
1642
|
+
if (now - timestamp >= DEDUPE_WINDOW_MS) {
|
|
1643
|
+
recentSignatures.delete(signature);
|
|
1644
|
+
}
|
|
1645
|
+
});
|
|
1646
|
+
}
|
|
1647
|
+
function buildSignature(error, eventType) {
|
|
1648
|
+
const firstStackLine = error.stack?.split('\n')[0] || '';
|
|
1649
|
+
return [
|
|
1650
|
+
eventType || '',
|
|
1651
|
+
error.code || '',
|
|
1652
|
+
error.message || '',
|
|
1653
|
+
firstStackLine,
|
|
1654
|
+
].join('|');
|
|
1655
|
+
}
|
|
1656
|
+
function normalizeError(reason, fallbackCode) {
|
|
1657
|
+
if (reason instanceof Error) {
|
|
1658
|
+
return {
|
|
1659
|
+
name: reason.name || 'Error',
|
|
1660
|
+
message: reason.message || 'Unknown error',
|
|
1661
|
+
code: getErrorCode(reason, fallbackCode),
|
|
1662
|
+
stack: reason.stack,
|
|
1663
|
+
};
|
|
1664
|
+
}
|
|
1665
|
+
if (typeof reason === 'object' && reason !== null) {
|
|
1666
|
+
const record = reason;
|
|
1667
|
+
return {
|
|
1668
|
+
name: toSafeString(record.name) || 'Error',
|
|
1669
|
+
message: toSafeString(record.message) || safeStringify(reason),
|
|
1670
|
+
code: toSafeString(record.code) || fallbackCode,
|
|
1671
|
+
stack: toSafeString(record.stack),
|
|
1672
|
+
};
|
|
1673
|
+
}
|
|
1674
|
+
return {
|
|
1675
|
+
name: 'Error',
|
|
1676
|
+
message: toSafeString(reason) || 'Unknown error',
|
|
1677
|
+
code: fallbackCode,
|
|
1678
|
+
};
|
|
1679
|
+
}
|
|
1680
|
+
function getErrorCode(error, fallbackCode) {
|
|
1681
|
+
const record = error;
|
|
1682
|
+
return typeof record.code === 'string' && record.code
|
|
1683
|
+
? record.code
|
|
1684
|
+
: fallbackCode;
|
|
1685
|
+
}
|
|
1686
|
+
function toSafeString(value) {
|
|
1687
|
+
if (typeof value === 'string') {
|
|
1688
|
+
return value;
|
|
1689
|
+
}
|
|
1690
|
+
if (typeof value === 'number' || typeof value === 'boolean') {
|
|
1691
|
+
return String(value);
|
|
1692
|
+
}
|
|
1693
|
+
return '';
|
|
1694
|
+
}
|
|
1695
|
+
function safeStringify(value) {
|
|
1696
|
+
try {
|
|
1697
|
+
return JSON.stringify(value);
|
|
1698
|
+
}
|
|
1699
|
+
catch {
|
|
1700
|
+
return 'Unserializable error';
|
|
1701
|
+
}
|
|
1702
|
+
}
|
|
1703
|
+
function getPageUrl() {
|
|
1704
|
+
if (typeof window === 'undefined' || !window.location) {
|
|
1705
|
+
return '';
|
|
1706
|
+
}
|
|
1707
|
+
const location = window.location;
|
|
1708
|
+
if (location.origin && location.pathname) {
|
|
1709
|
+
return `${location.origin}${location.pathname}`;
|
|
1710
|
+
}
|
|
1711
|
+
return (location.href || '').split(/[?#]/)[0];
|
|
1712
|
+
}
|
|
1713
|
+
|
|
1498
1714
|
/**
|
|
1499
1715
|
* @fileoverview Checkout instance manager for Funnefox SDK
|
|
1500
1716
|
*/
|
|
@@ -1504,6 +1720,8 @@ class CheckoutInstance extends EventEmitter {
|
|
|
1504
1720
|
this.counter = 0;
|
|
1505
1721
|
this.cachedSessionResponse = null;
|
|
1506
1722
|
this.cardSessionFieldConfig = {};
|
|
1723
|
+
this.isTelemetryEnabled = false;
|
|
1724
|
+
this.telemetryCleanup = null;
|
|
1507
1725
|
this.handleInputChange = (inputName, error) => {
|
|
1508
1726
|
this.emit(EVENTS.INPUT_ERROR, { name: inputName, error });
|
|
1509
1727
|
};
|
|
@@ -1666,6 +1884,7 @@ class CheckoutInstance extends EventEmitter {
|
|
|
1666
1884
|
await this.createSession();
|
|
1667
1885
|
await this._initializePrimerCheckout();
|
|
1668
1886
|
this._setState('ready');
|
|
1887
|
+
this.startUnhandledTelemetry();
|
|
1669
1888
|
this.checkoutConfig?.onInitialized?.();
|
|
1670
1889
|
return this;
|
|
1671
1890
|
}
|
|
@@ -1740,6 +1959,7 @@ class CheckoutInstance extends EventEmitter {
|
|
|
1740
1959
|
sessionResponse = await sessionRequest;
|
|
1741
1960
|
}
|
|
1742
1961
|
this.cachedSessionResponse = sessionResponse;
|
|
1962
|
+
this.isTelemetryEnabled = !!sessionResponse.data?.sdk_telemetry_enabled;
|
|
1743
1963
|
this.isCollectingApplePayEmail =
|
|
1744
1964
|
!!sessionResponse.data?.collect_apple_pay_email;
|
|
1745
1965
|
this.applySessionCardFieldConfig(sessionResponse);
|
|
@@ -2024,6 +2244,7 @@ class CheckoutInstance extends EventEmitter {
|
|
|
2024
2244
|
if (this.isDestroyed)
|
|
2025
2245
|
return;
|
|
2026
2246
|
try {
|
|
2247
|
+
this.stopUnhandledTelemetry();
|
|
2027
2248
|
CheckoutInstance.sessionCache.clear();
|
|
2028
2249
|
await this.primerWrapper.destroy();
|
|
2029
2250
|
this._setState('destroyed');
|
|
@@ -2198,6 +2419,7 @@ class CheckoutInstance extends EventEmitter {
|
|
|
2198
2419
|
}
|
|
2199
2420
|
await this.primerWrapper.initializeHeadlessCheckout(this.clientToken, checkoutOptions, method);
|
|
2200
2421
|
const methodInterface = await this.primerWrapper.initMethod(method, element, methodOptions);
|
|
2422
|
+
this.startUnhandledTelemetry(method);
|
|
2201
2423
|
return {
|
|
2202
2424
|
...methodInterface,
|
|
2203
2425
|
destroy: async () => {
|
|
@@ -2206,25 +2428,34 @@ class CheckoutInstance extends EventEmitter {
|
|
|
2206
2428
|
},
|
|
2207
2429
|
};
|
|
2208
2430
|
}
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2431
|
+
startUnhandledTelemetry(paymentMethod) {
|
|
2432
|
+
if (paymentMethod) {
|
|
2433
|
+
this.telemetryPaymentMethod = paymentMethod;
|
|
2434
|
+
}
|
|
2435
|
+
if (!this.isTelemetryEnabled || this.telemetryCleanup) {
|
|
2436
|
+
return;
|
|
2437
|
+
}
|
|
2438
|
+
this.telemetryCleanup = startUnhandledErrorTelemetry({
|
|
2439
|
+
id: this.id,
|
|
2440
|
+
orgId: this.orgId,
|
|
2441
|
+
enabled: this.isTelemetryEnabled,
|
|
2442
|
+
getContext: () => ({
|
|
2443
|
+
checkoutId: this.id,
|
|
2444
|
+
orderId: this.orderId,
|
|
2445
|
+
priceId: this.checkoutConfig.priceId,
|
|
2446
|
+
state: this.state,
|
|
2447
|
+
paymentMethod: this.telemetryPaymentMethod,
|
|
2448
|
+
reqId: this.cachedSessionResponse?.req_id,
|
|
2449
|
+
}),
|
|
2450
|
+
});
|
|
2451
|
+
}
|
|
2452
|
+
stopUnhandledTelemetry() {
|
|
2453
|
+
this.telemetryCleanup?.();
|
|
2454
|
+
this.telemetryCleanup = null;
|
|
2455
|
+
this.telemetryPaymentMethod = undefined;
|
|
2221
2456
|
}
|
|
2222
|
-
const url = `https://billing.funnelfox.com/sdk_report/${encodeURIComponent(orgId)}/crash?${params.toString()}`;
|
|
2223
|
-
const img = new Image();
|
|
2224
|
-
img.src = url;
|
|
2225
|
-
img.style.display = 'none';
|
|
2226
|
-
document.body.appendChild(img);
|
|
2227
2457
|
}
|
|
2458
|
+
CheckoutInstance.sessionCache = new Map();
|
|
2228
2459
|
|
|
2229
2460
|
/**
|
|
2230
2461
|
* @fileoverview Public API with configuration and orchestration logic
|
package/dist/chunk-index.es.js
CHANGED
|
@@ -368,6 +368,9 @@ function generateUUID() {
|
|
|
368
368
|
return v.toString(16);
|
|
369
369
|
});
|
|
370
370
|
}
|
|
371
|
+
function isBrowser() {
|
|
372
|
+
return typeof window !== 'undefined' && typeof document !== 'undefined';
|
|
373
|
+
}
|
|
371
374
|
function sleep(ms) {
|
|
372
375
|
return new Promise(resolve => setTimeout(resolve, ms));
|
|
373
376
|
}
|
|
@@ -486,7 +489,7 @@ var PaymentMethod;
|
|
|
486
489
|
/**
|
|
487
490
|
* @fileoverview Constants for Funnefox SDK
|
|
488
491
|
*/
|
|
489
|
-
const SDK_VERSION = '0.
|
|
492
|
+
const SDK_VERSION = '0.8.0-beta.0';
|
|
490
493
|
const DEFAULTS = {
|
|
491
494
|
BASE_URL: 'https://billing.funnelfox.com',
|
|
492
495
|
REGION: 'default',
|
|
@@ -1493,6 +1496,219 @@ async function loadAirwallexDeviceFingerprint(sessionId, isLivemode = true) {
|
|
|
1493
1496
|
});
|
|
1494
1497
|
}
|
|
1495
1498
|
|
|
1499
|
+
const MAX_QUERY_LENGTH = 1800;
|
|
1500
|
+
function getErrorImage(orgId, options) {
|
|
1501
|
+
if (typeof document === 'undefined') {
|
|
1502
|
+
return;
|
|
1503
|
+
}
|
|
1504
|
+
const params = new URLSearchParams({
|
|
1505
|
+
message: truncate(options.message, 500),
|
|
1506
|
+
code: options.code || 'SDK_ERROR',
|
|
1507
|
+
timestamp: Date.now().toString(),
|
|
1508
|
+
sdk_version: SDK_VERSION,
|
|
1509
|
+
});
|
|
1510
|
+
if (options.req_id) {
|
|
1511
|
+
appendIfFits(params, 'req_id', options.req_id, MAX_QUERY_LENGTH);
|
|
1512
|
+
}
|
|
1513
|
+
if (options.context) {
|
|
1514
|
+
Object.entries(options.context).forEach(([key, value]) => {
|
|
1515
|
+
if (value !== undefined && value !== null) {
|
|
1516
|
+
appendIfFits(params, key, truncate(String(value), 1000), MAX_QUERY_LENGTH);
|
|
1517
|
+
}
|
|
1518
|
+
});
|
|
1519
|
+
}
|
|
1520
|
+
const url = `https://billing.funnelfox.com/sdk_report/${encodeURIComponent(orgId)}/crash?${params.toString()}`;
|
|
1521
|
+
const img = new Image();
|
|
1522
|
+
img.src = url;
|
|
1523
|
+
img.style.display = 'none';
|
|
1524
|
+
img.onload = () => {
|
|
1525
|
+
img.remove();
|
|
1526
|
+
};
|
|
1527
|
+
img.onerror = () => {
|
|
1528
|
+
img.remove();
|
|
1529
|
+
};
|
|
1530
|
+
(document.body || document.documentElement)?.appendChild(img);
|
|
1531
|
+
}
|
|
1532
|
+
function truncate(value, maxLength) {
|
|
1533
|
+
return value.length > maxLength ? value.slice(0, maxLength) : value;
|
|
1534
|
+
}
|
|
1535
|
+
function appendIfFits(params, key, value, maxLength) {
|
|
1536
|
+
const nextParams = new URLSearchParams(params);
|
|
1537
|
+
nextParams.append(key, value);
|
|
1538
|
+
if (nextParams.toString().length > maxLength) {
|
|
1539
|
+
return false;
|
|
1540
|
+
}
|
|
1541
|
+
params.append(key, value);
|
|
1542
|
+
return true;
|
|
1543
|
+
}
|
|
1544
|
+
|
|
1545
|
+
let activeScope = null;
|
|
1546
|
+
const recentSignatures = new Map();
|
|
1547
|
+
const DEDUPE_WINDOW_MS = 3000;
|
|
1548
|
+
let isListening = false;
|
|
1549
|
+
function startUnhandledErrorTelemetry(scope) {
|
|
1550
|
+
if (!scope.enabled || !isBrowser()) {
|
|
1551
|
+
return () => { };
|
|
1552
|
+
}
|
|
1553
|
+
activeScope = scope;
|
|
1554
|
+
attachListeners();
|
|
1555
|
+
return () => {
|
|
1556
|
+
stopUnhandledErrorTelemetry(scope.id);
|
|
1557
|
+
};
|
|
1558
|
+
}
|
|
1559
|
+
function stopUnhandledErrorTelemetry(scopeId) {
|
|
1560
|
+
if (activeScope?.id === scopeId) {
|
|
1561
|
+
activeScope = null;
|
|
1562
|
+
recentSignatures.clear();
|
|
1563
|
+
detachListeners();
|
|
1564
|
+
}
|
|
1565
|
+
}
|
|
1566
|
+
function attachListeners() {
|
|
1567
|
+
if (isListening || typeof window === 'undefined') {
|
|
1568
|
+
return;
|
|
1569
|
+
}
|
|
1570
|
+
window.addEventListener('error', handleWindowError);
|
|
1571
|
+
window.addEventListener('unhandledrejection', handleUnhandledRejection);
|
|
1572
|
+
isListening = true;
|
|
1573
|
+
}
|
|
1574
|
+
function detachListeners() {
|
|
1575
|
+
if (!isListening || typeof window === 'undefined') {
|
|
1576
|
+
return;
|
|
1577
|
+
}
|
|
1578
|
+
window.removeEventListener('error', handleWindowError);
|
|
1579
|
+
window.removeEventListener('unhandledrejection', handleUnhandledRejection);
|
|
1580
|
+
isListening = false;
|
|
1581
|
+
}
|
|
1582
|
+
function handleWindowError(event) {
|
|
1583
|
+
const normalized = normalizeError(event.error || event.message || 'Unhandled browser error', 'UNHANDLED_ERROR');
|
|
1584
|
+
reportToActiveScopes(normalized, {
|
|
1585
|
+
event_type: 'error',
|
|
1586
|
+
filename: event.filename,
|
|
1587
|
+
lineno: event.lineno,
|
|
1588
|
+
colno: event.colno,
|
|
1589
|
+
});
|
|
1590
|
+
}
|
|
1591
|
+
function handleUnhandledRejection(event) {
|
|
1592
|
+
const normalized = normalizeError(event.reason || 'Unhandled promise rejection', 'UNHANDLED_REJECTION');
|
|
1593
|
+
reportToActiveScopes(normalized, {
|
|
1594
|
+
event_type: 'unhandledrejection',
|
|
1595
|
+
});
|
|
1596
|
+
}
|
|
1597
|
+
function reportToActiveScopes(error, eventContext) {
|
|
1598
|
+
if (!activeScope) {
|
|
1599
|
+
return;
|
|
1600
|
+
}
|
|
1601
|
+
if (!canReport(error, eventContext.event_type)) {
|
|
1602
|
+
return;
|
|
1603
|
+
}
|
|
1604
|
+
try {
|
|
1605
|
+
const context = activeScope.getContext();
|
|
1606
|
+
getErrorImage(activeScope.orgId, {
|
|
1607
|
+
message: `${error.name}: ${error.message}`,
|
|
1608
|
+
code: error.code,
|
|
1609
|
+
req_id: context.reqId,
|
|
1610
|
+
context: {
|
|
1611
|
+
...eventContext,
|
|
1612
|
+
checkout_id: context.checkoutId,
|
|
1613
|
+
order_id: context.orderId,
|
|
1614
|
+
price_id: context.priceId,
|
|
1615
|
+
checkout_state: context.state,
|
|
1616
|
+
payment_method: context.paymentMethod,
|
|
1617
|
+
stack: error.stack,
|
|
1618
|
+
page_url: getPageUrl(),
|
|
1619
|
+
},
|
|
1620
|
+
});
|
|
1621
|
+
}
|
|
1622
|
+
catch {
|
|
1623
|
+
// Telemetry must never affect checkout behavior.
|
|
1624
|
+
}
|
|
1625
|
+
}
|
|
1626
|
+
function canReport(error, eventType) {
|
|
1627
|
+
const now = Date.now();
|
|
1628
|
+
pruneExpiredSignatures(now);
|
|
1629
|
+
const signature = buildSignature(error, eventType);
|
|
1630
|
+
const previousReportTime = recentSignatures.get(signature);
|
|
1631
|
+
if (typeof previousReportTime === 'number' &&
|
|
1632
|
+
now - previousReportTime < DEDUPE_WINDOW_MS) {
|
|
1633
|
+
return false;
|
|
1634
|
+
}
|
|
1635
|
+
recentSignatures.set(signature, now);
|
|
1636
|
+
return true;
|
|
1637
|
+
}
|
|
1638
|
+
function pruneExpiredSignatures(now) {
|
|
1639
|
+
recentSignatures.forEach((timestamp, signature) => {
|
|
1640
|
+
if (now - timestamp >= DEDUPE_WINDOW_MS) {
|
|
1641
|
+
recentSignatures.delete(signature);
|
|
1642
|
+
}
|
|
1643
|
+
});
|
|
1644
|
+
}
|
|
1645
|
+
function buildSignature(error, eventType) {
|
|
1646
|
+
const firstStackLine = error.stack?.split('\n')[0] || '';
|
|
1647
|
+
return [
|
|
1648
|
+
eventType || '',
|
|
1649
|
+
error.code || '',
|
|
1650
|
+
error.message || '',
|
|
1651
|
+
firstStackLine,
|
|
1652
|
+
].join('|');
|
|
1653
|
+
}
|
|
1654
|
+
function normalizeError(reason, fallbackCode) {
|
|
1655
|
+
if (reason instanceof Error) {
|
|
1656
|
+
return {
|
|
1657
|
+
name: reason.name || 'Error',
|
|
1658
|
+
message: reason.message || 'Unknown error',
|
|
1659
|
+
code: getErrorCode(reason, fallbackCode),
|
|
1660
|
+
stack: reason.stack,
|
|
1661
|
+
};
|
|
1662
|
+
}
|
|
1663
|
+
if (typeof reason === 'object' && reason !== null) {
|
|
1664
|
+
const record = reason;
|
|
1665
|
+
return {
|
|
1666
|
+
name: toSafeString(record.name) || 'Error',
|
|
1667
|
+
message: toSafeString(record.message) || safeStringify(reason),
|
|
1668
|
+
code: toSafeString(record.code) || fallbackCode,
|
|
1669
|
+
stack: toSafeString(record.stack),
|
|
1670
|
+
};
|
|
1671
|
+
}
|
|
1672
|
+
return {
|
|
1673
|
+
name: 'Error',
|
|
1674
|
+
message: toSafeString(reason) || 'Unknown error',
|
|
1675
|
+
code: fallbackCode,
|
|
1676
|
+
};
|
|
1677
|
+
}
|
|
1678
|
+
function getErrorCode(error, fallbackCode) {
|
|
1679
|
+
const record = error;
|
|
1680
|
+
return typeof record.code === 'string' && record.code
|
|
1681
|
+
? record.code
|
|
1682
|
+
: fallbackCode;
|
|
1683
|
+
}
|
|
1684
|
+
function toSafeString(value) {
|
|
1685
|
+
if (typeof value === 'string') {
|
|
1686
|
+
return value;
|
|
1687
|
+
}
|
|
1688
|
+
if (typeof value === 'number' || typeof value === 'boolean') {
|
|
1689
|
+
return String(value);
|
|
1690
|
+
}
|
|
1691
|
+
return '';
|
|
1692
|
+
}
|
|
1693
|
+
function safeStringify(value) {
|
|
1694
|
+
try {
|
|
1695
|
+
return JSON.stringify(value);
|
|
1696
|
+
}
|
|
1697
|
+
catch {
|
|
1698
|
+
return 'Unserializable error';
|
|
1699
|
+
}
|
|
1700
|
+
}
|
|
1701
|
+
function getPageUrl() {
|
|
1702
|
+
if (typeof window === 'undefined' || !window.location) {
|
|
1703
|
+
return '';
|
|
1704
|
+
}
|
|
1705
|
+
const location = window.location;
|
|
1706
|
+
if (location.origin && location.pathname) {
|
|
1707
|
+
return `${location.origin}${location.pathname}`;
|
|
1708
|
+
}
|
|
1709
|
+
return (location.href || '').split(/[?#]/)[0];
|
|
1710
|
+
}
|
|
1711
|
+
|
|
1496
1712
|
/**
|
|
1497
1713
|
* @fileoverview Checkout instance manager for Funnefox SDK
|
|
1498
1714
|
*/
|
|
@@ -1502,6 +1718,8 @@ class CheckoutInstance extends EventEmitter {
|
|
|
1502
1718
|
this.counter = 0;
|
|
1503
1719
|
this.cachedSessionResponse = null;
|
|
1504
1720
|
this.cardSessionFieldConfig = {};
|
|
1721
|
+
this.isTelemetryEnabled = false;
|
|
1722
|
+
this.telemetryCleanup = null;
|
|
1505
1723
|
this.handleInputChange = (inputName, error) => {
|
|
1506
1724
|
this.emit(EVENTS.INPUT_ERROR, { name: inputName, error });
|
|
1507
1725
|
};
|
|
@@ -1664,6 +1882,7 @@ class CheckoutInstance extends EventEmitter {
|
|
|
1664
1882
|
await this.createSession();
|
|
1665
1883
|
await this._initializePrimerCheckout();
|
|
1666
1884
|
this._setState('ready');
|
|
1885
|
+
this.startUnhandledTelemetry();
|
|
1667
1886
|
this.checkoutConfig?.onInitialized?.();
|
|
1668
1887
|
return this;
|
|
1669
1888
|
}
|
|
@@ -1738,6 +1957,7 @@ class CheckoutInstance extends EventEmitter {
|
|
|
1738
1957
|
sessionResponse = await sessionRequest;
|
|
1739
1958
|
}
|
|
1740
1959
|
this.cachedSessionResponse = sessionResponse;
|
|
1960
|
+
this.isTelemetryEnabled = !!sessionResponse.data?.sdk_telemetry_enabled;
|
|
1741
1961
|
this.isCollectingApplePayEmail =
|
|
1742
1962
|
!!sessionResponse.data?.collect_apple_pay_email;
|
|
1743
1963
|
this.applySessionCardFieldConfig(sessionResponse);
|
|
@@ -2022,6 +2242,7 @@ class CheckoutInstance extends EventEmitter {
|
|
|
2022
2242
|
if (this.isDestroyed)
|
|
2023
2243
|
return;
|
|
2024
2244
|
try {
|
|
2245
|
+
this.stopUnhandledTelemetry();
|
|
2025
2246
|
CheckoutInstance.sessionCache.clear();
|
|
2026
2247
|
await this.primerWrapper.destroy();
|
|
2027
2248
|
this._setState('destroyed');
|
|
@@ -2196,6 +2417,7 @@ class CheckoutInstance extends EventEmitter {
|
|
|
2196
2417
|
}
|
|
2197
2418
|
await this.primerWrapper.initializeHeadlessCheckout(this.clientToken, checkoutOptions, method);
|
|
2198
2419
|
const methodInterface = await this.primerWrapper.initMethod(method, element, methodOptions);
|
|
2420
|
+
this.startUnhandledTelemetry(method);
|
|
2199
2421
|
return {
|
|
2200
2422
|
...methodInterface,
|
|
2201
2423
|
destroy: async () => {
|
|
@@ -2204,25 +2426,34 @@ class CheckoutInstance extends EventEmitter {
|
|
|
2204
2426
|
},
|
|
2205
2427
|
};
|
|
2206
2428
|
}
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2429
|
+
startUnhandledTelemetry(paymentMethod) {
|
|
2430
|
+
if (paymentMethod) {
|
|
2431
|
+
this.telemetryPaymentMethod = paymentMethod;
|
|
2432
|
+
}
|
|
2433
|
+
if (!this.isTelemetryEnabled || this.telemetryCleanup) {
|
|
2434
|
+
return;
|
|
2435
|
+
}
|
|
2436
|
+
this.telemetryCleanup = startUnhandledErrorTelemetry({
|
|
2437
|
+
id: this.id,
|
|
2438
|
+
orgId: this.orgId,
|
|
2439
|
+
enabled: this.isTelemetryEnabled,
|
|
2440
|
+
getContext: () => ({
|
|
2441
|
+
checkoutId: this.id,
|
|
2442
|
+
orderId: this.orderId,
|
|
2443
|
+
priceId: this.checkoutConfig.priceId,
|
|
2444
|
+
state: this.state,
|
|
2445
|
+
paymentMethod: this.telemetryPaymentMethod,
|
|
2446
|
+
reqId: this.cachedSessionResponse?.req_id,
|
|
2447
|
+
}),
|
|
2448
|
+
});
|
|
2449
|
+
}
|
|
2450
|
+
stopUnhandledTelemetry() {
|
|
2451
|
+
this.telemetryCleanup?.();
|
|
2452
|
+
this.telemetryCleanup = null;
|
|
2453
|
+
this.telemetryPaymentMethod = undefined;
|
|
2219
2454
|
}
|
|
2220
|
-
const url = `https://billing.funnelfox.com/sdk_report/${encodeURIComponent(orgId)}/crash?${params.toString()}`;
|
|
2221
|
-
const img = new Image();
|
|
2222
|
-
img.src = url;
|
|
2223
|
-
img.style.display = 'none';
|
|
2224
|
-
document.body.appendChild(img);
|
|
2225
2455
|
}
|
|
2456
|
+
CheckoutInstance.sessionCache = new Map();
|
|
2226
2457
|
|
|
2227
2458
|
/**
|
|
2228
2459
|
* @fileoverview Public API with configuration and orchestration logic
|