@symbiote-native/auth-session 0.0.0-stage → 0.1.1

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.
Files changed (179) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +157 -2
  3. package/build/angular/index.d.ts +2 -0
  4. package/build/angular/index.js +3 -0
  5. package/build/angular/use-auth-request.d.ts +7 -0
  6. package/build/angular/use-auth-request.js +11 -0
  7. package/build/core/auth-request-controllers.d.ts +44 -0
  8. package/build/core/auth-request-controllers.js +122 -0
  9. package/build/core/auth-request-hooks.d.ts +67 -0
  10. package/build/core/auth-request-hooks.js +75 -0
  11. package/build/core/auth-request.d.ts +29 -0
  12. package/build/core/auth-request.js +161 -0
  13. package/build/core/auth-request.types.d.ts +46 -0
  14. package/build/core/auth-request.types.js +23 -0
  15. package/build/core/auth-session.d.ts +10 -0
  16. package/build/core/auth-session.js +37 -0
  17. package/build/core/auth-session.types.d.ts +27 -0
  18. package/build/core/auth-session.types.js +1 -0
  19. package/build/core/base64.d.ts +1 -0
  20. package/build/core/base64.js +28 -0
  21. package/build/core/discovery.d.ts +57 -0
  22. package/build/core/discovery.js +29 -0
  23. package/build/core/errors.d.ts +29 -0
  24. package/build/core/errors.js +74 -0
  25. package/build/core/fetch.d.ts +15 -0
  26. package/build/core/fetch.js +40 -0
  27. package/build/core/index.d.ts +24 -0
  28. package/build/core/index.js +14 -0
  29. package/build/core/pkce.d.ts +9 -0
  30. package/build/core/pkce.js +41 -0
  31. package/build/core/prompt-string.d.ts +2 -0
  32. package/build/core/prompt-string.js +5 -0
  33. package/build/core/providers/facebook.d.ts +16 -0
  34. package/build/core/providers/facebook.js +43 -0
  35. package/build/core/providers/google-exchange-controller.d.ts +10 -0
  36. package/build/core/providers/google-exchange-controller.js +73 -0
  37. package/build/core/providers/google.d.ts +22 -0
  38. package/build/core/providers/google.js +52 -0
  39. package/build/core/providers/provider-hook-setup.d.ts +19 -0
  40. package/build/core/providers/provider-hook-setup.js +78 -0
  41. package/build/core/providers/provider-utils.d.ts +2 -0
  42. package/build/core/providers/provider-utils.js +8 -0
  43. package/build/core/providers/provider.types.d.ts +5 -0
  44. package/build/core/providers/provider.types.js +1 -0
  45. package/build/core/query-params.d.ts +5 -0
  46. package/build/core/query-params.js +12 -0
  47. package/build/core/session-result.d.ts +6 -0
  48. package/build/core/session-result.js +4 -0
  49. package/build/core/token-request.d.ts +89 -0
  50. package/build/core/token-request.js +328 -0
  51. package/build/core/token-request.types.d.ts +52 -0
  52. package/build/core/token-request.types.js +14 -0
  53. package/build/react/index.d.ts +3 -0
  54. package/build/react/index.js +2 -0
  55. package/build/react/use-auth-request.d.ts +20 -0
  56. package/build/react/use-auth-request.js +52 -0
  57. package/build/solid/index.d.ts +2 -0
  58. package/build/solid/index.js +2 -0
  59. package/build/solid/use-auth-request.d.ts +6 -0
  60. package/build/solid/use-auth-request.js +10 -0
  61. package/build/svelte/index.d.ts +2 -0
  62. package/build/svelte/index.js +2 -0
  63. package/build/svelte/use-auth-request.svelte.d.ts +28 -0
  64. package/build/svelte/use-auth-request.svelte.js +11 -0
  65. package/build/vue/index.d.ts +2 -0
  66. package/build/vue/index.js +2 -0
  67. package/build/vue/use-auth-request.d.ts +7 -0
  68. package/build/vue/use-auth-request.js +12 -0
  69. package/build-ngc/angular/index.d.ts +2 -0
  70. package/build-ngc/angular/index.js +4 -0
  71. package/build-ngc/angular/index.js.map +1 -0
  72. package/build-ngc/angular/use-auth-request.d.ts +7 -0
  73. package/build-ngc/angular/use-auth-request.js +12 -0
  74. package/build-ngc/angular/use-auth-request.js.map +1 -0
  75. package/build-ngc/core/auth-request-controllers.d.ts +44 -0
  76. package/build-ngc/core/auth-request-controllers.js +123 -0
  77. package/build-ngc/core/auth-request-controllers.js.map +1 -0
  78. package/build-ngc/core/auth-request-hooks.d.ts +67 -0
  79. package/build-ngc/core/auth-request-hooks.js +76 -0
  80. package/build-ngc/core/auth-request-hooks.js.map +1 -0
  81. package/build-ngc/core/auth-request.d.ts +29 -0
  82. package/build-ngc/core/auth-request.js +162 -0
  83. package/build-ngc/core/auth-request.js.map +1 -0
  84. package/build-ngc/core/auth-request.types.d.ts +46 -0
  85. package/build-ngc/core/auth-request.types.js +24 -0
  86. package/build-ngc/core/auth-request.types.js.map +1 -0
  87. package/build-ngc/core/auth-session.d.ts +10 -0
  88. package/build-ngc/core/auth-session.js +38 -0
  89. package/build-ngc/core/auth-session.js.map +1 -0
  90. package/build-ngc/core/auth-session.types.d.ts +27 -0
  91. package/build-ngc/core/auth-session.types.js +2 -0
  92. package/build-ngc/core/auth-session.types.js.map +1 -0
  93. package/build-ngc/core/base64.d.ts +1 -0
  94. package/build-ngc/core/base64.js +29 -0
  95. package/build-ngc/core/base64.js.map +1 -0
  96. package/build-ngc/core/discovery.d.ts +57 -0
  97. package/build-ngc/core/discovery.js +30 -0
  98. package/build-ngc/core/discovery.js.map +1 -0
  99. package/build-ngc/core/errors.d.ts +29 -0
  100. package/build-ngc/core/errors.js +75 -0
  101. package/build-ngc/core/errors.js.map +1 -0
  102. package/build-ngc/core/fetch.d.ts +15 -0
  103. package/build-ngc/core/fetch.js +41 -0
  104. package/build-ngc/core/fetch.js.map +1 -0
  105. package/build-ngc/core/index.d.ts +24 -0
  106. package/build-ngc/core/index.js +15 -0
  107. package/build-ngc/core/index.js.map +1 -0
  108. package/build-ngc/core/pkce.d.ts +9 -0
  109. package/build-ngc/core/pkce.js +42 -0
  110. package/build-ngc/core/pkce.js.map +1 -0
  111. package/build-ngc/core/prompt-string.d.ts +2 -0
  112. package/build-ngc/core/prompt-string.js +6 -0
  113. package/build-ngc/core/prompt-string.js.map +1 -0
  114. package/build-ngc/core/providers/facebook.d.ts +16 -0
  115. package/build-ngc/core/providers/facebook.js +44 -0
  116. package/build-ngc/core/providers/facebook.js.map +1 -0
  117. package/build-ngc/core/providers/google-exchange-controller.d.ts +10 -0
  118. package/build-ngc/core/providers/google-exchange-controller.js +74 -0
  119. package/build-ngc/core/providers/google-exchange-controller.js.map +1 -0
  120. package/build-ngc/core/providers/google.d.ts +22 -0
  121. package/build-ngc/core/providers/google.js +53 -0
  122. package/build-ngc/core/providers/google.js.map +1 -0
  123. package/build-ngc/core/providers/provider-hook-setup.d.ts +19 -0
  124. package/build-ngc/core/providers/provider-hook-setup.js +79 -0
  125. package/build-ngc/core/providers/provider-hook-setup.js.map +1 -0
  126. package/build-ngc/core/providers/provider-utils.d.ts +2 -0
  127. package/build-ngc/core/providers/provider-utils.js +9 -0
  128. package/build-ngc/core/providers/provider-utils.js.map +1 -0
  129. package/build-ngc/core/providers/provider.types.d.ts +5 -0
  130. package/build-ngc/core/providers/provider.types.js +2 -0
  131. package/build-ngc/core/providers/provider.types.js.map +1 -0
  132. package/build-ngc/core/query-params.d.ts +5 -0
  133. package/build-ngc/core/query-params.js +13 -0
  134. package/build-ngc/core/query-params.js.map +1 -0
  135. package/build-ngc/core/session-result.d.ts +6 -0
  136. package/build-ngc/core/session-result.js +5 -0
  137. package/build-ngc/core/session-result.js.map +1 -0
  138. package/build-ngc/core/token-request.d.ts +89 -0
  139. package/build-ngc/core/token-request.js +329 -0
  140. package/build-ngc/core/token-request.js.map +1 -0
  141. package/build-ngc/core/token-request.types.d.ts +52 -0
  142. package/build-ngc/core/token-request.types.js +15 -0
  143. package/build-ngc/core/token-request.types.js.map +1 -0
  144. package/package.json +151 -3
  145. package/src/angular/index.ts +11 -0
  146. package/src/angular/use-auth-request.ts +30 -0
  147. package/src/core/auth-request-controllers.ts +233 -0
  148. package/src/core/auth-request-hooks.ts +342 -0
  149. package/src/core/auth-request.ts +199 -0
  150. package/src/core/auth-request.types.ts +55 -0
  151. package/src/core/auth-session.ts +63 -0
  152. package/src/core/auth-session.types.ts +29 -0
  153. package/src/core/base64.ts +33 -0
  154. package/src/core/discovery.ts +102 -0
  155. package/src/core/errors.ts +102 -0
  156. package/src/core/fetch.ts +71 -0
  157. package/src/core/index.ts +97 -0
  158. package/src/core/pkce.ts +60 -0
  159. package/src/core/prompt-string.ts +8 -0
  160. package/src/core/providers/facebook.ts +66 -0
  161. package/src/core/providers/google-exchange-controller.ts +117 -0
  162. package/src/core/providers/google.ts +81 -0
  163. package/src/core/providers/provider-hook-setup.ts +117 -0
  164. package/src/core/providers/provider-utils.ts +18 -0
  165. package/src/core/providers/provider.types.ts +6 -0
  166. package/src/core/query-params.ts +20 -0
  167. package/src/core/session-result.ts +15 -0
  168. package/src/core/token-request.ts +452 -0
  169. package/src/core/token-request.types.ts +60 -0
  170. package/src/react/index.ts +11 -0
  171. package/src/react/use-auth-request.ts +151 -0
  172. package/src/solid/index.ts +10 -0
  173. package/src/solid/use-auth-request.ts +24 -0
  174. package/src/svelte/index.ts +10 -0
  175. package/src/svelte/svelte-ambient.d.ts +3 -0
  176. package/src/svelte/svelte-compile.test-helper.ts +7 -0
  177. package/src/svelte/use-auth-request.svelte.ts +22 -0
  178. package/src/vue/index.ts +10 -0
  179. package/src/vue/use-auth-request.ts +38 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 A. Prokopenko
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,158 @@
1
- # Temporary Holding Version
1
+ # @symbiote-native/auth-session
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Add "Sign in with ..." through your identity provider's web page: the app opens a browser tab, the
4
+ user signs in, the provider redirects back with a code, and you exchange it for a token. One API
5
+ for every [SymbioteNative](../../README.md) adapter (React, Vue, Svelte, Solid and Angular).
6
+
7
+ It ports [`expo-auth-session`](https://github.com/expo/expo/tree/main/packages/expo-auth-session),
8
+ a PKCE-based OAuth2/OpenID Connect flow over a browser tab, with its hooks in each framework's own
9
+ idiom.
10
+
11
+ Unlike every other package in this catalog, `expo-auth-session` ships **no native code at all** -
12
+ it is a pure-JS flow built entirely on other Expo JS APIs. So instead of wrapping
13
+ `expo-auth-session` directly, this package hand-ports its logic and depends on the packages that
14
+ already cover its building blocks: [`@symbiote-native/web-browser`](../web-browser) (the
15
+ authorization tab), [`@symbiote-native/crypto`](../crypto) (PKCE), and
16
+ [`@symbiote-native/application`](../application) (the default native redirect scheme). No
17
+ `expo-modules-core` dependency, no `native-link.json` - same shape as
18
+ [`@symbiote-native/standard-web-crypto`](../standard-web-crypto).
19
+
20
+ ## Install
21
+
22
+ **New app:**
23
+
24
+ ```bash
25
+ npx @symbiote-native/cli new my-app --auth-session
26
+ ```
27
+
28
+ **Existing SymbioteNative app:**
29
+
30
+ ```bash
31
+ npx @symbiote-native/cli add --auth-session
32
+ ```
33
+
34
+ Either way: installs `@symbiote-native/auth-session`, which pulls in
35
+ `@symbiote-native/web-browser`/`@symbiote-native/crypto`/`@symbiote-native/application` as regular
36
+ dependencies. If those three aren't already installed as standalone packages in your app, their
37
+ own native autolinking wiring still applies - see
38
+ [`@symbiote-native/web-browser`'s README](../web-browser/README.md) for the one-time app steps.
39
+
40
+ <details>
41
+ <summary>Manual install (no CLI)</summary>
42
+
43
+ ```bash
44
+ npm install @symbiote-native/auth-session
45
+ ```
46
+
47
+ </details>
48
+
49
+ ### Register a URL scheme for the redirect
50
+
51
+ The provider redirects back through a custom URL scheme (for example `myapp://`), so the app has to
52
+ own one: register it in your iOS and Android projects and rebuild. Without a scheme the sign-in
53
+ still completes, but the result cannot reach your app and the user has to close the browser tab by
54
+ hand, which reads as a cancelled event. Allow-list the same redirect URI at your provider.
55
+
56
+ ## Notes
57
+
58
+ - **Never put secret keys in app code.** A client secret in a mobile app is not secret; keep it on
59
+ your server. PKCE exists so a public client needs none.
60
+ - **Filter auth redirects out of your own link handlers.** An auth redirect is one more deep link;
61
+ put a recognizable marker in your own `redirectUri` so your `Linking` handler or router can
62
+ ignore it.
63
+
64
+ ## What's not ported
65
+
66
+ - **The Expo Go / `auth.expo.io` proxy flow** (`SessionUrlProvider`, the legacy
67
+ `AuthSession.getRedirectUrl`/`getDefaultReturnUrl`) - built entirely on `expo-constants`'
68
+ app-manifest concept (`Constants.expoConfig`, `ExecutionEnvironment`), which doesn't exist in
69
+ this bare, Metro-only, non-Expo-CLI project. `@symbiote-native/constants` ports only the native
70
+ fields of `expo-constants` and never the manifest, so it does not bring this flow back.
71
+ - **`makeRedirectUri`'s manifest-scheme auto-detection.** Upstream's version calls
72
+ `expo-linking`'s `createURL`/`resolveScheme`, which read a scheme out of the same app manifest.
73
+ This port's `makeRedirectUri` never reads a manifest: pass `native` for a production build, or
74
+ `scheme` otherwise - it throws if neither is given, rather than guessing.
75
+ - **Upstream's `Request<T, B>` base class is renamed `TokenRequest`'s internal `BaseRequest`** to
76
+ avoid shadowing the global `Request` type used by `fetch`.
77
+
78
+ ## Not ported (test scope)
79
+
80
+ Upstream ships tests for `AuthRequest`, `Base64`, `Errors`, `QueryParams`, and `TokenRequest` - all
81
+ five are ported (`src/core/*.test.ts`). `AuthSession-test.*.ts` and `SessionUrlProvider-test.ts`
82
+ are **not** ported: both test only the dropped manifest/proxy flow above. `auth-session.test.ts`
83
+ here instead covers this port's own trimmed `makeRedirectUri` contract.
84
+
85
+ ## Hooks
86
+
87
+ Every hook upstream ships is ported to every adapter, the load, prompt and code-exchange logic is
88
+ shared in `core/` and each adapter supplies only its lifecycle:
89
+
90
+ | Upstream | React | Vue / Solid / Svelte | Angular |
91
+ |---|---|---|---|
92
+ | `useAutoDiscovery` | `useAutoDiscovery(issuer)` | same name, getter or ref arguments | `injectAutoDiscovery` |
93
+ | `useLoadedAuthRequest` | `useLoadedAuthRequest(config, discovery, RequestClass)` | same name | `injectLoadedAuthRequest` |
94
+ | `useAuthRequestResult` | `useAuthRequestResult(request, discovery, options?)` | same name | `injectAuthRequestResult` |
95
+ | `useAuthRequest` | `useAuthRequest(config, discovery)` | same name | `injectAuthRequest` |
96
+ | Google `useAuthRequest` / `useIdTokenAuthRequest` | `useGoogleAuthRequest` / `useGoogleIdTokenAuthRequest` | same names | `injectGoogleAuthRequest` / `injectGoogleIdTokenAuthRequest` |
97
+ | Facebook `useAuthRequest` | `useFacebookAuthRequest` | same name | `injectFacebookAuthRequest` |
98
+
99
+ Each returns `[request, result, promptAsync]`, as boxes in the adapter's own shape (a `ShallowRef`
100
+ in Vue, an accessor in Solid, `{ current }` in Svelte, a `Signal` in Angular; React returns the
101
+ values). Arguments are getters in Solid, Svelte and Angular, refs or getters in Vue.
102
+
103
+ Provider hooks are flat exports with the provider in the name, since there is no `providers/*`
104
+ subpath. The Google hook exchanges the code for a token on its own when the code flow is used, the
105
+ id token is then in `result.params.id_token`. Off web, `useGoogleIdTokenAuthRequest` follows the
106
+ default code flow, as upstream does.
107
+
108
+ ## Shape
109
+
110
+ ```
111
+ src/core/ AuthRequest, TokenRequest family, Discovery, Errors, PKCE, Base64, QueryParams,
112
+ Fetch, AuthSession (dismiss/makeRedirectUri/loadAsync), providers/{google,facebook},
113
+ the hook controllers and `createAuthRequestHooks`
114
+ src/{react,vue,solid,svelte,angular}/ each adapter's hook bindings
115
+ ```
116
+
117
+ ## Use it
118
+
119
+ ```ts
120
+ import { AuthRequest, exchangeCodeAsync, makeRedirectUri } from '@symbiote-native/auth-session';
121
+
122
+ const discovery = {
123
+ authorizationEndpoint: 'https://example.com/oauth/authorize',
124
+ tokenEndpoint: 'https://example.com/oauth/token',
125
+ };
126
+
127
+ const request = new AuthRequest({
128
+ clientId: 'my-client-id',
129
+ redirectUri: makeRedirectUri({ scheme: 'myapp' }),
130
+ scopes: ['openid', 'profile'],
131
+ });
132
+
133
+ const result = await request.promptAsync(discovery);
134
+ if (result.type === 'success') {
135
+ const token = await exchangeCodeAsync(
136
+ { clientId: 'my-client-id', code: result.params.code, redirectUri: request.redirectUri },
137
+ discovery,
138
+ );
139
+ }
140
+ ```
141
+
142
+ ## Common questions
143
+
144
+ - **Which redirect URI do I register?** Print `makeRedirectUri({ scheme })`: `my-scheme://redirect`
145
+ in a development build. Register exactly that string with the provider.
146
+ - **`request` is `null`.** `useAuthRequest` loads it asynchronously; disable the button until it is set.
147
+ - **PKCE?** On by default; pass `request.codeVerifier` as `extraParams` when exchanging the code.
148
+ - **Redirect lost on iOS.** Check the scheme is registered in the native project.
149
+
150
+ Sources: [Expo docs: AuthSession](https://docs.expo.dev/versions/latest/sdk/auth-session/),
151
+ [Expo guide: authentication](https://docs.expo.dev/guides/authentication/),
152
+ [expo/expo#10514](https://github.com/expo/expo/issues/10514).
153
+
154
+ ## Test it
155
+
156
+ ```bash
157
+ pnpm vitest run packages/auth-session
158
+ ```
@@ -0,0 +1,2 @@
1
+ export * from '../core';
2
+ export { injectAuthRequest, injectAuthRequestResult, injectAutoDiscovery, injectFacebookAuthRequest, injectGoogleAuthRequest, injectGoogleIdTokenAuthRequest, injectLoadedAuthRequest, } from './use-auth-request';
@@ -0,0 +1,3 @@
1
+ // The hooks are the `inject*` twins below, the rest is the shared core with nothing to wrap
2
+ export * from '../core/index.js';
3
+ export { injectAuthRequest, injectAuthRequestResult, injectAutoDiscovery, injectFacebookAuthRequest, injectGoogleAuthRequest, injectGoogleIdTokenAuthRequest, injectLoadedAuthRequest, } from './use-auth-request.js';
@@ -0,0 +1,7 @@
1
+ import type { Signal } from '@angular/core';
2
+ export declare const injectAuthRequest: (config: () => import(".").IAuthRequestConfig, discovery: () => import(".").IDiscoveryDocument | null) => readonly [Signal<import(".").AuthRequest | null>, Signal<import(".").IAuthSessionResult | null>, import(".").IPromptMethod], injectAuthRequestResult: (request: () => import(".").AuthRequest | null, discovery: () => import(".").IDiscoveryDocument | null, customOptions?: (() => import("@symbiote-native/web-browser").IWebBrowserOpenOptions & {
3
+ preferEphemeralSession?: boolean;
4
+ preferUniversalLinks?: boolean;
5
+ } & {
6
+ url?: string;
7
+ }) | undefined) => readonly [Signal<import(".").IAuthSessionResult | null>, import(".").IPromptMethod], injectAutoDiscovery: (issuerOrDiscovery: () => import(".").IIssuerOrDiscovery) => Signal<import(".").IDiscoveryDocument | null>, injectFacebookAuthRequest: (config?: (() => Partial<import(".").IFacebookAuthRequestConfig>) | undefined, redirectUriOptions?: (() => Partial<import(".").IAuthSessionRedirectUriOptions>) | undefined) => readonly [Signal<import(".").FacebookAuthRequest | null>, Signal<import(".").IAuthSessionResult | null>, import(".").IPromptMethod], injectGoogleAuthRequest: (config?: (() => Partial<import(".").IGoogleAuthRequestConfig>) | undefined, redirectUriOptions?: (() => Partial<import(".").IAuthSessionRedirectUriOptions>) | undefined) => readonly [Signal<import(".").GoogleAuthRequest | null>, Signal<import(".").IAuthSessionResult | null>, import(".").IPromptMethod], injectGoogleIdTokenAuthRequest: (config: () => Partial<import(".").IGoogleAuthRequestConfig>, redirectUriOptions?: (() => Partial<import(".").IAuthSessionRedirectUriOptions>) | undefined) => readonly [Signal<import(".").GoogleAuthRequest | null>, Signal<import(".").IAuthSessionResult | null>, import(".").IPromptMethod], injectLoadedAuthRequest: (config: () => import(".").IAuthRequestConfig, discovery: () => import(".").IDiscoveryDocument | null, RequestClass: import(".").IAuthRequestClass<import(".").AuthRequest>) => Signal<import(".").AuthRequest | null>;
@@ -0,0 +1,11 @@
1
+ // Angular twins of `expo-auth-session`'s auth request hooks, `injectX` shape, call them in an
2
+ // injection context: getter or signal arguments, signal results
3
+ import { createEventValueHook, createResourceHook, } from '@symbiote-native/angular';
4
+ import { GETTER_ARGS, createAuthRequestHooks } from '../core/index.js';
5
+ export const { useAuthRequest: injectAuthRequest, useAuthRequestResult: injectAuthRequestResult, useAutoDiscovery: injectAutoDiscovery, useFacebookAuthRequest: injectFacebookAuthRequest, useGoogleAuthRequest: injectGoogleAuthRequest, useGoogleIdTokenAuthRequest: injectGoogleIdTokenAuthRequest, useLoadedAuthRequest: injectLoadedAuthRequest, } = createAuthRequestHooks({
6
+ createResourceHook,
7
+ createEventValueHook,
8
+ readResource: box => box(),
9
+ readValue: box => box(),
10
+ ...GETTER_ARGS,
11
+ });
@@ -0,0 +1,44 @@
1
+ import type { IEventValueSource } from '@symbiote-native/engine';
2
+ import type { IAuthDiscoveryDocument, IAuthRequestConfig, IAuthRequestPromptOptions } from './auth-request.types';
3
+ import type { IAuthSessionResult } from './auth-session.types';
4
+ import type { IDiscoveryDocument, IIssuerOrDiscovery } from './discovery';
5
+ export declare const AUTH_CELL_CHANGE = "change";
6
+ export type IAuthCell<T> = IEventValueSource<T, typeof AUTH_CELL_CHANGE> & {
7
+ getSnapshot: () => T;
8
+ };
9
+ type IWritableAuthCell<T> = IAuthCell<T> & {
10
+ set: (value: T) => void;
11
+ };
12
+ export declare function createAuthCell<T>(initial: T): IWritableAuthCell<T>;
13
+ /** What a request has to offer for a hook to load it and prompt with it */
14
+ export type ILoadableRequest = {
15
+ url: string | null;
16
+ codeVerifier?: string;
17
+ makeAuthUrlAsync: (discovery: IAuthDiscoveryDocument) => Promise<string>;
18
+ promptAsync: (discovery: IAuthDiscoveryDocument, options?: IAuthRequestPromptOptions) => Promise<IAuthSessionResult>;
19
+ };
20
+ export type IAuthRequestClass<TRequest extends ILoadableRequest> = new (config: IAuthRequestConfig) => TRequest;
21
+ type ICellResourceController<TArgs extends unknown[], TValue> = {
22
+ resolve: (...args: TArgs) => IAuthCell<TValue>;
23
+ flushDispose: () => void;
24
+ dispose: () => void;
25
+ };
26
+ export declare function createAutoDiscoveryController(): ICellResourceController<[
27
+ IIssuerOrDiscovery
28
+ ], IDiscoveryDocument | null>;
29
+ export declare function createLoadedRequestController<TRequest extends ILoadableRequest = ILoadableRequest>(): {
30
+ resolve: (config: IAuthRequestConfig, discovery: IDiscoveryDocument | null, RequestClass: IAuthRequestClass<TRequest>) => IAuthCell<TRequest | null>;
31
+ flushDispose: () => void;
32
+ dispose: () => void;
33
+ };
34
+ export type IPromptMethod = (options?: IAuthRequestPromptOptions) => Promise<IAuthSessionResult>;
35
+ export type IRequestResultHolder = {
36
+ result: IAuthCell<IAuthSessionResult | null>;
37
+ promptAsync: IPromptMethod;
38
+ };
39
+ export declare function createRequestResultController(): {
40
+ resolve: (request: ILoadableRequest | null, discovery: IDiscoveryDocument | null, customOptions?: IAuthRequestPromptOptions) => IRequestResultHolder;
41
+ flushDispose: () => void;
42
+ dispose: () => void;
43
+ };
44
+ export {};
@@ -0,0 +1,122 @@
1
+ // Framework-agnostic halves of `useAutoDiscovery`, `useLoadedAuthRequest` and
2
+ // `useAuthRequestResult`. Each controller binds to an adapter's `createResourceHook`, and the value
3
+ // it hands back is a cell the adapter follows with its `createEventValueHook`
4
+ import { createResourceController } from '@symbiote-native/engine';
5
+ import { resolveDiscoveryAsync } from './discovery.js';
6
+ import { createPromptString } from './prompt-string.js';
7
+ export const AUTH_CELL_CHANGE = 'change';
8
+ export function createAuthCell(initial) {
9
+ let value = initial;
10
+ const listeners = new Set();
11
+ return {
12
+ getSnapshot: () => value,
13
+ set(next) {
14
+ value = next;
15
+ for (const listener of [...listeners])
16
+ listener(next);
17
+ },
18
+ addListener(_event, listener) {
19
+ listeners.add(listener);
20
+ return { remove: () => void listeners.delete(listener) };
21
+ },
22
+ };
23
+ }
24
+ export function createAutoDiscoveryController() {
25
+ const controller = createResourceController(issuerOrDiscovery => {
26
+ const cell = createAuthCell(null);
27
+ let isAllowed = true;
28
+ void resolveDiscoveryAsync(issuerOrDiscovery).then(discovery => {
29
+ if (isAllowed)
30
+ cell.set(discovery);
31
+ });
32
+ return {
33
+ cell,
34
+ stop: () => {
35
+ isAllowed = false;
36
+ },
37
+ };
38
+ }, resource => resource.stop());
39
+ return {
40
+ resolve: issuerOrDiscovery => controller.resolve(issuerOrDiscovery).cell,
41
+ flushDispose: controller.flushDispose,
42
+ dispose: controller.dispose,
43
+ };
44
+ }
45
+ // Everything upstream lists as the effect's dependencies, folded to primitives
46
+ function loadedRequestKey(config, discovery) {
47
+ return JSON.stringify([
48
+ discovery?.authorizationEndpoint,
49
+ config.clientId,
50
+ config.redirectUri,
51
+ config.responseType,
52
+ config.clientSecret,
53
+ config.codeChallenge,
54
+ config.state,
55
+ config.usePKCE,
56
+ config.scopes?.join(' '),
57
+ createPromptString(config.prompt),
58
+ JSON.stringify(config.extraParams ?? {}),
59
+ ]);
60
+ }
61
+ export function createLoadedRequestController() {
62
+ // The request that finished loading last, shown while a changed config reloads
63
+ let lastLoaded = null;
64
+ let RequestClassInUse = null;
65
+ const controller = createResourceController(({ config, discovery }) => {
66
+ const cell = createAuthCell(lastLoaded);
67
+ let isMounted = true;
68
+ if (discovery && RequestClassInUse) {
69
+ const request = new RequestClassInUse(config);
70
+ void request.makeAuthUrlAsync(discovery).then(() => {
71
+ if (!isMounted)
72
+ return;
73
+ lastLoaded = request;
74
+ cell.set(request);
75
+ });
76
+ }
77
+ return {
78
+ cell,
79
+ stop: () => {
80
+ isMounted = false;
81
+ },
82
+ };
83
+ }, resource => resource.stop(), (a, b) => a.key === b.key);
84
+ return {
85
+ resolve(config, discovery, RequestClass) {
86
+ RequestClassInUse = RequestClass;
87
+ return controller.resolve({
88
+ key: loadedRequestKey(config, discovery),
89
+ config,
90
+ discovery,
91
+ }).cell;
92
+ },
93
+ flushDispose: controller.flushDispose,
94
+ dispose: controller.dispose,
95
+ };
96
+ }
97
+ export function createRequestResultController() {
98
+ const result = createAuthCell(null);
99
+ let latest = { request: null, discovery: null, customOptions: {} };
100
+ // Reads the latest inputs when called, so one stable function serves every render
101
+ const promptAsync = async (options = {}) => {
102
+ const { request, discovery, customOptions } = latest;
103
+ if (!discovery || !request) {
104
+ throw new Error('Cannot prompt to authenticate until the request has finished loading.');
105
+ }
106
+ const prompted = await request.promptAsync(discovery, {
107
+ ...customOptions,
108
+ ...options,
109
+ });
110
+ result.set(prompted);
111
+ return prompted;
112
+ };
113
+ const holder = { result, promptAsync };
114
+ return {
115
+ resolve(request, discovery, customOptions = {}) {
116
+ latest = { request, discovery, customOptions };
117
+ return holder;
118
+ },
119
+ flushDispose: () => undefined,
120
+ dispose: () => undefined,
121
+ };
122
+ }
@@ -0,0 +1,67 @@
1
+ import { AuthRequest } from './auth-request';
2
+ import type { IAuthCell, IAuthRequestClass, IPromptMethod } from './auth-request-controllers';
3
+ import { AUTH_CELL_CHANGE } from './auth-request-controllers';
4
+ import type { IAuthRequestConfig, IAuthRequestPromptOptions } from './auth-request.types';
5
+ import type { IAuthSessionRedirectUriOptions, IAuthSessionResult } from './auth-session.types';
6
+ import type { IDiscoveryDocument, IIssuerOrDiscovery } from './discovery';
7
+ import type { FacebookAuthRequest, IFacebookAuthRequestConfig } from './providers/facebook';
8
+ import type { GoogleAuthRequest, IGoogleAuthRequestConfig } from './providers/google';
9
+ /** A type-level function, how an adapter says "its box of `T`" */
10
+ export interface IHkt {
11
+ readonly input: unknown;
12
+ readonly output: unknown;
13
+ }
14
+ export type IApply<F extends IHkt, T> = (F & {
15
+ readonly input: T;
16
+ })['output'];
17
+ /** A plain getter, the argument shape of Solid, Svelte and Angular */
18
+ export interface IGetterKind extends IHkt {
19
+ readonly output: () => this['input'];
20
+ }
21
+ type IBoxed<T> = {
22
+ readonly current: T;
23
+ };
24
+ /** A `{ current }` box, Svelte's result shape */
25
+ export interface IBoxedKind extends IHkt {
26
+ readonly output: IBoxed<this['input']>;
27
+ }
28
+ /** The argument half of a kit for adapters whose arguments are plain getters */
29
+ export declare const GETTER_ARGS: {
30
+ toGetter: <T>(arg: () => T) => (() => T);
31
+ constant: <T>(value: T) => (() => T);
32
+ };
33
+ type IDiscoveryValue = IDiscoveryDocument | null;
34
+ type IResultValue = IAuthSessionResult | null;
35
+ export type IAuthResourceController<TArgs extends unknown[], TResource> = {
36
+ resolve: (...args: TArgs) => TResource;
37
+ flushDispose: () => void;
38
+ dispose: () => void;
39
+ };
40
+ /** The adapter's own primitives the hooks are built from */
41
+ export type IAuthHooksKit<FResource extends IHkt, FValue extends IHkt, FArg extends IHkt> = {
42
+ createResourceHook: <TArgs extends unknown[], TResource>(createController: () => IAuthResourceController<TArgs, TResource>) => (getArgs: () => TArgs) => IApply<FResource, TResource>;
43
+ readResource: <T>(box: IApply<FResource, T>) => T;
44
+ createEventValueHook: <TValue>(event: typeof AUTH_CELL_CHANGE, getValue: (source: IAuthCell<TValue>) => TValue) => (getSource: () => IAuthCell<TValue>) => IApply<FValue, TValue>;
45
+ readValue: <T>(box: IApply<FValue, T>) => T;
46
+ /** Turns whatever the adapter accepts as an argument into a getter */
47
+ toGetter: <T>(arg: IApply<FArg, T>) => () => T;
48
+ /** Wraps a fixed value in the adapter's argument shape */
49
+ constant: <T>(value: T) => IApply<FArg, T>;
50
+ };
51
+ type IUseAuthRequestBoxes<FValue extends IHkt, TRequest extends AuthRequest> = readonly [
52
+ IApply<FValue, TRequest | null>,
53
+ IApply<FValue, IResultValue>,
54
+ IPromptMethod
55
+ ];
56
+ type IAnyKit = IAuthHooksKit<IHkt, IHkt, IHkt>;
57
+ export type IAuthRequestHooks<FValue extends IHkt, FArg extends IHkt> = {
58
+ useAutoDiscovery: (issuerOrDiscovery: IApply<FArg, IIssuerOrDiscovery>) => IApply<FValue, IDiscoveryValue>;
59
+ useLoadedAuthRequest: (config: IApply<FArg, IAuthRequestConfig>, discovery: IApply<FArg, IDiscoveryValue>, RequestClass: IAuthRequestClass<AuthRequest>) => IApply<FValue, AuthRequest | null>;
60
+ useAuthRequestResult: (request: IApply<FArg, AuthRequest | null>, discovery: IApply<FArg, IDiscoveryValue>, customOptions?: IApply<FArg, IAuthRequestPromptOptions>) => readonly [IApply<FValue, IResultValue>, IPromptMethod];
61
+ useAuthRequest: (config: IApply<FArg, IAuthRequestConfig>, discovery: IApply<FArg, IDiscoveryValue>) => IUseAuthRequestBoxes<FValue, AuthRequest>;
62
+ useGoogleAuthRequest: (config?: IApply<FArg, Partial<IGoogleAuthRequestConfig>>, redirectUriOptions?: IApply<FArg, Partial<IAuthSessionRedirectUriOptions>>) => IUseAuthRequestBoxes<FValue, GoogleAuthRequest>;
63
+ useGoogleIdTokenAuthRequest: (config: IApply<FArg, Partial<IGoogleAuthRequestConfig>>, redirectUriOptions?: IApply<FArg, Partial<IAuthSessionRedirectUriOptions>>) => IUseAuthRequestBoxes<FValue, GoogleAuthRequest>;
64
+ useFacebookAuthRequest: (config?: IApply<FArg, Partial<IFacebookAuthRequestConfig>>, redirectUriOptions?: IApply<FArg, Partial<IAuthSessionRedirectUriOptions>>) => IUseAuthRequestBoxes<FValue, FacebookAuthRequest>;
65
+ };
66
+ export declare function createAuthRequestHooks<FResource extends IHkt, FValue extends IHkt, FArg extends IHkt>(kit: IAuthHooksKit<FResource, FValue, FArg>): IAuthRequestHooks<FValue, FArg>;
67
+ export type { IAnyKit as IAuthHooksAnyKit };
@@ -0,0 +1,75 @@
1
+ // The auth request hooks written once for every adapter whose hooks take getters (Vue, Solid,
2
+ // Svelte, Angular), React keeps its own wiring. An adapter's boxes (`ShallowRef`, `Accessor`,
3
+ // `{ current }`, `Signal`) are type-level functions here, since one hook returns several box types
4
+ import { AuthRequest } from './auth-request.js';
5
+ import { AUTH_CELL_CHANGE, createAutoDiscoveryController, createLoadedRequestController, createRequestResultController, } from './auth-request-controllers.js';
6
+ import { discovery as facebookDiscovery, FacebookAuthRequest as FacebookRequest, } from './providers/facebook.js';
7
+ import { createGoogleExchangeController } from './providers/google-exchange-controller.js';
8
+ import { discovery as googleDiscovery, GoogleAuthRequest as GoogleRequest, } from './providers/google.js';
9
+ import { resolveFacebookRequestSetup, resolveGoogleRequestSetup, toGoogleIdTokenConfig, } from './providers/provider-hook-setup.js';
10
+ /** The argument half of a kit for adapters whose arguments are plain getters */
11
+ export const GETTER_ARGS = {
12
+ toGetter: (arg) => arg,
13
+ constant: (value) => () => value,
14
+ };
15
+ function readAuthCell(cell) {
16
+ return cell.getSnapshot();
17
+ }
18
+ function valueHook(kit) {
19
+ return kit.createEventValueHook(AUTH_CELL_CHANGE, readAuthCell);
20
+ }
21
+ function discoveryOf(kit, getIssuer) {
22
+ const cell = kit.createResourceHook(createAutoDiscoveryController)(() => [
23
+ getIssuer(),
24
+ ]);
25
+ return valueHook(kit)(() => kit.readResource(cell));
26
+ }
27
+ function loadedRequestOf(kit, getConfig, getDiscovery, RequestClass) {
28
+ const useCell = kit.createResourceHook(() => createLoadedRequestController());
29
+ const cell = useCell(() => [getConfig(), getDiscovery(), RequestClass]);
30
+ return valueHook(kit)(() => kit.readResource(cell));
31
+ }
32
+ function resultOf(kit, getRequest, getDiscovery, getOptions) {
33
+ const useHolder = kit.createResourceHook(createRequestResultController);
34
+ const holder = useHolder(() => [getRequest(), getDiscovery(), getOptions()]);
35
+ const result = valueHook(kit)(() => kit.readResource(holder).result);
36
+ return [result, options => kit.readResource(holder).promptAsync(options)];
37
+ }
38
+ function baseRequestOf(kit, getConfig, getDiscovery) {
39
+ const request = loadedRequestOf(kit, getConfig, getDiscovery, AuthRequest);
40
+ const [result, promptAsync] = resultOf(kit, () => kit.readValue(request), getDiscovery, () => ({}));
41
+ return [request, result, promptAsync];
42
+ }
43
+ function googleRequestOf(kit, getConfig, getRedirectUriOptions) {
44
+ const getSetup = () => resolveGoogleRequestSetup(getConfig(), getRedirectUriOptions());
45
+ const request = loadedRequestOf(kit, () => getSetup().requestConfig, () => googleDiscovery, GoogleRequest);
46
+ const [result, promptAsync] = resultOf(kit, () => kit.readValue(request), () => googleDiscovery, () => ({}));
47
+ const useExchange = kit.createResourceHook(createGoogleExchangeController);
48
+ const exchanged = useExchange(() => [
49
+ kit.readValue(result),
50
+ kit.readValue(request),
51
+ getSetup().exchange,
52
+ ]);
53
+ const fullResult = valueHook(kit)(() => kit.readResource(exchanged));
54
+ return [request, fullResult, promptAsync];
55
+ }
56
+ function facebookRequestOf(kit, getConfig, getRedirectUriOptions) {
57
+ const request = loadedRequestOf(kit, () => resolveFacebookRequestSetup(getConfig(), getRedirectUriOptions()), () => facebookDiscovery, FacebookRequest);
58
+ const [result, promptAsync] = resultOf(kit, () => kit.readValue(request), () => facebookDiscovery, () => ({}));
59
+ return [request, result, promptAsync];
60
+ }
61
+ export function createAuthRequestHooks(kit) {
62
+ const noOptions = kit.constant({});
63
+ return {
64
+ useAutoDiscovery: issuer => discoveryOf(kit, kit.toGetter(issuer)),
65
+ useLoadedAuthRequest: (config, discovery, RequestClass) => loadedRequestOf(kit, kit.toGetter(config), kit.toGetter(discovery), RequestClass),
66
+ useAuthRequestResult: (request, discovery, customOptions = kit.constant({})) => resultOf(kit, kit.toGetter(request), kit.toGetter(discovery), kit.toGetter(customOptions)),
67
+ useAuthRequest: (config, discovery) => baseRequestOf(kit, kit.toGetter(config), kit.toGetter(discovery)),
68
+ useGoogleAuthRequest: (config = kit.constant({}), redirect = noOptions) => googleRequestOf(kit, kit.toGetter(config), kit.toGetter(redirect)),
69
+ useGoogleIdTokenAuthRequest: (config, redirect = noOptions) => {
70
+ const getConfig = kit.toGetter(config);
71
+ return googleRequestOf(kit, () => toGoogleIdTokenConfig(getConfig()), kit.toGetter(redirect));
72
+ },
73
+ useFacebookAuthRequest: (config = kit.constant({}), redirect = noOptions) => facebookRequestOf(kit, kit.toGetter(config), kit.toGetter(redirect)),
74
+ };
75
+ }
@@ -0,0 +1,29 @@
1
+ import type { IAuthDiscoveryDocument, IAuthRequestConfig, IAuthRequestPromptOptions, Prompt } from './auth-request.types';
2
+ import { CodeChallengeMethod, ResponseType } from './auth-request.types';
3
+ import type { IAuthSessionResult } from './auth-session.types';
4
+ /** Manages an authorization request, https://tools.ietf.org/html/rfc6749#section-4.1.1 */
5
+ export declare class AuthRequest implements Omit<IAuthRequestConfig, 'state'> {
6
+ /** Protection against Cross-Site Request Forgery. */
7
+ state: string;
8
+ url: string | null;
9
+ codeVerifier?: string;
10
+ codeChallenge?: string;
11
+ readonly responseType: ResponseType | string;
12
+ readonly clientId: string;
13
+ readonly extraParams: Record<string, string>;
14
+ readonly usePKCE?: boolean;
15
+ readonly codeChallengeMethod: CodeChallengeMethod;
16
+ readonly redirectUri: string;
17
+ readonly scopes?: string[];
18
+ readonly clientSecret?: string;
19
+ readonly prompt?: Prompt | Prompt[];
20
+ constructor(request: IAuthRequestConfig);
21
+ /** Loads and returns a valid auth request config, generating PKCE fields if needed. */
22
+ getAuthRequestConfigAsync(): Promise<IAuthRequestConfig>;
23
+ /** Prompts the user to authorize, opening a browser session for the auth URL. */
24
+ promptAsync(discovery: IAuthDiscoveryDocument, { url, ...options }?: IAuthRequestPromptOptions): Promise<IAuthSessionResult>;
25
+ parseReturnUrl(url: string): IAuthSessionResult;
26
+ /** Builds and caches the authorization URL. */
27
+ makeAuthUrlAsync(discovery: IAuthDiscoveryDocument): Promise<string>;
28
+ private ensureCodeIsSetupAsync;
29
+ }