Skip to content

feat: say a session ended, and name the identity provider as a pair - #191

Merged
sea-snake merged 13 commits into
mainfrom
docs/upgrading-v9
Sep 15, 2026
Merged

sea-snake merged 13 commits into
mainfrom
docs/upgrading-v9

Conversation

@sea-snake

@sea-snake sea-snake commented Sep 15, 2026 •

Copy link
Copy Markdown
Contributor

Two things, since the second came out of reviewing the first.

identityProvider is a pair

The option was { authorizeUrl?, canisterId? } — object optional, and both fields optional inside it. That let half a deployment be named: a custom authorizeUrl with no canisterId renders the ceremony at your provider and mints against mainnet, which is exactly the misconfiguration the TypeError for bare URLs was added to catch. The two values configure one deployment, so they are now required together:

  identityProvider?: {
-   authorizeUrl?: string | URL;
-   canisterId?: Principal | string;
+   authorizeUrl: string | URL;
+   canisterId: Principal | string;
  };

Omitting the option still means mainnet Internet Identity, so an app on mainnet configures nothing. A half-named object is a type error, and for a caller without types it throws a TypeError that says so rather than the Cannot convert undefined or null to object it produced before.

9.0.0 is tagged but not published (latest on npm is 8.0.3), so this tightens an API nobody can be depending on yet.

docs/quick-start.md needed the same fix: it still passed identityProvider as a string, which neither typechecks nor survives the constructor, and it declared the canister id only for AttributesIdentity. It now names both halves and varies only the URL by network.

Upgrading guide

Adds docs/src/content/docs/upgrading/v9.md beside the v4 and v8 guides. Prose rather than a checklist, ordered so that what stops a v8 build compiling comes first:

  • a client per page, disposed when it goes — the sign-in lives in storage, so AuthClient is no longer a singleton to thread through an app
  • getStatus() and subscribe(), what expired and signed-in-elsewhere are for, and that other tabs arrive the same way
  • storage split in two and renamed: storage → credentialStorage, the new stateStorage and namespace, and why keyType went with them
  • IdleManager removed, with the session's own maxTimeToIdle / maxTimeToLive in its place — including that v8's quiet 8-hour cap is now the provider's 30 days
  • identityProvider as a pair, plus the identity and targets options that are gone
  • what the client does unprompted: re-minting, the foreground and activity refresh, disableBrowserActivity, sign-out ending the session everywhere, and the four errors worth catching
  • shorter notes on shared sessions, SSO domains, getPrincipal() and agentOptions

Every API name, default and removal in it was checked against the code rather than recalled.

Verification

typecheck, test (356 passing, one new), codestyle:check and build + publint all pass. The new test was falsified twice: it first passed with the guard removed, because Principal.from(undefined) throws a TypeError of its own, so it now asserts the message; with that, removing the guard fails it. Reverting the two fields to optional makes tsc report both @ts-expect-error directives as unused, so the type-level half of the check is load-bearing too.

The two guides the upgrading page points at are named rather than linked, since no page under docs/ links to another and the published site serves them under a versioned base path.

🤖 Generated with Claude Code

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@sea-snake
sea-snake requested a review from a team as a code owner September 15, 2026 15:29
Copilot AI lite review requested due to automatic review settings September 15, 2026 15:29

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Correct expiry subscription behavior, sign-out guarantees, and SupersededError disposal coverage.

Get a fresh assessment by requesting another Copilot review.

Pull request overview

Adds a v9 upgrade guide for @icp-sdk/auth, covering storage-backed sessions, API changes, lifecycle behavior, and migration details.

Changes:

  • Documents client lifecycle, status APIs, and session storage changes.
  • Covers session limits, provider configuration, refresh behavior, and errors.
  • Summarizes shared sessions, SSO, principals, and agent options.
File summaries
File Summary
docs/src/content/docs/upgrading/v9.md New v9 migration and API behavior guide.
Review details

Suppressed comments (4)

docs/src/content/docs/upgrading/v9.md:65

  • maxTimeToIdle is enforced by the provider, and a refresh that discovers that idle expiry invokes the client's SessionGoneError path, which removes the stored state. Subscribers therefore observe signed-out; expired is derived from the stored expiration (maxTimeToLive) and does not distinguish an idle timeout. As written, both the prose and replacement callback recommend a state that will not be produced.
An idle-out then arrives like any other change, as an `expired` status. Whatever `onIdle` did for you belongs there:

docs/src/content/docs/upgrading/v9.md:91

  • This is not unconditional: signOut() only revokes the provider session when this origin has a stored session credential, and it propagates a revoke failure after clearing local state. In particular, an origin currently in signed-in-elsewhere has nothing to revoke, so claiming that the session cannot be resumed can be false. Please qualify this as a successful revoke and mention the failure case.
Signing out ends the session at the canister. Every tab and every sibling subdomain that shared it is signed out, and it cannot be resumed — a new sign-in is a new session.

docs/src/content/docs/upgrading/v9.md:93

  • SupersededError is also thrown when dispose() is called while signIn() is in flight (AuthClient.#assertCurrent), not only when a later sign-in or sign-out takes over. Because this guide recommends disposing clients on unmount, omitting that cause can leave a normal lifecycle rejection uncaught; please include disposal in the description.
Four errors are worth naming in that flow: `SessionGoneError` when the session has ended or been revoked, `InteractionRequiredError` when a silent request needs a real ceremony after all, `SessionNotHeldError` when a sign-in exists for the domain but this origin holds no credential for it, and `SupersededError` when a later sign-in or sign-out took over from the one you were waiting on.

docs/src/content/docs/upgrading/v9.md:91

  • This overstates the revocation guarantee. signOut() removes the shared state and revokes the provider session, but app delegations already issued to other tabs or sibling subdomains can remain usable until their short expiry; subsequent refreshes and mints are rejected. The existing shared-sessions guide documents this window, so distinguish status convergence from immediate invalidation here.
Signing out ends the session at the canister. Every tab and every sibling subdomain that shared it is signed out, and it cannot be resumed — a new sign-in is a new session.
  • Files reviewed: 1/1 changed files
  • Comments generated: 1
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread docs/src/content/docs/upgrading/v9.md
sea-snake and others added 2 commits September 15, 2026 17:37
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The pair configures one deployment, so half of it rendered the ceremony at one
provider and minted against another. Both fields are required where the option
is given, and omitting it still means mainnet.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@sea-snake sea-snake changed the title docs: upgrading guide for v9 fix: name the identity provider's URL and canister together, and document v9 Sep 15, 2026
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@sea-snake sea-snake changed the title fix: name the identity provider's URL and canister together, and document v9 fix: name the identity provider's URL and canister together Sep 15, 2026
A session the identity provider refuses is published as expired, naming the
account it ended for, so every tab and sibling can say so. Signing out removes
the record, which is what reads as signed out. The cookie's lifetime no longer
tracks the session's, or the browser would drop the record at the moment it
became worth reading.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@sea-snake sea-snake changed the title fix: name the identity provider's URL and canister together feat: say a session ended, and name the identity provider as a pair Sep 15, 2026
sea-snake and others added 8 commits September 15, 2026 18:28
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@sea-snake
sea-snake merged commit bd5e5ef into main Sep 15, 2026
10 checks passed
@sea-snake
sea-snake deleted the docs/upgrading-v9 branch September 15, 2026 17:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants