@01.software/sdk 0.51.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 +127 -0
- package/README.md +54 -11
- package/dist/client.cjs +420 -53
- 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 +420 -53
- package/dist/client.js.map +1 -1
- package/dist/{collection-client-hYvVfHIm.d.ts → collection-client-Bmr53saa.d.ts} +4 -4
- package/dist/{collection-client-CWAeuzV4.d.cts → collection-client-DxuBDso_.d.cts} +4 -4
- package/dist/{const-D8LixhAJ.d.cts → const-BKUqg96Q.d.ts} +2 -2
- package/dist/{const-JD8obaKg.d.ts → const-Cv1DCsp6.d.cts} +2 -2
- package/dist/{index-Bu0kCc6o.d.cts → index-CGsT4AQ8.d.cts} +24 -7
- package/dist/{index-B0Hjh3Ey.d.ts → index-DFHLLqYu.d.ts} +24 -7
- package/dist/index.cjs +434 -55
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +11 -10
- package/dist/index.d.ts +11 -10
- package/dist/index.js +434 -55
- package/dist/index.js.map +1 -1
- package/dist/{payload-types-BOsckg8B.d.cts → payload-types-Bk-duc5k.d.cts} +295 -100
- package/dist/{payload-types-BOsckg8B.d.ts → payload-types-Bk-duc5k.d.ts} +295 -100
- package/dist/query.cjs +14 -1
- 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 +14 -1
- 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 +98 -13
- 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 +98 -13
- package/dist/server.js.map +1 -1
- package/dist/{types-Bj8j7VHU.d.ts → types-CEK8uTvU.d.ts} +212 -11
- package/dist/{types-B14sEt1z.d.cts → types-CNdpj2fI.d.cts} +2 -2
- package/dist/{types-5ZlcAFuU.d.ts → types-D4mjGFj4.d.ts} +2 -2
- package/dist/{types-CM5qiR2k.d.cts → types-Dhk5NX7Z.d.cts} +212 -11
- 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 +13 -2
- package/dist/webhook.cjs.map +1 -1
- package/dist/webhook.d.cts +5 -5
- package/dist/webhook.d.ts +5 -5
- package/dist/webhook.js +13 -2
- package/dist/webhook.js.map +1 -1
- package/package.json +6 -5
package/dist/client.js
CHANGED
|
@@ -1353,6 +1353,11 @@ var COMMERCE_ENDPOINT_OPERATIONS = {
|
|
|
1353
1353
|
detail: strictCommerceOperation("checkouts", "post", "/detail"),
|
|
1354
1354
|
query: strictCommerceOperation("checkouts", "post", "/query")
|
|
1355
1355
|
},
|
|
1356
|
+
draftOrders: {
|
|
1357
|
+
calculate: operation("draft-orders", "post", "/:id/calculate"),
|
|
1358
|
+
complete: operation("draft-orders", "post", "/:id/complete"),
|
|
1359
|
+
finalize: operation("draft-orders", "post", "/:id/finalize")
|
|
1360
|
+
},
|
|
1356
1361
|
transactions: {
|
|
1357
1362
|
update: operation("transactions", "post", "/update")
|
|
1358
1363
|
},
|
|
@@ -1453,7 +1458,15 @@ var portOneStandardActionCapabilityOperations = /* @__PURE__ */ new Set([
|
|
|
1453
1458
|
var MEMBERSHIP_ENDPOINT_OPERATIONS = {
|
|
1454
1459
|
subscribe: operation(null, "post", "/memberships/subscribe"),
|
|
1455
1460
|
cancel: operation(null, "post", "/memberships/:id/cancel"),
|
|
1456
|
-
me: operation(null, "get", "/memberships/me")
|
|
1461
|
+
me: operation(null, "get", "/memberships/me"),
|
|
1462
|
+
chargeResult: operation(null, "post", "/memberships/:id/charge-result"),
|
|
1463
|
+
refundResult: operation(null, "post", "/memberships/:id/refund-result"),
|
|
1464
|
+
revoke: operation(null, "post", "/memberships/:id/revoke")
|
|
1465
|
+
};
|
|
1466
|
+
var CUSTOMER_GROUP_ENDPOINT_OPERATIONS = {
|
|
1467
|
+
members: operation("customer-groups", "get", "/:id/members"),
|
|
1468
|
+
grant: operation("customer-groups", "post", "/:id/members/grant"),
|
|
1469
|
+
revoke: operation("customer-groups", "post", "/:id/members/revoke")
|
|
1457
1470
|
};
|
|
1458
1471
|
var EMBEDDED_ADMIN_ENDPOINT_OPERATIONS = {
|
|
1459
1472
|
show: operation("tenants", "get", "/embedded-admin-app"),
|
|
@@ -2163,7 +2176,10 @@ var ORDER_PRIMARY_DISPLAY_STATUSES = [
|
|
|
2163
2176
|
];
|
|
2164
2177
|
var CHECKOUT_STATUSES = ["open", "placed", "abandoned"];
|
|
2165
2178
|
var ABANDONED_REASONS = ["expired", "customer", "operator"];
|
|
2166
|
-
|
|
2179
|
+
function managedPaymentProviders(providers) {
|
|
2180
|
+
return providers;
|
|
2181
|
+
}
|
|
2182
|
+
var PAYMENT_PROVIDERS = managedPaymentProviders(["toss", "portone"]);
|
|
2167
2183
|
var PAYMENT_PROVIDER_STATUSES = [
|
|
2168
2184
|
"requires_payment_method",
|
|
2169
2185
|
"requires_action",
|
|
@@ -2211,6 +2227,26 @@ function invalidCanonicalResponse(resource, details) {
|
|
|
2211
2227
|
function fail(resource, path, expected) {
|
|
2212
2228
|
invalidCanonicalResponse(resource, { path, expected });
|
|
2213
2229
|
}
|
|
2230
|
+
function providerMismatch(resource, expected, actual, operation2) {
|
|
2231
|
+
if (operation2 === "cancel") {
|
|
2232
|
+
const method = {
|
|
2233
|
+
toss: "cancel()",
|
|
2234
|
+
portone: "cancelPortOne()"
|
|
2235
|
+
}[actual];
|
|
2236
|
+
throw createApiError(
|
|
2237
|
+
`Canonical ${resource} response provider mismatch: expected ${expected}, received ${actual}.`,
|
|
2238
|
+
502,
|
|
2239
|
+
{ resource, path: "$.provider", expected, actual },
|
|
2240
|
+
`The server applied the cancellation to a ${actual} PaymentSession, but this SDK method expects ${expected}.`,
|
|
2241
|
+
`Re-invoke ${method} with the same idempotencyKey; do not create a new key.`
|
|
2242
|
+
);
|
|
2243
|
+
}
|
|
2244
|
+
invalidCanonicalResponse(resource, {
|
|
2245
|
+
path: "$.provider",
|
|
2246
|
+
expected,
|
|
2247
|
+
actual
|
|
2248
|
+
});
|
|
2249
|
+
}
|
|
2214
2250
|
function record(value, resource, path) {
|
|
2215
2251
|
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
2216
2252
|
fail(resource, path, "object");
|
|
@@ -3503,7 +3539,7 @@ function validateNextAction(value, provider, resource, path) {
|
|
|
3503
3539
|
if (action.customerEmail !== void 0)
|
|
3504
3540
|
validateCustomer({ email: action.customerEmail }, resource, path);
|
|
3505
3541
|
}
|
|
3506
|
-
function validatePublicPaymentSession(value, expectedProvider) {
|
|
3542
|
+
function validatePublicPaymentSession(value, expectedProvider, operation2) {
|
|
3507
3543
|
const resource = "PaymentSession";
|
|
3508
3544
|
const session = record(value, resource, "$");
|
|
3509
3545
|
const provider = enumValue(
|
|
@@ -3512,9 +3548,6 @@ function validatePublicPaymentSession(value, expectedProvider) {
|
|
|
3512
3548
|
resource,
|
|
3513
3549
|
"$.provider"
|
|
3514
3550
|
);
|
|
3515
|
-
if (expectedProvider !== void 0 && provider !== expectedProvider) {
|
|
3516
|
-
fail(resource, "$.provider", expectedProvider);
|
|
3517
|
-
}
|
|
3518
3551
|
const status = enumValue(
|
|
3519
3552
|
session.providerStatus,
|
|
3520
3553
|
PAYMENT_PROVIDER_STATUSES,
|
|
@@ -3718,44 +3751,128 @@ function validatePublicPaymentSession(value, expectedProvider) {
|
|
|
3718
3751
|
"$.cancellationReason"
|
|
3719
3752
|
);
|
|
3720
3753
|
}
|
|
3754
|
+
if (expectedProvider !== void 0 && provider !== expectedProvider) {
|
|
3755
|
+
if (operation2 === "cancel" && status !== "canceling" && status !== "canceled") {
|
|
3756
|
+
fail(
|
|
3757
|
+
resource,
|
|
3758
|
+
"$.providerStatus",
|
|
3759
|
+
"canceling or canceled after a successful cancellation"
|
|
3760
|
+
);
|
|
3761
|
+
}
|
|
3762
|
+
providerMismatch(resource, expectedProvider, provider, operation2);
|
|
3763
|
+
}
|
|
3721
3764
|
return value;
|
|
3722
3765
|
}
|
|
3723
3766
|
|
|
3724
3767
|
// src/core/customer/customer-auth.ts
|
|
3725
3768
|
var DEFAULT_TIMEOUT2 = 15e3;
|
|
3726
|
-
|
|
3769
|
+
var REFRESH_LOCK_TIMEOUT = DEFAULT_TIMEOUT2 * 3;
|
|
3770
|
+
var SESSION_RECORD_VERSION = 1;
|
|
3771
|
+
var STORAGE_UNUSABLE = /* @__PURE__ */ Symbol("storage-unusable");
|
|
3772
|
+
function canonicalApiIdentity(apiUrl) {
|
|
3727
3773
|
try {
|
|
3728
|
-
|
|
3774
|
+
const url = new URL(apiUrl);
|
|
3775
|
+
return `${url.protocol.toLowerCase()}//${url.host.toLowerCase()}${url.pathname.replace(/\/+$/, "")}`;
|
|
3776
|
+
} catch {
|
|
3777
|
+
return apiUrl.trim().toLowerCase().replace(/\/+$/, "");
|
|
3778
|
+
}
|
|
3779
|
+
}
|
|
3780
|
+
function buildRefreshLockName(apiUrl, publishableKey, storageKey) {
|
|
3781
|
+
return `01software:customer-auth-refresh:${canonicalApiIdentity(apiUrl)}:${publishableKey}:${storageKey}`;
|
|
3782
|
+
}
|
|
3783
|
+
function getRefreshLockRequester() {
|
|
3784
|
+
try {
|
|
3785
|
+
if (typeof navigator === "undefined") return null;
|
|
3786
|
+
const locks = navigator.locks;
|
|
3787
|
+
if (!locks || typeof locks.request !== "function") return null;
|
|
3788
|
+
return (name, options, callback) => locks.request(
|
|
3789
|
+
name,
|
|
3790
|
+
options,
|
|
3791
|
+
() => callback()
|
|
3792
|
+
);
|
|
3793
|
+
} catch {
|
|
3794
|
+
return null;
|
|
3795
|
+
}
|
|
3796
|
+
}
|
|
3797
|
+
function parseSessionRecord(raw) {
|
|
3798
|
+
try {
|
|
3799
|
+
const parsed = JSON.parse(raw);
|
|
3800
|
+
if (!parsed || typeof parsed !== "object") return null;
|
|
3801
|
+
const candidate = parsed;
|
|
3802
|
+
if (candidate.v !== SESSION_RECORD_VERSION) return null;
|
|
3803
|
+
if (typeof candidate.rev !== "number" || !Number.isFinite(candidate.rev)) {
|
|
3804
|
+
return null;
|
|
3805
|
+
}
|
|
3806
|
+
const token = candidate.token;
|
|
3807
|
+
const refreshToken = candidate.refreshToken;
|
|
3808
|
+
if (token !== null && typeof token !== "string") return null;
|
|
3809
|
+
if (refreshToken !== null && typeof refreshToken !== "string") return null;
|
|
3810
|
+
return {
|
|
3811
|
+
v: SESSION_RECORD_VERSION,
|
|
3812
|
+
rev: candidate.rev,
|
|
3813
|
+
token,
|
|
3814
|
+
refreshToken
|
|
3815
|
+
};
|
|
3729
3816
|
} catch {
|
|
3730
3817
|
return null;
|
|
3731
3818
|
}
|
|
3732
3819
|
}
|
|
3820
|
+
function readSessionRecord(recordKey, mirrorKey, mirrorRefreshKey) {
|
|
3821
|
+
let raw;
|
|
3822
|
+
try {
|
|
3823
|
+
raw = localStorage.getItem(recordKey);
|
|
3824
|
+
} catch {
|
|
3825
|
+
return STORAGE_UNUSABLE;
|
|
3826
|
+
}
|
|
3827
|
+
if (raw !== null) {
|
|
3828
|
+
const parsed = parseSessionRecord(raw);
|
|
3829
|
+
return parsed ? { record: parsed, stored: true } : STORAGE_UNUSABLE;
|
|
3830
|
+
}
|
|
3831
|
+
try {
|
|
3832
|
+
return {
|
|
3833
|
+
record: {
|
|
3834
|
+
v: SESSION_RECORD_VERSION,
|
|
3835
|
+
rev: 0,
|
|
3836
|
+
token: localStorage.getItem(mirrorKey),
|
|
3837
|
+
refreshToken: localStorage.getItem(mirrorRefreshKey)
|
|
3838
|
+
},
|
|
3839
|
+
stored: false
|
|
3840
|
+
};
|
|
3841
|
+
} catch {
|
|
3842
|
+
return STORAGE_UNUSABLE;
|
|
3843
|
+
}
|
|
3844
|
+
}
|
|
3733
3845
|
var CustomerAuth = class {
|
|
3734
3846
|
constructor(publishableKey, options, apiUrl) {
|
|
3735
3847
|
this.refreshPromise = null;
|
|
3848
|
+
/** Canonical record key; null unless persistence is active in a browser. */
|
|
3849
|
+
this.persistRecordKey = null;
|
|
3850
|
+
/** Advisory single-value access mirror, plus the retired refresh mirror. */
|
|
3851
|
+
this.persistMirrorKey = null;
|
|
3852
|
+
this.persistMirrorRefreshKey = null;
|
|
3853
|
+
/** Cross-context refresh lock name; null when there is no shared storage. */
|
|
3854
|
+
this.refreshLockName = null;
|
|
3855
|
+
/** Revision of the canonical record this instance last observed. */
|
|
3856
|
+
this.sessionRev = 0;
|
|
3736
3857
|
this.publishableKey = publishableKey;
|
|
3737
3858
|
this.baseUrl = resolveApiUrl(apiUrl);
|
|
3738
3859
|
const persist = options?.persist ?? true;
|
|
3739
3860
|
if (persist) {
|
|
3740
3861
|
const key = typeof persist === "string" ? persist : "customer-token";
|
|
3741
|
-
const refreshKey = `${key}:refresh`;
|
|
3742
3862
|
const isBrowser = typeof window !== "undefined";
|
|
3743
|
-
this.token =
|
|
3744
|
-
this.refreshTokenValue =
|
|
3745
|
-
|
|
3746
|
-
|
|
3747
|
-
|
|
3748
|
-
|
|
3749
|
-
|
|
3750
|
-
|
|
3751
|
-
|
|
3752
|
-
|
|
3753
|
-
|
|
3754
|
-
|
|
3755
|
-
|
|
3756
|
-
} catch {
|
|
3757
|
-
}
|
|
3758
|
-
} : void 0;
|
|
3863
|
+
this.token = null;
|
|
3864
|
+
this.refreshTokenValue = null;
|
|
3865
|
+
if (isBrowser) {
|
|
3866
|
+
this.persistRecordKey = `${key}:session`;
|
|
3867
|
+
this.persistMirrorKey = key;
|
|
3868
|
+
this.persistMirrorRefreshKey = `${key}:refresh`;
|
|
3869
|
+
this.refreshLockName = buildRefreshLockName(
|
|
3870
|
+
this.baseUrl,
|
|
3871
|
+
publishableKey,
|
|
3872
|
+
key
|
|
3873
|
+
);
|
|
3874
|
+
this.syncFromPersistedStorage();
|
|
3875
|
+
}
|
|
3759
3876
|
} else {
|
|
3760
3877
|
this.token = options?.token ?? null;
|
|
3761
3878
|
this.refreshTokenValue = options?.refreshToken ?? null;
|
|
@@ -3783,66 +3900,299 @@ var CustomerAuth = class {
|
|
|
3783
3900
|
body: JSON.stringify(data)
|
|
3784
3901
|
}
|
|
3785
3902
|
);
|
|
3786
|
-
this.
|
|
3787
|
-
|
|
3903
|
+
if (result.refreshToken) this.setSession(result.token, result.refreshToken);
|
|
3904
|
+
else this.setToken(result.token);
|
|
3788
3905
|
return result;
|
|
3789
3906
|
}
|
|
3790
3907
|
/**
|
|
3791
3908
|
* Refresh the current token. Uses a refresh token when available, otherwise
|
|
3792
3909
|
* falls back to the legacy bearer-token compatibility path.
|
|
3910
|
+
*
|
|
3911
|
+
* Refresh tokens are single-use, so a persisted session shared by several
|
|
3912
|
+
* tabs or SDK instances must rotate at most once: concurrent rotations of the
|
|
3913
|
+
* same token are replay from the server's point of view and revoke the whole
|
|
3914
|
+
* family. Persisted browser sessions therefore serialize rotation across
|
|
3915
|
+
* contexts and re-read the canonical persisted credentials inside the lock.
|
|
3793
3916
|
*/
|
|
3794
3917
|
async refreshToken() {
|
|
3918
|
+
this.syncFromPersistedStorage();
|
|
3795
3919
|
if (!this.refreshTokenValue && !this.token) {
|
|
3796
3920
|
throw new ApiError("Not authenticated", 401);
|
|
3797
3921
|
}
|
|
3798
3922
|
if (this.refreshPromise) return this.refreshPromise;
|
|
3799
|
-
|
|
3923
|
+
const intended = this.currentFence();
|
|
3924
|
+
this.refreshPromise = this.rotateAcrossContexts(intended);
|
|
3800
3925
|
try {
|
|
3801
3926
|
return await this.refreshPromise;
|
|
3802
3927
|
} finally {
|
|
3803
3928
|
this.refreshPromise = null;
|
|
3804
3929
|
}
|
|
3805
3930
|
}
|
|
3806
|
-
|
|
3807
|
-
|
|
3931
|
+
/**
|
|
3932
|
+
* Re-read the canonical persisted session. In persisted mode the shared
|
|
3933
|
+
* record — not this instance's memory — is the source of truth, so another
|
|
3934
|
+
* context's rotation, login, or logout is observed before the next credential
|
|
3935
|
+
* use. Change callbacks are deliberately not invoked: this is a read.
|
|
3936
|
+
*/
|
|
3937
|
+
syncFromPersistedStorage() {
|
|
3938
|
+
const canonical = this.readCanonicalSession();
|
|
3939
|
+
if (canonical === STORAGE_UNUSABLE) {
|
|
3940
|
+
this.disablePersistence();
|
|
3941
|
+
return;
|
|
3942
|
+
}
|
|
3943
|
+
if (!canonical) return;
|
|
3944
|
+
this.sessionRev = canonical.record.rev;
|
|
3945
|
+
this.token = canonical.record.token;
|
|
3946
|
+
this.refreshTokenValue = canonical.record.refreshToken;
|
|
3947
|
+
}
|
|
3948
|
+
/** `null` when this instance does not persist. */
|
|
3949
|
+
readCanonicalSession() {
|
|
3950
|
+
if (!this.persistRecordKey || !this.persistMirrorKey || !this.persistMirrorRefreshKey) {
|
|
3951
|
+
return null;
|
|
3952
|
+
}
|
|
3953
|
+
return readSessionRecord(
|
|
3954
|
+
this.persistRecordKey,
|
|
3955
|
+
this.persistMirrorKey,
|
|
3956
|
+
this.persistMirrorRefreshKey
|
|
3957
|
+
);
|
|
3958
|
+
}
|
|
3959
|
+
/**
|
|
3960
|
+
* Publish a change to canonical storage.
|
|
3961
|
+
*
|
|
3962
|
+
* The canonical pair is re-read immediately before the single record write so
|
|
3963
|
+
* that a field this call does not change is taken from that read rather than
|
|
3964
|
+
* from this instance's possibly stale copy. That merge is BEST-EFFORT: it
|
|
3965
|
+
* removes the stale counterpart an instance would otherwise carry, but it is
|
|
3966
|
+
* not a compare-and-swap. Web Storage offers none, and the synchronous public
|
|
3967
|
+
* mutators cannot await the refresh lease, so a commit that lands between
|
|
3968
|
+
* this read and the store operation is lost by ordering — including the
|
|
3969
|
+
* counterpart just read, which is then republished stale. The window is one
|
|
3970
|
+
* store operation wide and the server remains authoritative; see
|
|
3971
|
+
* `CUST-AUTH-033` for the stated limit.
|
|
3972
|
+
*
|
|
3973
|
+
* Returns `false` only when `requireLiveSession` refused a write because the
|
|
3974
|
+
* canonical session had already been cleared.
|
|
3975
|
+
*/
|
|
3976
|
+
commitPersistedSession(change, options) {
|
|
3977
|
+
const recordKey = this.persistRecordKey;
|
|
3978
|
+
const mirrorKey = this.persistMirrorKey;
|
|
3979
|
+
const mirrorRefreshKey = this.persistMirrorRefreshKey;
|
|
3980
|
+
const canonical = this.readCanonicalSession();
|
|
3981
|
+
if (canonical === null || !recordKey || !mirrorKey || !mirrorRefreshKey) {
|
|
3982
|
+
return true;
|
|
3983
|
+
}
|
|
3984
|
+
if (canonical === STORAGE_UNUSABLE) {
|
|
3985
|
+
this.disablePersistence();
|
|
3986
|
+
return true;
|
|
3987
|
+
}
|
|
3988
|
+
const current = canonical.record;
|
|
3989
|
+
if (options?.requireLiveSession && !current.token && !current.refreshToken) {
|
|
3990
|
+
return false;
|
|
3991
|
+
}
|
|
3992
|
+
const next = {
|
|
3993
|
+
v: SESSION_RECORD_VERSION,
|
|
3994
|
+
rev: current.rev + 1,
|
|
3995
|
+
token: "token" in change ? change.token ?? null : current.token,
|
|
3996
|
+
refreshToken: "refreshToken" in change ? change.refreshToken ?? null : current.refreshToken
|
|
3997
|
+
};
|
|
3998
|
+
if (
|
|
3999
|
+
// Only a state that is genuinely persisted can already say what this
|
|
4000
|
+
// write would say. An absent record reads as an empty pair, and treating
|
|
4001
|
+
// that as a no-op would drop the very tombstone that stops a later
|
|
4002
|
+
// migration from resurrecting the session out of the earlier layout.
|
|
4003
|
+
canonical.stored && next.token === current.token && next.refreshToken === current.refreshToken
|
|
4004
|
+
) {
|
|
4005
|
+
this.sessionRev = current.rev;
|
|
4006
|
+
this.token = current.token;
|
|
4007
|
+
this.refreshTokenValue = current.refreshToken;
|
|
4008
|
+
return true;
|
|
4009
|
+
}
|
|
4010
|
+
try {
|
|
4011
|
+
localStorage.setItem(recordKey, JSON.stringify(next));
|
|
4012
|
+
} catch {
|
|
4013
|
+
this.disablePersistence();
|
|
4014
|
+
return true;
|
|
4015
|
+
}
|
|
4016
|
+
this.sessionRev = next.rev;
|
|
4017
|
+
this.token = next.token;
|
|
4018
|
+
this.refreshTokenValue = next.refreshToken;
|
|
4019
|
+
try {
|
|
4020
|
+
localStorage.removeItem(mirrorRefreshKey);
|
|
4021
|
+
} catch {
|
|
4022
|
+
return true;
|
|
4023
|
+
}
|
|
4024
|
+
try {
|
|
4025
|
+
if (next.token) localStorage.setItem(mirrorKey, next.token);
|
|
4026
|
+
else localStorage.removeItem(mirrorKey);
|
|
4027
|
+
} catch {
|
|
4028
|
+
}
|
|
4029
|
+
return true;
|
|
4030
|
+
}
|
|
4031
|
+
/**
|
|
4032
|
+
* Storage rejected a write or a read, so it is not a canonical store for this
|
|
4033
|
+
* instance. Fall back to the memory-only session and take no lease: there is
|
|
4034
|
+
* no shared state left to coordinate. Nothing is purged — the record commit
|
|
4035
|
+
* is atomic, so no inconsistent state was published, and another context's
|
|
4036
|
+
* valid session must not be destroyed by this instance's local failure.
|
|
4037
|
+
*/
|
|
4038
|
+
disablePersistence() {
|
|
4039
|
+
this.persistRecordKey = null;
|
|
4040
|
+
this.persistMirrorKey = null;
|
|
4041
|
+
this.persistMirrorRefreshKey = null;
|
|
4042
|
+
this.refreshLockName = null;
|
|
4043
|
+
}
|
|
4044
|
+
/**
|
|
4045
|
+
* Compare the canonical record with the fence this call captured before it
|
|
4046
|
+
* left. Every persisted decision — rotate, converge, or fail closed — goes
|
|
4047
|
+
* through this one comparison. The revision catches a logout, a fresh login,
|
|
4048
|
+
* or a manual setter even when token values coincide; the value comparison
|
|
4049
|
+
* additionally catches a mirror-only writer that cannot bump a revision.
|
|
4050
|
+
*/
|
|
4051
|
+
classifyCanonicalState(intended) {
|
|
4052
|
+
if (!this.persistRecordKey) return "current";
|
|
4053
|
+
this.syncFromPersistedStorage();
|
|
4054
|
+
if (!this.persistRecordKey) return "current";
|
|
4055
|
+
if (!this.token && !this.refreshTokenValue) return "cleared";
|
|
4056
|
+
if (intended.refreshToken !== null) {
|
|
4057
|
+
return this.refreshTokenValue === intended.refreshToken ? "current" : "superseded";
|
|
4058
|
+
}
|
|
4059
|
+
return this.token === intended.token && !this.refreshTokenValue ? "current" : "superseded";
|
|
4060
|
+
}
|
|
4061
|
+
/** The canonical persisted pair, as a refresh result. */
|
|
4062
|
+
canonicalPair(token) {
|
|
4063
|
+
return {
|
|
4064
|
+
token,
|
|
4065
|
+
...this.refreshTokenValue ? { refreshToken: this.refreshTokenValue } : {}
|
|
4066
|
+
};
|
|
4067
|
+
}
|
|
4068
|
+
/** Current credentials plus the revision they were observed at. */
|
|
4069
|
+
currentFence() {
|
|
4070
|
+
return { token: this.token, refreshToken: this.refreshTokenValue };
|
|
4071
|
+
}
|
|
4072
|
+
/**
|
|
4073
|
+
* Clear an access credential a 401 just proved unusable — but only while it
|
|
4074
|
+
* is still the canonical one. A rotation committed by another context while
|
|
4075
|
+
* the request was in flight is newer than this 401, so it must survive; the
|
|
4076
|
+
* instance adopts it instead. The refresh entry is never touched here: a
|
|
4077
|
+
* rejected access token says nothing about the refresh family.
|
|
4078
|
+
*/
|
|
4079
|
+
invalidateAccessToken(spentToken) {
|
|
4080
|
+
this.syncFromPersistedStorage();
|
|
4081
|
+
if (this.token !== spentToken) return;
|
|
4082
|
+
this.setToken(null);
|
|
4083
|
+
}
|
|
4084
|
+
/** Commit both credentials in one canonical write. */
|
|
4085
|
+
setSession(token, refreshToken, options) {
|
|
4086
|
+
this.token = token;
|
|
4087
|
+
this.refreshTokenValue = refreshToken;
|
|
4088
|
+
const committed = this.commitPersistedSession(
|
|
4089
|
+
{ token, refreshToken },
|
|
4090
|
+
options
|
|
4091
|
+
);
|
|
4092
|
+
if (!committed) return false;
|
|
4093
|
+
this.onTokenChange?.(this.token);
|
|
4094
|
+
this.onRefreshTokenChange?.(this.refreshTokenValue);
|
|
4095
|
+
return true;
|
|
4096
|
+
}
|
|
4097
|
+
/**
|
|
4098
|
+
* Serialize rotation against every other context sharing the same storage
|
|
4099
|
+
* key and API/tenant identity. Coordination is best-effort: when it is
|
|
4100
|
+
* unavailable or times out the rotation still happens, so server-side reuse
|
|
4101
|
+
* detection is never bypassed by a client that failed to coordinate.
|
|
4102
|
+
*/
|
|
4103
|
+
async rotateAcrossContexts(intended) {
|
|
4104
|
+
const lockName = this.refreshLockName;
|
|
4105
|
+
const requestLock = lockName ? getRefreshLockRequester() : null;
|
|
4106
|
+
if (!lockName || !requestLock) {
|
|
4107
|
+
return this.rotateWithCanonicalState(intended);
|
|
4108
|
+
}
|
|
4109
|
+
const controller = new AbortController();
|
|
4110
|
+
const timer = setTimeout(() => controller.abort(), REFRESH_LOCK_TIMEOUT);
|
|
4111
|
+
let entered = false;
|
|
4112
|
+
try {
|
|
4113
|
+
return await requestLock(
|
|
4114
|
+
lockName,
|
|
4115
|
+
{ signal: controller.signal },
|
|
4116
|
+
async () => {
|
|
4117
|
+
entered = true;
|
|
4118
|
+
return this.rotateWithCanonicalState(intended);
|
|
4119
|
+
}
|
|
4120
|
+
);
|
|
4121
|
+
} catch (error) {
|
|
4122
|
+
if (entered) throw error;
|
|
4123
|
+
return this.rotateWithCanonicalState(intended);
|
|
4124
|
+
} finally {
|
|
4125
|
+
clearTimeout(timer);
|
|
4126
|
+
}
|
|
4127
|
+
}
|
|
4128
|
+
/**
|
|
4129
|
+
* Decide, against freshly re-read canonical state, whether this context still
|
|
4130
|
+
* owns the rotation.
|
|
4131
|
+
*/
|
|
4132
|
+
async rotateWithCanonicalState(intended) {
|
|
4133
|
+
let fence = intended;
|
|
4134
|
+
for (let attempt = 0; attempt < 2; attempt += 1) {
|
|
4135
|
+
const state = this.classifyCanonicalState(fence);
|
|
4136
|
+
if (state === "cleared") throw new ApiError("Not authenticated", 401);
|
|
4137
|
+
if (state === "current") return this._doRefreshToken(fence);
|
|
4138
|
+
if (this.token) return this.canonicalPair(this.token);
|
|
4139
|
+
fence = this.currentFence();
|
|
4140
|
+
}
|
|
4141
|
+
throw new ApiError("Not authenticated", 401);
|
|
4142
|
+
}
|
|
4143
|
+
async _doRefreshToken(intended) {
|
|
4144
|
+
const init = intended.refreshToken ? {
|
|
3808
4145
|
method: "POST",
|
|
3809
|
-
body: JSON.stringify({ refreshToken:
|
|
4146
|
+
body: JSON.stringify({ refreshToken: intended.refreshToken })
|
|
3810
4147
|
} : {
|
|
3811
4148
|
method: "POST",
|
|
3812
|
-
headers: { Authorization: `Bearer ${
|
|
4149
|
+
headers: { Authorization: `Bearer ${intended.token}` }
|
|
3813
4150
|
};
|
|
3814
4151
|
const result = await this.requestJson(
|
|
3815
4152
|
"/api/customers/refresh",
|
|
3816
4153
|
init
|
|
3817
4154
|
);
|
|
3818
|
-
this.
|
|
3819
|
-
if (
|
|
3820
|
-
|
|
4155
|
+
const state = this.classifyCanonicalState(intended);
|
|
4156
|
+
if (state === "cleared") throw new ApiError("Not authenticated", 401);
|
|
4157
|
+
if (state === "superseded") {
|
|
4158
|
+
if (this.token) return this.canonicalPair(this.token);
|
|
4159
|
+
throw new ApiError("Not authenticated", 401);
|
|
4160
|
+
}
|
|
4161
|
+
const replacedAccessToken = this.token !== null && this.token !== intended.token ? this.token : null;
|
|
4162
|
+
const nextToken = replacedAccessToken ?? result.token;
|
|
4163
|
+
const nextRefresh = result.refreshToken ?? null;
|
|
4164
|
+
if (!this.setSession(nextToken, nextRefresh, { requireLiveSession: true })) {
|
|
4165
|
+
throw new ApiError("Not authenticated", 401);
|
|
4166
|
+
}
|
|
4167
|
+
return {
|
|
4168
|
+
token: nextToken,
|
|
4169
|
+
...nextRefresh ? { refreshToken: nextRefresh } : {}
|
|
4170
|
+
};
|
|
3821
4171
|
}
|
|
3822
4172
|
/**
|
|
3823
4173
|
* Clear the stored token
|
|
3824
4174
|
*/
|
|
3825
4175
|
logout() {
|
|
3826
|
-
this.
|
|
3827
|
-
this.setRefreshToken(null);
|
|
4176
|
+
this.setSession(null, null);
|
|
3828
4177
|
}
|
|
3829
4178
|
/**
|
|
3830
4179
|
* Get the current authenticated customer's profile
|
|
3831
4180
|
*/
|
|
3832
4181
|
async me() {
|
|
3833
|
-
|
|
4182
|
+
const token = this.getToken();
|
|
4183
|
+
if (!token) return null;
|
|
3834
4184
|
try {
|
|
3835
4185
|
const data = await this.requestJson(
|
|
3836
4186
|
"/api/customers/me",
|
|
3837
4187
|
{
|
|
3838
4188
|
method: "GET",
|
|
3839
|
-
headers: { Authorization: `Bearer ${
|
|
4189
|
+
headers: { Authorization: `Bearer ${token}` }
|
|
3840
4190
|
}
|
|
3841
4191
|
);
|
|
3842
4192
|
return data.customer ?? null;
|
|
3843
4193
|
} catch (error) {
|
|
3844
4194
|
if (error instanceof SDKError && error.status === 401) {
|
|
3845
|
-
this.
|
|
4195
|
+
this.invalidateAccessToken(token);
|
|
3846
4196
|
return null;
|
|
3847
4197
|
}
|
|
3848
4198
|
throw error;
|
|
@@ -3870,12 +4220,13 @@ var CustomerAuth = class {
|
|
|
3870
4220
|
* Update the authenticated customer's profile (name, phone, marketingConsent)
|
|
3871
4221
|
*/
|
|
3872
4222
|
async updateProfile(data) {
|
|
3873
|
-
|
|
4223
|
+
const token = this.getToken();
|
|
4224
|
+
if (!token) throw new ApiError("Not authenticated", 401);
|
|
3874
4225
|
const result = await this.requestJson(
|
|
3875
4226
|
"/api/customers/me",
|
|
3876
4227
|
{
|
|
3877
4228
|
method: "PATCH",
|
|
3878
|
-
headers: { Authorization: `Bearer ${
|
|
4229
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
3879
4230
|
body: JSON.stringify(data)
|
|
3880
4231
|
}
|
|
3881
4232
|
);
|
|
@@ -3885,10 +4236,11 @@ var CustomerAuth = class {
|
|
|
3885
4236
|
* Change the password of the currently authenticated customer
|
|
3886
4237
|
*/
|
|
3887
4238
|
async changePassword(currentPassword, newPassword) {
|
|
3888
|
-
|
|
4239
|
+
const token = this.getToken();
|
|
4240
|
+
if (!token) throw new ApiError("Not authenticated", 401);
|
|
3889
4241
|
await this.requestJson("/api/customers/change-password", {
|
|
3890
4242
|
method: "POST",
|
|
3891
|
-
headers: { Authorization: `Bearer ${
|
|
4243
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
3892
4244
|
body: JSON.stringify({ currentPassword, newPassword })
|
|
3893
4245
|
});
|
|
3894
4246
|
}
|
|
@@ -3896,7 +4248,8 @@ var CustomerAuth = class {
|
|
|
3896
4248
|
* Get the authenticated customer's orders with pagination and optional status filter
|
|
3897
4249
|
*/
|
|
3898
4250
|
async getMyOrders(options) {
|
|
3899
|
-
|
|
4251
|
+
const token = this.getToken();
|
|
4252
|
+
if (!token) throw new ApiError("Not authenticated", 401);
|
|
3900
4253
|
const params = new URLSearchParams();
|
|
3901
4254
|
if (options?.page) params.set("page", String(options.page));
|
|
3902
4255
|
if (options?.limit) params.set("limit", String(options.limit));
|
|
@@ -3918,21 +4271,27 @@ var CustomerAuth = class {
|
|
|
3918
4271
|
`/api/customers/me/orders${qs ? `?${qs}` : ""}`,
|
|
3919
4272
|
{
|
|
3920
4273
|
method: "GET",
|
|
3921
|
-
headers: { Authorization: `Bearer ${
|
|
4274
|
+
headers: { Authorization: `Bearer ${token}` }
|
|
3922
4275
|
}
|
|
3923
4276
|
);
|
|
3924
4277
|
return validatePublicOrderList(result);
|
|
3925
4278
|
}
|
|
3926
4279
|
/**
|
|
3927
|
-
* Get the current token (or null if not authenticated)
|
|
4280
|
+
* Get the current token (or null if not authenticated).
|
|
4281
|
+
*
|
|
4282
|
+
* In persisted mode this re-reads shared storage first, so a rotation or
|
|
4283
|
+
* logout performed by another tab is observed before the token is used.
|
|
3928
4284
|
*/
|
|
3929
4285
|
getToken() {
|
|
4286
|
+
this.syncFromPersistedStorage();
|
|
3930
4287
|
return this.token;
|
|
3931
4288
|
}
|
|
3932
4289
|
/**
|
|
3933
|
-
* Get the current refresh token (or null if unavailable)
|
|
4290
|
+
* Get the current refresh token (or null if unavailable). Persisted mode
|
|
4291
|
+
* re-reads shared storage first, like {@link CustomerAuth.getToken}.
|
|
3934
4292
|
*/
|
|
3935
4293
|
getRefreshToken() {
|
|
4294
|
+
this.syncFromPersistedStorage();
|
|
3936
4295
|
return this.refreshTokenValue;
|
|
3937
4296
|
}
|
|
3938
4297
|
/**
|
|
@@ -3940,6 +4299,7 @@ var CustomerAuth = class {
|
|
|
3940
4299
|
*/
|
|
3941
4300
|
setToken(token) {
|
|
3942
4301
|
this.token = token;
|
|
4302
|
+
this.commitPersistedSession({ token });
|
|
3943
4303
|
this.onTokenChange?.(token);
|
|
3944
4304
|
}
|
|
3945
4305
|
/**
|
|
@@ -3947,13 +4307,14 @@ var CustomerAuth = class {
|
|
|
3947
4307
|
*/
|
|
3948
4308
|
setRefreshToken(refreshToken) {
|
|
3949
4309
|
this.refreshTokenValue = refreshToken;
|
|
4310
|
+
this.commitPersistedSession({ refreshToken });
|
|
3950
4311
|
this.onRefreshTokenChange?.(refreshToken);
|
|
3951
4312
|
}
|
|
3952
4313
|
/**
|
|
3953
4314
|
* Check if the customer is currently authenticated
|
|
3954
4315
|
*/
|
|
3955
4316
|
isAuthenticated() {
|
|
3956
|
-
return this.
|
|
4317
|
+
return this.getToken() !== null;
|
|
3957
4318
|
}
|
|
3958
4319
|
/**
|
|
3959
4320
|
* Internal: make a request with timeout and error handling.
|
|
@@ -4299,8 +4660,12 @@ var PaymentSessionApi = class extends CustomerScopedApi {
|
|
|
4299
4660
|
...Object.keys(headers).length > 0 && { headers }
|
|
4300
4661
|
});
|
|
4301
4662
|
}
|
|
4302
|
-
async paymentSession(result, expectedProvider) {
|
|
4303
|
-
return validatePublicPaymentSession(
|
|
4663
|
+
async paymentSession(result, expectedProvider, operation2) {
|
|
4664
|
+
return validatePublicPaymentSession(
|
|
4665
|
+
await result,
|
|
4666
|
+
expectedProvider,
|
|
4667
|
+
operation2
|
|
4668
|
+
);
|
|
4304
4669
|
}
|
|
4305
4670
|
create(params) {
|
|
4306
4671
|
const { idempotencyKey, ...body } = params;
|
|
@@ -4347,7 +4712,8 @@ var PaymentSessionApi = class extends CustomerScopedApi {
|
|
|
4347
4712
|
const endpoint = this.serverTransport ? COMMERCE_ENDPOINTS.paymentSessions.server.cancel : COMMERCE_ENDPOINTS.paymentSessions.cancel;
|
|
4348
4713
|
return this.paymentSession(
|
|
4349
4714
|
this.post(endpoint, body, idempotencyKey),
|
|
4350
|
-
"toss"
|
|
4715
|
+
"toss",
|
|
4716
|
+
"cancel"
|
|
4351
4717
|
);
|
|
4352
4718
|
}
|
|
4353
4719
|
cancelPortOne(params) {
|
|
@@ -4356,7 +4722,8 @@ var PaymentSessionApi = class extends CustomerScopedApi {
|
|
|
4356
4722
|
const endpoint = this.serverTransport ? COMMERCE_ENDPOINTS.paymentSessions.server.cancel : COMMERCE_ENDPOINTS.paymentSessions.cancel;
|
|
4357
4723
|
return this.paymentSession(
|
|
4358
4724
|
this.post(endpoint, body, idempotencyKey),
|
|
4359
|
-
"portone"
|
|
4725
|
+
"portone",
|
|
4726
|
+
"cancel"
|
|
4360
4727
|
);
|
|
4361
4728
|
}
|
|
4362
4729
|
confirm(params) {
|