@meshagent/meshagent-react-auth 0.36.2 → 0.37.0

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.
package/CHANGELOG.md CHANGED
@@ -1,3 +1,16 @@
1
+ ## [0.37.0]
2
+ - Breaking: Database client now supports `json`, `uuid`, `list`, and `struct` types with typed wrappers (DatabaseJson/DatabaseStruct/DatabaseExpression/DatabaseDate/DatabaseUuid); list/struct values must be wrapped and update now takes `values` only.
3
+ - Breaking: Containers build now streams build contexts (start/data chunks) with `mountPath`/`chunks` and removes `start_build`.
4
+ - Breaking: Toolkit/hosting refactor replaces RemoteToolkit with startHostedToolkit/HostedToolkit, removes ToolkitConfiguration and `supports_context`, and updates React/Tailwind helpers to start hosted toolkits.
5
+ - Participant tokens now include LLM grants and richer grant serialization (including allowed toolkits and extra payload preservation), and schema helpers add `json`/`uuid` data types.
6
+ - The JS SDK now exports its version constant for client-side visibility.
7
+
8
+ ## [0.36.3]
9
+ - Storage client now supports move operations and emits file moved events.
10
+ - Secrets client now supports existence checks.
11
+ - Project user add calls now omit permission fields unless explicitly set.
12
+ - Updated JS toolchain dependencies: esbuild 0.28.0, jest 30.3.0, mocha 12.0.0-beta-9.2, @tailwindcss/vite 4.2.2, @vitejs/plugin-react 6.0.1, vite 8.0.7.
13
+
1
14
  ## [0.36.2]
2
15
  - Breaking: Removed share-connect API from the TypeScript client (`connectShare` / RoomShareConnectionInfo).
3
16
  - OAuth helpers now default requested scope to `profile` (TS auth + React auth).
package/README.md CHANGED
@@ -1,3 +1,408 @@
1
1
  # Meshagent React Auth
2
2
 
