@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.js
CHANGED
|
@@ -97,6 +97,7 @@ var COMMERCE_ERROR_CODES = [
|
|
|
97
97
|
"payment_session_not_cancelable",
|
|
98
98
|
"payment_session_conflict",
|
|
99
99
|
"provider_identity_mismatch",
|
|
100
|
+
"managed_refund_attestation_forbidden",
|
|
100
101
|
"payment_method_unavailable",
|
|
101
102
|
"provider_unavailable",
|
|
102
103
|
"lock_timeout",
|
|
@@ -639,11 +640,11 @@ function parseRetryAfter(response) {
|
|
|
639
640
|
permitsAutomaticRetry: seconds <= MAX_AUTOMATIC_RETRY_AFTER_SECONDS
|
|
640
641
|
};
|
|
641
642
|
}
|
|
642
|
-
function mapResponseToError(status, parsed, details, requestId, retryAfter,
|
|
643
|
+
function mapResponseToError(status, parsed, details, requestId, retryAfter, canonicalCommerceError) {
|
|
643
644
|
const errorDetails = {
|
|
644
645
|
...details,
|
|
645
646
|
...parsed.errors && { errors: parsed.errors },
|
|
646
|
-
|
|
647
|
+
...!canonicalCommerceError && parsed.body && { body: parsed.body }
|
|
647
648
|
};
|
|
648
649
|
const suggestion = getParsedErrorSuggestion(status, parsed);
|
|
649
650
|
const finalize = (error) => {
|
|
@@ -651,7 +652,7 @@ function mapResponseToError(status, parsed, details, requestId, retryAfter, erro
|
|
|
651
652
|
if (retryAfter !== void 0) {
|
|
652
653
|
Object.assign(withRequestId, { retryAfter });
|
|
653
654
|
}
|
|
654
|
-
return
|
|
655
|
+
return canonicalCommerceError ? enhanceCommerceError(withRequestId, parsed.body, requestId) : enhanceNonCommerceError(withRequestId, parsed.reason);
|
|
655
656
|
};
|
|
656
657
|
if (status === 400 || status === 422) {
|
|
657
658
|
return finalize(
|
|
@@ -757,6 +758,7 @@ async function httpFetch(url, options) {
|
|
|
757
758
|
retry,
|
|
758
759
|
onUnauthorized,
|
|
759
760
|
errorFamily,
|
|
761
|
+
typedCommerceErrorReasons,
|
|
760
762
|
...requestInit
|
|
761
763
|
} = options || {};
|
|
762
764
|
const baseUrl = resolveApiUrl(apiUrl);
|
|
@@ -810,7 +812,8 @@ async function httpFetch(url, options) {
|
|
|
810
812
|
});
|
|
811
813
|
if (!response.ok) {
|
|
812
814
|
const parsed = await parseErrorBody(response);
|
|
813
|
-
const
|
|
815
|
+
const canonicalCommerceError = errorFamily === "commerce" || typeof parsed.reason === "string" && typedCommerceErrorReasons?.includes(parsed.reason) === true;
|
|
816
|
+
const commerceEnvelope = canonicalCommerceError ? parseCommerceErrorEnvelope(parsed.body, requestId) : void 0;
|
|
814
817
|
if (isUsageLimitExceededResponse(response)) {
|
|
815
818
|
const limit = parseInt(
|
|
816
819
|
response.headers.get("X-Usage-Limit") || "0",
|
|
@@ -838,7 +841,7 @@ async function httpFetch(url, options) {
|
|
|
838
841
|
),
|
|
839
842
|
requestId
|
|
840
843
|
);
|
|
841
|
-
throw
|
|
844
|
+
throw canonicalCommerceError ? enhanceCommerceError(usageError, parsed.body, requestId) : enhanceNonCommerceError(usageError, parsed.reason);
|
|
842
845
|
}
|
|
843
846
|
if (response.status === 401 && onUnauthorized && customerToken && !hasRetried401 && (commerceEnvelope?.reason ?? parsed.reason) === "token_expired") {
|
|
844
847
|
hasRetried401 = true;
|
|
@@ -863,7 +866,7 @@ async function httpFetch(url, options) {
|
|
|
863
866
|
details,
|
|
864
867
|
requestId,
|
|
865
868
|
retryAfter.seconds,
|
|
866
|
-
|
|
869
|
+
canonicalCommerceError
|
|
867
870
|
);
|
|
868
871
|
const method2 = (requestInit.method || "GET").toUpperCase();
|
|
869
872
|
const isTerminal = error instanceof RateLimitError || error instanceof UsageLimitError;
|
|
@@ -1041,15 +1044,15 @@ var HttpClient = class {
|
|
|
1041
1044
|
throw err;
|
|
1042
1045
|
}
|
|
1043
1046
|
}
|
|
1044
|
-
buildUrl(
|
|
1045
|
-
if (!options) return
|
|
1047
|
+
buildUrl(endpoint, options) {
|
|
1048
|
+
if (!options) return endpoint;
|
|
1046
1049
|
const { fallbackLocale, ...rest } = options;
|
|
1047
1050
|
const queryOptions = fallbackLocale === void 0 ? rest : {
|
|
1048
1051
|
...rest,
|
|
1049
1052
|
"fallback-locale": fallbackLocale === false ? "none" : fallbackLocale
|
|
1050
1053
|
};
|
|
1051
1054
|
const queryString = stringify(queryOptions, { addQueryPrefix: true });
|
|
1052
|
-
return queryString ? `${
|
|
1055
|
+
return queryString ? `${endpoint}${queryString}` : endpoint;
|
|
1053
1056
|
}
|
|
1054
1057
|
assertJsonResponse(response) {
|
|
1055
1058
|
const contentType = response.headers.get("content-type");
|
|
@@ -1198,39 +1201,39 @@ var ReadOnlyCollectionClient = class extends HttpClient {
|
|
|
1198
1201
|
from(collection) {
|
|
1199
1202
|
return new ReadOnlyCollectionQueryBuilder(this, collection);
|
|
1200
1203
|
}
|
|
1201
|
-
async requestFind(
|
|
1202
|
-
const url = this.buildUrl(
|
|
1204
|
+
async requestFind(endpoint, options) {
|
|
1205
|
+
const url = this.buildUrl(endpoint, options);
|
|
1203
1206
|
const response = await this.fetchWithTracking(url, {
|
|
1204
1207
|
...this.defaultOptions,
|
|
1205
1208
|
method: "GET"
|
|
1206
1209
|
});
|
|
1207
1210
|
return this.parseFindResponse(response);
|
|
1208
1211
|
}
|
|
1209
|
-
async requestFindEndpoint(
|
|
1210
|
-
const response = await this.fetchWithTracking(
|
|
1212
|
+
async requestFindEndpoint(endpoint, data) {
|
|
1213
|
+
const response = await this.fetchWithTracking(endpoint, {
|
|
1211
1214
|
...this.defaultOptions,
|
|
1212
1215
|
method: "POST",
|
|
1213
1216
|
body: data ? JSON.stringify(data) : void 0
|
|
1214
1217
|
});
|
|
1215
1218
|
return this.parseFindResponse(response);
|
|
1216
1219
|
}
|
|
1217
|
-
async requestFindEndpointGet(
|
|
1218
|
-
const response = await this.fetchWithTracking(
|
|
1220
|
+
async requestFindEndpointGet(endpoint) {
|
|
1221
|
+
const response = await this.fetchWithTracking(endpoint, {
|
|
1219
1222
|
...this.defaultOptions,
|
|
1220
1223
|
method: "GET"
|
|
1221
1224
|
});
|
|
1222
1225
|
return this.parseFindResponse(response);
|
|
1223
1226
|
}
|
|
1224
|
-
async requestFindById(
|
|
1225
|
-
const url = this.buildUrl(
|
|
1227
|
+
async requestFindById(endpoint, options) {
|
|
1228
|
+
const url = this.buildUrl(endpoint, options);
|
|
1226
1229
|
const response = await this.fetchWithTracking(url, {
|
|
1227
1230
|
...this.defaultOptions,
|
|
1228
1231
|
method: "GET"
|
|
1229
1232
|
});
|
|
1230
1233
|
return this.parseDocumentResponse(response);
|
|
1231
1234
|
}
|
|
1232
|
-
async requestCount(
|
|
1233
|
-
const url = this.buildUrl(
|
|
1235
|
+
async requestCount(endpoint, options) {
|
|
1236
|
+
const url = this.buildUrl(endpoint, options);
|
|
1234
1237
|
const response = await this.fetchWithTracking(url, {
|
|
1235
1238
|
...this.defaultOptions,
|
|
1236
1239
|
method: "GET"
|
|
@@ -1240,7 +1243,7 @@ var ReadOnlyCollectionClient = class extends HttpClient {
|
|
|
1240
1243
|
};
|
|
1241
1244
|
|
|
1242
1245
|
// src/core/api/parse-response.ts
|
|
1243
|
-
async function parseApiResponse(response,
|
|
1246
|
+
async function parseApiResponse(response, endpoint, options = {}) {
|
|
1244
1247
|
if (response.status === 204 || response.headers.get("content-length") === "0") {
|
|
1245
1248
|
return void 0;
|
|
1246
1249
|
}
|
|
@@ -1249,7 +1252,7 @@ async function parseApiResponse(response, endpoint2, options = {}) {
|
|
|
1249
1252
|
parsed = await response.json();
|
|
1250
1253
|
} catch {
|
|
1251
1254
|
throw createApiError(
|
|
1252
|
-
`Invalid JSON response from ${
|
|
1255
|
+
`Invalid JSON response from ${endpoint}`,
|
|
1253
1256
|
response.status,
|
|
1254
1257
|
void 0,
|
|
1255
1258
|
"Server returned an invalid response.",
|
|
@@ -1299,6 +1302,7 @@ async function parseApiResponse(response, endpoint2, options = {}) {
|
|
|
1299
1302
|
|
|
1300
1303
|
// ../../internal/http-contracts/src/index.ts
|
|
1301
1304
|
var operationBrand = /* @__PURE__ */ Symbol("sdk-http-operation");
|
|
1305
|
+
var PORTONE_STANDARD_ACTION_CAPABILITIES_MEDIA_TYPE = "application/vnd.01software.portone-action-capabilities.v1+json";
|
|
1302
1306
|
var operation = (collection, method, path, errorFamily) => Object.freeze({
|
|
1303
1307
|
[operationBrand]: true,
|
|
1304
1308
|
collection,
|
|
@@ -1307,6 +1311,10 @@ var operation = (collection, method, path, errorFamily) => Object.freeze({
|
|
|
1307
1311
|
...errorFamily && { errorFamily }
|
|
1308
1312
|
});
|
|
1309
1313
|
var strictCommerceOperation = (collection, method, path) => operation(collection, method, path, "commerce");
|
|
1314
|
+
var selectiveCommerceOperation = (collection, method, path, typedCommerceErrorReasons) => Object.freeze({
|
|
1315
|
+
...operation(collection, method, path),
|
|
1316
|
+
typedCommerceErrorReasons: Object.freeze([...typedCommerceErrorReasons])
|
|
1317
|
+
});
|
|
1310
1318
|
var COMMERCE_ENDPOINT_OPERATIONS = {
|
|
1311
1319
|
products: {
|
|
1312
1320
|
detail: operation("products", "get", "/detail"),
|
|
@@ -1338,7 +1346,12 @@ var COMMERCE_ENDPOINT_OPERATIONS = {
|
|
|
1338
1346
|
confirmPayment: operation("orders", "post", "/confirm-payment"),
|
|
1339
1347
|
byPayment: operation("orders", "get", "/by-payment"),
|
|
1340
1348
|
cancel: operation("orders", "post", "/cancel"),
|
|
1341
|
-
resolveCancelRefund:
|
|
1349
|
+
resolveCancelRefund: selectiveCommerceOperation(
|
|
1350
|
+
"orders",
|
|
1351
|
+
"post",
|
|
1352
|
+
"/resolve-cancel-refund",
|
|
1353
|
+
["managed_refund_attestation_forbidden"]
|
|
1354
|
+
),
|
|
1342
1355
|
checkout: strictCommerceOperation("orders", "post", "/checkout"),
|
|
1343
1356
|
createFulfillment: operation("orders", "post", "/create-fulfillment"),
|
|
1344
1357
|
updateFulfillment: operation("orders", "post", "/update-fulfillment")
|
|
@@ -1347,6 +1360,11 @@ var COMMERCE_ENDPOINT_OPERATIONS = {
|
|
|
1347
1360
|
detail: strictCommerceOperation("checkouts", "post", "/detail"),
|
|
1348
1361
|
query: strictCommerceOperation("checkouts", "post", "/query")
|
|
1349
1362
|
},
|
|
1363
|
+
draftOrders: {
|
|
1364
|
+
calculate: operation("draft-orders", "post", "/:id/calculate"),
|
|
1365
|
+
complete: operation("draft-orders", "post", "/:id/complete"),
|
|
1366
|
+
finalize: operation("draft-orders", "post", "/:id/finalize")
|
|
1367
|
+
},
|
|
1350
1368
|
transactions: {
|
|
1351
1369
|
update: operation("transactions", "post", "/update")
|
|
1352
1370
|
},
|
|
@@ -1409,6 +1427,16 @@ var COMMERCE_ENDPOINT_OPERATIONS = {
|
|
|
1409
1427
|
"payment-sessions",
|
|
1410
1428
|
"post",
|
|
1411
1429
|
"/server/confirm"
|
|
1430
|
+
),
|
|
1431
|
+
findPortOne: strictCommerceOperation(
|
|
1432
|
+
"payment-sessions",
|
|
1433
|
+
"post",
|
|
1434
|
+
"/server/portone/find"
|
|
1435
|
+
),
|
|
1436
|
+
reconcilePortOne: strictCommerceOperation(
|
|
1437
|
+
"payment-sessions",
|
|
1438
|
+
"post",
|
|
1439
|
+
"/server/portone/reconcile"
|
|
1412
1440
|
)
|
|
1413
1441
|
},
|
|
1414
1442
|
custom: {
|
|
@@ -1428,12 +1456,34 @@ var COMMERCE_ENDPOINT_OPERATIONS = {
|
|
|
1428
1456
|
validate: operation("discounts", "post", "/validate")
|
|
1429
1457
|
}
|
|
1430
1458
|
};
|
|
1459
|
+
var portOneStandardActionCapabilityOperations = /* @__PURE__ */ new Set([
|
|
1460
|
+
COMMERCE_ENDPOINT_OPERATIONS.paymentSessions.create,
|
|
1461
|
+
COMMERCE_ENDPOINT_OPERATIONS.paymentSessions.retrieve,
|
|
1462
|
+
COMMERCE_ENDPOINT_OPERATIONS.paymentSessions.server.create,
|
|
1463
|
+
COMMERCE_ENDPOINT_OPERATIONS.paymentSessions.server.retrieve
|
|
1464
|
+
]);
|
|
1431
1465
|
var MEMBERSHIP_ENDPOINT_OPERATIONS = {
|
|
1432
1466
|
subscribe: operation(null, "post", "/memberships/subscribe"),
|
|
1433
1467
|
cancel: operation(null, "post", "/memberships/:id/cancel"),
|
|
1434
|
-
me: operation(null, "get", "/memberships/me")
|
|
1468
|
+
me: operation(null, "get", "/memberships/me"),
|
|
1469
|
+
chargeResult: operation(null, "post", "/memberships/:id/charge-result"),
|
|
1470
|
+
refundResult: operation(null, "post", "/memberships/:id/refund-result"),
|
|
1471
|
+
revoke: operation(null, "post", "/memberships/:id/revoke")
|
|
1472
|
+
};
|
|
1473
|
+
var CUSTOMER_GROUP_ENDPOINT_OPERATIONS = {
|
|
1474
|
+
members: operation("customer-groups", "get", "/:id/members"),
|
|
1475
|
+
grant: operation("customer-groups", "post", "/:id/members/grant"),
|
|
1476
|
+
revoke: operation("customer-groups", "post", "/:id/members/revoke")
|
|
1477
|
+
};
|
|
1478
|
+
var EMBEDDED_ADMIN_ENDPOINT_OPERATIONS = {
|
|
1479
|
+
show: operation("tenants", "get", "/embedded-admin-app"),
|
|
1480
|
+
configure: operation("tenants", "patch", "/embedded-admin-app"),
|
|
1481
|
+
disable: operation("tenants", "delete", "/embedded-admin-app")
|
|
1435
1482
|
};
|
|
1436
1483
|
var EVENTS_ENDPOINT_OPERATIONS = {
|
|
1484
|
+
events: {
|
|
1485
|
+
convertSchedule: operation("events", "post", "/:id/schedule")
|
|
1486
|
+
},
|
|
1437
1487
|
occurrences: {
|
|
1438
1488
|
range: operation("event-occurrences", "get", "/range"),
|
|
1439
1489
|
admissionAvailability: operation(
|
|
@@ -1474,46 +1524,51 @@ function resolveOperationMethod(descriptor) {
|
|
|
1474
1524
|
}
|
|
1475
1525
|
|
|
1476
1526
|
// src/core/api/endpoints.ts
|
|
1477
|
-
var endpoint = (method, path) => ({ method, path });
|
|
1478
1527
|
var sharedEndpoint = (descriptor) => descriptor;
|
|
1479
|
-
function resolveOperationMethod2(descriptor) {
|
|
1480
|
-
return "collection" in descriptor ? resolveOperationMethod(descriptor) : descriptor.method;
|
|
1481
|
-
}
|
|
1482
1528
|
function resolveOperationErrorFamily(descriptor) {
|
|
1483
|
-
return
|
|
1484
|
-
}
|
|
1485
|
-
function resolveOperationPath2(descriptor, options) {
|
|
1486
|
-
if ("collection" in descriptor) {
|
|
1487
|
-
return resolveOperationPath(descriptor, options);
|
|
1488
|
-
}
|
|
1489
|
-
const query = options?.query?.toString().replace(/^\?/, "");
|
|
1490
|
-
return query ? `${descriptor.path}?${query}` : descriptor.path;
|
|
1529
|
+
return descriptor.errorFamily;
|
|
1491
1530
|
}
|
|
1492
1531
|
var COMMERCE_ENDPOINTS = {
|
|
1493
1532
|
products: {
|
|
1494
|
-
detail:
|
|
1495
|
-
detailCatalog:
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1533
|
+
detail: sharedEndpoint(COMMERCE_ENDPOINT_OPERATIONS.products.detail),
|
|
1534
|
+
detailCatalog: sharedEndpoint(
|
|
1535
|
+
COMMERCE_ENDPOINT_OPERATIONS.products.detailCatalog
|
|
1536
|
+
),
|
|
1537
|
+
listingGroups: sharedEndpoint(
|
|
1538
|
+
COMMERCE_ENDPOINT_OPERATIONS.products.listingGroups
|
|
1539
|
+
),
|
|
1540
|
+
listingGroupsCatalog: sharedEndpoint(
|
|
1541
|
+
COMMERCE_ENDPOINT_OPERATIONS.products.listingGroupsCatalog
|
|
1542
|
+
),
|
|
1543
|
+
// The manifest keeps the GET read and the POST body-query apart; this
|
|
1544
|
+
// semantic name has always resolved to the GET one.
|
|
1545
|
+
listingGroupsQuery: sharedEndpoint(
|
|
1546
|
+
COMMERCE_ENDPOINT_OPERATIONS.products.listingGroupsQueryGet
|
|
1547
|
+
),
|
|
1548
|
+
listingGroupsQueryCatalog: sharedEndpoint(
|
|
1549
|
+
COMMERCE_ENDPOINT_OPERATIONS.products.listingGroupsQueryCatalog
|
|
1500
1550
|
),
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
"GET",
|
|
1504
|
-
"/api/products/listing-groups/query/catalog"
|
|
1551
|
+
stockCheck: sharedEndpoint(
|
|
1552
|
+
COMMERCE_ENDPOINT_OPERATIONS.products.stockCheck
|
|
1505
1553
|
),
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1554
|
+
stockSnapshot: sharedEndpoint(
|
|
1555
|
+
COMMERCE_ENDPOINT_OPERATIONS.products.stockSnapshot
|
|
1556
|
+
),
|
|
1557
|
+
upsert: sharedEndpoint(COMMERCE_ENDPOINT_OPERATIONS.products.upsert)
|
|
1509
1558
|
},
|
|
1510
1559
|
orders: {
|
|
1511
|
-
update:
|
|
1512
|
-
byPayment:
|
|
1513
|
-
cancel:
|
|
1514
|
-
resolveCancelRefund:
|
|
1515
|
-
|
|
1516
|
-
|
|
1560
|
+
update: sharedEndpoint(COMMERCE_ENDPOINT_OPERATIONS.orders.update),
|
|
1561
|
+
byPayment: sharedEndpoint(COMMERCE_ENDPOINT_OPERATIONS.orders.byPayment),
|
|
1562
|
+
cancel: sharedEndpoint(COMMERCE_ENDPOINT_OPERATIONS.orders.cancel),
|
|
1563
|
+
resolveCancelRefund: sharedEndpoint(
|
|
1564
|
+
COMMERCE_ENDPOINT_OPERATIONS.orders.resolveCancelRefund
|
|
1565
|
+
),
|
|
1566
|
+
createFulfillment: sharedEndpoint(
|
|
1567
|
+
COMMERCE_ENDPOINT_OPERATIONS.orders.createFulfillment
|
|
1568
|
+
),
|
|
1569
|
+
updateFulfillment: sharedEndpoint(
|
|
1570
|
+
COMMERCE_ENDPOINT_OPERATIONS.orders.updateFulfillment
|
|
1571
|
+
)
|
|
1517
1572
|
},
|
|
1518
1573
|
checkouts: {
|
|
1519
1574
|
create: sharedEndpoint(COMMERCE_ENDPOINT_OPERATIONS.orders.checkout),
|
|
@@ -1525,18 +1580,21 @@ var COMMERCE_ENDPOINTS = {
|
|
|
1525
1580
|
}
|
|
1526
1581
|
},
|
|
1527
1582
|
returns: {
|
|
1528
|
-
create:
|
|
1529
|
-
update:
|
|
1530
|
-
returnRefund:
|
|
1583
|
+
create: sharedEndpoint(COMMERCE_ENDPOINT_OPERATIONS.returns.create),
|
|
1584
|
+
update: sharedEndpoint(COMMERCE_ENDPOINT_OPERATIONS.returns.update),
|
|
1585
|
+
returnRefund: sharedEndpoint(
|
|
1586
|
+
COMMERCE_ENDPOINT_OPERATIONS.returns.returnRefund
|
|
1587
|
+
)
|
|
1531
1588
|
},
|
|
1532
1589
|
fulfillmentOrders: {
|
|
1533
|
-
prepare:
|
|
1534
|
-
|
|
1535
|
-
"/api/fulfillment-orders/prepare-fulfillment-order"
|
|
1590
|
+
prepare: sharedEndpoint(
|
|
1591
|
+
COMMERCE_ENDPOINT_OPERATIONS.fulfillmentOrders.prepare
|
|
1536
1592
|
)
|
|
1537
1593
|
},
|
|
1538
1594
|
fulfillments: {
|
|
1539
|
-
bulkImport:
|
|
1595
|
+
bulkImport: sharedEndpoint(
|
|
1596
|
+
COMMERCE_ENDPOINT_OPERATIONS.fulfillments.bulkImport
|
|
1597
|
+
)
|
|
1540
1598
|
},
|
|
1541
1599
|
carts: {
|
|
1542
1600
|
create: sharedEndpoint(COMMERCE_ENDPOINT_OPERATIONS.carts.create),
|
|
@@ -1601,42 +1659,21 @@ var COMMERCE_ENDPOINTS = {
|
|
|
1601
1659
|
),
|
|
1602
1660
|
confirm: sharedEndpoint(
|
|
1603
1661
|
COMMERCE_ENDPOINT_OPERATIONS.paymentSessions.server.confirm
|
|
1662
|
+
),
|
|
1663
|
+
findPortOne: sharedEndpoint(
|
|
1664
|
+
COMMERCE_ENDPOINT_OPERATIONS.paymentSessions.server.findPortOne
|
|
1665
|
+
),
|
|
1666
|
+
reconcilePortOne: sharedEndpoint(
|
|
1667
|
+
COMMERCE_ENDPOINT_OPERATIONS.paymentSessions.server.reconcilePortOne
|
|
1604
1668
|
)
|
|
1605
1669
|
}
|
|
1606
1670
|
},
|
|
1607
1671
|
shippingPolicies: {
|
|
1608
|
-
calculate:
|
|
1672
|
+
calculate: sharedEndpoint(
|
|
1673
|
+
COMMERCE_ENDPOINT_OPERATIONS.shippingPolicies.calculate
|
|
1674
|
+
)
|
|
1609
1675
|
}
|
|
1610
1676
|
};
|
|
1611
|
-
function isCommerceEndpointDescriptor(value) {
|
|
1612
|
-
if (!value || typeof value !== "object") return false;
|
|
1613
|
-
const candidate = value;
|
|
1614
|
-
return typeof candidate.method === "string" && typeof candidate.path === "string";
|
|
1615
|
-
}
|
|
1616
|
-
function flattenEndpointPaths(value) {
|
|
1617
|
-
if (isCommerceEndpointDescriptor(value)) return [resolveOperationPath2(value)];
|
|
1618
|
-
if (!value || typeof value !== "object") return [];
|
|
1619
|
-
return Object.values(value).flatMap((entry) => flattenEndpointPaths(entry));
|
|
1620
|
-
}
|
|
1621
|
-
function flattenEndpointDescriptors(value) {
|
|
1622
|
-
if (isCommerceEndpointDescriptor(value)) return [value];
|
|
1623
|
-
if (!value || typeof value !== "object") return [];
|
|
1624
|
-
return Object.values(value).flatMap(
|
|
1625
|
-
(entry) => flattenEndpointDescriptors(entry)
|
|
1626
|
-
);
|
|
1627
|
-
}
|
|
1628
|
-
var COMMERCE_ENDPOINT_PATHS = flattenEndpointPaths(COMMERCE_ENDPOINTS);
|
|
1629
|
-
var COMMERCE_ENDPOINT_DESCRIPTORS = flattenEndpointDescriptors(COMMERCE_ENDPOINTS);
|
|
1630
|
-
var MEMBERSHIP_ENDPOINTS = {
|
|
1631
|
-
subscribe: "/api/memberships/subscribe",
|
|
1632
|
-
cancel: (id) => `/api/memberships/${id}/cancel`,
|
|
1633
|
-
me: "/api/memberships/me"
|
|
1634
|
-
};
|
|
1635
|
-
var MEMBERSHIP_ENDPOINT_PATHS = [
|
|
1636
|
-
MEMBERSHIP_ENDPOINTS.subscribe,
|
|
1637
|
-
MEMBERSHIP_ENDPOINTS.cancel(":id"),
|
|
1638
|
-
MEMBERSHIP_ENDPOINTS.me
|
|
1639
|
-
];
|
|
1640
1677
|
|
|
1641
1678
|
// src/core/api/customer-scoped-api.ts
|
|
1642
1679
|
var CustomerScopedTransport = class {
|
|
@@ -1657,10 +1694,10 @@ var CustomerScopedTransport = class {
|
|
|
1657
1694
|
this.onUnauthorized = options.onUnauthorized;
|
|
1658
1695
|
this.onRequestId = options.onRequestId;
|
|
1659
1696
|
}
|
|
1660
|
-
async request(
|
|
1697
|
+
async request(endpoint, options) {
|
|
1661
1698
|
const token = typeof this.customerToken === "function" ? this.customerToken() : this.customerToken;
|
|
1662
1699
|
try {
|
|
1663
|
-
const response = await httpFetch(
|
|
1700
|
+
const response = await httpFetch(endpoint, {
|
|
1664
1701
|
method: options.method,
|
|
1665
1702
|
apiUrl: this.apiUrl,
|
|
1666
1703
|
publishableKey: this.publishableKey,
|
|
@@ -1674,7 +1711,7 @@ var CustomerScopedTransport = class {
|
|
|
1674
1711
|
...options.errorFamily && { errorFamily: options.errorFamily }
|
|
1675
1712
|
});
|
|
1676
1713
|
this.onRequestId?.(response.headers.get("x-request-id") ?? null);
|
|
1677
|
-
return parseApiResponse(response,
|
|
1714
|
+
return parseApiResponse(response, endpoint, {
|
|
1678
1715
|
errorFamily: options.errorFamily
|
|
1679
1716
|
});
|
|
1680
1717
|
} catch (err) {
|
|
@@ -1688,12 +1725,12 @@ var CustomerScopedApi = class {
|
|
|
1688
1725
|
constructor(apiName, options) {
|
|
1689
1726
|
this.transport = new CustomerScopedTransport(apiName, options);
|
|
1690
1727
|
}
|
|
1691
|
-
requestEndpoint(
|
|
1692
|
-
return this.transport.request(
|
|
1693
|
-
method:
|
|
1728
|
+
requestEndpoint(endpoint, body, options) {
|
|
1729
|
+
return this.transport.request(resolveOperationPath(endpoint, options), {
|
|
1730
|
+
method: resolveOperationMethod(endpoint),
|
|
1694
1731
|
body,
|
|
1695
1732
|
headers: options?.headers,
|
|
1696
|
-
errorFamily: resolveOperationErrorFamily(
|
|
1733
|
+
errorFamily: resolveOperationErrorFamily(endpoint)
|
|
1697
1734
|
});
|
|
1698
1735
|
}
|
|
1699
1736
|
};
|
|
@@ -1701,8 +1738,8 @@ var RawCustomerScopedApi = class {
|
|
|
1701
1738
|
constructor(apiName, options) {
|
|
1702
1739
|
this.transport = new CustomerScopedTransport(apiName, options);
|
|
1703
1740
|
}
|
|
1704
|
-
request(
|
|
1705
|
-
return this.transport.request(
|
|
1741
|
+
request(endpoint, options) {
|
|
1742
|
+
return this.transport.request(endpoint, options);
|
|
1706
1743
|
}
|
|
1707
1744
|
};
|
|
1708
1745
|
|
|
@@ -1777,11 +1814,11 @@ var CommunityClient = class extends RawCustomerScopedApi {
|
|
|
1777
1814
|
if (params.page !== void 0) urlParams.set("page", String(params.page));
|
|
1778
1815
|
return `/api/comments/public?${urlParams.toString()}`;
|
|
1779
1816
|
}
|
|
1780
|
-
async execute(
|
|
1781
|
-
return this.request(
|
|
1817
|
+
async execute(endpoint, method, body) {
|
|
1818
|
+
return this.request(endpoint, { method, body });
|
|
1782
1819
|
}
|
|
1783
|
-
async executeDoc(
|
|
1784
|
-
const response = await this.execute(
|
|
1820
|
+
async executeDoc(endpoint, method, body) {
|
|
1821
|
+
const response = await this.execute(endpoint, method, body);
|
|
1785
1822
|
return unwrapPayloadDoc(response);
|
|
1786
1823
|
}
|
|
1787
1824
|
createPost(params) {
|
|
@@ -2047,8 +2084,8 @@ var CommunityClient = class extends RawCustomerScopedApi {
|
|
|
2047
2084
|
|
|
2048
2085
|
// src/core/content/content-client.ts
|
|
2049
2086
|
var PublicContentApi = class extends RawCustomerScopedApi {
|
|
2050
|
-
get(
|
|
2051
|
-
return this.request(
|
|
2087
|
+
get(endpoint) {
|
|
2088
|
+
return this.request(endpoint, { method: "GET" });
|
|
2052
2089
|
}
|
|
2053
2090
|
};
|
|
2054
2091
|
function appendCommonParams(search, options, defaultSort) {
|
|
@@ -2146,7 +2183,10 @@ var ORDER_PRIMARY_DISPLAY_STATUSES = [
|
|
|
2146
2183
|
];
|
|
2147
2184
|
var CHECKOUT_STATUSES = ["open", "placed", "abandoned"];
|
|
2148
2185
|
var ABANDONED_REASONS = ["expired", "customer", "operator"];
|
|
2149
|
-
|
|
2186
|
+
function managedPaymentProviders(providers) {
|
|
2187
|
+
return providers;
|
|
2188
|
+
}
|
|
2189
|
+
var PAYMENT_PROVIDERS = managedPaymentProviders(["toss", "portone"]);
|
|
2150
2190
|
var PAYMENT_PROVIDER_STATUSES = [
|
|
2151
2191
|
"requires_payment_method",
|
|
2152
2192
|
"requires_action",
|
|
@@ -2194,6 +2234,26 @@ function invalidCanonicalResponse(resource, details) {
|
|
|
2194
2234
|
function fail(resource, path, expected) {
|
|
2195
2235
|
invalidCanonicalResponse(resource, { path, expected });
|
|
2196
2236
|
}
|
|
2237
|
+
function providerMismatch(resource, expected, actual, operation2) {
|
|
2238
|
+
if (operation2 === "cancel") {
|
|
2239
|
+
const method = {
|
|
2240
|
+
toss: "cancel()",
|
|
2241
|
+
portone: "cancelPortOne()"
|
|
2242
|
+
}[actual];
|
|
2243
|
+
throw createApiError(
|
|
2244
|
+
`Canonical ${resource} response provider mismatch: expected ${expected}, received ${actual}.`,
|
|
2245
|
+
502,
|
|
2246
|
+
{ resource, path: "$.provider", expected, actual },
|
|
2247
|
+
`The server applied the cancellation to a ${actual} PaymentSession, but this SDK method expects ${expected}.`,
|
|
2248
|
+
`Re-invoke ${method} with the same idempotencyKey; do not create a new key.`
|
|
2249
|
+
);
|
|
2250
|
+
}
|
|
2251
|
+
invalidCanonicalResponse(resource, {
|
|
2252
|
+
path: "$.provider",
|
|
2253
|
+
expected,
|
|
2254
|
+
actual
|
|
2255
|
+
});
|
|
2256
|
+
}
|
|
2197
2257
|
function record(value, resource, path) {
|
|
2198
2258
|
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
2199
2259
|
fail(resource, path, "object");
|
|
@@ -2229,6 +2289,9 @@ function nullableString(value, resource, path) {
|
|
|
2229
2289
|
fail(resource, path, "string or null");
|
|
2230
2290
|
}
|
|
2231
2291
|
}
|
|
2292
|
+
function nullableNonEmptyString(value, resource, path) {
|
|
2293
|
+
if (value !== null) string(value, resource, path);
|
|
2294
|
+
}
|
|
2232
2295
|
function cartLineQuantity(value, resource, path) {
|
|
2233
2296
|
const quantity = positiveInteger(value, resource, path);
|
|
2234
2297
|
if (quantity > MAX_CART_LINE_QUANTITY) {
|
|
@@ -2738,8 +2801,8 @@ function validatePublicOrderItem(value, resource, path) {
|
|
|
2738
2801
|
validateReference(item.variant, resource, `${path}.variant`);
|
|
2739
2802
|
}
|
|
2740
2803
|
string(item.productTitle, resource, `${path}.productTitle`);
|
|
2741
|
-
|
|
2742
|
-
|
|
2804
|
+
nullableNonEmptyString(item.variantTitle, resource, `${path}.variantTitle`);
|
|
2805
|
+
nullableNonEmptyString(item.sku, resource, `${path}.sku`);
|
|
2743
2806
|
validateAdmissions(item.admissions, resource, `${path}.admissions`);
|
|
2744
2807
|
const quantity = positiveInteger(item.quantity, resource, `${path}.quantity`);
|
|
2745
2808
|
const unitPrice = safeMinor(
|
|
@@ -2990,8 +3053,8 @@ function validateCheckoutItem(value, resource, path) {
|
|
|
2990
3053
|
validateReference(item.product, resource, `${path}.product`);
|
|
2991
3054
|
validateReference(item.variant, resource, `${path}.variant`);
|
|
2992
3055
|
string(item.productTitle, resource, `${path}.productTitle`);
|
|
2993
|
-
|
|
2994
|
-
|
|
3056
|
+
nullableNonEmptyString(item.variantTitle, resource, `${path}.variantTitle`);
|
|
3057
|
+
nullableNonEmptyString(item.sku, resource, `${path}.sku`);
|
|
2995
3058
|
validateAdmissions(item.admissions, resource, `${path}.admissions`);
|
|
2996
3059
|
cartLineQuantity(item.quantity, resource, `${path}.quantity`);
|
|
2997
3060
|
const unitPrice = safeMinor(
|
|
@@ -3323,11 +3386,21 @@ function validateNextAction(value, provider, resource, path) {
|
|
|
3323
3386
|
const action = record(value, resource, path);
|
|
3324
3387
|
if (provider === "portone") {
|
|
3325
3388
|
if (action.type === "portone_sdk") {
|
|
3389
|
+
const hasCancelable = "cancelable" in action;
|
|
3390
|
+
const hasRetryPolicy = "retryPolicy" in action;
|
|
3391
|
+
if (hasCancelable !== hasRetryPolicy) {
|
|
3392
|
+
fail(
|
|
3393
|
+
resource,
|
|
3394
|
+
path,
|
|
3395
|
+
"exact released action or exact negotiated capability action"
|
|
3396
|
+
);
|
|
3397
|
+
}
|
|
3326
3398
|
exact(
|
|
3327
3399
|
action,
|
|
3328
3400
|
[
|
|
3329
3401
|
"type",
|
|
3330
3402
|
"version",
|
|
3403
|
+
...hasCancelable ? ["cancelable", "retryPolicy"] : [],
|
|
3331
3404
|
"storeId",
|
|
3332
3405
|
"channelKey",
|
|
3333
3406
|
"paymentId",
|
|
@@ -3342,6 +3415,11 @@ function validateNextAction(value, provider, resource, path) {
|
|
|
3342
3415
|
path
|
|
3343
3416
|
);
|
|
3344
3417
|
if (action.version !== "v2") fail(resource, `${path}.version`, "v2");
|
|
3418
|
+
if (hasCancelable && action.cancelable !== false)
|
|
3419
|
+
fail(resource, `${path}.cancelable`, "false");
|
|
3420
|
+
if (hasRetryPolicy && action.retryPolicy !== "same_action_only") {
|
|
3421
|
+
fail(resource, `${path}.retryPolicy`, "same_action_only");
|
|
3422
|
+
}
|
|
3345
3423
|
for (const key of ["storeId", "channelKey"]) {
|
|
3346
3424
|
boundedString(action[key], 255, resource, `${path}.${key}`);
|
|
3347
3425
|
}
|
|
@@ -3388,6 +3466,10 @@ function validateNextAction(value, provider, resource, path) {
|
|
|
3388
3466
|
"HTTPS or loopback URL without embedded credentials"
|
|
3389
3467
|
);
|
|
3390
3468
|
}
|
|
3469
|
+
if (!hasCancelable) {
|
|
3470
|
+
action.cancelable = false;
|
|
3471
|
+
action.retryPolicy = "same_action_only";
|
|
3472
|
+
}
|
|
3391
3473
|
return;
|
|
3392
3474
|
}
|
|
3393
3475
|
exact(action, ["type", "checkoutUrl"], [], resource, path);
|
|
@@ -3464,7 +3546,7 @@ function validateNextAction(value, provider, resource, path) {
|
|
|
3464
3546
|
if (action.customerEmail !== void 0)
|
|
3465
3547
|
validateCustomer({ email: action.customerEmail }, resource, path);
|
|
3466
3548
|
}
|
|
3467
|
-
function validatePublicPaymentSession(value, expectedProvider) {
|
|
3549
|
+
function validatePublicPaymentSession(value, expectedProvider, operation2) {
|
|
3468
3550
|
const resource = "PaymentSession";
|
|
3469
3551
|
const session = record(value, resource, "$");
|
|
3470
3552
|
const provider = enumValue(
|
|
@@ -3473,9 +3555,6 @@ function validatePublicPaymentSession(value, expectedProvider) {
|
|
|
3473
3555
|
resource,
|
|
3474
3556
|
"$.provider"
|
|
3475
3557
|
);
|
|
3476
|
-
if (expectedProvider !== void 0 && provider !== expectedProvider) {
|
|
3477
|
-
fail(resource, "$.provider", expectedProvider);
|
|
3478
|
-
}
|
|
3479
3558
|
const status = enumValue(
|
|
3480
3559
|
session.providerStatus,
|
|
3481
3560
|
PAYMENT_PROVIDER_STATUSES,
|
|
@@ -3679,44 +3758,128 @@ function validatePublicPaymentSession(value, expectedProvider) {
|
|
|
3679
3758
|
"$.cancellationReason"
|
|
3680
3759
|
);
|
|
3681
3760
|
}
|
|
3761
|
+
if (expectedProvider !== void 0 && provider !== expectedProvider) {
|
|
3762
|
+
if (operation2 === "cancel" && status !== "canceling" && status !== "canceled") {
|
|
3763
|
+
fail(
|
|
3764
|
+
resource,
|
|
3765
|
+
"$.providerStatus",
|
|
3766
|
+
"canceling or canceled after a successful cancellation"
|
|
3767
|
+
);
|
|
3768
|
+
}
|
|
3769
|
+
providerMismatch(resource, expectedProvider, provider, operation2);
|
|
3770
|
+
}
|
|
3682
3771
|
return value;
|
|
3683
3772
|
}
|
|
3684
3773
|
|
|
3685
3774
|
// src/core/customer/customer-auth.ts
|
|
3686
3775
|
var DEFAULT_TIMEOUT2 = 15e3;
|
|
3687
|
-
|
|
3776
|
+
var REFRESH_LOCK_TIMEOUT = DEFAULT_TIMEOUT2 * 3;
|
|
3777
|
+
var SESSION_RECORD_VERSION = 1;
|
|
3778
|
+
var STORAGE_UNUSABLE = /* @__PURE__ */ Symbol("storage-unusable");
|
|
3779
|
+
function canonicalApiIdentity(apiUrl) {
|
|
3688
3780
|
try {
|
|
3689
|
-
|
|
3781
|
+
const url = new URL(apiUrl);
|
|
3782
|
+
return `${url.protocol.toLowerCase()}//${url.host.toLowerCase()}${url.pathname.replace(/\/+$/, "")}`;
|
|
3783
|
+
} catch {
|
|
3784
|
+
return apiUrl.trim().toLowerCase().replace(/\/+$/, "");
|
|
3785
|
+
}
|
|
3786
|
+
}
|
|
3787
|
+
function buildRefreshLockName(apiUrl, publishableKey, storageKey) {
|
|
3788
|
+
return `01software:customer-auth-refresh:${canonicalApiIdentity(apiUrl)}:${publishableKey}:${storageKey}`;
|
|
3789
|
+
}
|
|
3790
|
+
function getRefreshLockRequester() {
|
|
3791
|
+
try {
|
|
3792
|
+
if (typeof navigator === "undefined") return null;
|
|
3793
|
+
const locks = navigator.locks;
|
|
3794
|
+
if (!locks || typeof locks.request !== "function") return null;
|
|
3795
|
+
return (name, options, callback) => locks.request(
|
|
3796
|
+
name,
|
|
3797
|
+
options,
|
|
3798
|
+
() => callback()
|
|
3799
|
+
);
|
|
3690
3800
|
} catch {
|
|
3691
3801
|
return null;
|
|
3692
3802
|
}
|
|
3693
3803
|
}
|
|
3804
|
+
function parseSessionRecord(raw) {
|
|
3805
|
+
try {
|
|
3806
|
+
const parsed = JSON.parse(raw);
|
|
3807
|
+
if (!parsed || typeof parsed !== "object") return null;
|
|
3808
|
+
const candidate = parsed;
|
|
3809
|
+
if (candidate.v !== SESSION_RECORD_VERSION) return null;
|
|
3810
|
+
if (typeof candidate.rev !== "number" || !Number.isFinite(candidate.rev)) {
|
|
3811
|
+
return null;
|
|
3812
|
+
}
|
|
3813
|
+
const token = candidate.token;
|
|
3814
|
+
const refreshToken = candidate.refreshToken;
|
|
3815
|
+
if (token !== null && typeof token !== "string") return null;
|
|
3816
|
+
if (refreshToken !== null && typeof refreshToken !== "string") return null;
|
|
3817
|
+
return {
|
|
3818
|
+
v: SESSION_RECORD_VERSION,
|
|
3819
|
+
rev: candidate.rev,
|
|
3820
|
+
token,
|
|
3821
|
+
refreshToken
|
|
3822
|
+
};
|
|
3823
|
+
} catch {
|
|
3824
|
+
return null;
|
|
3825
|
+
}
|
|
3826
|
+
}
|
|
3827
|
+
function readSessionRecord(recordKey, mirrorKey, mirrorRefreshKey) {
|
|
3828
|
+
let raw;
|
|
3829
|
+
try {
|
|
3830
|
+
raw = localStorage.getItem(recordKey);
|
|
3831
|
+
} catch {
|
|
3832
|
+
return STORAGE_UNUSABLE;
|
|
3833
|
+
}
|
|
3834
|
+
if (raw !== null) {
|
|
3835
|
+
const parsed = parseSessionRecord(raw);
|
|
3836
|
+
return parsed ? { record: parsed, stored: true } : STORAGE_UNUSABLE;
|
|
3837
|
+
}
|
|
3838
|
+
try {
|
|
3839
|
+
return {
|
|
3840
|
+
record: {
|
|
3841
|
+
v: SESSION_RECORD_VERSION,
|
|
3842
|
+
rev: 0,
|
|
3843
|
+
token: localStorage.getItem(mirrorKey),
|
|
3844
|
+
refreshToken: localStorage.getItem(mirrorRefreshKey)
|
|
3845
|
+
},
|
|
3846
|
+
stored: false
|
|
3847
|
+
};
|
|
3848
|
+
} catch {
|
|
3849
|
+
return STORAGE_UNUSABLE;
|
|
3850
|
+
}
|
|
3851
|
+
}
|
|
3694
3852
|
var CustomerAuth = class {
|
|
3695
3853
|
constructor(publishableKey, options, apiUrl) {
|
|
3696
3854
|
this.refreshPromise = null;
|
|
3855
|
+
/** Canonical record key; null unless persistence is active in a browser. */
|
|
3856
|
+
this.persistRecordKey = null;
|
|
3857
|
+
/** Advisory single-value access mirror, plus the retired refresh mirror. */
|
|
3858
|
+
this.persistMirrorKey = null;
|
|
3859
|
+
this.persistMirrorRefreshKey = null;
|
|
3860
|
+
/** Cross-context refresh lock name; null when there is no shared storage. */
|
|
3861
|
+
this.refreshLockName = null;
|
|
3862
|
+
/** Revision of the canonical record this instance last observed. */
|
|
3863
|
+
this.sessionRev = 0;
|
|
3697
3864
|
this.publishableKey = publishableKey;
|
|
3698
3865
|
this.baseUrl = resolveApiUrl(apiUrl);
|
|
3699
3866
|
const persist = options?.persist ?? true;
|
|
3700
3867
|
if (persist) {
|
|
3701
3868
|
const key = typeof persist === "string" ? persist : "customer-token";
|
|
3702
|
-
const refreshKey = `${key}:refresh`;
|
|
3703
3869
|
const isBrowser = typeof window !== "undefined";
|
|
3704
|
-
this.token =
|
|
3705
|
-
this.refreshTokenValue =
|
|
3706
|
-
|
|
3707
|
-
|
|
3708
|
-
|
|
3709
|
-
|
|
3710
|
-
|
|
3711
|
-
|
|
3712
|
-
|
|
3713
|
-
|
|
3714
|
-
|
|
3715
|
-
|
|
3716
|
-
|
|
3717
|
-
} catch {
|
|
3718
|
-
}
|
|
3719
|
-
} : void 0;
|
|
3870
|
+
this.token = null;
|
|
3871
|
+
this.refreshTokenValue = null;
|
|
3872
|
+
if (isBrowser) {
|
|
3873
|
+
this.persistRecordKey = `${key}:session`;
|
|
3874
|
+
this.persistMirrorKey = key;
|
|
3875
|
+
this.persistMirrorRefreshKey = `${key}:refresh`;
|
|
3876
|
+
this.refreshLockName = buildRefreshLockName(
|
|
3877
|
+
this.baseUrl,
|
|
3878
|
+
publishableKey,
|
|
3879
|
+
key
|
|
3880
|
+
);
|
|
3881
|
+
this.syncFromPersistedStorage();
|
|
3882
|
+
}
|
|
3720
3883
|
} else {
|
|
3721
3884
|
this.token = options?.token ?? null;
|
|
3722
3885
|
this.refreshTokenValue = options?.refreshToken ?? null;
|
|
@@ -3744,66 +3907,299 @@ var CustomerAuth = class {
|
|
|
3744
3907
|
body: JSON.stringify(data)
|
|
3745
3908
|
}
|
|
3746
3909
|
);
|
|
3747
|
-
this.
|
|
3748
|
-
|
|
3910
|
+
if (result.refreshToken) this.setSession(result.token, result.refreshToken);
|
|
3911
|
+
else this.setToken(result.token);
|
|
3749
3912
|
return result;
|
|
3750
3913
|
}
|
|
3751
3914
|
/**
|
|
3752
3915
|
* Refresh the current token. Uses a refresh token when available, otherwise
|
|
3753
3916
|
* falls back to the legacy bearer-token compatibility path.
|
|
3917
|
+
*
|
|
3918
|
+
* Refresh tokens are single-use, so a persisted session shared by several
|
|
3919
|
+
* tabs or SDK instances must rotate at most once: concurrent rotations of the
|
|
3920
|
+
* same token are replay from the server's point of view and revoke the whole
|
|
3921
|
+
* family. Persisted browser sessions therefore serialize rotation across
|
|
3922
|
+
* contexts and re-read the canonical persisted credentials inside the lock.
|
|
3754
3923
|
*/
|
|
3755
3924
|
async refreshToken() {
|
|
3925
|
+
this.syncFromPersistedStorage();
|
|
3756
3926
|
if (!this.refreshTokenValue && !this.token) {
|
|
3757
3927
|
throw new ApiError("Not authenticated", 401);
|
|
3758
3928
|
}
|
|
3759
3929
|
if (this.refreshPromise) return this.refreshPromise;
|
|
3760
|
-
|
|
3930
|
+
const intended = this.currentFence();
|
|
3931
|
+
this.refreshPromise = this.rotateAcrossContexts(intended);
|
|
3761
3932
|
try {
|
|
3762
3933
|
return await this.refreshPromise;
|
|
3763
3934
|
} finally {
|
|
3764
3935
|
this.refreshPromise = null;
|
|
3765
3936
|
}
|
|
3766
3937
|
}
|
|
3767
|
-
|
|
3768
|
-
|
|
3938
|
+
/**
|
|
3939
|
+
* Re-read the canonical persisted session. In persisted mode the shared
|
|
3940
|
+
* record — not this instance's memory — is the source of truth, so another
|
|
3941
|
+
* context's rotation, login, or logout is observed before the next credential
|
|
3942
|
+
* use. Change callbacks are deliberately not invoked: this is a read.
|
|
3943
|
+
*/
|
|
3944
|
+
syncFromPersistedStorage() {
|
|
3945
|
+
const canonical = this.readCanonicalSession();
|
|
3946
|
+
if (canonical === STORAGE_UNUSABLE) {
|
|
3947
|
+
this.disablePersistence();
|
|
3948
|
+
return;
|
|
3949
|
+
}
|
|
3950
|
+
if (!canonical) return;
|
|
3951
|
+
this.sessionRev = canonical.record.rev;
|
|
3952
|
+
this.token = canonical.record.token;
|
|
3953
|
+
this.refreshTokenValue = canonical.record.refreshToken;
|
|
3954
|
+
}
|
|
3955
|
+
/** `null` when this instance does not persist. */
|
|
3956
|
+
readCanonicalSession() {
|
|
3957
|
+
if (!this.persistRecordKey || !this.persistMirrorKey || !this.persistMirrorRefreshKey) {
|
|
3958
|
+
return null;
|
|
3959
|
+
}
|
|
3960
|
+
return readSessionRecord(
|
|
3961
|
+
this.persistRecordKey,
|
|
3962
|
+
this.persistMirrorKey,
|
|
3963
|
+
this.persistMirrorRefreshKey
|
|
3964
|
+
);
|
|
3965
|
+
}
|
|
3966
|
+
/**
|
|
3967
|
+
* Publish a change to canonical storage.
|
|
3968
|
+
*
|
|
3969
|
+
* The canonical pair is re-read immediately before the single record write so
|
|
3970
|
+
* that a field this call does not change is taken from that read rather than
|
|
3971
|
+
* from this instance's possibly stale copy. That merge is BEST-EFFORT: it
|
|
3972
|
+
* removes the stale counterpart an instance would otherwise carry, but it is
|
|
3973
|
+
* not a compare-and-swap. Web Storage offers none, and the synchronous public
|
|
3974
|
+
* mutators cannot await the refresh lease, so a commit that lands between
|
|
3975
|
+
* this read and the store operation is lost by ordering — including the
|
|
3976
|
+
* counterpart just read, which is then republished stale. The window is one
|
|
3977
|
+
* store operation wide and the server remains authoritative; see
|
|
3978
|
+
* `CUST-AUTH-033` for the stated limit.
|
|
3979
|
+
*
|
|
3980
|
+
* Returns `false` only when `requireLiveSession` refused a write because the
|
|
3981
|
+
* canonical session had already been cleared.
|
|
3982
|
+
*/
|
|
3983
|
+
commitPersistedSession(change, options) {
|
|
3984
|
+
const recordKey = this.persistRecordKey;
|
|
3985
|
+
const mirrorKey = this.persistMirrorKey;
|
|
3986
|
+
const mirrorRefreshKey = this.persistMirrorRefreshKey;
|
|
3987
|
+
const canonical = this.readCanonicalSession();
|
|
3988
|
+
if (canonical === null || !recordKey || !mirrorKey || !mirrorRefreshKey) {
|
|
3989
|
+
return true;
|
|
3990
|
+
}
|
|
3991
|
+
if (canonical === STORAGE_UNUSABLE) {
|
|
3992
|
+
this.disablePersistence();
|
|
3993
|
+
return true;
|
|
3994
|
+
}
|
|
3995
|
+
const current = canonical.record;
|
|
3996
|
+
if (options?.requireLiveSession && !current.token && !current.refreshToken) {
|
|
3997
|
+
return false;
|
|
3998
|
+
}
|
|
3999
|
+
const next = {
|
|
4000
|
+
v: SESSION_RECORD_VERSION,
|
|
4001
|
+
rev: current.rev + 1,
|
|
4002
|
+
token: "token" in change ? change.token ?? null : current.token,
|
|
4003
|
+
refreshToken: "refreshToken" in change ? change.refreshToken ?? null : current.refreshToken
|
|
4004
|
+
};
|
|
4005
|
+
if (
|
|
4006
|
+
// Only a state that is genuinely persisted can already say what this
|
|
4007
|
+
// write would say. An absent record reads as an empty pair, and treating
|
|
4008
|
+
// that as a no-op would drop the very tombstone that stops a later
|
|
4009
|
+
// migration from resurrecting the session out of the earlier layout.
|
|
4010
|
+
canonical.stored && next.token === current.token && next.refreshToken === current.refreshToken
|
|
4011
|
+
) {
|
|
4012
|
+
this.sessionRev = current.rev;
|
|
4013
|
+
this.token = current.token;
|
|
4014
|
+
this.refreshTokenValue = current.refreshToken;
|
|
4015
|
+
return true;
|
|
4016
|
+
}
|
|
4017
|
+
try {
|
|
4018
|
+
localStorage.setItem(recordKey, JSON.stringify(next));
|
|
4019
|
+
} catch {
|
|
4020
|
+
this.disablePersistence();
|
|
4021
|
+
return true;
|
|
4022
|
+
}
|
|
4023
|
+
this.sessionRev = next.rev;
|
|
4024
|
+
this.token = next.token;
|
|
4025
|
+
this.refreshTokenValue = next.refreshToken;
|
|
4026
|
+
try {
|
|
4027
|
+
localStorage.removeItem(mirrorRefreshKey);
|
|
4028
|
+
} catch {
|
|
4029
|
+
return true;
|
|
4030
|
+
}
|
|
4031
|
+
try {
|
|
4032
|
+
if (next.token) localStorage.setItem(mirrorKey, next.token);
|
|
4033
|
+
else localStorage.removeItem(mirrorKey);
|
|
4034
|
+
} catch {
|
|
4035
|
+
}
|
|
4036
|
+
return true;
|
|
4037
|
+
}
|
|
4038
|
+
/**
|
|
4039
|
+
* Storage rejected a write or a read, so it is not a canonical store for this
|
|
4040
|
+
* instance. Fall back to the memory-only session and take no lease: there is
|
|
4041
|
+
* no shared state left to coordinate. Nothing is purged — the record commit
|
|
4042
|
+
* is atomic, so no inconsistent state was published, and another context's
|
|
4043
|
+
* valid session must not be destroyed by this instance's local failure.
|
|
4044
|
+
*/
|
|
4045
|
+
disablePersistence() {
|
|
4046
|
+
this.persistRecordKey = null;
|
|
4047
|
+
this.persistMirrorKey = null;
|
|
4048
|
+
this.persistMirrorRefreshKey = null;
|
|
4049
|
+
this.refreshLockName = null;
|
|
4050
|
+
}
|
|
4051
|
+
/**
|
|
4052
|
+
* Compare the canonical record with the fence this call captured before it
|
|
4053
|
+
* left. Every persisted decision — rotate, converge, or fail closed — goes
|
|
4054
|
+
* through this one comparison. The revision catches a logout, a fresh login,
|
|
4055
|
+
* or a manual setter even when token values coincide; the value comparison
|
|
4056
|
+
* additionally catches a mirror-only writer that cannot bump a revision.
|
|
4057
|
+
*/
|
|
4058
|
+
classifyCanonicalState(intended) {
|
|
4059
|
+
if (!this.persistRecordKey) return "current";
|
|
4060
|
+
this.syncFromPersistedStorage();
|
|
4061
|
+
if (!this.persistRecordKey) return "current";
|
|
4062
|
+
if (!this.token && !this.refreshTokenValue) return "cleared";
|
|
4063
|
+
if (intended.refreshToken !== null) {
|
|
4064
|
+
return this.refreshTokenValue === intended.refreshToken ? "current" : "superseded";
|
|
4065
|
+
}
|
|
4066
|
+
return this.token === intended.token && !this.refreshTokenValue ? "current" : "superseded";
|
|
4067
|
+
}
|
|
4068
|
+
/** The canonical persisted pair, as a refresh result. */
|
|
4069
|
+
canonicalPair(token) {
|
|
4070
|
+
return {
|
|
4071
|
+
token,
|
|
4072
|
+
...this.refreshTokenValue ? { refreshToken: this.refreshTokenValue } : {}
|
|
4073
|
+
};
|
|
4074
|
+
}
|
|
4075
|
+
/** Current credentials plus the revision they were observed at. */
|
|
4076
|
+
currentFence() {
|
|
4077
|
+
return { token: this.token, refreshToken: this.refreshTokenValue };
|
|
4078
|
+
}
|
|
4079
|
+
/**
|
|
4080
|
+
* Clear an access credential a 401 just proved unusable — but only while it
|
|
4081
|
+
* is still the canonical one. A rotation committed by another context while
|
|
4082
|
+
* the request was in flight is newer than this 401, so it must survive; the
|
|
4083
|
+
* instance adopts it instead. The refresh entry is never touched here: a
|
|
4084
|
+
* rejected access token says nothing about the refresh family.
|
|
4085
|
+
*/
|
|
4086
|
+
invalidateAccessToken(spentToken) {
|
|
4087
|
+
this.syncFromPersistedStorage();
|
|
4088
|
+
if (this.token !== spentToken) return;
|
|
4089
|
+
this.setToken(null);
|
|
4090
|
+
}
|
|
4091
|
+
/** Commit both credentials in one canonical write. */
|
|
4092
|
+
setSession(token, refreshToken, options) {
|
|
4093
|
+
this.token = token;
|
|
4094
|
+
this.refreshTokenValue = refreshToken;
|
|
4095
|
+
const committed = this.commitPersistedSession(
|
|
4096
|
+
{ token, refreshToken },
|
|
4097
|
+
options
|
|
4098
|
+
);
|
|
4099
|
+
if (!committed) return false;
|
|
4100
|
+
this.onTokenChange?.(this.token);
|
|
4101
|
+
this.onRefreshTokenChange?.(this.refreshTokenValue);
|
|
4102
|
+
return true;
|
|
4103
|
+
}
|
|
4104
|
+
/**
|
|
4105
|
+
* Serialize rotation against every other context sharing the same storage
|
|
4106
|
+
* key and API/tenant identity. Coordination is best-effort: when it is
|
|
4107
|
+
* unavailable or times out the rotation still happens, so server-side reuse
|
|
4108
|
+
* detection is never bypassed by a client that failed to coordinate.
|
|
4109
|
+
*/
|
|
4110
|
+
async rotateAcrossContexts(intended) {
|
|
4111
|
+
const lockName = this.refreshLockName;
|
|
4112
|
+
const requestLock = lockName ? getRefreshLockRequester() : null;
|
|
4113
|
+
if (!lockName || !requestLock) {
|
|
4114
|
+
return this.rotateWithCanonicalState(intended);
|
|
4115
|
+
}
|
|
4116
|
+
const controller = new AbortController();
|
|
4117
|
+
const timer = setTimeout(() => controller.abort(), REFRESH_LOCK_TIMEOUT);
|
|
4118
|
+
let entered = false;
|
|
4119
|
+
try {
|
|
4120
|
+
return await requestLock(
|
|
4121
|
+
lockName,
|
|
4122
|
+
{ signal: controller.signal },
|
|
4123
|
+
async () => {
|
|
4124
|
+
entered = true;
|
|
4125
|
+
return this.rotateWithCanonicalState(intended);
|
|
4126
|
+
}
|
|
4127
|
+
);
|
|
4128
|
+
} catch (error) {
|
|
4129
|
+
if (entered) throw error;
|
|
4130
|
+
return this.rotateWithCanonicalState(intended);
|
|
4131
|
+
} finally {
|
|
4132
|
+
clearTimeout(timer);
|
|
4133
|
+
}
|
|
4134
|
+
}
|
|
4135
|
+
/**
|
|
4136
|
+
* Decide, against freshly re-read canonical state, whether this context still
|
|
4137
|
+
* owns the rotation.
|
|
4138
|
+
*/
|
|
4139
|
+
async rotateWithCanonicalState(intended) {
|
|
4140
|
+
let fence = intended;
|
|
4141
|
+
for (let attempt = 0; attempt < 2; attempt += 1) {
|
|
4142
|
+
const state = this.classifyCanonicalState(fence);
|
|
4143
|
+
if (state === "cleared") throw new ApiError("Not authenticated", 401);
|
|
4144
|
+
if (state === "current") return this._doRefreshToken(fence);
|
|
4145
|
+
if (this.token) return this.canonicalPair(this.token);
|
|
4146
|
+
fence = this.currentFence();
|
|
4147
|
+
}
|
|
4148
|
+
throw new ApiError("Not authenticated", 401);
|
|
4149
|
+
}
|
|
4150
|
+
async _doRefreshToken(intended) {
|
|
4151
|
+
const init = intended.refreshToken ? {
|
|
3769
4152
|
method: "POST",
|
|
3770
|
-
body: JSON.stringify({ refreshToken:
|
|
4153
|
+
body: JSON.stringify({ refreshToken: intended.refreshToken })
|
|
3771
4154
|
} : {
|
|
3772
4155
|
method: "POST",
|
|
3773
|
-
headers: { Authorization: `Bearer ${
|
|
4156
|
+
headers: { Authorization: `Bearer ${intended.token}` }
|
|
3774
4157
|
};
|
|
3775
4158
|
const result = await this.requestJson(
|
|
3776
4159
|
"/api/customers/refresh",
|
|
3777
4160
|
init
|
|
3778
4161
|
);
|
|
3779
|
-
this.
|
|
3780
|
-
if (
|
|
3781
|
-
|
|
4162
|
+
const state = this.classifyCanonicalState(intended);
|
|
4163
|
+
if (state === "cleared") throw new ApiError("Not authenticated", 401);
|
|
4164
|
+
if (state === "superseded") {
|
|
4165
|
+
if (this.token) return this.canonicalPair(this.token);
|
|
4166
|
+
throw new ApiError("Not authenticated", 401);
|
|
4167
|
+
}
|
|
4168
|
+
const replacedAccessToken = this.token !== null && this.token !== intended.token ? this.token : null;
|
|
4169
|
+
const nextToken = replacedAccessToken ?? result.token;
|
|
4170
|
+
const nextRefresh = result.refreshToken ?? null;
|
|
4171
|
+
if (!this.setSession(nextToken, nextRefresh, { requireLiveSession: true })) {
|
|
4172
|
+
throw new ApiError("Not authenticated", 401);
|
|
4173
|
+
}
|
|
4174
|
+
return {
|
|
4175
|
+
token: nextToken,
|
|
4176
|
+
...nextRefresh ? { refreshToken: nextRefresh } : {}
|
|
4177
|
+
};
|
|
3782
4178
|
}
|
|
3783
4179
|
/**
|
|
3784
4180
|
* Clear the stored token
|
|
3785
4181
|
*/
|
|
3786
4182
|
logout() {
|
|
3787
|
-
this.
|
|
3788
|
-
this.setRefreshToken(null);
|
|
4183
|
+
this.setSession(null, null);
|
|
3789
4184
|
}
|
|
3790
4185
|
/**
|
|
3791
4186
|
* Get the current authenticated customer's profile
|
|
3792
4187
|
*/
|
|
3793
4188
|
async me() {
|
|
3794
|
-
|
|
4189
|
+
const token = this.getToken();
|
|
4190
|
+
if (!token) return null;
|
|
3795
4191
|
try {
|
|
3796
4192
|
const data = await this.requestJson(
|
|
3797
4193
|
"/api/customers/me",
|
|
3798
4194
|
{
|
|
3799
4195
|
method: "GET",
|
|
3800
|
-
headers: { Authorization: `Bearer ${
|
|
4196
|
+
headers: { Authorization: `Bearer ${token}` }
|
|
3801
4197
|
}
|
|
3802
4198
|
);
|
|
3803
4199
|
return data.customer ?? null;
|
|
3804
4200
|
} catch (error) {
|
|
3805
4201
|
if (error instanceof SDKError && error.status === 401) {
|
|
3806
|
-
this.
|
|
4202
|
+
this.invalidateAccessToken(token);
|
|
3807
4203
|
return null;
|
|
3808
4204
|
}
|
|
3809
4205
|
throw error;
|
|
@@ -3831,12 +4227,13 @@ var CustomerAuth = class {
|
|
|
3831
4227
|
* Update the authenticated customer's profile (name, phone, marketingConsent)
|
|
3832
4228
|
*/
|
|
3833
4229
|
async updateProfile(data) {
|
|
3834
|
-
|
|
4230
|
+
const token = this.getToken();
|
|
4231
|
+
if (!token) throw new ApiError("Not authenticated", 401);
|
|
3835
4232
|
const result = await this.requestJson(
|
|
3836
4233
|
"/api/customers/me",
|
|
3837
4234
|
{
|
|
3838
4235
|
method: "PATCH",
|
|
3839
|
-
headers: { Authorization: `Bearer ${
|
|
4236
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
3840
4237
|
body: JSON.stringify(data)
|
|
3841
4238
|
}
|
|
3842
4239
|
);
|
|
@@ -3846,10 +4243,11 @@ var CustomerAuth = class {
|
|
|
3846
4243
|
* Change the password of the currently authenticated customer
|
|
3847
4244
|
*/
|
|
3848
4245
|
async changePassword(currentPassword, newPassword) {
|
|
3849
|
-
|
|
4246
|
+
const token = this.getToken();
|
|
4247
|
+
if (!token) throw new ApiError("Not authenticated", 401);
|
|
3850
4248
|
await this.requestJson("/api/customers/change-password", {
|
|
3851
4249
|
method: "POST",
|
|
3852
|
-
headers: { Authorization: `Bearer ${
|
|
4250
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
3853
4251
|
body: JSON.stringify({ currentPassword, newPassword })
|
|
3854
4252
|
});
|
|
3855
4253
|
}
|
|
@@ -3857,7 +4255,8 @@ var CustomerAuth = class {
|
|
|
3857
4255
|
* Get the authenticated customer's orders with pagination and optional status filter
|
|
3858
4256
|
*/
|
|
3859
4257
|
async getMyOrders(options) {
|
|
3860
|
-
|
|
4258
|
+
const token = this.getToken();
|
|
4259
|
+
if (!token) throw new ApiError("Not authenticated", 401);
|
|
3861
4260
|
const params = new URLSearchParams();
|
|
3862
4261
|
if (options?.page) params.set("page", String(options.page));
|
|
3863
4262
|
if (options?.limit) params.set("limit", String(options.limit));
|
|
@@ -3879,21 +4278,27 @@ var CustomerAuth = class {
|
|
|
3879
4278
|
`/api/customers/me/orders${qs ? `?${qs}` : ""}`,
|
|
3880
4279
|
{
|
|
3881
4280
|
method: "GET",
|
|
3882
|
-
headers: { Authorization: `Bearer ${
|
|
4281
|
+
headers: { Authorization: `Bearer ${token}` }
|
|
3883
4282
|
}
|
|
3884
4283
|
);
|
|
3885
4284
|
return validatePublicOrderList(result);
|
|
3886
4285
|
}
|
|
3887
4286
|
/**
|
|
3888
|
-
* Get the current token (or null if not authenticated)
|
|
4287
|
+
* Get the current token (or null if not authenticated).
|
|
4288
|
+
*
|
|
4289
|
+
* In persisted mode this re-reads shared storage first, so a rotation or
|
|
4290
|
+
* logout performed by another tab is observed before the token is used.
|
|
3889
4291
|
*/
|
|
3890
4292
|
getToken() {
|
|
4293
|
+
this.syncFromPersistedStorage();
|
|
3891
4294
|
return this.token;
|
|
3892
4295
|
}
|
|
3893
4296
|
/**
|
|
3894
|
-
* Get the current refresh token (or null if unavailable)
|
|
4297
|
+
* Get the current refresh token (or null if unavailable). Persisted mode
|
|
4298
|
+
* re-reads shared storage first, like {@link CustomerAuth.getToken}.
|
|
3895
4299
|
*/
|
|
3896
4300
|
getRefreshToken() {
|
|
4301
|
+
this.syncFromPersistedStorage();
|
|
3897
4302
|
return this.refreshTokenValue;
|
|
3898
4303
|
}
|
|
3899
4304
|
/**
|
|
@@ -3901,6 +4306,7 @@ var CustomerAuth = class {
|
|
|
3901
4306
|
*/
|
|
3902
4307
|
setToken(token) {
|
|
3903
4308
|
this.token = token;
|
|
4309
|
+
this.commitPersistedSession({ token });
|
|
3904
4310
|
this.onTokenChange?.(token);
|
|
3905
4311
|
}
|
|
3906
4312
|
/**
|
|
@@ -3908,13 +4314,14 @@ var CustomerAuth = class {
|
|
|
3908
4314
|
*/
|
|
3909
4315
|
setRefreshToken(refreshToken) {
|
|
3910
4316
|
this.refreshTokenValue = refreshToken;
|
|
4317
|
+
this.commitPersistedSession({ refreshToken });
|
|
3911
4318
|
this.onRefreshTokenChange?.(refreshToken);
|
|
3912
4319
|
}
|
|
3913
4320
|
/**
|
|
3914
4321
|
* Check if the customer is currently authenticated
|
|
3915
4322
|
*/
|
|
3916
4323
|
isAuthenticated() {
|
|
3917
|
-
return this.
|
|
4324
|
+
return this.getToken() !== null;
|
|
3918
4325
|
}
|
|
3919
4326
|
/**
|
|
3920
4327
|
* Internal: make a request with timeout and error handling.
|
|
@@ -4122,8 +4529,8 @@ var CartApi = class extends CustomerScopedApi {
|
|
|
4122
4529
|
endpoint(key) {
|
|
4123
4530
|
return this.serverTransport ? COMMERCE_ENDPOINTS.carts.server[key] : COMMERCE_ENDPOINTS.carts[key];
|
|
4124
4531
|
}
|
|
4125
|
-
execute(
|
|
4126
|
-
return this.requestEndpoint(
|
|
4532
|
+
execute(endpoint, body, idempotencyKey) {
|
|
4533
|
+
return this.requestEndpoint(endpoint, body, {
|
|
4127
4534
|
...idempotencyKey && {
|
|
4128
4535
|
headers: { "X-Idempotency-Key": idempotencyKey }
|
|
4129
4536
|
}
|
|
@@ -4140,26 +4547,26 @@ var CartApi = class extends CustomerScopedApi {
|
|
|
4140
4547
|
);
|
|
4141
4548
|
}
|
|
4142
4549
|
mine(params = {}) {
|
|
4143
|
-
const
|
|
4144
|
-
return this.execute(
|
|
4550
|
+
const endpoint = this.endpoint("mine");
|
|
4551
|
+
return this.execute(endpoint, params).then(
|
|
4145
4552
|
(value) => validateCartMineResult(value)
|
|
4146
4553
|
);
|
|
4147
4554
|
}
|
|
4148
4555
|
merge(params) {
|
|
4149
4556
|
const { idempotencyKey, ...body } = params;
|
|
4150
4557
|
assertCommerceIdempotencyKey(idempotencyKey);
|
|
4151
|
-
const
|
|
4152
|
-
return this.execute(
|
|
4558
|
+
const endpoint = this.endpoint("merge");
|
|
4559
|
+
return this.execute(endpoint, body, idempotencyKey).then(
|
|
4153
4560
|
(value) => validateCartMergeResult(value)
|
|
4154
4561
|
);
|
|
4155
4562
|
}
|
|
4156
4563
|
addItem(params) {
|
|
4157
4564
|
return this.mutate(this.endpoint("addItem"), params);
|
|
4158
4565
|
}
|
|
4159
|
-
mutate(
|
|
4566
|
+
mutate(endpoint, params) {
|
|
4160
4567
|
const { idempotencyKey, ...body } = params;
|
|
4161
4568
|
assertCommerceIdempotencyKey(idempotencyKey);
|
|
4162
|
-
return this.execute(
|
|
4569
|
+
return this.execute(endpoint, body, idempotencyKey).then(
|
|
4163
4570
|
(value) => validateCartMutationResult(value)
|
|
4164
4571
|
);
|
|
4165
4572
|
}
|
|
@@ -4185,8 +4592,8 @@ var CheckoutApi = class extends CustomerScopedApi {
|
|
|
4185
4592
|
constructor(options) {
|
|
4186
4593
|
super("CheckoutApi", { ...options, requiresCredential: false });
|
|
4187
4594
|
}
|
|
4188
|
-
post(
|
|
4189
|
-
return this.requestEndpoint(
|
|
4595
|
+
post(endpoint, body, idempotencyKey) {
|
|
4596
|
+
return this.requestEndpoint(endpoint, body, {
|
|
4190
4597
|
...idempotencyKey && {
|
|
4191
4598
|
headers: { "X-Idempotency-Key": idempotencyKey }
|
|
4192
4599
|
}
|
|
@@ -4253,15 +4660,23 @@ var PaymentSessionApi = class extends CustomerScopedApi {
|
|
|
4253
4660
|
super("PaymentSessionApi", { ...options, requiresCredential: false });
|
|
4254
4661
|
this.serverTransport = Boolean(options.secretKey);
|
|
4255
4662
|
}
|
|
4256
|
-
post(
|
|
4257
|
-
|
|
4258
|
-
...idempotencyKey && {
|
|
4259
|
-
|
|
4663
|
+
post(endpoint, body, idempotencyKey, negotiateStandardActionCapabilities = false) {
|
|
4664
|
+
const headers = {
|
|
4665
|
+
...idempotencyKey && { "X-Idempotency-Key": idempotencyKey },
|
|
4666
|
+
...negotiateStandardActionCapabilities && {
|
|
4667
|
+
Accept: PORTONE_STANDARD_ACTION_CAPABILITIES_MEDIA_TYPE
|
|
4260
4668
|
}
|
|
4669
|
+
};
|
|
4670
|
+
return this.requestEndpoint(endpoint, body, {
|
|
4671
|
+
...Object.keys(headers).length > 0 && { headers }
|
|
4261
4672
|
});
|
|
4262
4673
|
}
|
|
4263
|
-
async paymentSession(result, expectedProvider) {
|
|
4264
|
-
return validatePublicPaymentSession(
|
|
4674
|
+
async paymentSession(result, expectedProvider, operation2) {
|
|
4675
|
+
return validatePublicPaymentSession(
|
|
4676
|
+
await result,
|
|
4677
|
+
expectedProvider,
|
|
4678
|
+
operation2
|
|
4679
|
+
);
|
|
4265
4680
|
}
|
|
4266
4681
|
create(params) {
|
|
4267
4682
|
const { idempotencyKey, ...body } = params;
|
|
@@ -4282,41 +4697,44 @@ var PaymentSessionApi = class extends CustomerScopedApi {
|
|
|
4282
4697
|
this.post(
|
|
4283
4698
|
this.serverTransport ? COMMERCE_ENDPOINTS.paymentSessions.server.create : COMMERCE_ENDPOINTS.paymentSessions.create,
|
|
4284
4699
|
body,
|
|
4285
|
-
idempotencyKey
|
|
4700
|
+
idempotencyKey,
|
|
4701
|
+
body.providerOptions.profile === "standard_v2"
|
|
4286
4702
|
),
|
|
4287
4703
|
"portone"
|
|
4288
4704
|
);
|
|
4289
4705
|
}
|
|
4290
4706
|
retrieve(params) {
|
|
4291
|
-
const
|
|
4707
|
+
const endpoint = this.serverTransport ? COMMERCE_ENDPOINTS.paymentSessions.server.retrieve : COMMERCE_ENDPOINTS.paymentSessions.retrieve;
|
|
4292
4708
|
return this.paymentSession(
|
|
4293
|
-
this.post(
|
|
4709
|
+
this.post(endpoint, params),
|
|
4294
4710
|
"toss"
|
|
4295
4711
|
);
|
|
4296
4712
|
}
|
|
4297
4713
|
retrievePortOne(params) {
|
|
4298
|
-
const
|
|
4714
|
+
const endpoint = this.serverTransport ? COMMERCE_ENDPOINTS.paymentSessions.server.retrieve : COMMERCE_ENDPOINTS.paymentSessions.retrieve;
|
|
4299
4715
|
return this.paymentSession(
|
|
4300
|
-
this.post(
|
|
4716
|
+
this.post(endpoint, params, void 0, true),
|
|
4301
4717
|
"portone"
|
|
4302
4718
|
);
|
|
4303
4719
|
}
|
|
4304
4720
|
cancel(params) {
|
|
4305
4721
|
const { idempotencyKey, ...body } = params;
|
|
4306
4722
|
assertCommerceIdempotencyKey(idempotencyKey);
|
|
4307
|
-
const
|
|
4723
|
+
const endpoint = this.serverTransport ? COMMERCE_ENDPOINTS.paymentSessions.server.cancel : COMMERCE_ENDPOINTS.paymentSessions.cancel;
|
|
4308
4724
|
return this.paymentSession(
|
|
4309
|
-
this.post(
|
|
4310
|
-
"toss"
|
|
4725
|
+
this.post(endpoint, body, idempotencyKey),
|
|
4726
|
+
"toss",
|
|
4727
|
+
"cancel"
|
|
4311
4728
|
);
|
|
4312
4729
|
}
|
|
4313
4730
|
cancelPortOne(params) {
|
|
4314
4731
|
const { idempotencyKey, ...body } = params;
|
|
4315
4732
|
assertCommerceIdempotencyKey(idempotencyKey);
|
|
4316
|
-
const
|
|
4733
|
+
const endpoint = this.serverTransport ? COMMERCE_ENDPOINTS.paymentSessions.server.cancel : COMMERCE_ENDPOINTS.paymentSessions.cancel;
|
|
4317
4734
|
return this.paymentSession(
|
|
4318
|
-
this.post(
|
|
4319
|
-
"portone"
|
|
4735
|
+
this.post(endpoint, body, idempotencyKey),
|
|
4736
|
+
"portone",
|
|
4737
|
+
"cancel"
|
|
4320
4738
|
);
|
|
4321
4739
|
}
|
|
4322
4740
|
confirm(params) {
|
|
@@ -4394,9 +4812,9 @@ function resolveVariantImageItems(variant, poolById) {
|
|
|
4394
4812
|
if (pooled) resolved.push(pooled);
|
|
4395
4813
|
continue;
|
|
4396
4814
|
}
|
|
4397
|
-
const
|
|
4398
|
-
if (
|
|
4399
|
-
const pooled = poolById.get(
|
|
4815
|
+
const mediaId = getMediaId(entry);
|
|
4816
|
+
if (mediaId) {
|
|
4817
|
+
const pooled = poolById.get(mediaId);
|
|
4400
4818
|
if (pooled) {
|
|
4401
4819
|
resolved.push(pooled);
|
|
4402
4820
|
}
|
|
@@ -4587,11 +5005,11 @@ function getFirstMediaId(values) {
|
|
|
4587
5005
|
}
|
|
4588
5006
|
return null;
|
|
4589
5007
|
}
|
|
4590
|
-
function inProductMediaPool(product,
|
|
4591
|
-
if (
|
|
5008
|
+
function inProductMediaPool(product, mediaId) {
|
|
5009
|
+
if (mediaId == null) return null;
|
|
4592
5010
|
const images = Array.isArray(product?.images) ? product.images : [];
|
|
4593
5011
|
for (const image of images) {
|
|
4594
|
-
if (extractEntityId(image) ===
|
|
5012
|
+
if (extractEntityId(image) === mediaId) return mediaId;
|
|
4595
5013
|
}
|
|
4596
5014
|
return null;
|
|
4597
5015
|
}
|
|
@@ -6172,8 +6590,8 @@ function isProductUpsertFieldValidationErrorBody(value) {
|
|
|
6172
6590
|
|
|
6173
6591
|
// src/core/commerce/commerce-client.ts
|
|
6174
6592
|
var BrowserCommerceApi = class extends CustomerScopedApi {
|
|
6175
|
-
requestEndpoint(
|
|
6176
|
-
return super.requestEndpoint(
|
|
6593
|
+
requestEndpoint(endpoint, body, options) {
|
|
6594
|
+
return super.requestEndpoint(endpoint, body, options);
|
|
6177
6595
|
}
|
|
6178
6596
|
};
|
|
6179
6597
|
var CommerceClient = class {
|
|
@@ -6373,11 +6791,11 @@ var EventsClient = class {
|
|
|
6373
6791
|
{ useCustomerAuth: false }
|
|
6374
6792
|
);
|
|
6375
6793
|
}
|
|
6376
|
-
async execute(
|
|
6794
|
+
async execute(endpoint, method, body, options = {}) {
|
|
6377
6795
|
const useCustomerAuth = options.useCustomerAuth === true;
|
|
6378
6796
|
const token = useCustomerAuth ? typeof this.customerToken === "function" ? this.customerToken() : this.customerToken : void 0;
|
|
6379
6797
|
try {
|
|
6380
|
-
const response = await httpFetch(
|
|
6798
|
+
const response = await httpFetch(endpoint, {
|
|
6381
6799
|
method,
|
|
6382
6800
|
apiUrl: this.apiUrl,
|
|
6383
6801
|
publishableKey: this.publishableKey,
|
|
@@ -6386,7 +6804,7 @@ var EventsClient = class {
|
|
|
6386
6804
|
...body !== void 0 && { body: JSON.stringify(body) }
|
|
6387
6805
|
});
|
|
6388
6806
|
this.onRequestId?.(response.headers.get("x-request-id") ?? null);
|
|
6389
|
-
return parseApiResponse(response,
|
|
6807
|
+
return parseApiResponse(response, endpoint);
|
|
6390
6808
|
} catch (err) {
|
|
6391
6809
|
const id = err instanceof SDKError ? err.requestId ?? null : null;
|
|
6392
6810
|
this.onRequestId?.(id);
|
|
@@ -6644,13 +7062,106 @@ function mergeProductDetailWithStock(catalog, snapshot) {
|
|
|
6644
7062
|
};
|
|
6645
7063
|
}
|
|
6646
7064
|
|
|
7065
|
+
// src/core/webhook/validation.ts
|
|
7066
|
+
function isValidWebhookEventData(data) {
|
|
7067
|
+
if (typeof data !== "object" || data === null) return false;
|
|
7068
|
+
const event = data;
|
|
7069
|
+
return typeof event.collection === "string" && typeof event.operation === "string" && event.operation.length > 0 && typeof event.data === "object" && event.data !== null;
|
|
7070
|
+
}
|
|
7071
|
+
|
|
7072
|
+
// src/core/webhook/receiver.ts
|
|
7073
|
+
async function verifySignature(rawBody, secret, signature, timestamp2, deliveryId) {
|
|
7074
|
+
const encoder = new TextEncoder();
|
|
7075
|
+
const signedPrefix = encoder.encode(`${timestamp2}.${deliveryId}.`);
|
|
7076
|
+
const signedPayload = new Uint8Array(signedPrefix.length + rawBody.length);
|
|
7077
|
+
signedPayload.set(signedPrefix);
|
|
7078
|
+
signedPayload.set(rawBody, signedPrefix.length);
|
|
7079
|
+
const key = await crypto.subtle.importKey(
|
|
7080
|
+
"raw",
|
|
7081
|
+
encoder.encode(secret),
|
|
7082
|
+
{ name: "HMAC", hash: "SHA-256" },
|
|
7083
|
+
false,
|
|
7084
|
+
["verify"]
|
|
7085
|
+
);
|
|
7086
|
+
if (signature.length % 2 !== 0 || !/^[0-9a-fA-F]*$/.test(signature)) {
|
|
7087
|
+
return false;
|
|
7088
|
+
}
|
|
7089
|
+
const sigBytes = new Uint8Array(
|
|
7090
|
+
(signature.match(/.{2}/g) ?? []).map((byte) => parseInt(byte, 16))
|
|
7091
|
+
);
|
|
7092
|
+
return crypto.subtle.verify("HMAC", key, sigBytes, signedPayload);
|
|
7093
|
+
}
|
|
7094
|
+
function timestampIsFresh(timestamp2, toleranceSeconds) {
|
|
7095
|
+
if (!/^\d+$/.test(timestamp2)) return false;
|
|
7096
|
+
const timestampMs = Number(timestamp2);
|
|
7097
|
+
if (!Number.isFinite(timestampMs)) return false;
|
|
7098
|
+
const skewMs = Math.abs(Date.now() - timestampMs);
|
|
7099
|
+
return skewMs <= toleranceSeconds * 1e3;
|
|
7100
|
+
}
|
|
7101
|
+
var MAX_TOLERANCE_SECONDS = 86400;
|
|
7102
|
+
function toleranceIsValid(toleranceSeconds) {
|
|
7103
|
+
return typeof toleranceSeconds === "number" && Number.isFinite(toleranceSeconds) && toleranceSeconds >= 0 && toleranceSeconds <= MAX_TOLERANCE_SECONDS;
|
|
7104
|
+
}
|
|
7105
|
+
async function processWebhookRequest(request, handler, verify) {
|
|
7106
|
+
try {
|
|
7107
|
+
const rawBody = new Uint8Array(await request.arrayBuffer());
|
|
7108
|
+
const verificationFailure = await verify?.(rawBody);
|
|
7109
|
+
if (verificationFailure) return verificationFailure;
|
|
7110
|
+
const body = JSON.parse(new TextDecoder().decode(rawBody));
|
|
7111
|
+
if (!isValidWebhookEventData(body)) {
|
|
7112
|
+
return new Response(
|
|
7113
|
+
JSON.stringify({ error: "Invalid webhook event format" }),
|
|
7114
|
+
{ status: 400, headers: { "Content-Type": "application/json" } }
|
|
7115
|
+
);
|
|
7116
|
+
}
|
|
7117
|
+
await handler(body);
|
|
7118
|
+
return new Response(
|
|
7119
|
+
JSON.stringify({ success: true, message: "Webhook processed" }),
|
|
7120
|
+
{ status: 200, headers: { "Content-Type": "application/json" } }
|
|
7121
|
+
);
|
|
7122
|
+
} catch (error) {
|
|
7123
|
+
console.error("Webhook processing error:", error);
|
|
7124
|
+
return new Response(JSON.stringify({ error: "Internal server error" }), {
|
|
7125
|
+
status: 500,
|
|
7126
|
+
headers: { "Content-Type": "application/json" }
|
|
7127
|
+
});
|
|
7128
|
+
}
|
|
7129
|
+
}
|
|
7130
|
+
async function handleSignedWebhook(request, handler, options) {
|
|
7131
|
+
const secret = options?.secret;
|
|
7132
|
+
const configuredTolerance = options?.toleranceSeconds;
|
|
7133
|
+
const toleranceSeconds = configuredTolerance ?? 300;
|
|
7134
|
+
if (typeof secret !== "string" || secret.trim().length === 0) {
|
|
7135
|
+
return new Response(
|
|
7136
|
+
JSON.stringify({ error: "Webhook signing secret is not configured" }),
|
|
7137
|
+
{ status: 500, headers: { "Content-Type": "application/json" } }
|
|
7138
|
+
);
|
|
7139
|
+
}
|
|
7140
|
+
if (!toleranceIsValid(toleranceSeconds)) {
|
|
7141
|
+
return new Response(
|
|
7142
|
+
JSON.stringify({ error: "Webhook signature tolerance is not valid" }),
|
|
7143
|
+
{ status: 500, headers: { "Content-Type": "application/json" } }
|
|
7144
|
+
);
|
|
7145
|
+
}
|
|
7146
|
+
return processWebhookRequest(request, handler, async (rawBody) => {
|
|
7147
|
+
const signature = request.headers.get("x-webhook-signature") || "";
|
|
7148
|
+
const timestamp2 = request.headers.get("x-webhook-timestamp") || "";
|
|
7149
|
+
const deliveryId = request.headers.get("x-webhook-delivery-id") || "";
|
|
7150
|
+
const valid = Boolean(timestamp2 && deliveryId) && timestampIsFresh(timestamp2, toleranceSeconds) && await verifySignature(rawBody, secret, signature, timestamp2, deliveryId);
|
|
7151
|
+
if (!valid) {
|
|
7152
|
+
return new Response(
|
|
7153
|
+
JSON.stringify({ error: "Invalid webhook signature" }),
|
|
7154
|
+
{ status: 401, headers: { "Content-Type": "application/json" } }
|
|
7155
|
+
);
|
|
7156
|
+
}
|
|
7157
|
+
});
|
|
7158
|
+
}
|
|
7159
|
+
|
|
6647
7160
|
// src/core/webhook/index.ts
|
|
6648
7161
|
var ORDER_CHANGED_EVENT_TYPE = "collection.orderChanged";
|
|
6649
7162
|
var COMMERCE_NOTIFICATION_EVENT_TYPE = "commerce.notification";
|
|
6650
7163
|
function isValidWebhookEvent(data) {
|
|
6651
|
-
|
|
6652
|
-
const obj = data;
|
|
6653
|
-
return typeof obj.collection === "string" && typeof obj.operation === "string" && obj.operation.length > 0 && typeof obj.data === "object" && obj.data !== null;
|
|
7164
|
+
return isValidWebhookEventData(data);
|
|
6654
7165
|
}
|
|
6655
7166
|
var COMMERCE_NOTIFICATION_OPERATION = "notification";
|
|
6656
7167
|
var COMMERCE_NOTIFICATION_EVENTS = [
|
|
@@ -6662,7 +7173,13 @@ var COMMERCE_NOTIFICATION_EVENTS = [
|
|
|
6662
7173
|
"returnApproved",
|
|
6663
7174
|
"returnRejected",
|
|
6664
7175
|
"returnCanceled",
|
|
6665
|
-
"returnCompleted"
|
|
7176
|
+
"returnCompleted",
|
|
7177
|
+
"paymentReconciliationRequired",
|
|
7178
|
+
"paymentRefundRequiresAttention",
|
|
7179
|
+
"membershipActivated",
|
|
7180
|
+
"membershipPastDue",
|
|
7181
|
+
"membershipExpired",
|
|
7182
|
+
"membershipCancelled"
|
|
6666
7183
|
];
|
|
6667
7184
|
function isStringOrNumber(value) {
|
|
6668
7185
|
return typeof value === "string" || typeof value === "number";
|
|
@@ -6692,18 +7209,29 @@ function isCommerceNotificationEventName(value) {
|
|
|
6692
7209
|
return typeof value === "string" && COMMERCE_NOTIFICATION_EVENTS.includes(value);
|
|
6693
7210
|
}
|
|
6694
7211
|
function isCommerceNotificationSourceCollection(value) {
|
|
6695
|
-
return value === "orders" || value === "fulfillments" || value === "returns";
|
|
7212
|
+
return value === "orders" || value === "fulfillments" || value === "returns" || value === "payment-sessions" || value === "payment-refund-attempts" || value === "customer-memberships";
|
|
6696
7213
|
}
|
|
6697
7214
|
function isCommerceNotificationData(value) {
|
|
6698
7215
|
if (!isRecord(value)) return false;
|
|
6699
7216
|
if (value.source !== void 0 && (!isRecord(value.source) || !isCommerceNotificationSourceCollection(value.source.collection) || !isStringOrNumber(value.source.id))) {
|
|
6700
7217
|
return false;
|
|
6701
7218
|
}
|
|
6702
|
-
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");
|
|
7219
|
+
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");
|
|
6703
7220
|
}
|
|
6704
7221
|
function isCommerceNotification(value) {
|
|
6705
7222
|
if (!isRecord(value)) return false;
|
|
6706
|
-
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");
|
|
7223
|
+
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");
|
|
7224
|
+
}
|
|
7225
|
+
function isPaymentIncidentNotificationData(value, event) {
|
|
7226
|
+
const reconciliation = event === "paymentReconciliationRequired";
|
|
7227
|
+
const expectedCollection = reconciliation ? "payment-sessions" : "payment-refund-attempts";
|
|
7228
|
+
const expectedStatus = reconciliation ? "reconciliation_required" : "requires_attention";
|
|
7229
|
+
const expectedReason = reconciliation ? "orderless_capture" : "local_commit_ambiguous";
|
|
7230
|
+
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);
|
|
7231
|
+
}
|
|
7232
|
+
function isMembershipNotificationData(value, event) {
|
|
7233
|
+
const expectedStatus = event === "membershipActivated" ? "active" : event === "membershipPastDue" ? "past_due" : event === "membershipExpired" ? "expired" : "cancelled";
|
|
7234
|
+
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));
|
|
6707
7235
|
}
|
|
6708
7236
|
function isWebhookCommerceNotificationChange(value) {
|
|
6709
7237
|
if (!isRecord(value)) return false;
|
|
@@ -6744,6 +7272,18 @@ function isCommerceNotificationWebhookEvent(event) {
|
|
|
6744
7272
|
if (!matchesOptionalId(change.sourceId, sourceId)) return false;
|
|
6745
7273
|
}
|
|
6746
7274
|
const changeSourceId = isWebhookCommerceNotificationChange(change) ? change.sourceId : void 0;
|
|
7275
|
+
if (notification.event === "paymentReconciliationRequired" || notification.event === "paymentRefundRequiresAttention") {
|
|
7276
|
+
if (!isPaymentIncidentNotificationData(data, notification.event)) {
|
|
7277
|
+
return false;
|
|
7278
|
+
}
|
|
7279
|
+
const incidentData = data;
|
|
7280
|
+
const expectedCollection = notification.event === "paymentReconciliationRequired" ? "payment-sessions" : "payment-refund-attempts";
|
|
7281
|
+
const notificationSourceId = notification.event === "paymentReconciliationRequired" ? notification.paymentSessionId : notification.refundAttemptId;
|
|
7282
|
+
return sourceCollection === expectedCollection && typeof notification.paymentSessionId === "string" && notification.paymentSessionId === incidentData.paymentSessionId && typeof notificationSourceId === "string" && notificationSourceId === incidentData.source.id && matchesOptionalId(changeSourceId, incidentData.source.id);
|
|
7283
|
+
}
|
|
7284
|
+
if (notification.event === "membershipActivated" || notification.event === "membershipPastDue" || notification.event === "membershipExpired" || notification.event === "membershipCancelled") {
|
|
7285
|
+
return sourceCollection === "customer-memberships" && isMembershipNotificationData(data, notification.event) && typeof notification.membershipId === "string" && notification.membershipId === data.membershipId && matchesOptionalId(changeSourceId, data.membershipId);
|
|
7286
|
+
}
|
|
6747
7287
|
if (notification.event === "orderPaid" || notification.event === "orderCanceled" || notification.event === "orderDelivered") {
|
|
6748
7288
|
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);
|
|
6749
7289
|
}
|
|
@@ -6812,80 +7352,8 @@ function createCustomerAuthWebhookHandler(handlers) {
|
|
|
6812
7352
|
await handlers.unhandled?.(event);
|
|
6813
7353
|
};
|
|
6814
7354
|
}
|
|
6815
|
-
async function verifySignature(payload, secret, signature, timestamp2, deliveryId) {
|
|
6816
|
-
const encoder = new TextEncoder();
|
|
6817
|
-
const key = await crypto.subtle.importKey(
|
|
6818
|
-
"raw",
|
|
6819
|
-
encoder.encode(secret),
|
|
6820
|
-
{ name: "HMAC", hash: "SHA-256" },
|
|
6821
|
-
false,
|
|
6822
|
-
["verify"]
|
|
6823
|
-
);
|
|
6824
|
-
if (signature.length % 2 !== 0 || !/^[0-9a-fA-F]*$/.test(signature)) {
|
|
6825
|
-
return false;
|
|
6826
|
-
}
|
|
6827
|
-
const sigBytes = new Uint8Array(
|
|
6828
|
-
(signature.match(/.{2}/g) ?? []).map((byte) => parseInt(byte, 16))
|
|
6829
|
-
);
|
|
6830
|
-
return crypto.subtle.verify(
|
|
6831
|
-
"HMAC",
|
|
6832
|
-
key,
|
|
6833
|
-
sigBytes,
|
|
6834
|
-
encoder.encode(`${timestamp2}.${deliveryId}.${payload}`)
|
|
6835
|
-
);
|
|
6836
|
-
}
|
|
6837
|
-
function timestampIsFresh(timestamp2, toleranceSeconds) {
|
|
6838
|
-
if (!/^\d+$/.test(timestamp2)) return false;
|
|
6839
|
-
const timestampMs = Number(timestamp2);
|
|
6840
|
-
if (!Number.isFinite(timestampMs)) return false;
|
|
6841
|
-
const skewMs = Math.abs(Date.now() - timestampMs);
|
|
6842
|
-
return skewMs <= toleranceSeconds * 1e3;
|
|
6843
|
-
}
|
|
6844
7355
|
async function handleWebhook(request, handler, options) {
|
|
6845
|
-
|
|
6846
|
-
const rawBody = await request.text();
|
|
6847
|
-
if (options?.secret) {
|
|
6848
|
-
const signature = request.headers.get("x-webhook-signature") || "";
|
|
6849
|
-
const timestamp2 = request.headers.get("x-webhook-timestamp") || "";
|
|
6850
|
-
const deliveryId = request.headers.get("x-webhook-delivery-id") || "";
|
|
6851
|
-
const toleranceSeconds = options.toleranceSeconds ?? 300;
|
|
6852
|
-
const valid = Boolean(timestamp2 && deliveryId) && timestampIsFresh(timestamp2, toleranceSeconds) && await verifySignature(
|
|
6853
|
-
rawBody,
|
|
6854
|
-
options.secret,
|
|
6855
|
-
signature,
|
|
6856
|
-
timestamp2,
|
|
6857
|
-
deliveryId
|
|
6858
|
-
);
|
|
6859
|
-
if (!valid) {
|
|
6860
|
-
return new Response(
|
|
6861
|
-
JSON.stringify({ error: "Invalid webhook signature" }),
|
|
6862
|
-
{ status: 401, headers: { "Content-Type": "application/json" } }
|
|
6863
|
-
);
|
|
6864
|
-
}
|
|
6865
|
-
} else {
|
|
6866
|
-
console.warn(
|
|
6867
|
-
"[@01.software/sdk] Webhook signature verification is disabled. Set { secret } in handleWebhook() options to enable HMAC-SHA256 verification."
|
|
6868
|
-
);
|
|
6869
|
-
}
|
|
6870
|
-
const body = JSON.parse(rawBody);
|
|
6871
|
-
if (!isValidWebhookEvent(body)) {
|
|
6872
|
-
return new Response(
|
|
6873
|
-
JSON.stringify({ error: "Invalid webhook event format" }),
|
|
6874
|
-
{ status: 400, headers: { "Content-Type": "application/json" } }
|
|
6875
|
-
);
|
|
6876
|
-
}
|
|
6877
|
-
await handler(body);
|
|
6878
|
-
return new Response(
|
|
6879
|
-
JSON.stringify({ success: true, message: "Webhook processed" }),
|
|
6880
|
-
{ status: 200, headers: { "Content-Type": "application/json" } }
|
|
6881
|
-
);
|
|
6882
|
-
} catch (error) {
|
|
6883
|
-
console.error("Webhook processing error:", error);
|
|
6884
|
-
return new Response(JSON.stringify({ error: "Internal server error" }), {
|
|
6885
|
-
status: 500,
|
|
6886
|
-
headers: { "Content-Type": "application/json" }
|
|
6887
|
-
});
|
|
6888
|
-
}
|
|
7356
|
+
return handleSignedWebhook(request, handler, options);
|
|
6889
7357
|
}
|
|
6890
7358
|
function createTypedWebhookHandler(collection, handler) {
|
|
6891
7359
|
return async (event) => {
|
|
@@ -6925,6 +7393,7 @@ var INTERNAL_COLLECTIONS = [
|
|
|
6925
7393
|
"payment-event-inbox",
|
|
6926
7394
|
"payment-refund-attempts",
|
|
6927
7395
|
"portone-payment-interactions",
|
|
7396
|
+
"commerce-payment-webhook-routes",
|
|
6928
7397
|
"commerce-notification-intents",
|
|
6929
7398
|
"commerce-idempotency-records",
|
|
6930
7399
|
"product-collection-items",
|
|
@@ -6932,6 +7401,7 @@ var INTERNAL_COLLECTIONS = [
|
|
|
6932
7401
|
"fulfillment-events",
|
|
6933
7402
|
"tenant-commerce-config",
|
|
6934
7403
|
"customer-oauth-clients",
|
|
7404
|
+
"customer-group-grants",
|
|
6935
7405
|
"api-keys",
|
|
6936
7406
|
"personal-access-tokens",
|
|
6937
7407
|
"tenant-entitlements",
|
|
@@ -6965,7 +7435,6 @@ var COLLECTIONS = [
|
|
|
6965
7435
|
"brands",
|
|
6966
7436
|
"customer-profiles",
|
|
6967
7437
|
"customer-profile-stats",
|
|
6968
|
-
"discounts",
|
|
6969
7438
|
"shipping-policies",
|
|
6970
7439
|
"shipping-zones",
|
|
6971
7440
|
"documents",
|
|
@@ -7041,6 +7510,9 @@ var SERVER_ONLY_COLLECTIONS = [
|
|
|
7041
7510
|
"cart-items",
|
|
7042
7511
|
"checkouts",
|
|
7043
7512
|
"checkout-items",
|
|
7513
|
+
// Raw discount definitions require a server credential. Browser discount
|
|
7514
|
+
// validation is exposed through the shaped POST /api/discounts/validate path.
|
|
7515
|
+
"discounts",
|
|
7044
7516
|
"draft-orders",
|
|
7045
7517
|
"draft-order-items",
|
|
7046
7518
|
"posts",
|
|
@@ -7206,160 +7678,6 @@ var RealtimeConnection = class {
|
|
|
7206
7678
|
}
|
|
7207
7679
|
};
|
|
7208
7680
|
|
|
7209
|
-
// src/utils/product-media.ts
|
|
7210
|
-
function mediaPointerOptionValues(values) {
|
|
7211
|
-
return (values ?? []).filter((value) => value != null && value !== "").map((value) => ({
|
|
7212
|
-
swatch: {
|
|
7213
|
-
type: "media",
|
|
7214
|
-
mediaItemId: value
|
|
7215
|
-
}
|
|
7216
|
-
}));
|
|
7217
|
-
}
|
|
7218
|
-
function resolveProductMedia(input) {
|
|
7219
|
-
const resolved = resolveProductSelectionMedia({
|
|
7220
|
-
...input,
|
|
7221
|
-
selectedVariant: input.selectedVariant ?? (input.variantFeaturedMediaItemId ? { images: [input.variantFeaturedMediaItemId] } : null),
|
|
7222
|
-
selectedOptionValues: input.selectedOptionValues ?? mediaPointerOptionValues(input.selectedOptionValueMediaItemIds)
|
|
7223
|
-
});
|
|
7224
|
-
if (resolved.source !== "none") {
|
|
7225
|
-
return {
|
|
7226
|
-
...resolved,
|
|
7227
|
-
source: resolved.source === "variant_media_selected" ? "variant_featured" : resolved.source
|
|
7228
|
-
};
|
|
7229
|
-
}
|
|
7230
|
-
const display = resolveProductDisplayMedia({
|
|
7231
|
-
productMediaPool: input.productMediaPool
|
|
7232
|
-
});
|
|
7233
|
-
return {
|
|
7234
|
-
primaryImage: display.primaryImage,
|
|
7235
|
-
images: display.images,
|
|
7236
|
-
source: display.source
|
|
7237
|
-
};
|
|
7238
|
-
}
|
|
7239
|
-
|
|
7240
|
-
// src/utils/product-gallery.ts
|
|
7241
|
-
function mediaId(value) {
|
|
7242
|
-
if (typeof value === "string" || typeof value === "number") {
|
|
7243
|
-
return String(value);
|
|
7244
|
-
}
|
|
7245
|
-
if (typeof value === "object" && value !== null && "id" in value) {
|
|
7246
|
-
const id = value.id;
|
|
7247
|
-
if (typeof id === "string" || typeof id === "number") return String(id);
|
|
7248
|
-
}
|
|
7249
|
-
return null;
|
|
7250
|
-
}
|
|
7251
|
-
function relationshipIds(values) {
|
|
7252
|
-
if (!Array.isArray(values)) return [];
|
|
7253
|
-
return values.map((value) => mediaId(value)).filter((value) => Boolean(value));
|
|
7254
|
-
}
|
|
7255
|
-
function buildPoolById2(pool) {
|
|
7256
|
-
const poolById = /* @__PURE__ */ new Map();
|
|
7257
|
-
for (const item of pool) {
|
|
7258
|
-
const id = mediaId(item);
|
|
7259
|
-
if (id) poolById.set(id, item);
|
|
7260
|
-
}
|
|
7261
|
-
return poolById;
|
|
7262
|
-
}
|
|
7263
|
-
function mediaSetItemsInPool(mediaSet, poolById) {
|
|
7264
|
-
const seen = /* @__PURE__ */ new Set();
|
|
7265
|
-
const items = [];
|
|
7266
|
-
const primaryId = mediaId(mediaSet.primaryMediaItemId);
|
|
7267
|
-
if (primaryId) {
|
|
7268
|
-
const primary = poolById.get(primaryId);
|
|
7269
|
-
if (primary) {
|
|
7270
|
-
seen.add(primaryId);
|
|
7271
|
-
items.push(primary);
|
|
7272
|
-
}
|
|
7273
|
-
}
|
|
7274
|
-
for (const itemId of relationshipIds(mediaSet.items)) {
|
|
7275
|
-
if (seen.has(itemId)) continue;
|
|
7276
|
-
const item = poolById.get(itemId);
|
|
7277
|
-
if (!item) continue;
|
|
7278
|
-
seen.add(itemId);
|
|
7279
|
-
items.push(item);
|
|
7280
|
-
}
|
|
7281
|
-
return items;
|
|
7282
|
-
}
|
|
7283
|
-
function resolveLegacyMediaSet(input) {
|
|
7284
|
-
if (!Array.isArray(input.mediaSets)) return null;
|
|
7285
|
-
const pool = input.productMediaPool ?? input.pool ?? [];
|
|
7286
|
-
const poolById = buildPoolById2(pool);
|
|
7287
|
-
const selectedVariantId = mediaId(input.selectedVariantId);
|
|
7288
|
-
if (selectedVariantId) {
|
|
7289
|
-
const exactSet = input.mediaSets.find(
|
|
7290
|
-
(set) => set.matchType === "exact-variant" && relationshipIds(set.variantIds).includes(selectedVariantId)
|
|
7291
|
-
);
|
|
7292
|
-
if (exactSet) {
|
|
7293
|
-
const images = mediaSetItemsInPool(exactSet, poolById);
|
|
7294
|
-
if (images.length > 0) {
|
|
7295
|
-
return {
|
|
7296
|
-
primaryImage: images[0] ?? null,
|
|
7297
|
-
images,
|
|
7298
|
-
source: "exact_variant_set"
|
|
7299
|
-
};
|
|
7300
|
-
}
|
|
7301
|
-
}
|
|
7302
|
-
}
|
|
7303
|
-
const selectedOptionValueIds = new Set(
|
|
7304
|
-
relationshipIds(input.selectedOptionValueIds)
|
|
7305
|
-
);
|
|
7306
|
-
const optionSetMatches = input.mediaSets.filter((set) => set.matchType === "option-values").map((set) => {
|
|
7307
|
-
const optionValueIds = relationshipIds(set.optionValueIds);
|
|
7308
|
-
if (optionValueIds.length === 0) return null;
|
|
7309
|
-
if (!optionValueIds.every((id) => selectedOptionValueIds.has(id))) {
|
|
7310
|
-
return null;
|
|
7311
|
-
}
|
|
7312
|
-
const images = mediaSetItemsInPool(set, poolById);
|
|
7313
|
-
if (images.length === 0) return null;
|
|
7314
|
-
return { optionValueCount: optionValueIds.length, images };
|
|
7315
|
-
}).filter(
|
|
7316
|
-
(match) => Boolean(match)
|
|
7317
|
-
).sort((a, b) => b.optionValueCount - a.optionValueCount);
|
|
7318
|
-
if (optionSetMatches.length > 0) {
|
|
7319
|
-
const images = optionSetMatches[0].images;
|
|
7320
|
-
return {
|
|
7321
|
-
primaryImage: images[0] ?? null,
|
|
7322
|
-
images,
|
|
7323
|
-
source: "option_value_set"
|
|
7324
|
-
};
|
|
7325
|
-
}
|
|
7326
|
-
const defaultSet = input.mediaSets.find((set) => set.matchType === "default");
|
|
7327
|
-
if (defaultSet) {
|
|
7328
|
-
const images = mediaSetItemsInPool(defaultSet, poolById);
|
|
7329
|
-
if (images.length > 0) {
|
|
7330
|
-
return {
|
|
7331
|
-
primaryImage: images[0] ?? null,
|
|
7332
|
-
images,
|
|
7333
|
-
source: "default_set"
|
|
7334
|
-
};
|
|
7335
|
-
}
|
|
7336
|
-
}
|
|
7337
|
-
return {
|
|
7338
|
-
primaryImage: null,
|
|
7339
|
-
images: [],
|
|
7340
|
-
source: "none"
|
|
7341
|
-
};
|
|
7342
|
-
}
|
|
7343
|
-
function resolveProductGallery(input) {
|
|
7344
|
-
const selectedVariantId = mediaId(input.selectedVariantId);
|
|
7345
|
-
const selectedOptionValueIds = relationshipIds(input.selectedOptionValueIds);
|
|
7346
|
-
const selection = resolveProductSelectionMedia({
|
|
7347
|
-
...input,
|
|
7348
|
-
productMediaPool: input.productMediaPool ?? input.pool ?? null,
|
|
7349
|
-
selectedVariant: input.selectedVariant ?? (selectedVariantId ? { id: selectedVariantId } : null),
|
|
7350
|
-
selectedOptionValues: input.selectedOptionValues ?? selectedOptionValueIds.map((id) => ({ id }))
|
|
7351
|
-
});
|
|
7352
|
-
if (selection.source !== "none") {
|
|
7353
|
-
return selection;
|
|
7354
|
-
}
|
|
7355
|
-
const legacyResolved = resolveLegacyMediaSet(input);
|
|
7356
|
-
if (legacyResolved && legacyResolved.source !== "none") return legacyResolved;
|
|
7357
|
-
return resolveProductDisplayMedia({
|
|
7358
|
-
productMediaPool: input.productMediaPool ?? input.pool ?? null,
|
|
7359
|
-
featuredImage: input.featuredImage
|
|
7360
|
-
});
|
|
7361
|
-
}
|
|
7362
|
-
|
|
7363
7681
|
// src/utils/image.ts
|
|
7364
7682
|
var IMAGE_SIZES = [200, 400, 800, 1200, 1600];
|
|
7365
7683
|
function imageSizeCandidates(sizes) {
|
|
@@ -7491,7 +7809,7 @@ function createAnalytics(config) {
|
|
|
7491
7809
|
}, destroy() {
|
|
7492
7810
|
} };
|
|
7493
7811
|
}
|
|
7494
|
-
const
|
|
7812
|
+
const endpoint = config.endpoint ?? `${resolveApiUrl()}/api/analytics/collect`;
|
|
7495
7813
|
const respectDnt = config.respectDnt !== false;
|
|
7496
7814
|
function isDntActive() {
|
|
7497
7815
|
if (!respectDnt) return false;
|
|
@@ -7532,10 +7850,10 @@ function createAnalytics(config) {
|
|
|
7532
7850
|
try {
|
|
7533
7851
|
if (typeof navigator.sendBeacon === "function") {
|
|
7534
7852
|
const blob = new Blob([body], { type: "text/plain" });
|
|
7535
|
-
const sent = navigator.sendBeacon(
|
|
7853
|
+
const sent = navigator.sendBeacon(endpoint, blob);
|
|
7536
7854
|
if (sent) return;
|
|
7537
7855
|
}
|
|
7538
|
-
fetch(
|
|
7856
|
+
fetch(endpoint, {
|
|
7539
7857
|
method: "POST",
|
|
7540
7858
|
keepalive: true,
|
|
7541
7859
|
headers: { "Content-Type": "application/json" },
|
|
@@ -7792,8 +8110,6 @@ export {
|
|
|
7792
8110
|
projectProductToListingShape,
|
|
7793
8111
|
resolveFeaturedImagePointer,
|
|
7794
8112
|
resolveProductDisplayMedia,
|
|
7795
|
-
resolveProductGallery,
|
|
7796
|
-
resolveProductMedia,
|
|
7797
8113
|
resolveProductSelection,
|
|
7798
8114
|
resolveProductSelectionFromMatrix,
|
|
7799
8115
|
resolveProductSelectionMedia,
|