Skip to content

Adapter framework strategy: extract framework-agnostic client core (deferred) #64

Closed

Description

@Bccorb

Summary

Extract a framework-agnostic client core so new SDKs (Vue, Angular, and similar) are thin bindings over one shared implementation instead of parallel reimplementations.

This issue has been re-scoped after review. The actionable near-term pieces were split into #76, #77, and #78. What remains here is the deferred extraction itself.

Key correction: the client is not the hard part

The obvious assumption is that the shared core is the HTTP client. It is not. These modules already have zero React imports and are effectively extraction-ready today:

  • src/client/createSeamlessAuthClient.ts
  • src/client/webauthnPrf.ts
  • src/client/webauthnSupport.ts
  • src/fetchWithAuth.ts
  • src/scopedRoles.ts
  • src/types.ts

The part that will actually be duplicated is the session state machine in src/AuthProvider.tsx: loading state, user, credentials, organizations, active organization, step-up status, validateToken, logout-on-401, markSignedIn, and refresh-after-mutation. That logic is React-only today. If a Vue composable and an Angular service each reimplement it, they will drift, and they will drift on auth state, which is the worst place for divergence.

So the core should expose a framework-agnostic session store (getState, subscribe, and actions), not just a fetch client. Each adapter then becomes a thin binding:

  • React: useSyncExternalStore
  • Vue: a composable or Pinia store
  • Angular: an injectable service exposing an observable

That reframes this work from moving files to extracting a state machine, which is the part that needs real design.

Naming constraints

  • @seamless-auth/core is already taken by the server monorepo (seamless-auth-server/packages/core), so it is not available for a client core.
  • @seamless-auth/types already exists as a separate package that this repo does not currently consume. Decide how it relates before introducing a third naming lane.
  • Suggested name: @seamless-auth/client.

Prerequisites

Timing

Do this as step one of the first non-React adapter (Vue or Angular), not as a speculative refactor. Extracting earlier costs a repo restructure, dual publishing, changesets reconfiguration, and a consumer migration, in exchange for benefits that cannot be collected until a second SDK exists.

Next.js (#78) explicitly does not depend on this work, because Next.js is React and needs SSR safety rather than a new binding.

Cross-repo note

The conformance harness in seamless-cli takes per-repo refs (react-ref, templates-ref, and similar). Each new adapter will need a ref and a lane there, so the harness needs generalizing as adapters are added. That work lives in seamless-cli, not this repo.

Related

Activity

  1. changed the title [-]Adapter framework strategy: extract framework-agnostic client-core[/-] [+]Adapter framework strategy: extract framework-agnostic client core (deferred)[/+] on Jul 19, 2026
  2. Bccorb commented on Jul 19, 2026

    @Bccorb
    ContributorAuthor

    Re-scoped after a design review. The near-term actionable pieces are split out so this issue is not perpetually blocked on the large version:

    The substantive change to this issue: the shared core is not primarily the HTTP client, which is already extraction-ready. It is the session state machine currently living in AuthProvider. The core should expose a framework-agnostic session store so Vue and Angular bindings cannot drift on auth state. Also noting that @seamless-auth/core is already taken by the server monorepo, so a client core needs a different name.

  3. Bccorb commented on Jul 20, 2026

    @Bccorb
    ContributorAuthor

    Scan update, current as of the 0.5.0 release candidate. Two of the three prerequisites this issue named are now met:

    What still blocks the extraction is unchanged and is the part that actually needs design: the session state machine in AuthProvider is still React-only, and that is what Vue and Angular bindings would otherwise each reimplement and drift on.

    One newly surfaced dependency: #92 asks whether provider helpers should throw or return results. That answer propagates directly into whatever the extracted bindings expose, so it is worth settling before this work starts rather than during it.

  4. Bccorb commented on Jul 29, 2026

    @Bccorb
    ContributorAuthor

    Decisions recorded, and phase 1 is open as #114.

    What is actually being extracted

    Confirming the re-scoped framing in the comment above: the HTTP client is not the hard part. src/client/*, src/fetchWithAuth.ts, src/scopedRoles.ts, and src/types.ts are already React-free and lint-enforced. The part a Vue composable and an Angular service would each reimplement, and drift on, is the session state machine in AuthProvider. That is what phase 1 moves.

    Both prerequisites named earlier are settled and this design inherits them: #24 fixed the client return shape, and #92 is closed with every provider helper returning a result. Nothing in the store throws.

    Decisions

    1. The store stays in this repo first, as src/session/, unexported. @seamless-auth/client gets published only once a second binding exists to validate the API. An API extracted against one consumer is a guess, and the second binding is what proves it. This also keeps Add Next.js support #78 unblocked.
    2. A failed session check no longer calls the logout endpoint. validateToken used to call logout() on any error, so a plain anonymous page load 401d on /users/me and then fired DELETE /logout for a session that does not exist. The store separates clearSession() (local) from logout() (network) and uses the former on a failed validation.
    3. Shared domain types move to @seamless-auth/types, which is the consolidation effort already underway across the other repos. No dependency is added here until that lands, so this issue does not block on it.
    4. hasSignedInBefore goes behind a SessionStoragePort, replacing the usePreviousSignIn hook. Browser, memory, and default implementations. The memory fallback is what makes the store safe to create during server-side rendering, which matters for Add Next.js support #78.

    Behaviors the extraction had to carry over

    Listing these because they are the parts that are easy to lose in a port, and they are now pinned by tests:

    • logout clears local state in a finally, so a server-side failure still leaves the UI signed out
    • updateCredential returns the credential itself, not the response wrapper
    • a failed refreshStepUpStatus clears the status to null, while a failed verification leaves it untouched
    • markSignedIn moved from a useEffect on user && isAuthenticated into the authenticated transition, so no binding has to remember to run it

    Two latent problems surfaced once the state left React and are fixed in #114: a slow session read could overwrite a newer one (switchOrganization, finishOAuthLogin, and handlePasskeyLogin all trigger a refresh, and React's batching hid the race), and snapshot references now stay stable so useSyncExternalStore cannot loop.

    Phasing

    This issue stays open for phase 3.

  5. Bccorb commented on Sep 13, 2026

    @Bccorb
    ContributorAuthor

    Shipped in #149, with the second binding in #151. @seamless-auth/client 0.1.0 holds the framework-free client and the session store (createAuthSession: getState, subscribe, actions), @seamless-auth/react 0.13.0 is a useSyncExternalStore binding over it, and @seamless-auth/react-native 0.1.0 is the second adapter, built the way this issue describes. A Vue or Angular adapter is now a binding over the same store.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions