feat(react-native): add the headless React Native binding - #151
Merged
Conversation
Bccorb
force-pushed
the
feat/react-native-binding
branch
2 times, most recently
from
September 13, 2026 03:05
6665147 to
db9e5fe
Compare
Draft
5 tasks
Contributor
Author
|
Pushed 94833ac: the transport now flattens a |
Contributor
Author
|
Also pushed faba6fa: |
13 tasks
Bccorb
force-pushed
the
feat/client-ports
branch
from
September 13, 2026 14:17
026e86e to
a190e84
Compare
Bccorb
force-pushed
the
feat/react-native-binding
branch
from
September 13, 2026 14:17
faba6fa to
99f6d4b
Compare
An error occurred while trying to automatically change base from
feat/client-ports
to
main
September 13, 2026 15:42
An AuthProvider over the shared session store, always on bearer transport; useAuth, useAuthClient, useLoginMethods and usePasskeySupport; and the native ports: createSecureStoreTokenStorage over expo-secure-store, createNativePasskeyPort over react-native-passkeys (mapping native failures onto the DOMException names the client's error readers understand), createWebBrowserOAuthRedirect over an expo-web-browser auth session resolving the callback's code and state, and describeDevice for passkey metadata. Each port takes its native module as a parameter, so an app installs only what it uses and Metro never resolves a module it did not. No screens: the app brings its own, the way a web app that skips AuthRoutes does. The flows, session state and token custody come from @seamless-auth/client.
Spreading a Headers instance copies its internals rather than its entries. On React Native the polyfill keeps a map field, and the nested object made Expo's native fetch refuse every authorizedFetch call whose caller built its headers with new Headers(). The caller's headers are now read as entries, whatever shape they came in, and replace a default of the same name case-insensitively instead of travelling beside it.
…apter calls An application reaching the adapter's own routes through authorizedFetch (its session list, a passthrough it adds) got no transport header, so in bearer transport the adapter answered in cookie mode and refused the call. A URL under the mount now takes the same road as the client's own calls: the header, the route's identity, and its effect on the held tokens.
Bccorb
force-pushed
the
feat/react-native-binding
branch
from
September 13, 2026 15:43
99f6d4b to
4800994
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Stacked on #150 (which is stacked on #149). The second binding over
@seamless-auth/client, and the package the RoxTarget pilot app and the Expo template will install. Part of fells-code/seamless-templates#40; the prebuilt native views (#126) are deliberately out of scope for the first version.@seamless-auth/react-native0.1.0:AuthProviderover the shared session store, always on bearer transport. Props:apiHost,basePath(default/auth),magicLinkRedirectUri,ports,fetch. No browser defaults: a native app supplies its ports, and the provider keys the session on their identities.useAuth,useAuthClient(the session's own client, which carries a sign-in from/loginthrough its OTP or passkey step),useLoginMethods,usePasskeySupport(reads the passkey port).createSecureStoreTokenStorage(SecureStore, { key?, storeOptions? }): the pair as one keystore entry; never throws (a locked or unparseable keystore reads as signed out).createNativePasskeyPort(Passkeys):react-native-passkeys; anullresult and the platform's cancel/duplicate codes map ontoNotAllowedError/InvalidStateErrorasPasskeyCeremonyError, other failures keep their name.createWebBrowserOAuthRedirect(WebBrowser, options?):openAuthSessionAsync, resolving{ type: 'callback', code, state }orcancelled;parseOAuthCallbackUrlexported for deep-link handling.describeDevice(Platform, name?)for passkey metadata.Bundle externals are
react,react/jsx-runtime,@seamless-auth/client.Test plan
npm run lint,npm run typecheck,npm run build,npx prettier --check .,npm run check-npm-buildnpm test: 406 passing across three projects, coverage 91.12 / 82.50 / 90.88 / 91.36react-native/tests/ports.test.ts(secure store round-trip, key and options passthrough, unparseable and throwing keystores, native passkey ceremonies and failure mapping, callback URL parsing for https and custom schemes, auth session outcomes, device description) andreact-native/tests/provider.test.tsx(session restored from the keystore over bearer transport with the right headers and nocredentials, signed-out start without a refresh call, custom mount path,useAuthoutside the provider)npx changeset status: client minor, react minor, react-native minorNot verified here