@01.software/sdk 0.49.0 → 0.56.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/CHANGELOG.md +257 -1
- package/MIGRATION.md +56 -0
- package/README.md +269 -56
- package/dist/client.cjs +608 -190
- package/dist/client.cjs.map +1 -1
- package/dist/client.d.cts +6 -6
- package/dist/client.d.ts +6 -6
- package/dist/client.js +608 -190
- package/dist/client.js.map +1 -1
- package/dist/{collection-client-DnDt6EQz.d.ts → collection-client-Bmr53saa.d.ts} +6 -4
- package/dist/{collection-client-XfGLPuTf.d.cts → collection-client-DxuBDso_.d.cts} +6 -4
- package/dist/{const-CBba4lDo.d.cts → const-BKUqg96Q.d.ts} +5 -5
- package/dist/{const-DCNV5Kq7.d.ts → const-Cv1DCsp6.d.cts} +5 -5
- package/dist/embedded-admin/server.cjs +306 -0
- package/dist/embedded-admin/server.cjs.map +1 -0
- package/dist/embedded-admin/server.d.cts +33 -0
- package/dist/embedded-admin/server.d.ts +33 -0
- package/dist/embedded-admin/server.js +288 -0
- package/dist/embedded-admin/server.js.map +1 -0
- package/dist/embedded-admin.cjs +400 -0
- package/dist/embedded-admin.cjs.map +1 -0
- package/dist/embedded-admin.d.cts +61 -0
- package/dist/embedded-admin.d.ts +61 -0
- package/dist/embedded-admin.js +377 -0
- package/dist/embedded-admin.js.map +1 -0
- package/dist/errors.cjs +1 -0
- package/dist/errors.cjs.map +1 -1
- package/dist/errors.d.cts +3 -3
- package/dist/errors.d.ts +3 -3
- package/dist/errors.js +1 -0
- package/dist/errors.js.map +1 -1
- package/dist/{index-DUuIqUS8.d.ts → index-CGsT4AQ8.d.cts} +89 -10
- package/dist/{index-CbKyoEzy.d.cts → index-DFHLLqYu.d.ts} +89 -10
- package/dist/index.cjs +752 -436
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +12 -69
- package/dist/index.d.ts +12 -69
- package/dist/index.js +752 -436
- package/dist/index.js.map +1 -1
- package/dist/{payload-types-MKbx5MuJ.d.cts → payload-types-Bk-duc5k.d.cts} +545 -141
- package/dist/{payload-types-MKbx5MuJ.d.ts → payload-types-Bk-duc5k.d.ts} +545 -141
- package/dist/query.cjs +108 -71
- package/dist/query.cjs.map +1 -1
- package/dist/query.d.cts +5 -5
- package/dist/query.d.ts +5 -5
- package/dist/query.js +108 -71
- package/dist/query.js.map +1 -1
- package/dist/realtime.d.cts +2 -2
- package/dist/realtime.d.ts +2 -2
- package/dist/server.cjs +502 -175
- package/dist/server.cjs.map +1 -1
- package/dist/server.d.cts +9 -8
- package/dist/server.d.ts +9 -8
- package/dist/server.js +502 -175
- package/dist/server.js.map +1 -1
- package/dist/storefront-cache.cjs +1 -0
- package/dist/storefront-cache.cjs.map +1 -1
- package/dist/storefront-cache.js +1 -0
- package/dist/storefront-cache.js.map +1 -1
- package/dist/{types-1UeOk1Uy.d.cts → types-CEK8uTvU.d.ts} +287 -30
- package/dist/{types-BKr_2avj.d.cts → types-CNdpj2fI.d.cts} +2 -2
- package/dist/{types-L_nh-ED7.d.ts → types-D4mjGFj4.d.ts} +2 -2
- package/dist/{types-Du4s5anw.d.ts → types-Dhk5NX7Z.d.cts} +287 -30
- package/dist/ui/form.d.cts +1 -1
- package/dist/ui/form.d.ts +1 -1
- package/dist/ui/video.d.cts +1 -1
- package/dist/ui/video.d.ts +1 -1
- package/dist/webhook.cjs +135 -80
- package/dist/webhook.cjs.map +1 -1
- package/dist/webhook.d.cts +21 -5
- package/dist/webhook.d.ts +21 -5
- package/dist/webhook.js +135 -80
- package/dist/webhook.js.map +1 -1
- package/package.json +28 -6
package/dist/index.cjs
CHANGED
|
@@ -130,8 +130,6 @@ __export(src_exports, {
|
|
|
130
130
|
projectProductToListingShape: () => projectProductToListingShape,
|
|
131
131
|
resolveFeaturedImagePointer: () => resolveFeaturedImagePointer,
|
|
132
132
|
resolveProductDisplayMedia: () => resolveProductDisplayMedia,
|
|
133
|
-
resolveProductGallery: () => resolveProductGallery,
|
|
134
|
-
resolveProductMedia: () => resolveProductMedia,
|
|
135
133
|
resolveProductSelection: () => resolveProductSelection,
|
|
136
134
|
resolveProductSelectionFromMatrix: () => resolveProductSelectionFromMatrix,
|
|
137
135
|
resolveProductSelectionMedia: () => resolveProductSelectionMedia,
|
|
@@ -243,6 +241,7 @@ var COMMERCE_ERROR_CODES = [
|
|
|
243
241
|
"payment_session_not_cancelable",
|
|
244
242
|
"payment_session_conflict",
|
|
245
243
|
"provider_identity_mismatch",
|
|
244
|
+
"managed_refund_attestation_forbidden",
|
|
246
245
|
"payment_method_unavailable",
|
|
247
246
|
"provider_unavailable",
|
|
248
247
|
"lock_timeout",
|
|
@@ -785,11 +784,11 @@ function parseRetryAfter(response) {
|
|
|
785
784
|
permitsAutomaticRetry: seconds <= MAX_AUTOMATIC_RETRY_AFTER_SECONDS
|
|
786
785
|
};
|
|
787
786
|
}
|
|
788
|
-
function mapResponseToError(status, parsed, details, requestId, retryAfter,
|
|
787
|
+
function mapResponseToError(status, parsed, details, requestId, retryAfter, canonicalCommerceError) {
|
|
789
788
|
const errorDetails = {
|
|
790
789
|
...details,
|
|
791
790
|
...parsed.errors && { errors: parsed.errors },
|
|
792
|
-
|
|
791
|
+
...!canonicalCommerceError && parsed.body && { body: parsed.body }
|
|
793
792
|
};
|
|
794
793
|
const suggestion = getParsedErrorSuggestion(status, parsed);
|
|
795
794
|
const finalize = (error) => {
|
|
@@ -797,7 +796,7 @@ function mapResponseToError(status, parsed, details, requestId, retryAfter, erro
|
|
|
797
796
|
if (retryAfter !== void 0) {
|
|
798
797
|
Object.assign(withRequestId, { retryAfter });
|
|
799
798
|
}
|
|
800
|
-
return
|
|
799
|
+
return canonicalCommerceError ? enhanceCommerceError(withRequestId, parsed.body, requestId) : enhanceNonCommerceError(withRequestId, parsed.reason);
|
|
801
800
|
};
|
|
802
801
|
if (status === 400 || status === 422) {
|
|
803
802
|
return finalize(
|
|
@@ -903,6 +902,7 @@ async function httpFetch(url, options) {
|
|
|
903
902
|
retry,
|
|
904
903
|
onUnauthorized,
|
|
905
904
|
errorFamily,
|
|
905
|
+
typedCommerceErrorReasons,
|
|
906
906
|
...requestInit
|
|
907
907
|
} = options || {};
|
|
908
908
|
const baseUrl = resolveApiUrl(apiUrl);
|
|
@@ -956,7 +956,8 @@ async function httpFetch(url, options) {
|
|
|
956
956
|
});
|
|
957
957
|
if (!response.ok) {
|
|
958
958
|
const parsed = await parseErrorBody(response);
|
|
959
|
-
const
|
|
959
|
+
const canonicalCommerceError = errorFamily === "commerce" || typeof parsed.reason === "string" && typedCommerceErrorReasons?.includes(parsed.reason) === true;
|
|
960
|
+
const commerceEnvelope = canonicalCommerceError ? parseCommerceErrorEnvelope(parsed.body, requestId) : void 0;
|
|
960
961
|
if (isUsageLimitExceededResponse(response)) {
|
|
961
962
|
const limit = parseInt(
|
|
962
963
|
response.headers.get("X-Usage-Limit") || "0",
|
|
@@ -984,7 +985,7 @@ async function httpFetch(url, options) {
|
|
|
984
985
|
),
|
|
985
986
|
requestId
|
|
986
987
|
);
|
|
987
|
-
throw
|
|
988
|
+
throw canonicalCommerceError ? enhanceCommerceError(usageError, parsed.body, requestId) : enhanceNonCommerceError(usageError, parsed.reason);
|
|
988
989
|
}
|
|
989
990
|
if (response.status === 401 && onUnauthorized && customerToken && !hasRetried401 && (commerceEnvelope?.reason ?? parsed.reason) === "token_expired") {
|
|
990
991
|
hasRetried401 = true;
|
|
@@ -1009,7 +1010,7 @@ async function httpFetch(url, options) {
|
|
|
1009
1010
|
details,
|
|
1010
1011
|
requestId,
|
|
1011
1012
|
retryAfter.seconds,
|
|
1012
|
-
|
|
1013
|
+
canonicalCommerceError
|
|
1013
1014
|
);
|
|
1014
1015
|
const method2 = (requestInit.method || "GET").toUpperCase();
|
|
1015
1016
|
const isTerminal = error instanceof RateLimitError || error instanceof UsageLimitError;
|
|
@@ -1187,15 +1188,15 @@ var HttpClient = class {
|
|
|
1187
1188
|
throw err;
|
|
1188
1189
|
}
|
|
1189
1190
|
}
|
|
1190
|
-
buildUrl(
|
|
1191
|
-
if (!options) return
|
|
1191
|
+
buildUrl(endpoint, options) {
|
|
1192
|
+
if (!options) return endpoint;
|
|
1192
1193
|
const { fallbackLocale, ...rest } = options;
|
|
1193
1194
|
const queryOptions = fallbackLocale === void 0 ? rest : {
|
|
1194
1195
|
...rest,
|
|
1195
1196
|
"fallback-locale": fallbackLocale === false ? "none" : fallbackLocale
|
|
1196
1197
|
};
|
|
1197
1198
|
const queryString = (0, import_qs_esm.stringify)(queryOptions, { addQueryPrefix: true });
|
|
1198
|
-
return queryString ? `${
|
|
1199
|
+
return queryString ? `${endpoint}${queryString}` : endpoint;
|
|
1199
1200
|
}
|
|
1200
1201
|
assertJsonResponse(response) {
|
|
1201
1202
|
const contentType = response.headers.get("content-type");
|
|
@@ -1344,39 +1345,39 @@ var ReadOnlyCollectionClient = class extends HttpClient {
|
|
|
1344
1345
|
from(collection) {
|
|
1345
1346
|
return new ReadOnlyCollectionQueryBuilder(this, collection);
|
|
1346
1347
|
}
|
|
1347
|
-
async requestFind(
|
|
1348
|
-
const url = this.buildUrl(
|
|
1348
|
+
async requestFind(endpoint, options) {
|
|
1349
|
+
const url = this.buildUrl(endpoint, options);
|
|
1349
1350
|
const response = await this.fetchWithTracking(url, {
|
|
1350
1351
|
...this.defaultOptions,
|
|
1351
1352
|
method: "GET"
|
|
1352
1353
|
});
|
|
1353
1354
|
return this.parseFindResponse(response);
|
|
1354
1355
|
}
|
|
1355
|
-
async requestFindEndpoint(
|
|
1356
|
-
const response = await this.fetchWithTracking(
|
|
1356
|
+
async requestFindEndpoint(endpoint, data) {
|
|
1357
|
+
const response = await this.fetchWithTracking(endpoint, {
|
|
1357
1358
|
...this.defaultOptions,
|
|
1358
1359
|
method: "POST",
|
|
1359
1360
|
body: data ? JSON.stringify(data) : void 0
|
|
1360
1361
|
});
|
|
1361
1362
|
return this.parseFindResponse(response);
|
|
1362
1363
|
}
|
|
1363
|
-
async requestFindEndpointGet(
|
|
1364
|
-
const response = await this.fetchWithTracking(
|
|
1364
|
+
async requestFindEndpointGet(endpoint) {
|
|
1365
|
+
const response = await this.fetchWithTracking(endpoint, {
|
|
1365
1366
|
...this.defaultOptions,
|
|
1366
1367
|
method: "GET"
|
|
1367
1368
|
});
|
|
1368
1369
|
return this.parseFindResponse(response);
|
|
1369
1370
|
}
|
|
1370
|
-
async requestFindById(
|
|
1371
|
-
const url = this.buildUrl(
|
|
1371
|
+
async requestFindById(endpoint, options) {
|
|
1372
|
+
const url = this.buildUrl(endpoint, options);
|
|
1372
1373
|
const response = await this.fetchWithTracking(url, {
|
|
1373
1374
|
...this.defaultOptions,
|
|
1374
1375
|
method: "GET"
|
|
1375
1376
|
});
|
|
1376
1377
|
return this.parseDocumentResponse(response);
|
|
1377
1378
|
}
|
|
1378
|
-
async requestCount(
|
|
1379
|
-
const url = this.buildUrl(
|
|
1379
|
+
async requestCount(endpoint, options) {
|
|
1380
|
+
const url = this.buildUrl(endpoint, options);
|
|
1380
1381
|
const response = await this.fetchWithTracking(url, {
|
|
1381
1382
|
...this.defaultOptions,
|
|
1382
1383
|
method: "GET"
|
|
@@ -1386,7 +1387,7 @@ var ReadOnlyCollectionClient = class extends HttpClient {
|
|
|
1386
1387
|
};
|
|
1387
1388
|
|
|
1388
1389
|
// src/core/api/parse-response.ts
|
|
1389
|
-
async function parseApiResponse(response,
|
|
1390
|
+
async function parseApiResponse(response, endpoint, options = {}) {
|
|
1390
1391
|
if (response.status === 204 || response.headers.get("content-length") === "0") {
|
|
1391
1392
|
return void 0;
|
|
1392
1393
|
}
|
|
@@ -1395,7 +1396,7 @@ async function parseApiResponse(response, endpoint2, options = {}) {
|
|
|
1395
1396
|
parsed = await response.json();
|
|
1396
1397
|
} catch {
|
|
1397
1398
|
throw createApiError(
|
|
1398
|
-
`Invalid JSON response from ${
|
|
1399
|
+
`Invalid JSON response from ${endpoint}`,
|
|
1399
1400
|
response.status,
|
|
1400
1401
|
void 0,
|
|
1401
1402
|
"Server returned an invalid response.",
|
|
@@ -1445,6 +1446,7 @@ async function parseApiResponse(response, endpoint2, options = {}) {
|
|
|
1445
1446
|
|
|
1446
1447
|
// ../../internal/http-contracts/src/index.ts
|
|
1447
1448
|
var operationBrand = /* @__PURE__ */ Symbol("sdk-http-operation");
|
|
1449
|
+
var PORTONE_STANDARD_ACTION_CAPABILITIES_MEDIA_TYPE = "application/vnd.01software.portone-action-capabilities.v1+json";
|
|
1448
1450
|
var operation = (collection, method, path, errorFamily) => Object.freeze({
|
|
1449
1451
|
[operationBrand]: true,
|
|
1450
1452
|
collection,
|
|
@@ -1453,6 +1455,10 @@ var operation = (collection, method, path, errorFamily) => Object.freeze({
|
|
|
1453
1455
|
...errorFamily && { errorFamily }
|
|
1454
1456
|
});
|
|
1455
1457
|
var strictCommerceOperation = (collection, method, path) => operation(collection, method, path, "commerce");
|
|
1458
|
+
var selectiveCommerceOperation = (collection, method, path, typedCommerceErrorReasons) => Object.freeze({
|
|
1459
|
+
...operation(collection, method, path),
|
|
1460
|
+
typedCommerceErrorReasons: Object.freeze([...typedCommerceErrorReasons])
|
|
1461
|
+
});
|
|
1456
1462
|
var COMMERCE_ENDPOINT_OPERATIONS = {
|
|
1457
1463
|
products: {
|
|
1458
1464
|
detail: operation("products", "get", "/detail"),
|
|
@@ -1484,7 +1490,12 @@ var COMMERCE_ENDPOINT_OPERATIONS = {
|
|
|
1484
1490
|
confirmPayment: operation("orders", "post", "/confirm-payment"),
|
|
1485
1491
|
byPayment: operation("orders", "get", "/by-payment"),
|
|
1486
1492
|
cancel: operation("orders", "post", "/cancel"),
|
|
1487
|
-
resolveCancelRefund:
|
|
1493
|
+
resolveCancelRefund: selectiveCommerceOperation(
|
|
1494
|
+
"orders",
|
|
1495
|
+
"post",
|
|
1496
|
+
"/resolve-cancel-refund",
|
|
1497
|
+
["managed_refund_attestation_forbidden"]
|
|
1498
|
+
),
|
|
1488
1499
|
checkout: strictCommerceOperation("orders", "post", "/checkout"),
|
|
1489
1500
|
createFulfillment: operation("orders", "post", "/create-fulfillment"),
|
|
1490
1501
|
updateFulfillment: operation("orders", "post", "/update-fulfillment")
|
|
@@ -1493,6 +1504,11 @@ var COMMERCE_ENDPOINT_OPERATIONS = {
|
|
|
1493
1504
|
detail: strictCommerceOperation("checkouts", "post", "/detail"),
|
|
1494
1505
|
query: strictCommerceOperation("checkouts", "post", "/query")
|
|
1495
1506
|
},
|
|
1507
|
+
draftOrders: {
|
|
1508
|
+
calculate: operation("draft-orders", "post", "/:id/calculate"),
|
|
1509
|
+
complete: operation("draft-orders", "post", "/:id/complete"),
|
|
1510
|
+
finalize: operation("draft-orders", "post", "/:id/finalize")
|
|
1511
|
+
},
|
|
1496
1512
|
transactions: {
|
|
1497
1513
|
update: operation("transactions", "post", "/update")
|
|
1498
1514
|
},
|
|
@@ -1555,6 +1571,16 @@ var COMMERCE_ENDPOINT_OPERATIONS = {
|
|
|
1555
1571
|
"payment-sessions",
|
|
1556
1572
|
"post",
|
|
1557
1573
|
"/server/confirm"
|
|
1574
|
+
),
|
|
1575
|
+
findPortOne: strictCommerceOperation(
|
|
1576
|
+
"payment-sessions",
|
|
1577
|
+
"post",
|
|
1578
|
+
"/server/portone/find"
|
|
1579
|
+
),
|
|
1580
|
+
reconcilePortOne: strictCommerceOperation(
|
|
1581
|
+
"payment-sessions",
|
|
1582
|
+
"post",
|
|
1583
|
+
"/server/portone/reconcile"
|
|
1558
1584
|
)
|
|
1559
1585
|
},
|
|
1560
1586
|
custom: {
|
|
@@ -1574,12 +1600,34 @@ var COMMERCE_ENDPOINT_OPERATIONS = {
|
|
|
1574
1600
|
validate: operation("discounts", "post", "/validate")
|
|
1575
1601
|
}
|
|
1576
1602
|
};
|
|
1603
|
+
var portOneStandardActionCapabilityOperations = /* @__PURE__ */ new Set([
|
|
1604
|
+
COMMERCE_ENDPOINT_OPERATIONS.paymentSessions.create,
|
|
1605
|
+
COMMERCE_ENDPOINT_OPERATIONS.paymentSessions.retrieve,
|
|
1606
|
+
COMMERCE_ENDPOINT_OPERATIONS.paymentSessions.server.create,
|
|
1607
|
+
COMMERCE_ENDPOINT_OPERATIONS.paymentSessions.server.retrieve
|
|
1608
|
+
]);
|
|
1577
1609
|
var MEMBERSHIP_ENDPOINT_OPERATIONS = {
|
|
1578
1610
|
subscribe: operation(null, "post", "/memberships/subscribe"),
|
|
1579
1611
|
cancel: operation(null, "post", "/memberships/:id/cancel"),
|
|
1580
|
-
me: operation(null, "get", "/memberships/me")
|
|
1612
|
+
me: operation(null, "get", "/memberships/me"),
|
|
1613
|
+
chargeResult: operation(null, "post", "/memberships/:id/charge-result"),
|
|
1614
|
+
refundResult: operation(null, "post", "/memberships/:id/refund-result"),
|
|
1615
|
+
revoke: operation(null, "post", "/memberships/:id/revoke")
|
|
1616
|
+
};
|
|
1617
|
+
var CUSTOMER_GROUP_ENDPOINT_OPERATIONS = {
|
|
1618
|
+
members: operation("customer-groups", "get", "/:id/members"),
|
|
1619
|
+
grant: operation("customer-groups", "post", "/:id/members/grant"),
|
|
1620
|
+
revoke: operation("customer-groups", "post", "/:id/members/revoke")
|
|
1621
|
+
};
|
|
1622
|
+
var EMBEDDED_ADMIN_ENDPOINT_OPERATIONS = {
|
|
1623
|
+
show: operation("tenants", "get", "/embedded-admin-app"),
|
|
1624
|
+
configure: operation("tenants", "patch", "/embedded-admin-app"),
|
|
1625
|
+
disable: operation("tenants", "delete", "/embedded-admin-app")
|
|
1581
1626
|
};
|
|
1582
1627
|
var EVENTS_ENDPOINT_OPERATIONS = {
|
|
1628
|
+
events: {
|
|
1629
|
+
convertSchedule: operation("events", "post", "/:id/schedule")
|
|
1630
|
+
},
|
|
1583
1631
|
occurrences: {
|
|
1584
1632
|
range: operation("event-occurrences", "get", "/range"),
|
|
1585
1633
|
admissionAvailability: operation(
|
|
@@ -1620,46 +1668,51 @@ function resolveOperationMethod(descriptor) {
|
|
|
1620
1668
|
}
|
|
1621
1669
|
|
|
1622
1670
|
// src/core/api/endpoints.ts
|
|
1623
|
-
var endpoint = (method, path) => ({ method, path });
|
|
1624
1671
|
var sharedEndpoint = (descriptor) => descriptor;
|
|
1625
|
-
function resolveOperationMethod2(descriptor) {
|
|
1626
|
-
return "collection" in descriptor ? resolveOperationMethod(descriptor) : descriptor.method;
|
|
1627
|
-
}
|
|
1628
1672
|
function resolveOperationErrorFamily(descriptor) {
|
|
1629
|
-
return
|
|
1630
|
-
}
|
|
1631
|
-
function resolveOperationPath2(descriptor, options) {
|
|
1632
|
-
if ("collection" in descriptor) {
|
|
1633
|
-
return resolveOperationPath(descriptor, options);
|
|
1634
|
-
}
|
|
1635
|
-
const query = options?.query?.toString().replace(/^\?/, "");
|
|
1636
|
-
return query ? `${descriptor.path}?${query}` : descriptor.path;
|
|
1673
|
+
return descriptor.errorFamily;
|
|
1637
1674
|
}
|
|
1638
1675
|
var COMMERCE_ENDPOINTS = {
|
|
1639
1676
|
products: {
|
|
1640
|
-
detail:
|
|
1641
|
-
detailCatalog:
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1677
|
+
detail: sharedEndpoint(COMMERCE_ENDPOINT_OPERATIONS.products.detail),
|
|
1678
|
+
detailCatalog: sharedEndpoint(
|
|
1679
|
+
COMMERCE_ENDPOINT_OPERATIONS.products.detailCatalog
|
|
1680
|
+
),
|
|
1681
|
+
listingGroups: sharedEndpoint(
|
|
1682
|
+
COMMERCE_ENDPOINT_OPERATIONS.products.listingGroups
|
|
1683
|
+
),
|
|
1684
|
+
listingGroupsCatalog: sharedEndpoint(
|
|
1685
|
+
COMMERCE_ENDPOINT_OPERATIONS.products.listingGroupsCatalog
|
|
1686
|
+
),
|
|
1687
|
+
// The manifest keeps the GET read and the POST body-query apart; this
|
|
1688
|
+
// semantic name has always resolved to the GET one.
|
|
1689
|
+
listingGroupsQuery: sharedEndpoint(
|
|
1690
|
+
COMMERCE_ENDPOINT_OPERATIONS.products.listingGroupsQueryGet
|
|
1691
|
+
),
|
|
1692
|
+
listingGroupsQueryCatalog: sharedEndpoint(
|
|
1693
|
+
COMMERCE_ENDPOINT_OPERATIONS.products.listingGroupsQueryCatalog
|
|
1646
1694
|
),
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
"GET",
|
|
1650
|
-
"/api/products/listing-groups/query/catalog"
|
|
1695
|
+
stockCheck: sharedEndpoint(
|
|
1696
|
+
COMMERCE_ENDPOINT_OPERATIONS.products.stockCheck
|
|
1651
1697
|
),
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1698
|
+
stockSnapshot: sharedEndpoint(
|
|
1699
|
+
COMMERCE_ENDPOINT_OPERATIONS.products.stockSnapshot
|
|
1700
|
+
),
|
|
1701
|
+
upsert: sharedEndpoint(COMMERCE_ENDPOINT_OPERATIONS.products.upsert)
|
|
1655
1702
|
},
|
|
1656
1703
|
orders: {
|
|
1657
|
-
update:
|
|
1658
|
-
byPayment:
|
|
1659
|
-
cancel:
|
|
1660
|
-
resolveCancelRefund:
|
|
1661
|
-
|
|
1662
|
-
|
|
1704
|
+
update: sharedEndpoint(COMMERCE_ENDPOINT_OPERATIONS.orders.update),
|
|
1705
|
+
byPayment: sharedEndpoint(COMMERCE_ENDPOINT_OPERATIONS.orders.byPayment),
|
|
1706
|
+
cancel: sharedEndpoint(COMMERCE_ENDPOINT_OPERATIONS.orders.cancel),
|
|
1707
|
+
resolveCancelRefund: sharedEndpoint(
|
|
1708
|
+
COMMERCE_ENDPOINT_OPERATIONS.orders.resolveCancelRefund
|
|
1709
|
+
),
|
|
1710
|
+
createFulfillment: sharedEndpoint(
|
|
1711
|
+
COMMERCE_ENDPOINT_OPERATIONS.orders.createFulfillment
|
|
1712
|
+
),
|
|
1713
|
+
updateFulfillment: sharedEndpoint(
|
|
1714
|
+
COMMERCE_ENDPOINT_OPERATIONS.orders.updateFulfillment
|
|
1715
|
+
)
|
|
1663
1716
|
},
|
|
1664
1717
|
checkouts: {
|
|
1665
1718
|
create: sharedEndpoint(COMMERCE_ENDPOINT_OPERATIONS.orders.checkout),
|
|
@@ -1671,18 +1724,21 @@ var COMMERCE_ENDPOINTS = {
|
|
|
1671
1724
|
}
|
|
1672
1725
|
},
|
|
1673
1726
|
returns: {
|
|
1674
|
-
create:
|
|
1675
|
-
update:
|
|
1676
|
-
returnRefund:
|
|
1727
|
+
create: sharedEndpoint(COMMERCE_ENDPOINT_OPERATIONS.returns.create),
|
|
1728
|
+
update: sharedEndpoint(COMMERCE_ENDPOINT_OPERATIONS.returns.update),
|
|
1729
|
+
returnRefund: sharedEndpoint(
|
|
1730
|
+
COMMERCE_ENDPOINT_OPERATIONS.returns.returnRefund
|
|
1731
|
+
)
|
|
1677
1732
|
},
|
|
1678
1733
|
fulfillmentOrders: {
|
|
1679
|
-
prepare:
|
|
1680
|
-
|
|
1681
|
-
"/api/fulfillment-orders/prepare-fulfillment-order"
|
|
1734
|
+
prepare: sharedEndpoint(
|
|
1735
|
+
COMMERCE_ENDPOINT_OPERATIONS.fulfillmentOrders.prepare
|
|
1682
1736
|
)
|
|
1683
1737
|
},
|
|
1684
1738
|
fulfillments: {
|
|
1685
|
-
bulkImport:
|
|
1739
|
+
bulkImport: sharedEndpoint(
|
|
1740
|
+
COMMERCE_ENDPOINT_OPERATIONS.fulfillments.bulkImport
|
|
1741
|
+
)
|
|
1686
1742
|
},
|
|
1687
1743
|
carts: {
|
|
1688
1744
|
create: sharedEndpoint(COMMERCE_ENDPOINT_OPERATIONS.carts.create),
|
|
@@ -1747,42 +1803,21 @@ var COMMERCE_ENDPOINTS = {
|
|
|
1747
1803
|
),
|
|
1748
1804
|
confirm: sharedEndpoint(
|
|
1749
1805
|
COMMERCE_ENDPOINT_OPERATIONS.paymentSessions.server.confirm
|
|
1806
|
+
),
|
|
1807
|
+
findPortOne: sharedEndpoint(
|
|
1808
|
+
COMMERCE_ENDPOINT_OPERATIONS.paymentSessions.server.findPortOne
|
|
1809
|
+
),
|
|
1810
|
+
reconcilePortOne: sharedEndpoint(
|
|
1811
|
+
COMMERCE_ENDPOINT_OPERATIONS.paymentSessions.server.reconcilePortOne
|
|
1750
1812
|
)
|
|
1751
1813
|
}
|
|
1752
1814
|
},
|
|
1753
1815
|
shippingPolicies: {
|
|
1754
|
-
calculate:
|
|
1816
|
+
calculate: sharedEndpoint(
|
|
1817
|
+
COMMERCE_ENDPOINT_OPERATIONS.shippingPolicies.calculate
|
|
1818
|
+
)
|
|
1755
1819
|
}
|
|
1756
1820
|
};
|
|
1757
|
-
function isCommerceEndpointDescriptor(value) {
|
|
1758
|
-
if (!value || typeof value !== "object") return false;
|
|
1759
|
-
const candidate = value;
|
|
1760
|
-
return typeof candidate.method === "string" && typeof candidate.path === "string";
|
|
1761
|
-
}
|
|
1762
|
-
function flattenEndpointPaths(value) {
|
|
1763
|
-
if (isCommerceEndpointDescriptor(value)) return [resolveOperationPath2(value)];
|
|
1764
|
-
if (!value || typeof value !== "object") return [];
|
|
1765
|
-
return Object.values(value).flatMap((entry) => flattenEndpointPaths(entry));
|
|
1766
|
-
}
|
|
1767
|
-
function flattenEndpointDescriptors(value) {
|
|
1768
|
-
if (isCommerceEndpointDescriptor(value)) return [value];
|
|
1769
|
-
if (!value || typeof value !== "object") return [];
|
|
1770
|
-
return Object.values(value).flatMap(
|
|
1771
|
-
(entry) => flattenEndpointDescriptors(entry)
|
|
1772
|
-
);
|
|
1773
|
-
}
|
|
1774
|
-
var COMMERCE_ENDPOINT_PATHS = flattenEndpointPaths(COMMERCE_ENDPOINTS);
|
|
1775
|
-
var COMMERCE_ENDPOINT_DESCRIPTORS = flattenEndpointDescriptors(COMMERCE_ENDPOINTS);
|
|
1776
|
-
var MEMBERSHIP_ENDPOINTS = {
|
|
1777
|
-
subscribe: "/api/memberships/subscribe",
|
|
1778
|
-
cancel: (id) => `/api/memberships/${id}/cancel`,
|
|
1779
|
-
me: "/api/memberships/me"
|
|
1780
|
-
};
|
|
1781
|
-
var MEMBERSHIP_ENDPOINT_PATHS = [
|
|
1782
|
-
MEMBERSHIP_ENDPOINTS.subscribe,
|
|
1783
|
-
MEMBERSHIP_ENDPOINTS.cancel(":id"),
|
|
1784
|
-
MEMBERSHIP_ENDPOINTS.me
|
|
1785
|
-
];
|
|
1786
1821
|
|
|
1787
1822
|
// src/core/api/customer-scoped-api.ts
|
|
1788
1823
|
var CustomerScopedTransport = class {
|
|
@@ -1803,10 +1838,10 @@ var CustomerScopedTransport = class {
|
|
|
1803
1838
|
this.onUnauthorized = options.onUnauthorized;
|
|
1804
1839
|
this.onRequestId = options.onRequestId;
|
|
1805
1840
|
}
|
|
1806
|
-
async request(
|
|
1841
|
+
async request(endpoint, options) {
|
|
1807
1842
|
const token = typeof this.customerToken === "function" ? this.customerToken() : this.customerToken;
|
|
1808
1843
|
try {
|
|
1809
|
-
const response = await httpFetch(
|
|
1844
|
+
const response = await httpFetch(endpoint, {
|
|
1810
1845
|
method: options.method,
|
|
1811
1846
|
apiUrl: this.apiUrl,
|
|
1812
1847
|
publishableKey: this.publishableKey,
|
|
@@ -1820,7 +1855,7 @@ var CustomerScopedTransport = class {
|
|
|
1820
1855
|
...options.errorFamily && { errorFamily: options.errorFamily }
|
|
1821
1856
|
});
|
|
1822
1857
|
this.onRequestId?.(response.headers.get("x-request-id") ?? null);
|
|
1823
|
-
return parseApiResponse(response,
|
|
1858
|
+
return parseApiResponse(response, endpoint, {
|
|
1824
1859
|
errorFamily: options.errorFamily
|
|
1825
1860
|
});
|
|
1826
1861
|
} catch (err) {
|
|
@@ -1834,12 +1869,12 @@ var CustomerScopedApi = class {
|
|
|
1834
1869
|
constructor(apiName, options) {
|
|
1835
1870
|
this.transport = new CustomerScopedTransport(apiName, options);
|
|
1836
1871
|
}
|
|
1837
|
-
requestEndpoint(
|
|
1838
|
-
return this.transport.request(
|
|
1839
|
-
method:
|
|
1872
|
+
requestEndpoint(endpoint, body, options) {
|
|
1873
|
+
return this.transport.request(resolveOperationPath(endpoint, options), {
|
|
1874
|
+
method: resolveOperationMethod(endpoint),
|
|
1840
1875
|
body,
|
|
1841
1876
|
headers: options?.headers,
|
|
1842
|
-
errorFamily: resolveOperationErrorFamily(
|
|
1877
|
+
errorFamily: resolveOperationErrorFamily(endpoint)
|
|
1843
1878
|
});
|
|
1844
1879
|
}
|
|
1845
1880
|
};
|
|
@@ -1847,8 +1882,8 @@ var RawCustomerScopedApi = class {
|
|
|
1847
1882
|
constructor(apiName, options) {
|
|
1848
1883
|
this.transport = new CustomerScopedTransport(apiName, options);
|
|
1849
1884
|
}
|
|
1850
|
-
request(
|
|
1851
|
-
return this.transport.request(
|
|
1885
|
+
request(endpoint, options) {
|
|
1886
|
+
return this.transport.request(endpoint, options);
|
|
1852
1887
|
}
|
|
1853
1888
|
};
|
|
1854
1889
|
|
|
@@ -1923,11 +1958,11 @@ var CommunityClient = class extends RawCustomerScopedApi {
|
|
|
1923
1958
|
if (params.page !== void 0) urlParams.set("page", String(params.page));
|
|
1924
1959
|
return `/api/comments/public?${urlParams.toString()}`;
|
|
1925
1960
|
}
|
|
1926
|
-
async execute(
|
|
1927
|
-
return this.request(
|
|
1961
|
+
async execute(endpoint, method, body) {
|
|
1962
|
+
return this.request(endpoint, { method, body });
|
|
1928
1963
|
}
|
|
1929
|
-
async executeDoc(
|
|
1930
|
-
const response = await this.execute(
|
|
1964
|
+
async executeDoc(endpoint, method, body) {
|
|
1965
|
+
const response = await this.execute(endpoint, method, body);
|
|
1931
1966
|
return unwrapPayloadDoc(response);
|
|
1932
1967
|
}
|
|
1933
1968
|
createPost(params) {
|
|
@@ -2193,8 +2228,8 @@ var CommunityClient = class extends RawCustomerScopedApi {
|
|
|
2193
2228
|
|
|
2194
2229
|
// src/core/content/content-client.ts
|
|
2195
2230
|
var PublicContentApi = class extends RawCustomerScopedApi {
|
|
2196
|
-
get(
|
|
2197
|
-
return this.request(
|
|
2231
|
+
get(endpoint) {
|
|
2232
|
+
return this.request(endpoint, { method: "GET" });
|
|
2198
2233
|
}
|
|
2199
2234
|
};
|
|
2200
2235
|
function appendCommonParams(search, options, defaultSort) {
|
|
@@ -2292,7 +2327,10 @@ var ORDER_PRIMARY_DISPLAY_STATUSES = [
|
|
|
2292
2327
|
];
|
|
2293
2328
|
var CHECKOUT_STATUSES = ["open", "placed", "abandoned"];
|
|
2294
2329
|
var ABANDONED_REASONS = ["expired", "customer", "operator"];
|
|
2295
|
-
|
|
2330
|
+
function managedPaymentProviders(providers) {
|
|
2331
|
+
return providers;
|
|
2332
|
+
}
|
|
2333
|
+
var PAYMENT_PROVIDERS = managedPaymentProviders(["toss", "portone"]);
|
|
2296
2334
|
var PAYMENT_PROVIDER_STATUSES = [
|
|
2297
2335
|
"requires_payment_method",
|
|
2298
2336
|
"requires_action",
|
|
@@ -2340,6 +2378,26 @@ function invalidCanonicalResponse(resource, details) {
|
|
|
2340
2378
|
function fail(resource, path, expected) {
|
|
2341
2379
|
invalidCanonicalResponse(resource, { path, expected });
|
|
2342
2380
|
}
|
|
2381
|
+
function providerMismatch(resource, expected, actual, operation2) {
|
|
2382
|
+
if (operation2 === "cancel") {
|
|
2383
|
+
const method = {
|
|
2384
|
+
toss: "cancel()",
|
|
2385
|
+
portone: "cancelPortOne()"
|
|
2386
|
+
}[actual];
|
|
2387
|
+
throw createApiError(
|
|
2388
|
+
`Canonical ${resource} response provider mismatch: expected ${expected}, received ${actual}.`,
|
|
2389
|
+
502,
|
|
2390
|
+
{ resource, path: "$.provider", expected, actual },
|
|
2391
|
+
`The server applied the cancellation to a ${actual} PaymentSession, but this SDK method expects ${expected}.`,
|
|
2392
|
+
`Re-invoke ${method} with the same idempotencyKey; do not create a new key.`
|
|
2393
|
+
);
|
|
2394
|
+
}
|
|
2395
|
+
invalidCanonicalResponse(resource, {
|
|
2396
|
+
path: "$.provider",
|
|
2397
|
+
expected,
|
|
2398
|
+
actual
|
|
2399
|
+
});
|
|
2400
|
+
}
|
|
2343
2401
|
function record(value, resource, path) {
|
|
2344
2402
|
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
2345
2403
|
fail(resource, path, "object");
|
|
@@ -2375,6 +2433,9 @@ function nullableString(value, resource, path) {
|
|
|
2375
2433
|
fail(resource, path, "string or null");
|
|
2376
2434
|
}
|
|
2377
2435
|
}
|
|
2436
|
+
function nullableNonEmptyString(value, resource, path) {
|
|
2437
|
+
if (value !== null) string(value, resource, path);
|
|
2438
|
+
}
|
|
2378
2439
|
function cartLineQuantity(value, resource, path) {
|
|
2379
2440
|
const quantity = positiveInteger(value, resource, path);
|
|
2380
2441
|
if (quantity > MAX_CART_LINE_QUANTITY) {
|
|
@@ -2884,8 +2945,8 @@ function validatePublicOrderItem(value, resource, path) {
|
|
|
2884
2945
|
validateReference(item.variant, resource, `${path}.variant`);
|
|
2885
2946
|
}
|
|
2886
2947
|
string(item.productTitle, resource, `${path}.productTitle`);
|
|
2887
|
-
|
|
2888
|
-
|
|
2948
|
+
nullableNonEmptyString(item.variantTitle, resource, `${path}.variantTitle`);
|
|
2949
|
+
nullableNonEmptyString(item.sku, resource, `${path}.sku`);
|
|
2889
2950
|
validateAdmissions(item.admissions, resource, `${path}.admissions`);
|
|
2890
2951
|
const quantity = positiveInteger(item.quantity, resource, `${path}.quantity`);
|
|
2891
2952
|
const unitPrice = safeMinor(
|
|
@@ -3136,8 +3197,8 @@ function validateCheckoutItem(value, resource, path) {
|
|
|
3136
3197
|
validateReference(item.product, resource, `${path}.product`);
|
|
3137
3198
|
validateReference(item.variant, resource, `${path}.variant`);
|
|
3138
3199
|
string(item.productTitle, resource, `${path}.productTitle`);
|
|
3139
|
-
|
|
3140
|
-
|
|
3200
|
+
nullableNonEmptyString(item.variantTitle, resource, `${path}.variantTitle`);
|
|
3201
|
+
nullableNonEmptyString(item.sku, resource, `${path}.sku`);
|
|
3141
3202
|
validateAdmissions(item.admissions, resource, `${path}.admissions`);
|
|
3142
3203
|
cartLineQuantity(item.quantity, resource, `${path}.quantity`);
|
|
3143
3204
|
const unitPrice = safeMinor(
|
|
@@ -3469,11 +3530,21 @@ function validateNextAction(value, provider, resource, path) {
|
|
|
3469
3530
|
const action = record(value, resource, path);
|
|
3470
3531
|
if (provider === "portone") {
|
|
3471
3532
|
if (action.type === "portone_sdk") {
|
|
3533
|
+
const hasCancelable = "cancelable" in action;
|
|
3534
|
+
const hasRetryPolicy = "retryPolicy" in action;
|
|
3535
|
+
if (hasCancelable !== hasRetryPolicy) {
|
|
3536
|
+
fail(
|
|
3537
|
+
resource,
|
|
3538
|
+
path,
|
|
3539
|
+
"exact released action or exact negotiated capability action"
|
|
3540
|
+
);
|
|
3541
|
+
}
|
|
3472
3542
|
exact(
|
|
3473
3543
|
action,
|
|
3474
3544
|
[
|
|
3475
3545
|
"type",
|
|
3476
3546
|
"version",
|
|
3547
|
+
...hasCancelable ? ["cancelable", "retryPolicy"] : [],
|
|
3477
3548
|
"storeId",
|
|
3478
3549
|
"channelKey",
|
|
3479
3550
|
"paymentId",
|
|
@@ -3488,6 +3559,11 @@ function validateNextAction(value, provider, resource, path) {
|
|
|
3488
3559
|
path
|
|
3489
3560
|
);
|
|
3490
3561
|
if (action.version !== "v2") fail(resource, `${path}.version`, "v2");
|
|
3562
|
+
if (hasCancelable && action.cancelable !== false)
|
|
3563
|
+
fail(resource, `${path}.cancelable`, "false");
|
|
3564
|
+
if (hasRetryPolicy && action.retryPolicy !== "same_action_only") {
|
|
3565
|
+
fail(resource, `${path}.retryPolicy`, "same_action_only");
|
|
3566
|
+
}
|
|
3491
3567
|
for (const key of ["storeId", "channelKey"]) {
|
|
3492
3568
|
boundedString(action[key], 255, resource, `${path}.${key}`);
|
|
3493
3569
|
}
|
|
@@ -3534,6 +3610,10 @@ function validateNextAction(value, provider, resource, path) {
|
|
|
3534
3610
|
"HTTPS or loopback URL without embedded credentials"
|
|
3535
3611
|
);
|
|
3536
3612
|
}
|
|
3613
|
+
if (!hasCancelable) {
|
|
3614
|
+
action.cancelable = false;
|
|
3615
|
+
action.retryPolicy = "same_action_only";
|
|
3616
|
+
}
|
|
3537
3617
|
return;
|
|
3538
3618
|
}
|
|
3539
3619
|
exact(action, ["type", "checkoutUrl"], [], resource, path);
|
|
@@ -3610,7 +3690,7 @@ function validateNextAction(value, provider, resource, path) {
|
|
|
3610
3690
|
if (action.customerEmail !== void 0)
|
|
3611
3691
|
validateCustomer({ email: action.customerEmail }, resource, path);
|
|
3612
3692
|
}
|
|
3613
|
-
function validatePublicPaymentSession(value, expectedProvider) {
|
|
3693
|
+
function validatePublicPaymentSession(value, expectedProvider, operation2) {
|
|
3614
3694
|
const resource = "PaymentSession";
|
|
3615
3695
|
const session = record(value, resource, "$");
|
|
3616
3696
|
const provider = enumValue(
|
|
@@ -3619,9 +3699,6 @@ function validatePublicPaymentSession(value, expectedProvider) {
|
|
|
3619
3699
|
resource,
|
|
3620
3700
|
"$.provider"
|
|
3621
3701
|
);
|
|
3622
|
-
if (expectedProvider !== void 0 && provider !== expectedProvider) {
|
|
3623
|
-
fail(resource, "$.provider", expectedProvider);
|
|
3624
|
-
}
|
|
3625
3702
|
const status = enumValue(
|
|
3626
3703
|
session.providerStatus,
|
|
3627
3704
|
PAYMENT_PROVIDER_STATUSES,
|
|
@@ -3825,44 +3902,128 @@ function validatePublicPaymentSession(value, expectedProvider) {
|
|
|
3825
3902
|
"$.cancellationReason"
|
|
3826
3903
|
);
|
|
3827
3904
|
}
|
|
3905
|
+
if (expectedProvider !== void 0 && provider !== expectedProvider) {
|
|
3906
|
+
if (operation2 === "cancel" && status !== "canceling" && status !== "canceled") {
|
|
3907
|
+
fail(
|
|
3908
|
+
resource,
|
|
3909
|
+
"$.providerStatus",
|
|
3910
|
+
"canceling or canceled after a successful cancellation"
|
|
3911
|
+
);
|
|
3912
|
+
}
|
|
3913
|
+
providerMismatch(resource, expectedProvider, provider, operation2);
|
|
3914
|
+
}
|
|
3828
3915
|
return value;
|
|
3829
3916
|
}
|
|
3830
3917
|
|
|
3831
3918
|
// src/core/customer/customer-auth.ts
|
|
3832
3919
|
var DEFAULT_TIMEOUT2 = 15e3;
|
|
3833
|
-
|
|
3920
|
+
var REFRESH_LOCK_TIMEOUT = DEFAULT_TIMEOUT2 * 3;
|
|
3921
|
+
var SESSION_RECORD_VERSION = 1;
|
|
3922
|
+
var STORAGE_UNUSABLE = /* @__PURE__ */ Symbol("storage-unusable");
|
|
3923
|
+
function canonicalApiIdentity(apiUrl) {
|
|
3834
3924
|
try {
|
|
3835
|
-
|
|
3925
|
+
const url = new URL(apiUrl);
|
|
3926
|
+
return `${url.protocol.toLowerCase()}//${url.host.toLowerCase()}${url.pathname.replace(/\/+$/, "")}`;
|
|
3927
|
+
} catch {
|
|
3928
|
+
return apiUrl.trim().toLowerCase().replace(/\/+$/, "");
|
|
3929
|
+
}
|
|
3930
|
+
}
|
|
3931
|
+
function buildRefreshLockName(apiUrl, publishableKey, storageKey) {
|
|
3932
|
+
return `01software:customer-auth-refresh:${canonicalApiIdentity(apiUrl)}:${publishableKey}:${storageKey}`;
|
|
3933
|
+
}
|
|
3934
|
+
function getRefreshLockRequester() {
|
|
3935
|
+
try {
|
|
3936
|
+
if (typeof navigator === "undefined") return null;
|
|
3937
|
+
const locks = navigator.locks;
|
|
3938
|
+
if (!locks || typeof locks.request !== "function") return null;
|
|
3939
|
+
return (name, options, callback) => locks.request(
|
|
3940
|
+
name,
|
|
3941
|
+
options,
|
|
3942
|
+
() => callback()
|
|
3943
|
+
);
|
|
3836
3944
|
} catch {
|
|
3837
3945
|
return null;
|
|
3838
3946
|
}
|
|
3839
3947
|
}
|
|
3948
|
+
function parseSessionRecord(raw) {
|
|
3949
|
+
try {
|
|
3950
|
+
const parsed = JSON.parse(raw);
|
|
3951
|
+
if (!parsed || typeof parsed !== "object") return null;
|
|
3952
|
+
const candidate = parsed;
|
|
3953
|
+
if (candidate.v !== SESSION_RECORD_VERSION) return null;
|
|
3954
|
+
if (typeof candidate.rev !== "number" || !Number.isFinite(candidate.rev)) {
|
|
3955
|
+
return null;
|
|
3956
|
+
}
|
|
3957
|
+
const token = candidate.token;
|
|
3958
|
+
const refreshToken = candidate.refreshToken;
|
|
3959
|
+
if (token !== null && typeof token !== "string") return null;
|
|
3960
|
+
if (refreshToken !== null && typeof refreshToken !== "string") return null;
|
|
3961
|
+
return {
|
|
3962
|
+
v: SESSION_RECORD_VERSION,
|
|
3963
|
+
rev: candidate.rev,
|
|
3964
|
+
token,
|
|
3965
|
+
refreshToken
|
|
3966
|
+
};
|
|
3967
|
+
} catch {
|
|
3968
|
+
return null;
|
|
3969
|
+
}
|
|
3970
|
+
}
|
|
3971
|
+
function readSessionRecord(recordKey, mirrorKey, mirrorRefreshKey) {
|
|
3972
|
+
let raw;
|
|
3973
|
+
try {
|
|
3974
|
+
raw = localStorage.getItem(recordKey);
|
|
3975
|
+
} catch {
|
|
3976
|
+
return STORAGE_UNUSABLE;
|
|
3977
|
+
}
|
|
3978
|
+
if (raw !== null) {
|
|
3979
|
+
const parsed = parseSessionRecord(raw);
|
|
3980
|
+
return parsed ? { record: parsed, stored: true } : STORAGE_UNUSABLE;
|
|
3981
|
+
}
|
|
3982
|
+
try {
|
|
3983
|
+
return {
|
|
3984
|
+
record: {
|
|
3985
|
+
v: SESSION_RECORD_VERSION,
|
|
3986
|
+
rev: 0,
|
|
3987
|
+
token: localStorage.getItem(mirrorKey),
|
|
3988
|
+
refreshToken: localStorage.getItem(mirrorRefreshKey)
|
|
3989
|
+
},
|
|
3990
|
+
stored: false
|
|
3991
|
+
};
|
|
3992
|
+
} catch {
|
|
3993
|
+
return STORAGE_UNUSABLE;
|
|
3994
|
+
}
|
|
3995
|
+
}
|
|
3840
3996
|
var CustomerAuth = class {
|
|
3841
3997
|
constructor(publishableKey, options, apiUrl) {
|
|
3842
3998
|
this.refreshPromise = null;
|
|
3999
|
+
/** Canonical record key; null unless persistence is active in a browser. */
|
|
4000
|
+
this.persistRecordKey = null;
|
|
4001
|
+
/** Advisory single-value access mirror, plus the retired refresh mirror. */
|
|
4002
|
+
this.persistMirrorKey = null;
|
|
4003
|
+
this.persistMirrorRefreshKey = null;
|
|
4004
|
+
/** Cross-context refresh lock name; null when there is no shared storage. */
|
|
4005
|
+
this.refreshLockName = null;
|
|
4006
|
+
/** Revision of the canonical record this instance last observed. */
|
|
4007
|
+
this.sessionRev = 0;
|
|
3843
4008
|
this.publishableKey = publishableKey;
|
|
3844
4009
|
this.baseUrl = resolveApiUrl(apiUrl);
|
|
3845
4010
|
const persist = options?.persist ?? true;
|
|
3846
4011
|
if (persist) {
|
|
3847
4012
|
const key = typeof persist === "string" ? persist : "customer-token";
|
|
3848
|
-
const refreshKey = `${key}:refresh`;
|
|
3849
4013
|
const isBrowser = typeof window !== "undefined";
|
|
3850
|
-
this.token =
|
|
3851
|
-
this.refreshTokenValue =
|
|
3852
|
-
|
|
3853
|
-
|
|
3854
|
-
|
|
3855
|
-
|
|
3856
|
-
|
|
3857
|
-
|
|
3858
|
-
|
|
3859
|
-
|
|
3860
|
-
|
|
3861
|
-
|
|
3862
|
-
|
|
3863
|
-
} catch {
|
|
3864
|
-
}
|
|
3865
|
-
} : void 0;
|
|
4014
|
+
this.token = null;
|
|
4015
|
+
this.refreshTokenValue = null;
|
|
4016
|
+
if (isBrowser) {
|
|
4017
|
+
this.persistRecordKey = `${key}:session`;
|
|
4018
|
+
this.persistMirrorKey = key;
|
|
4019
|
+
this.persistMirrorRefreshKey = `${key}:refresh`;
|
|
4020
|
+
this.refreshLockName = buildRefreshLockName(
|
|
4021
|
+
this.baseUrl,
|
|
4022
|
+
publishableKey,
|
|
4023
|
+
key
|
|
4024
|
+
);
|
|
4025
|
+
this.syncFromPersistedStorage();
|
|
4026
|
+
}
|
|
3866
4027
|
} else {
|
|
3867
4028
|
this.token = options?.token ?? null;
|
|
3868
4029
|
this.refreshTokenValue = options?.refreshToken ?? null;
|
|
@@ -3890,66 +4051,299 @@ var CustomerAuth = class {
|
|
|
3890
4051
|
body: JSON.stringify(data)
|
|
3891
4052
|
}
|
|
3892
4053
|
);
|
|
3893
|
-
this.
|
|
3894
|
-
|
|
4054
|
+
if (result.refreshToken) this.setSession(result.token, result.refreshToken);
|
|
4055
|
+
else this.setToken(result.token);
|
|
3895
4056
|
return result;
|
|
3896
4057
|
}
|
|
3897
4058
|
/**
|
|
3898
4059
|
* Refresh the current token. Uses a refresh token when available, otherwise
|
|
3899
4060
|
* falls back to the legacy bearer-token compatibility path.
|
|
4061
|
+
*
|
|
4062
|
+
* Refresh tokens are single-use, so a persisted session shared by several
|
|
4063
|
+
* tabs or SDK instances must rotate at most once: concurrent rotations of the
|
|
4064
|
+
* same token are replay from the server's point of view and revoke the whole
|
|
4065
|
+
* family. Persisted browser sessions therefore serialize rotation across
|
|
4066
|
+
* contexts and re-read the canonical persisted credentials inside the lock.
|
|
3900
4067
|
*/
|
|
3901
4068
|
async refreshToken() {
|
|
4069
|
+
this.syncFromPersistedStorage();
|
|
3902
4070
|
if (!this.refreshTokenValue && !this.token) {
|
|
3903
4071
|
throw new ApiError("Not authenticated", 401);
|
|
3904
4072
|
}
|
|
3905
4073
|
if (this.refreshPromise) return this.refreshPromise;
|
|
3906
|
-
|
|
4074
|
+
const intended = this.currentFence();
|
|
4075
|
+
this.refreshPromise = this.rotateAcrossContexts(intended);
|
|
3907
4076
|
try {
|
|
3908
4077
|
return await this.refreshPromise;
|
|
3909
4078
|
} finally {
|
|
3910
4079
|
this.refreshPromise = null;
|
|
3911
4080
|
}
|
|
3912
4081
|
}
|
|
3913
|
-
|
|
3914
|
-
|
|
4082
|
+
/**
|
|
4083
|
+
* Re-read the canonical persisted session. In persisted mode the shared
|
|
4084
|
+
* record — not this instance's memory — is the source of truth, so another
|
|
4085
|
+
* context's rotation, login, or logout is observed before the next credential
|
|
4086
|
+
* use. Change callbacks are deliberately not invoked: this is a read.
|
|
4087
|
+
*/
|
|
4088
|
+
syncFromPersistedStorage() {
|
|
4089
|
+
const canonical = this.readCanonicalSession();
|
|
4090
|
+
if (canonical === STORAGE_UNUSABLE) {
|
|
4091
|
+
this.disablePersistence();
|
|
4092
|
+
return;
|
|
4093
|
+
}
|
|
4094
|
+
if (!canonical) return;
|
|
4095
|
+
this.sessionRev = canonical.record.rev;
|
|
4096
|
+
this.token = canonical.record.token;
|
|
4097
|
+
this.refreshTokenValue = canonical.record.refreshToken;
|
|
4098
|
+
}
|
|
4099
|
+
/** `null` when this instance does not persist. */
|
|
4100
|
+
readCanonicalSession() {
|
|
4101
|
+
if (!this.persistRecordKey || !this.persistMirrorKey || !this.persistMirrorRefreshKey) {
|
|
4102
|
+
return null;
|
|
4103
|
+
}
|
|
4104
|
+
return readSessionRecord(
|
|
4105
|
+
this.persistRecordKey,
|
|
4106
|
+
this.persistMirrorKey,
|
|
4107
|
+
this.persistMirrorRefreshKey
|
|
4108
|
+
);
|
|
4109
|
+
}
|
|
4110
|
+
/**
|
|
4111
|
+
* Publish a change to canonical storage.
|
|
4112
|
+
*
|
|
4113
|
+
* The canonical pair is re-read immediately before the single record write so
|
|
4114
|
+
* that a field this call does not change is taken from that read rather than
|
|
4115
|
+
* from this instance's possibly stale copy. That merge is BEST-EFFORT: it
|
|
4116
|
+
* removes the stale counterpart an instance would otherwise carry, but it is
|
|
4117
|
+
* not a compare-and-swap. Web Storage offers none, and the synchronous public
|
|
4118
|
+
* mutators cannot await the refresh lease, so a commit that lands between
|
|
4119
|
+
* this read and the store operation is lost by ordering — including the
|
|
4120
|
+
* counterpart just read, which is then republished stale. The window is one
|
|
4121
|
+
* store operation wide and the server remains authoritative; see
|
|
4122
|
+
* `CUST-AUTH-033` for the stated limit.
|
|
4123
|
+
*
|
|
4124
|
+
* Returns `false` only when `requireLiveSession` refused a write because the
|
|
4125
|
+
* canonical session had already been cleared.
|
|
4126
|
+
*/
|
|
4127
|
+
commitPersistedSession(change, options) {
|
|
4128
|
+
const recordKey = this.persistRecordKey;
|
|
4129
|
+
const mirrorKey = this.persistMirrorKey;
|
|
4130
|
+
const mirrorRefreshKey = this.persistMirrorRefreshKey;
|
|
4131
|
+
const canonical = this.readCanonicalSession();
|
|
4132
|
+
if (canonical === null || !recordKey || !mirrorKey || !mirrorRefreshKey) {
|
|
4133
|
+
return true;
|
|
4134
|
+
}
|
|
4135
|
+
if (canonical === STORAGE_UNUSABLE) {
|
|
4136
|
+
this.disablePersistence();
|
|
4137
|
+
return true;
|
|
4138
|
+
}
|
|
4139
|
+
const current = canonical.record;
|
|
4140
|
+
if (options?.requireLiveSession && !current.token && !current.refreshToken) {
|
|
4141
|
+
return false;
|
|
4142
|
+
}
|
|
4143
|
+
const next = {
|
|
4144
|
+
v: SESSION_RECORD_VERSION,
|
|
4145
|
+
rev: current.rev + 1,
|
|
4146
|
+
token: "token" in change ? change.token ?? null : current.token,
|
|
4147
|
+
refreshToken: "refreshToken" in change ? change.refreshToken ?? null : current.refreshToken
|
|
4148
|
+
};
|
|
4149
|
+
if (
|
|
4150
|
+
// Only a state that is genuinely persisted can already say what this
|
|
4151
|
+
// write would say. An absent record reads as an empty pair, and treating
|
|
4152
|
+
// that as a no-op would drop the very tombstone that stops a later
|
|
4153
|
+
// migration from resurrecting the session out of the earlier layout.
|
|
4154
|
+
canonical.stored && next.token === current.token && next.refreshToken === current.refreshToken
|
|
4155
|
+
) {
|
|
4156
|
+
this.sessionRev = current.rev;
|
|
4157
|
+
this.token = current.token;
|
|
4158
|
+
this.refreshTokenValue = current.refreshToken;
|
|
4159
|
+
return true;
|
|
4160
|
+
}
|
|
4161
|
+
try {
|
|
4162
|
+
localStorage.setItem(recordKey, JSON.stringify(next));
|
|
4163
|
+
} catch {
|
|
4164
|
+
this.disablePersistence();
|
|
4165
|
+
return true;
|
|
4166
|
+
}
|
|
4167
|
+
this.sessionRev = next.rev;
|
|
4168
|
+
this.token = next.token;
|
|
4169
|
+
this.refreshTokenValue = next.refreshToken;
|
|
4170
|
+
try {
|
|
4171
|
+
localStorage.removeItem(mirrorRefreshKey);
|
|
4172
|
+
} catch {
|
|
4173
|
+
return true;
|
|
4174
|
+
}
|
|
4175
|
+
try {
|
|
4176
|
+
if (next.token) localStorage.setItem(mirrorKey, next.token);
|
|
4177
|
+
else localStorage.removeItem(mirrorKey);
|
|
4178
|
+
} catch {
|
|
4179
|
+
}
|
|
4180
|
+
return true;
|
|
4181
|
+
}
|
|
4182
|
+
/**
|
|
4183
|
+
* Storage rejected a write or a read, so it is not a canonical store for this
|
|
4184
|
+
* instance. Fall back to the memory-only session and take no lease: there is
|
|
4185
|
+
* no shared state left to coordinate. Nothing is purged — the record commit
|
|
4186
|
+
* is atomic, so no inconsistent state was published, and another context's
|
|
4187
|
+
* valid session must not be destroyed by this instance's local failure.
|
|
4188
|
+
*/
|
|
4189
|
+
disablePersistence() {
|
|
4190
|
+
this.persistRecordKey = null;
|
|
4191
|
+
this.persistMirrorKey = null;
|
|
4192
|
+
this.persistMirrorRefreshKey = null;
|
|
4193
|
+
this.refreshLockName = null;
|
|
4194
|
+
}
|
|
4195
|
+
/**
|
|
4196
|
+
* Compare the canonical record with the fence this call captured before it
|
|
4197
|
+
* left. Every persisted decision — rotate, converge, or fail closed — goes
|
|
4198
|
+
* through this one comparison. The revision catches a logout, a fresh login,
|
|
4199
|
+
* or a manual setter even when token values coincide; the value comparison
|
|
4200
|
+
* additionally catches a mirror-only writer that cannot bump a revision.
|
|
4201
|
+
*/
|
|
4202
|
+
classifyCanonicalState(intended) {
|
|
4203
|
+
if (!this.persistRecordKey) return "current";
|
|
4204
|
+
this.syncFromPersistedStorage();
|
|
4205
|
+
if (!this.persistRecordKey) return "current";
|
|
4206
|
+
if (!this.token && !this.refreshTokenValue) return "cleared";
|
|
4207
|
+
if (intended.refreshToken !== null) {
|
|
4208
|
+
return this.refreshTokenValue === intended.refreshToken ? "current" : "superseded";
|
|
4209
|
+
}
|
|
4210
|
+
return this.token === intended.token && !this.refreshTokenValue ? "current" : "superseded";
|
|
4211
|
+
}
|
|
4212
|
+
/** The canonical persisted pair, as a refresh result. */
|
|
4213
|
+
canonicalPair(token) {
|
|
4214
|
+
return {
|
|
4215
|
+
token,
|
|
4216
|
+
...this.refreshTokenValue ? { refreshToken: this.refreshTokenValue } : {}
|
|
4217
|
+
};
|
|
4218
|
+
}
|
|
4219
|
+
/** Current credentials plus the revision they were observed at. */
|
|
4220
|
+
currentFence() {
|
|
4221
|
+
return { token: this.token, refreshToken: this.refreshTokenValue };
|
|
4222
|
+
}
|
|
4223
|
+
/**
|
|
4224
|
+
* Clear an access credential a 401 just proved unusable — but only while it
|
|
4225
|
+
* is still the canonical one. A rotation committed by another context while
|
|
4226
|
+
* the request was in flight is newer than this 401, so it must survive; the
|
|
4227
|
+
* instance adopts it instead. The refresh entry is never touched here: a
|
|
4228
|
+
* rejected access token says nothing about the refresh family.
|
|
4229
|
+
*/
|
|
4230
|
+
invalidateAccessToken(spentToken) {
|
|
4231
|
+
this.syncFromPersistedStorage();
|
|
4232
|
+
if (this.token !== spentToken) return;
|
|
4233
|
+
this.setToken(null);
|
|
4234
|
+
}
|
|
4235
|
+
/** Commit both credentials in one canonical write. */
|
|
4236
|
+
setSession(token, refreshToken, options) {
|
|
4237
|
+
this.token = token;
|
|
4238
|
+
this.refreshTokenValue = refreshToken;
|
|
4239
|
+
const committed = this.commitPersistedSession(
|
|
4240
|
+
{ token, refreshToken },
|
|
4241
|
+
options
|
|
4242
|
+
);
|
|
4243
|
+
if (!committed) return false;
|
|
4244
|
+
this.onTokenChange?.(this.token);
|
|
4245
|
+
this.onRefreshTokenChange?.(this.refreshTokenValue);
|
|
4246
|
+
return true;
|
|
4247
|
+
}
|
|
4248
|
+
/**
|
|
4249
|
+
* Serialize rotation against every other context sharing the same storage
|
|
4250
|
+
* key and API/tenant identity. Coordination is best-effort: when it is
|
|
4251
|
+
* unavailable or times out the rotation still happens, so server-side reuse
|
|
4252
|
+
* detection is never bypassed by a client that failed to coordinate.
|
|
4253
|
+
*/
|
|
4254
|
+
async rotateAcrossContexts(intended) {
|
|
4255
|
+
const lockName = this.refreshLockName;
|
|
4256
|
+
const requestLock = lockName ? getRefreshLockRequester() : null;
|
|
4257
|
+
if (!lockName || !requestLock) {
|
|
4258
|
+
return this.rotateWithCanonicalState(intended);
|
|
4259
|
+
}
|
|
4260
|
+
const controller = new AbortController();
|
|
4261
|
+
const timer = setTimeout(() => controller.abort(), REFRESH_LOCK_TIMEOUT);
|
|
4262
|
+
let entered = false;
|
|
4263
|
+
try {
|
|
4264
|
+
return await requestLock(
|
|
4265
|
+
lockName,
|
|
4266
|
+
{ signal: controller.signal },
|
|
4267
|
+
async () => {
|
|
4268
|
+
entered = true;
|
|
4269
|
+
return this.rotateWithCanonicalState(intended);
|
|
4270
|
+
}
|
|
4271
|
+
);
|
|
4272
|
+
} catch (error) {
|
|
4273
|
+
if (entered) throw error;
|
|
4274
|
+
return this.rotateWithCanonicalState(intended);
|
|
4275
|
+
} finally {
|
|
4276
|
+
clearTimeout(timer);
|
|
4277
|
+
}
|
|
4278
|
+
}
|
|
4279
|
+
/**
|
|
4280
|
+
* Decide, against freshly re-read canonical state, whether this context still
|
|
4281
|
+
* owns the rotation.
|
|
4282
|
+
*/
|
|
4283
|
+
async rotateWithCanonicalState(intended) {
|
|
4284
|
+
let fence = intended;
|
|
4285
|
+
for (let attempt = 0; attempt < 2; attempt += 1) {
|
|
4286
|
+
const state = this.classifyCanonicalState(fence);
|
|
4287
|
+
if (state === "cleared") throw new ApiError("Not authenticated", 401);
|
|
4288
|
+
if (state === "current") return this._doRefreshToken(fence);
|
|
4289
|
+
if (this.token) return this.canonicalPair(this.token);
|
|
4290
|
+
fence = this.currentFence();
|
|
4291
|
+
}
|
|
4292
|
+
throw new ApiError("Not authenticated", 401);
|
|
4293
|
+
}
|
|
4294
|
+
async _doRefreshToken(intended) {
|
|
4295
|
+
const init = intended.refreshToken ? {
|
|
3915
4296
|
method: "POST",
|
|
3916
|
-
body: JSON.stringify({ refreshToken:
|
|
4297
|
+
body: JSON.stringify({ refreshToken: intended.refreshToken })
|
|
3917
4298
|
} : {
|
|
3918
4299
|
method: "POST",
|
|
3919
|
-
headers: { Authorization: `Bearer ${
|
|
4300
|
+
headers: { Authorization: `Bearer ${intended.token}` }
|
|
3920
4301
|
};
|
|
3921
4302
|
const result = await this.requestJson(
|
|
3922
4303
|
"/api/customers/refresh",
|
|
3923
4304
|
init
|
|
3924
4305
|
);
|
|
3925
|
-
this.
|
|
3926
|
-
if (
|
|
3927
|
-
|
|
4306
|
+
const state = this.classifyCanonicalState(intended);
|
|
4307
|
+
if (state === "cleared") throw new ApiError("Not authenticated", 401);
|
|
4308
|
+
if (state === "superseded") {
|
|
4309
|
+
if (this.token) return this.canonicalPair(this.token);
|
|
4310
|
+
throw new ApiError("Not authenticated", 401);
|
|
4311
|
+
}
|
|
4312
|
+
const replacedAccessToken = this.token !== null && this.token !== intended.token ? this.token : null;
|
|
4313
|
+
const nextToken = replacedAccessToken ?? result.token;
|
|
4314
|
+
const nextRefresh = result.refreshToken ?? null;
|
|
4315
|
+
if (!this.setSession(nextToken, nextRefresh, { requireLiveSession: true })) {
|
|
4316
|
+
throw new ApiError("Not authenticated", 401);
|
|
4317
|
+
}
|
|
4318
|
+
return {
|
|
4319
|
+
token: nextToken,
|
|
4320
|
+
...nextRefresh ? { refreshToken: nextRefresh } : {}
|
|
4321
|
+
};
|
|
3928
4322
|
}
|
|
3929
4323
|
/**
|
|
3930
4324
|
* Clear the stored token
|
|
3931
4325
|
*/
|
|
3932
4326
|
logout() {
|
|
3933
|
-
this.
|
|
3934
|
-
this.setRefreshToken(null);
|
|
4327
|
+
this.setSession(null, null);
|
|
3935
4328
|
}
|
|
3936
4329
|
/**
|
|
3937
4330
|
* Get the current authenticated customer's profile
|
|
3938
4331
|
*/
|
|
3939
4332
|
async me() {
|
|
3940
|
-
|
|
4333
|
+
const token = this.getToken();
|
|
4334
|
+
if (!token) return null;
|
|
3941
4335
|
try {
|
|
3942
4336
|
const data = await this.requestJson(
|
|
3943
4337
|
"/api/customers/me",
|
|
3944
4338
|
{
|
|
3945
4339
|
method: "GET",
|
|
3946
|
-
headers: { Authorization: `Bearer ${
|
|
4340
|
+
headers: { Authorization: `Bearer ${token}` }
|
|
3947
4341
|
}
|
|
3948
4342
|
);
|
|
3949
4343
|
return data.customer ?? null;
|
|
3950
4344
|
} catch (error) {
|
|
3951
4345
|
if (error instanceof SDKError && error.status === 401) {
|
|
3952
|
-
this.
|
|
4346
|
+
this.invalidateAccessToken(token);
|
|
3953
4347
|
return null;
|
|
3954
4348
|
}
|
|
3955
4349
|
throw error;
|
|
@@ -3977,12 +4371,13 @@ var CustomerAuth = class {
|
|
|
3977
4371
|
* Update the authenticated customer's profile (name, phone, marketingConsent)
|
|
3978
4372
|
*/
|
|
3979
4373
|
async updateProfile(data) {
|
|
3980
|
-
|
|
4374
|
+
const token = this.getToken();
|
|
4375
|
+
if (!token) throw new ApiError("Not authenticated", 401);
|
|
3981
4376
|
const result = await this.requestJson(
|
|
3982
4377
|
"/api/customers/me",
|
|
3983
4378
|
{
|
|
3984
4379
|
method: "PATCH",
|
|
3985
|
-
headers: { Authorization: `Bearer ${
|
|
4380
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
3986
4381
|
body: JSON.stringify(data)
|
|
3987
4382
|
}
|
|
3988
4383
|
);
|
|
@@ -3992,10 +4387,11 @@ var CustomerAuth = class {
|
|
|
3992
4387
|
* Change the password of the currently authenticated customer
|
|
3993
4388
|
*/
|
|
3994
4389
|
async changePassword(currentPassword, newPassword) {
|
|
3995
|
-
|
|
4390
|
+
const token = this.getToken();
|
|
4391
|
+
if (!token) throw new ApiError("Not authenticated", 401);
|
|
3996
4392
|
await this.requestJson("/api/customers/change-password", {
|
|
3997
4393
|
method: "POST",
|
|
3998
|
-
headers: { Authorization: `Bearer ${
|
|
4394
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
3999
4395
|
body: JSON.stringify({ currentPassword, newPassword })
|
|
4000
4396
|
});
|
|
4001
4397
|
}
|
|
@@ -4003,7 +4399,8 @@ var CustomerAuth = class {
|
|
|
4003
4399
|
* Get the authenticated customer's orders with pagination and optional status filter
|
|
4004
4400
|
*/
|
|
4005
4401
|
async getMyOrders(options) {
|
|
4006
|
-
|
|
4402
|
+
const token = this.getToken();
|
|
4403
|
+
if (!token) throw new ApiError("Not authenticated", 401);
|
|
4007
4404
|
const params = new URLSearchParams();
|
|
4008
4405
|
if (options?.page) params.set("page", String(options.page));
|
|
4009
4406
|
if (options?.limit) params.set("limit", String(options.limit));
|
|
@@ -4025,21 +4422,27 @@ var CustomerAuth = class {
|
|
|
4025
4422
|
`/api/customers/me/orders${qs ? `?${qs}` : ""}`,
|
|
4026
4423
|
{
|
|
4027
4424
|
method: "GET",
|
|
4028
|
-
headers: { Authorization: `Bearer ${
|
|
4425
|
+
headers: { Authorization: `Bearer ${token}` }
|
|
4029
4426
|
}
|
|
4030
4427
|
);
|
|
4031
4428
|
return validatePublicOrderList(result);
|
|
4032
4429
|
}
|
|
4033
4430
|
/**
|
|
4034
|
-
* Get the current token (or null if not authenticated)
|
|
4431
|
+
* Get the current token (or null if not authenticated).
|
|
4432
|
+
*
|
|
4433
|
+
* In persisted mode this re-reads shared storage first, so a rotation or
|
|
4434
|
+
* logout performed by another tab is observed before the token is used.
|
|
4035
4435
|
*/
|
|
4036
4436
|
getToken() {
|
|
4437
|
+
this.syncFromPersistedStorage();
|
|
4037
4438
|
return this.token;
|
|
4038
4439
|
}
|
|
4039
4440
|
/**
|
|
4040
|
-
* Get the current refresh token (or null if unavailable)
|
|
4441
|
+
* Get the current refresh token (or null if unavailable). Persisted mode
|
|
4442
|
+
* re-reads shared storage first, like {@link CustomerAuth.getToken}.
|
|
4041
4443
|
*/
|
|
4042
4444
|
getRefreshToken() {
|
|
4445
|
+
this.syncFromPersistedStorage();
|
|
4043
4446
|
return this.refreshTokenValue;
|
|
4044
4447
|
}
|
|
4045
4448
|
/**
|
|
@@ -4047,6 +4450,7 @@ var CustomerAuth = class {
|
|
|
4047
4450
|
*/
|
|
4048
4451
|
setToken(token) {
|
|
4049
4452
|
this.token = token;
|
|
4453
|
+
this.commitPersistedSession({ token });
|
|
4050
4454
|
this.onTokenChange?.(token);
|
|
4051
4455
|
}
|
|
4052
4456
|
/**
|
|
@@ -4054,13 +4458,14 @@ var CustomerAuth = class {
|
|
|
4054
4458
|
*/
|
|
4055
4459
|
setRefreshToken(refreshToken) {
|
|
4056
4460
|
this.refreshTokenValue = refreshToken;
|
|
4461
|
+
this.commitPersistedSession({ refreshToken });
|
|
4057
4462
|
this.onRefreshTokenChange?.(refreshToken);
|
|
4058
4463
|
}
|
|
4059
4464
|
/**
|
|
4060
4465
|
* Check if the customer is currently authenticated
|
|
4061
4466
|
*/
|
|
4062
4467
|
isAuthenticated() {
|
|
4063
|
-
return this.
|
|
4468
|
+
return this.getToken() !== null;
|
|
4064
4469
|
}
|
|
4065
4470
|
/**
|
|
4066
4471
|
* Internal: make a request with timeout and error handling.
|
|
@@ -4268,8 +4673,8 @@ var CartApi = class extends CustomerScopedApi {
|
|
|
4268
4673
|
endpoint(key) {
|
|
4269
4674
|
return this.serverTransport ? COMMERCE_ENDPOINTS.carts.server[key] : COMMERCE_ENDPOINTS.carts[key];
|
|
4270
4675
|
}
|
|
4271
|
-
execute(
|
|
4272
|
-
return this.requestEndpoint(
|
|
4676
|
+
execute(endpoint, body, idempotencyKey) {
|
|
4677
|
+
return this.requestEndpoint(endpoint, body, {
|
|
4273
4678
|
...idempotencyKey && {
|
|
4274
4679
|
headers: { "X-Idempotency-Key": idempotencyKey }
|
|
4275
4680
|
}
|
|
@@ -4286,26 +4691,26 @@ var CartApi = class extends CustomerScopedApi {
|
|
|
4286
4691
|
);
|
|
4287
4692
|
}
|
|
4288
4693
|
mine(params = {}) {
|
|
4289
|
-
const
|
|
4290
|
-
return this.execute(
|
|
4694
|
+
const endpoint = this.endpoint("mine");
|
|
4695
|
+
return this.execute(endpoint, params).then(
|
|
4291
4696
|
(value) => validateCartMineResult(value)
|
|
4292
4697
|
);
|
|
4293
4698
|
}
|
|
4294
4699
|
merge(params) {
|
|
4295
4700
|
const { idempotencyKey, ...body } = params;
|
|
4296
4701
|
assertCommerceIdempotencyKey(idempotencyKey);
|
|
4297
|
-
const
|
|
4298
|
-
return this.execute(
|
|
4702
|
+
const endpoint = this.endpoint("merge");
|
|
4703
|
+
return this.execute(endpoint, body, idempotencyKey).then(
|
|
4299
4704
|
(value) => validateCartMergeResult(value)
|
|
4300
4705
|
);
|
|
4301
4706
|
}
|
|
4302
4707
|
addItem(params) {
|
|
4303
4708
|
return this.mutate(this.endpoint("addItem"), params);
|
|
4304
4709
|
}
|
|
4305
|
-
mutate(
|
|
4710
|
+
mutate(endpoint, params) {
|
|
4306
4711
|
const { idempotencyKey, ...body } = params;
|
|
4307
4712
|
assertCommerceIdempotencyKey(idempotencyKey);
|
|
4308
|
-
return this.execute(
|
|
4713
|
+
return this.execute(endpoint, body, idempotencyKey).then(
|
|
4309
4714
|
(value) => validateCartMutationResult(value)
|
|
4310
4715
|
);
|
|
4311
4716
|
}
|
|
@@ -4331,8 +4736,8 @@ var CheckoutApi = class extends CustomerScopedApi {
|
|
|
4331
4736
|
constructor(options) {
|
|
4332
4737
|
super("CheckoutApi", { ...options, requiresCredential: false });
|
|
4333
4738
|
}
|
|
4334
|
-
post(
|
|
4335
|
-
return this.requestEndpoint(
|
|
4739
|
+
post(endpoint, body, idempotencyKey) {
|
|
4740
|
+
return this.requestEndpoint(endpoint, body, {
|
|
4336
4741
|
...idempotencyKey && {
|
|
4337
4742
|
headers: { "X-Idempotency-Key": idempotencyKey }
|
|
4338
4743
|
}
|
|
@@ -4399,15 +4804,23 @@ var PaymentSessionApi = class extends CustomerScopedApi {
|
|
|
4399
4804
|
super("PaymentSessionApi", { ...options, requiresCredential: false });
|
|
4400
4805
|
this.serverTransport = Boolean(options.secretKey);
|
|
4401
4806
|
}
|
|
4402
|
-
post(
|
|
4403
|
-
|
|
4404
|
-
...idempotencyKey && {
|
|
4405
|
-
|
|
4807
|
+
post(endpoint, body, idempotencyKey, negotiateStandardActionCapabilities = false) {
|
|
4808
|
+
const headers = {
|
|
4809
|
+
...idempotencyKey && { "X-Idempotency-Key": idempotencyKey },
|
|
4810
|
+
...negotiateStandardActionCapabilities && {
|
|
4811
|
+
Accept: PORTONE_STANDARD_ACTION_CAPABILITIES_MEDIA_TYPE
|
|
4406
4812
|
}
|
|
4813
|
+
};
|
|
4814
|
+
return this.requestEndpoint(endpoint, body, {
|
|
4815
|
+
...Object.keys(headers).length > 0 && { headers }
|
|
4407
4816
|
});
|
|
4408
4817
|
}
|
|
4409
|
-
async paymentSession(result, expectedProvider) {
|
|
4410
|
-
return validatePublicPaymentSession(
|
|
4818
|
+
async paymentSession(result, expectedProvider, operation2) {
|
|
4819
|
+
return validatePublicPaymentSession(
|
|
4820
|
+
await result,
|
|
4821
|
+
expectedProvider,
|
|
4822
|
+
operation2
|
|
4823
|
+
);
|
|
4411
4824
|
}
|
|
4412
4825
|
create(params) {
|
|
4413
4826
|
const { idempotencyKey, ...body } = params;
|
|
@@ -4428,41 +4841,44 @@ var PaymentSessionApi = class extends CustomerScopedApi {
|
|
|
4428
4841
|
this.post(
|
|
4429
4842
|
this.serverTransport ? COMMERCE_ENDPOINTS.paymentSessions.server.create : COMMERCE_ENDPOINTS.paymentSessions.create,
|
|
4430
4843
|
body,
|
|
4431
|
-
idempotencyKey
|
|
4844
|
+
idempotencyKey,
|
|
4845
|
+
body.providerOptions.profile === "standard_v2"
|
|
4432
4846
|
),
|
|
4433
4847
|
"portone"
|
|
4434
4848
|
);
|
|
4435
4849
|
}
|
|
4436
4850
|
retrieve(params) {
|
|
4437
|
-
const
|
|
4851
|
+
const endpoint = this.serverTransport ? COMMERCE_ENDPOINTS.paymentSessions.server.retrieve : COMMERCE_ENDPOINTS.paymentSessions.retrieve;
|
|
4438
4852
|
return this.paymentSession(
|
|
4439
|
-
this.post(
|
|
4853
|
+
this.post(endpoint, params),
|
|
4440
4854
|
"toss"
|
|
4441
4855
|
);
|
|
4442
4856
|
}
|
|
4443
4857
|
retrievePortOne(params) {
|
|
4444
|
-
const
|
|
4858
|
+
const endpoint = this.serverTransport ? COMMERCE_ENDPOINTS.paymentSessions.server.retrieve : COMMERCE_ENDPOINTS.paymentSessions.retrieve;
|
|
4445
4859
|
return this.paymentSession(
|
|
4446
|
-
this.post(
|
|
4860
|
+
this.post(endpoint, params, void 0, true),
|
|
4447
4861
|
"portone"
|
|
4448
4862
|
);
|
|
4449
4863
|
}
|
|
4450
4864
|
cancel(params) {
|
|
4451
4865
|
const { idempotencyKey, ...body } = params;
|
|
4452
4866
|
assertCommerceIdempotencyKey(idempotencyKey);
|
|
4453
|
-
const
|
|
4867
|
+
const endpoint = this.serverTransport ? COMMERCE_ENDPOINTS.paymentSessions.server.cancel : COMMERCE_ENDPOINTS.paymentSessions.cancel;
|
|
4454
4868
|
return this.paymentSession(
|
|
4455
|
-
this.post(
|
|
4456
|
-
"toss"
|
|
4869
|
+
this.post(endpoint, body, idempotencyKey),
|
|
4870
|
+
"toss",
|
|
4871
|
+
"cancel"
|
|
4457
4872
|
);
|
|
4458
4873
|
}
|
|
4459
4874
|
cancelPortOne(params) {
|
|
4460
4875
|
const { idempotencyKey, ...body } = params;
|
|
4461
4876
|
assertCommerceIdempotencyKey(idempotencyKey);
|
|
4462
|
-
const
|
|
4877
|
+
const endpoint = this.serverTransport ? COMMERCE_ENDPOINTS.paymentSessions.server.cancel : COMMERCE_ENDPOINTS.paymentSessions.cancel;
|
|
4463
4878
|
return this.paymentSession(
|
|
4464
|
-
this.post(
|
|
4465
|
-
"portone"
|
|
4879
|
+
this.post(endpoint, body, idempotencyKey),
|
|
4880
|
+
"portone",
|
|
4881
|
+
"cancel"
|
|
4466
4882
|
);
|
|
4467
4883
|
}
|
|
4468
4884
|
confirm(params) {
|
|
@@ -4540,9 +4956,9 @@ function resolveVariantImageItems(variant, poolById) {
|
|
|
4540
4956
|
if (pooled) resolved.push(pooled);
|
|
4541
4957
|
continue;
|
|
4542
4958
|
}
|
|
4543
|
-
const
|
|
4544
|
-
if (
|
|
4545
|
-
const pooled = poolById.get(
|
|
4959
|
+
const mediaId = getMediaId(entry);
|
|
4960
|
+
if (mediaId) {
|
|
4961
|
+
const pooled = poolById.get(mediaId);
|
|
4546
4962
|
if (pooled) {
|
|
4547
4963
|
resolved.push(pooled);
|
|
4548
4964
|
}
|
|
@@ -4733,11 +5149,11 @@ function getFirstMediaId(values) {
|
|
|
4733
5149
|
}
|
|
4734
5150
|
return null;
|
|
4735
5151
|
}
|
|
4736
|
-
function inProductMediaPool(product,
|
|
4737
|
-
if (
|
|
5152
|
+
function inProductMediaPool(product, mediaId) {
|
|
5153
|
+
if (mediaId == null) return null;
|
|
4738
5154
|
const images = Array.isArray(product?.images) ? product.images : [];
|
|
4739
5155
|
for (const image of images) {
|
|
4740
|
-
if (extractEntityId(image) ===
|
|
5156
|
+
if (extractEntityId(image) === mediaId) return mediaId;
|
|
4741
5157
|
}
|
|
4742
5158
|
return null;
|
|
4743
5159
|
}
|
|
@@ -6318,8 +6734,8 @@ function isProductUpsertFieldValidationErrorBody(value) {
|
|
|
6318
6734
|
|
|
6319
6735
|
// src/core/commerce/commerce-client.ts
|
|
6320
6736
|
var BrowserCommerceApi = class extends CustomerScopedApi {
|
|
6321
|
-
requestEndpoint(
|
|
6322
|
-
return super.requestEndpoint(
|
|
6737
|
+
requestEndpoint(endpoint, body, options) {
|
|
6738
|
+
return super.requestEndpoint(endpoint, body, options);
|
|
6323
6739
|
}
|
|
6324
6740
|
};
|
|
6325
6741
|
var CommerceClient = class {
|
|
@@ -6519,11 +6935,11 @@ var EventsClient = class {
|
|
|
6519
6935
|
{ useCustomerAuth: false }
|
|
6520
6936
|
);
|
|
6521
6937
|
}
|
|
6522
|
-
async execute(
|
|
6938
|
+
async execute(endpoint, method, body, options = {}) {
|
|
6523
6939
|
const useCustomerAuth = options.useCustomerAuth === true;
|
|
6524
6940
|
const token = useCustomerAuth ? typeof this.customerToken === "function" ? this.customerToken() : this.customerToken : void 0;
|
|
6525
6941
|
try {
|
|
6526
|
-
const response = await httpFetch(
|
|
6942
|
+
const response = await httpFetch(endpoint, {
|
|
6527
6943
|
method,
|
|
6528
6944
|
apiUrl: this.apiUrl,
|
|
6529
6945
|
publishableKey: this.publishableKey,
|
|
@@ -6532,7 +6948,7 @@ var EventsClient = class {
|
|
|
6532
6948
|
...body !== void 0 && { body: JSON.stringify(body) }
|
|
6533
6949
|
});
|
|
6534
6950
|
this.onRequestId?.(response.headers.get("x-request-id") ?? null);
|
|
6535
|
-
return parseApiResponse(response,
|
|
6951
|
+
return parseApiResponse(response, endpoint);
|
|
6536
6952
|
} catch (err) {
|
|
6537
6953
|
const id = err instanceof SDKError ? err.requestId ?? null : null;
|
|
6538
6954
|
this.onRequestId?.(id);
|
|
@@ -6790,13 +7206,106 @@ function mergeProductDetailWithStock(catalog, snapshot) {
|
|
|
6790
7206
|
};
|
|
6791
7207
|
}
|
|
6792
7208
|
|
|
7209
|
+
// src/core/webhook/validation.ts
|
|
7210
|
+
function isValidWebhookEventData(data) {
|
|
7211
|
+
if (typeof data !== "object" || data === null) return false;
|
|
7212
|
+
const event = data;
|
|
7213
|
+
return typeof event.collection === "string" && typeof event.operation === "string" && event.operation.length > 0 && typeof event.data === "object" && event.data !== null;
|
|
7214
|
+
}
|
|
7215
|
+
|
|
7216
|
+
// src/core/webhook/receiver.ts
|
|
7217
|
+
async function verifySignature(rawBody, secret, signature, timestamp2, deliveryId) {
|
|
7218
|
+
const encoder = new TextEncoder();
|
|
7219
|
+
const signedPrefix = encoder.encode(`${timestamp2}.${deliveryId}.`);
|
|
7220
|
+
const signedPayload = new Uint8Array(signedPrefix.length + rawBody.length);
|
|
7221
|
+
signedPayload.set(signedPrefix);
|
|
7222
|
+
signedPayload.set(rawBody, signedPrefix.length);
|
|
7223
|
+
const key = await crypto.subtle.importKey(
|
|
7224
|
+
"raw",
|
|
7225
|
+
encoder.encode(secret),
|
|
7226
|
+
{ name: "HMAC", hash: "SHA-256" },
|
|
7227
|
+
false,
|
|
7228
|
+
["verify"]
|
|
7229
|
+
);
|
|
7230
|
+
if (signature.length % 2 !== 0 || !/^[0-9a-fA-F]*$/.test(signature)) {
|
|
7231
|
+
return false;
|
|
7232
|
+
}
|
|
7233
|
+
const sigBytes = new Uint8Array(
|
|
7234
|
+
(signature.match(/.{2}/g) ?? []).map((byte) => parseInt(byte, 16))
|
|
7235
|
+
);
|
|
7236
|
+
return crypto.subtle.verify("HMAC", key, sigBytes, signedPayload);
|
|
7237
|
+
}
|
|
7238
|
+
function timestampIsFresh(timestamp2, toleranceSeconds) {
|
|
7239
|
+
if (!/^\d+$/.test(timestamp2)) return false;
|
|
7240
|
+
const timestampMs = Number(timestamp2);
|
|
7241
|
+
if (!Number.isFinite(timestampMs)) return false;
|
|
7242
|
+
const skewMs = Math.abs(Date.now() - timestampMs);
|
|
7243
|
+
return skewMs <= toleranceSeconds * 1e3;
|
|
7244
|
+
}
|
|
7245
|
+
var MAX_TOLERANCE_SECONDS = 86400;
|
|
7246
|
+
function toleranceIsValid(toleranceSeconds) {
|
|
7247
|
+
return typeof toleranceSeconds === "number" && Number.isFinite(toleranceSeconds) && toleranceSeconds >= 0 && toleranceSeconds <= MAX_TOLERANCE_SECONDS;
|
|
7248
|
+
}
|
|
7249
|
+
async function processWebhookRequest(request, handler, verify) {
|
|
7250
|
+
try {
|
|
7251
|
+
const rawBody = new Uint8Array(await request.arrayBuffer());
|
|
7252
|
+
const verificationFailure = await verify?.(rawBody);
|
|
7253
|
+
if (verificationFailure) return verificationFailure;
|
|
7254
|
+
const body = JSON.parse(new TextDecoder().decode(rawBody));
|
|
7255
|
+
if (!isValidWebhookEventData(body)) {
|
|
7256
|
+
return new Response(
|
|
7257
|
+
JSON.stringify({ error: "Invalid webhook event format" }),
|
|
7258
|
+
{ status: 400, headers: { "Content-Type": "application/json" } }
|
|
7259
|
+
);
|
|
7260
|
+
}
|
|
7261
|
+
await handler(body);
|
|
7262
|
+
return new Response(
|
|
7263
|
+
JSON.stringify({ success: true, message: "Webhook processed" }),
|
|
7264
|
+
{ status: 200, headers: { "Content-Type": "application/json" } }
|
|
7265
|
+
);
|
|
7266
|
+
} catch (error) {
|
|
7267
|
+
console.error("Webhook processing error:", error);
|
|
7268
|
+
return new Response(JSON.stringify({ error: "Internal server error" }), {
|
|
7269
|
+
status: 500,
|
|
7270
|
+
headers: { "Content-Type": "application/json" }
|
|
7271
|
+
});
|
|
7272
|
+
}
|
|
7273
|
+
}
|
|
7274
|
+
async function handleSignedWebhook(request, handler, options) {
|
|
7275
|
+
const secret = options?.secret;
|
|
7276
|
+
const configuredTolerance = options?.toleranceSeconds;
|
|
7277
|
+
const toleranceSeconds = configuredTolerance ?? 300;
|
|
7278
|
+
if (typeof secret !== "string" || secret.trim().length === 0) {
|
|
7279
|
+
return new Response(
|
|
7280
|
+
JSON.stringify({ error: "Webhook signing secret is not configured" }),
|
|
7281
|
+
{ status: 500, headers: { "Content-Type": "application/json" } }
|
|
7282
|
+
);
|
|
7283
|
+
}
|
|
7284
|
+
if (!toleranceIsValid(toleranceSeconds)) {
|
|
7285
|
+
return new Response(
|
|
7286
|
+
JSON.stringify({ error: "Webhook signature tolerance is not valid" }),
|
|
7287
|
+
{ status: 500, headers: { "Content-Type": "application/json" } }
|
|
7288
|
+
);
|
|
7289
|
+
}
|
|
7290
|
+
return processWebhookRequest(request, handler, async (rawBody) => {
|
|
7291
|
+
const signature = request.headers.get("x-webhook-signature") || "";
|
|
7292
|
+
const timestamp2 = request.headers.get("x-webhook-timestamp") || "";
|
|
7293
|
+
const deliveryId = request.headers.get("x-webhook-delivery-id") || "";
|
|
7294
|
+
const valid = Boolean(timestamp2 && deliveryId) && timestampIsFresh(timestamp2, toleranceSeconds) && await verifySignature(rawBody, secret, signature, timestamp2, deliveryId);
|
|
7295
|
+
if (!valid) {
|
|
7296
|
+
return new Response(
|
|
7297
|
+
JSON.stringify({ error: "Invalid webhook signature" }),
|
|
7298
|
+
{ status: 401, headers: { "Content-Type": "application/json" } }
|
|
7299
|
+
);
|
|
7300
|
+
}
|
|
7301
|
+
});
|
|
7302
|
+
}
|
|
7303
|
+
|
|
6793
7304
|
// src/core/webhook/index.ts
|
|
6794
7305
|
var ORDER_CHANGED_EVENT_TYPE = "collection.orderChanged";
|
|
6795
7306
|
var COMMERCE_NOTIFICATION_EVENT_TYPE = "commerce.notification";
|
|
6796
7307
|
function isValidWebhookEvent(data) {
|
|
6797
|
-
|
|
6798
|
-
const obj = data;
|
|
6799
|
-
return typeof obj.collection === "string" && typeof obj.operation === "string" && obj.operation.length > 0 && typeof obj.data === "object" && obj.data !== null;
|
|
7308
|
+
return isValidWebhookEventData(data);
|
|
6800
7309
|
}
|
|
6801
7310
|
var COMMERCE_NOTIFICATION_OPERATION = "notification";
|
|
6802
7311
|
var COMMERCE_NOTIFICATION_EVENTS = [
|
|
@@ -6808,7 +7317,13 @@ var COMMERCE_NOTIFICATION_EVENTS = [
|
|
|
6808
7317
|
"returnApproved",
|
|
6809
7318
|
"returnRejected",
|
|
6810
7319
|
"returnCanceled",
|
|
6811
|
-
"returnCompleted"
|
|
7320
|
+
"returnCompleted",
|
|
7321
|
+
"paymentReconciliationRequired",
|
|
7322
|
+
"paymentRefundRequiresAttention",
|
|
7323
|
+
"membershipActivated",
|
|
7324
|
+
"membershipPastDue",
|
|
7325
|
+
"membershipExpired",
|
|
7326
|
+
"membershipCancelled"
|
|
6812
7327
|
];
|
|
6813
7328
|
function isStringOrNumber(value) {
|
|
6814
7329
|
return typeof value === "string" || typeof value === "number";
|
|
@@ -6838,18 +7353,29 @@ function isCommerceNotificationEventName(value) {
|
|
|
6838
7353
|
return typeof value === "string" && COMMERCE_NOTIFICATION_EVENTS.includes(value);
|
|
6839
7354
|
}
|
|
6840
7355
|
function isCommerceNotificationSourceCollection(value) {
|
|
6841
|
-
return value === "orders" || value === "fulfillments" || value === "returns";
|
|
7356
|
+
return value === "orders" || value === "fulfillments" || value === "returns" || value === "payment-sessions" || value === "payment-refund-attempts" || value === "customer-memberships";
|
|
6842
7357
|
}
|
|
6843
7358
|
function isCommerceNotificationData(value) {
|
|
6844
7359
|
if (!isRecord(value)) return false;
|
|
6845
7360
|
if (value.source !== void 0 && (!isRecord(value.source) || !isCommerceNotificationSourceCollection(value.source.collection) || !isStringOrNumber(value.source.id))) {
|
|
6846
7361
|
return false;
|
|
6847
7362
|
}
|
|
6848
|
-
return (value.orderId === void 0 || isStringOrNumber(value.orderId)) && (value.orderNumber === void 0 || typeof value.orderNumber === "string") && (value.status === void 0 || typeof value.status === "string") && (value.totalAmount === void 0 || typeof value.totalAmount === "number") && (value.currency === void 0 || typeof value.currency === "string") && (value.fulfillmentId === void 0 || isStringOrNumber(value.fulfillmentId)) && (value.fulfillmentStatus === void 0 || typeof value.fulfillmentStatus === "string") && (value.shippedAt === void 0 || typeof value.shippedAt === "string") && (value.returnId === void 0 || isStringOrNumber(value.returnId)) && (value.returnStatus === void 0 || typeof value.returnStatus === "string") && (value.refundAmount === void 0 || typeof value.refundAmount === "number") && (value.completedAt === void 0 || typeof value.completedAt === "string");
|
|
7363
|
+
return (value.orderId === void 0 || isStringOrNumber(value.orderId)) && (value.orderNumber === void 0 || typeof value.orderNumber === "string") && (value.status === void 0 || typeof value.status === "string") && (value.totalAmount === void 0 || typeof value.totalAmount === "number") && (value.currency === void 0 || typeof value.currency === "string") && (value.fulfillmentId === void 0 || isStringOrNumber(value.fulfillmentId)) && (value.fulfillmentStatus === void 0 || typeof value.fulfillmentStatus === "string") && (value.shippedAt === void 0 || typeof value.shippedAt === "string") && (value.returnId === void 0 || isStringOrNumber(value.returnId)) && (value.returnStatus === void 0 || typeof value.returnStatus === "string") && (value.refundAmount === void 0 || typeof value.refundAmount === "number") && (value.completedAt === void 0 || typeof value.completedAt === "string") && (value.paymentSessionId === void 0 || typeof value.paymentSessionId === "string") && (value.checkoutId === void 0 || typeof value.checkoutId === "string") && (value.refundAttemptId === void 0 || typeof value.refundAttemptId === "string") && (value.reason === void 0 || typeof value.reason === "string") && (value.observedAt === void 0 || typeof value.observedAt === "string") && (value.providerObject === void 0 || isRecord(value.providerObject) && typeof value.providerObject.provider === "string" && (value.providerObject.kind === "payment" || value.providerObject.kind === "refund") && typeof value.providerObject.id === "string") && (value.money === void 0 || isRecord(value.money) && Number.isSafeInteger(value.money.amountMinor) && Number(value.money.amountMinor) > 0 && typeof value.money.currency === "string");
|
|
6849
7364
|
}
|
|
6850
7365
|
function isCommerceNotification(value) {
|
|
6851
7366
|
if (!isRecord(value)) return false;
|
|
6852
|
-
return isCommerceNotificationEventName(value.event) && typeof value.intentId === "string" && value.intentId.length > 0 && typeof value.dedupeKey === "string" && value.dedupeKey.length > 0 && (value.orderId === void 0 || typeof value.orderId === "string") && (value.fulfillmentId === void 0 || typeof value.fulfillmentId === "string") && (value.returnId === void 0 || typeof value.returnId === "string");
|
|
7367
|
+
return isCommerceNotificationEventName(value.event) && typeof value.intentId === "string" && value.intentId.length > 0 && typeof value.dedupeKey === "string" && value.dedupeKey.length > 0 && (value.orderId === void 0 || typeof value.orderId === "string") && (value.fulfillmentId === void 0 || typeof value.fulfillmentId === "string") && (value.returnId === void 0 || typeof value.returnId === "string") && (value.paymentSessionId === void 0 || typeof value.paymentSessionId === "string") && (value.refundAttemptId === void 0 || typeof value.refundAttemptId === "string");
|
|
7368
|
+
}
|
|
7369
|
+
function isPaymentIncidentNotificationData(value, event) {
|
|
7370
|
+
const reconciliation = event === "paymentReconciliationRequired";
|
|
7371
|
+
const expectedCollection = reconciliation ? "payment-sessions" : "payment-refund-attempts";
|
|
7372
|
+
const expectedStatus = reconciliation ? "reconciliation_required" : "requires_attention";
|
|
7373
|
+
const expectedReason = reconciliation ? "orderless_capture" : "local_commit_ambiguous";
|
|
7374
|
+
return value.source?.collection === expectedCollection && typeof value.source.id === "string" && value.source.id.length > 0 && typeof value.paymentSessionId === "string" && value.paymentSessionId.length > 0 && isRecord(value.providerObject) && typeof value.providerObject.provider === "string" && value.providerObject.provider.length > 0 && (value.providerObject.kind === "payment" || value.providerObject.kind === "refund") && typeof value.providerObject.id === "string" && value.providerObject.id.length > 0 && isRecord(value.money) && Number.isSafeInteger(value.money.amountMinor) && Number(value.money.amountMinor) > 0 && typeof value.money.currency === "string" && value.money.currency.length > 0 && value.status === expectedStatus && value.reason === expectedReason && typeof value.observedAt === "string" && value.observedAt.length > 0 && !Number.isNaN(Date.parse(value.observedAt)) && (value.checkoutId === void 0 || typeof value.checkoutId === "string" && value.checkoutId.length > 0) && (value.orderId === void 0 || typeof value.orderId === "string" && value.orderId.length > 0) && (reconciliation ? value.source.id === value.paymentSessionId && value.providerObject.kind === "payment" && value.refundAttemptId === void 0 : typeof value.refundAttemptId === "string" && value.refundAttemptId.length > 0 && value.source.id === value.refundAttemptId);
|
|
7375
|
+
}
|
|
7376
|
+
function isMembershipNotificationData(value, event) {
|
|
7377
|
+
const expectedStatus = event === "membershipActivated" ? "active" : event === "membershipPastDue" ? "past_due" : event === "membershipExpired" ? "expired" : "cancelled";
|
|
7378
|
+
return typeof value.membershipId === "string" && value.membershipId.length > 0 && typeof value.planId === "string" && value.planId.length > 0 && typeof value.customerId === "string" && value.customerId.length > 0 && typeof value.fromStatus === "string" && value.fromStatus.length > 0 && value.fromStatus !== value.toStatus && value.toStatus === expectedStatus && typeof value.occurredAt === "string" && !Number.isNaN(Date.parse(value.occurredAt));
|
|
6853
7379
|
}
|
|
6854
7380
|
function isWebhookCommerceNotificationChange(value) {
|
|
6855
7381
|
if (!isRecord(value)) return false;
|
|
@@ -6890,6 +7416,18 @@ function isCommerceNotificationWebhookEvent(event) {
|
|
|
6890
7416
|
if (!matchesOptionalId(change.sourceId, sourceId)) return false;
|
|
6891
7417
|
}
|
|
6892
7418
|
const changeSourceId = isWebhookCommerceNotificationChange(change) ? change.sourceId : void 0;
|
|
7419
|
+
if (notification.event === "paymentReconciliationRequired" || notification.event === "paymentRefundRequiresAttention") {
|
|
7420
|
+
if (!isPaymentIncidentNotificationData(data, notification.event)) {
|
|
7421
|
+
return false;
|
|
7422
|
+
}
|
|
7423
|
+
const incidentData = data;
|
|
7424
|
+
const expectedCollection = notification.event === "paymentReconciliationRequired" ? "payment-sessions" : "payment-refund-attempts";
|
|
7425
|
+
const notificationSourceId = notification.event === "paymentReconciliationRequired" ? notification.paymentSessionId : notification.refundAttemptId;
|
|
7426
|
+
return sourceCollection === expectedCollection && typeof notification.paymentSessionId === "string" && notification.paymentSessionId === incidentData.paymentSessionId && typeof notificationSourceId === "string" && notificationSourceId === incidentData.source.id && matchesOptionalId(changeSourceId, incidentData.source.id);
|
|
7427
|
+
}
|
|
7428
|
+
if (notification.event === "membershipActivated" || notification.event === "membershipPastDue" || notification.event === "membershipExpired" || notification.event === "membershipCancelled") {
|
|
7429
|
+
return sourceCollection === "customer-memberships" && isMembershipNotificationData(data, notification.event) && typeof notification.membershipId === "string" && notification.membershipId === data.membershipId && matchesOptionalId(changeSourceId, data.membershipId);
|
|
7430
|
+
}
|
|
6893
7431
|
if (notification.event === "orderPaid" || notification.event === "orderCanceled" || notification.event === "orderDelivered") {
|
|
6894
7432
|
return sourceCollection === "orders" && typeof notification.orderId === "string" && notification.orderId.length > 0 && matchesOptionalId(data.orderId, sourceId) && matchesOptionalId(notification.orderId, sourceId) && matchesOptionalId(data.orderId, notification.orderId) && matchesOptionalId(changeSourceId, data.orderId ?? notification.orderId);
|
|
6895
7433
|
}
|
|
@@ -6958,80 +7496,8 @@ function createCustomerAuthWebhookHandler(handlers) {
|
|
|
6958
7496
|
await handlers.unhandled?.(event);
|
|
6959
7497
|
};
|
|
6960
7498
|
}
|
|
6961
|
-
async function verifySignature(payload, secret, signature, timestamp2, deliveryId) {
|
|
6962
|
-
const encoder = new TextEncoder();
|
|
6963
|
-
const key = await crypto.subtle.importKey(
|
|
6964
|
-
"raw",
|
|
6965
|
-
encoder.encode(secret),
|
|
6966
|
-
{ name: "HMAC", hash: "SHA-256" },
|
|
6967
|
-
false,
|
|
6968
|
-
["verify"]
|
|
6969
|
-
);
|
|
6970
|
-
if (signature.length % 2 !== 0 || !/^[0-9a-fA-F]*$/.test(signature)) {
|
|
6971
|
-
return false;
|
|
6972
|
-
}
|
|
6973
|
-
const sigBytes = new Uint8Array(
|
|
6974
|
-
(signature.match(/.{2}/g) ?? []).map((byte) => parseInt(byte, 16))
|
|
6975
|
-
);
|
|
6976
|
-
return crypto.subtle.verify(
|
|
6977
|
-
"HMAC",
|
|
6978
|
-
key,
|
|
6979
|
-
sigBytes,
|
|
6980
|
-
encoder.encode(`${timestamp2}.${deliveryId}.${payload}`)
|
|
6981
|
-
);
|
|
6982
|
-
}
|
|
6983
|
-
function timestampIsFresh(timestamp2, toleranceSeconds) {
|
|
6984
|
-
if (!/^\d+$/.test(timestamp2)) return false;
|
|
6985
|
-
const timestampMs = Number(timestamp2);
|
|
6986
|
-
if (!Number.isFinite(timestampMs)) return false;
|
|
6987
|
-
const skewMs = Math.abs(Date.now() - timestampMs);
|
|
6988
|
-
return skewMs <= toleranceSeconds * 1e3;
|
|
6989
|
-
}
|
|
6990
7499
|
async function handleWebhook(request, handler, options) {
|
|
6991
|
-
|
|
6992
|
-
const rawBody = await request.text();
|
|
6993
|
-
if (options?.secret) {
|
|
6994
|
-
const signature = request.headers.get("x-webhook-signature") || "";
|
|
6995
|
-
const timestamp2 = request.headers.get("x-webhook-timestamp") || "";
|
|
6996
|
-
const deliveryId = request.headers.get("x-webhook-delivery-id") || "";
|
|
6997
|
-
const toleranceSeconds = options.toleranceSeconds ?? 300;
|
|
6998
|
-
const valid = Boolean(timestamp2 && deliveryId) && timestampIsFresh(timestamp2, toleranceSeconds) && await verifySignature(
|
|
6999
|
-
rawBody,
|
|
7000
|
-
options.secret,
|
|
7001
|
-
signature,
|
|
7002
|
-
timestamp2,
|
|
7003
|
-
deliveryId
|
|
7004
|
-
);
|
|
7005
|
-
if (!valid) {
|
|
7006
|
-
return new Response(
|
|
7007
|
-
JSON.stringify({ error: "Invalid webhook signature" }),
|
|
7008
|
-
{ status: 401, headers: { "Content-Type": "application/json" } }
|
|
7009
|
-
);
|
|
7010
|
-
}
|
|
7011
|
-
} else {
|
|
7012
|
-
console.warn(
|
|
7013
|
-
"[@01.software/sdk] Webhook signature verification is disabled. Set { secret } in handleWebhook() options to enable HMAC-SHA256 verification."
|
|
7014
|
-
);
|
|
7015
|
-
}
|
|
7016
|
-
const body = JSON.parse(rawBody);
|
|
7017
|
-
if (!isValidWebhookEvent(body)) {
|
|
7018
|
-
return new Response(
|
|
7019
|
-
JSON.stringify({ error: "Invalid webhook event format" }),
|
|
7020
|
-
{ status: 400, headers: { "Content-Type": "application/json" } }
|
|
7021
|
-
);
|
|
7022
|
-
}
|
|
7023
|
-
await handler(body);
|
|
7024
|
-
return new Response(
|
|
7025
|
-
JSON.stringify({ success: true, message: "Webhook processed" }),
|
|
7026
|
-
{ status: 200, headers: { "Content-Type": "application/json" } }
|
|
7027
|
-
);
|
|
7028
|
-
} catch (error) {
|
|
7029
|
-
console.error("Webhook processing error:", error);
|
|
7030
|
-
return new Response(JSON.stringify({ error: "Internal server error" }), {
|
|
7031
|
-
status: 500,
|
|
7032
|
-
headers: { "Content-Type": "application/json" }
|
|
7033
|
-
});
|
|
7034
|
-
}
|
|
7500
|
+
return handleSignedWebhook(request, handler, options);
|
|
7035
7501
|
}
|
|
7036
7502
|
function createTypedWebhookHandler(collection, handler) {
|
|
7037
7503
|
return async (event) => {
|
|
@@ -7071,6 +7537,7 @@ var INTERNAL_COLLECTIONS = [
|
|
|
7071
7537
|
"payment-event-inbox",
|
|
7072
7538
|
"payment-refund-attempts",
|
|
7073
7539
|
"portone-payment-interactions",
|
|
7540
|
+
"commerce-payment-webhook-routes",
|
|
7074
7541
|
"commerce-notification-intents",
|
|
7075
7542
|
"commerce-idempotency-records",
|
|
7076
7543
|
"product-collection-items",
|
|
@@ -7078,6 +7545,7 @@ var INTERNAL_COLLECTIONS = [
|
|
|
7078
7545
|
"fulfillment-events",
|
|
7079
7546
|
"tenant-commerce-config",
|
|
7080
7547
|
"customer-oauth-clients",
|
|
7548
|
+
"customer-group-grants",
|
|
7081
7549
|
"api-keys",
|
|
7082
7550
|
"personal-access-tokens",
|
|
7083
7551
|
"tenant-entitlements",
|
|
@@ -7111,7 +7579,6 @@ var COLLECTIONS = [
|
|
|
7111
7579
|
"brands",
|
|
7112
7580
|
"customer-profiles",
|
|
7113
7581
|
"customer-profile-stats",
|
|
7114
|
-
"discounts",
|
|
7115
7582
|
"shipping-policies",
|
|
7116
7583
|
"shipping-zones",
|
|
7117
7584
|
"documents",
|
|
@@ -7187,6 +7654,9 @@ var SERVER_ONLY_COLLECTIONS = [
|
|
|
7187
7654
|
"cart-items",
|
|
7188
7655
|
"checkouts",
|
|
7189
7656
|
"checkout-items",
|
|
7657
|
+
// Raw discount definitions require a server credential. Browser discount
|
|
7658
|
+
// validation is exposed through the shaped POST /api/discounts/validate path.
|
|
7659
|
+
"discounts",
|
|
7190
7660
|
"draft-orders",
|
|
7191
7661
|
"draft-order-items",
|
|
7192
7662
|
"posts",
|
|
@@ -7352,160 +7822,6 @@ var RealtimeConnection = class {
|
|
|
7352
7822
|
}
|
|
7353
7823
|
};
|
|
7354
7824
|
|
|
7355
|
-
// src/utils/product-media.ts
|
|
7356
|
-
function mediaPointerOptionValues(values) {
|
|
7357
|
-
return (values ?? []).filter((value) => value != null && value !== "").map((value) => ({
|
|
7358
|
-
swatch: {
|
|
7359
|
-
type: "media",
|
|
7360
|
-
mediaItemId: value
|
|
7361
|
-
}
|
|
7362
|
-
}));
|
|
7363
|
-
}
|
|
7364
|
-
function resolveProductMedia(input) {
|
|
7365
|
-
const resolved = resolveProductSelectionMedia({
|
|
7366
|
-
...input,
|
|
7367
|
-
selectedVariant: input.selectedVariant ?? (input.variantFeaturedMediaItemId ? { images: [input.variantFeaturedMediaItemId] } : null),
|
|
7368
|
-
selectedOptionValues: input.selectedOptionValues ?? mediaPointerOptionValues(input.selectedOptionValueMediaItemIds)
|
|
7369
|
-
});
|
|
7370
|
-
if (resolved.source !== "none") {
|
|
7371
|
-
return {
|
|
7372
|
-
...resolved,
|
|
7373
|
-
source: resolved.source === "variant_media_selected" ? "variant_featured" : resolved.source
|
|
7374
|
-
};
|
|
7375
|
-
}
|
|
7376
|
-
const display = resolveProductDisplayMedia({
|
|
7377
|
-
productMediaPool: input.productMediaPool
|
|
7378
|
-
});
|
|
7379
|
-
return {
|
|
7380
|
-
primaryImage: display.primaryImage,
|
|
7381
|
-
images: display.images,
|
|
7382
|
-
source: display.source
|
|
7383
|
-
};
|
|
7384
|
-
}
|
|
7385
|
-
|
|
7386
|
-
// src/utils/product-gallery.ts
|
|
7387
|
-
function mediaId(value) {
|
|
7388
|
-
if (typeof value === "string" || typeof value === "number") {
|
|
7389
|
-
return String(value);
|
|
7390
|
-
}
|
|
7391
|
-
if (typeof value === "object" && value !== null && "id" in value) {
|
|
7392
|
-
const id = value.id;
|
|
7393
|
-
if (typeof id === "string" || typeof id === "number") return String(id);
|
|
7394
|
-
}
|
|
7395
|
-
return null;
|
|
7396
|
-
}
|
|
7397
|
-
function relationshipIds(values) {
|
|
7398
|
-
if (!Array.isArray(values)) return [];
|
|
7399
|
-
return values.map((value) => mediaId(value)).filter((value) => Boolean(value));
|
|
7400
|
-
}
|
|
7401
|
-
function buildPoolById2(pool) {
|
|
7402
|
-
const poolById = /* @__PURE__ */ new Map();
|
|
7403
|
-
for (const item of pool) {
|
|
7404
|
-
const id = mediaId(item);
|
|
7405
|
-
if (id) poolById.set(id, item);
|
|
7406
|
-
}
|
|
7407
|
-
return poolById;
|
|
7408
|
-
}
|
|
7409
|
-
function mediaSetItemsInPool(mediaSet, poolById) {
|
|
7410
|
-
const seen = /* @__PURE__ */ new Set();
|
|
7411
|
-
const items = [];
|
|
7412
|
-
const primaryId = mediaId(mediaSet.primaryMediaItemId);
|
|
7413
|
-
if (primaryId) {
|
|
7414
|
-
const primary = poolById.get(primaryId);
|
|
7415
|
-
if (primary) {
|
|
7416
|
-
seen.add(primaryId);
|
|
7417
|
-
items.push(primary);
|
|
7418
|
-
}
|
|
7419
|
-
}
|
|
7420
|
-
for (const itemId of relationshipIds(mediaSet.items)) {
|
|
7421
|
-
if (seen.has(itemId)) continue;
|
|
7422
|
-
const item = poolById.get(itemId);
|
|
7423
|
-
if (!item) continue;
|
|
7424
|
-
seen.add(itemId);
|
|
7425
|
-
items.push(item);
|
|
7426
|
-
}
|
|
7427
|
-
return items;
|
|
7428
|
-
}
|
|
7429
|
-
function resolveLegacyMediaSet(input) {
|
|
7430
|
-
if (!Array.isArray(input.mediaSets)) return null;
|
|
7431
|
-
const pool = input.productMediaPool ?? input.pool ?? [];
|
|
7432
|
-
const poolById = buildPoolById2(pool);
|
|
7433
|
-
const selectedVariantId = mediaId(input.selectedVariantId);
|
|
7434
|
-
if (selectedVariantId) {
|
|
7435
|
-
const exactSet = input.mediaSets.find(
|
|
7436
|
-
(set) => set.matchType === "exact-variant" && relationshipIds(set.variantIds).includes(selectedVariantId)
|
|
7437
|
-
);
|
|
7438
|
-
if (exactSet) {
|
|
7439
|
-
const images = mediaSetItemsInPool(exactSet, poolById);
|
|
7440
|
-
if (images.length > 0) {
|
|
7441
|
-
return {
|
|
7442
|
-
primaryImage: images[0] ?? null,
|
|
7443
|
-
images,
|
|
7444
|
-
source: "exact_variant_set"
|
|
7445
|
-
};
|
|
7446
|
-
}
|
|
7447
|
-
}
|
|
7448
|
-
}
|
|
7449
|
-
const selectedOptionValueIds = new Set(
|
|
7450
|
-
relationshipIds(input.selectedOptionValueIds)
|
|
7451
|
-
);
|
|
7452
|
-
const optionSetMatches = input.mediaSets.filter((set) => set.matchType === "option-values").map((set) => {
|
|
7453
|
-
const optionValueIds = relationshipIds(set.optionValueIds);
|
|
7454
|
-
if (optionValueIds.length === 0) return null;
|
|
7455
|
-
if (!optionValueIds.every((id) => selectedOptionValueIds.has(id))) {
|
|
7456
|
-
return null;
|
|
7457
|
-
}
|
|
7458
|
-
const images = mediaSetItemsInPool(set, poolById);
|
|
7459
|
-
if (images.length === 0) return null;
|
|
7460
|
-
return { optionValueCount: optionValueIds.length, images };
|
|
7461
|
-
}).filter(
|
|
7462
|
-
(match) => Boolean(match)
|
|
7463
|
-
).sort((a, b) => b.optionValueCount - a.optionValueCount);
|
|
7464
|
-
if (optionSetMatches.length > 0) {
|
|
7465
|
-
const images = optionSetMatches[0].images;
|
|
7466
|
-
return {
|
|
7467
|
-
primaryImage: images[0] ?? null,
|
|
7468
|
-
images,
|
|
7469
|
-
source: "option_value_set"
|
|
7470
|
-
};
|
|
7471
|
-
}
|
|
7472
|
-
const defaultSet = input.mediaSets.find((set) => set.matchType === "default");
|
|
7473
|
-
if (defaultSet) {
|
|
7474
|
-
const images = mediaSetItemsInPool(defaultSet, poolById);
|
|
7475
|
-
if (images.length > 0) {
|
|
7476
|
-
return {
|
|
7477
|
-
primaryImage: images[0] ?? null,
|
|
7478
|
-
images,
|
|
7479
|
-
source: "default_set"
|
|
7480
|
-
};
|
|
7481
|
-
}
|
|
7482
|
-
}
|
|
7483
|
-
return {
|
|
7484
|
-
primaryImage: null,
|
|
7485
|
-
images: [],
|
|
7486
|
-
source: "none"
|
|
7487
|
-
};
|
|
7488
|
-
}
|
|
7489
|
-
function resolveProductGallery(input) {
|
|
7490
|
-
const selectedVariantId = mediaId(input.selectedVariantId);
|
|
7491
|
-
const selectedOptionValueIds = relationshipIds(input.selectedOptionValueIds);
|
|
7492
|
-
const selection = resolveProductSelectionMedia({
|
|
7493
|
-
...input,
|
|
7494
|
-
productMediaPool: input.productMediaPool ?? input.pool ?? null,
|
|
7495
|
-
selectedVariant: input.selectedVariant ?? (selectedVariantId ? { id: selectedVariantId } : null),
|
|
7496
|
-
selectedOptionValues: input.selectedOptionValues ?? selectedOptionValueIds.map((id) => ({ id }))
|
|
7497
|
-
});
|
|
7498
|
-
if (selection.source !== "none") {
|
|
7499
|
-
return selection;
|
|
7500
|
-
}
|
|
7501
|
-
const legacyResolved = resolveLegacyMediaSet(input);
|
|
7502
|
-
if (legacyResolved && legacyResolved.source !== "none") return legacyResolved;
|
|
7503
|
-
return resolveProductDisplayMedia({
|
|
7504
|
-
productMediaPool: input.productMediaPool ?? input.pool ?? null,
|
|
7505
|
-
featuredImage: input.featuredImage
|
|
7506
|
-
});
|
|
7507
|
-
}
|
|
7508
|
-
|
|
7509
7825
|
// src/utils/image.ts
|
|
7510
7826
|
var IMAGE_SIZES = [200, 400, 800, 1200, 1600];
|
|
7511
7827
|
function imageSizeCandidates(sizes) {
|
|
@@ -7637,7 +7953,7 @@ function createAnalytics(config) {
|
|
|
7637
7953
|
}, destroy() {
|
|
7638
7954
|
} };
|
|
7639
7955
|
}
|
|
7640
|
-
const
|
|
7956
|
+
const endpoint = config.endpoint ?? `${resolveApiUrl()}/api/analytics/collect`;
|
|
7641
7957
|
const respectDnt = config.respectDnt !== false;
|
|
7642
7958
|
function isDntActive() {
|
|
7643
7959
|
if (!respectDnt) return false;
|
|
@@ -7678,10 +7994,10 @@ function createAnalytics(config) {
|
|
|
7678
7994
|
try {
|
|
7679
7995
|
if (typeof navigator.sendBeacon === "function") {
|
|
7680
7996
|
const blob = new Blob([body], { type: "text/plain" });
|
|
7681
|
-
const sent = navigator.sendBeacon(
|
|
7997
|
+
const sent = navigator.sendBeacon(endpoint, blob);
|
|
7682
7998
|
if (sent) return;
|
|
7683
7999
|
}
|
|
7684
|
-
fetch(
|
|
8000
|
+
fetch(endpoint, {
|
|
7685
8001
|
method: "POST",
|
|
7686
8002
|
keepalive: true,
|
|
7687
8003
|
headers: { "Content-Type": "application/json" },
|