3
- React helpers for Meshagent OAuth login, PKCE, token refresh, and auth state.
3
+ React helpers for browser OAuth login against Meshagent.
4
+
5
+ This package sits on top of `@meshagent/meshagent-ts-auth` and gives you React-friendly hooks for:
6
+
7
+ - starting a PKCE OAuth redirect
8
+ - exchanging the callback `code` for tokens
9
+ - refreshing access tokens before protected requests
10
+ - loading the current user profile into auth state
11
+ - subscribing to auth state from React components
12
+
13
+ It is designed for browser React apps and does not require React Query or a provider wrapper.
14
+
15
+ ## Install
16
+
17
+ ```sh
18
+ npm install @meshagent/meshagent-react-auth
19
+ ```
20
+
21
+ If your app also imports lower-level auth primitives or the core Meshagent client directly, add those as direct dependencies too:
22
+
23
+ ```sh
24
+ npm install @meshagent/meshagent-ts-auth @meshagent/meshagent
25
+ ```
26
+
27
+ ## Before You Get Started
28
+
29
+ Create an OAuth client in Meshagent Studio and register your callback URL.
30
+
31
+ 1. Sign in to https://studio.meshagent.com.
32
+ 2. Select your project
33
+ 3. Open "OAuth Clients" at the bottom of the left menu
34
+ 4. Click "New OAuth Client" button at the top
35
+ 5. Add
36
+ - name for the client id (e.g. "My App")
37
+ - enter "email" for the scope
38
+ - add redirect URIs (e.g. "http://localhost:3000/")
39
+ 6. Upon successful creation, you will see the client id and client secret. Click "Copy" button to copy the client id and client secret to clipboard. You will need them later when you run the app.
40
+
41
+
42
+ You will need these values throughout the examples:
43
+
44
+ - `oauthClientId`: the OAuth client ID from Meshagent Studio
45
+ - `callbackUrl`: the URL Meshagent redirects back to after login - this must match the redirect URI you registered in Studio
46
+
47
+ Optional
48
+ - `serverUrl`: your Meshagent API base URL, uses `https://api.meshagent.com` as default
49
+
50
+
51
+ ## What This Package Exports
52
+
53
+ - `useAuth`: main hook for login, callback handling, session refresh, and current-user loading
54
+ - `useLoginScope`: compatibility alias for the older signed-in hook API
55
+ - `useEnsureLogin`: compatibility alias for the older same-page login API
56
+ - `useMAuthResponse`: callback hook that exchanges the OAuth authorization code
57
+ - `useMeshagentAuth`: React subscription hook for auth state
58
+ - `buildOAuthAuthorizeUrl`: manual helper for custom login launches
59
+
60
+ ## Typical Flow
61
+
62
+ 1. Render your app normally. No auth provider wrapper is required.
63
+ 2. For the simplest same-page flow, run `useAuth(...)` and read auth state with `useMeshagentAuth()`.
64
+ 3. If you prefer a dedicated callback route, render `useMAuthResponse(...)` on that route and protect the rest of the app with `useAuth(...)`.
65
+ 4. Read live auth state anywhere with `useMeshagentAuth()`.
66
+
67
+ `useAuth(...)` defaults `autoSignIn` to `true`, so it will immediately launch the OAuth redirect when there is no session yet. Set `autoSignIn: false` when you want to show your own "Sign in" button.
68
+
69
+ ## Quick Start
70
+
71
+ ### Simplest Flow: `useAuth(...)`
72
+
73
+ Use `useAuth(...)` when you want the same component to handle both the initial login redirect and the callback return.
74
+
75
+ If you omit `callbackUrl`, it uses the current page as the callback URL and strips the OAuth query params after a successful return.
76
+
77
+ ```tsx
78
+ import {
79
+ useAuth,
80
+ useMeshagentAuth,
81
+ } from "@meshagent/meshagent-react-auth";
82
+
83
+ const oauthClientId = "YOUR_OAUTH_CLIENT_ID";
84
+
85
+ export function App(): JSX.Element {
86
+ const { failed, refreshing, refresh } = useAuth({
87
+ oauthClientId,
88
+ });
89
+ const auth = useMeshagentAuth();
90
+
91
+ if (refreshing) {
92
+ return <p>Signing you in...</p>;
93
+ }
94
+
95
+ if (failed) {
96
+ return (
97
+ <div>
98
+ <p>Authentication failed: {String(failed)}</p>
99
+ <button onClick={refresh}>Retry</button>
100
+ </div>
101
+ );
102
+ }
103
+
104
+ return <pre>{JSON.stringify(auth, null, 2)}</pre>;
105
+ }
106
+ ```
107
+
108
+ `useAuth(...)` uses the default shared `meshagentAuth` store by default, but it also accepts custom `auth` and `storage` instances when you need them. `useEnsureLogin(...)` remains available as a compatibility alias for this same-page flow.
109
+
110
+ ### 1. Handle The OAuth Callback
111
+
112
+ Use `useMAuthResponse(...)` on the route that matches your registered callback URL.
113
+
114
+ If you do not pass `authorizationCode`, the hook reads `?code=...` from `window.location`.
115
+
116
+ ```tsx
117
+ import { useEffect } from "react";
118
+
119
+ import { useMAuthResponse } from "@meshagent/meshagent-react-auth";
120
+
121
+ // callbackUrl must match redirect URI registered in Studio, e.g. "http://localhost:3000/auth/callback"
122
+ const callbackUrl = new URL("/auth/callback", window.location.origin);
123
+ const oauthClientId = "YOUR_OAUTH_CLIENT_ID";
124
+
125
+ export function AuthCallbackPage(): JSX.Element {
126
+ const { status, error } = useMAuthResponse({
127
+ callbackUrl,
128
+ oauthClientId,
129
+ });
130
+
131
+ useEffect(() => {
132
+ if (status === "success") {
133
+ window.location.replace("/");
134
+ }
135
+ }, [status]);
136
+
137
+ if (status === "idle") {
138
+ return <p>Missing authorization code.</p>;
139
+ }
140
+
141
+ if (status === "loading") {
142
+ return <p>Signing you in...</p>;
143
+ }
144
+
145
+ if (status === "error") {
146
+ return <p>Authentication failed: {error}</p>;
147
+ }
148
+
149
+ return <p>Login complete. Redirecting...</p>;
150
+ }
151
+ ```
152
+
153
+ After a successful exchange, the hook stores the access token, refresh token, expiration, and current user in the underlying auth store.
154
+
155
+ ### 2. Protect The Signed-In Part Of Your App
156
+
157
+ Use `useAuth(...)` anywhere you want to ensure that:
158
+
159
+ - the user has a session
160
+ - the access token is still valid enough
161
+ - the current user profile has been loaded into auth state
162
+
163
+ The example below disables auto-redirect so the UI can show manual sign-in buttons.
164
+
165
+ ```tsx
166
+ import {
167
+ useAuth,
168
+ useMeshagentAuth,
169
+ } from "@meshagent/meshagent-react-auth";
170
+ import { meshagentAuth } from "@meshagent/meshagent-ts-auth";
171
+
172
+ const callbackUrl = new URL("/auth/callback", window.location.origin);
173
+ const oauthClientId = "YOUR_OAUTH_CLIENT_ID";
174
+
175
+ export function ProtectedApp(): JSX.Element {
176
+ const {
177
+ failed,
178
+ isSigningIn,
179
+ refreshing,
180
+ signIn,
181
+ refresh,
182
+ user,
183
+ } = useAuth({
184
+ callbackUrl,
185
+ oauthClientId,
186
+ scope: "email",
187
+ autoSignIn: false,
188
+ });
189
+
190
+ if (refreshing) {
191
+ return <p>Checking session...</p>;
192
+ }
193
+
194
+ if (!user) {
195
+ return (
196
+ <div>
197
+ {failed ? <p>Authentication failed: {String(failed)}</p> : null}
198
+
199
+ <button onClick={() => void signIn()} disabled={isSigningIn}>
200
+ Sign in
201
+ </button>
202
+
203
+ <button onClick={() => void signIn("google")} disabled={isSigningIn}>
204
+ Continue with Google
205
+ </button>
206
+
207
+ <button onClick={refresh} disabled={isSigningIn}>
208
+ Retry
209
+ </button>
210
+ </div>
211
+ );
212
+ }
213
+
214
+ return <AuthenticatedHome />;
215
+ }
216
+
217
+ function AuthenticatedHome(): JSX.Element {
218
+ const auth = useMeshagentAuth();
219
+
220
+ return (
221
+ <div>
222
+ <p>Signed in.</p>
223
+ <pre>{JSON.stringify(auth, null, 2)}</pre>
224
+ <button onClick={() => meshagentAuth.signOut()}>Sign out</button>
225
+ </div>
226
+ );
227
+ }
228
+ ```
229
+
230
+ If you want automatic redirect instead of a sign-in screen, omit `autoSignIn`:
231
+
232
+ ```tsx
233
+ const auth = useAuth({ callbackUrl, oauthClientId });
234
+ ```
235
+
236
+ When there is no session, that hook will immediately start the OAuth redirect flow.
237
+
238
+ ### 3. Read Auth State Anywhere
239
+
240
+ `useMeshagentAuth()` is the smallest hook in the package. It subscribes to the underlying auth store and returns the current snapshot:
241
+
242
+ ```tsx
243
+ import { useMeshagentAuth } from "@meshagent/meshagent-react-auth";
244
+
245
+ export function SessionDebugPanel(): JSX.Element {
246
+ const auth = useMeshagentAuth();
247
+
248
+ return <pre>{JSON.stringify(auth, null, 2)}</pre>;
249
+ }
250
+ ```
251
+
252
+ The returned snapshot has this shape:
253
+
254
+ ```ts
255
+ {
256
+ accessToken: string | null;
257
+ refreshToken: string | null;
258
+ expiration: Date | null;
259
+ user: Record<string, unknown> | null;
260
+ }
261
+ ```
262
+
263
+ `user` is intentionally application-specific. Model it in your app instead of assuming fixed fields in shared code.
264
+
265
+ ## Example: Manual Authorize URL
266
+
267
+ Use `buildOAuthAuthorizeUrl(...)` when you want manual control over when and how the browser is redirected.
268
+
269
+ This helper also saves the PKCE verifier so that `useMAuthResponse(...)` can complete the callback exchange later.
270
+
271
+ ```tsx
272
+ import { buildOAuthAuthorizeUrl } from "@meshagent/meshagent-react-auth";
273
+
274
+ const serverUrl = "https://api.meshagent.com";
275
+ const callbackUrl = new URL("/auth/callback", window.location.origin);
276
+ const oauthClientId = "YOUR_OAUTH_CLIENT_ID";
277
+
278
+ export async function signInWithGoogle(): Promise<void> {
279
+ const authorizeUrl = await buildOAuthAuthorizeUrl({
280
+ serverUrl,
281
+ callbackUrl,
282
+ oauthClientId,
283
+ scope: "email",
284
+ provider: "google",
285
+ extraQueryParams: {
286
+ invitation: "abc123",
287
+ },
288
+ });
289
+
290
+ window.location.assign(authorizeUrl.toString());
291
+ }
292
+ ```
293
+
294
+ If you pass custom `auth` or `storage`, use the same instances in both the login-launch side and the callback side.
295
+ Passing `storage` alone changes PKCE verifier storage. Session persistence follows the `auth` instance you use.
296
+
297
+ ## API Reference
298
+
299
+ ### `useAuth(options)`
300
+
301
+ Primary auth hook for browser login flows.
302
+
303
+ What it does:
304
+
305
+ - launches the browser OAuth redirect when there is no session yet
306
+ - exchanges the callback `code` when the current URL matches the callback URL
307
+ - loads the current user into auth state
308
+ - refreshes the access token when needed before protected requests
309
+ - signs out automatically if token refresh fails
310
+ - signs out automatically if profile loading returns `401` or `403`
311
+ - strips OAuth query params from the current URL after a successful callback exchange
312
+ - dedupes duplicate effect runs for the same login attempt
313
+
314
+ Important options:
315
+
316
+ - `oauthClientId`: required OAuth client ID
317
+ - `serverUrl`: optional Meshagent base URL, defaults to `https://api.meshagent.com`
318
+ - `callbackUrl`: optional callback URL, defaults to the current page
319
+ - `scope`: optional OAuth scope, defaults to `"profile"`
320
+ - `provider`: optional provider slug to pass through to `/oauth/authorize`
321
+ - `extraQueryParams`: optional extra query params for `/oauth/authorize`
322
+ - `storage`: optional custom storage for PKCE verifier caching
323
+ - `auth`: optional custom `MeshagentAuth` instance
324
+ - `autoSignIn`: optional, defaults to `true`
325
+
326
+ Returned fields:
327
+
328
+ | Field | Meaning |
329
+ | --- | --- |
330
+ | `failed` | Most recent error, or `null` |
331
+ | `isCancelled` | Currently always `false` in this browser redirect implementation |
332
+ | `isSigningIn` | `true` while a sign-in launch is in progress |
333
+ | `refreshing` | `true` while callback exchange, session validation, or profile loading is running |
334
+ | `user` | Current user from auth state, or `null` |
335
+ | `signIn(provider?)` | Starts the OAuth redirect, optionally forcing a provider |
336
+ | `refresh()` | Re-runs the auth flow |
337
+ | `isLoginLaunched` | Useful for deciding when to show login or retry UI |
338
+
339
+ `useAuth(...)` replaces the overlap between `useEnsureLogin(...)` and `useLoginScope(...)`. Both legacy names still exist as wrappers for compatibility.
340
+
341
+ ### Legacy Aliases
342
+
343
+ `useEnsureLogin(options)` is a compatibility wrapper over `useAuth(...)` for the old same-page login API. It still returns:
344
+
345
+ | Field | Meaning |
346
+ | --- | --- |
347
+ | `status` | `"loading"`, `"success"`, or `"error"` |
348
+ | `error` | Error message string, or `null` |
349
+ | `refresh()` | Re-runs the auth flow |
350
+
351
+ `useLoginScope(options)` is a compatibility wrapper over `useAuth(...)` for the older signed-in hook API. It returns the same fields as `useAuth(...)`, while keeping `callbackUrl` required and preserving its previous default `scope` behavior.
352
+
353
+ ### `useMAuthResponse(props)`
354
+
355
+ Hook for your OAuth callback route.
356
+
357
+ What it does:
358
+
359
+ - reads the authorization code from `authorizationCode` or the current URL
360
+ - exchanges the code at `/oauth/token`
361
+ - stores access token, refresh token, and expiration in auth state
362
+ - loads the current user profile
363
+ - stores that user in auth state
364
+
365
+ It does not navigate away after success. Redirect to the rest of your app yourself.
366
+ If you used custom storage for the PKCE verifier during login launch, pass that same `storage` here.
367
+
368
+ Return value:
369
+
370
+ | `status` | Meaning |
371
+ | --- | --- |
372
+ | `"idle"` | No authorization code was found |
373
+ | `"loading"` | Token exchange is in progress |
374
+ | `"success"` | Exchange and profile load completed |
375
+ | `"error"` | Exchange failed |
376
+
377
+ The returned `error` is the message string from the failed query, or `null`.
378
+
379
+ ### `useMeshagentAuth(auth?)`
380
+
381
+ Subscribes to a `MeshagentAuth` store with `useSyncExternalStore` and returns the current snapshot.
382
+
383
+ Use this anywhere you want React components to update when tokens, expiration, or user state changes.
384
+
385
+ ### `buildOAuthAuthorizeUrl(params)`
386
+
387
+ Builds the `/oauth/authorize` URL and stores a PKCE verifier before redirect.
388
+
389
+ Supported parameters:
390
+
391
+ - `serverUrl`
392
+ - `callbackUrl`
393
+ - `oauthClientId`
394
+ - `scope`
395
+ - `provider`
396
+ - `extraQueryParams`
397
+ - `storage`
398
+
399
+ Use this helper when the default `signIn()` behavior is too opinionated and you need manual redirect control.
400
+ `storage` here controls where the PKCE verifier is cached before the redirect.
401
+
402
+ ## Notes
403
+
404
+ - This package is for browser redirect-based OAuth flows. `useAuth(...)` and its legacy aliases will throw if they try to launch login outside a browser environment.
405
+ - No provider wrapper is required. `useAuth(...)`, `useEnsureLogin(...)`, `useLoginScope(...)`, and `useMAuthResponse(...)` manage their own async state.
406
+ - `useMAuthResponse(...)` does not clear the `code` query parameter or navigate after success. Most apps should redirect away from the callback page after `status === "success"`.
407
+ - Sign-out functionality lives in `@meshagent/meshagent-ts-auth`, for example `meshagentAuth.signOut()`.
408
+ - If you need the low-level primitives directly, see `@meshagent/meshagent-ts-auth`. This package is the React layer on top of it.
@@ -1,4 +1,5 @@
1
- export { QueryClient, QueryClientProvider } from "@tanstack/react-query";
2
1
  export * from "./mauth-response";
