@ratespecial/logto-angular 1.0.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/README.md ADDED
@@ -0,0 +1,507 @@
1
+ # @ratespecial/logto-angular
2
+
3
+ An Angular 21+ library that wraps [`@logto/browser`](https://docs.logto.io/sdk/browser) into an Angular-idiomatic authentication layer: a facade service, a route guard, two HTTP interceptors, callback and signed-out components, route-history tracking, a provider factory, and a test helper.
4
+
5
+ ---
6
+
7
+ ## Overview
8
+
9
+ - **Angular 21+ standalone** — no NgModules required.
10
+ - **Peer dependency on `@logto/browser` ^3.0.13** — one SDK client per app, shared across all resources.
11
+ - **Injection-token driven** — every configurable value flows through DI; no static singletons.
12
+ - **Signals + OnPush** — components use Angular signals; no Zone.js pressure.
13
+ - **Secondary entry point** — `@ratespecial/logto-angular/testing` ships test helpers separately so they are never bundled in production.
14
+
15
+ ### Requirements
16
+
17
+ | Dependency | Version |
18
+ |---|---|
19
+ | `@angular/core` | ^21.2.0 |
20
+ | `@angular/common` | ^21.2.0 |
21
+ | `@angular/router` | ^21.2.0 |
22
+ | `@logto/browser` | ^3.0.13 |
23
+ | `rxjs` | ~7.8.0 |
24
+
25
+ ---
26
+
27
+ ## Installation
28
+
29
+ ```bash
30
+ npm install @ratespecial/logto-angular @logto/browser
31
+ ```
32
+
33
+ Or with Yarn:
34
+
35
+ ```bash
36
+ yarn add @ratespecial/logto-angular @logto/browser
37
+ ```
38
+
39
+ ---
40
+
41
+ ## Quick start
42
+
43
+ ### 1. Wire providers in `app.config.ts`
44
+
45
+ ```ts
46
+ import {APP_INITIALIZER, ApplicationConfig, provideAppInitializer} from '@angular/core';
47
+ import {provideHttpClient, withInterceptors} from '@angular/common/http';
48
+ import {provideRouter} from '@angular/router';
49
+ import {
50
+ provideLogtoAuth,
51
+ logtoTokenInterceptor,
52
+ logoutOnUnauthInterceptor,
53
+ initializeRouteTracking,
54
+ } from '@ratespecial/logto-angular';
55
+ import {environment} from './environments/environment';
56
+ import {routes} from './app.routes';
57
+
58
+ export const appConfig: ApplicationConfig = {
59
+ providers: [
60
+ provideRouter(routes),
61
+
62
+ provideHttpClient(
63
+ withInterceptors([
64
+ logtoTokenInterceptor, // attaches Bearer tokens to matched routes
65
+ logoutOnUnauthInterceptor, // logs out on 401
66
+ ]),
67
+ ),
68
+
69
+ provideLogtoAuth({
70
+ ...environment.logto,
71
+ logoutHookFactories: [
72
+ // optional: runs inside injection context, so inject() works here
73
+ // () => {
74
+ // const store = inject(Store);
75
+ // return () => store.dispatch(new ClearState());
76
+ // },
77
+ ],
78
+ }),
79
+
80
+ // Track routes so the user returns to where they were after login
81
+ provideAppInitializer(initializeRouteTracking()),
82
+ ],
83
+ };
84
+ ```
85
+
86
+ ### 2. Add auth routes in `app.routes.ts`
87
+
88
+ ```ts
89
+ import {Routes} from '@angular/router';
90
+ import {getAuthRoutes, authGuard} from '@ratespecial/logto-angular';
91
+ import {environment} from './environments/environment';
92
+
93
+ export const routes: Routes = [
94
+ {path: '', pathMatch: 'full', redirectTo: '/dashboard'},
95
+
96
+ // Spread the callback + signed-out routes from the lib
97
+ ...getAuthRoutes(environment.logto.routing),
98
+
99
+ // Protect your app routes
100
+ {
101
+ path: 'dashboard',
102
+ loadComponent: () => import('./dashboard/dashboard.component'),
103
+ canActivate: [authGuard],
104
+ },
105
+ ];
106
+ ```
107
+
108
+ ### 3. Define the environment config
109
+
110
+ ```ts
111
+ // src/environments/environment.ts
112
+ import {UserScope} from '@logto/browser';
113
+ import {LogtoAuthConfig} from '@ratespecial/logto-angular';
114
+
115
+ export const environment = {
116
+ logto: {
117
+ endpoint: 'https://your-tenant.logto.app',
118
+ appId: 'your-app-id',
119
+ scopes: [UserScope.Email, UserScope.Profile, 'app:read'],
120
+ resources: ['https://api.yourapp.com'],
121
+ routing: {
122
+ callbackPath: '/auth/callback',
123
+ signedOutPath: '/auth/signed-out',
124
+ primaryResource: 'https://api.yourapp.com',
125
+ secureRoutes: [
126
+ {resource: 'https://api.yourapp.com', routes: ['/api']},
127
+ ],
128
+ },
129
+ } satisfies LogtoAuthConfig,
130
+ };
131
+ ```
132
+
133
+ ---
134
+
135
+ ## Configuration reference
136
+
137
+ ### `LogtoAuthConfig`
138
+
139
+ Extends `@logto/browser`'s native `LogtoConfig` with two extra fields:
140
+
141
+ | Field | Type | Required | Description |
142
+ |---|---|---|---|
143
+ | `endpoint` | `string` | Yes | Your Logto tenant URL. |
144
+ | `appId` | `string` | Yes | Application ID from the Logto console. |
145
+ | `scopes` | `string[]` | No | OIDC scopes to request. |
146
+ | `resources` | `string[]` | No | API resource indicators registered in Logto. |
147
+ | `routing` | `LogtoRoutingConfig` | Yes | Angular routing/behavior addon (see below). |
148
+ | `noAccessMessage` | `string` | No | Error shown when the user authenticates but has no scopes on the primary resource. |
149
+
150
+ ### `LogtoRoutingConfig`
151
+
152
+ | Field | Type | Required | Description |
153
+ |---|---|---|---|
154
+ | `callbackPath` | `string` | Yes | Path Logto redirects to after sign-in (e.g. `/auth/callback`). |
155
+ | `signedOutPath` | `string` | Yes | Path shown after sign-out (e.g. `/auth/signed-out`). |
156
+ | `primaryResource` | `string` | No | The primary API resource indicator. Defaults to the first `secureRoutes` resource. Used for the scope-access gate in `CallbackComponent` and by consumers that need a specific resource token. |
157
+ | `secureRoutes` | `SecureRouteMapping[]` | Yes | Maps request URLs to resource tokens for the HTTP interceptor. |
158
+
159
+ ### `SecureRouteMapping`
160
+
161
+ | Field | Type | Description |
162
+ |---|---|---|
163
+ | `resource` | `string` | Logto API resource indicator to fetch a token for. |
164
+ | `routes` | `string[]` | Request URL prefixes (matched with `startsWith`) that require this resource's token. |
165
+
166
+ ### `LogtoAuthOptions`
167
+
168
+ Passed to `provideLogtoAuth()`. Extends `LogtoAuthConfig` with:
169
+
170
+ | Field | Type | Description |
171
+ |---|---|---|
172
+ | `logoutHookFactories` | `Array<() => AuthLogoutHook>` | Factories that run inside an injection context and return a hook to call at logout time. |
173
+
174
+ ---
175
+
176
+ ## Feature guide
177
+
178
+ ### `AuthService`
179
+
180
+ A singleton service (provided in root) that owns the authenticated state and all Logto operations.
181
+
182
+ ```ts
183
+ import {AuthService} from '@ratespecial/logto-angular';
184
+
185
+ @Component({...})
186
+ export class MyComponent {
187
+ private auth = inject(AuthService);
188
+
189
+ constructor() {
190
+ // Subscribe to auth state changes
191
+ this.auth.isAuthenticated$.subscribe(authenticated => {
192
+ console.log('authenticated?', authenticated);
193
+ });
194
+ }
195
+ }
196
+ ```
197
+
198
+ **Public API:**
199
+
200
+ | Method/Property | Return | Description |
201
+ |---|---|---|
202
+ | `isAuthenticated$` | `Observable<boolean>` | Replay-current stream; `distinctUntilChanged`. |
203
+ | `refreshAuthState()` | `Promise<boolean>` | Reads session from storage (no network); pushes result to stream. |
204
+ | `signIn(redirectUri?)` | `void` | Full-page redirect to Logto hosted UI. Default redirect URI is built from `callbackPath`. |
205
+ | `handleCallback(callbackUri)` | `Promise<void>` | Complete the OIDC callback, then refresh auth state. |
206
+ | `getAccessToken(resource?)` | `Promise<string>` | Resource-scoped JWT; client refreshes/caches transparently. |
207
+ | `getAccessTokenClaims(resource?)` | `Promise<AccessTokenClaims>` | Decoded claims of the resource token (e.g. `scope`). |
208
+ | `getIdTokenClaims()` | `Promise<IdTokenClaims>` | Decoded claims of the ID token (e.g. `sub`, `name`, `email`). |
209
+ | `logout()` | `void` | Fires hooks, emits `false`, calls `signOut`, navigates to `signedOutPath` on error. |
210
+
211
+ ### `authGuard`
212
+
213
+ A functional `CanActivateFn` that checks for an active Logto session and redirects to the login flow when absent.
214
+
215
+ **Behavior:**
216
+
217
+ 1. Calls `AuthService.refreshAuthState()`.
218
+ 2. If authenticated, returns `true` (allows navigation).
219
+ 3. If not authenticated:
220
+ - Saves the attempted URL via `HistoryService.setLastVisitedRoute()` (skipped for `/auth/*` paths to avoid loops).
221
+ - Calls `AuthService.signIn()` (full-page redirect).
222
+ - Returns `false`.
223
+
224
+ After sign-in, `CallbackComponent` restores navigation to the saved URL via `HistoryService.consumeLastVisitedRoute()`.
225
+
226
+ ```ts
227
+ // app.routes.ts
228
+ {
229
+ path: 'dashboard',
230
+ canActivate: [authGuard],
231
+ loadComponent: () => import('./dashboard/dashboard.component'),
232
+ },
233
+ ```
234
+
235
+ ### `logtoTokenInterceptor`
236
+
237
+ Attaches a resource-scoped `Bearer` token to outgoing HTTP requests that match a configured route prefix.
238
+
239
+ **How it works:**
240
+
241
+ - Reads `LOGTO_AUTH_CONFIG.routing.secureRoutes` at intercept time.
242
+ - Calls `resourceForUrl(req.url, secureRoutes)` to find the first mapping whose `routes` entry matches the request URL via `startsWith`.
243
+ - If a resource matches, calls `AuthService.getAccessToken(resource)` (the Logto SDK refreshes/caches the token).
244
+ - Adds `Authorization: Bearer <token>` and forwards the cloned request.
245
+ - If no resource matches, or the token is empty, the request passes through unchanged.
246
+
247
+ **Using `resourceForUrl` standalone:**
248
+
249
+ ```ts
250
+ import {resourceForUrl} from '@ratespecial/logto-angular';
251
+
252
+ const resource = resourceForUrl('/api/v2/users', secureRoutes);
253
+ // 'https://api.yourapp.com' (or undefined)
254
+ ```
255
+
256
+ **Multi-resource example:**
257
+
258
+ ```ts
259
+ routing: {
260
+ secureRoutes: [
261
+ {resource: 'https://api.yourapp.com', routes: ['/api']},
262
+ {resource: 'https://billing.yourapp.com', routes: ['/billing', '/payments']},
263
+ ],
264
+ }
265
+ ```
266
+
267
+ Requests to `/api/...` get a token for `https://api.yourapp.com`; requests to `/billing/...` or `/payments/...` get a token for `https://billing.yourapp.com`.
268
+
269
+ ### `logoutOnUnauthInterceptor`
270
+
271
+ Calls `AuthService.logout()` whenever any HTTP response returns `401 Unauthorized`.
272
+
273
+ ```ts
274
+ provideHttpClient(
275
+ withInterceptors([logtoTokenInterceptor, logoutOnUnauthInterceptor]),
276
+ ),
277
+ ```
278
+
279
+ Order matters: place `logtoTokenInterceptor` first so the token is attached before the response interceptor evaluates it.
280
+
281
+ ### `CallbackComponent` and `SignedOutComponent`
282
+
283
+ These are registered automatically by `getAuthRoutes()`:
284
+
285
+ - **`CallbackComponent`** (`lib-callback`): Handles the OIDC redirect. Calls `handleCallback`, checks that the primary resource token carries scopes (access gate), then navigates to the restored route or `/`. Displays a spinner while loading and an error message on failure or no-access.
286
+ - **`SignedOutComponent`** (`lib-signed-out`): Shows a "Signed out" card with a "Sign in again" button that restarts the Logto flow.
287
+
288
+ Both use `ChangeDetectionStrategy.OnPush` and signals.
289
+
290
+ ### `HistoryService`
291
+
292
+ Persists the last visited route in `sessionStorage` across the OIDC redirect round-trip.
293
+
294
+ ```ts
295
+ import {HistoryService} from '@ratespecial/logto-angular';
296
+
297
+ @Injectable({providedIn: 'root'})
298
+ export class MyService {
299
+ private history = inject(HistoryService);
300
+ }
301
+ ```
302
+
303
+ | Method | Description |
304
+ |---|---|
305
+ | `setLastVisitedRoute(route)` | Writes to `sessionStorage` (key: `auth.lastVisitedRoute`). |
306
+ | `getLastVisitedRoute()` | Reads from `sessionStorage`; returns `null` if absent. |
307
+ | `clearLastVisitedRoute()` | Removes the stored value. |
308
+ | `consumeLastVisitedRoute()` | Reads then clears in one call. Used by `CallbackComponent`. |
309
+
310
+ All methods swallow `sessionStorage` errors (e.g. quota exceeded) and log them to `console.error`.
311
+
312
+ ### `initializeRouteTracking`
313
+
314
+ An `APP_INITIALIZER` factory that subscribes to `Router.events` and records each non-auth route via `HistoryService`. Routes starting with `/auth` are excluded to prevent redirect loops.
315
+
316
+ ```ts
317
+ import {provideAppInitializer} from '@angular/core';
318
+ import {initializeRouteTracking} from '@ratespecial/logto-angular';
319
+
320
+ providers: [
321
+ provideAppInitializer(initializeRouteTracking()),,
322
+ ]
323
+ ```
324
+
325
+ ### Logout hooks
326
+
327
+ Register side-effects that run at the start of `AuthService.logout()` before the sign-out redirect. Common uses: clearing NGXS/NgRx state, flushing caches, firing telemetry events.
328
+
329
+ Each factory receives an injection context (DI works inside it), and returns an `AuthLogoutHook` — a function that returns `void` or `Observable<unknown>`. Async hooks are fire-and-forget.
330
+
331
+ **Via `provideLogtoAuth` (recommended):**
332
+
333
+ ```ts
334
+ provideLogtoAuth({
335
+ ...environment.logto,
336
+ logoutHookFactories: [
337
+ () => {
338
+ const store = inject(Store); // inject() works here
339
+ return () => store.dispatch(new ClearState()); // returns Observable
340
+ },
341
+ () => {
342
+ const cache = inject(CacheService);
343
+ return () => cache.clear(); // returns void
344
+ },
345
+ ],
346
+ })
347
+ ```
348
+
349
+ **Direct token registration (advanced):**
350
+
351
+ ```ts
352
+ {provide: AUTH_LOGOUT_HOOK, multi: true, useFactory: () => {
353
+ const store = inject(Store);
354
+ return () => store.dispatch(new ClearState());
355
+ }}
356
+ ```
357
+
358
+ ### Injection tokens
359
+
360
+ | Token | Type | Provided by | Consumed by |
361
+ |---|---|---|---|
362
+ | `LOGTO_AUTH_CONFIG` | `LogtoAuthConfig` | `provideLogtoAuth()` | `AuthService`, `logtoTokenInterceptor`, `CallbackComponent` |
363
+ | `LOGTO_CLIENT` | `LogtoClient` | `provideLogtoAuth()` | `AuthService` |
364
+ | `PRIMARY_RESOURCE` | `string` | `provideLogtoAuth()` | `CallbackComponent`, app components needing a specific token |
365
+ | `AUTH_LOGOUT_HOOK` | `AuthLogoutHook[]` | `provideLogtoAuth()` (multi) | `AuthService.logout()` |
366
+
367
+ ---
368
+
369
+ ## Testing
370
+
371
+ Use `provideLogtoTesting()` from the secondary entry point to inject test doubles without a real Logto setup.
372
+
373
+ ```ts
374
+ import {provideLogtoTesting} from '@ratespecial/logto-angular/testing';
375
+ ```
376
+
377
+ **Default behaviour:** the stub client always returns unauthenticated with an empty access token and empty scope claims. The default config uses `https://api.example.test` as the primary resource.
378
+
379
+ **Basic usage:**
380
+
381
+ ```ts
382
+ TestBed.configureTestingModule({
383
+ providers: [
384
+ ...provideLogtoTesting(),
385
+ MyComponent,
386
+ ],
387
+ });
388
+ ```
389
+
390
+ **Override client methods:**
391
+
392
+ ```ts
393
+ TestBed.configureTestingModule({
394
+ providers: [
395
+ ...provideLogtoTesting({
396
+ isAuthenticated: async () => true,
397
+ getAccessToken: async () => 'my-test-token',
398
+ getAccessTokenClaims: async () => ({scope: 'read write'}),
399
+ }),
400
+ ],
401
+ });
402
+ ```
403
+
404
+ **Override config:**
405
+
406
+ ```ts
407
+ TestBed.configureTestingModule({
408
+ providers: [
409
+ ...provideLogtoTesting(
410
+ {}, // no client overrides
411
+ {
412
+ noAccessMessage: 'Custom access-denied message.',
413
+ routing: {
414
+ callbackPath: '/custom/callback',
415
+ signedOutPath: '/custom/signed-out',
416
+ primaryResource: 'https://api.custom.test',
417
+ secureRoutes: [{resource: 'https://api.custom.test', routes: ['/api']}],
418
+ },
419
+ },
420
+ ),
421
+ ],
422
+ });
423
+ ```
424
+
425
+ ---
426
+
427
+ ## Building and local linking
428
+
429
+ ### Build the library
430
+
431
+ ```bash
432
+ # From the logto-angular workspace root
433
+ ng build logto-angular
434
+ ```
435
+
436
+ Output is emitted to `dist/logto-angular` (with `dist/logto-angular/testing` for the secondary entry point).
437
+
438
+ ### Link to a consuming app (Yarn `file:`)
439
+
440
+ Use Yarn's `file:` protocol — **not** `portal:` or `link:` — to reference the built dist.
441
+
442
+ In the consuming app's `package.json`:
443
+
444
+ ```json
445
+ {
446
+ "dependencies": {
447
+ "@ratespecial/logto-angular": "file:../../logto-angular/dist/logto-angular"
448
+ }
449
+ }
450
+ ```
451
+
452
+ Then install:
453
+
454
+ ```bash
455
+ yarn install
456
+ ```
457
+
458
+ **Why `file:` and not `portal:`?**
459
+
460
+ `portal:` creates a symlink whose *real* path still sits inside the `logto-angular` workspace. Node.js and esbuild follow the symlink back to the real location, then walk up the directory tree and find `/home/fish/logto-angular/node_modules/@angular` — a second Angular instance alongside the consumer's own. This causes `NG0203` injection errors at runtime and structural type-incompatibility errors at compile time (the two Angular copies produce distinct class definitions even at the same version).
461
+
462
+ `file:` snapshots the dist as a real directory copy under the consumer's `node_modules`. Peer-dependency resolution then finds only the consumer's single Angular, eliminating the dual-instance problem entirely.
463
+
464
+ **After each library rebuild, re-run `yarn install` in the consumer** to pick up the latest dist snapshot — `file:` does not live-update the way a symlink does:
465
+
466
+ ```bash
467
+ # Terminal 1 — rebuild library
468
+ ng build logto-angular
469
+
470
+ # Terminal 2 — re-snapshot in consumer
471
+ yarn install # or: yarn up @ratespecial/logto-angular
472
+ ```
473
+
474
+ **Belt-and-suspenders: `preserveSymlinks`**
475
+
476
+ Add `preserveSymlinks: true` to the consumer's Angular build target in `angular.json` and to its `tsconfig.json` `compilerOptions`. This prevents the Angular compiler from canonicalizing any remaining symlinks and ensures module resolution stays within the consumer's `node_modules`:
477
+
478
+ ```json
479
+ // angular.json — inside the build target's options
480
+ {
481
+ "preserveSymlinks": true
482
+ }
483
+ ```
484
+
485
+ ```json
486
+ // tsconfig.json
487
+ {
488
+ "compilerOptions": {
489
+ "preserveSymlinks": true
490
+ }
491
+ }
492
+ ```
493
+
494
+ ### Run tests
495
+
496
+ ```bash
497
+ ng test
498
+ ```
499
+
500
+ ### Publish
501
+
502
+ Update `version` in `projects/logto-angular/package.json`, then:
503
+
504
+ ```bash
505
+ ng build logto-angular
506
+ npm publish dist/logto-angular --access restricted
507
+ ```
@@ -0,0 +1,49 @@
1
+ import { LOGTO_CLIENT, LOGTO_AUTH_CONFIG, PRIMARY_RESOURCE } from '@ratespecial/logto-angular';
2
+
3
+ const DEFAULT_PRIMARY_RESOURCE = 'https://api.example.test';
4
+ const DEFAULT_TEST_CONFIG = {
5
+ endpoint: 'https://test.logto.app',
6
+ appId: 'test-app',
7
+ routing: {
8
+ callbackPath: '/auth/callback',
9
+ signedOutPath: '/auth/signed-out',
10
+ primaryResource: DEFAULT_PRIMARY_RESOURCE,
11
+ secureRoutes: [{ resource: DEFAULT_PRIMARY_RESOURCE, routes: ['/api'] }],
12
+ },
13
+ };
14
+ /**
15
+ * Test doubles for the auth layer. Provides a no-op `LogtoClient` (unauthenticated by default)
16
+ * plus the `LOGTO_AUTH_CONFIG` / `PRIMARY_RESOURCE` tokens that `AuthService` and the auth
17
+ * components depend on, so components under test can inject them without a real Logto setup.
18
+ *
19
+ * @param clientOverrides - Partial `LogtoClient` methods to override on the stub.
20
+ * @param configOverrides - Partial `LogtoAuthConfig` fields to override on the default test config.
21
+ */
22
+ function provideLogtoTesting(clientOverrides = {}, configOverrides = {}) {
23
+ const config = { ...DEFAULT_TEST_CONFIG, ...configOverrides };
24
+ const primaryResource = config.routing.primaryResource ??
25
+ config.routing.secureRoutes[0]?.resource ??
26
+ DEFAULT_PRIMARY_RESOURCE;
27
+ const stub = {
28
+ isAuthenticated: async () => false,
29
+ getAccessToken: async () => '',
30
+ getAccessTokenClaims: async () => ({ scope: '' }),
31
+ getIdTokenClaims: async () => ({}),
32
+ signIn: async () => undefined,
33
+ signOut: async () => undefined,
34
+ handleSignInCallback: async () => undefined,
35
+ ...clientOverrides,
36
+ };
37
+ return [
38
+ { provide: LOGTO_CLIENT, useValue: stub },
39
+ { provide: LOGTO_AUTH_CONFIG, useValue: config },
40
+ { provide: PRIMARY_RESOURCE, useValue: primaryResource },
41
+ ];
42
+ }
43
+
44
+ /**
45
+ * Generated bundle index. Do not edit.
46
+ */
47
+
48
+ export { provideLogtoTesting };
49
+ //# sourceMappingURL=ratespecial-logto-angular-testing.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ratespecial-logto-angular-testing.mjs","sources":["../../../projects/logto-angular/testing/src/provide-logto-testing.ts","../../../projects/logto-angular/testing/src/ratespecial-logto-angular-testing.ts"],"sourcesContent":["import { Provider } from '@angular/core';\nimport type LogtoClient from '@logto/browser';\nimport type { LogtoAuthConfig } from '@ratespecial/logto-angular';\nimport { LOGTO_AUTH_CONFIG, LOGTO_CLIENT, PRIMARY_RESOURCE } from '@ratespecial/logto-angular';\n\nconst DEFAULT_PRIMARY_RESOURCE = 'https://api.example.test';\n\nconst DEFAULT_TEST_CONFIG: LogtoAuthConfig = {\n endpoint: 'https://test.logto.app',\n appId: 'test-app',\n routing: {\n callbackPath: '/auth/callback',\n signedOutPath: '/auth/signed-out',\n primaryResource: DEFAULT_PRIMARY_RESOURCE,\n secureRoutes: [{ resource: DEFAULT_PRIMARY_RESOURCE, routes: ['/api'] }],\n },\n};\n\n/**\n * Test doubles for the auth layer. Provides a no-op `LogtoClient` (unauthenticated by default)\n * plus the `LOGTO_AUTH_CONFIG` / `PRIMARY_RESOURCE` tokens that `AuthService` and the auth\n * components depend on, so components under test can inject them without a real Logto setup.\n *\n * @param clientOverrides - Partial `LogtoClient` methods to override on the stub.\n * @param configOverrides - Partial `LogtoAuthConfig` fields to override on the default test config.\n */\nexport function provideLogtoTesting(\n clientOverrides: Partial<LogtoClient> = {},\n configOverrides: Partial<LogtoAuthConfig> = {},\n): Provider[] {\n const config: LogtoAuthConfig = { ...DEFAULT_TEST_CONFIG, ...configOverrides };\n const primaryResource =\n config.routing.primaryResource ??\n config.routing.secureRoutes[0]?.resource ??\n DEFAULT_PRIMARY_RESOURCE;\n\n const stub = {\n isAuthenticated: async () => false,\n getAccessToken: async () => '',\n getAccessTokenClaims: async () => ({ scope: '' }),\n getIdTokenClaims: async () => ({}),\n signIn: async () => undefined,\n signOut: async () => undefined,\n handleSignInCallback: async () => undefined,\n ...clientOverrides,\n } as unknown as LogtoClient;\n\n return [\n { provide: LOGTO_CLIENT, useValue: stub },\n { provide: LOGTO_AUTH_CONFIG, useValue: config },\n { provide: PRIMARY_RESOURCE, useValue: primaryResource },\n ];\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":";;AAKA,MAAM,wBAAwB,GAAG,0BAA0B;AAE3D,MAAM,mBAAmB,GAAoB;AAC3C,IAAA,QAAQ,EAAE,wBAAwB;AAClC,IAAA,KAAK,EAAE,UAAU;AACjB,IAAA,OAAO,EAAE;AACP,QAAA,YAAY,EAAE,gBAAgB;AAC9B,QAAA,aAAa,EAAE,kBAAkB;AACjC,QAAA,eAAe,EAAE,wBAAwB;AACzC,QAAA,YAAY,EAAE,CAAC,EAAE,QAAQ,EAAE,wBAAwB,EAAE,MAAM,EAAE,CAAC,MAAM,CAAC,EAAE,CAAC;AACzE,KAAA;CACF;AAED;;;;;;;AAOG;SACa,mBAAmB,CACjC,kBAAwC,EAAE,EAC1C,kBAA4C,EAAE,EAAA;IAE9C,MAAM,MAAM,GAAoB,EAAE,GAAG,mBAAmB,EAAE,GAAG,eAAe,EAAE;AAC9E,IAAA,MAAM,eAAe,GACnB,MAAM,CAAC,OAAO,CAAC,eAAe;QAC9B,MAAM,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC,CAAC,EAAE,QAAQ;AACxC,QAAA,wBAAwB;AAE1B,IAAA,MAAM,IAAI,GAAG;AACX,QAAA,eAAe,EAAE,YAAY,KAAK;AAClC,QAAA,cAAc,EAAE,YAAY,EAAE;QAC9B,oBAAoB,EAAE,aAAa,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC;AACjD,QAAA,gBAAgB,EAAE,aAAa,EAAE,CAAC;AAClC,QAAA,MAAM,EAAE,YAAY,SAAS;AAC7B,QAAA,OAAO,EAAE,YAAY,SAAS;AAC9B,QAAA,oBAAoB,EAAE,YAAY,SAAS;AAC3C,QAAA,GAAG,eAAe;KACO;IAE3B,OAAO;AACL,QAAA,EAAE,OAAO,EAAE,YAAY,EAAE,QAAQ,EAAE,IAAI,EAAE;AACzC,QAAA,EAAE,OAAO,EAAE,iBAAiB,EAAE,QAAQ,EAAE,MAAM,EAAE;AAChD,QAAA,EAAE,OAAO,EAAE,gBAAgB,EAAE,QAAQ,EAAE,eAAe,EAAE;KACzD;AACH;;ACpDA;;AAEG;;;;"}