@iloveagents/foundry-agent 0.3.0 → 0.4.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.
Files changed (51) hide show
  1. package/README.md +16 -0
  2. package/dist/client/agui-runner.d.ts +53 -0
  3. package/dist/client/agui-runner.js +320 -0
  4. package/dist/client/runner-events.d.ts +54 -0
  5. package/dist/client/runner-events.js +1 -0
  6. package/dist/client/service-fetch.d.ts +112 -0
  7. package/dist/client/service-fetch.js +244 -0
  8. package/dist/index.d.ts +7 -0
  9. package/dist/index.js +10 -0
  10. package/dist/msal/auth-config.d.ts +91 -0
  11. package/dist/msal/auth-config.js +70 -0
  12. package/dist/msal/auth-store.d.ts +95 -0
  13. package/dist/msal/auth-store.js +372 -0
  14. package/dist/msal/index.d.ts +3 -0
  15. package/dist/msal/index.js +3 -0
  16. package/dist/msal/token-fetch.d.ts +16 -0
  17. package/dist/msal/token-fetch.js +57 -0
  18. package/dist/store/citation-store.d.ts +42 -0
  19. package/dist/store/citation-store.js +14 -0
  20. package/dist/store/link-store.d.ts +29 -0
  21. package/dist/store/link-store.js +28 -0
  22. package/dist/store/streaming-status-store.d.ts +15 -0
  23. package/dist/store/streaming-status-store.js +9 -0
  24. package/dist/tools/registry.d.ts +48 -0
  25. package/dist/tools/registry.js +50 -0
  26. package/package.json +23 -9
  27. package/AGENTS.md +0 -91
  28. package/CHANGELOG.md +0 -180
  29. package/CLAUDE.md +0 -1
  30. package/src/__tests__/agui-runner.test.ts +0 -404
  31. package/src/__tests__/auth-store.test.ts +0 -596
  32. package/src/__tests__/citation-store.test.ts +0 -52
  33. package/src/__tests__/client-tool-registry.test.ts +0 -84
  34. package/src/__tests__/link-store.test.ts +0 -48
  35. package/src/__tests__/service-fetch.test.ts +0 -525
  36. package/src/__tests__/streaming-status-store.test.ts +0 -22
  37. package/src/__tests__/token-fetch.test.ts +0 -134
  38. package/src/client/agui-runner.ts +0 -382
  39. package/src/client/runner-events.ts +0 -27
  40. package/src/client/service-fetch.ts +0 -318
  41. package/src/index.ts +0 -27
  42. package/src/msal/auth-config.ts +0 -150
  43. package/src/msal/auth-store.ts +0 -517
  44. package/src/msal/index.ts +0 -14
  45. package/src/msal/token-fetch.ts +0 -68
  46. package/src/store/citation-store.ts +0 -52
  47. package/src/store/link-store.ts +0 -53
  48. package/src/store/streaming-status-store.ts +0 -21
  49. package/src/tools/registry.ts +0 -112
  50. package/tsconfig.json +0 -15
  51. package/vitest.config.ts +0 -8
