local-lite 0.0.12 → 0.0.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/README.md +5 -3
  2. package/dist/access/client.d.ts +9 -0
  3. package/dist/access/client.js +56 -0
  4. package/dist/access/engine.d.ts +2 -0
  5. package/dist/access/engine.js +46 -0
  6. package/dist/access/errors.d.ts +14 -0
  7. package/dist/access/errors.js +23 -0
  8. package/dist/access/guard.d.ts +24 -0
  9. package/dist/access/guard.js +104 -0
  10. package/dist/access/index.d.ts +5 -0
  11. package/dist/access/index.js +21 -0
  12. package/dist/access/react/index.d.ts +32 -0
  13. package/dist/access/react/index.js +42 -0
  14. package/dist/access/types.d.ts +26 -0
  15. package/dist/access/types.js +2 -0
  16. package/dist/auth/apple.d.ts +91 -0
  17. package/dist/auth/apple.js +264 -0
  18. package/dist/auth/github.d.ts +72 -0
  19. package/dist/auth/github.js +284 -0
  20. package/dist/auth/index.d.ts +3 -0
  21. package/dist/auth/index.js +3 -0
  22. package/dist/auth/microsoft.d.ts +76 -0
  23. package/dist/auth/microsoft.js +310 -0
  24. package/dist/backups/encryption.d.ts +18 -0
  25. package/dist/backups/encryption.js +122 -0
  26. package/dist/backups/index.d.ts +2 -0
  27. package/dist/backups/index.js +11 -1
  28. package/dist/backups/onedrive.d.ts +22 -0
  29. package/dist/backups/onedrive.js +114 -0
  30. package/dist/core/client.d.ts +33 -0
  31. package/dist/core/client.js +46 -0
  32. package/dist/core/conflict.d.ts +8 -1
  33. package/dist/core/conflict.js +75 -0
  34. package/dist/core/index.d.ts +1 -0
  35. package/dist/core/index.js +1 -0
  36. package/dist/core/maintenance.d.ts +66 -0
  37. package/dist/core/maintenance.js +157 -0
  38. package/dist/core/sync-worker.js +6 -3
  39. package/dist/core/types.d.ts +34 -4
  40. package/dist/index.d.ts +3 -0
  41. package/dist/index.js +3 -0
  42. package/dist/react/auth.d.ts +35 -0
  43. package/dist/react/auth.js +272 -0
  44. package/dist/react/index.d.ts +12 -0
  45. package/dist/react/index.js +51 -0
  46. package/dist/utils/portability.d.ts +73 -0
  47. package/dist/utils/portability.js +276 -0
  48. package/package.json +21 -1
