@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.cjs
CHANGED
|
@@ -1396,6 +1396,11 @@ var COMMERCE_ENDPOINT_OPERATIONS = {
|
|
|
1396
1396
|
detail: strictCommerceOperation("checkouts", "post", "/detail"),
|
|
1397
1397
|
query: strictCommerceOperation("checkouts", "post", "/query")
|
|
1398
1398
|
},
|
|
1399
|
+
draftOrders: {
|
|
1400
|
+
calculate: operation("draft-orders", "post", "/:id/calculate"),
|
|
1401
|
+
complete: operation("draft-orders", "post", "/:id/complete"),
|
|
1402
|
+
finalize: operation("draft-orders", "post", "/:id/finalize")
|
|
1403
|
+
},
|
|
1399
1404
|
transactions: {
|
|
1400
1405
|
update: operation("transactions", "post", "/update")
|
|
1401
1406
|
},
|
|
@@ -1496,7 +1501,15 @@ var portOneStandardActionCapabilityOperations = /* @__PURE__ */ new Set([
|
|
|
1496
1501
|
var MEMBERSHIP_ENDPOINT_OPERATIONS = {
|
|
1497
1502
|
subscribe: operation(null, "post", "/memberships/subscribe"),
|
|
1498
1503
|
cancel: operation(null, "post", "/memberships/:id/cancel"),
|
|
1499
|
-
me: operation(null, "get", "/memberships/me")
|
|
1504
|
+
me: operation(null, "get", "/memberships/me"),
|
|
1505
|
+
chargeResult: operation(null, "post", "/memberships/:id/charge-result"),
|
|
1506
|
+
refundResult: operation(null, "post", "/memberships/:id/refund-result"),
|
|
1507
|
+
revoke: operation(null, "post", "/memberships/:id/revoke")
|
|
1508
|
+
};
|
|
1509
|
+
var CUSTOMER_GROUP_ENDPOINT_OPERATIONS = {
|
|
1510
|
+
members: operation("customer-groups", "get", "/:id/members"),
|
|
1511
|
+
grant: operation("customer-groups", "post", "/:id/members/grant"),
|
|
1512
|
+
revoke: operation("customer-groups", "post", "/:id/members/revoke")
|
|
1500
1513
|
};
|
|
1501
1514
|
var EMBEDDED_ADMIN_ENDPOINT_OPERATIONS = {
|
|
1502
1515
|
show: operation("tenants", "get", "/embedded-admin-app"),
|
|
@@ -2206,7 +2219,10 @@ var ORDER_PRIMARY_DISPLAY_STATUSES = [
|
|
|
2206
2219
|
];
|
|
2207
2220
|
var CHECKOUT_STATUSES = ["open", "placed", "abandoned"];
|
|
2208
2221
|
var ABANDONED_REASONS = ["expired", "customer", "operator"];
|
|
2209
|
-
|
|
2222
|
+
function managedPaymentProviders(providers) {
|
|
2223
|
+
return providers;
|
|
2224
|
+
}
|
|
2225
|
+
var PAYMENT_PROVIDERS = managedPaymentProviders(["toss", "portone"]);
|
|
2210
2226
|
var PAYMENT_PROVIDER_STATUSES = [
|
|
2211
2227
|
"requires_payment_method",
|
|
2212
2228
|
"requires_action",
|
|
@@ -2254,6 +2270,26 @@ function invalidCanonicalResponse(resource, details) {
|
|
|
2254
2270
|
function fail(resource, path, expected) {
|
|
2255
2271
|
invalidCanonicalResponse(resource, { path, expected });
|
|
2256
2272
|
}
|
|
2273
|
+
function providerMismatch(resource, expected, actual, operation2) {
|
|
2274
|
+
if (operation2 === "cancel") {
|
|
2275
|
+
const method = {
|
|
2276
|
+
toss: "cancel()",
|
|
2277
|
+
portone: "cancelPortOne()"
|
|
2278
|
+
}[actual];
|
|
2279
|
+
throw createApiError(
|
|
2280
|
+
`Canonical ${resource} response provider mismatch: expected ${expected}, received ${actual}.`,
|
|
2281
|
+
502,
|
|
2282
|
+
{ resource, path: "$.provider", expected, actual },
|
|
2283
|
+
`The server applied the cancellation to a ${actual} PaymentSession, but this SDK method expects ${expected}.`,
|
|
2284
|
+
`Re-invoke ${method} with the same idempotencyKey; do not create a new key.`
|
|
2285
|
+
);
|
|
2286
|
+
}
|
|
2287
|
+
invalidCanonicalResponse(resource, {
|
|
2288
|
+
path: "$.provider",
|
|
2289
|
+
expected,
|
|
2290
|
+
actual
|
|
2291
|
+
});
|
|
2292
|
+
}
|
|
2257
2293
|
function record(value, resource, path) {
|
|
2258
2294
|
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
2259
2295
|
fail(resource, path, "object");
|
|
@@ -3546,7 +3582,7 @@ function validateNextAction(value, provider, resource, path) {
|
|
|
3546
3582
|
if (action.customerEmail !== void 0)
|
|
3547
3583
|
validateCustomer({ email: action.customerEmail }, resource, path);
|
|
3548
3584
|
}
|
|
3549
|
-
function validatePublicPaymentSession(value, expectedProvider) {
|
|
3585
|
+
function validatePublicPaymentSession(value, expectedProvider, operation2) {
|
|
3550
3586
|
const resource = "PaymentSession";
|
|
3551
3587
|
const session = record(value, resource, "$");
|
|
3552
3588
|
const provider = enumValue(
|
|
@@ -3555,9 +3591,6 @@ function validatePublicPaymentSession(value, expectedProvider) {
|
|
|
3555
3591
|
resource,
|
|
3556
3592
|
"$.provider"
|
|
3557
3593
|
);
|
|
3558
|
-
if (expectedProvider !== void 0 && provider !== expectedProvider) {
|
|
3559
|
-
fail(resource, "$.provider", expectedProvider);
|
|
3560
|
-
}
|
|
3561
3594
|
const status = enumValue(
|
|
3562
3595
|
session.providerStatus,
|
|
3563
3596
|
PAYMENT_PROVIDER_STATUSES,
|
|
@@ -3761,44 +3794,128 @@ function validatePublicPaymentSession(value, expectedProvider) {
|
|
|
3761
3794
|
"$.cancellationReason"
|
|
3762
3795
|
);
|
|
3763
3796
|
}
|
|
3797
|
+
if (expectedProvider !== void 0 && provider !== expectedProvider) {
|
|
3798
|
+
if (operation2 === "cancel" && status !== "canceling" && status !== "canceled") {
|
|
3799
|
+
fail(
|
|
3800
|
+
resource,
|
|
3801
|
+
"$.providerStatus",
|
|
3802
|
+
"canceling or canceled after a successful cancellation"
|
|
3803
|
+
);
|
|
3804
|
+
}
|
|
3805
|
+
providerMismatch(resource, expectedProvider, provider, operation2);
|
|
3806
|
+
}
|
|
3764
3807
|
return value;
|
|
3765
3808
|
}
|
|
3766
3809
|
|
|
3767
3810
|
// src/core/customer/customer-auth.ts
|
|
3768
3811
|
var DEFAULT_TIMEOUT2 = 15e3;
|
|
3769
|
-
|
|
3812
|
+
var REFRESH_LOCK_TIMEOUT = DEFAULT_TIMEOUT2 * 3;
|
|
3813
|
+
var SESSION_RECORD_VERSION = 1;
|
|
3814
|
+
var STORAGE_UNUSABLE = /* @__PURE__ */ Symbol("storage-unusable");
|
|
3815
|
+
function canonicalApiIdentity(apiUrl) {
|
|
3770
3816
|
try {
|
|
3771
|
-
|
|
3817
|
+
const url = new URL(apiUrl);
|
|
3818
|
+
return `${url.protocol.toLowerCase()}//${url.host.toLowerCase()}${url.pathname.replace(/\/+$/, "")}`;
|
|
3819
|
+
} catch {
|
|
3820
|
+
return apiUrl.trim().toLowerCase().replace(/\/+$/, "");
|
|
3821
|
+
}
|
|
3822
|
+
}
|
|
3823
|
+
function buildRefreshLockName(apiUrl, publishableKey, storageKey) {
|
|
3824
|
+
return `01software:customer-auth-refresh:${canonicalApiIdentity(apiUrl)}:${publishableKey}:${storageKey}`;
|
|
3825
|
+
}
|
|
3826
|
+
function getRefreshLockRequester() {
|
|
3827
|
+
try {
|
|
3828
|
+
if (typeof navigator === "undefined") return null;
|
|
3829
|
+
const locks = navigator.locks;
|
|
3830
|
+
if (!locks || typeof locks.request !== "function") return null;
|
|
3831
|
+
return (name, options, callback) => locks.request(
|
|
3832
|
+
name,
|
|
3833
|
+
options,
|
|
3834
|
+
() => callback()
|
|
3835
|
+
);
|
|
3836
|
+
} catch {
|
|
3837
|
+
return null;
|
|
3838
|
+
}
|
|
3839
|
+
}
|
|
3840
|
+
function parseSessionRecord(raw) {
|
|
3841
|
+
try {
|
|
3842
|
+
const parsed = JSON.parse(raw);
|
|
3843
|
+
if (!parsed || typeof parsed !== "object") return null;
|
|
3844
|
+
const candidate = parsed;
|
|
3845
|
+
if (candidate.v !== SESSION_RECORD_VERSION) return null;
|
|
3846
|
+
if (typeof candidate.rev !== "number" || !Number.isFinite(candidate.rev)) {
|
|
3847
|
+
return null;
|
|
3848
|
+
}
|
|
3849
|
+
const token = candidate.token;
|
|
3850
|
+
const refreshToken = candidate.refreshToken;
|
|
3851
|
+
if (token !== null && typeof token !== "string") return null;
|
|
3852
|
+
if (refreshToken !== null && typeof refreshToken !== "string") return null;
|
|
3853
|
+
return {
|
|
3854
|
+
v: SESSION_RECORD_VERSION,
|
|
3855
|
+
rev: candidate.rev,
|
|
3856
|
+
token,
|
|
3857
|
+
refreshToken
|
|
3858
|
+
};
|
|
3772
3859
|
} catch {
|
|
3773
3860
|
return null;
|
|
3774
3861
|
}
|
|
3775
3862
|
}
|
|
3863
|
+
function readSessionRecord(recordKey, mirrorKey, mirrorRefreshKey) {
|
|
3864
|
+
let raw;
|
|
3865
|
+
try {
|
|
3866
|
+
raw = localStorage.getItem(recordKey);
|
|
3867
|
+
} catch {
|
|
3868
|
+
return STORAGE_UNUSABLE;
|
|
3869
|
+
}
|
|
3870
|
+
if (raw !== null) {
|
|
3871
|
+
const parsed = parseSessionRecord(raw);
|
|
3872
|
+
return parsed ? { record: parsed, stored: true } : STORAGE_UNUSABLE;
|
|
3873
|
+
}
|
|
3874
|
+
try {
|
|
3875
|
+
return {
|
|
3876
|
+
record: {
|
|
3877
|
+
v: SESSION_RECORD_VERSION,
|
|
3878
|
+
rev: 0,
|
|
3879
|
+
token: localStorage.getItem(mirrorKey),
|
|
3880
|
+
refreshToken: localStorage.getItem(mirrorRefreshKey)
|
|
3881
|
+
},
|
|
3882
|
+
stored: false
|
|
3883
|
+
};
|
|
3884
|
+
} catch {
|
|
3885
|
+
return STORAGE_UNUSABLE;
|
|
3886
|
+
}
|
|
3887
|
+
}
|
|
3776
3888
|
var CustomerAuth = class {
|
|
3777
3889
|
constructor(publishableKey, options, apiUrl) {
|
|
3778
3890
|
this.refreshPromise = null;
|
|
3891
|
+
/** Canonical record key; null unless persistence is active in a browser. */
|
|
3892
|
+
this.persistRecordKey = null;
|
|
3893
|
+
/** Advisory single-value access mirror, plus the retired refresh mirror. */
|
|
3894
|
+
this.persistMirrorKey = null;
|
|
3895
|
+
this.persistMirrorRefreshKey = null;
|
|
3896
|
+
/** Cross-context refresh lock name; null when there is no shared storage. */
|
|
3897
|
+
this.refreshLockName = null;
|
|
3898
|
+
/** Revision of the canonical record this instance last observed. */
|
|
3899
|
+
this.sessionRev = 0;
|
|
3779
3900
|
this.publishableKey = publishableKey;
|
|
3780
3901
|
this.baseUrl = resolveApiUrl(apiUrl);
|
|
3781
3902
|
const persist = options?.persist ?? true;
|
|
3782
3903
|
if (persist) {
|
|
3783
3904
|
const key = typeof persist === "string" ? persist : "customer-token";
|
|
3784
|
-
const refreshKey = `${key}:refresh`;
|
|
3785
3905
|
const isBrowser = typeof window !== "undefined";
|
|
3786
|
-
this.token =
|
|
3787
|
-
this.refreshTokenValue =
|
|
3788
|
-
|
|
3789
|
-
|
|
3790
|
-
|
|
3791
|
-
|
|
3792
|
-
|
|
3793
|
-
|
|
3794
|
-
|
|
3795
|
-
|
|
3796
|
-
|
|
3797
|
-
|
|
3798
|
-
|
|
3799
|
-
} catch {
|
|
3800
|
-
}
|
|
3801
|
-
} : void 0;
|
|
3906
|
+
this.token = null;
|
|
3907
|
+
this.refreshTokenValue = null;
|
|
3908
|
+
if (isBrowser) {
|
|
3909
|
+
this.persistRecordKey = `${key}:session`;
|
|
3910
|
+
this.persistMirrorKey = key;
|
|
3911
|
+
this.persistMirrorRefreshKey = `${key}:refresh`;
|
|
3912
|
+
this.refreshLockName = buildRefreshLockName(
|
|
3913
|
+
this.baseUrl,
|
|
3914
|
+
publishableKey,
|
|
3915
|
+
key
|
|
3916
|
+
);
|
|
3917
|
+
this.syncFromPersistedStorage();
|
|
3918
|
+
}
|
|
3802
3919
|
} else {
|
|
3803
3920
|
this.token = options?.token ?? null;
|
|
3804
3921
|
this.refreshTokenValue = options?.refreshToken ?? null;
|
|
@@ -3826,66 +3943,299 @@ var CustomerAuth = class {
|
|
|
3826
3943
|
body: JSON.stringify(data)
|
|
3827
3944
|
}
|
|
3828
3945
|
);
|
|
3829
|
-
this.
|
|
3830
|
-
|
|
3946
|
+
if (result.refreshToken) this.setSession(result.token, result.refreshToken);
|
|
3947
|
+
else this.setToken(result.token);
|
|
3831
3948
|
return result;
|
|
3832
3949
|
}
|
|
3833
3950
|
/**
|
|
3834
3951
|
* Refresh the current token. Uses a refresh token when available, otherwise
|
|
3835
3952
|
* falls back to the legacy bearer-token compatibility path.
|
|
3953
|
+
*
|
|
3954
|
+
* Refresh tokens are single-use, so a persisted session shared by several
|
|
3955
|
+
* tabs or SDK instances must rotate at most once: concurrent rotations of the
|
|
3956
|
+
* same token are replay from the server's point of view and revoke the whole
|
|
3957
|
+
* family. Persisted browser sessions therefore serialize rotation across
|
|
3958
|
+
* contexts and re-read the canonical persisted credentials inside the lock.
|
|
3836
3959
|
*/
|
|
3837
3960
|
async refreshToken() {
|
|
3961
|
+
this.syncFromPersistedStorage();
|
|
3838
3962
|
if (!this.refreshTokenValue && !this.token) {
|
|
3839
3963
|
throw new ApiError("Not authenticated", 401);
|
|
3840
3964
|
}
|
|
3841
3965
|
if (this.refreshPromise) return this.refreshPromise;
|
|
3842
|
-
|
|
3966
|
+
const intended = this.currentFence();
|
|
3967
|
+
this.refreshPromise = this.rotateAcrossContexts(intended);
|
|
3843
3968
|
try {
|
|
3844
3969
|
return await this.refreshPromise;
|
|
3845
3970
|
} finally {
|
|
3846
3971
|
this.refreshPromise = null;
|
|
3847
3972
|
}
|
|
3848
3973
|
}
|
|
3849
|
-
|
|
3850
|
-
|
|
3974
|
+
/**
|
|
3975
|
+
* Re-read the canonical persisted session. In persisted mode the shared
|
|
3976
|
+
* record — not this instance's memory — is the source of truth, so another
|
|
3977
|
+
* context's rotation, login, or logout is observed before the next credential
|
|
3978
|
+
* use. Change callbacks are deliberately not invoked: this is a read.
|
|
3979
|
+
*/
|
|
3980
|
+
syncFromPersistedStorage() {
|
|
3981
|
+
const canonical = this.readCanonicalSession();
|
|
3982
|
+
if (canonical === STORAGE_UNUSABLE) {
|
|
3983
|
+
this.disablePersistence();
|
|
3984
|
+
return;
|
|
3985
|
+
}
|
|
3986
|
+
if (!canonical) return;
|
|
3987
|
+
this.sessionRev = canonical.record.rev;
|
|
3988
|
+
this.token = canonical.record.token;
|
|
3989
|
+
this.refreshTokenValue = canonical.record.refreshToken;
|
|
3990
|
+
}
|
|
3991
|
+
/** `null` when this instance does not persist. */
|
|
3992
|
+
readCanonicalSession() {
|
|
3993
|
+
if (!this.persistRecordKey || !this.persistMirrorKey || !this.persistMirrorRefreshKey) {
|
|
3994
|
+
return null;
|
|
3995
|
+
}
|
|
3996
|
+
return readSessionRecord(
|
|
3997
|
+
this.persistRecordKey,
|
|
3998
|
+
this.persistMirrorKey,
|
|
3999
|
+
this.persistMirrorRefreshKey
|
|
4000
|
+
);
|
|
4001
|
+
}
|
|
4002
|
+
/**
|
|
4003
|
+
* Publish a change to canonical storage.
|
|
4004
|
+
*
|
|
4005
|
+
* The canonical pair is re-read immediately before the single record write so
|
|
4006
|
+
* that a field this call does not change is taken from that read rather than
|
|
4007
|
+
* from this instance's possibly stale copy. That merge is BEST-EFFORT: it
|
|
4008
|
+
* removes the stale counterpart an instance would otherwise carry, but it is
|
|
4009
|
+
* not a compare-and-swap. Web Storage offers none, and the synchronous public
|
|
4010
|
+
* mutators cannot await the refresh lease, so a commit that lands between
|
|
4011
|
+
* this read and the store operation is lost by ordering — including the
|
|
4012
|
+
* counterpart just read, which is then republished stale. The window is one
|
|
4013
|
+
* store operation wide and the server remains authoritative; see
|
|
4014
|
+
* `CUST-AUTH-033` for the stated limit.
|
|
4015
|
+
*
|
|
4016
|
+
* Returns `false` only when `requireLiveSession` refused a write because the
|
|
4017
|
+
* canonical session had already been cleared.
|
|
4018
|
+
*/
|
|
4019
|
+
commitPersistedSession(change, options) {
|
|
4020
|
+
const recordKey = this.persistRecordKey;
|
|
4021
|
+
const mirrorKey = this.persistMirrorKey;
|
|
4022
|
+
const mirrorRefreshKey = this.persistMirrorRefreshKey;
|
|
4023
|
+
const canonical = this.readCanonicalSession();
|
|
4024
|
+
if (canonical === null || !recordKey || !mirrorKey || !mirrorRefreshKey) {
|
|
4025
|
+
return true;
|
|
4026
|
+
}
|
|
4027
|
+
if (canonical === STORAGE_UNUSABLE) {
|
|
4028
|
+
this.disablePersistence();
|
|
4029
|
+
return true;
|
|
4030
|
+
}
|
|
4031
|
+
const current = canonical.record;
|
|
4032
|
+
if (options?.requireLiveSession && !current.token && !current.refreshToken) {
|
|
4033
|
+
return false;
|
|
4034
|
+
}
|
|
4035
|
+
const next = {
|
|
4036
|
+
v: SESSION_RECORD_VERSION,
|
|
4037
|
+
rev: current.rev + 1,
|
|
4038
|
+
token: "token" in change ? change.token ?? null : current.token,
|
|
4039
|
+
refreshToken: "refreshToken" in change ? change.refreshToken ?? null : current.refreshToken
|
|
4040
|
+
};
|
|
4041
|
+
if (
|
|
4042
|
+
// Only a state that is genuinely persisted can already say what this
|
|
4043
|
+
// write would say. An absent record reads as an empty pair, and treating
|
|
4044
|
+
// that as a no-op would drop the very tombstone that stops a later
|
|
4045
|
+
// migration from resurrecting the session out of the earlier layout.
|
|
4046
|
+
canonical.stored && next.token === current.token && next.refreshToken === current.refreshToken
|
|
4047
|
+
) {
|
|
4048
|
+
this.sessionRev = current.rev;
|
|
4049
|
+
this.token = current.token;
|
|
4050
|
+
this.refreshTokenValue = current.refreshToken;
|
|
4051
|
+
return true;
|
|
4052
|
+
}
|
|
4053
|
+
try {
|
|
4054
|
+
localStorage.setItem(recordKey, JSON.stringify(next));
|
|
4055
|
+
} catch {
|
|
4056
|
+
this.disablePersistence();
|
|
4057
|
+
return true;
|
|
4058
|
+
}
|
|
4059
|
+
this.sessionRev = next.rev;
|
|
4060
|
+
this.token = next.token;
|
|
4061
|
+
this.refreshTokenValue = next.refreshToken;
|
|
4062
|
+
try {
|
|
4063
|
+
localStorage.removeItem(mirrorRefreshKey);
|
|
4064
|
+
} catch {
|
|
4065
|
+
return true;
|
|
4066
|
+
}
|
|
4067
|
+
try {
|
|
4068
|
+
if (next.token) localStorage.setItem(mirrorKey, next.token);
|
|
4069
|
+
else localStorage.removeItem(mirrorKey);
|
|
4070
|
+
} catch {
|
|
4071
|
+
}
|
|
4072
|
+
return true;
|
|
4073
|
+
}
|
|
4074
|
+
/**
|
|
4075
|
+
* Storage rejected a write or a read, so it is not a canonical store for this
|
|
4076
|
+
* instance. Fall back to the memory-only session and take no lease: there is
|
|
4077
|
+
* no shared state left to coordinate. Nothing is purged — the record commit
|
|
4078
|
+
* is atomic, so no inconsistent state was published, and another context's
|
|
4079
|
+
* valid session must not be destroyed by this instance's local failure.
|
|
4080
|
+
*/
|
|
4081
|
+
disablePersistence() {
|
|
4082
|
+
this.persistRecordKey = null;
|
|
4083
|
+
this.persistMirrorKey = null;
|
|
4084
|
+
this.persistMirrorRefreshKey = null;
|
|
4085
|
+
this.refreshLockName = null;
|
|
4086
|
+
}
|
|
4087
|
+
/**
|
|
4088
|
+
* Compare the canonical record with the fence this call captured before it
|
|
4089
|
+
* left. Every persisted decision — rotate, converge, or fail closed — goes
|
|
4090
|
+
* through this one comparison. The revision catches a logout, a fresh login,
|
|
4091
|
+
* or a manual setter even when token values coincide; the value comparison
|
|
4092
|
+
* additionally catches a mirror-only writer that cannot bump a revision.
|
|
4093
|
+
*/
|
|
4094
|
+
classifyCanonicalState(intended) {
|
|
4095
|
+
if (!this.persistRecordKey) return "current";
|
|
4096
|
+
this.syncFromPersistedStorage();
|
|
4097
|
+
if (!this.persistRecordKey) return "current";
|
|
4098
|
+
if (!this.token && !this.refreshTokenValue) return "cleared";
|
|
4099
|
+
if (intended.refreshToken !== null) {
|
|
4100
|
+
return this.refreshTokenValue === intended.refreshToken ? "current" : "superseded";
|
|
4101
|
+
}
|
|
4102
|
+
return this.token === intended.token && !this.refreshTokenValue ? "current" : "superseded";
|
|
4103
|
+
}
|
|
4104
|
+
/** The canonical persisted pair, as a refresh result. */
|
|
4105
|
+
canonicalPair(token) {
|
|
4106
|
+
return {
|
|
4107
|
+
token,
|
|
4108
|
+
...this.refreshTokenValue ? { refreshToken: this.refreshTokenValue } : {}
|
|
4109
|
+
};
|
|
4110
|
+
}
|
|
4111
|
+
/** Current credentials plus the revision they were observed at. */
|
|
4112
|
+
currentFence() {
|
|
4113
|
+
return { token: this.token, refreshToken: this.refreshTokenValue };
|
|
4114
|
+
}
|
|
4115
|
+
/**
|
|
4116
|
+
* Clear an access credential a 401 just proved unusable — but only while it
|
|
4117
|
+
* is still the canonical one. A rotation committed by another context while
|
|
4118
|
+
* the request was in flight is newer than this 401, so it must survive; the
|
|
4119
|
+
* instance adopts it instead. The refresh entry is never touched here: a
|
|
4120
|
+
* rejected access token says nothing about the refresh family.
|
|
4121
|
+
*/
|
|
4122
|
+
invalidateAccessToken(spentToken) {
|
|
4123
|
+
this.syncFromPersistedStorage();
|
|
4124
|
+
if (this.token !== spentToken) return;
|
|
4125
|
+
this.setToken(null);
|
|
4126
|
+
}
|
|
4127
|
+
/** Commit both credentials in one canonical write. */
|
|
4128
|
+
setSession(token, refreshToken, options) {
|
|
4129
|
+
this.token = token;
|
|
4130
|
+
this.refreshTokenValue = refreshToken;
|
|
4131
|
+
const committed = this.commitPersistedSession(
|
|
4132
|
+
{ token, refreshToken },
|
|
4133
|
+
options
|
|
4134
|
+
);
|
|
4135
|
+
if (!committed) return false;
|
|
4136
|
+
this.onTokenChange?.(this.token);
|
|
4137
|
+
this.onRefreshTokenChange?.(this.refreshTokenValue);
|
|
4138
|
+
return true;
|
|
4139
|
+
}
|
|
4140
|
+
/**
|
|
4141
|
+
* Serialize rotation against every other context sharing the same storage
|
|
4142
|
+
* key and API/tenant identity. Coordination is best-effort: when it is
|
|
4143
|
+
* unavailable or times out the rotation still happens, so server-side reuse
|
|
4144
|
+
* detection is never bypassed by a client that failed to coordinate.
|
|
4145
|
+
*/
|
|
4146
|
+
async rotateAcrossContexts(intended) {
|
|
4147
|
+
const lockName = this.refreshLockName;
|
|
4148
|
+
const requestLock = lockName ? getRefreshLockRequester() : null;
|
|
4149
|
+
if (!lockName || !requestLock) {
|
|
4150
|
+
return this.rotateWithCanonicalState(intended);
|
|
4151
|
+
}
|
|
4152
|
+
const controller = new AbortController();
|
|
4153
|
+
const timer = setTimeout(() => controller.abort(), REFRESH_LOCK_TIMEOUT);
|
|
4154
|
+
let entered = false;
|
|
4155
|
+
try {
|
|
4156
|
+
return await requestLock(
|
|
4157
|
+
lockName,
|
|
4158
|
+
{ signal: controller.signal },
|
|
4159
|
+
async () => {
|
|
4160
|
+
entered = true;
|
|
4161
|
+
return this.rotateWithCanonicalState(intended);
|
|
4162
|
+
}
|
|
4163
|
+
);
|
|
4164
|
+
} catch (error) {
|
|
4165
|
+
if (entered) throw error;
|
|
4166
|
+
return this.rotateWithCanonicalState(intended);
|
|
4167
|
+
} finally {
|
|
4168
|
+
clearTimeout(timer);
|
|
4169
|
+
}
|
|
4170
|
+
}
|
|
4171
|
+
/**
|
|
4172
|
+
* Decide, against freshly re-read canonical state, whether this context still
|
|
4173
|
+
* owns the rotation.
|
|
4174
|
+
*/
|
|
4175
|
+
async rotateWithCanonicalState(intended) {
|
|
4176
|
+
let fence = intended;
|
|
4177
|
+
for (let attempt = 0; attempt < 2; attempt += 1) {
|
|
4178
|
+
const state = this.classifyCanonicalState(fence);
|
|
4179
|
+
if (state === "cleared") throw new ApiError("Not authenticated", 401);
|
|
4180
|
+
if (state === "current") return this._doRefreshToken(fence);
|
|
4181
|
+
if (this.token) return this.canonicalPair(this.token);
|
|
4182
|
+
fence = this.currentFence();
|
|
4183
|
+
}
|
|
4184
|
+
throw new ApiError("Not authenticated", 401);
|
|
4185
|
+
}
|
|
4186
|
+
async _doRefreshToken(intended) {
|
|
4187
|
+
const init = intended.refreshToken ? {
|
|
3851
4188
|
method: "POST",
|
|
3852
|
-
body: JSON.stringify({ refreshToken:
|
|
4189
|
+
body: JSON.stringify({ refreshToken: intended.refreshToken })
|
|
3853
4190
|
} : {
|
|
3854
4191
|
method: "POST",
|
|
3855
|
-
headers: { Authorization: `Bearer ${
|
|
4192
|
+
headers: { Authorization: `Bearer ${intended.token}` }
|
|
3856
4193
|
};
|
|
3857
4194
|
const result = await this.requestJson(
|
|
3858
4195
|
"/api/customers/refresh",
|
|
3859
4196
|
init
|
|
3860
4197
|
);
|
|
3861
|
-
this.
|
|
3862
|
-
if (
|
|
3863
|
-
|
|
4198
|
+
const state = this.classifyCanonicalState(intended);
|
|
4199
|
+
if (state === "cleared") throw new ApiError("Not authenticated", 401);
|
|
4200
|
+
if (state === "superseded") {
|
|
4201
|
+
if (this.token) return this.canonicalPair(this.token);
|
|
4202
|
+
throw new ApiError("Not authenticated", 401);
|
|
4203
|
+
}
|
|
4204
|
+
const replacedAccessToken = this.token !== null && this.token !== intended.token ? this.token : null;
|
|
4205
|
+
const nextToken = replacedAccessToken ?? result.token;
|
|
4206
|
+
const nextRefresh = result.refreshToken ?? null;
|
|
4207
|
+
if (!this.setSession(nextToken, nextRefresh, { requireLiveSession: true })) {
|
|
4208
|
+
throw new ApiError("Not authenticated", 401);
|
|
4209
|
+
}
|
|
4210
|
+
return {
|
|
4211
|
+
token: nextToken,
|
|
4212
|
+
...nextRefresh ? { refreshToken: nextRefresh } : {}
|
|
4213
|
+
};
|
|
3864
4214
|
}
|
|
3865
4215
|
/**
|
|
3866
4216
|
* Clear the stored token
|
|
3867
4217
|
*/
|
|
3868
4218
|
logout() {
|
|
3869
|
-
this.
|
|
3870
|
-
this.setRefreshToken(null);
|
|
4219
|
+
this.setSession(null, null);
|
|
3871
4220
|
}
|
|
3872
4221
|
/**
|
|
3873
4222
|
* Get the current authenticated customer's profile
|
|
3874
4223
|
*/
|
|
3875
4224
|
async me() {
|
|
3876
|
-
|
|
4225
|
+
const token = this.getToken();
|
|
4226
|
+
if (!token) return null;
|
|
3877
4227
|
try {
|
|
3878
4228
|
const data = await this.requestJson(
|
|
3879
4229
|
"/api/customers/me",
|
|
3880
4230
|
{
|
|
3881
4231
|
method: "GET",
|
|
3882
|
-
headers: { Authorization: `Bearer ${
|
|
4232
|
+
headers: { Authorization: `Bearer ${token}` }
|
|
3883
4233
|
}
|
|
3884
4234
|
);
|
|
3885
4235
|
return data.customer ?? null;
|
|
3886
4236
|
} catch (error) {
|
|
3887
4237
|
if (error instanceof SDKError && error.status === 401) {
|
|
3888
|
-
this.
|
|
4238
|
+
this.invalidateAccessToken(token);
|
|
3889
4239
|
return null;
|
|
3890
4240
|
}
|
|
3891
4241
|
throw error;
|
|
@@ -3913,12 +4263,13 @@ var CustomerAuth = class {
|
|
|
3913
4263
|
* Update the authenticated customer's profile (name, phone, marketingConsent)
|
|
3914
4264
|
*/
|
|
3915
4265
|
async updateProfile(data) {
|
|
3916
|
-
|
|
4266
|
+
const token = this.getToken();
|
|
4267
|
+
if (!token) throw new ApiError("Not authenticated", 401);
|
|
3917
4268
|
const result = await this.requestJson(
|
|
3918
4269
|
"/api/customers/me",
|
|
3919
4270
|
{
|
|
3920
4271
|
method: "PATCH",
|
|
3921
|
-
headers: { Authorization: `Bearer ${
|
|
4272
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
3922
4273
|
body: JSON.stringify(data)
|
|
3923
4274
|
}
|
|
3924
4275
|
);
|
|
@@ -3928,10 +4279,11 @@ var CustomerAuth = class {
|
|
|
3928
4279
|
* Change the password of the currently authenticated customer
|
|
3929
4280
|
*/
|
|
3930
4281
|
async changePassword(currentPassword, newPassword) {
|
|
3931
|
-
|
|
4282
|
+
const token = this.getToken();
|
|
4283
|
+
if (!token) throw new ApiError("Not authenticated", 401);
|
|
3932
4284
|
await this.requestJson("/api/customers/change-password", {
|
|
3933
4285
|
method: "POST",
|
|
3934
|
-
headers: { Authorization: `Bearer ${
|
|
4286
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
3935
4287
|
body: JSON.stringify({ currentPassword, newPassword })
|
|
3936
4288
|
});
|
|
3937
4289
|
}
|
|
@@ -3939,7 +4291,8 @@ var CustomerAuth = class {
|
|
|
3939
4291
|
* Get the authenticated customer's orders with pagination and optional status filter
|
|
3940
4292
|
*/
|
|
3941
4293
|
async getMyOrders(options) {
|
|
3942
|
-
|
|
4294
|
+
const token = this.getToken();
|
|
4295
|
+
if (!token) throw new ApiError("Not authenticated", 401);
|
|
3943
4296
|
const params = new URLSearchParams();
|
|
3944
4297
|
if (options?.page) params.set("page", String(options.page));
|
|
3945
4298
|
if (options?.limit) params.set("limit", String(options.limit));
|
|
@@ -3961,21 +4314,27 @@ var CustomerAuth = class {
|
|
|
3961
4314
|
`/api/customers/me/orders${qs ? `?${qs}` : ""}`,
|
|
3962
4315
|
{
|
|
3963
4316
|
method: "GET",
|
|
3964
|
-
headers: { Authorization: `Bearer ${
|
|
4317
|
+
headers: { Authorization: `Bearer ${token}` }
|
|
3965
4318
|
}
|
|
3966
4319
|
);
|
|
3967
4320
|
return validatePublicOrderList(result);
|
|
3968
4321
|
}
|
|
3969
4322
|
/**
|
|
3970
|
-
* Get the current token (or null if not authenticated)
|
|
4323
|
+
* Get the current token (or null if not authenticated).
|
|
4324
|
+
*
|
|
4325
|
+
* In persisted mode this re-reads shared storage first, so a rotation or
|
|
4326
|
+
* logout performed by another tab is observed before the token is used.
|
|
3971
4327
|
*/
|
|
3972
4328
|
getToken() {
|
|
4329
|
+
this.syncFromPersistedStorage();
|
|
3973
4330
|
return this.token;
|
|
3974
4331
|
}
|
|
3975
4332
|
/**
|
|
3976
|
-
* Get the current refresh token (or null if unavailable)
|
|
4333
|
+
* Get the current refresh token (or null if unavailable). Persisted mode
|
|
4334
|
+
* re-reads shared storage first, like {@link CustomerAuth.getToken}.
|
|
3977
4335
|
*/
|
|
3978
4336
|
getRefreshToken() {
|
|
4337
|
+
this.syncFromPersistedStorage();
|
|
3979
4338
|
return this.refreshTokenValue;
|
|
3980
4339
|
}
|
|
3981
4340
|
/**
|
|
@@ -3983,6 +4342,7 @@ var CustomerAuth = class {
|
|
|
3983
4342
|
*/
|
|
3984
4343
|
setToken(token) {
|
|
3985
4344
|
this.token = token;
|
|
4345
|
+
this.commitPersistedSession({ token });
|
|
3986
4346
|
this.onTokenChange?.(token);
|
|
3987
4347
|
}
|
|
3988
4348
|
/**
|
|
@@ -3990,13 +4350,14 @@ var CustomerAuth = class {
|
|
|
3990
4350
|
*/
|
|
3991
4351
|
setRefreshToken(refreshToken) {
|
|
3992
4352
|
this.refreshTokenValue = refreshToken;
|
|
4353
|
+
this.commitPersistedSession({ refreshToken });
|
|
3993
4354
|
this.onRefreshTokenChange?.(refreshToken);
|
|
3994
4355
|
}
|
|
3995
4356
|
/**
|
|
3996
4357
|
* Check if the customer is currently authenticated
|
|
3997
4358
|
*/
|
|
3998
4359
|
isAuthenticated() {
|
|
3999
|
-
return this.
|
|
4360
|
+
return this.getToken() !== null;
|
|
4000
4361
|
}
|
|
4001
4362
|
/**
|
|
4002
4363
|
* Internal: make a request with timeout and error handling.
|
|
@@ -4342,8 +4703,12 @@ var PaymentSessionApi = class extends CustomerScopedApi {
|
|
|
4342
4703
|
...Object.keys(headers).length > 0 && { headers }
|
|
4343
4704
|
});
|
|
4344
4705
|
}
|
|
4345
|
-
async paymentSession(result, expectedProvider) {
|
|
4346
|
-
return validatePublicPaymentSession(
|
|
4706
|
+
async paymentSession(result, expectedProvider, operation2) {
|
|
4707
|
+
return validatePublicPaymentSession(
|
|
4708
|
+
await result,
|
|
4709
|
+
expectedProvider,
|
|
4710
|
+
operation2
|
|
4711
|
+
);
|
|
4347
4712
|
}
|
|
4348
4713
|
create(params) {
|
|
4349
4714
|
const { idempotencyKey, ...body } = params;
|
|
@@ -4390,7 +4755,8 @@ var PaymentSessionApi = class extends CustomerScopedApi {
|
|
|
4390
4755
|
const endpoint = this.serverTransport ? COMMERCE_ENDPOINTS.paymentSessions.server.cancel : COMMERCE_ENDPOINTS.paymentSessions.cancel;
|
|
4391
4756
|
return this.paymentSession(
|
|
4392
4757
|
this.post(endpoint, body, idempotencyKey),
|
|
4393
|
-
"toss"
|
|
4758
|
+
"toss",
|
|
4759
|
+
"cancel"
|
|
4394
4760
|
);
|
|
4395
4761
|
}
|
|
4396
4762
|
cancelPortOne(params) {
|
|
@@ -4399,7 +4765,8 @@ var PaymentSessionApi = class extends CustomerScopedApi {
|
|
|
4399
4765
|
const endpoint = this.serverTransport ? COMMERCE_ENDPOINTS.paymentSessions.server.cancel : COMMERCE_ENDPOINTS.paymentSessions.cancel;
|
|
4400
4766
|
return this.paymentSession(
|
|
4401
4767
|
this.post(endpoint, body, idempotencyKey),
|
|
4402
|
-
"portone"
|
|
4768
|
+
"portone",
|
|
4769
|
+
"cancel"
|
|
4403
4770
|
);
|
|
4404
4771
|
}
|
|
4405
4772
|
confirm(params) {
|