@@ -1,318 +0,0 @@
1
- /**
2
- * Per-service fetch factory.
3
- *
4
- * Returns an authenticated `fetch`-shaped function that:
5
- * 1. Rewrites local-relative URLs to a configured router base in production
6
- * (Vite proxy in dev → httpRouteConfigs FQDN in prod).
7
- * 2. Acquires a Bearer token via the supplied `acquireToken` callback and
8
- * attaches it as the `Authorization` header.
9
- *
10
- * `@iloveagents/foundry-agent` stays auth-mechanism-agnostic — `acquireToken` is
11
- * supplied by the host (apps/web wires it to MSAL via the `/msal` subpath;
12
- * future shells could plug in a different token source).
13
- */
14
-
15
- export interface ServiceFetchOptions {
16
- /**
17
- * Acquire an access token for outgoing requests. Return `null` to skip
18
- * token attachment (callers without auth — e.g. local dev — pass through
19
- * to native fetch).
20
- *
21
- * The optional ``{ forceRefresh: true }`` argument is passed by the
22
- * fetch interceptor on a 401 retry — the auth layer should bypass
23
- * its local token cache and round-trip the token endpoint so we
24
- * stop re-sending an access token the resource server has already
25
- * rejected (canonical MSAL.js fix for tab-open-overnight 401 loops).
26
- */
27
- acquireToken: (options?: { forceRefresh?: boolean }) => Promise<string | null>;
28
-
29
- /**
30
- * Force interactive recovery (e.g. ``loginRedirect``) when even a
31
- * force-refreshed access token gets rejected by the resource server.
32
- * The fetch interceptor calls this after a SECOND consecutive 401 —
33
- * at that point we know the silent refresh produced a token the
34
- * server still won't accept (audience drift, conditional-access
35
- * re-eval, tenant-policy change), and the only correct UX is to
36
- * mint a fresh session.
37
- *
38
- * Implementations should clear cached auth state and start a redirect
39
- * to the IdP. They MUST throw rather than return so the fetch caller
40
- * can stop processing the in-flight request — when this resolves
41
- * normally the redirect is in flight and the page is about to
42
- * navigate away.
43
- *
44
- * Optional: when omitted, the fetch interceptor lets the second 401
45
- * propagate as-is. Hosts without an interactive recovery path (e.g.
46
- * tests, embedded apps) should leave it unset.
47
- */
48
- recoverFromHardAuthFailure?: (reason: unknown) => Promise<never>;
49
-
50
- /**
51
- * Router FQDN for production (e.g. `https://lastspace-prod.eastus2.example.com`).
52
- * Empty / undefined leaves the URL untouched (Vite proxy handles routing
53
- * in dev).
54
- */
55
- baseUrl?: string;
56
-
57
- /**
58
- * Resolve `window.location.origin` (or equivalent) for the current runtime.
59
- * Defaults to a browser-aware lookup; non-browser callers can override.
60
- */
61
- originResolver?: () => string;
62
- }
63
-
64
- export type ServiceFetch = (
65
- input: string | URL | Request,
66
- init?: RequestInit,
67
- ) => Promise<Response>;
68
-
69
- /**
70
- * Wrapper thrown when ``acquireToken`` rejects inside the fetch
71
- * interceptor. Lets the outer 401-retry layer distinguish
72
- * token-acquisition failures (which warrant interactive recovery) from
73
- * generic ``fetch`` rejections like network drops, aborts, or CORS
74
- * preflight failures (which do NOT — those would needlessly bounce the
75
- * user through ``loginRedirect`` on a transient transport error).
76
- *
77
- * Exported so hosts that wrap ``serviceFetch`` further can ``instanceof``
78
- * against the same class without re-declaring it.
79
- */
80
- export class TokenAcquisitionError extends Error {
81
- override readonly name = "TokenAcquisitionError";
82
- readonly cause: unknown;
83
- constructor(cause: unknown) {
84
- super(cause instanceof Error ? cause.message : String(cause));
85
- this.cause = cause;
86
- }
87
- }
88
-
89
- const defaultOriginResolver = (): string =>
90
- typeof window !== "undefined" ? window.location.origin : "";
91
-
92
- /**
93
- * Build a service-fetch function. Returns a function with the same shape as
94
- * `fetch` that rewrites URLs + attaches the Bearer token from `acquireToken`.
95
- */
96
- export function createServiceFetch(options: ServiceFetchOptions): ServiceFetch {
97
- const routerBase = (options.baseUrl ?? "").replace(/\/+$/, "");
98
- const resolveOrigin = options.originResolver ?? defaultOriginResolver;
99
-
100
- function resolveUrl(url: string): string {
101
- if (!routerBase) return url; // dev: Vite proxy handles routing
102
- const origin = resolveOrigin();
103
- const rel = origin && url.startsWith(origin) ? url.slice(origin.length) : url;
104
- return `${routerBase}${rel}`;
105
- }
106
-
107
- return async function serviceFetch(input, init): Promise<Response> {
108
- const url =
109
- typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url;
110
- const resolvedUrl = resolveUrl(url);
111
-
112
- // Merge headers from a `Request` input (if provided), then `init`. The
113
- // Request-input branch must preserve method / body / credentials /
114
- // signal / etc. — silently dropping them by reading only `.url` would
115
- // turn POST/PUT into GET and drop required headers.
116
- const baseHeaders = new Headers(input instanceof Request ? input.headers : undefined);
117
- if (init?.headers) {
118
- new Headers(init.headers).forEach((value, key) => baseHeaders.set(key, value));
119
- }
120
- const callerSuppliedAuth = baseHeaders.has("Authorization");
121
-
122
- function buildRequestInitFrom(req: Request): RequestInit {
123
- // ReadableStream bodies are single-consume. ``req.clone()`` returns a
124
- // fresh Request whose body stream is independent — call it once per
125
- // dispatch attempt so the 401 retry can replay POST/PUT bodies
126
- // intact.
127
- const fresh = req.clone();
128
- const ri: RequestInit = {
129
- method: fresh.method,
130
- body: fresh.method === "GET" || fresh.method === "HEAD" ? undefined : fresh.body,
131
- credentials: fresh.credentials,
132
- mode: fresh.mode,
133
- cache: fresh.cache,
134
- redirect: fresh.redirect,
135
- referrer: fresh.referrer,
136
- integrity: fresh.integrity,
137
- signal: fresh.signal,
138
- };
139
- // Streaming bodies need duplex: "half"; harmless when there's no body.
140
- if (fresh.body !== null) {
141
- (ri as RequestInit & { duplex?: string }).duplex = "half";
142
- }
143
- return ri;
144
- }
145
-
146
- async function dispatch(forceRefreshToken: boolean): Promise<Response> {
147
- const headers = new Headers(baseHeaders);
148
- if (!callerSuppliedAuth) {
149
- try {
150
- const token = await options.acquireToken(
151
- forceRefreshToken ? { forceRefresh: true } : undefined,
152
- );
153
- if (token) {
154
- headers.set("Authorization", `Bearer ${token}`);
155
- }
156
- } catch (error) {
157
- // A thrown token-acquisition error usually means the auth layer
158
- // is starting an interactive recovery. Do not downgrade
159
- // protected API calls to anonymous requests; that creates
160
- // noisy 401s and stale UI. Wrap the error so the outer
161
- // 401-retry layer can distinguish it from generic ``fetch``
162
- // rejections (network drops, aborts, CORS preflight fails)
163
- // and only escalate to ``recoverFromHardAuthFailure`` for
164
- // genuine auth failures.
165
- throw new TokenAcquisitionError(error);
166
- }
167
- }
168
- if (input instanceof Request) {
169
- // Re-clone for THIS attempt — the body stream of the original is
170
- // either still pristine (first attempt) or already consumed
171
- // (second attempt); ``clone()`` always returns a fresh, replayable
172
- // copy.
173
- const requestInit = buildRequestInitFrom(input);
174
- return fetch(resolvedUrl, { ...requestInit, ...init, headers });
175
- }
176
- return fetch(resolvedUrl, { ...init, headers });
177
- }
178
-
179
- const response = await dispatch(false);
180
-
181
- // Long-lived tab recovery: when the resource server returns 401 the
182
- // local MSAL cache may still hold a token MSAL itself thinks is valid
183
- // (claims challenge, conditional-access re-eval, audience drift, or
184
- // the user simply left the tab open past the cached access token's
185
- // server-side validity). Force-refresh the token via the refresh
186
- // token grant and replay the request once. If the refresh-token is
187
- // also gone (24h SPA cap), ``acquireToken`` will throw an
188
- // interaction-required error and the auth layer kicks off
189
- // ``loginRedirect`` — that's the only correct UX for a hard expiry.
190
- //
191
- // Conditions for retry: 401, no caller-supplied auth header (we own
192
- // the token), and the server didn't already see a fresh token (we
193
- // only retry once).
194
- if (response.status !== 401 || callerSuppliedAuth) {
195
- return response;
196
- }
197
- // Drain the failed response body — letting it sit unread keeps the
198
- // underlying connection occupied on some runtimes.
199
- response.body?.cancel().catch(() => undefined);
200
-
201
- // Two failure shapes for the retry:
202
- // (a) The force-refreshed token grant FAILS — refresh token gone
203
- // (24h SPA cap), interaction-required claims challenge, etc.
204
- // ``acquireToken`` throws inside ``dispatch`` BEFORE the fetch
205
- // leaves the browser. ``dispatch`` re-throws wrapped in
206
- // ``TokenAcquisitionError`` so we can distinguish this case
207
- // from network errors below.
208
- // (b) The grant succeeds but the resource server STILL rejects
209
- // the token (audience drift, server-side policy change). The
210
- // retried response status is 401.
211
- // Both must funnel into ``recoverFromHardAuthFailure`` so the auth
212
- // layer can kick off ``loginRedirect`` — the only correct UX for a
213
- // hard expiry. We treat the two auth paths symmetrically.
214
- //
215
- // We deliberately do NOT trigger recovery on generic ``dispatch``
216
- // throws (TypeError from network drops, AbortError from caller
217
- // cancellation, CORS-preflight failures, etc.) — those are
218
- // transient transport problems, not auth state corruption, and
219
- // bouncing the user through ``loginRedirect`` for a flaky network
220
- // would be a much worse UX than letting the error propagate.
221
- let retried: Response;
222
- try {
223
- retried = await dispatch(true);
224
- } catch (err) {
225
- if (err instanceof TokenAcquisitionError && options.recoverFromHardAuthFailure) {
226
- // ``recoverFromHardAuthFailure`` is expected to throw once the
227
- // redirect is in flight; if it returns (e.g. test stub) we
228
- // re-throw the wrapped original so the caller still sees the
229
- // auth failure rather than a phantom recovery. We pass the
230
- // unwrapped ``cause`` to recovery so it sees the real MSAL
231
- // error (InteractionRequiredAuthError etc.) for telemetry.
232
- await options.recoverFromHardAuthFailure(err.cause);
233
- }
234
- throw err;
235
- }
236
-
237
- // Hard auth failure path (b): even the force-refreshed token got
238
- // rejected. A token minted from the cached refresh token carries
239
- // the same identity claims as the original, so it'll keep getting
240
- // rejected for the same reason. Only a brand-new ``loginRedirect``
241
- // produces a token bound to the current server policy. Without
242
- // this branch the user sees an endless 401 loop until they
243
- // manually log out.
244
- if (retried.status === 401 && options.recoverFromHardAuthFailure) {
245
- // Continuous Access Evaluation / Conditional-Access claims
246
- // challenge passthrough. When the resource server requires a
247
- // step-up (MFA, device compliance, revocation invalidation),
248
- // it returns 401 with ``WWW-Authenticate: Bearer ... claims="…"``.
249
- // We forward the payload through the recovery callback so the
250
- // auth layer's redirect carries it to Entra ID and the
251
- // re-minted token satisfies the exact challenge. Without it,
252
- // Entra re-issues the same already-rejected claims set and the
253
- // user loops back into the broken state.
254
- const claims = parseClaimsChallengeFromWwwAuthenticate(
255
- retried.headers.get("www-authenticate"),
256
- );
257
- retried.body?.cancel().catch(() => undefined);
258
- const reason = new AuthInteractionRequiredError(
259
- "API returned 401 after force-refresh retry",
260
- claims ? { claims } : undefined,
261
- );
262
- // ``recoverFromHardAuthFailure`` throws once the redirect is in
263
- // flight; the throw stops the calling pipeline so we don't
264
- // return a stale 401 the caller might handle as a real failure.
265
- await options.recoverFromHardAuthFailure(reason);
266
- }
267
- return retried;
268
- };
269
- }
270
-
271
- /**
272
- * Error class the fetch interceptor raises (and forwards through
273
- * ``recoverFromHardAuthFailure``) when a 401 needs interactive
274
- * recovery. Exposed so the auth-layer recovery can pick up a
275
- * ``claims`` field if one was extracted from the
276
- * ``WWW-Authenticate`` header.
277
- */
278
- export class AuthInteractionRequiredError extends Error {
279
- override readonly name = "AuthInteractionRequiredError";
280
- readonly claims?: string;
281
- constructor(message: string, options?: { claims?: string }) {
282
- super(message);
283
- this.claims = options?.claims;
284
- }
285
- }
286
-
287
- /**
288
- * Extract the claims challenge string from a resource server's
289
- * ``WWW-Authenticate: Bearer ... claims="…"`` header. Returns
290
- * ``undefined`` when the header is missing, malformed, or carries
291
- * no claims directive.
292
- *
293
- * The value is forwarded VERBATIM to MSAL's
294
- * ``acquireTokenRedirect({ claims })``; MSAL handles the
295
- * base64-url decode + JSON parse itself. We don't try to validate
296
- * the inner shape — letting Entra speak for itself avoids drift if
297
- * the schema evolves.
298
- *
299
- * Spec: RFC 6750 ``WWW-Authenticate`` + CAE claims-challenge
300
- * supplement (Microsoft Identity Platform docs).
301
- */
302
- export function parseClaimsChallengeFromWwwAuthenticate(
303
- header: string | null | undefined,
304
- ): string | undefined {
305
- if (!header) return undefined;
306
- // ``WWW-Authenticate`` can carry multiple challenges separated by
307
- // commas (e.g. ``Basic realm="x", Bearer ...``). We only care
308
- // about the Bearer challenge.
309
- const bearer = header.match(/Bearer\s+([^,]+(?:,(?!\s*[A-Za-z]+\s)[^,]*)*)/i);
310
- if (!bearer) return undefined;
311
- // Inside the Bearer params, find ``claims="..."`` (quoted) or
312
- // ``claims=token68`` (unquoted, base64url).
313
- const quoted = bearer[1].match(/claims\s*=\s*"([^"]*)"/i);
314
- if (quoted) return quoted[1].length > 0 ? quoted[1] : undefined;
315
- const unquoted = bearer[1].match(/claims\s*=\s*([A-Za-z0-9_\-+/=]+)/i);
316
- if (unquoted) return unquoted[1].length > 0 ? unquoted[1] : undefined;
317
- return undefined;
318
- }
package/src/index.ts DELETED
@@ -1,27 +0,0 @@
1
- // --- AG-UI runner ---
2
- export { AGUIRunner, type AGUIRunnerOptions, type AGUIRunInput } from "./client/agui-runner.ts";
3
- export type { RunnerEvent } from "./client/runner-events.ts";
4
-
5
- // --- Service fetch factory ---
6
- export {
7
- createServiceFetch,
8
- type ServiceFetch,
9
- type ServiceFetchOptions,
10
- } from "./client/service-fetch.ts";
11
-
12
- // --- Tool registry ---
13
- export { clientToolRegistry, type ClientToolEntry, type ToolRegistry } from "./tools/registry.ts";
14
-
15
- // --- Stores (vanilla) ---
16
- export { streamingStatusStore, type StreamingStatus } from "./store/streaming-status-store.ts";
17
- export {
18
- citationStore,
19
- type CitationResult,
20
- type CitationHandler,
21
- } from "./store/citation-store.ts";
22
- export {
23
- linkStore,
24
- resolveLinkHandler,
25
- type LinkHandler,
26
- type ResolvedLinkHandler,
27
- } from "./store/link-store.ts";
@@ -1,150 +0,0 @@
1
- /**
2
- * MSAL configuration types and PublicClientApplication singleton.
3
- *
4
- * `initializeMsal()` creates and caches the singleton PublicClientApplication.
5
- * `getMsalInstance()` returns null until initialization has completed.
6
- *
7
- * `@azure/msal-browser` is loaded via dynamic import so `@iloveagents/foundry-agent`
8
- * does not pull MSAL into bundles that don't need it.
9
- */
10
-
11
- export interface MsalAuthConfig {
12
- clientId: string;
13
- authority: string; // https://login.microsoftonline.com/{tenantId}
14
- redirectUri: string; // window.location.origin
15
- apiScope: string; // api://{apiClientId}/access_as_user
16
- postLogoutRedirectUri?: string;
17
- }
18
-
19
- export interface MsalAccountInfo {
20
- username: string;
21
- localAccountId: string;
22
- name?: string | null;
23
- }
24
-
25
- export interface MsalClientApplication {
26
- initialize(): Promise<void>;
27
- handleRedirectPromise(): Promise<unknown>;
28
- clearCache(request?: { account?: MsalAccountInfo }): Promise<void>;
29
- getAllAccounts(): MsalAccountInfo[];
30
- loginRedirect(request: {
31
- scopes: string[];
32
- prompt?: string;
33
- /** Same claims-challenge passthrough as ``acquireTokenRedirect``. */
34
- claims?: string;
35
- }): Promise<void>;
36
- logoutRedirect(): Promise<void>;
37
- setActiveAccount(account: MsalAccountInfo | null): void;
38
- acquireTokenSilent(request: {
39
- scopes: string[];
40
- account: MsalAccountInfo;
41
- /**
42
- * Skip MSAL's local cache and force a round-trip to the token
43
- * endpoint using the cached refresh token. The fetch interceptor
44
- * uses this on a 401 retry — the first attempt may have served a
45
- * cached access token MSAL still thought valid (within
46
- * ``tokenRenewalOffsetSeconds``) that the resource server has
47
- * since rejected (claims challenge, conditional access re-eval,
48
- * audience drift).
49
- */
50
- forceRefresh?: boolean;
51
- }): Promise<{ accessToken: string }>;
52
- acquireTokenRedirect(request: {
53
- scopes: string[];
54
- account: MsalAccountInfo;
55
- /**
56
- * Force a fresh interactive login regardless of Entra SSO state.
57
- * Without ``prompt: "login"``, when the user still has a live
58
- * Entra session, the redirect silently round-trips and returns
59
- * THE SAME stale token / claims — leaving the SPA back in the
60
- * exact broken state we tried to recover from. See
61
- * https://learn.microsoft.com/entra/identity-platform/msal-error-handling-js
62
- * "Hard expiry / silent redirect loop" pattern.
63
- */
64
- prompt?: string;
65
- /**
66
- * Optional Continuous Access Evaluation / Conditional Access
67
- * claims challenge payload, parsed from a resource-server 401
68
- * response's ``WWW-Authenticate: Bearer ... claims="..."``
69
- * header. When present, MSAL passes it through to Entra so the
70
- * issued token explicitly satisfies the challenge (MFA step-up,
71
- * device-compliance refresh, revocation invalidation, etc.).
72
- */
73
- claims?: string;
74
- }): Promise<void>;
75
- acquireTokenPopup(request: {
76
- scopes: string[];
77
- account: MsalAccountInfo;
78
- }): Promise<{ accessToken: string }>;
79
- }
80
-
81
- let msalInstance: MsalClientApplication | null = null;
82
- let msalConfig: MsalAuthConfig | null = null;
83
-
84
- /**
85
- * Initialize the MSAL singleton. Must be called once before rendering.
86
- * Dynamically imports `@azure/msal-browser` so the dependency stays optional.
87
- */
88
- export async function initializeMsal(
89
- config: MsalAuthConfig,
90
- ): Promise<MsalClientApplication> {
91
- if (msalInstance) return msalInstance;
92
-
93
- const { PublicClientApplication: PCA } = await import("@azure/msal-browser");
94
-
95
- // Token-renewal config — see Microsoft Learn:
96
- // https://learn.microsoft.com/entra/msal/javascript/browser/errors
97
- //
98
- // SPA refresh tokens are 24 h, non-sliding, non-renewable. After that
99
- // window the user MUST re-auth; nothing the SPA can do silently saves
100
- // it. Goal of these knobs is to make the unavoidable interactive
101
- // bounce predictable and to keep the silent path healthy in between.
102
- const msalConfiguration = {
103
- auth: {
104
- clientId: config.clientId,
105
- authority: config.authority,
106
- redirectUri: config.redirectUri,
107
- postLogoutRedirectUri: config.postLogoutRedirectUri ?? config.redirectUri,
108
- },
109
- cache: {
110
- // localStorage is required for Playwright E2E tests — sessionStorage
111
- // is not preserved across page navigations in the Playwright context.
112
- cacheLocation: "localStorage",
113
- // Cache key includes a hash of any `claims` parameter. Without
114
- // this, MSAL serves the same cached access token even after a
115
- // claims-challenge / token revocation / role change. The MSAL
116
- // team has signalled this will become the default; opt in early.
117
- claimsBasedCachingEnabled: true,
118
- },
119
- system: {
120
- // Treat access tokens as "expired" 10 min before the actual exp
121
- // claim instead of MSAL's default 5 min. Eliminates the race
122
- // where the SPA's clock thinks the token is still valid but the
123
- // resource server rejects it as expired (clock skew, slow request
124
- // queueing, etc.).
125
- tokenRenewalOffsetSeconds: 600,
126
- // Default 6 s is too tight on modern browsers — third-party
127
- // storage partitioning + slower CPUs in the silent iframe can
128
- // push the round-trip past it. 10 s is the value MSAL Angular
129
- // and React samples ship with.
130
- iframeHashTimeout: 10000,
131
- },
132
- };
133
-
134
- msalInstance = new (PCA as new (config: typeof msalConfiguration) => MsalClientApplication)(
135
- msalConfiguration,
136
- );
137
- await msalInstance.initialize();
138
- msalConfig = config;
139
- return msalInstance;
140
- }
141
-
142
- /** Get the MSAL instance (null when not configured). */
143
- export function getMsalInstance(): MsalClientApplication | null {
144
- return msalInstance;
145
- }
146
-
147
- /** Get the MSAL auth config (null when not configured). */
148
- export function getMsalConfig(): MsalAuthConfig | null {
149
- return msalConfig;
150
- }