@superbuilders/primer-tives 4.0.4 → 4.0.5
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/README.md +30 -2
- package/dist/client/auth/provider.d.ts +6 -1
- package/dist/client/auth/provider.d.ts.map +1 -1
- package/dist/client/auth/storage.d.ts +8 -0
- package/dist/client/auth/storage.d.ts.map +1 -0
- package/dist/client/index.js +80 -16
- package/dist/client/index.js.map +8 -7
- package/dist/client/session.d.ts +1 -1
- package/dist/client/session.d.ts.map +1 -1
- package/dist/client/start.d.ts.map +1 -1
- package/dist/version.d.ts +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -19,7 +19,7 @@ bun add @superbuilders/primer-tives
|
|
|
19
19
|
|
|
20
20
|
## Version
|
|
21
21
|
|
|
22
|
-
The current SDK version is `4.0.
|
|
22
|
+
The current SDK version is `4.0.5`.
|
|
23
23
|
|
|
24
24
|
## Entrypoints
|
|
25
25
|
|
|
@@ -210,12 +210,40 @@ The SDK uses Primer's production runtime by default.
|
|
|
210
210
|
| Shape | Semantics |
|
|
211
211
|
| --- | --- |
|
|
212
212
|
| `accessToken` present | `start` validates the token shape locally and returns `AccessTokenStartState`. This mode cannot return sign-in states. |
|
|
213
|
-
| `accessToken` absent | `start`
|
|
213
|
+
| `accessToken` absent | `start` uses managed hosted-auth mode. It reads and writes the SDK-managed browser session token cache documented below. |
|
|
214
214
|
|
|
215
215
|
The public API exposes hosted auth as state behavior. `SignInRequiredState.login()` and `SignInFailedState.login()` are the sign-in transitions. `AuthUnavailableState` and `AuthConfigInvalidState` expose no login operation.
|
|
216
216
|
|
|
217
217
|
When `subject` is present, it must be a concrete literal subject at the options declaration site. Do not pass a broad `Subject` value into `PrimerOptions`; narrow it in host control flow, then construct options with `satisfies PrimerOptionsWithManagedAuth<"math", ...>`, `satisfies PrimerOptionsWithAccessToken<"math", ...>`, or the corresponding literal subject variant.
|
|
218
218
|
|
|
219
|
+
## Managed Auth Token Persistence
|
|
220
|
+
|
|
221
|
+
Managed hosted auth is designed for browser-only consumers that do not have their own server-side token broker. When `accessToken` is absent, PrimerTives owns a session-scoped browser token cache with one fixed storage location:
|
|
222
|
+
|
|
223
|
+
```txt
|
|
224
|
+
sessionStorage["primer:access-token:<publishableKey>"]
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
This is intentionally zero config. There is no storage selector and no persistence flag. Managed-auth mode always uses `globalThis.sessionStorage`; if `sessionStorage` is unavailable, `start` returns `AuthUnavailableState`.
|
|
228
|
+
|
|
229
|
+
The cache stores only the final Primer access token returned by hosted Timeback sign-in. OAuth transaction state, nonce, verifier, and callback validation state are not stored in browser storage; those remain server-managed by Primer.
|
|
230
|
+
|
|
231
|
+
Managed-auth startup behavior is:
|
|
232
|
+
|
|
233
|
+
| Situation | SDK behavior |
|
|
234
|
+
| --- | --- |
|
|
235
|
+
| Valid cached token exists | `start` uses it and enters the runtime without opening sign-in. |
|
|
236
|
+
| No cached token exists | `start` returns `SignInRequiredState`. |
|
|
237
|
+
| Cached token is malformed or expired | SDK clears the key and returns `SignInRequiredState`. |
|
|
238
|
+
| Hosted sign-in succeeds | SDK validates the returned token, stores it at the documented key, then starts the runtime. |
|
|
239
|
+
| Runtime rejects a managed cached token as expired or invalid | SDK clears the key and returns `SignInFailedState` so the app can ask the learner to sign in again. |
|
|
240
|
+
|
|
241
|
+
Access-token mode bypasses this cache entirely. If `accessToken` is present in `start` options, PrimerTives does not read, write, or clear `sessionStorage`.
|
|
242
|
+
|
|
243
|
+
The cached value is a bearer credential. Browser-only consumers get reload convenience from this default, but any script that can read the page can read the token. Host applications must maintain normal XSS protections and should use access-token mode if they need to own token storage themselves.
|
|
244
|
+
|
|
245
|
+
The canonical managed sign-in endpoint is `/api/auth/timeback/start`. Legacy `/auth/timeback` URLs are not part of the PrimerTives 4.0.5 contract.
|
|
246
|
+
|
|
219
247
|
## Auth Semantics
|
|
220
248
|
|
|
221
249
|
An access token is expected to be JWS-shaped: it starts with `eyJ` and contains exactly two dots. If a provided token does not match that shape, `start` returns `FatalState` with `ErrMalformedAccessToken`.
|
|
@@ -9,6 +9,7 @@ type AccessTokenResolverOptions = {
|
|
|
9
9
|
type ResolvedAccessTokenResult = {
|
|
10
10
|
readonly kind: "resolved";
|
|
11
11
|
readonly accessToken: ResolvedAccessToken;
|
|
12
|
+
readonly clearCachedAccessToken?: () => void;
|
|
12
13
|
};
|
|
13
14
|
type MissingAccessTokenResult = {
|
|
14
15
|
readonly kind: "missing";
|
|
@@ -17,7 +18,11 @@ type FatalAccessTokenResult = {
|
|
|
17
18
|
readonly kind: "fatal";
|
|
18
19
|
readonly error: Error;
|
|
19
20
|
};
|
|
20
|
-
type
|
|
21
|
+
type AuthUnavailableResult = {
|
|
22
|
+
readonly kind: "auth-unavailable";
|
|
23
|
+
readonly error: Error;
|
|
24
|
+
};
|
|
25
|
+
type ExistingAccessTokenResult = ResolvedAccessTokenResult | MissingAccessTokenResult | AuthUnavailableResult | FatalAccessTokenResult;
|
|
21
26
|
type HostedLoginResult = ResolvedAccessTokenResult | {
|
|
22
27
|
readonly kind: "sign-in-failed";
|
|
23
28
|
readonly error: Error;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"provider.d.ts","sourceRoot":"","sources":["../../../src/client/auth/provider.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oCAAoC,CAAA;AAGtE,OAAO,EAEN,KAAK,mBAAmB,EACxB,MAAM,sDAAsD,CAAA;
|
|
1
|
+
{"version":3,"file":"provider.d.ts","sourceRoot":"","sources":["../../../src/client/auth/provider.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oCAAoC,CAAA;AAGtE,OAAO,EAEN,KAAK,mBAAmB,EACxB,MAAM,sDAAsD,CAAA;AAS7D,KAAK,0BAA0B,GAAG;IACjC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAA;IAC/B,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAA;IAC7B,QAAQ,CAAC,MAAM,EAAE,YAAY,CAAA;CAC7B,CAAA;AAED,KAAK,yBAAyB,GAAG;IAChC,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAA;IACzB,QAAQ,CAAC,WAAW,EAAE,mBAAmB,CAAA;IACzC,QAAQ,CAAC,sBAAsB,CAAC,EAAE,MAAM,IAAI,CAAA;CAC5C,CAAA;AAED,KAAK,wBAAwB,GAAG;IAC/B,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAA;CACxB,CAAA;AAED,KAAK,sBAAsB,GAAG;IAC7B,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAA;IACtB,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAA;CACrB,CAAA;AAED,KAAK,qBAAqB,GAAG;IAC5B,QAAQ,CAAC,IAAI,EAAE,kBAAkB,CAAA;IACjC,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAA;CACrB,CAAA;AAED,KAAK,yBAAyB,GAC3B,yBAAyB,GACzB,wBAAwB,GACxB,qBAAqB,GACrB,sBAAsB,CAAA;AAEzB,KAAK,iBAAiB,GACnB,yBAAyB,GACzB;IAAE,QAAQ,CAAC,IAAI,EAAE,gBAAgB,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAA;CAAE,GAC1D;IAAE,QAAQ,CAAC,IAAI,EAAE,kBAAkB,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAA;CAAE,GAC5D;IAAE,QAAQ,CAAC,IAAI,EAAE,qBAAqB,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAA;CAAE,CAAA;AA4DlE,iBAAS,0BAA0B,CAClC,OAAO,EAAE,0BAA0B,GACjC,yBAAyB,CAW3B;AAYD,iBAAe,gBAAgB,CAAC,OAAO,EAAE,0BAA0B,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAyB/F;AAED,OAAO,EAAE,gBAAgB,EAAE,0BAA0B,EAAE,CAAA;AACvD,YAAY,EAAE,0BAA0B,EAAE,yBAAyB,EAAE,iBAAiB,EAAE,CAAA"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { PrimerLogger } from "../../logger";
|
|
2
|
+
declare function managedAuthAccessTokenStorageKey(publishableKey: string): string;
|
|
3
|
+
declare function managedAuthStorage(logger: PrimerLogger): Storage;
|
|
4
|
+
declare function loadManagedAuthAccessToken(storage: Storage, publishableKey: string): string | null;
|
|
5
|
+
declare function storeManagedAuthAccessToken(storage: Storage, publishableKey: string, accessToken: string): void;
|
|
6
|
+
declare function clearManagedAuthAccessToken(storage: Storage, publishableKey: string): void;
|
|
7
|
+
export { clearManagedAuthAccessToken, loadManagedAuthAccessToken, managedAuthAccessTokenStorageKey, managedAuthStorage, storeManagedAuthAccessToken };
|
|
8
|
+
//# sourceMappingURL=storage.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"storage.d.ts","sourceRoot":"","sources":["../../../src/client/auth/storage.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oCAAoC,CAAA;AAItE,iBAAS,gCAAgC,CAAC,cAAc,EAAE,MAAM,GAAG,MAAM,CAExE;AAED,iBAAS,kBAAkB,CAAC,MAAM,EAAE,YAAY,GAAG,OAAO,CAMzD;AAED,iBAAS,0BAA0B,CAAC,OAAO,EAAE,OAAO,EAAE,cAAc,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAM3F;AAED,iBAAS,2BAA2B,CACnC,OAAO,EAAE,OAAO,EAChB,cAAc,EAAE,MAAM,EACtB,WAAW,EAAE,MAAM,GACjB,IAAI,CAEN;AAED,iBAAS,2BAA2B,CAAC,OAAO,EAAE,OAAO,EAAE,cAAc,EAAE,MAAM,GAAG,IAAI,CAEnF;AAED,OAAO,EACN,2BAA2B,EAC3B,0BAA0B,EAC1B,gCAAgC,EAChC,kBAAkB,EAClB,2BAA2B,EAC3B,CAAA"}
|
package/dist/client/index.js
CHANGED
|
@@ -6783,7 +6783,7 @@ function submissionValidationMessage(result) {
|
|
|
6783
6783
|
import * as errors3 from "@superbuilders/errors";
|
|
6784
6784
|
|
|
6785
6785
|
// src/version.ts
|
|
6786
|
-
var SDK_VERSION = "4.0.
|
|
6786
|
+
var SDK_VERSION = "4.0.5";
|
|
6787
6787
|
var NPM_PACKAGE_URL = "https://www.npmjs.com/package/@superbuilders/primer-tives";
|
|
6788
6788
|
|
|
6789
6789
|
// src/client/transport.ts
|
|
@@ -7507,9 +7507,9 @@ function makeSession(sc) {
|
|
|
7507
7507
|
};
|
|
7508
7508
|
const result = await sc.transport(body);
|
|
7509
7509
|
if (!result.ok) {
|
|
7510
|
-
if (errors10.is(result.error, ErrTokenExpired) && sc.reauthenticate !== null) {
|
|
7511
|
-
logger.warn({ phase, intentKind: intent.kind }, "session token
|
|
7512
|
-
return sc.reauthenticate();
|
|
7510
|
+
if ((errors10.is(result.error, ErrTokenExpired) || errors10.is(result.error, ErrInvalidAccessToken)) && sc.reauthenticate !== null) {
|
|
7511
|
+
logger.warn({ phase, intentKind: intent.kind }, "session token rejected");
|
|
7512
|
+
return sc.reauthenticate(result.error);
|
|
7513
7513
|
}
|
|
7514
7514
|
if (isFatalError(result.error)) {
|
|
7515
7515
|
logger.error({
|
|
@@ -7841,6 +7841,32 @@ function resolveAccessToken(token, logger) {
|
|
|
7841
7841
|
return { value: token, [resolvedAccessTokenBrand]: true };
|
|
7842
7842
|
}
|
|
7843
7843
|
|
|
7844
|
+
// src/client/auth/storage.ts
|
|
7845
|
+
var MANAGED_AUTH_ACCESS_TOKEN_KEY_PREFIX = "primer:access-token";
|
|
7846
|
+
function managedAuthAccessTokenStorageKey(publishableKey) {
|
|
7847
|
+
return `${MANAGED_AUTH_ACCESS_TOKEN_KEY_PREFIX}:${publishableKey}`;
|
|
7848
|
+
}
|
|
7849
|
+
function managedAuthStorage(logger) {
|
|
7850
|
+
if (typeof globalThis.sessionStorage === "undefined") {
|
|
7851
|
+
logger.error("managed auth storage unavailable");
|
|
7852
|
+
throw ErrAuthUnavailable;
|
|
7853
|
+
}
|
|
7854
|
+
return globalThis.sessionStorage;
|
|
7855
|
+
}
|
|
7856
|
+
function loadManagedAuthAccessToken(storage, publishableKey) {
|
|
7857
|
+
const token = storage.getItem(managedAuthAccessTokenStorageKey(publishableKey));
|
|
7858
|
+
if (token === null || token.length === 0) {
|
|
7859
|
+
return null;
|
|
7860
|
+
}
|
|
7861
|
+
return token;
|
|
7862
|
+
}
|
|
7863
|
+
function storeManagedAuthAccessToken(storage, publishableKey, accessToken) {
|
|
7864
|
+
storage.setItem(managedAuthAccessTokenStorageKey(publishableKey), accessToken);
|
|
7865
|
+
}
|
|
7866
|
+
function clearManagedAuthAccessToken(storage, publishableKey) {
|
|
7867
|
+
storage.removeItem(managedAuthAccessTokenStorageKey(publishableKey));
|
|
7868
|
+
}
|
|
7869
|
+
|
|
7844
7870
|
// src/client/auth/provider.ts
|
|
7845
7871
|
function resolveProvidedAccessToken(token, logger) {
|
|
7846
7872
|
const result = errors12.trySync(function resolveProvidedToken() {
|
|
@@ -7851,20 +7877,53 @@ function resolveProvidedAccessToken(token, logger) {
|
|
|
7851
7877
|
}
|
|
7852
7878
|
return { kind: "resolved", accessToken: result.data };
|
|
7853
7879
|
}
|
|
7854
|
-
function resolveHostedAccessToken(token,
|
|
7880
|
+
function resolveHostedAccessToken(token, storage, options) {
|
|
7855
7881
|
const result = errors12.trySync(function resolveHostedToken() {
|
|
7856
|
-
return resolveAccessToken(token, logger);
|
|
7882
|
+
return resolveAccessToken(token, options.logger);
|
|
7857
7883
|
});
|
|
7858
7884
|
if (result.error) {
|
|
7859
7885
|
return { kind: "sign-in-failed", error: result.error };
|
|
7860
7886
|
}
|
|
7861
|
-
|
|
7887
|
+
storeManagedAuthAccessToken(storage, options.publishableKey, token);
|
|
7888
|
+
return {
|
|
7889
|
+
kind: "resolved",
|
|
7890
|
+
accessToken: result.data,
|
|
7891
|
+
clearCachedAccessToken: function clearCachedAccessToken() {
|
|
7892
|
+
clearManagedAuthAccessToken(storage, options.publishableKey);
|
|
7893
|
+
}
|
|
7894
|
+
};
|
|
7895
|
+
}
|
|
7896
|
+
function resolveStoredAccessToken(storage, options) {
|
|
7897
|
+
const stored = loadManagedAuthAccessToken(storage, options.publishableKey);
|
|
7898
|
+
if (stored === null) {
|
|
7899
|
+
return { kind: "missing" };
|
|
7900
|
+
}
|
|
7901
|
+
const result = errors12.trySync(function resolveStoredToken() {
|
|
7902
|
+
return resolveAccessToken(stored, options.logger);
|
|
7903
|
+
});
|
|
7904
|
+
if (result.error) {
|
|
7905
|
+
clearManagedAuthAccessToken(storage, options.publishableKey);
|
|
7906
|
+
return { kind: "missing" };
|
|
7907
|
+
}
|
|
7908
|
+
return {
|
|
7909
|
+
kind: "resolved",
|
|
7910
|
+
accessToken: result.data,
|
|
7911
|
+
clearCachedAccessToken: function clearCachedAccessToken() {
|
|
7912
|
+
clearManagedAuthAccessToken(storage, options.publishableKey);
|
|
7913
|
+
}
|
|
7914
|
+
};
|
|
7862
7915
|
}
|
|
7863
7916
|
function resolveExistingAccessToken(options) {
|
|
7864
7917
|
if (options.accessToken !== undefined) {
|
|
7865
7918
|
return resolveProvidedAccessToken(options.accessToken, options.logger);
|
|
7866
7919
|
}
|
|
7867
|
-
|
|
7920
|
+
const storageResult = errors12.trySync(function readManagedAuthStorage() {
|
|
7921
|
+
return managedAuthStorage(options.logger);
|
|
7922
|
+
});
|
|
7923
|
+
if (storageResult.error) {
|
|
7924
|
+
return { kind: "auth-unavailable", error: storageResult.error };
|
|
7925
|
+
}
|
|
7926
|
+
return resolveStoredAccessToken(storageResult.data, options);
|
|
7868
7927
|
}
|
|
7869
7928
|
function authFailureResult(error) {
|
|
7870
7929
|
if (errors12.is(error, ErrAuthConfigInvalid)) {
|
|
@@ -7878,9 +7937,10 @@ function authFailureResult(error) {
|
|
|
7878
7937
|
async function beginHostedLogin(options) {
|
|
7879
7938
|
const logger = options.logger;
|
|
7880
7939
|
const contextResult = errors12.trySync(function readContext() {
|
|
7940
|
+
const storage = managedAuthStorage(logger);
|
|
7881
7941
|
const url = currentUrl(logger);
|
|
7882
7942
|
const clientState = randomClientState(logger);
|
|
7883
|
-
return { url, clientState };
|
|
7943
|
+
return { storage, url, clientState };
|
|
7884
7944
|
});
|
|
7885
7945
|
if (contextResult.error) {
|
|
7886
7946
|
return authFailureResult(contextResult.error);
|
|
@@ -7896,7 +7956,7 @@ async function beginHostedLogin(options) {
|
|
|
7896
7956
|
if (accessTokenResult.error) {
|
|
7897
7957
|
return authFailureResult(accessTokenResult.error);
|
|
7898
7958
|
}
|
|
7899
|
-
return resolveHostedAccessToken(accessTokenResult.data,
|
|
7959
|
+
return resolveHostedAccessToken(accessTokenResult.data, context.storage, options);
|
|
7900
7960
|
}
|
|
7901
7961
|
|
|
7902
7962
|
// src/client/auth-state.ts
|
|
@@ -7965,8 +8025,11 @@ function primerOrigin(origin) {
|
|
|
7965
8025
|
}
|
|
7966
8026
|
async function startRuntime(config, resolved) {
|
|
7967
8027
|
let reauthenticate = null;
|
|
7968
|
-
if (resolved.
|
|
7969
|
-
reauthenticate =
|
|
8028
|
+
if (resolved.clearCachedAccessToken !== undefined) {
|
|
8029
|
+
reauthenticate = function reauthenticateManagedAuth(error) {
|
|
8030
|
+
resolved.clearCachedAccessToken?.();
|
|
8031
|
+
return Promise.resolve(makeSignInFailedState(config, error));
|
|
8032
|
+
};
|
|
7970
8033
|
}
|
|
7971
8034
|
const transport = createTransport({
|
|
7972
8035
|
accessToken: resolved.accessToken,
|
|
@@ -8018,9 +8081,7 @@ async function loginAndStart(config) {
|
|
|
8018
8081
|
}
|
|
8019
8082
|
return startRuntime(config, {
|
|
8020
8083
|
accessToken: result.accessToken,
|
|
8021
|
-
|
|
8022
|
-
return Promise.resolve(makeSignInFailedState(config, ErrTokenExpired));
|
|
8023
|
-
}
|
|
8084
|
+
clearCachedAccessToken: result.clearCachedAccessToken
|
|
8024
8085
|
});
|
|
8025
8086
|
}
|
|
8026
8087
|
async function start(options) {
|
|
@@ -8050,6 +8111,9 @@ async function start(options) {
|
|
|
8050
8111
|
toJSON: poisonToJSON
|
|
8051
8112
|
};
|
|
8052
8113
|
}
|
|
8114
|
+
if (accessToken.kind === "auth-unavailable") {
|
|
8115
|
+
return authUnavailableState(accessToken.error);
|
|
8116
|
+
}
|
|
8053
8117
|
if (accessToken.kind === "missing") {
|
|
8054
8118
|
return makeSignInRequiredState(config);
|
|
8055
8119
|
}
|
|
@@ -8059,4 +8123,4 @@ export {
|
|
|
8059
8123
|
start
|
|
8060
8124
|
};
|
|
8061
8125
|
|
|
8062
|
-
//# debugId=
|
|
8126
|
+
//# debugId=D2887B68B961EF6364756E2164756E21
|