Reads the stored token set as-is, with no expiry check and no refresh.
Returns undefined if nothing is stored, if what's stored isn't valid
JSON, or if it parses but isn't a usable TokenSet (see isTokenSet)
— a foreign value under this key, storage corruption, or a truncated
write. All of those are treated as "signed out" rather than thrown,
since the caller's own restore() is expected to handle "no session"
silently per the plugin contract's SourceAuth.restore doc (no popups,
no navigation).
A malformed entry is not separately reported anywhere: this package
has no logger, warning callback or telemetry channel — every signal it
emits is a return value or a thrown OAuthError — and inventing one
for this single case would be a new public API surface. The condition
is still visible to the caller, just not distinguishable from "never
signed in": getValidAccessToken() throws NotSignedInError either
way, which routes to the same remedy (interactive sign-in). If a
provider later needs to tell the two apart, that wants a deliberate
diagnostics hook on TokenManagerConfig, not an ad-hoc console.warn
from a library.
Returns a currently-valid access token, refreshing first if the stored
one is expired or within refreshSkewMs of expiring.
Concurrent calls collapse onto one refresh request: the first caller to
observe an expired token starts the refresh and records the in-flight
promise on the current session's pendingRefresh; every other caller
that reaches the refresh step before it settles, and is still on that
same session, returns that same promise instead of issuing its own
request. The refresh's async work only actually begins once this
synchronous bookkeeping (checking/setting pendingRefresh) has run, so
two calls issued back-to-back from the same tick — e.g. via
Promise.all — cannot both pass the check before either sets the field.
Throws NotSignedInError if there is no stored session — including a
stored entry that isn't a usable TokenSet, which counts as "no
session" rather than being served as a token (see getTokens) — or if
the stored session is expired with no refresh token to recover with,
or if clear()/setTokens() replaced the session at any point after
this call started (see the session capture below).
Persists a freshly obtained token set (from exchangeAuthorizationCode,
typically). Overwrites whatever was stored under this key.
Replaces this.session, exactly as clear() does and for the same
reason: this call may be a brand-new interactive sign-in landing while
a refresh of the previous session is still in flight (no clear()
in between — e.g. re-authenticating as a different account). Without
the swap, that stale refresh's queued write still sees
session === this.session once its turn comes up, passes the check,
and overwrites the tokens this call is about to persist with the
stale refreshed ones. The swap is synchronous, before the write is
even enqueued, so the disarm is in place no matter how the two writes
end up ordered in the queue.
What makes the swap sufficient is that the operation being disarmed
captured its session before its first await, in
getValidAccessToken. It did not always: while the session was read
inside refresh() — i.e. after the caller's storage read — a
setTokens() landing during that read was captured as the stale
refresh's own session, so every check below compared the new session
against itself and the stale write went through anyway.
Forgets the stored session (sign-out). Does not call the provider's token-revocation endpoint, if it has one — that's provider-specific and out of this package's scope; callers should revoke first if they want server-side revocation, then call this.