@treeviz/familysearch-sdk 1.0.10
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/LICENSE +21 -0
- package/README.md +227 -0
- package/dist/auth/index.cjs +313 -0
- package/dist/auth/index.cjs.map +1 -0
- package/dist/auth/index.d.cts +124 -0
- package/dist/auth/index.d.ts +124 -0
- package/dist/auth/index.js +293 -0
- package/dist/auth/index.js.map +1 -0
- package/dist/client-DIpYSHtx.d.ts +162 -0
- package/dist/client-ohjqX4t5.d.cts +162 -0
- package/dist/index-D6H-lvis.d.cts +484 -0
- package/dist/index-D6H-lvis.d.ts +484 -0
- package/dist/index.cjs +1689 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +6 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +1653 -0
- package/dist/index.js.map +1 -0
- package/dist/places/index.cjs +94 -0
- package/dist/places/index.cjs.map +1 -0
- package/dist/places/index.d.cts +69 -0
- package/dist/places/index.d.ts +69 -0
- package/dist/places/index.js +89 -0
- package/dist/places/index.js.map +1 -0
- package/dist/tree/index.cjs +191 -0
- package/dist/tree/index.cjs.map +1 -0
- package/dist/tree/index.d.cts +47 -0
- package/dist/tree/index.d.ts +47 -0
- package/dist/tree/index.js +186 -0
- package/dist/tree/index.js.map +1 -0
- package/dist/utils/index.cjs +663 -0
- package/dist/utils/index.cjs.map +1 -0
- package/dist/utils/index.d.cts +46 -0
- package/dist/utils/index.d.ts +46 -0
- package/dist/utils/index.js +660 -0
- package/dist/utils/index.js.map +1 -0
- package/package.json +78 -0
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { F as FamilySearchEnvironment, s as OAuthEndpoints, t as OAuthConfig, O as OAuthTokenResponse, u as OAuthStateValidation } from '../index-D6H-lvis.cjs';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* FamilySearch OAuth Authentication Module
|
|
5
|
+
*
|
|
6
|
+
* Provides OAuth 2.0 authentication utilities for FamilySearch API v3.
|
|
7
|
+
* This module is designed to be framework-agnostic and can be used
|
|
8
|
+
* in any JavaScript/TypeScript environment.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
declare const OAUTH_ENDPOINTS: Record<FamilySearchEnvironment, OAuthEndpoints>;
|
|
12
|
+
/**
|
|
13
|
+
* Get OAuth endpoints for a specific environment
|
|
14
|
+
*/
|
|
15
|
+
declare function getOAuthEndpoints(environment?: FamilySearchEnvironment): OAuthEndpoints;
|
|
16
|
+
/**
|
|
17
|
+
* Generate a cryptographically secure random state for CSRF protection
|
|
18
|
+
*/
|
|
19
|
+
declare function generateOAuthState(): string;
|
|
20
|
+
/**
|
|
21
|
+
* Build the authorization URL for OAuth flow
|
|
22
|
+
*/
|
|
23
|
+
declare function buildAuthorizationUrl(config: OAuthConfig, state: string, options?: {
|
|
24
|
+
scopes?: string[];
|
|
25
|
+
prompt?: string;
|
|
26
|
+
}): string;
|
|
27
|
+
/**
|
|
28
|
+
* Exchange authorization code for access token
|
|
29
|
+
*/
|
|
30
|
+
declare function exchangeCodeForToken(code: string, config: OAuthConfig): Promise<OAuthTokenResponse>;
|
|
31
|
+
/**
|
|
32
|
+
* Refresh an access token using a refresh token
|
|
33
|
+
*/
|
|
34
|
+
declare function refreshAccessToken(refreshToken: string, config: OAuthConfig): Promise<OAuthTokenResponse>;
|
|
35
|
+
/**
|
|
36
|
+
* Validate an access token by making a test API call
|
|
37
|
+
*/
|
|
38
|
+
declare function validateAccessToken(accessToken: string, environment?: FamilySearchEnvironment): Promise<boolean>;
|
|
39
|
+
/**
|
|
40
|
+
* Get user info from access token
|
|
41
|
+
*/
|
|
42
|
+
declare function getUserInfo(accessToken: string, environment?: FamilySearchEnvironment): Promise<{
|
|
43
|
+
sub: string;
|
|
44
|
+
name?: string;
|
|
45
|
+
given_name?: string;
|
|
46
|
+
family_name?: string;
|
|
47
|
+
email?: string;
|
|
48
|
+
email_verified?: boolean;
|
|
49
|
+
} | null>;
|
|
50
|
+
/**
|
|
51
|
+
* Storage keys for OAuth state management
|
|
52
|
+
*/
|
|
53
|
+
declare const OAUTH_STORAGE_KEYS: {
|
|
54
|
+
readonly state: "fs_oauth_state";
|
|
55
|
+
readonly linkMode: "fs_oauth_link_mode";
|
|
56
|
+
readonly lang: "fs_oauth_lang";
|
|
57
|
+
readonly parentUid: "fs_oauth_parent_uid";
|
|
58
|
+
};
|
|
59
|
+
/**
|
|
60
|
+
* Store OAuth state in localStorage for popup flow
|
|
61
|
+
* Uses localStorage instead of sessionStorage because popup windows
|
|
62
|
+
* don't share sessionStorage with the parent window
|
|
63
|
+
*/
|
|
64
|
+
declare function storeOAuthState(state: string, options?: {
|
|
65
|
+
isLinkMode?: boolean;
|
|
66
|
+
lang?: string;
|
|
67
|
+
parentUid?: string;
|
|
68
|
+
}): void;
|
|
69
|
+
/**
|
|
70
|
+
* Validate OAuth state from callback and extract metadata
|
|
71
|
+
* Returns invalid state if localStorage is not available (SSR/Node.js environments)
|
|
72
|
+
*/
|
|
73
|
+
declare function validateOAuthState(state: string): OAuthStateValidation;
|
|
74
|
+
/**
|
|
75
|
+
* Open OAuth authorization in a popup window
|
|
76
|
+
*/
|
|
77
|
+
declare function openOAuthPopup(authUrl: string, options?: {
|
|
78
|
+
width?: number;
|
|
79
|
+
height?: number;
|
|
80
|
+
windowName?: string;
|
|
81
|
+
}): Window | null;
|
|
82
|
+
/**
|
|
83
|
+
* Parse OAuth callback parameters from URL
|
|
84
|
+
*/
|
|
85
|
+
declare function parseCallbackParams(url?: string): {
|
|
86
|
+
code?: string;
|
|
87
|
+
state?: string;
|
|
88
|
+
error?: string;
|
|
89
|
+
error_description?: string;
|
|
90
|
+
};
|
|
91
|
+
/**
|
|
92
|
+
* Generate a storage key scoped to a user ID
|
|
93
|
+
*/
|
|
94
|
+
declare function getTokenStorageKey(userId: string, type: "access" | "expires" | "refresh" | "environment"): string;
|
|
95
|
+
/**
|
|
96
|
+
* Store access token with expiration
|
|
97
|
+
* Per FamilySearch compatibility requirements:
|
|
98
|
+
* - Access tokens stored in sessionStorage (cleared on browser close)
|
|
99
|
+
* - Refresh tokens stored in localStorage (for re-authentication)
|
|
100
|
+
*/
|
|
101
|
+
declare function storeTokens(userId: string, tokens: {
|
|
102
|
+
accessToken: string;
|
|
103
|
+
expiresAt?: number;
|
|
104
|
+
refreshToken?: string;
|
|
105
|
+
environment?: string;
|
|
106
|
+
}): void;
|
|
107
|
+
/**
|
|
108
|
+
* Get stored access token
|
|
109
|
+
*/
|
|
110
|
+
declare function getStoredAccessToken(userId: string): string | null;
|
|
111
|
+
/**
|
|
112
|
+
* Get stored refresh token
|
|
113
|
+
*/
|
|
114
|
+
declare function getStoredRefreshToken(userId: string): string | null;
|
|
115
|
+
/**
|
|
116
|
+
* Clear all stored tokens for a user
|
|
117
|
+
*/
|
|
118
|
+
declare function clearStoredTokens(userId: string): void;
|
|
119
|
+
/**
|
|
120
|
+
* Clear all FamilySearch tokens from storage
|
|
121
|
+
*/
|
|
122
|
+
declare function clearAllTokens(): void;
|
|
123
|
+
|
|
124
|
+
export { OAUTH_ENDPOINTS, OAUTH_STORAGE_KEYS, buildAuthorizationUrl, clearAllTokens, clearStoredTokens, exchangeCodeForToken, generateOAuthState, getOAuthEndpoints, getStoredAccessToken, getStoredRefreshToken, getTokenStorageKey, getUserInfo, openOAuthPopup, parseCallbackParams, refreshAccessToken, storeOAuthState, storeTokens, validateAccessToken, validateOAuthState };
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { F as FamilySearchEnvironment, s as OAuthEndpoints, t as OAuthConfig, O as OAuthTokenResponse, u as OAuthStateValidation } from '../index-D6H-lvis.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* FamilySearch OAuth Authentication Module
|
|
5
|
+
*
|
|
6
|
+
* Provides OAuth 2.0 authentication utilities for FamilySearch API v3.
|
|
7
|
+
* This module is designed to be framework-agnostic and can be used
|
|
8
|
+
* in any JavaScript/TypeScript environment.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
declare const OAUTH_ENDPOINTS: Record<FamilySearchEnvironment, OAuthEndpoints>;
|
|
12
|
+
/**
|
|
13
|
+
* Get OAuth endpoints for a specific environment
|
|
14
|
+
*/
|
|
15
|
+
declare function getOAuthEndpoints(environment?: FamilySearchEnvironment): OAuthEndpoints;
|
|
16
|
+
/**
|
|
17
|
+
* Generate a cryptographically secure random state for CSRF protection
|
|
18
|
+
*/
|
|
19
|
+
declare function generateOAuthState(): string;
|
|
20
|
+
/**
|
|
21
|
+
* Build the authorization URL for OAuth flow
|
|
22
|
+
*/
|
|
23
|
+
declare function buildAuthorizationUrl(config: OAuthConfig, state: string, options?: {
|
|
24
|
+
scopes?: string[];
|
|
25
|
+
prompt?: string;
|
|
26
|
+
}): string;
|
|
27
|
+
/**
|
|
28
|
+
* Exchange authorization code for access token
|
|
29
|
+
*/
|
|
30
|
+
declare function exchangeCodeForToken(code: string, config: OAuthConfig): Promise<OAuthTokenResponse>;
|
|
31
|
+
/**
|
|
32
|
+
* Refresh an access token using a refresh token
|
|
33
|
+
*/
|
|
34
|
+
declare function refreshAccessToken(refreshToken: string, config: OAuthConfig): Promise<OAuthTokenResponse>;
|
|
35
|
+
/**
|
|
36
|
+
* Validate an access token by making a test API call
|
|
37
|
+
*/
|
|
38
|
+
declare function validateAccessToken(accessToken: string, environment?: FamilySearchEnvironment): Promise<boolean>;
|
|
39
|
+
/**
|
|
40
|
+
* Get user info from access token
|
|
41
|
+
*/
|
|
42
|
+
declare function getUserInfo(accessToken: string, environment?: FamilySearchEnvironment): Promise<{
|
|
43
|
+
sub: string;
|
|
44
|
+
name?: string;
|
|
45
|
+
given_name?: string;
|
|
46
|
+
family_name?: string;
|
|
47
|
+
email?: string;
|
|
48
|
+
email_verified?: boolean;
|
|
49
|
+
} | null>;
|
|
50
|
+
/**
|
|
51
|
+
* Storage keys for OAuth state management
|
|
52
|
+
*/
|
|
53
|
+
declare const OAUTH_STORAGE_KEYS: {
|
|
54
|
+
readonly state: "fs_oauth_state";
|
|
55
|
+
readonly linkMode: "fs_oauth_link_mode";
|
|
56
|
+
readonly lang: "fs_oauth_lang";
|
|
57
|
+
readonly parentUid: "fs_oauth_parent_uid";
|
|
58
|
+
};
|
|
59
|
+
/**
|
|
60
|
+
* Store OAuth state in localStorage for popup flow
|
|
61
|
+
* Uses localStorage instead of sessionStorage because popup windows
|
|
62
|
+
* don't share sessionStorage with the parent window
|
|
63
|
+
*/
|
|
64
|
+
declare function storeOAuthState(state: string, options?: {
|
|
65
|
+
isLinkMode?: boolean;
|
|
66
|
+
lang?: string;
|
|
67
|
+
parentUid?: string;
|
|
68
|
+
}): void;
|
|
69
|
+
/**
|
|
70
|
+
* Validate OAuth state from callback and extract metadata
|
|
71
|
+
* Returns invalid state if localStorage is not available (SSR/Node.js environments)
|
|
72
|
+
*/
|
|
73
|
+
declare function validateOAuthState(state: string): OAuthStateValidation;
|
|
74
|
+
/**
|
|
75
|
+
* Open OAuth authorization in a popup window
|
|
76
|
+
*/
|
|
77
|
+
declare function openOAuthPopup(authUrl: string, options?: {
|
|
78
|
+
width?: number;
|
|
79
|
+
height?: number;
|
|
80
|
+
windowName?: string;
|
|
81
|
+
}): Window | null;
|
|
82
|
+
/**
|
|
83
|
+
* Parse OAuth callback parameters from URL
|
|
84
|
+
*/
|
|
85
|
+
declare function parseCallbackParams(url?: string): {
|
|
86
|
+
code?: string;
|
|
87
|
+
state?: string;
|
|
88
|
+
error?: string;
|
|
89
|
+
error_description?: string;
|
|
90
|
+
};
|
|
91
|
+
/**
|
|
92
|
+
* Generate a storage key scoped to a user ID
|
|
93
|
+
*/
|
|
94
|
+
declare function getTokenStorageKey(userId: string, type: "access" | "expires" | "refresh" | "environment"): string;
|
|
95
|
+
/**
|
|
96
|
+
* Store access token with expiration
|
|
97
|
+
* Per FamilySearch compatibility requirements:
|
|
98
|
+
* - Access tokens stored in sessionStorage (cleared on browser close)
|
|
99
|
+
* - Refresh tokens stored in localStorage (for re-authentication)
|
|
100
|
+
*/
|
|
101
|
+
declare function storeTokens(userId: string, tokens: {
|
|
102
|
+
accessToken: string;
|
|
103
|
+
expiresAt?: number;
|
|
104
|
+
refreshToken?: string;
|
|
105
|
+
environment?: string;
|
|
106
|
+
}): void;
|
|
107
|
+
/**
|
|
108
|
+
* Get stored access token
|
|
109
|
+
*/
|
|
110
|
+
declare function getStoredAccessToken(userId: string): string | null;
|
|
111
|
+
/**
|
|
112
|
+
* Get stored refresh token
|
|
113
|
+
*/
|
|
114
|
+
declare function getStoredRefreshToken(userId: string): string | null;
|
|
115
|
+
/**
|
|
116
|
+
* Clear all stored tokens for a user
|
|
117
|
+
*/
|
|
118
|
+
declare function clearStoredTokens(userId: string): void;
|
|
119
|
+
/**
|
|
120
|
+
* Clear all FamilySearch tokens from storage
|
|
121
|
+
*/
|
|
122
|
+
declare function clearAllTokens(): void;
|
|
123
|
+
|
|
124
|
+
export { OAUTH_ENDPOINTS, OAUTH_STORAGE_KEYS, buildAuthorizationUrl, clearAllTokens, clearStoredTokens, exchangeCodeForToken, generateOAuthState, getOAuthEndpoints, getStoredAccessToken, getStoredRefreshToken, getTokenStorageKey, getUserInfo, openOAuthPopup, parseCallbackParams, refreshAccessToken, storeOAuthState, storeTokens, validateAccessToken, validateOAuthState };
|
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
// src/auth/oauth.ts
|
|
2
|
+
var OAUTH_ENDPOINTS = {
|
|
3
|
+
production: {
|
|
4
|
+
authorization: "https://ident.familysearch.org/cis-web/oauth2/v3/authorization",
|
|
5
|
+
token: "https://ident.familysearch.org/cis-web/oauth2/v3/token",
|
|
6
|
+
currentUser: "https://api.familysearch.org/platform/users/current"
|
|
7
|
+
},
|
|
8
|
+
beta: {
|
|
9
|
+
authorization: "https://identbeta.familysearch.org/cis-web/oauth2/v3/authorization",
|
|
10
|
+
token: "https://identbeta.familysearch.org/cis-web/oauth2/v3/token",
|
|
11
|
+
currentUser: "https://apibeta.familysearch.org/platform/users/current"
|
|
12
|
+
},
|
|
13
|
+
integration: {
|
|
14
|
+
authorization: "https://identint.familysearch.org/cis-web/oauth2/v3/authorization",
|
|
15
|
+
token: "https://identint.familysearch.org/cis-web/oauth2/v3/token",
|
|
16
|
+
currentUser: "https://api-integ.familysearch.org/platform/users/current"
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
function getOAuthEndpoints(environment = "integration") {
|
|
20
|
+
return OAUTH_ENDPOINTS[environment];
|
|
21
|
+
}
|
|
22
|
+
function generateOAuthState() {
|
|
23
|
+
const array = new Uint8Array(32);
|
|
24
|
+
crypto.getRandomValues(array);
|
|
25
|
+
return Array.from(array, (byte) => byte.toString(16).padStart(2, "0")).join(
|
|
26
|
+
""
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
function buildAuthorizationUrl(config, state, options = {}) {
|
|
30
|
+
const endpoints = getOAuthEndpoints(config.environment);
|
|
31
|
+
const url = new URL(endpoints.authorization);
|
|
32
|
+
url.searchParams.set("response_type", "code");
|
|
33
|
+
url.searchParams.set("client_id", config.clientId);
|
|
34
|
+
url.searchParams.set("redirect_uri", config.redirectUri);
|
|
35
|
+
url.searchParams.set("state", state);
|
|
36
|
+
if (options.scopes && options.scopes.length > 0) {
|
|
37
|
+
url.searchParams.set("scope", options.scopes.join(" "));
|
|
38
|
+
}
|
|
39
|
+
if (options.prompt) {
|
|
40
|
+
url.searchParams.set("prompt", options.prompt);
|
|
41
|
+
}
|
|
42
|
+
return url.toString();
|
|
43
|
+
}
|
|
44
|
+
async function exchangeCodeForToken(code, config) {
|
|
45
|
+
const endpoints = getOAuthEndpoints(config.environment);
|
|
46
|
+
const response = await fetch(endpoints.token, {
|
|
47
|
+
method: "POST",
|
|
48
|
+
headers: {
|
|
49
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
50
|
+
Accept: "application/json"
|
|
51
|
+
},
|
|
52
|
+
body: new URLSearchParams({
|
|
53
|
+
grant_type: "authorization_code",
|
|
54
|
+
code,
|
|
55
|
+
client_id: config.clientId,
|
|
56
|
+
redirect_uri: config.redirectUri
|
|
57
|
+
})
|
|
58
|
+
});
|
|
59
|
+
if (!response.ok) {
|
|
60
|
+
const error = await response.text();
|
|
61
|
+
throw new Error(`Failed to exchange code for token: ${error}`);
|
|
62
|
+
}
|
|
63
|
+
return response.json();
|
|
64
|
+
}
|
|
65
|
+
async function refreshAccessToken(refreshToken, config) {
|
|
66
|
+
const endpoints = getOAuthEndpoints(config.environment);
|
|
67
|
+
const response = await fetch(endpoints.token, {
|
|
68
|
+
method: "POST",
|
|
69
|
+
headers: {
|
|
70
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
71
|
+
Accept: "application/json"
|
|
72
|
+
},
|
|
73
|
+
body: new URLSearchParams({
|
|
74
|
+
grant_type: "refresh_token",
|
|
75
|
+
refresh_token: refreshToken,
|
|
76
|
+
client_id: config.clientId
|
|
77
|
+
})
|
|
78
|
+
});
|
|
79
|
+
if (!response.ok) {
|
|
80
|
+
const error = await response.text();
|
|
81
|
+
throw new Error(`Failed to refresh token: ${error}`);
|
|
82
|
+
}
|
|
83
|
+
return response.json();
|
|
84
|
+
}
|
|
85
|
+
async function validateAccessToken(accessToken, environment = "integration") {
|
|
86
|
+
const endpoints = getOAuthEndpoints(environment);
|
|
87
|
+
try {
|
|
88
|
+
const response = await fetch(endpoints.currentUser, {
|
|
89
|
+
headers: {
|
|
90
|
+
Authorization: `Bearer ${accessToken}`,
|
|
91
|
+
Accept: "application/json"
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
return response.ok;
|
|
95
|
+
} catch {
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
async function getUserInfo(accessToken, environment = "integration") {
|
|
100
|
+
const endpoints = getOAuthEndpoints(environment);
|
|
101
|
+
try {
|
|
102
|
+
const response = await fetch(endpoints.currentUser, {
|
|
103
|
+
headers: {
|
|
104
|
+
Authorization: `Bearer ${accessToken}`,
|
|
105
|
+
Accept: "application/json"
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
if (!response.ok) {
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
const data = await response.json();
|
|
112
|
+
const fsUser = data.users?.[0];
|
|
113
|
+
if (!fsUser || !fsUser.id) {
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
return {
|
|
117
|
+
sub: fsUser.id,
|
|
118
|
+
name: fsUser.contactName || fsUser.displayName,
|
|
119
|
+
given_name: fsUser.givenName,
|
|
120
|
+
family_name: fsUser.familyName,
|
|
121
|
+
email: fsUser.email,
|
|
122
|
+
email_verified: fsUser.email ? true : false
|
|
123
|
+
};
|
|
124
|
+
} catch {
|
|
125
|
+
return null;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
var OAUTH_STORAGE_KEYS = {
|
|
129
|
+
state: "fs_oauth_state",
|
|
130
|
+
linkMode: "fs_oauth_link_mode",
|
|
131
|
+
lang: "fs_oauth_lang",
|
|
132
|
+
parentUid: "fs_oauth_parent_uid"
|
|
133
|
+
};
|
|
134
|
+
function storeOAuthState(state, options = {}) {
|
|
135
|
+
if (typeof localStorage === "undefined") {
|
|
136
|
+
throw new Error(
|
|
137
|
+
"localStorage is not available. For server-side usage, implement custom state storage."
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
localStorage.setItem(OAUTH_STORAGE_KEYS.state, state);
|
|
141
|
+
if (options.isLinkMode) {
|
|
142
|
+
localStorage.setItem(OAUTH_STORAGE_KEYS.linkMode, "true");
|
|
143
|
+
} else {
|
|
144
|
+
localStorage.removeItem(OAUTH_STORAGE_KEYS.linkMode);
|
|
145
|
+
}
|
|
146
|
+
if (options.lang) {
|
|
147
|
+
localStorage.setItem(OAUTH_STORAGE_KEYS.lang, options.lang);
|
|
148
|
+
} else {
|
|
149
|
+
localStorage.removeItem(OAUTH_STORAGE_KEYS.lang);
|
|
150
|
+
}
|
|
151
|
+
if (options.parentUid) {
|
|
152
|
+
localStorage.setItem(OAUTH_STORAGE_KEYS.parentUid, options.parentUid);
|
|
153
|
+
} else {
|
|
154
|
+
localStorage.removeItem(OAUTH_STORAGE_KEYS.parentUid);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
function validateOAuthState(state) {
|
|
158
|
+
if (typeof localStorage === "undefined") {
|
|
159
|
+
return { valid: false, isLinkMode: false };
|
|
160
|
+
}
|
|
161
|
+
const storedState = localStorage.getItem(OAUTH_STORAGE_KEYS.state);
|
|
162
|
+
const isLinkMode = localStorage.getItem(OAUTH_STORAGE_KEYS.linkMode) === "true";
|
|
163
|
+
const lang = localStorage.getItem(OAUTH_STORAGE_KEYS.lang) || void 0;
|
|
164
|
+
const parentUid = localStorage.getItem(OAUTH_STORAGE_KEYS.parentUid) || void 0;
|
|
165
|
+
localStorage.removeItem(OAUTH_STORAGE_KEYS.state);
|
|
166
|
+
localStorage.removeItem(OAUTH_STORAGE_KEYS.linkMode);
|
|
167
|
+
localStorage.removeItem(OAUTH_STORAGE_KEYS.lang);
|
|
168
|
+
localStorage.removeItem(OAUTH_STORAGE_KEYS.parentUid);
|
|
169
|
+
return {
|
|
170
|
+
valid: storedState === state,
|
|
171
|
+
isLinkMode,
|
|
172
|
+
lang,
|
|
173
|
+
parentUid
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
function openOAuthPopup(authUrl, options = {}) {
|
|
177
|
+
if (typeof window === "undefined") {
|
|
178
|
+
throw new Error("window is not available");
|
|
179
|
+
}
|
|
180
|
+
const width = options.width || 500;
|
|
181
|
+
const height = options.height || 600;
|
|
182
|
+
const windowName = options.windowName || "FamilySearch Login";
|
|
183
|
+
const left = window.screenX + (window.outerWidth - width) / 2;
|
|
184
|
+
const top = window.screenY + (window.outerHeight - height) / 2;
|
|
185
|
+
const popup = window.open(
|
|
186
|
+
authUrl,
|
|
187
|
+
windowName,
|
|
188
|
+
`width=${width},height=${height},left=${left},top=${top},toolbar=no,location=no,status=no,menubar=no,scrollbars=yes,resizable=yes`
|
|
189
|
+
);
|
|
190
|
+
if (popup) {
|
|
191
|
+
popup.focus();
|
|
192
|
+
}
|
|
193
|
+
return popup;
|
|
194
|
+
}
|
|
195
|
+
function parseCallbackParams(url = typeof window !== "undefined" ? window.location.href : "") {
|
|
196
|
+
const urlObj = new URL(url);
|
|
197
|
+
const params = urlObj.searchParams;
|
|
198
|
+
return {
|
|
199
|
+
code: params.get("code") || void 0,
|
|
200
|
+
state: params.get("state") || void 0,
|
|
201
|
+
error: params.get("error") || void 0,
|
|
202
|
+
error_description: params.get("error_description") || void 0
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
function getTokenStorageKey(userId, type) {
|
|
206
|
+
return `fs_token_${userId}_${type}`;
|
|
207
|
+
}
|
|
208
|
+
function storeTokens(userId, tokens) {
|
|
209
|
+
if (typeof sessionStorage === "undefined" || typeof localStorage === "undefined") {
|
|
210
|
+
throw new Error("Storage APIs are not available");
|
|
211
|
+
}
|
|
212
|
+
sessionStorage.setItem(
|
|
213
|
+
getTokenStorageKey(userId, "access"),
|
|
214
|
+
tokens.accessToken
|
|
215
|
+
);
|
|
216
|
+
if (tokens.expiresAt) {
|
|
217
|
+
sessionStorage.setItem(
|
|
218
|
+
getTokenStorageKey(userId, "expires"),
|
|
219
|
+
tokens.expiresAt.toString()
|
|
220
|
+
);
|
|
221
|
+
}
|
|
222
|
+
if (tokens.refreshToken) {
|
|
223
|
+
localStorage.setItem(
|
|
224
|
+
getTokenStorageKey(userId, "refresh"),
|
|
225
|
+
tokens.refreshToken
|
|
226
|
+
);
|
|
227
|
+
}
|
|
228
|
+
if (tokens.environment) {
|
|
229
|
+
localStorage.setItem(
|
|
230
|
+
getTokenStorageKey(userId, "environment"),
|
|
231
|
+
tokens.environment
|
|
232
|
+
);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
function getStoredAccessToken(userId) {
|
|
236
|
+
if (typeof sessionStorage === "undefined") {
|
|
237
|
+
return null;
|
|
238
|
+
}
|
|
239
|
+
const token = sessionStorage.getItem(getTokenStorageKey(userId, "access"));
|
|
240
|
+
const expiresAt = sessionStorage.getItem(
|
|
241
|
+
getTokenStorageKey(userId, "expires")
|
|
242
|
+
);
|
|
243
|
+
if (!token) {
|
|
244
|
+
return null;
|
|
245
|
+
}
|
|
246
|
+
const EXPIRATION_BUFFER = 5 * 60 * 1e3;
|
|
247
|
+
if (expiresAt && Date.now() > parseInt(expiresAt) - EXPIRATION_BUFFER) {
|
|
248
|
+
return null;
|
|
249
|
+
}
|
|
250
|
+
return token;
|
|
251
|
+
}
|
|
252
|
+
function getStoredRefreshToken(userId) {
|
|
253
|
+
if (typeof localStorage === "undefined") {
|
|
254
|
+
return null;
|
|
255
|
+
}
|
|
256
|
+
return localStorage.getItem(getTokenStorageKey(userId, "refresh"));
|
|
257
|
+
}
|
|
258
|
+
function clearStoredTokens(userId) {
|
|
259
|
+
if (typeof sessionStorage !== "undefined") {
|
|
260
|
+
sessionStorage.removeItem(getTokenStorageKey(userId, "access"));
|
|
261
|
+
sessionStorage.removeItem(getTokenStorageKey(userId, "expires"));
|
|
262
|
+
}
|
|
263
|
+
if (typeof localStorage !== "undefined") {
|
|
264
|
+
localStorage.removeItem(getTokenStorageKey(userId, "refresh"));
|
|
265
|
+
localStorage.removeItem(getTokenStorageKey(userId, "environment"));
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
function clearAllTokens() {
|
|
269
|
+
if (typeof sessionStorage === "undefined" || typeof localStorage === "undefined") {
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
const keysToRemove = [];
|
|
273
|
+
for (let i = 0; i < sessionStorage.length; i++) {
|
|
274
|
+
const key = sessionStorage.key(i);
|
|
275
|
+
if (key && key.startsWith("fs_token_")) {
|
|
276
|
+
keysToRemove.push(key);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
for (let i = 0; i < localStorage.length; i++) {
|
|
280
|
+
const key = localStorage.key(i);
|
|
281
|
+
if (key && key.startsWith("fs_token_")) {
|
|
282
|
+
keysToRemove.push(key);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
keysToRemove.forEach((key) => {
|
|
286
|
+
sessionStorage.removeItem(key);
|
|
287
|
+
localStorage.removeItem(key);
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
export { OAUTH_ENDPOINTS, OAUTH_STORAGE_KEYS, buildAuthorizationUrl, clearAllTokens, clearStoredTokens, exchangeCodeForToken, generateOAuthState, getOAuthEndpoints, getStoredAccessToken, getStoredRefreshToken, getTokenStorageKey, getUserInfo, openOAuthPopup, parseCallbackParams, refreshAccessToken, storeOAuthState, storeTokens, validateAccessToken, validateOAuthState };
|
|
292
|
+
//# sourceMappingURL=index.js.map
|
|
293
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/auth/oauth.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\n * FamilySearch OAuth Authentication Module\n *\n * Provides OAuth 2.0 authentication utilities for FamilySearch API v3.\n * This module is designed to be framework-agnostic and can be used\n * in any JavaScript/TypeScript environment.\n */\n\nimport type {\n\tFamilySearchEnvironment,\n\tOAuthConfig,\n\tOAuthEndpoints,\n\tOAuthStateValidation,\n\tOAuthTokenResponse,\n} from \"../types\";\n\n// OAuth endpoints by environment\nconst OAUTH_ENDPOINTS: Record<FamilySearchEnvironment, OAuthEndpoints> = {\n\tproduction: {\n\t\tauthorization:\n\t\t\t\"https://ident.familysearch.org/cis-web/oauth2/v3/authorization\",\n\t\ttoken: \"https://ident.familysearch.org/cis-web/oauth2/v3/token\",\n\t\tcurrentUser: \"https://api.familysearch.org/platform/users/current\",\n\t},\n\tbeta: {\n\t\tauthorization:\n\t\t\t\"https://identbeta.familysearch.org/cis-web/oauth2/v3/authorization\",\n\t\ttoken: \"https://identbeta.familysearch.org/cis-web/oauth2/v3/token\",\n\t\tcurrentUser: \"https://apibeta.familysearch.org/platform/users/current\",\n\t},\n\tintegration: {\n\t\tauthorization:\n\t\t\t\"https://identint.familysearch.org/cis-web/oauth2/v3/authorization\",\n\t\ttoken: \"https://identint.familysearch.org/cis-web/oauth2/v3/token\",\n\t\tcurrentUser:\n\t\t\t\"https://api-integ.familysearch.org/platform/users/current\",\n\t},\n};\n\n/**\n * Get OAuth endpoints for a specific environment\n */\nexport function getOAuthEndpoints(\n\tenvironment: FamilySearchEnvironment = \"integration\"\n): OAuthEndpoints {\n\treturn OAUTH_ENDPOINTS[environment];\n}\n\n/**\n * Generate a cryptographically secure random state for CSRF protection\n */\nexport function generateOAuthState(): string {\n\tconst array = new Uint8Array(32);\n\tcrypto.getRandomValues(array);\n\treturn Array.from(array, (byte) => byte.toString(16).padStart(2, \"0\")).join(\n\t\t\"\"\n\t);\n}\n\n/**\n * Build the authorization URL for OAuth flow\n */\nexport function buildAuthorizationUrl(\n\tconfig: OAuthConfig,\n\tstate: string,\n\toptions: {\n\t\tscopes?: string[];\n\t\tprompt?: string;\n\t} = {}\n): string {\n\tconst endpoints = getOAuthEndpoints(config.environment);\n\tconst url = new URL(endpoints.authorization);\n\n\turl.searchParams.set(\"response_type\", \"code\");\n\turl.searchParams.set(\"client_id\", config.clientId);\n\turl.searchParams.set(\"redirect_uri\", config.redirectUri);\n\turl.searchParams.set(\"state\", state);\n\n\tif (options.scopes && options.scopes.length > 0) {\n\t\turl.searchParams.set(\"scope\", options.scopes.join(\" \"));\n\t}\n\n\tif (options.prompt) {\n\t\turl.searchParams.set(\"prompt\", options.prompt);\n\t}\n\n\treturn url.toString();\n}\n\n/**\n * Exchange authorization code for access token\n */\nexport async function exchangeCodeForToken(\n\tcode: string,\n\tconfig: OAuthConfig\n): Promise<OAuthTokenResponse> {\n\tconst endpoints = getOAuthEndpoints(config.environment);\n\n\tconst response = await fetch(endpoints.token, {\n\t\tmethod: \"POST\",\n\t\theaders: {\n\t\t\t\"Content-Type\": \"application/x-www-form-urlencoded\",\n\t\t\tAccept: \"application/json\",\n\t\t},\n\t\tbody: new URLSearchParams({\n\t\t\tgrant_type: \"authorization_code\",\n\t\t\tcode: code,\n\t\t\tclient_id: config.clientId,\n\t\t\tredirect_uri: config.redirectUri,\n\t\t}),\n\t});\n\n\tif (!response.ok) {\n\t\tconst error = await response.text();\n\t\tthrow new Error(`Failed to exchange code for token: ${error}`);\n\t}\n\n\treturn response.json();\n}\n\n/**\n * Refresh an access token using a refresh token\n */\nexport async function refreshAccessToken(\n\trefreshToken: string,\n\tconfig: OAuthConfig\n): Promise<OAuthTokenResponse> {\n\tconst endpoints = getOAuthEndpoints(config.environment);\n\n\tconst response = await fetch(endpoints.token, {\n\t\tmethod: \"POST\",\n\t\theaders: {\n\t\t\t\"Content-Type\": \"application/x-www-form-urlencoded\",\n\t\t\tAccept: \"application/json\",\n\t\t},\n\t\tbody: new URLSearchParams({\n\t\t\tgrant_type: \"refresh_token\",\n\t\t\trefresh_token: refreshToken,\n\t\t\tclient_id: config.clientId,\n\t\t}),\n\t});\n\n\tif (!response.ok) {\n\t\tconst error = await response.text();\n\t\tthrow new Error(`Failed to refresh token: ${error}`);\n\t}\n\n\treturn response.json();\n}\n\n/**\n * Validate an access token by making a test API call\n */\nexport async function validateAccessToken(\n\taccessToken: string,\n\tenvironment: FamilySearchEnvironment = \"integration\"\n): Promise<boolean> {\n\tconst endpoints = getOAuthEndpoints(environment);\n\n\ttry {\n\t\tconst response = await fetch(endpoints.currentUser, {\n\t\t\theaders: {\n\t\t\t\tAuthorization: `Bearer ${accessToken}`,\n\t\t\t\tAccept: \"application/json\",\n\t\t\t},\n\t\t});\n\n\t\treturn response.ok;\n\t} catch {\n\t\treturn false;\n\t}\n}\n\n/**\n * Get user info from access token\n */\nexport async function getUserInfo(\n\taccessToken: string,\n\tenvironment: FamilySearchEnvironment = \"integration\"\n): Promise<{\n\tsub: string;\n\tname?: string;\n\tgiven_name?: string;\n\tfamily_name?: string;\n\temail?: string;\n\temail_verified?: boolean;\n} | null> {\n\tconst endpoints = getOAuthEndpoints(environment);\n\n\ttry {\n\t\tconst response = await fetch(endpoints.currentUser, {\n\t\t\theaders: {\n\t\t\t\tAuthorization: `Bearer ${accessToken}`,\n\t\t\t\tAccept: \"application/json\",\n\t\t\t},\n\t\t});\n\n\t\tif (!response.ok) {\n\t\t\treturn null;\n\t\t}\n\n\t\tconst data = await response.json();\n\t\tconst fsUser = data.users?.[0];\n\n\t\tif (!fsUser || !fsUser.id) {\n\t\t\treturn null;\n\t\t}\n\n\t\treturn {\n\t\t\tsub: fsUser.id,\n\t\t\tname: fsUser.contactName || fsUser.displayName,\n\t\t\tgiven_name: fsUser.givenName,\n\t\t\tfamily_name: fsUser.familyName,\n\t\t\temail: fsUser.email,\n\t\t\temail_verified: fsUser.email ? true : false,\n\t\t};\n\t} catch {\n\t\treturn null;\n\t}\n}\n\n// ====================================\n// Browser-specific OAuth Helpers\n// ====================================\n\n/**\n * Storage keys for OAuth state management\n */\nexport const OAUTH_STORAGE_KEYS = {\n\tstate: \"fs_oauth_state\",\n\tlinkMode: \"fs_oauth_link_mode\",\n\tlang: \"fs_oauth_lang\",\n\tparentUid: \"fs_oauth_parent_uid\",\n} as const;\n\n/**\n * Store OAuth state in localStorage for popup flow\n * Uses localStorage instead of sessionStorage because popup windows\n * don't share sessionStorage with the parent window\n */\nexport function storeOAuthState(\n\tstate: string,\n\toptions: {\n\t\tisLinkMode?: boolean;\n\t\tlang?: string;\n\t\tparentUid?: string;\n\t} = {}\n): void {\n\tif (typeof localStorage === \"undefined\") {\n\t\t// In server-side or non-browser environments, state storage is not available\n\t\t// Callers should handle this by implementing their own state storage mechanism\n\t\tthrow new Error(\n\t\t\t\"localStorage is not available. For server-side usage, implement custom state storage.\"\n\t\t);\n\t}\n\n\tlocalStorage.setItem(OAUTH_STORAGE_KEYS.state, state);\n\n\tif (options.isLinkMode) {\n\t\tlocalStorage.setItem(OAUTH_STORAGE_KEYS.linkMode, \"true\");\n\t} else {\n\t\tlocalStorage.removeItem(OAUTH_STORAGE_KEYS.linkMode);\n\t}\n\n\tif (options.lang) {\n\t\tlocalStorage.setItem(OAUTH_STORAGE_KEYS.lang, options.lang);\n\t} else {\n\t\tlocalStorage.removeItem(OAUTH_STORAGE_KEYS.lang);\n\t}\n\n\tif (options.parentUid) {\n\t\tlocalStorage.setItem(OAUTH_STORAGE_KEYS.parentUid, options.parentUid);\n\t} else {\n\t\tlocalStorage.removeItem(OAUTH_STORAGE_KEYS.parentUid);\n\t}\n}\n\n/**\n * Validate OAuth state from callback and extract metadata\n * Returns invalid state if localStorage is not available (SSR/Node.js environments)\n */\nexport function validateOAuthState(state: string): OAuthStateValidation {\n\tif (typeof localStorage === \"undefined\") {\n\t\t// In server-side environments, return invalid state\n\t\t// Callers should implement their own state validation for SSR\n\t\treturn { valid: false, isLinkMode: false };\n\t}\n\n\tconst storedState = localStorage.getItem(OAUTH_STORAGE_KEYS.state);\n\tconst isLinkMode =\n\t\tlocalStorage.getItem(OAUTH_STORAGE_KEYS.linkMode) === \"true\";\n\tconst lang = localStorage.getItem(OAUTH_STORAGE_KEYS.lang) || undefined;\n\tconst parentUid =\n\t\tlocalStorage.getItem(OAUTH_STORAGE_KEYS.parentUid) || undefined;\n\n\t// Clean up stored values\n\tlocalStorage.removeItem(OAUTH_STORAGE_KEYS.state);\n\tlocalStorage.removeItem(OAUTH_STORAGE_KEYS.linkMode);\n\tlocalStorage.removeItem(OAUTH_STORAGE_KEYS.lang);\n\tlocalStorage.removeItem(OAUTH_STORAGE_KEYS.parentUid);\n\n\treturn {\n\t\tvalid: storedState === state,\n\t\tisLinkMode,\n\t\tlang,\n\t\tparentUid,\n\t};\n}\n\n/**\n * Open OAuth authorization in a popup window\n */\nexport function openOAuthPopup(\n\tauthUrl: string,\n\toptions: {\n\t\twidth?: number;\n\t\theight?: number;\n\t\twindowName?: string;\n\t} = {}\n): Window | null {\n\tif (typeof window === \"undefined\") {\n\t\tthrow new Error(\"window is not available\");\n\t}\n\n\tconst width = options.width || 500;\n\tconst height = options.height || 600;\n\tconst windowName = options.windowName || \"FamilySearch Login\";\n\n\tconst left = window.screenX + (window.outerWidth - width) / 2;\n\tconst top = window.screenY + (window.outerHeight - height) / 2;\n\n\tconst popup = window.open(\n\t\tauthUrl,\n\t\twindowName,\n\t\t`width=${width},height=${height},left=${left},top=${top},toolbar=no,location=no,status=no,menubar=no,scrollbars=yes,resizable=yes`\n\t);\n\n\tif (popup) {\n\t\tpopup.focus();\n\t}\n\n\treturn popup;\n}\n\n/**\n * Parse OAuth callback parameters from URL\n */\nexport function parseCallbackParams(\n\turl: string = typeof window !== \"undefined\" ? window.location.href : \"\"\n): {\n\tcode?: string;\n\tstate?: string;\n\terror?: string;\n\terror_description?: string;\n} {\n\tconst urlObj = new URL(url);\n\tconst params = urlObj.searchParams;\n\n\treturn {\n\t\tcode: params.get(\"code\") || undefined,\n\t\tstate: params.get(\"state\") || undefined,\n\t\terror: params.get(\"error\") || undefined,\n\t\terror_description: params.get(\"error_description\") || undefined,\n\t};\n}\n\n// ====================================\n// Token Storage Helpers\n// ====================================\n\n/**\n * Generate a storage key scoped to a user ID\n */\nexport function getTokenStorageKey(\n\tuserId: string,\n\ttype: \"access\" | \"expires\" | \"refresh\" | \"environment\"\n): string {\n\treturn `fs_token_${userId}_${type}`;\n}\n\n/**\n * Store access token with expiration\n * Per FamilySearch compatibility requirements:\n * - Access tokens stored in sessionStorage (cleared on browser close)\n * - Refresh tokens stored in localStorage (for re-authentication)\n */\nexport function storeTokens(\n\tuserId: string,\n\ttokens: {\n\t\taccessToken: string;\n\t\texpiresAt?: number;\n\t\trefreshToken?: string;\n\t\tenvironment?: string;\n\t}\n): void {\n\tif (typeof sessionStorage === \"undefined\" || typeof localStorage === \"undefined\") {\n\t\tthrow new Error(\"Storage APIs are not available\");\n\t}\n\n\t// Access tokens in sessionStorage (temporary)\n\tsessionStorage.setItem(\n\t\tgetTokenStorageKey(userId, \"access\"),\n\t\ttokens.accessToken\n\t);\n\n\tif (tokens.expiresAt) {\n\t\tsessionStorage.setItem(\n\t\t\tgetTokenStorageKey(userId, \"expires\"),\n\t\t\ttokens.expiresAt.toString()\n\t\t);\n\t}\n\n\t// Refresh tokens in localStorage (persistent)\n\tif (tokens.refreshToken) {\n\t\tlocalStorage.setItem(\n\t\t\tgetTokenStorageKey(userId, \"refresh\"),\n\t\t\ttokens.refreshToken\n\t\t);\n\t}\n\n\tif (tokens.environment) {\n\t\tlocalStorage.setItem(\n\t\t\tgetTokenStorageKey(userId, \"environment\"),\n\t\t\ttokens.environment\n\t\t);\n\t}\n}\n\n/**\n * Get stored access token\n */\nexport function getStoredAccessToken(userId: string): string | null {\n\tif (typeof sessionStorage === \"undefined\") {\n\t\treturn null;\n\t}\n\n\tconst token = sessionStorage.getItem(getTokenStorageKey(userId, \"access\"));\n\tconst expiresAt = sessionStorage.getItem(\n\t\tgetTokenStorageKey(userId, \"expires\")\n\t);\n\n\tif (!token) {\n\t\treturn null;\n\t}\n\n\t// Check expiration with 5-minute buffer\n\tconst EXPIRATION_BUFFER = 5 * 60 * 1000;\n\tif (expiresAt && Date.now() > parseInt(expiresAt) - EXPIRATION_BUFFER) {\n\t\treturn null;\n\t}\n\n\treturn token;\n}\n\n/**\n * Get stored refresh token\n */\nexport function getStoredRefreshToken(userId: string): string | null {\n\tif (typeof localStorage === \"undefined\") {\n\t\treturn null;\n\t}\n\n\treturn localStorage.getItem(getTokenStorageKey(userId, \"refresh\"));\n}\n\n/**\n * Clear all stored tokens for a user\n */\nexport function clearStoredTokens(userId: string): void {\n\tif (typeof sessionStorage !== \"undefined\") {\n\t\tsessionStorage.removeItem(getTokenStorageKey(userId, \"access\"));\n\t\tsessionStorage.removeItem(getTokenStorageKey(userId, \"expires\"));\n\t}\n\n\tif (typeof localStorage !== \"undefined\") {\n\t\tlocalStorage.removeItem(getTokenStorageKey(userId, \"refresh\"));\n\t\tlocalStorage.removeItem(getTokenStorageKey(userId, \"environment\"));\n\t}\n}\n\n/**\n * Clear all FamilySearch tokens from storage\n */\nexport function clearAllTokens(): void {\n\tif (typeof sessionStorage === \"undefined\" || typeof localStorage === \"undefined\") {\n\t\treturn;\n\t}\n\n\tconst keysToRemove: string[] = [];\n\n\t// Find all fs_token_* keys in sessionStorage\n\tfor (let i = 0; i < sessionStorage.length; i++) {\n\t\tconst key = sessionStorage.key(i);\n\t\tif (key && key.startsWith(\"fs_token_\")) {\n\t\t\tkeysToRemove.push(key);\n\t\t}\n\t}\n\n\t// Find all fs_token_* keys in localStorage\n\tfor (let i = 0; i < localStorage.length; i++) {\n\t\tconst key = localStorage.key(i);\n\t\tif (key && key.startsWith(\"fs_token_\")) {\n\t\t\tkeysToRemove.push(key);\n\t\t}\n\t}\n\n\t// Remove from both storages\n\tkeysToRemove.forEach((key) => {\n\t\tsessionStorage.removeItem(key);\n\t\tlocalStorage.removeItem(key);\n\t});\n}\n\nexport { OAUTH_ENDPOINTS };\n"]}
|