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