2
+ export * from "./use-auth";
3
+ export * from "./use-ensure-login";
3
4
  export * from "./use-login-scope";
4
5
  export * from "./use-meshagent-auth";
package/dist/cjs/index.js CHANGED
@@ -14,10 +14,8 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
14
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
- exports.QueryClientProvider = exports.QueryClient = void 0;
18
- var react_query_1 = require("@tanstack/react-query");
19
- Object.defineProperty(exports, "QueryClient", { enumerable: true, get: function () { return react_query_1.QueryClient; } });
20
- Object.defineProperty(exports, "QueryClientProvider", { enumerable: true, get: function () { return react_query_1.QueryClientProvider; } });
21
17
  __exportStar(require("./mauth-response"), exports);
18
+ __exportStar(require("./use-auth"), exports);
19
+ __exportStar(require("./use-ensure-login"), exports);
22
20
  __exportStar(require("./use-login-scope"), exports);
23
21
  __exportStar(require("./use-meshagent-auth"), exports);
@@ -1,10 +1,18 @@
1
- import type { MAuthResponseProps } from "@meshagent/meshagent-ts-auth";
1
+ import type { StorageLike, MeshagentAuth } from "@meshagent/meshagent-ts-auth";
2
2
  declare enum UseMAuthResponseStatus {
3
3
  Idle = "idle",
4
4
  Loading = "loading",
5
5
  Success = "success",
6
6
  Error = "error"
7
7
  }
