@docyrus/app-auth-ui 0.0.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.
package/dist/index.js ADDED
@@ -0,0 +1,562 @@
1
+ import { createContext, useState, useRef, useEffect, useMemo, useContext } from 'react';
2
+ import { BrowserOAuth2TokenStorage, OAuth2Client, RestApiClient } from '@docyrus/api-client';
3
+ import { jsx, Fragment } from 'react/jsx-runtime';
4
+
5
+ // src/components/docyrus-auth-provider.tsx
6
+
7
+ // src/constants.ts
8
+ var DEFAULT_API_URL = "https://alpha-api.docyrus.com";
9
+ var DEFAULT_OAUTH_CLIENT_ID = "90565525-8283-4881-82a9-8613eb82ae27";
10
+ var DEFAULT_OAUTH_SCOPES = [
11
+ "offline_access",
12
+ "Read.All",
13
+ "Users.Read",
14
+ "Users.Read.All",
15
+ "DS.Read.All"
16
+ ];
17
+ var DEFAULT_CALLBACK_PATH = "/auth/callback";
18
+ var DEFAULT_ALLOWED_HOST_PATTERN = /^https:\/\/[^/]+\.docyrus\.app$/;
19
+ var REDIRECT_RETURN_KEY = "docyrus_auth_return_url";
20
+ var TOKEN_EXPIRY_BUFFER_SECONDS = 60;
21
+ var IFRAME_TOKEN_REFRESH_TIMEOUT_MS = 1e4;
22
+
23
+ // src/core/auth-detector.ts
24
+ function detectAuthMode(allowedOrigins, allowedPattern) {
25
+ if (typeof window === "undefined") return "standalone";
26
+ let isInIframe = false;
27
+ try {
28
+ isInIframe = window.self !== window.top;
29
+ } catch {
30
+ isInIframe = true;
31
+ }
32
+ if (!isInIframe) return "standalone";
33
+ const pattern = allowedPattern ?? DEFAULT_ALLOWED_HOST_PATTERN;
34
+ try {
35
+ const referrerOrigin = new URL(document.referrer).origin;
36
+ if (allowedOrigins?.includes(referrerOrigin)) return "iframe";
37
+ if (pattern.test(referrerOrigin)) return "iframe";
38
+ } catch {
39
+ }
40
+ return "standalone";
41
+ }
42
+ var StandaloneOAuth2Auth = class {
43
+ oauth2Client;
44
+ storage;
45
+ callbackPath;
46
+ constructor(config = {}) {
47
+ const apiUrl = config.apiUrl ?? DEFAULT_API_URL;
48
+ const clientId = config.clientId ?? DEFAULT_OAUTH_CLIENT_ID;
49
+ const scopes = config.scopes ?? DEFAULT_OAUTH_SCOPES;
50
+ this.callbackPath = config.callbackPath ?? DEFAULT_CALLBACK_PATH;
51
+ const redirectUri = config.redirectUri ?? `${window.location.origin}${this.callbackPath}`;
52
+ this.storage = new BrowserOAuth2TokenStorage(
53
+ window.localStorage,
54
+ config.storageKeyPrefix ?? "docyrus_oauth2"
55
+ );
56
+ this.oauth2Client = new OAuth2Client({
57
+ baseURL: apiUrl,
58
+ clientId,
59
+ redirectUri,
60
+ defaultScopes: scopes,
61
+ usePKCE: true,
62
+ tokenStorage: this.storage
63
+ });
64
+ }
65
+ /**
66
+ * Check if the current page URL is the OAuth2 callback.
67
+ * Called on app initialization to detect returning from the auth server.
68
+ */
69
+ isCallbackUrl() {
70
+ return window.location.pathname === this.callbackPath && (window.location.search.includes("code=") || window.location.search.includes("error="));
71
+ }
72
+ /**
73
+ * Initiate the OAuth2 authorization code flow.
74
+ * Stores the current URL for post-auth redirect, then navigates
75
+ * the page to the authorization endpoint.
76
+ *
77
+ * PKCE state (codeVerifier, state) is stored in localStorage
78
+ * by the OAuth2Client and survives the page redirect.
79
+ */
80
+ async initiateLogin() {
81
+ window.localStorage.setItem(REDIRECT_RETURN_KEY, window.location.href);
82
+ const { url } = await this.oauth2Client.getAuthorizationUrl();
83
+ window.location.href = url;
84
+ }
85
+ /**
86
+ * Handle the OAuth2 callback URL.
87
+ * Reads PKCE state from localStorage, validates the state param,
88
+ * exchanges the authorization code for tokens, and stores them.
89
+ */
90
+ async handleCallback() {
91
+ return this.oauth2Client.handleCallback(window.location.href);
92
+ }
93
+ /**
94
+ * Get the URL the user was on before the OAuth redirect.
95
+ * Clears the stored value after retrieval.
96
+ */
97
+ getReturnUrl() {
98
+ const url = window.localStorage.getItem(REDIRECT_RETURN_KEY);
99
+ window.localStorage.removeItem(REDIRECT_RETURN_KEY);
100
+ return url;
101
+ }
102
+ /** Get currently stored tokens (e.g., on page reload). */
103
+ async getStoredTokens() {
104
+ return this.oauth2Client.getTokens();
105
+ }
106
+ /** Check if the stored token is expired (with 60s buffer). */
107
+ async isTokenExpired() {
108
+ return this.oauth2Client.isTokenExpired();
109
+ }
110
+ /** Refresh the access token using the stored refresh token. */
111
+ async refreshToken() {
112
+ return this.oauth2Client.refreshAccessToken();
113
+ }
114
+ /** Get a valid access token, refreshing if needed. */
115
+ async getValidAccessToken() {
116
+ return this.oauth2Client.getValidAccessToken();
117
+ }
118
+ /** Logout: revoke token and clear storage. */
119
+ async logout() {
120
+ return this.oauth2Client.logout();
121
+ }
122
+ };
123
+
124
+ // src/core/iframe-auth.ts
125
+ var IframeAuth = class {
126
+ allowedOrigins;
127
+ allowedPattern;
128
+ onTokensReceived = null;
129
+ refreshPromise = null;
130
+ messageHandler = null;
131
+ constructor(allowedOrigins, allowedPattern) {
132
+ this.allowedOrigins = allowedOrigins ?? [];
133
+ this.allowedPattern = allowedPattern ?? DEFAULT_ALLOWED_HOST_PATTERN;
134
+ }
135
+ /**
136
+ * Start listening for postMessage events from the host.
137
+ * The callback is invoked every time valid tokens are received.
138
+ */
139
+ start(onTokensReceived) {
140
+ this.onTokensReceived = onTokensReceived;
141
+ this.messageHandler = this.handleMessage.bind(this);
142
+ window.addEventListener("message", this.messageHandler);
143
+ }
144
+ /** Stop listening for postMessage events. */
145
+ stop() {
146
+ if (this.messageHandler) {
147
+ window.removeEventListener("message", this.messageHandler);
148
+ this.messageHandler = null;
149
+ }
150
+ this.rejectPendingRefresh("IframeAuth stopped");
151
+ }
152
+ /**
153
+ * Request fresh tokens from the host.
154
+ * Posts a "token-refresh-request" to the host, then waits for
155
+ * a "signin" response.
156
+ *
157
+ * Only one refresh request can be in-flight at a time.
158
+ * Additional callers share the same promise.
159
+ */
160
+ requestTokenRefresh() {
161
+ if (this.refreshPromise) {
162
+ return new Promise((resolve, reject) => {
163
+ const existing = this.refreshPromise;
164
+ const originalResolve = existing.resolve;
165
+ const originalReject = existing.reject;
166
+ existing.resolve = (tokens) => {
167
+ originalResolve(tokens);
168
+ resolve(tokens);
169
+ };
170
+ existing.reject = (error) => {
171
+ originalReject(error);
172
+ reject(error);
173
+ };
174
+ });
175
+ }
176
+ return new Promise((resolve, reject) => {
177
+ const timeoutId = setTimeout(() => {
178
+ this.refreshPromise = null;
179
+ reject(new Error("Token refresh request timed out"));
180
+ }, IFRAME_TOKEN_REFRESH_TIMEOUT_MS);
181
+ this.refreshPromise = { resolve, reject, timeoutId };
182
+ window.parent.postMessage({ type: "token-refresh-request" }, "*");
183
+ });
184
+ }
185
+ isOriginAllowed(origin) {
186
+ if (this.allowedOrigins.includes(origin)) return true;
187
+ return this.allowedPattern.test(origin);
188
+ }
189
+ handleMessage(ev) {
190
+ if (!this.isOriginAllowed(ev.origin)) return;
191
+ const data = ev.data;
192
+ if (!data || typeof data !== "object" || data.type !== "signin") return;
193
+ const signinData = data;
194
+ if (!signinData.accessToken) return;
195
+ const tokens = {
196
+ accessToken: signinData.accessToken,
197
+ refreshToken: signinData.refreshToken,
198
+ /*
199
+ * We don't know the exact expiry from the host, so estimate 1 hour.
200
+ * The host will send new tokens before expiry via the refresh mechanism.
201
+ */
202
+ expiresAt: Math.floor(Date.now() / 1e3) + 3600,
203
+ scope: ""
204
+ };
205
+ this.onTokensReceived?.(tokens);
206
+ if (this.refreshPromise) {
207
+ clearTimeout(this.refreshPromise.timeoutId);
208
+ this.refreshPromise.resolve(tokens);
209
+ this.refreshPromise = null;
210
+ }
211
+ }
212
+ rejectPendingRefresh(reason) {
213
+ if (this.refreshPromise) {
214
+ clearTimeout(this.refreshPromise.timeoutId);
215
+ this.refreshPromise.reject(new Error(reason));
216
+ this.refreshPromise = null;
217
+ }
218
+ }
219
+ };
220
+
221
+ // src/core/auth-manager.ts
222
+ var AuthManager = class {
223
+ mode;
224
+ status = "loading";
225
+ tokens = null;
226
+ error = null;
227
+ client = null;
228
+ standaloneAuth = null;
229
+ iframeAuth = null;
230
+ tokenRefreshTimer = null;
231
+ listeners = /* @__PURE__ */ new Set();
232
+ config;
233
+ constructor(config = {}) {
234
+ this.config = config;
235
+ this.mode = config.forceMode ?? detectAuthMode(config.allowedHostOrigins);
236
+ }
237
+ getMode() {
238
+ return this.mode;
239
+ }
240
+ getStatus() {
241
+ return this.status;
242
+ }
243
+ getTokens() {
244
+ return this.tokens;
245
+ }
246
+ getClient() {
247
+ return this.client;
248
+ }
249
+ getError() {
250
+ return this.error;
251
+ }
252
+ subscribe(listener) {
253
+ this.listeners.add(listener);
254
+ return () => this.listeners.delete(listener);
255
+ }
256
+ notify() {
257
+ for (const listener of this.listeners) {
258
+ listener({
259
+ status: this.status,
260
+ tokens: this.tokens,
261
+ error: this.error
262
+ });
263
+ }
264
+ }
265
+ /** Initialize the auth manager. Must be called once on mount. */
266
+ async initialize() {
267
+ try {
268
+ if (this.mode === "iframe") {
269
+ this.initializeIframeMode();
270
+ } else {
271
+ await this.initializeStandaloneMode();
272
+ }
273
+ } catch (err) {
274
+ this.error = err instanceof Error ? err : new Error(String(err));
275
+ this.status = "unauthenticated";
276
+ this.notify();
277
+ }
278
+ }
279
+ /**
280
+ * Standalone mode initialization.
281
+ * 1. Check if we're returning from an OAuth callback.
282
+ * 2. If not, check for existing tokens in localStorage.
283
+ * 3. If tokens are expired, try to refresh.
284
+ */
285
+ async initializeStandaloneMode() {
286
+ this.standaloneAuth = new StandaloneOAuth2Auth(this.config);
287
+ if (this.standaloneAuth.isCallbackUrl()) {
288
+ const tokens = await this.standaloneAuth.handleCallback();
289
+ this.setAuthenticated(tokens);
290
+ const returnUrl = this.standaloneAuth.getReturnUrl();
291
+ if (returnUrl) {
292
+ window.location.replace(returnUrl);
293
+ } else {
294
+ window.history.replaceState({}, "", "/");
295
+ }
296
+ return;
297
+ }
298
+ const stored = await this.standaloneAuth.getStoredTokens();
299
+ if (stored) {
300
+ const isExpired = await this.standaloneAuth.isTokenExpired();
301
+ if (!isExpired) {
302
+ this.setAuthenticated(stored);
303
+ return;
304
+ }
305
+ if (stored.refreshToken) {
306
+ try {
307
+ const newTokens = await this.standaloneAuth.refreshToken();
308
+ this.setAuthenticated(newTokens);
309
+ return;
310
+ } catch {
311
+ }
312
+ }
313
+ }
314
+ this.status = "unauthenticated";
315
+ this.notify();
316
+ }
317
+ /**
318
+ * Iframe mode initialization.
319
+ * Listen for postMessage tokens from the host.
320
+ * Status remains 'loading' until the host sends the first signin message.
321
+ */
322
+ initializeIframeMode() {
323
+ this.iframeAuth = new IframeAuth(this.config.allowedHostOrigins);
324
+ this.iframeAuth.start((tokens) => {
325
+ this.setAuthenticated(tokens);
326
+ });
327
+ }
328
+ /** Initiate sign-in. Only meaningful in standalone mode. */
329
+ async signIn() {
330
+ if (this.mode !== "standalone" || !this.standaloneAuth) return;
331
+ await this.standaloneAuth.initiateLogin();
332
+ }
333
+ /**
334
+ * Sign out.
335
+ * Standalone: revoke token, clear localStorage, reset state.
336
+ * Iframe: clear local state (host manages the actual session).
337
+ */
338
+ async signOut() {
339
+ this.clearTokenRefreshTimer();
340
+ if (this.mode === "standalone" && this.standaloneAuth) {
341
+ await this.standaloneAuth.logout();
342
+ }
343
+ if (this.mode === "iframe" && this.iframeAuth) {
344
+ this.iframeAuth.stop();
345
+ }
346
+ this.tokens = null;
347
+ this.client = null;
348
+ this.status = "unauthenticated";
349
+ this.error = null;
350
+ this.notify();
351
+ }
352
+ /**
353
+ * Called when valid tokens are received (either mode).
354
+ * Creates/updates the RestApiClient and schedules token refresh.
355
+ */
356
+ setAuthenticated(tokens) {
357
+ this.tokens = tokens;
358
+ this.error = null;
359
+ this.status = "authenticated";
360
+ this.createClient();
361
+ this.scheduleTokenRefresh(tokens);
362
+ this.notify();
363
+ }
364
+ /**
365
+ * Create a RestApiClient with a custom TokenManager that proactively
366
+ * refreshes the token in getToken(). This is necessary because
367
+ * BaseApiClient.getAccessToken() only calls tokenManager.getToken()
368
+ * and does NOT auto-call refreshToken() when the token is expired.
369
+ */
370
+ createClient() {
371
+ const apiUrl = this.config.apiUrl ?? DEFAULT_API_URL;
372
+ const tokenManager = {
373
+ getToken: () => this.getValidToken(),
374
+ setToken: (token) => {
375
+ if (this.tokens) {
376
+ this.tokens = { ...this.tokens, accessToken: token };
377
+ }
378
+ },
379
+ clearToken: () => {
380
+ this.tokens = null;
381
+ },
382
+ refreshToken: () => this.handleTokenRefresh()
383
+ };
384
+ this.client = new RestApiClient({
385
+ baseURL: apiUrl,
386
+ tokenManager
387
+ });
388
+ }
389
+ /**
390
+ * Get a valid access token, proactively refreshing if expired.
391
+ * Called by the RestApiClient on every request via tokenManager.getToken().
392
+ */
393
+ async getValidToken() {
394
+ if (!this.tokens) return null;
395
+ if (this.tokens.expiresAt) {
396
+ const nowSec = Math.floor(Date.now() / 1e3);
397
+ if (nowSec >= this.tokens.expiresAt - TOKEN_EXPIRY_BUFFER_SECONDS) {
398
+ try {
399
+ const freshToken = await this.handleTokenRefresh();
400
+ return freshToken;
401
+ } catch {
402
+ return null;
403
+ }
404
+ }
405
+ }
406
+ return this.tokens.accessToken;
407
+ }
408
+ /**
409
+ * Handle token refresh depending on mode.
410
+ * Standalone: use OAuth2Client.getValidAccessToken() which auto-refreshes.
411
+ * Iframe: send postMessage to host requesting new tokens.
412
+ */
413
+ async handleTokenRefresh() {
414
+ if (this.mode === "standalone" && this.standaloneAuth) {
415
+ const newTokens = await this.standaloneAuth.refreshToken();
416
+ this.tokens = newTokens;
417
+ this.scheduleTokenRefresh(newTokens);
418
+ this.notify();
419
+ return newTokens.accessToken;
420
+ }
421
+ if (this.mode === "iframe" && this.iframeAuth) {
422
+ const newTokens = await this.iframeAuth.requestTokenRefresh();
423
+ return newTokens.accessToken;
424
+ }
425
+ throw new Error("Cannot refresh token: auth not initialized");
426
+ }
427
+ /** Schedule proactive token refresh before expiry. */
428
+ scheduleTokenRefresh(tokens) {
429
+ this.clearTokenRefreshTimer();
430
+ if (!tokens.expiresAt) return;
431
+ const expiresAtMs = tokens.expiresAt * 1e3;
432
+ const bufferMs = TOKEN_EXPIRY_BUFFER_SECONDS * 1e3;
433
+ const refreshInMs = expiresAtMs - bufferMs - Date.now();
434
+ if (refreshInMs <= 0) {
435
+ this.handleTokenRefresh().catch((err) => {
436
+ this.error = err instanceof Error ? err : new Error(String(err));
437
+ this.status = "unauthenticated";
438
+ this.notify();
439
+ });
440
+ return;
441
+ }
442
+ this.tokenRefreshTimer = setTimeout(() => {
443
+ this.handleTokenRefresh().catch((err) => {
444
+ this.error = err instanceof Error ? err : new Error(String(err));
445
+ this.status = "unauthenticated";
446
+ this.notify();
447
+ });
448
+ }, refreshInMs);
449
+ }
450
+ clearTokenRefreshTimer() {
451
+ if (this.tokenRefreshTimer) {
452
+ clearTimeout(this.tokenRefreshTimer);
453
+ this.tokenRefreshTimer = null;
454
+ }
455
+ }
456
+ /** Cleanup: remove listeners, timers, stop iframe auth. */
457
+ destroy() {
458
+ this.clearTokenRefreshTimer();
459
+ if (this.iframeAuth) this.iframeAuth.stop();
460
+ this.listeners.clear();
461
+ }
462
+ };
463
+ var DocyrusAuthContext = createContext(null);
464
+ function DocyrusAuthProvider({
465
+ children,
466
+ ...config
467
+ }) {
468
+ const [status, setStatus] = useState("loading");
469
+ const [tokens, setTokens] = useState(null);
470
+ const [client, setClient] = useState(null);
471
+ const [mode, setMode] = useState(null);
472
+ const [error, setError] = useState(null);
473
+ const managerRef = useRef(null);
474
+ useEffect(() => {
475
+ const manager = new AuthManager(config);
476
+ managerRef.current = manager;
477
+ setMode(manager.getMode());
478
+ const unsubscribe = manager.subscribe((state) => {
479
+ setStatus(state.status);
480
+ setTokens(state.tokens);
481
+ setError(state.error);
482
+ setClient(manager.getClient());
483
+ });
484
+ manager.initialize();
485
+ return () => {
486
+ unsubscribe();
487
+ manager.destroy();
488
+ };
489
+ }, []);
490
+ const signIn = useMemo(() => {
491
+ return () => {
492
+ managerRef.current?.signIn();
493
+ };
494
+ }, []);
495
+ const signOut = useMemo(() => {
496
+ return async () => {
497
+ await managerRef.current?.signOut();
498
+ };
499
+ }, []);
500
+ const value = useMemo(() => ({
501
+ status,
502
+ mode,
503
+ client,
504
+ tokens,
505
+ signIn,
506
+ signOut,
507
+ error
508
+ }), [
509
+ status,
510
+ mode,
511
+ client,
512
+ tokens,
513
+ signIn,
514
+ signOut,
515
+ error
516
+ ]);
517
+ return /* @__PURE__ */ jsx(DocyrusAuthContext.Provider, { value, children });
518
+ }
519
+ function useDocyrusAuth() {
520
+ const context = useContext(DocyrusAuthContext);
521
+ if (!context) {
522
+ throw new Error(
523
+ "useDocyrusAuth must be used within a <DocyrusAuthProvider>. Wrap your app with <DocyrusAuthProvider> to provide auth context."
524
+ );
525
+ }
526
+ return context;
527
+ }
528
+
529
+ // src/hooks/use-docyrus-client.ts
530
+ function useDocyrusClient() {
531
+ const { client } = useDocyrusAuth();
532
+ return client;
533
+ }
534
+ function SignInButton({
535
+ label = "Sign in with Docyrus",
536
+ className,
537
+ style,
538
+ children,
539
+ disabled = false
540
+ }) {
541
+ const { signIn, status, mode } = useDocyrusAuth();
542
+ if (mode === "iframe") return null;
543
+ if (status === "authenticated") return null;
544
+ if (children) {
545
+ return /* @__PURE__ */ jsx(Fragment, { children: children({ signIn, status }) });
546
+ }
547
+ return /* @__PURE__ */ jsx(
548
+ "button",
549
+ {
550
+ type: "button",
551
+ onClick: signIn,
552
+ disabled: disabled || status === "loading",
553
+ className,
554
+ style,
555
+ children: status === "loading" ? "Loading..." : label
556
+ }
557
+ );
558
+ }
559
+
560
+ export { AuthManager, DEFAULT_API_URL, DEFAULT_CALLBACK_PATH, DEFAULT_OAUTH_CLIENT_ID, DEFAULT_OAUTH_SCOPES, DocyrusAuthContext, DocyrusAuthProvider, IframeAuth, SignInButton, StandaloneOAuth2Auth, detectAuthMode, useDocyrusAuth, useDocyrusClient };
561
+ //# sourceMappingURL=index.js.map
562
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/constants.ts","../src/core/auth-detector.ts","../src/core/oauth2-auth.ts","../src/core/iframe-auth.ts","../src/core/auth-manager.ts","../src/components/docyrus-auth-provider.tsx","../src/hooks/use-docyrus-auth.ts","../src/hooks/use-docyrus-client.ts","../src/components/sign-in-button.tsx"],"names":["jsx"],"mappings":"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","file":"index.js","sourcesContent":["export const DEFAULT_API_URL = 'https://alpha-api.docyrus.com';\n\nexport const DEFAULT_OAUTH_CLIENT_ID = '90565525-8283-4881-82a9-8613eb82ae27';\n\nexport const DEFAULT_OAUTH_SCOPES = [\n 'offline_access',\n 'Read.All',\n 'Users.Read',\n 'Users.Read.All',\n 'DS.Read.All'\n];\n\nexport const DEFAULT_CALLBACK_PATH = '/auth/callback';\n\nexport const DEFAULT_ALLOWED_HOST_PATTERN = /^https:\\/\\/[^/]+\\.docyrus\\.app$/;\n\n/** localStorage key to persist the pre-redirect location */\nexport const REDIRECT_RETURN_KEY = 'docyrus_auth_return_url';\n\n/** Buffer in seconds before token expiration to trigger refresh */\nexport const TOKEN_EXPIRY_BUFFER_SECONDS = 60;\n\n/** Timeout for iframe token refresh requests (ms) */\nexport const IFRAME_TOKEN_REFRESH_TIMEOUT_MS = 10_000;\n","import { type AuthMode } from '../types';\n\nimport { DEFAULT_ALLOWED_HOST_PATTERN } from '../constants';\n\n/**\n * Detect whether the app is inside an iframe from a trusted *.docyrus.app host\n * or running as a standalone page.\n *\n * If we are in an iframe but NOT from a trusted origin, we fall back\n * to standalone mode (graceful degradation).\n */\nexport function detectAuthMode(\n allowedOrigins?: string[],\n allowedPattern?: RegExp\n): AuthMode {\n if (typeof window === 'undefined') return 'standalone';\n\n let isInIframe = false;\n\n try {\n isInIframe = window.self !== window.top;\n } catch {\n // Cross-origin iframe throws — so we ARE in an iframe\n isInIframe = true;\n }\n\n if (!isInIframe) return 'standalone';\n\n /*\n * We're in an iframe. Verify the host origin is trusted via document.referrer.\n * The actual security is enforced by validating event.origin on every postMessage.\n */\n const pattern = allowedPattern ?? DEFAULT_ALLOWED_HOST_PATTERN;\n\n try {\n const referrerOrigin = new URL(document.referrer).origin;\n\n if (allowedOrigins?.includes(referrerOrigin)) return 'iframe';\n if (pattern.test(referrerOrigin)) return 'iframe';\n } catch {\n // Invalid or empty referrer\n }\n\n // In iframe but not from a trusted host — treat as standalone\n return 'standalone';\n}\n","import {\n OAuth2Client,\n BrowserOAuth2TokenStorage,\n type OAuth2Tokens\n} from '@docyrus/api-client';\n\nimport { type DocyrusAuthConfig } from '../types';\n\nimport {\n DEFAULT_API_URL,\n DEFAULT_OAUTH_CLIENT_ID,\n DEFAULT_OAUTH_SCOPES,\n DEFAULT_CALLBACK_PATH,\n REDIRECT_RETURN_KEY\n} from '../constants';\n\n/**\n * Standalone OAuth2 authorization code flow with PKCE via page redirect.\n * Wraps api-client's OAuth2Client and BrowserOAuth2TokenStorage.\n */\nexport class StandaloneOAuth2Auth {\n private oauth2Client: OAuth2Client;\n private storage: BrowserOAuth2TokenStorage;\n private callbackPath: string;\n constructor(config: DocyrusAuthConfig = {}) {\n const apiUrl = config.apiUrl ?? DEFAULT_API_URL;\n const clientId = config.clientId ?? DEFAULT_OAUTH_CLIENT_ID;\n const scopes = config.scopes ?? DEFAULT_OAUTH_SCOPES;\n\n this.callbackPath = config.callbackPath ?? DEFAULT_CALLBACK_PATH;\n const redirectUri = config.redirectUri\n ?? `${window.location.origin}${this.callbackPath}`;\n\n this.storage = new BrowserOAuth2TokenStorage(\n window.localStorage,\n config.storageKeyPrefix ?? 'docyrus_oauth2'\n );\n\n this.oauth2Client = new OAuth2Client({\n baseURL: apiUrl,\n clientId,\n redirectUri,\n defaultScopes: scopes,\n usePKCE: true,\n tokenStorage: this.storage\n });\n }\n /**\n * Check if the current page URL is the OAuth2 callback.\n * Called on app initialization to detect returning from the auth server.\n */\n isCallbackUrl(): boolean {\n return window.location.pathname === this.callbackPath\n && (window.location.search.includes('code=')\n || window.location.search.includes('error='));\n }\n /**\n * Initiate the OAuth2 authorization code flow.\n * Stores the current URL for post-auth redirect, then navigates\n * the page to the authorization endpoint.\n *\n * PKCE state (codeVerifier, state) is stored in localStorage\n * by the OAuth2Client and survives the page redirect.\n */\n async initiateLogin(): Promise<void> {\n window.localStorage.setItem(REDIRECT_RETURN_KEY, window.location.href);\n\n const { url } = await this.oauth2Client.getAuthorizationUrl();\n\n window.location.href = url;\n }\n /**\n * Handle the OAuth2 callback URL.\n * Reads PKCE state from localStorage, validates the state param,\n * exchanges the authorization code for tokens, and stores them.\n */\n async handleCallback(): Promise<OAuth2Tokens> {\n return this.oauth2Client.handleCallback(window.location.href);\n }\n /**\n * Get the URL the user was on before the OAuth redirect.\n * Clears the stored value after retrieval.\n */\n getReturnUrl(): string | null {\n const url = window.localStorage.getItem(REDIRECT_RETURN_KEY);\n\n window.localStorage.removeItem(REDIRECT_RETURN_KEY);\n\n return url;\n }\n /** Get currently stored tokens (e.g., on page reload). */\n async getStoredTokens(): Promise<OAuth2Tokens | null> {\n return this.oauth2Client.getTokens();\n }\n /** Check if the stored token is expired (with 60s buffer). */\n async isTokenExpired(): Promise<boolean> {\n return this.oauth2Client.isTokenExpired();\n }\n /** Refresh the access token using the stored refresh token. */\n async refreshToken(): Promise<OAuth2Tokens> {\n return this.oauth2Client.refreshAccessToken();\n }\n /** Get a valid access token, refreshing if needed. */\n async getValidAccessToken(): Promise<string> {\n return this.oauth2Client.getValidAccessToken();\n }\n /** Logout: revoke token and clear storage. */\n async logout(): Promise<void> {\n return this.oauth2Client.logout();\n }\n}\n","import { type OAuth2Tokens } from '@docyrus/api-client';\n\nimport { type HostSignInMessage, type HostToAppMessage } from '../types';\n\nimport {\n DEFAULT_ALLOWED_HOST_PATTERN,\n IFRAME_TOKEN_REFRESH_TIMEOUT_MS\n} from '../constants';\n\ntype IframeAuthCallback = (tokens: OAuth2Tokens) => void;\n\n/**\n * Iframe authentication via postMessage.\n *\n * Protocol:\n * - Host → App: { type: \"signin\", accessToken, refreshToken }\n * - App → Host: { type: \"token-refresh-request\" }\n *\n * Every incoming message's event.origin is validated against\n * the allowed host pattern before processing.\n */\nexport class IframeAuth {\n private allowedOrigins: string[];\n private allowedPattern: RegExp;\n private onTokensReceived: IframeAuthCallback | null = null;\n private refreshPromise: {\n resolve: (tokens: OAuth2Tokens) => void;\n reject: (error: Error) => void;\n timeoutId: ReturnType<typeof setTimeout>;\n } | null = null;\n private messageHandler: ((ev: MessageEvent) => void) | null = null;\n constructor(\n allowedOrigins?: string[],\n allowedPattern?: RegExp\n ) {\n this.allowedOrigins = allowedOrigins ?? [];\n this.allowedPattern = allowedPattern ?? DEFAULT_ALLOWED_HOST_PATTERN;\n }\n /**\n * Start listening for postMessage events from the host.\n * The callback is invoked every time valid tokens are received.\n */\n start(onTokensReceived: IframeAuthCallback): void {\n this.onTokensReceived = onTokensReceived;\n this.messageHandler = this.handleMessage.bind(this);\n window.addEventListener('message', this.messageHandler);\n }\n /** Stop listening for postMessage events. */\n stop(): void {\n if (this.messageHandler) {\n window.removeEventListener('message', this.messageHandler);\n this.messageHandler = null;\n }\n this.rejectPendingRefresh('IframeAuth stopped');\n }\n /**\n * Request fresh tokens from the host.\n * Posts a \"token-refresh-request\" to the host, then waits for\n * a \"signin\" response.\n *\n * Only one refresh request can be in-flight at a time.\n * Additional callers share the same promise.\n */\n requestTokenRefresh(): Promise<OAuth2Tokens> {\n if (this.refreshPromise) {\n return new Promise<OAuth2Tokens>((resolve, reject) => {\n const existing = this.refreshPromise!;\n const originalResolve = existing.resolve;\n const originalReject = existing.reject;\n\n existing.resolve = (tokens) => {\n originalResolve(tokens);\n resolve(tokens);\n };\n existing.reject = (error) => {\n originalReject(error);\n reject(error);\n };\n });\n }\n\n return new Promise<OAuth2Tokens>((resolve, reject) => {\n const timeoutId = setTimeout(() => {\n this.refreshPromise = null;\n reject(new Error('Token refresh request timed out'));\n }, IFRAME_TOKEN_REFRESH_TIMEOUT_MS);\n\n this.refreshPromise = { resolve, reject, timeoutId };\n\n window.parent.postMessage({ type: 'token-refresh-request' }, '*');\n });\n }\n private isOriginAllowed(origin: string): boolean {\n if (this.allowedOrigins.includes(origin)) return true;\n\n return this.allowedPattern.test(origin);\n }\n private handleMessage(ev: MessageEvent): void {\n if (!this.isOriginAllowed(ev.origin)) return;\n\n const data = ev.data as HostToAppMessage;\n\n if (!data || typeof data !== 'object' || data.type !== 'signin') return;\n\n const signinData = data as HostSignInMessage;\n\n if (!signinData.accessToken) return;\n\n const tokens: OAuth2Tokens = {\n accessToken: signinData.accessToken,\n refreshToken: signinData.refreshToken,\n /*\n * We don't know the exact expiry from the host, so estimate 1 hour.\n * The host will send new tokens before expiry via the refresh mechanism.\n */\n expiresAt: Math.floor(Date.now() / 1000) + 3600,\n scope: ''\n };\n\n this.onTokensReceived?.(tokens);\n\n if (this.refreshPromise) {\n clearTimeout(this.refreshPromise.timeoutId);\n this.refreshPromise.resolve(tokens);\n this.refreshPromise = null;\n }\n }\n private rejectPendingRefresh(reason: string): void {\n if (this.refreshPromise) {\n clearTimeout(this.refreshPromise.timeoutId);\n this.refreshPromise.reject(new Error(reason));\n this.refreshPromise = null;\n }\n }\n}\n","import {\n RestApiClient,\n type OAuth2Tokens,\n type TokenManager\n} from '@docyrus/api-client';\n\nimport { type AuthMode, type AuthStatus, type DocyrusAuthConfig } from '../types';\n\nimport { detectAuthMode } from './auth-detector';\nimport { StandaloneOAuth2Auth } from './oauth2-auth';\nimport { IframeAuth } from './iframe-auth';\nimport { DEFAULT_API_URL, TOKEN_EXPIRY_BUFFER_SECONDS } from '../constants';\n\nexport type AuthStateListener = (state: {\n status: AuthStatus;\n tokens: OAuth2Tokens | null;\n error: Error | null;\n}) => void;\n\n/**\n * Unified authentication manager.\n * Orchestrates standalone OAuth2 and iframe postMessage modes,\n * exposes a pre-configured RestApiClient, and manages token lifecycle.\n */\nexport class AuthManager {\n private mode: AuthMode;\n private status: AuthStatus = 'loading';\n private tokens: OAuth2Tokens | null = null;\n private error: Error | null = null;\n private client: RestApiClient | null = null;\n private standaloneAuth: StandaloneOAuth2Auth | null = null;\n private iframeAuth: IframeAuth | null = null;\n private tokenRefreshTimer: ReturnType<typeof setTimeout> | null = null;\n private listeners: Set<AuthStateListener> = new Set();\n private config: DocyrusAuthConfig;\n constructor(config: DocyrusAuthConfig = {}) {\n this.config = config;\n this.mode = config.forceMode\n ?? detectAuthMode(config.allowedHostOrigins);\n }\n getMode(): AuthMode {\n return this.mode;\n }\n getStatus(): AuthStatus {\n return this.status;\n }\n getTokens(): OAuth2Tokens | null {\n return this.tokens;\n }\n getClient(): RestApiClient | null {\n return this.client;\n }\n getError(): Error | null {\n return this.error;\n }\n subscribe(listener: AuthStateListener): () => void {\n this.listeners.add(listener);\n\n return () => this.listeners.delete(listener);\n }\n private notify(): void {\n for (const listener of this.listeners) {\n listener({\n status: this.status,\n tokens: this.tokens,\n error: this.error\n });\n }\n }\n /** Initialize the auth manager. Must be called once on mount. */\n async initialize(): Promise<void> {\n try {\n if (this.mode === 'iframe') {\n this.initializeIframeMode();\n } else {\n await this.initializeStandaloneMode();\n }\n } catch (err) {\n this.error = err instanceof Error ? err : new Error(String(err));\n this.status = 'unauthenticated';\n this.notify();\n }\n }\n /**\n * Standalone mode initialization.\n * 1. Check if we're returning from an OAuth callback.\n * 2. If not, check for existing tokens in localStorage.\n * 3. If tokens are expired, try to refresh.\n */\n private async initializeStandaloneMode(): Promise<void> {\n this.standaloneAuth = new StandaloneOAuth2Auth(this.config);\n\n // Case 1: We're at the callback URL after OAuth redirect\n if (this.standaloneAuth.isCallbackUrl()) {\n const tokens = await this.standaloneAuth.handleCallback();\n\n this.setAuthenticated(tokens);\n\n // Navigate away from the callback URL\n const returnUrl = this.standaloneAuth.getReturnUrl();\n\n if (returnUrl) {\n window.location.replace(returnUrl);\n } else {\n window.history.replaceState({}, '', '/');\n }\n\n return;\n }\n\n // Case 2: Check for existing tokens\n const stored = await this.standaloneAuth.getStoredTokens();\n\n if (stored) {\n const isExpired = await this.standaloneAuth.isTokenExpired();\n\n if (!isExpired) {\n this.setAuthenticated(stored);\n\n return;\n }\n\n // Token expired, try refresh\n if (stored.refreshToken) {\n try {\n const newTokens = await this.standaloneAuth.refreshToken();\n\n this.setAuthenticated(newTokens);\n\n return;\n } catch {\n // Refresh failed, require re-login\n }\n }\n }\n\n // Case 3: No tokens or refresh failed\n this.status = 'unauthenticated';\n this.notify();\n }\n /**\n * Iframe mode initialization.\n * Listen for postMessage tokens from the host.\n * Status remains 'loading' until the host sends the first signin message.\n */\n private initializeIframeMode(): void {\n this.iframeAuth = new IframeAuth(this.config.allowedHostOrigins);\n\n this.iframeAuth.start((tokens: OAuth2Tokens) => {\n this.setAuthenticated(tokens);\n });\n }\n /** Initiate sign-in. Only meaningful in standalone mode. */\n async signIn(): Promise<void> {\n if (this.mode !== 'standalone' || !this.standaloneAuth) return;\n\n await this.standaloneAuth.initiateLogin();\n }\n /**\n * Sign out.\n * Standalone: revoke token, clear localStorage, reset state.\n * Iframe: clear local state (host manages the actual session).\n */\n async signOut(): Promise<void> {\n this.clearTokenRefreshTimer();\n\n if (this.mode === 'standalone' && this.standaloneAuth) {\n await this.standaloneAuth.logout();\n }\n\n if (this.mode === 'iframe' && this.iframeAuth) {\n this.iframeAuth.stop();\n }\n\n this.tokens = null;\n this.client = null;\n this.status = 'unauthenticated';\n this.error = null;\n this.notify();\n }\n /**\n * Called when valid tokens are received (either mode).\n * Creates/updates the RestApiClient and schedules token refresh.\n */\n private setAuthenticated(tokens: OAuth2Tokens): void {\n this.tokens = tokens;\n this.error = null;\n this.status = 'authenticated';\n\n this.createClient();\n this.scheduleTokenRefresh(tokens);\n this.notify();\n }\n /**\n * Create a RestApiClient with a custom TokenManager that proactively\n * refreshes the token in getToken(). This is necessary because\n * BaseApiClient.getAccessToken() only calls tokenManager.getToken()\n * and does NOT auto-call refreshToken() when the token is expired.\n */\n private createClient(): void {\n const apiUrl = this.config.apiUrl ?? DEFAULT_API_URL;\n\n const tokenManager: TokenManager = {\n getToken: () => this.getValidToken(),\n setToken: (token: string) => {\n if (this.tokens) {\n this.tokens = { ...this.tokens, accessToken: token };\n }\n },\n clearToken: () => {\n this.tokens = null;\n },\n refreshToken: () => this.handleTokenRefresh()\n };\n\n this.client = new RestApiClient({\n baseURL: apiUrl,\n tokenManager\n });\n }\n /**\n * Get a valid access token, proactively refreshing if expired.\n * Called by the RestApiClient on every request via tokenManager.getToken().\n */\n private async getValidToken(): Promise<string | null> {\n if (!this.tokens) return null;\n\n // Check if token is expired (with buffer)\n if (this.tokens.expiresAt) {\n const nowSec = Math.floor(Date.now() / 1000);\n\n if (nowSec >= this.tokens.expiresAt - TOKEN_EXPIRY_BUFFER_SECONDS) {\n try {\n const freshToken = await this.handleTokenRefresh();\n\n return freshToken;\n } catch {\n return null;\n }\n }\n }\n\n return this.tokens.accessToken;\n }\n /**\n * Handle token refresh depending on mode.\n * Standalone: use OAuth2Client.getValidAccessToken() which auto-refreshes.\n * Iframe: send postMessage to host requesting new tokens.\n */\n private async handleTokenRefresh(): Promise<string> {\n if (this.mode === 'standalone' && this.standaloneAuth) {\n const newTokens = await this.standaloneAuth.refreshToken();\n\n this.tokens = newTokens;\n this.scheduleTokenRefresh(newTokens);\n this.notify();\n\n return newTokens.accessToken;\n }\n\n if (this.mode === 'iframe' && this.iframeAuth) {\n const newTokens = await this.iframeAuth.requestTokenRefresh();\n\n return newTokens.accessToken;\n }\n\n throw new Error('Cannot refresh token: auth not initialized');\n }\n /** Schedule proactive token refresh before expiry. */\n private scheduleTokenRefresh(tokens: OAuth2Tokens): void {\n this.clearTokenRefreshTimer();\n\n if (!tokens.expiresAt) return;\n\n const expiresAtMs = tokens.expiresAt * 1000;\n const bufferMs = TOKEN_EXPIRY_BUFFER_SECONDS * 1000;\n const refreshInMs = expiresAtMs - bufferMs - Date.now();\n\n if (refreshInMs <= 0) {\n // Already needs refresh\n this.handleTokenRefresh().catch((err) => {\n this.error = err instanceof Error ? err : new Error(String(err));\n this.status = 'unauthenticated';\n this.notify();\n });\n\n return;\n }\n\n this.tokenRefreshTimer = setTimeout(() => {\n this.handleTokenRefresh().catch((err) => {\n this.error = err instanceof Error ? err : new Error(String(err));\n this.status = 'unauthenticated';\n this.notify();\n });\n }, refreshInMs);\n }\n private clearTokenRefreshTimer(): void {\n if (this.tokenRefreshTimer) {\n clearTimeout(this.tokenRefreshTimer);\n this.tokenRefreshTimer = null;\n }\n }\n /** Cleanup: remove listeners, timers, stop iframe auth. */\n destroy(): void {\n this.clearTokenRefreshTimer();\n\n if (this.iframeAuth) this.iframeAuth.stop();\n\n this.listeners.clear();\n }\n}\n","import {\n createContext,\n useEffect,\n useRef,\n useState,\n useMemo,\n type ReactNode\n} from 'react';\n\nimport { type OAuth2Tokens, type RestApiClient } from '@docyrus/api-client';\n\nimport {\n type AuthStatus,\n type AuthMode,\n type DocyrusAuthContextValue,\n type DocyrusAuthConfig\n} from '../types';\n\nimport { AuthManager } from '../core/auth-manager';\n\nexport const DocyrusAuthContext = createContext<DocyrusAuthContextValue | null>(null);\n\nexport interface DocyrusAuthProviderProps extends DocyrusAuthConfig {\n children: ReactNode;\n}\n\nexport function DocyrusAuthProvider({\n children,\n ...config\n}: DocyrusAuthProviderProps) {\n const [status, setStatus] = useState<AuthStatus>('loading');\n const [tokens, setTokens] = useState<OAuth2Tokens | null>(null);\n const [client, setClient] = useState<RestApiClient | null>(null);\n const [mode, setMode] = useState<AuthMode | null>(null);\n const [error, setError] = useState<Error | null>(null);\n const managerRef = useRef<AuthManager | null>(null);\n\n useEffect(() => {\n const manager = new AuthManager(config);\n\n managerRef.current = manager;\n setMode(manager.getMode());\n\n const unsubscribe = manager.subscribe((state) => {\n setStatus(state.status);\n setTokens(state.tokens);\n setError(state.error);\n setClient(manager.getClient());\n });\n\n manager.initialize();\n\n return () => {\n unsubscribe();\n manager.destroy();\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n const signIn = useMemo(() => {\n return () => {\n managerRef.current?.signIn();\n };\n }, []);\n\n const signOut = useMemo(() => {\n return async () => {\n await managerRef.current?.signOut();\n };\n }, []);\n\n const value = useMemo<DocyrusAuthContextValue>(() => ({\n status,\n mode,\n client,\n tokens,\n signIn,\n signOut,\n error\n }), [\n status,\n mode,\n client,\n tokens,\n signIn,\n signOut,\n error\n ]);\n\n return (\n <DocyrusAuthContext.Provider value={value}>\n {children}\n </DocyrusAuthContext.Provider>\n );\n}\n","import { useContext } from 'react';\n\nimport { type DocyrusAuthContextValue } from '../types';\n\nimport { DocyrusAuthContext } from '../components/docyrus-auth-provider';\n\n/**\n * Hook to access Docyrus auth state.\n * Must be used within a <DocyrusAuthProvider>.\n *\n * Returns: { status, mode, tokens, signIn, signOut, client, error }\n */\nexport function useDocyrusAuth(): DocyrusAuthContextValue {\n const context = useContext(DocyrusAuthContext);\n\n if (!context) {\n throw new Error(\n 'useDocyrusAuth must be used within a <DocyrusAuthProvider>. '\n + 'Wrap your app with <DocyrusAuthProvider> to provide auth context.'\n );\n }\n\n return context;\n}\n","import { type RestApiClient } from '@docyrus/api-client';\n\nimport { useDocyrusAuth } from './use-docyrus-auth';\n\n/**\n * Hook to get the pre-configured RestApiClient.\n * Returns null when not authenticated.\n *\n * Throws if used outside DocyrusAuthProvider.\n */\nexport function useDocyrusClient(): RestApiClient | null {\n const { client } = useDocyrusAuth();\n\n return client;\n}\n","import { type ReactNode, type CSSProperties } from 'react';\n\nimport { type AuthStatus } from '../types';\n\nimport { useDocyrusAuth } from '../hooks/use-docyrus-auth';\n\nexport interface SignInButtonProps {\n /** Custom label. Defaults to \"Sign in with Docyrus\" */\n label?: string;\n /** CSS class name for the button */\n className?: string;\n /** Inline styles */\n style?: CSSProperties;\n /** Custom render function. Receives signIn and status. */\n children?: (props: {\n signIn: () => void;\n status: AuthStatus;\n }) => ReactNode;\n /** Disable the button */\n disabled?: boolean;\n}\n\n/**\n * \"Sign in with Docyrus\" button.\n *\n * In iframe mode: renders nothing (auth is handled by the host).\n * When authenticated: renders nothing.\n *\n * Intentionally unstyled — use className, style, or the render-prop\n * children pattern for full customization.\n */\nexport function SignInButton({\n label = 'Sign in with Docyrus',\n className,\n style,\n children,\n disabled = false\n}: SignInButtonProps) {\n const { signIn, status, mode } = useDocyrusAuth();\n\n // In iframe mode, the host handles sign-in\n if (mode === 'iframe') return null;\n\n // Already authenticated\n if (status === 'authenticated') return null;\n\n // Custom render\n if (children) {\n return <>{children({ signIn, status })}</>;\n }\n\n return (\n <button\n type=\"button\"\n onClick={signIn}\n disabled={disabled || status === 'loading'}\n className={className}\n style={style}>\n {status === 'loading' ? 'Loading...' : label}\n </button>\n );\n}\n"]}