@@ -0,0 +1,264 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.AppleAuthClient = exports.DEFAULT_APPLE_AUTH_SCOPES = exports.APPLE_TOKEN_ENDPOINT = exports.APPLE_AUTHORIZATION_ENDPOINT = void 0;
4
+ exports.createInMemoryAppleTokenStore = createInMemoryAppleTokenStore;
5
+ exports.createAppleAuthTokenStore = createAppleAuthTokenStore;
6
+ exports.APPLE_AUTHORIZATION_ENDPOINT = 'https://appleid.apple.com/auth/authorize';
7
+ exports.APPLE_TOKEN_ENDPOINT = 'https://appleid.apple.com/auth/token';
8
+ exports.DEFAULT_APPLE_AUTH_SCOPES = ['name', 'email'];
9
+ function secureRandomBytes(length) {
10
+ const crypto = globalThis.crypto;
11
+ if (!crypto?.getRandomValues) {
12
+ throw new Error('Secure random number generation is unavailable.');
13
+ }
14
+ return crypto.getRandomValues(new Uint8Array(length));
15
+ }
16
+ function base64UrlEncode(bytes) {
17
+ const alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_';
18
+ let result = '';
19
+ for (let index = 0; index < bytes.length; index += 3) {
20
+ const first = bytes[index];
21
+ const second = bytes[index + 1];
22
+ const third = bytes[index + 2];
23
+ result += alphabet[first >> 2];
24
+ result += alphabet[((first & 3) << 4) | (second === undefined ? 0 : second >> 4)];
25
+ if (second !== undefined) {
26
+ result += alphabet[((second & 15) << 2) | (third === undefined ? 0 : third >> 6)];
27
+ }
28
+ if (third !== undefined)
29
+ result += alphabet[third & 63];
30
+ }
31
+ return result;
32
+ }
33
+ function parseJwtClaims(jwt) {
34
+ try {
35
+ const parts = jwt.split('.');
36
+ if (parts.length < 2)
37
+ return {};
38
+ let base64 = parts[1].replace(/-/g, '+').replace(/_/g, '/');
39
+ while (base64.length % 4) {
40
+ base64 += '=';
41
+ }
42
+ const json = typeof atob === 'function'
43
+ ? atob(base64)
44
+ : Buffer.from(base64, 'base64').toString('utf8');
45
+ return JSON.parse(json);
46
+ }
47
+ catch {
48
+ return {};
49
+ }
50
+ }
51
+ function validateTokens(value) {
52
+ if (!value || typeof value !== 'object')
53
+ return false;
54
+ const tokens = value;
55
+ return typeof tokens.idToken === 'string';
56
+ }
57
+ function validateUser(value) {
58
+ if (!value || typeof value !== 'object')
59
+ return false;
60
+ const user = value;
61
+ return typeof user.id === 'string' && typeof user.email === 'string';
62
+ }
63
+ function createInMemoryAppleTokenStore() {
64
+ let storedTokens = null;
65
+ let storedUser = null;
66
+ return {
67
+ getTokens: async () => storedTokens,
68
+ setTokens: async (tokens) => {
69
+ storedTokens = tokens;
70
+ },
71
+ removeTokens: async () => {
72
+ storedTokens = null;
73
+ },
74
+ getUser: async () => storedUser,
75
+ setUser: async (user) => {
76
+ storedUser = user;
77
+ },
78
+ };
79
+ }
80
+ function createAppleAuthTokenStore(storage, prefix = 'local-lite-apple-auth') {
81
+ const tokensKey = `${prefix}-tokens`;
82
+ const userKey = `${prefix}-user`;
83
+ return {
84
+ getTokens: async () => {
85
+ const raw = await storage.getItem(tokensKey);
86
+ return validateTokens(raw) ? raw : null;
87
+ },
88
+ setTokens: tokens => storage.setItem(tokensKey, tokens),
89
+ removeTokens: () => storage.removeItem(tokensKey),
90
+ getUser: async () => {
91
+ const raw = await storage.getItem(userKey);
92
+ return validateUser(raw) ? raw : null;
93
+ },
94
+ setUser: async (user) => {
95
+ if (user) {
96
+ await storage.setItem(userKey, user);
97
+ }
98
+ else {
99
+ await storage.removeItem(userKey);
100
+ }
101
+ },
102
+ };
103
+ }
104
+ class AppleAuthClient {
105
+ options;
106
+ tokenStore;
107
+ listeners = new Set();
108
+ currentUser = null;
109
+ constructor(options) {
110
+ this.options = options;
111
+ if (!options.clientId?.trim()) {
112
+ throw new Error('Apple OAuth client ID (Service ID or App Bundle ID) is required.');
113
+ }
114
+ if (options.tokenStore) {
115
+ this.tokenStore = options.tokenStore;
116
+ }
117
+ else if (options.storage) {
118
+ this.tokenStore = createAppleAuthTokenStore(options.storage);
119
+ }
120
+ else {
121
+ this.tokenStore = createInMemoryAppleTokenStore();
122
+ }
123
+ }
124
+ onAuthStateChange(listener) {
125
+ this.listeners.add(listener);
126
+ return () => {
127
+ this.listeners.delete(listener);
128
+ };
129
+ }
130
+ notify(user) {
131
+ this.currentUser = user;
132
+ for (const listener of this.listeners) {
133
+ try {
134
+ listener(user);
135
+ }
136
+ catch (err) {
137
+ console.error('Error in AppleAuthClient listener:', err);
138
+ }
139
+ }
140
+ }
141
+ /**
142
+ * Signs in with Apple.
143
+ * If a native credential object (from expo-apple-authentication or native ASAuthorizationAppleIDCredential)
144
+ * is passed, processes and verifies it directly.
145
+ * Otherwise, launches the PKCE web/browser authorization flow.
146
+ */
147
+ async signIn(nativeCredential) {
148
+ if (nativeCredential) {
149
+ return this.processNativeCredential(nativeCredential);
150
+ }
151
+ if (!this.options.authorize) {
152
+ throw new Error('AppleAuthClient requires an authorize callback for web/browser authorization, or a native credential passed to signIn().');
153
+ }
154
+ if (!this.options.redirectUri) {
155
+ throw new Error('Apple OAuth redirectUri is required for web authorization flow.');
156
+ }
157
+ const randomBytes = this.options.randomBytes ?? secureRandomBytes;
158
+ const state = base64UrlEncode(randomBytes(32));
159
+ const nonce = base64UrlEncode(randomBytes(32));
160
+ const scopes = this.options.scopes && this.options.scopes.length > 0
161
+ ? this.options.scopes.join(' ')
162
+ : exports.DEFAULT_APPLE_AUTH_SCOPES.join(' ');
163
+ const params = new URLSearchParams({
164
+ client_id: this.options.clientId,
165
+ redirect_uri: this.options.redirectUri,
166
+ response_type: 'code id_token',
167
+ response_mode: 'form_post',
168
+ scope: scopes,
169
+ state,
170
+ nonce,
171
+ });
172
+ const authorization = await this.options.authorize({
173
+ authorizationUrl: `${exports.APPLE_AUTHORIZATION_ENDPOINT}?${params.toString()}`,
174
+ redirectUri: this.options.redirectUri,
175
+ state,
176
+ nonce,
177
+ });
178
+ if (!authorization) {
179
+ return null;
180
+ }
181
+ if (authorization.state && authorization.state !== state) {
182
+ throw new Error('Apple OAuth state validation failed. Please try connecting again.');
183
+ }
184
+ if (!authorization.id_token) {
185
+ throw new Error('Apple OAuth did not return an id_token.');
186
+ }
187
+ const claims = parseJwtClaims(authorization.id_token);
188
+ const id = claims.sub || 'apple-user';
189
+ const email = claims.email || authorization.user?.email || `${id}@privaterelay.appleid.com`;
190
+ const givenName = authorization.user?.name?.firstName;
191
+ const familyName = authorization.user?.name?.lastName;
192
+ const nameParts = [givenName, familyName].filter(Boolean);
193
+ const name = nameParts.length > 0 ? nameParts.join(' ') : email.split('@')[0];
194
+ const tokens = {
195
+ idToken: authorization.id_token,
196
+ expiresAt: claims.exp ? claims.exp * 1000 : undefined,
197
+ };
198
+ await this.tokenStore.setTokens(tokens);
199
+ const user = {
200
+ id,
201
+ email,
202
+ name,
203
+ givenName,
204
+ familyName,
205
+ identityToken: authorization.id_token,
206
+ authorizationCode: authorization.code,
207
+ };
208
+ await this.tokenStore.setUser(user);
209
+ this.notify(user);
210
+ return user;
211
+ }
212
+ async processNativeCredential(cred) {
213
+ const claims = cred.identityToken ? parseJwtClaims(cred.identityToken) : {};
214
+ const id = cred.user || claims.sub;
215
+ const email = cred.email || claims.email || `${id}@privaterelay.appleid.com`;
216
+ const givenName = cred.fullName?.givenName || undefined;
217
+ const familyName = cred.fullName?.familyName || undefined;
218
+ const nameParts = [givenName, familyName].filter(Boolean);
219
+ const name = nameParts.length > 0 ? nameParts.join(' ') : email.split('@')[0];
220
+ const priorUser = await this.tokenStore.getUser();
221
+ // Apple only returns full name and email on the very first sign in; preserve prior if present
222
+ const resolvedGivenName = givenName || priorUser?.givenName;
223
+ const resolvedFamilyName = familyName || priorUser?.familyName;
224
+ const resolvedName = name !== email.split('@')[0] ? name : priorUser?.name || name;
225
+ const user = {
226
+ id,
227
+ email: cred.email || priorUser?.email || email,
228
+ name: resolvedName,
229
+ givenName: resolvedGivenName,
230
+ familyName: resolvedFamilyName,
231
+ identityToken: cred.identityToken || undefined,
232
+ authorizationCode: cred.authorizationCode || undefined,
233
+ };
234
+ if (cred.identityToken) {
235
+ await this.tokenStore.setTokens({
236
+ idToken: cred.identityToken,
237
+ expiresAt: claims.exp ? claims.exp * 1000 : undefined,
238
+ });
239
+ }
240
+ await this.tokenStore.setUser(user);
241
+ this.notify(user);
242
+ return user;
243
+ }
244
+ async signOut() {
245
+ await this.tokenStore.removeTokens();
246
+ await this.tokenStore.setUser(null);
247
+ this.notify(null);
248
+ }
249
+ async isSignedIn() {
250
+ const tokens = await this.tokenStore.getTokens();
251
+ return validateTokens(tokens);
252
+ }
253
+ async getUser() {
254
+ if (this.currentUser)
255
+ return this.currentUser;
256
+ const cached = await this.tokenStore.getUser();
257
+ if (cached) {
258
+ this.currentUser = cached;
259
+ return cached;
260
+ }
261
+ return null;
262
+ }
263
+ }
264
+ exports.AppleAuthClient = AppleAuthClient;
@@ -0,0 +1,72 @@
1
+ export declare const GITHUB_AUTHORIZATION_ENDPOINT = "https://github.com/login/oauth/authorize";
2
+ export declare const GITHUB_TOKEN_ENDPOINT = "https://github.com/login/oauth/access_token";
3
+ export declare const GITHUB_API_ENDPOINT = "https://api.github.com";
4
+ export declare const DEFAULT_GITHUB_AUTH_SCOPES: string[];
5
+ export interface GitHubUser {
6
+ id: string;
7
+ login: string;
8
+ name: string;
9
+ email: string;
10
+ avatarUrl?: string;
11
+ bio?: string;
12
+ company?: string;
13
+ location?: string;
14
+ }
15
+ export interface GitHubAuthTokens {
16
+ accessToken: string;
17
+ tokenType?: string;
18
+ scope?: string;
19
+ expiresAt?: number;
20
+ }
21
+ export interface GitHubAuthTokenStore {
22
+ getTokens(): Promise<GitHubAuthTokens | null>;
23
+ setTokens(tokens: GitHubAuthTokens): Promise<void>;
24
+ removeTokens(): Promise<void>;
25
+ getUser(): Promise<GitHubUser | null>;
26
+ setUser(user: GitHubUser | null): Promise<void>;
27
+ }
28
+ export interface GitHubAuthKeyValueStorage {
29
+ getItem(key: string): Promise<unknown>;
30
+ setItem(key: string, value: unknown): Promise<void>;
31
+ removeItem(key: string): Promise<void>;
32
+ }
33
+ export interface GitHubAuthorizationRequest {
34
+ authorizationUrl: string;
35
+ redirectUri: string;
36
+ state: string;
37
+ }
38
+ export interface GitHubAuthorizationResponse {
39
+ code: string;
40
+ state: string;
41
+ }
42
+ export interface GitHubAuthClientOptions {
43
+ clientId: string;
44
+ clientSecret?: string;
45
+ tokenExchangeUrl?: string;
46
+ redirectUri: string;
47
+ scopes?: string[];
48
+ authorize: (request: GitHubAuthorizationRequest) => Promise<GitHubAuthorizationResponse | null>;
49
+ tokenStore?: GitHubAuthTokenStore;
50
+ storage?: GitHubAuthKeyValueStorage;
51
+ randomBytes?: (length: number) => Uint8Array;
52
+ fetch?: typeof fetch;
53
+ allowSignup?: boolean;
54
+ }
55
+ export declare function createInMemoryGitHubTokenStore(): GitHubAuthTokenStore;
56
+ export declare function createGitHubAuthTokenStore(storage: GitHubAuthKeyValueStorage, prefix?: string): GitHubAuthTokenStore;
57
+ export declare class GitHubAuthClient {
58
+ private readonly options;
59
+ private readonly tokenStore;
60
+ private readonly fetcher;
61
+ private readonly listeners;
62
+ private currentUser;
63
+ constructor(options: GitHubAuthClientOptions);
64
+ onAuthStateChange(listener: (user: GitHubUser | null) => void): () => void;
65
+ private notify;
66
+ signIn(): Promise<GitHubUser | null>;
67
+ signOut(): Promise<void>;
68
+ isSignedIn(): Promise<boolean>;
69
+ getUser(): Promise<GitHubUser | null>;
70
+ getValidAccessToken(): Promise<string>;
71
+ private fetchUserProfile;
72
+ }
@@ -0,0 +1,284 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.GitHubAuthClient = exports.DEFAULT_GITHUB_AUTH_SCOPES = exports.GITHUB_API_ENDPOINT = exports.GITHUB_TOKEN_ENDPOINT = exports.GITHUB_AUTHORIZATION_ENDPOINT = void 0;
4
+ exports.createInMemoryGitHubTokenStore = createInMemoryGitHubTokenStore;
5
+ exports.createGitHubAuthTokenStore = createGitHubAuthTokenStore;
6
+ exports.GITHUB_AUTHORIZATION_ENDPOINT = 'https://github.com/login/oauth/authorize';
7
+ exports.GITHUB_TOKEN_ENDPOINT = 'https://github.com/login/oauth/access_token';
8
+ exports.GITHUB_API_ENDPOINT = 'https://api.github.com';
9
+ exports.DEFAULT_GITHUB_AUTH_SCOPES = ['read:user', 'user:email'];
10
+ function secureRandomBytes(length) {
11
+ const crypto = globalThis.crypto;
12
+ if (!crypto?.getRandomValues) {
13
+ throw new Error('Secure random number generation is unavailable.');
14
+ }
15
+ return crypto.getRandomValues(new Uint8Array(length));
16
+ }
17
+ function base64UrlEncode(bytes) {
18
+ const alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_';
19
+ let result = '';
20
+ for (let index = 0; index < bytes.length; index += 3) {
21
+ const first = bytes[index];
22
+ const second = bytes[index + 1];
23
+ const third = bytes[index + 2];
24
+ result += alphabet[first >> 2];
25
+ result += alphabet[((first & 3) << 4) | (second === undefined ? 0 : second >> 4)];
26
+ if (second !== undefined) {
27
+ result += alphabet[((second & 15) << 2) | (third === undefined ? 0 : third >> 6)];
28
+ }
29
+ if (third !== undefined)
30
+ result += alphabet[third & 63];
31
+ }
32
+ return result;
33
+ }
34
+ function validateTokens(value) {
35
+ if (!value || typeof value !== 'object')
36
+ return false;
37
+ const tokens = value;
38
+ return typeof tokens.accessToken === 'string';
39
+ }
40
+ function validateUser(value) {
41
+ if (!value || typeof value !== 'object')
42
+ return false;
43
+ const user = value;
44
+ return (typeof user.id === 'string' &&
45
+ typeof user.login === 'string' &&
46
+ typeof user.name === 'string');
47
+ }
48
+ function createInMemoryGitHubTokenStore() {
49
+ let storedTokens = null;
50
+ let storedUser = null;
51
+ return {
52
+ getTokens: async () => storedTokens,
53
+ setTokens: async (tokens) => {
54
+ storedTokens = tokens;
55
+ },
56
+ removeTokens: async () => {
57
+ storedTokens = null;
58
+ },
59
+ getUser: async () => storedUser,
60
+ setUser: async (user) => {
61
+ storedUser = user;
62
+ },
63
+ };
64
+ }
65
+ function createGitHubAuthTokenStore(storage, prefix = 'local-lite-github-auth') {
66
+ const tokensKey = `${prefix}-tokens`;
67
+ const userKey = `${prefix}-user`;
68
+ return {
69
+ getTokens: async () => {
70
+ const raw = await storage.getItem(tokensKey);
71
+ return validateTokens(raw) ? raw : null;
72
+ },
73
+ setTokens: tokens => storage.setItem(tokensKey, tokens),
74
+ removeTokens: () => storage.removeItem(tokensKey),
75
+ getUser: async () => {
76
+ const raw = await storage.getItem(userKey);
77
+ return validateUser(raw) ? raw : null;
78
+ },
79
+ setUser: async (user) => {
80
+ if (user) {
81
+ await storage.setItem(userKey, user);
82
+ }
83
+ else {
84
+ await storage.removeItem(userKey);
85
+ }
86
+ },
87
+ };
88
+ }
89
+ class GitHubAuthClient {
90
+ options;
91
+ tokenStore;
92
+ fetcher;
93
+ listeners = new Set();
94
+ currentUser = null;
95
+ constructor(options) {
96
+ this.options = options;
97
+ if (!options.clientId?.trim()) {
98
+ throw new Error('GitHub OAuth client ID is required.');
99
+ }
100
+ if (!options.redirectUri?.trim()) {
101
+ throw new Error('GitHub OAuth redirect URI is required.');
102
+ }
103
+ this.fetcher = options.fetch ?? fetch;
104
+ if (options.tokenStore) {
105
+ this.tokenStore = options.tokenStore;
106
+ }
107
+ else if (options.storage) {
108
+ this.tokenStore = createGitHubAuthTokenStore(options.storage);
109
+ }
110
+ else {
111
+ this.tokenStore = createInMemoryGitHubTokenStore();
112
+ }
113
+ }
114
+ onAuthStateChange(listener) {
115
+ this.listeners.add(listener);
116
+ return () => {
117
+ this.listeners.delete(listener);
118
+ };
119
+ }
120
+ notify(user) {
121
+ this.currentUser = user;
122
+ for (const listener of this.listeners) {
123
+ try {
124
+ listener(user);
125
+ }
126
+ catch (err) {
127
+ console.error('Error in GitHubAuthClient listener:', err);
128
+ }
129
+ }
130
+ }
131
+ async signIn() {
132
+ const randomBytes = this.options.randomBytes ?? secureRandomBytes;
133
+ const stateBytes = randomBytes(32);
134
+ if (stateBytes.length !== 32) {
135
+ throw new Error('The randomBytes provider returned an invalid number of bytes.');
136
+ }
137
+ const state = base64UrlEncode(stateBytes);
138
+ const scopes = this.options.scopes && this.options.scopes.length > 0
139
+ ? this.options.scopes
140
+ : exports.DEFAULT_GITHUB_AUTH_SCOPES;
141
+ const authorizationParams = new URLSearchParams({
142
+ client_id: this.options.clientId,
143
+ redirect_uri: this.options.redirectUri,
144
+ scope: scopes.join(' '),
145
+ state,
146
+ allow_signup: String(this.options.allowSignup ?? true),
147
+ });
148
+ const authorization = await this.options.authorize({
149
+ authorizationUrl: `${exports.GITHUB_AUTHORIZATION_ENDPOINT}?${authorizationParams.toString()}`,
150
+ redirectUri: this.options.redirectUri,
151
+ state,
152
+ });
153
+ if (!authorization) {
154
+ return null;
155
+ }
156
+ if (authorization.state !== state) {
157
+ throw new Error('GitHub OAuth state validation failed. Please try connecting again.');
158
+ }
159
+ if (!authorization.code) {
160
+ throw new Error('GitHub OAuth did not return an authorization code.');
161
+ }
162
+ const tokenEndpoint = this.options.tokenExchangeUrl || exports.GITHUB_TOKEN_ENDPOINT;
163
+ const requestPayload = {
164
+ client_id: this.options.clientId,
165
+ code: authorization.code,
166
+ redirect_uri: this.options.redirectUri,
167
+ state,
168
+ };
169
+ if (this.options.clientSecret) {
170
+ requestPayload.client_secret = this.options.clientSecret;
171
+ }
172
+ const response = await this.fetcher(tokenEndpoint, {
173
+ method: 'POST',
174
+ headers: {
175
+ 'Content-Type': 'application/json',
176
+ Accept: 'application/json',
177
+ },
178
+ body: JSON.stringify(requestPayload),
179
+ });
180
+ const payload = (await response.json());
181
+ if (!response.ok || payload.error) {
182
+ throw new Error(payload.error_description || payload.error || `GitHub OAuth token exchange failed (${response.status}).`);
183
+ }
184
+ if (!payload.access_token) {
185
+ throw new Error('GitHub OAuth did not return an access token.');
186
+ }
187
+ const storedTokens = {
188
+ accessToken: payload.access_token,
189
+ tokenType: payload.token_type,
190
+ scope: payload.scope,
191
+ expiresAt: payload.expires_in ? Date.now() + payload.expires_in * 1000 : undefined,
192
+ };
193
+ await this.tokenStore.setTokens(storedTokens);
194
+ const user = await this.fetchUserProfile(storedTokens.accessToken);
195
+ await this.tokenStore.setUser(user);
196
+ this.notify(user);
197
+ return user;
198
+ }
199
+ async signOut() {
200
+ await this.tokenStore.removeTokens();
201
+ await this.tokenStore.setUser(null);
202
+ this.notify(null);
203
+ }
204
+ async isSignedIn() {
205
+ const tokens = await this.tokenStore.getTokens();
206
+ return validateTokens(tokens);
207
+ }
208
+ async getUser() {
209
+ if (this.currentUser) {
210
+ return this.currentUser;
211
+ }
212
+ const cached = await this.tokenStore.getUser();
213
+ if (cached) {
214
+ this.currentUser = cached;
215
+ return cached;
216
+ }
217
+ const tokens = await this.tokenStore.getTokens();
218
+ if (!validateTokens(tokens)) {
219
+ return null;
220
+ }
221
+ try {
222
+ const user = await this.fetchUserProfile(tokens.accessToken);
223
+ await this.tokenStore.setUser(user);
224
+ this.currentUser = user;
225
+ return user;
226
+ }
227
+ catch {
228
+ return null;
229
+ }
230
+ }
231
+ async getValidAccessToken() {
232
+ const tokens = await this.tokenStore.getTokens();
233
+ if (!validateTokens(tokens)) {
234
+ throw new Error('No GitHub account signed in. Please sign in first.');
235
+ }
236
+ return tokens.accessToken;
237
+ }
238
+ async fetchUserProfile(accessToken) {
239
+ const res = await this.fetcher(`${exports.GITHUB_API_ENDPOINT}/user`, {
240
+ headers: {
241
+ Authorization: `Bearer ${accessToken}`,
242
+ Accept: 'application/vnd.github.v3+json',
243
+ 'User-Agent': 'local-lite-auth',
244
+ },
245
+ });
246
+ if (!res.ok) {
247
+ throw new Error(`Failed to fetch GitHub user profile (${res.status}).`);
248
+ }
249
+ const raw = (await res.json());
250
+ let email = raw.email;
251
+ // If email is private on profile, fetch from /user/emails endpoint
252
+ if (!email) {
253
+ try {
254
+ const emailsRes = await this.fetcher(`${exports.GITHUB_API_ENDPOINT}/user/emails`, {
255
+ headers: {
256
+ Authorization: `Bearer ${accessToken}`,
257
+ Accept: 'application/vnd.github.v3+json',
258
+ 'User-Agent': 'local-lite-auth',
259
+ },
260
+ });
261
+ if (emailsRes.ok) {
262
+ const emails = (await emailsRes.json());
263
+ const primary = emails.find(e => e.primary && e.verified) || emails[0];
264
+ if (primary)
265
+ email = primary.email;
266
+ }
267
+ }
268
+ catch {
269
+ // Fallback to username generated placeholder if emails restricted
270
+ }
271
+ }
272
+ return {
273
+ id: String(raw.id),
274
+ login: raw.login,
275
+ name: raw.name || raw.login,
276
+ email: email || `${raw.login}@users.noreply.github.com`,
277
+ avatarUrl: raw.avatar_url,
278
+ bio: raw.bio,
279
+ company: raw.company,
280
+ location: raw.location,
281
+ };
282
+ }
283
+ }
284
+ exports.GitHubAuthClient = GitHubAuthClient;
@@ -1 +1,4 @@
1
1
  export * from './google.js';
2
+ export * from './microsoft.js';
3
+ export * from './github.js';
4
+ export * from './apple.js';
@@ -15,3 +15,6 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
17
  __exportStar(require("./google.js"), exports);
18
+ __exportStar(require("./microsoft.js"), exports);
19
+ __exportStar(require("./github.js"), exports);
20
+ __exportStar(require("./apple.js"), exports);