@witnium-tech/witniumchain 0.9.0 → 0.10.0
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/FRONTEND-INTEGRATION.md +30 -0
- package/dist/index.d.mts +37 -0
- package/dist/index.d.ts +37 -0
- package/dist/index.js +99 -14
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +99 -14
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -109,6 +109,16 @@ var WitniumchainClient = class {
|
|
|
109
109
|
// tokens rotate on use (D10), so a second concurrent refresh would race the
|
|
110
110
|
// first and one of them would 401.
|
|
111
111
|
refreshInFlight;
|
|
112
|
+
// True when the current `accessToken` was minted by the session→token
|
|
113
|
+
// exchange (`exchangeSessionForToken`) rather than the OAuth code/refresh
|
|
114
|
+
// flow. Drives the 401-retry: a session-exchanged token has no refresh
|
|
115
|
+
// credential, so on expiry the SDK re-mints from the cookie instead of
|
|
116
|
+
// refreshing. Reset to false whenever an OAuth /token response lands.
|
|
117
|
+
accessTokenFromSession = false;
|
|
118
|
+
// Single-flight gate for the session→token exchange. A cookie-only client's
|
|
119
|
+
// first BearerJWT call (and any concurrent fan-out) awaits one in-flight
|
|
120
|
+
// mint rather than minting N tokens.
|
|
121
|
+
sessionExchangeInFlight;
|
|
112
122
|
/** Subscriptions / billing helpers. See {@link Subscriptions}. */
|
|
113
123
|
subscriptions;
|
|
114
124
|
/** Delegated-key namespace including the one-call {@link DelegatedKeys.provision} flow. */
|
|
@@ -785,6 +795,84 @@ var WitniumchainClient = class {
|
|
|
785
795
|
})();
|
|
786
796
|
return this.refreshInFlight;
|
|
787
797
|
}
|
|
798
|
+
/**
|
|
799
|
+
* Exchange the logged-in session (the `wac_session` cookie) for a short-lived
|
|
800
|
+
* bearer access token and cache it as the client's `accessToken`.
|
|
801
|
+
*
|
|
802
|
+
* This is the SDK side of the session→token model (accounts'
|
|
803
|
+
* `POST /v1/auth/access-token`): the dashboard authenticates with the
|
|
804
|
+
* cookie, but the operational API (delegated keys, witness writes, chain
|
|
805
|
+
* reads) is bearer-only — no endpoint accepts the ambient cookie, so there's
|
|
806
|
+
* no CSRF surface even with `SameSite=None`. The minted token is
|
|
807
|
+
* byte-compatible with an OAuth user token (same signing key, `iss`, `aud`,
|
|
808
|
+
* scope + org/role/contracts claims), so every `BearerJWT` route accepts it.
|
|
809
|
+
*
|
|
810
|
+
* Callers rarely invoke this directly — {@link applyAuth} mints transparently
|
|
811
|
+
* on the first `BearerJWT` call when no token is set and re-mints on a 401.
|
|
812
|
+
* It's exposed so a dashboard can pre-warm the token right after login.
|
|
813
|
+
*
|
|
814
|
+
* Requires a session: either `sessionCookie` on the constructor (Node) or an
|
|
815
|
+
* ambient `wac_session` cookie carried by the browser (`credentials:
|
|
816
|
+
* 'include'`). Throws {@link WitniumchainApiError} (401) when there's no
|
|
817
|
+
* valid session.
|
|
818
|
+
*/
|
|
819
|
+
async exchangeSessionForToken() {
|
|
820
|
+
if (this.sessionExchangeInFlight) return this.sessionExchangeInFlight;
|
|
821
|
+
this.sessionExchangeInFlight = (async () => {
|
|
822
|
+
try {
|
|
823
|
+
const res = await this.req("POST", "/v1/auth/access-token", { auth: "SessionCookie" });
|
|
824
|
+
this.accessToken = res.accessToken;
|
|
825
|
+
this.accessTokenFromSession = true;
|
|
826
|
+
this.refreshToken = void 0;
|
|
827
|
+
this.hasRefreshCookie = false;
|
|
828
|
+
return {
|
|
829
|
+
accessToken: res.accessToken,
|
|
830
|
+
expiresAt: Math.floor(new Date(res.expiresAt).getTime() / 1e3)
|
|
831
|
+
};
|
|
832
|
+
} finally {
|
|
833
|
+
this.sessionExchangeInFlight = void 0;
|
|
834
|
+
}
|
|
835
|
+
})();
|
|
836
|
+
return this.sessionExchangeInFlight;
|
|
837
|
+
}
|
|
838
|
+
/**
|
|
839
|
+
* Best-effort transparent mint for the `BearerJWT` path: if no access token
|
|
840
|
+
* is cached, try the session exchange (single-flight). Swallows failure so
|
|
841
|
+
* the caller in {@link applyAuth} surfaces the actionable "no token / no
|
|
842
|
+
* session" guidance rather than a raw 401 from the exchange endpoint.
|
|
843
|
+
*/
|
|
844
|
+
async ensureSessionAccessToken() {
|
|
845
|
+
if (this.accessToken) return;
|
|
846
|
+
try {
|
|
847
|
+
await this.exchangeSessionForToken();
|
|
848
|
+
} catch {
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
/**
|
|
852
|
+
* Obtain a fresh access token for a 401-retry, picking the right mechanism:
|
|
853
|
+
* an OAuth refresh when a refresh credential exists, else a session re-mint
|
|
854
|
+
* for a session-exchanged token. Returns true when a fresh token is in hand.
|
|
855
|
+
*/
|
|
856
|
+
async reauthenticateForRetry() {
|
|
857
|
+
if (this.refreshToken || this.hasRefreshCookie) {
|
|
858
|
+
try {
|
|
859
|
+
await this.refreshAccessToken();
|
|
860
|
+
return true;
|
|
861
|
+
} catch {
|
|
862
|
+
return false;
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
if (this.accessTokenFromSession) {
|
|
866
|
+
this.accessToken = void 0;
|
|
867
|
+
try {
|
|
868
|
+
await this.exchangeSessionForToken();
|
|
869
|
+
return true;
|
|
870
|
+
} catch {
|
|
871
|
+
return false;
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
return false;
|
|
875
|
+
}
|
|
788
876
|
/**
|
|
789
877
|
* End the OAuth session.
|
|
790
878
|
*
|
|
@@ -799,6 +887,7 @@ var WitniumchainClient = class {
|
|
|
799
887
|
*/
|
|
800
888
|
signOut() {
|
|
801
889
|
this.accessToken = void 0;
|
|
890
|
+
this.accessTokenFromSession = false;
|
|
802
891
|
this.refreshToken = void 0;
|
|
803
892
|
this.hasRefreshCookie = false;
|
|
804
893
|
this.pendingLogins.clear();
|
|
@@ -896,6 +985,7 @@ var WitniumchainClient = class {
|
|
|
896
985
|
});
|
|
897
986
|
}
|
|
898
987
|
this.accessToken = tokens.access_token;
|
|
988
|
+
this.accessTokenFromSession = false;
|
|
899
989
|
if (tokens.refresh_token) {
|
|
900
990
|
this.refreshToken = tokens.refresh_token;
|
|
901
991
|
} else {
|
|
@@ -1026,13 +1116,9 @@ var WitniumchainClient = class {
|
|
|
1026
1116
|
if (opts._isRetry) return false;
|
|
1027
1117
|
if (opts.auth !== "BearerJWT") return false;
|
|
1028
1118
|
if (res.status !== 401) return false;
|
|
1029
|
-
if (!this.refreshToken && !this.hasRefreshCookie
|
|
1030
|
-
try {
|
|
1031
|
-
await this.refreshAccessToken();
|
|
1032
|
-
return true;
|
|
1033
|
-
} catch {
|
|
1119
|
+
if (!this.refreshToken && !this.hasRefreshCookie && !this.accessTokenFromSession)
|
|
1034
1120
|
return false;
|
|
1035
|
-
|
|
1121
|
+
return this.reauthenticateForRetry();
|
|
1036
1122
|
}
|
|
1037
1123
|
/**
|
|
1038
1124
|
* Same decision as `shouldRefreshAndRetry` but for the JSON-parsed path,
|
|
@@ -1046,18 +1132,14 @@ var WitniumchainClient = class {
|
|
|
1046
1132
|
if (opts._isRetry) return false;
|
|
1047
1133
|
if (opts.auth !== "BearerJWT") return false;
|
|
1048
1134
|
if (status !== 401) return false;
|
|
1049
|
-
if (!this.refreshToken && !this.hasRefreshCookie
|
|
1135
|
+
if (!this.refreshToken && !this.hasRefreshCookie && !this.accessTokenFromSession)
|
|
1136
|
+
return false;
|
|
1050
1137
|
const body = parsed;
|
|
1051
1138
|
const label = typeof body?.error === "string" ? body.error : void 0;
|
|
1052
1139
|
if (label !== void 0 && label !== "token_expired" && label !== "invalid_token") {
|
|
1053
1140
|
return false;
|
|
1054
1141
|
}
|
|
1055
|
-
|
|
1056
|
-
await this.refreshAccessToken();
|
|
1057
|
-
return true;
|
|
1058
|
-
} catch {
|
|
1059
|
-
return false;
|
|
1060
|
-
}
|
|
1142
|
+
return this.reauthenticateForRetry();
|
|
1061
1143
|
}
|
|
1062
1144
|
/**
|
|
1063
1145
|
* Resolve which base URL to call. Default is accounts (`baseUrl`). When a
|
|
@@ -1097,9 +1179,12 @@ var WitniumchainClient = class {
|
|
|
1097
1179
|
return;
|
|
1098
1180
|
}
|
|
1099
1181
|
case "BearerJWT": {
|
|
1182
|
+
if (!this.accessToken) {
|
|
1183
|
+
await this.ensureSessionAccessToken();
|
|
1184
|
+
}
|
|
1100
1185
|
if (!this.accessToken) {
|
|
1101
1186
|
throw new Error(
|
|
1102
|
-
`WitniumchainClient: ${method} ${path} requires an OAuth access token. Pass \`accessToken\` to the constructor,
|
|
1187
|
+
`WitniumchainClient: ${method} ${path} requires an OAuth access token. Pass \`accessToken\` to the constructor, call \`beginOAuthLogin\`/\`completeOAuthLogin\`, or \u2014 for a logged-in dashboard \u2014 provide \`sessionCookie\` (Node) or rely on the browser \`wac_session\` cookie so the SDK can mint one.`
|
|
1103
1188
|
);
|
|
1104
1189
|
}
|
|
1105
1190
|
headers["authorization"] = `Bearer ${this.accessToken}`;
|