@rool-dev/sdk 0.11.3-dev.5dfa2e5 → 0.11.3-dev.a6a4bb1
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 +91 -4
- package/dist/auth-base.d.ts +100 -0
- package/dist/auth-base.d.ts.map +1 -0
- package/dist/auth-base.js +377 -0
- package/dist/auth-base.js.map +1 -0
- package/dist/auth-browser.d.ts +6 -77
- package/dist/auth-browser.d.ts.map +1 -1
- package/dist/auth-browser.js +32 -328
- package/dist/auth-browser.js.map +1 -1
- package/dist/auth-native.d.ts +72 -0
- package/dist/auth-native.d.ts.map +1 -0
- package/dist/auth-native.js +260 -0
- package/dist/auth-native.js.map +1 -0
- package/dist/auth.d.ts +17 -1
- package/dist/auth.d.ts.map +1 -1
- package/dist/auth.js +36 -5
- package/dist/auth.js.map +1 -1
- package/dist/client.d.ts +23 -1
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +36 -0
- package/dist/client.js.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/reroute.d.ts.map +1 -1
- package/dist/reroute.js +9 -0
- package/dist/reroute.js.map +1 -1
- package/dist/subscription.d.ts.map +1 -1
- package/dist/subscription.js +3 -1
- package/dist/subscription.js.map +1 -1
- package/dist/types.d.ts +45 -3
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -142,6 +142,74 @@ if (!authenticated) {
|
|
|
142
142
|
if (!authenticated) throw new Error('Login required');
|
|
143
143
|
```
|
|
144
144
|
|
|
145
|
+
### Native (Capacitor, Cordova, Tauri, ...)
|
|
146
|
+
|
|
147
|
+
Use the native PKCE provider for JS app shells that sign in through an external
|
|
148
|
+
system browser. `login()`/`signup()` open the auth server's `/authorize` page
|
|
149
|
+
via the `openExternal` callback you supply; when the deep link returns, feed it
|
|
150
|
+
to `client.handleAuthRedirect(url)` from your platform's deep-link handler. The
|
|
151
|
+
code is exchanged for a session at `/token` and tokens are stored like the
|
|
152
|
+
browser provider.
|
|
153
|
+
|
|
154
|
+
```typescript
|
|
155
|
+
import { RoolClient, NativePkceAuthProvider } from '@rool-dev/sdk';
|
|
156
|
+
import { Browser } from '@capacitor/browser';
|
|
157
|
+
import { App } from '@capacitor/app';
|
|
158
|
+
|
|
159
|
+
const client = new RoolClient({
|
|
160
|
+
authProvider: new NativePkceAuthProvider({
|
|
161
|
+
redirectUri: 'roolandroidauth://auth/callback', // must match the server allowlist
|
|
162
|
+
defaultProvider: 'google', // 'google' | 'apple'
|
|
163
|
+
openExternal: (url) => Browser.open({ url }),
|
|
164
|
+
}),
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
// Complete sign-in when the OS hands the app its deep link.
|
|
168
|
+
App.addListener('appUrlOpen', async ({ url }) => {
|
|
169
|
+
if (await client.handleAuthRedirect(url)) {
|
|
170
|
+
await Browser.close();
|
|
171
|
+
// Now authenticated — refresh your UI.
|
|
172
|
+
}
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
if (!(await client.initialize())) {
|
|
176
|
+
// Opens the system browser; completion arrives via the listener above.
|
|
177
|
+
await client.login('My App'); // pass { provider: 'apple' } to override the default
|
|
178
|
+
}
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
#### Email + password and magic links (native)
|
|
182
|
+
|
|
183
|
+
The native provider also supports email/password sign-in and magic links —
|
|
184
|
+
no system browser, the server returns the token set as JSON directly.
|
|
185
|
+
|
|
186
|
+
```typescript
|
|
187
|
+
// Password sign-in
|
|
188
|
+
const result = await client.signInWithPassword(email, password);
|
|
189
|
+
if (result.status === 'signed_in') {
|
|
190
|
+
// authenticated — refresh your UI
|
|
191
|
+
} else {
|
|
192
|
+
// status === 'verify_required': the email isn't verified yet and the server
|
|
193
|
+
// has emailed a magic link. Tell the user to check their inbox.
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Or request a magic link explicitly
|
|
197
|
+
await client.requestMagicLink(email);
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
The magic link carries a `?verify=<token>` param; complete sign-in by passing
|
|
201
|
+
that token to `client.verify(token)` once the link lands back in the app.
|
|
202
|
+
|
|
203
|
+
> **⚠️ Magic links open the website, not the app, until Universal Links / App
|
|
204
|
+
> Links are configured.** The emailed link is an `https://` URL for the Rool
|
|
205
|
+
> web app. On native, an `https` link only re-opens your app if you've set up
|
|
206
|
+
> [iOS Universal Links](https://developer.apple.com/ios/universal-links/) /
|
|
207
|
+
> [Android App Links](https://developer.android.com/training/app-links) for that
|
|
208
|
+
> domain (custom-scheme deep links don't apply to email links). **Without that
|
|
209
|
+
> setup the magic link completes sign-in in the browser/website, not in the
|
|
210
|
+
> native app** — so for now treat magic links on native as a website hand-off,
|
|
211
|
+
> and prefer password or social sign-in for an in-app experience.
|
|
212
|
+
|
|
145
213
|
### Auth API
|
|
146
214
|
|
|
147
215
|
| Method | Description |
|
|
@@ -150,6 +218,9 @@ if (!authenticated) throw new Error('Login required');
|
|
|
150
218
|
| `login(appName, params?): Promise<void>` | Start login flow. |
|
|
151
219
|
| `signup(appName, params?): Promise<void>` | Start signup flow. |
|
|
152
220
|
| `verify(token): Promise<boolean>` | Complete email verification token flow; returns `false` when the active auth provider does not implement verification. |
|
|
221
|
+
| `handleAuthRedirect(url): Promise<boolean>` | Complete a native PKCE sign-in from a deep-link callback URL. Returns `false` when the active auth provider does not implement it. |
|
|
222
|
+
| `signInWithPassword(email, password): Promise<PasswordSignInResult>` | Email + password sign-in (native provider). Resolves `{ status: 'signed_in' }`, or `{ status: 'verify_required' }` when the email is unverified (a magic link was emailed). Rejects on bad credentials. |
|
|
223
|
+
| `requestMagicLink(email): Promise<void>` | Email the user a magic sign-in link (native provider). See the caveat below — on native the link opens the **website**, not the app, until Universal Links / App Links are configured. |
|
|
153
224
|
| `logout(): void` | Clear auth state and close open spaces. |
|
|
154
225
|
| `isAuthenticated(): Promise<boolean>` | Whether credentials are held locally. No network call — a server outage does not read as logged out. |
|
|
155
226
|
| `getAuthUser(): AuthUser` | Return auth identity decoded from the token. |
|
|
@@ -671,6 +742,10 @@ Archives include objects, metadata, channels/conversations, and file storage.
|
|
|
671
742
|
## Data Types
|
|
672
743
|
|
|
673
744
|
```typescript
|
|
745
|
+
// Outcome of signInWithPassword. 'verify_required' means the account's email
|
|
746
|
+
// isn't verified yet and the server has emailed a magic link.
|
|
747
|
+
type PasswordSignInResult = { status: 'signed_in' | 'verify_required' };
|
|
748
|
+
|
|
674
749
|
type FieldType =
|
|
675
750
|
| { kind: 'string' }
|
|
676
751
|
| { kind: 'number' }
|
|
@@ -717,10 +792,22 @@ interface SpaceInvite {
|
|
|
717
792
|
useCount: number;
|
|
718
793
|
}
|
|
719
794
|
|
|
720
|
-
// Outcome of the invite email send
|
|
721
|
-
//
|
|
722
|
-
//
|
|
723
|
-
|
|
795
|
+
// Outcome of the invite email send. Null when no email was involved (open link).
|
|
796
|
+
// The invite is always minted and its `url` is usable regardless of this value;
|
|
797
|
+
// only the email delivery is reflected here.
|
|
798
|
+
// - 'sent': email dispatched
|
|
799
|
+
// - 'not_configured': server has no mail provider (local dev)
|
|
800
|
+
// - 'failed': provider rejected the send
|
|
801
|
+
// - 'cooldown': a recent invite to this same address was already emailed
|
|
802
|
+
// - 'rate_limited': the inviter hit their daily email-invite cap
|
|
803
|
+
// Treat unknown values as not sent.
|
|
804
|
+
type InviteEmailStatus =
|
|
805
|
+
| 'sent'
|
|
806
|
+
| 'not_configured'
|
|
807
|
+
| 'failed'
|
|
808
|
+
| 'cooldown'
|
|
809
|
+
| 'rate_limited'
|
|
810
|
+
| (string & {});
|
|
724
811
|
|
|
725
812
|
interface SpaceInviteCreated {
|
|
726
813
|
inviteId: string;
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import type { AuthProvider, AuthUser } from './types.js';
|
|
2
|
+
import { type Logger } from './logger.js';
|
|
3
|
+
export interface BaseTokenAuthConfig {
|
|
4
|
+
/** Auth URL (e.g. https://rool.dev/auth). Injected by RoolClient if omitted. */
|
|
5
|
+
authUrl?: string;
|
|
6
|
+
/** Injected by RoolClient if omitted. */
|
|
7
|
+
logger?: Logger;
|
|
8
|
+
/** Injected by RoolClient if omitted, so auth-state reaches client events. */
|
|
9
|
+
onAuthStateChanged?: (authenticated: boolean) => void;
|
|
10
|
+
}
|
|
11
|
+
export declare abstract class BaseTokenAuthProvider implements AuthProvider {
|
|
12
|
+
protected logger: Logger;
|
|
13
|
+
private _authUrl;
|
|
14
|
+
private _onAuthStateChanged;
|
|
15
|
+
private refreshPromise;
|
|
16
|
+
private refreshTimeoutId;
|
|
17
|
+
private boundVisibilityHandler;
|
|
18
|
+
constructor(config?: BaseTokenAuthConfig);
|
|
19
|
+
setAuthUrl(url: string): void;
|
|
20
|
+
setLogger(logger: Logger): void;
|
|
21
|
+
setAuthStateChangedHandler(handler: (authenticated: boolean) => void): void;
|
|
22
|
+
abstract initialize(): boolean;
|
|
23
|
+
abstract login(appName: string, params?: Record<string, string>): Promise<void> | void;
|
|
24
|
+
abstract signup(appName: string, params?: Record<string, string>): Promise<void> | void;
|
|
25
|
+
/**
|
|
26
|
+
* Check if user is currently authenticated.
|
|
27
|
+
*
|
|
28
|
+
* This reports identity (do we hold credentials), NOT server reachability.
|
|
29
|
+
* It deliberately does not perform a network refresh: a backend outage must
|
|
30
|
+
* not read as "logged out". A genuinely invalid/expired refresh token
|
|
31
|
+
* surfaces later as a 401 on first real use, which clears tokens and fires
|
|
32
|
+
* onAuthStateChanged(false) — the only path that ends the session.
|
|
33
|
+
*/
|
|
34
|
+
isAuthenticated(): Promise<boolean>;
|
|
35
|
+
/**
|
|
36
|
+
* Get current access token and rool token, refreshing if expired.
|
|
37
|
+
* Returns undefined if not authenticated.
|
|
38
|
+
*/
|
|
39
|
+
getTokens(): Promise<{
|
|
40
|
+
accessToken: string;
|
|
41
|
+
roolToken: string;
|
|
42
|
+
} | undefined>;
|
|
43
|
+
/**
|
|
44
|
+
* Get auth identity decoded from JWT token.
|
|
45
|
+
*/
|
|
46
|
+
getAuthUser(): AuthUser;
|
|
47
|
+
/**
|
|
48
|
+
* Complete an email verification flow. Exchanges a verify JWT (from the
|
|
49
|
+
* verification email link) for a fresh token set and signs the user in.
|
|
50
|
+
*/
|
|
51
|
+
verify(token: string): Promise<boolean>;
|
|
52
|
+
/**
|
|
53
|
+
* Logout - clear all tokens and state.
|
|
54
|
+
*/
|
|
55
|
+
logout(): void;
|
|
56
|
+
/**
|
|
57
|
+
* Destroy auth manager - clear refresh timers and listeners.
|
|
58
|
+
*/
|
|
59
|
+
destroy(): void;
|
|
60
|
+
/** Auth URL without trailing slash */
|
|
61
|
+
protected get authBaseUrl(): string;
|
|
62
|
+
protected notifyAuthState(authenticated: boolean): void;
|
|
63
|
+
/** Persist a fresh token set and (re)arm the background refresh timer. */
|
|
64
|
+
protected acceptTokens(accessToken: string, refreshToken: string | null, roolToken: string | null, expiresAt: number): void;
|
|
65
|
+
/** Arm auto-refresh + visibility re-scheduling. Call from initialize(). */
|
|
66
|
+
protected initBase(): void;
|
|
67
|
+
/**
|
|
68
|
+
* Overridable hook: clear subclass-owned transient state on logout/cancel
|
|
69
|
+
* (e.g. an in-flight OAuth `state` or PKCE verifier). No-op by default.
|
|
70
|
+
*/
|
|
71
|
+
protected clearTransientState(): void;
|
|
72
|
+
protected get storagePrefix(): string;
|
|
73
|
+
/** Build a storage key scoped to this auth endpoint. */
|
|
74
|
+
protected keyFor(name: string): string;
|
|
75
|
+
protected get storageKeys(): {
|
|
76
|
+
readonly access: `${string}access_token`;
|
|
77
|
+
readonly refresh: `${string}refresh_token`;
|
|
78
|
+
readonly rool: `${string}rool_token`;
|
|
79
|
+
readonly expiresAt: `${string}token_expires_at`;
|
|
80
|
+
};
|
|
81
|
+
protected readString(key: string): string | null;
|
|
82
|
+
protected writeString(key: string, value: string): void;
|
|
83
|
+
protected removeString(key: string): void;
|
|
84
|
+
protected scheduleTokenRefresh(): void;
|
|
85
|
+
protected writeTokens(accessToken: string | null, refreshToken: string | null, expiresAt: number | null): void;
|
|
86
|
+
protected writeRoolToken(token: string | null): void;
|
|
87
|
+
protected clearTokens(): void;
|
|
88
|
+
protected readAccessToken(): string | null;
|
|
89
|
+
protected readRoolToken(): string;
|
|
90
|
+
protected readExpiresAt(): number | null;
|
|
91
|
+
/**
|
|
92
|
+
* Get a short hash of the auth URL for scoping storage by endpoint.
|
|
93
|
+
*/
|
|
94
|
+
private get endpointHash();
|
|
95
|
+
private tryRefreshToken;
|
|
96
|
+
private listenForVisibility;
|
|
97
|
+
private cancelScheduledRefresh;
|
|
98
|
+
private decodeAuthUser;
|
|
99
|
+
}
|
|
100
|
+
//# sourceMappingURL=auth-base.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth-base.d.ts","sourceRoot":"","sources":["../src/auth-base.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,377 @@
|
|
|
1
|
+
// =============================================================================
|
|
2
|
+
// Base token auth provider
|
|
3
|
+
// Shared machinery for browser-style providers: endpoint-scoped localStorage
|
|
4
|
+
// token storage, expiry-aware getTokens with deduplicated background refresh,
|
|
5
|
+
// visibility-driven re-scheduling, and JWT identity decode.
|
|
6
|
+
//
|
|
7
|
+
// Subclasses implement only how a session STARTS (login/signup) and how the
|
|
8
|
+
// callback arrives — BrowserAuthProvider via a URL fragment,
|
|
9
|
+
// NativePkceAuthProvider via a deep link + code exchange. Everything after the
|
|
10
|
+
// tokens land (storage, refresh, decode) is shared here.
|
|
11
|
+
// =============================================================================
|
|
12
|
+
import { defaultLogger } from './logger.js';
|
|
13
|
+
const REFRESH_BUFFER_MS = 15 * 60 * 1000; // Refresh 15 minutes before expiry
|
|
14
|
+
const DEFAULT_STORAGE_PREFIX = 'rool_';
|
|
15
|
+
export class BaseTokenAuthProvider {
|
|
16
|
+
logger;
|
|
17
|
+
_authUrl;
|
|
18
|
+
_onAuthStateChanged;
|
|
19
|
+
refreshPromise = null;
|
|
20
|
+
refreshTimeoutId = null;
|
|
21
|
+
boundVisibilityHandler = null;
|
|
22
|
+
constructor(config = {}) {
|
|
23
|
+
this._authUrl = (config.authUrl ?? '').replace(/\/+$/, '');
|
|
24
|
+
this.logger = config.logger ?? defaultLogger;
|
|
25
|
+
this._onAuthStateChanged = config.onAuthStateChanged ?? (() => { });
|
|
26
|
+
}
|
|
27
|
+
// Injection hooks — RoolClient's AuthManager calls these for providers it
|
|
28
|
+
// didn't construct itself, so a custom provider gets the resolved auth URL,
|
|
29
|
+
// logger, and a handler that bridges auth-state to client events.
|
|
30
|
+
setAuthUrl(url) {
|
|
31
|
+
this._authUrl = url.replace(/\/+$/, '');
|
|
32
|
+
}
|
|
33
|
+
setLogger(logger) {
|
|
34
|
+
this.logger = logger;
|
|
35
|
+
}
|
|
36
|
+
setAuthStateChangedHandler(handler) {
|
|
37
|
+
this._onAuthStateChanged = handler;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Check if user is currently authenticated.
|
|
41
|
+
*
|
|
42
|
+
* This reports identity (do we hold credentials), NOT server reachability.
|
|
43
|
+
* It deliberately does not perform a network refresh: a backend outage must
|
|
44
|
+
* not read as "logged out". A genuinely invalid/expired refresh token
|
|
45
|
+
* surfaces later as a 401 on first real use, which clears tokens and fires
|
|
46
|
+
* onAuthStateChanged(false) — the only path that ends the session.
|
|
47
|
+
*/
|
|
48
|
+
async isAuthenticated() {
|
|
49
|
+
return this.readAccessToken() !== null;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Get current access token and rool token, refreshing if expired.
|
|
53
|
+
* Returns undefined if not authenticated.
|
|
54
|
+
*/
|
|
55
|
+
async getTokens() {
|
|
56
|
+
const accessToken = this.readAccessToken();
|
|
57
|
+
if (!accessToken)
|
|
58
|
+
return undefined;
|
|
59
|
+
// Token expired or about to expire - try refresh
|
|
60
|
+
const expiresAt = this.readExpiresAt();
|
|
61
|
+
if (expiresAt && Date.now() >= expiresAt - REFRESH_BUFFER_MS) {
|
|
62
|
+
const refreshed = await this.tryRefreshToken();
|
|
63
|
+
if (!refreshed)
|
|
64
|
+
return undefined;
|
|
65
|
+
const refreshedToken = this.readAccessToken();
|
|
66
|
+
if (!refreshedToken)
|
|
67
|
+
return undefined;
|
|
68
|
+
return { accessToken: refreshedToken, roolToken: this.readRoolToken() };
|
|
69
|
+
}
|
|
70
|
+
return { accessToken, roolToken: this.readRoolToken() };
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Get auth identity decoded from JWT token.
|
|
74
|
+
*/
|
|
75
|
+
getAuthUser() {
|
|
76
|
+
const accessToken = this.readAccessToken();
|
|
77
|
+
if (!accessToken)
|
|
78
|
+
return { email: null, name: null };
|
|
79
|
+
return this.decodeAuthUser(accessToken);
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Complete an email verification flow. Exchanges a verify JWT (from the
|
|
83
|
+
* verification email link) for a fresh token set and signs the user in.
|
|
84
|
+
*/
|
|
85
|
+
async verify(token) {
|
|
86
|
+
let response;
|
|
87
|
+
try {
|
|
88
|
+
response = await fetch(`${this.authBaseUrl}/verify-and-signin`, {
|
|
89
|
+
method: 'POST',
|
|
90
|
+
headers: { 'Content-Type': 'application/json' },
|
|
91
|
+
body: JSON.stringify({ token }),
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
catch (error) {
|
|
95
|
+
this.logger.error('[RoolClient] verify network error:', error);
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
if (!response.ok) {
|
|
99
|
+
this.logger.warn(`[RoolClient] verify failed: ${response.status}`);
|
|
100
|
+
return false;
|
|
101
|
+
}
|
|
102
|
+
let data;
|
|
103
|
+
try {
|
|
104
|
+
data = await response.json();
|
|
105
|
+
}
|
|
106
|
+
catch (error) {
|
|
107
|
+
this.logger.error('[RoolClient] verify response parse error:', error);
|
|
108
|
+
return false;
|
|
109
|
+
}
|
|
110
|
+
const idToken = data.id_token ?? null;
|
|
111
|
+
const refreshToken = data.refresh_token ?? null;
|
|
112
|
+
const expiresAt = data.expires_in ? Date.now() + data.expires_in * 1000 : NaN;
|
|
113
|
+
if (!idToken || !Number.isFinite(expiresAt)) {
|
|
114
|
+
this.logger.error('[RoolClient] verify response missing id_token or expires_in');
|
|
115
|
+
return false;
|
|
116
|
+
}
|
|
117
|
+
this.acceptTokens(idToken, refreshToken, data.rool_token ?? null, expiresAt);
|
|
118
|
+
this.notifyAuthState(true);
|
|
119
|
+
return true;
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Logout - clear all tokens and state.
|
|
123
|
+
*/
|
|
124
|
+
logout() {
|
|
125
|
+
this.clearTokens();
|
|
126
|
+
this.clearTransientState();
|
|
127
|
+
this.cancelScheduledRefresh();
|
|
128
|
+
this.notifyAuthState(false);
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Destroy auth manager - clear refresh timers and listeners.
|
|
132
|
+
*/
|
|
133
|
+
destroy() {
|
|
134
|
+
this.cancelScheduledRefresh();
|
|
135
|
+
if (this.boundVisibilityHandler) {
|
|
136
|
+
document.removeEventListener('visibilitychange', this.boundVisibilityHandler);
|
|
137
|
+
this.boundVisibilityHandler = null;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
// ===========================================================================
|
|
141
|
+
// Protected helpers for subclasses
|
|
142
|
+
// ===========================================================================
|
|
143
|
+
/** Auth URL without trailing slash */
|
|
144
|
+
get authBaseUrl() {
|
|
145
|
+
return this._authUrl;
|
|
146
|
+
}
|
|
147
|
+
notifyAuthState(authenticated) {
|
|
148
|
+
this._onAuthStateChanged(authenticated);
|
|
149
|
+
}
|
|
150
|
+
/** Persist a fresh token set and (re)arm the background refresh timer. */
|
|
151
|
+
acceptTokens(accessToken, refreshToken, roolToken, expiresAt) {
|
|
152
|
+
this.writeTokens(accessToken, refreshToken, expiresAt);
|
|
153
|
+
this.writeRoolToken(roolToken);
|
|
154
|
+
this.scheduleTokenRefresh();
|
|
155
|
+
}
|
|
156
|
+
/** Arm auto-refresh + visibility re-scheduling. Call from initialize(). */
|
|
157
|
+
initBase() {
|
|
158
|
+
this.scheduleTokenRefresh();
|
|
159
|
+
this.listenForVisibility();
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Overridable hook: clear subclass-owned transient state on logout/cancel
|
|
163
|
+
* (e.g. an in-flight OAuth `state` or PKCE verifier). No-op by default.
|
|
164
|
+
*/
|
|
165
|
+
clearTransientState() { }
|
|
166
|
+
get storagePrefix() {
|
|
167
|
+
return `${DEFAULT_STORAGE_PREFIX}${this.endpointHash}_`;
|
|
168
|
+
}
|
|
169
|
+
/** Build a storage key scoped to this auth endpoint. */
|
|
170
|
+
keyFor(name) {
|
|
171
|
+
return `${this.storagePrefix}${name}`;
|
|
172
|
+
}
|
|
173
|
+
get storageKeys() {
|
|
174
|
+
const prefix = this.storagePrefix;
|
|
175
|
+
return {
|
|
176
|
+
access: `${prefix}access_token`,
|
|
177
|
+
refresh: `${prefix}refresh_token`,
|
|
178
|
+
rool: `${prefix}rool_token`,
|
|
179
|
+
expiresAt: `${prefix}token_expires_at`,
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
readString(key) {
|
|
183
|
+
try {
|
|
184
|
+
return localStorage.getItem(key);
|
|
185
|
+
}
|
|
186
|
+
catch {
|
|
187
|
+
return null;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
writeString(key, value) {
|
|
191
|
+
try {
|
|
192
|
+
localStorage.setItem(key, value);
|
|
193
|
+
}
|
|
194
|
+
catch {
|
|
195
|
+
// Ignore storage restrictions
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
removeString(key) {
|
|
199
|
+
try {
|
|
200
|
+
localStorage.removeItem(key);
|
|
201
|
+
}
|
|
202
|
+
catch {
|
|
203
|
+
// Ignore storage restrictions
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
scheduleTokenRefresh() {
|
|
207
|
+
this.cancelScheduledRefresh();
|
|
208
|
+
const expiresAt = this.readExpiresAt();
|
|
209
|
+
if (!expiresAt)
|
|
210
|
+
return;
|
|
211
|
+
const refreshAt = expiresAt - REFRESH_BUFFER_MS;
|
|
212
|
+
const delay = refreshAt - Date.now();
|
|
213
|
+
if (delay <= 0) {
|
|
214
|
+
// Already needs refresh
|
|
215
|
+
void this.tryRefreshToken();
|
|
216
|
+
}
|
|
217
|
+
else {
|
|
218
|
+
this.refreshTimeoutId = setTimeout(() => {
|
|
219
|
+
void this.tryRefreshToken();
|
|
220
|
+
}, delay);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
writeTokens(accessToken, refreshToken, expiresAt) {
|
|
224
|
+
if (accessToken) {
|
|
225
|
+
localStorage.setItem(this.storageKeys.access, accessToken);
|
|
226
|
+
}
|
|
227
|
+
else {
|
|
228
|
+
localStorage.removeItem(this.storageKeys.access);
|
|
229
|
+
}
|
|
230
|
+
if (refreshToken) {
|
|
231
|
+
localStorage.setItem(this.storageKeys.refresh, refreshToken);
|
|
232
|
+
}
|
|
233
|
+
else {
|
|
234
|
+
localStorage.removeItem(this.storageKeys.refresh);
|
|
235
|
+
}
|
|
236
|
+
if (expiresAt !== null && Number.isFinite(expiresAt)) {
|
|
237
|
+
localStorage.setItem(this.storageKeys.expiresAt, Math.floor(expiresAt).toString());
|
|
238
|
+
}
|
|
239
|
+
else {
|
|
240
|
+
localStorage.removeItem(this.storageKeys.expiresAt);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
writeRoolToken(token) {
|
|
244
|
+
if (token) {
|
|
245
|
+
localStorage.setItem(this.storageKeys.rool, token);
|
|
246
|
+
}
|
|
247
|
+
else {
|
|
248
|
+
localStorage.removeItem(this.storageKeys.rool);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
clearTokens() {
|
|
252
|
+
this.writeTokens(null, null, null);
|
|
253
|
+
this.writeRoolToken(null);
|
|
254
|
+
}
|
|
255
|
+
readAccessToken() {
|
|
256
|
+
return localStorage.getItem(this.storageKeys.access);
|
|
257
|
+
}
|
|
258
|
+
readRoolToken() {
|
|
259
|
+
return localStorage.getItem(this.storageKeys.rool) ?? '';
|
|
260
|
+
}
|
|
261
|
+
readExpiresAt() {
|
|
262
|
+
const raw = localStorage.getItem(this.storageKeys.expiresAt);
|
|
263
|
+
if (!raw)
|
|
264
|
+
return null;
|
|
265
|
+
const parsed = Number.parseInt(raw, 10);
|
|
266
|
+
return Number.isFinite(parsed) ? parsed : null;
|
|
267
|
+
}
|
|
268
|
+
// ===========================================================================
|
|
269
|
+
// Private
|
|
270
|
+
// ===========================================================================
|
|
271
|
+
/**
|
|
272
|
+
* Get a short hash of the auth URL for scoping storage by endpoint.
|
|
273
|
+
*/
|
|
274
|
+
get endpointHash() {
|
|
275
|
+
const url = this.authBaseUrl;
|
|
276
|
+
// Simple djb2 hash
|
|
277
|
+
let hash = 5381;
|
|
278
|
+
for (let i = 0; i < url.length; i++) {
|
|
279
|
+
hash = ((hash << 5) + hash) ^ url.charCodeAt(i);
|
|
280
|
+
}
|
|
281
|
+
return (hash >>> 0).toString(16).padStart(8, '0');
|
|
282
|
+
}
|
|
283
|
+
async tryRefreshToken() {
|
|
284
|
+
// Deduplicate concurrent refresh attempts
|
|
285
|
+
if (this.refreshPromise) {
|
|
286
|
+
return this.refreshPromise;
|
|
287
|
+
}
|
|
288
|
+
const refreshToken = localStorage.getItem(this.storageKeys.refresh);
|
|
289
|
+
if (!refreshToken)
|
|
290
|
+
return false;
|
|
291
|
+
const roolToken = localStorage.getItem(this.storageKeys.rool);
|
|
292
|
+
this.refreshPromise = (async () => {
|
|
293
|
+
let response;
|
|
294
|
+
try {
|
|
295
|
+
response = await fetch(`${this.authBaseUrl}/refresh`, {
|
|
296
|
+
method: 'POST',
|
|
297
|
+
headers: { 'Content-Type': 'application/json' },
|
|
298
|
+
body: JSON.stringify({
|
|
299
|
+
refresh_token: refreshToken,
|
|
300
|
+
rool_token: roolToken,
|
|
301
|
+
}),
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
catch (error) {
|
|
305
|
+
// Network error - don't clear tokens, might work next time
|
|
306
|
+
this.logger.warn('[RoolClient] Token refresh network error:', error);
|
|
307
|
+
return false;
|
|
308
|
+
}
|
|
309
|
+
// 400/401 = refresh token is invalid, clear everything
|
|
310
|
+
if (response.status === 400 || response.status === 401) {
|
|
311
|
+
this.logger.warn('[RoolClient] Refresh token invalid, clearing credentials');
|
|
312
|
+
this.clearTokens();
|
|
313
|
+
this.notifyAuthState(false);
|
|
314
|
+
return false;
|
|
315
|
+
}
|
|
316
|
+
// Other HTTP errors - don't clear tokens, might be transient
|
|
317
|
+
if (!response.ok) {
|
|
318
|
+
this.logger.warn(`[RoolClient] Token refresh failed: ${response.status} ${response.statusText}`);
|
|
319
|
+
return false;
|
|
320
|
+
}
|
|
321
|
+
// Success - parse and store new tokens
|
|
322
|
+
try {
|
|
323
|
+
const data = await response.json();
|
|
324
|
+
const accessToken = data.id_token ?? null;
|
|
325
|
+
const nextRefreshToken = data.refresh_token ?? refreshToken;
|
|
326
|
+
const expiresAt = data.expires_in ? Date.now() + data.expires_in * 1000 : NaN;
|
|
327
|
+
if (!accessToken || !Number.isFinite(expiresAt)) {
|
|
328
|
+
this.logger.error('[RoolClient] Refresh response missing id_token or expires_in');
|
|
329
|
+
return false;
|
|
330
|
+
}
|
|
331
|
+
this.writeTokens(accessToken, nextRefreshToken, expiresAt);
|
|
332
|
+
this.writeRoolToken(data.rool_token ?? null);
|
|
333
|
+
this.scheduleTokenRefresh();
|
|
334
|
+
return true;
|
|
335
|
+
}
|
|
336
|
+
catch (error) {
|
|
337
|
+
this.logger.error('[RoolClient] Failed to parse refresh response:', error);
|
|
338
|
+
return false;
|
|
339
|
+
}
|
|
340
|
+
})().finally(() => {
|
|
341
|
+
this.refreshPromise = null;
|
|
342
|
+
});
|
|
343
|
+
return this.refreshPromise;
|
|
344
|
+
}
|
|
345
|
+
listenForVisibility() {
|
|
346
|
+
if (typeof document === 'undefined')
|
|
347
|
+
return;
|
|
348
|
+
if (this.boundVisibilityHandler)
|
|
349
|
+
return;
|
|
350
|
+
this.boundVisibilityHandler = () => {
|
|
351
|
+
if (document.visibilityState === 'visible') {
|
|
352
|
+
this.scheduleTokenRefresh();
|
|
353
|
+
}
|
|
354
|
+
};
|
|
355
|
+
document.addEventListener('visibilitychange', this.boundVisibilityHandler);
|
|
356
|
+
}
|
|
357
|
+
cancelScheduledRefresh() {
|
|
358
|
+
if (this.refreshTimeoutId !== null) {
|
|
359
|
+
clearTimeout(this.refreshTimeoutId);
|
|
360
|
+
this.refreshTimeoutId = null;
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
decodeAuthUser(accessToken) {
|
|
364
|
+
try {
|
|
365
|
+
const payload = JSON.parse(atob(accessToken.split('.')[1]));
|
|
366
|
+
return {
|
|
367
|
+
email: payload.email || null,
|
|
368
|
+
name: payload.name || null,
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
catch (error) {
|
|
372
|
+
this.logger.error('[RoolClient] Failed to decode token:', error);
|
|
373
|
+
return { email: null, name: null };
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
//# sourceMappingURL=auth-base.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth-base.js","sourceRoot":"","sources":["../src/auth-base.ts"],"names":[],"mappings":"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"}
|