8
+ export interface MAuthResponseProps {
9
+ serverUrl?: string | URL;
10
+ callbackUrl: string | URL;
11
+ oauthClientId: string;
12
+ authorizationCode?: string;
13
+ storage?: StorageLike;
14
+ auth?: MeshagentAuth;
15
+ }
8
16
  export interface MAuthResponse {
9
17
  status: UseMAuthResponseStatus;
10
18
  error: string | null;
@@ -1,11 +1,13 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.useMAuthResponse = useMAuthResponse;
4
- const react_query_1 = require("@tanstack/react-query");
4
+ const react_1 = require("react");
5
5
  const meshagent_ts_auth_1 = require("@meshagent/meshagent-ts-auth");
6
+ const reference_key_1 = require("./reference-key");
6
7
  function toUrl(value) {
7
8
  return value instanceof URL ? value : new URL(value);
8
9
  }
10
+ const inFlightTokenExchanges = new Map();
9
11
  var UseMAuthResponseStatus;
10
12
  (function (UseMAuthResponseStatus) {
11
13
  UseMAuthResponseStatus["Idle"] = "idle";
@@ -13,50 +15,88 @@ var UseMAuthResponseStatus;
13
15
  UseMAuthResponseStatus["Success"] = "success";
14
16
  UseMAuthResponseStatus["Error"] = "error";
15
17
  })(UseMAuthResponseStatus || (UseMAuthResponseStatus = {}));
16
- function toStatus(hasAuthCode, tokenExchangeQuery) {
17
- if (!hasAuthCode) {
18
- return UseMAuthResponseStatus.Idle;
19
- }
20
- if (tokenExchangeQuery.isPending) {
21
- return UseMAuthResponseStatus.Loading;
22
- }
23
- if (tokenExchangeQuery.isError) {
24
- return UseMAuthResponseStatus.Error;
18
+ function buildTokenExchangeKey(params) {
19
+ return JSON.stringify([
20
+ params.serverUrl,
21
+ params.callbackUrl,
22
+ params.oauthClientId,
23
+ params.code,
24
+ params.storageKey,
25
+ params.authKey,
26
+ ]);
27
+ }
28
+ function toErrorMessage(error) {
29
+ if (error instanceof Error) {
30
+ return error.message;
25
31
  }
26
- return UseMAuthResponseStatus.Success;
32
+ return String(error);
27
33
  }
28
34
  ;
29
35
  function useMAuthResponse({ serverUrl, callbackUrl, oauthClientId, authorizationCode, storage, auth, }) {
36
+ const serverUrlKey = toUrl(serverUrl ?? "http://api.meshcentral.com").toString();
37
+ const callbackUrlKey = toUrl(callbackUrl).toString();
30
38
  const code = authorizationCode ?? (0, meshagent_ts_auth_1.getAuthorizationCode)() ?? undefined;
31
- const tokenExchangeQuery = (0, react_query_1.useQuery)({
32
- queryKey: [
33
- "mauth-response",
34
- toUrl(serverUrl).toString(),
35
- toUrl(callbackUrl).toString(),
39
+ const [status, setStatus] = (0, react_1.useState)(code === undefined ? UseMAuthResponseStatus.Idle : UseMAuthResponseStatus.Loading);
40
+ const [error, setError] = (0, react_1.useState)(null);
41
+ const tokenExchangeKey = code === undefined
42
+ ? null
43
+ : buildTokenExchangeKey({
44
+ serverUrl: serverUrlKey,
45
+ callbackUrl: callbackUrlKey,
36
46
  oauthClientId,
37
47
  code,
38
- ],
39
- enabled: code !== undefined,
40
- retry: false,
41
- refetchOnReconnect: false,
42
- refetchOnWindowFocus: false,
43
- queryFn: async ({ signal }) => {
44
- if (code === undefined) {
45
- throw new Error("Missing authorization code");
46
- }
47
- await (0, meshagent_ts_auth_1.exchangeToken)({
48
- serverUrl,
48
+ storageKey: (0, reference_key_1.getReferenceKey)(storage, "default-storage"),
49
+ authKey: (0, reference_key_1.getReferenceKey)(auth, "default-auth"),
50
+ });
51
+ (0, react_1.useEffect)(() => {
52
+ if (code === undefined || tokenExchangeKey === null) {
53
+ setStatus(UseMAuthResponseStatus.Idle);
54
+ setError(null);
55
+ return;
56
+ }
57
+ let cancelled = false;
58
+ setStatus(UseMAuthResponseStatus.Loading);
59
+ setError(null);
60
+ let tokenExchange = inFlightTokenExchanges.get(tokenExchangeKey);
61
+ if (!tokenExchange) {
62
+ tokenExchange = (0, meshagent_ts_auth_1.exchangeToken)({
63
+ serverUrl: serverUrlKey,
49
64
  callbackUrl,
50
65
  oauthClientId,
51
66
  authorizationCode: code,
52
67
  storage,
53
68
  auth,
54
- }, signal);
55
- return null;
56
- },
57
- });
58
- return {
59
- status: toStatus(code !== undefined, tokenExchangeQuery),
60
- error: tokenExchangeQuery.error?.message ?? null,
61
- };
69
+ }).finally(() => {
70
+ inFlightTokenExchanges.delete(tokenExchangeKey);
71
+ });
72
+ inFlightTokenExchanges.set(tokenExchangeKey, tokenExchange);
73
+ }
74
+ tokenExchange
75
+ .then(() => {
76
+ if (cancelled) {
77
+ return;
78
+ }
79
+ setStatus(UseMAuthResponseStatus.Success);
80
+ setError(null);
81
+ })
82
+ .catch((exchangeError) => {
83
+ if (cancelled) {
84
+ return;
85
+ }
86
+ setStatus(UseMAuthResponseStatus.Error);
87
+ setError(toErrorMessage(exchangeError));
88
+ });
89
+ return () => {
90
+ cancelled = true;
91
+ };
92
+ }, [
93
+ auth,
94
+ callbackUrl,
95
+ code,
96
+ oauthClientId,
97
+ serverUrl,
98
+ storage,
99
+ tokenExchangeKey,
100
+ ]);
101
+ return { status, error };
62
102
  }
@@ -0,0 +1 @@
1
+ export declare function getReferenceKey(value: object | null | undefined, fallback: string): string;
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getReferenceKey = getReferenceKey;
4
+ const objectIds = new WeakMap();
5
+ let nextObjectId = 1;
6
+ function getReferenceKey(value, fallback) {
7
+ if (!value) {
8
+ return fallback;
9
+ }
10
+ const existing = objectIds.get(value);
11
+ if (existing !== undefined) {
12
+ return `${fallback}:${existing}`;
13
+ }
14
+ const id = nextObjectId;
15
+ nextObjectId += 1;
16
+ objectIds.set(value, id);
17
+ return `${fallback}:${id}`;
18
+ }
@@ -0,0 +1,35 @@
1
+ import { MeshagentAuth } from "@meshagent/meshagent-ts-auth";
2
+ import type { StorageLike, MeshagentUser } from "@meshagent/meshagent-ts-auth";
3
+ export declare function buildOAuthAuthorizeUrl(params: {
4
+ serverUrl: string | URL;
5
+ callbackUrl: string | URL;
6
+ oauthClientId: string;
7
+ scope?: string;
8
+ provider?: string | null;
9
+ extraQueryParams?: Record<string, string>;
10
+ storage?: StorageLike;
11
+ }): Promise<URL>;
12
+ export interface AuthRenderProps {
13
+ failed: unknown;
14
+ isCancelled: boolean;
15
+ isSigningIn: boolean;
16
+ refreshing: boolean;
17
+ user: MeshagentUser | null;
18
+ signIn: (provider?: string | null) => Promise<void>;
19
+ refresh: () => void;
20
+ }
21
+ export interface UseAuthProps {
22
+ serverUrl?: string | URL;
23
+ callbackUrl?: string | URL;
24
+ oauthClientId: string;
25
+ scope?: string;
26
+ provider?: string;
27
+ extraQueryParams?: Record<string, string>;
28
+ storage?: StorageLike;
29
+ auth?: MeshagentAuth;
30
+ autoSignIn?: boolean;
31
+ }
32
+ export interface UseAuthResult extends AuthRenderProps {
33
+ isLoginLaunched: boolean;
34
+ }
35
+ export declare function useAuth(props: UseAuthProps): UseAuthResult;