@luxdb/sdk 1.2.1 → 1.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,504 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.LuxAuthClient = void 0;
4
+ const utils_1 = require("./utils");
5
+ class LuxAuthClient {
6
+ constructor(options = {}) {
7
+ this.currentSession = null;
8
+ this.loadedSession = false;
9
+ this.refreshTimer = null;
10
+ this.listeners = new Set();
11
+ this.httpUrl = options.httpUrl?.replace(/\/+$/, '');
12
+ this.apiKey = options.apiKey;
13
+ this.authToken = options.authToken;
14
+ this.fetchImpl = resolveFetch(options.fetch);
15
+ this.persistSession = options.persistSession ?? false;
16
+ this.autoRefreshToken = options.autoRefreshToken ?? this.persistSession;
17
+ this.storage = options.storage === undefined ? defaultBrowserStorage() : options.storage;
18
+ this.storageKey = options.storageKey ?? 'lux.auth.session';
19
+ this.refreshMarginSeconds = options.refreshMarginSeconds ?? 60;
20
+ if (this.authToken) {
21
+ this.currentSession = null;
22
+ }
23
+ }
24
+ async getSession() {
25
+ try {
26
+ return (0, utils_1.ok)({ session: await this.getSessionValue() });
27
+ }
28
+ catch (error) {
29
+ return (0, utils_1.err)('LUX_AUTH_SESSION_ERROR', 'Failed to get auth session', (0, utils_1.toLuxError)(error));
30
+ }
31
+ }
32
+ async getSessionValue() {
33
+ await this.loadStoredSession();
34
+ if (this.currentSession && isExpired(this.currentSession, this.refreshMarginSeconds)) {
35
+ if (this.autoRefreshToken && this.currentSession.refresh_token) {
36
+ const refreshed = await this.refreshSession(this.currentSession.refresh_token);
37
+ return refreshed.data?.session ?? null;
38
+ }
39
+ await this.clearSessionValue();
40
+ return null;
41
+ }
42
+ return this.currentSession;
43
+ }
44
+ async getAccessToken() {
45
+ const session = await this.getSessionValue();
46
+ return session?.access_token ?? this.authToken;
47
+ }
48
+ async setSession(session) {
49
+ try {
50
+ return (0, utils_1.ok)({ session: await this.setSessionValue(session) });
51
+ }
52
+ catch (error) {
53
+ return (0, utils_1.err)('LUX_AUTH_SESSION_ERROR', 'Failed to set auth session', (0, utils_1.toLuxError)(error));
54
+ }
55
+ }
56
+ async setSessionValue(session) {
57
+ if (typeof session === 'string') {
58
+ this.authToken = session;
59
+ this.currentSession = null;
60
+ return null;
61
+ }
62
+ if (!session) {
63
+ await this.clearSessionValue();
64
+ return null;
65
+ }
66
+ await this.saveSession(normalizeSession(session), 'SESSION_UPDATED');
67
+ return this.currentSession;
68
+ }
69
+ async clearSession() {
70
+ try {
71
+ await this.clearSessionValue();
72
+ return (0, utils_1.ok)(null);
73
+ }
74
+ catch (error) {
75
+ return (0, utils_1.err)('LUX_AUTH_SESSION_ERROR', 'Failed to clear auth session', (0, utils_1.toLuxError)(error));
76
+ }
77
+ }
78
+ async clearSessionValue() {
79
+ this.authToken = undefined;
80
+ this.currentSession = null;
81
+ this.loadedSession = true;
82
+ this.clearRefreshTimer();
83
+ if (this.persistSession && this.storage) {
84
+ await this.storage.removeItem(this.storageKey);
85
+ }
86
+ this.emit('SIGNED_OUT', null);
87
+ }
88
+ onAuthStateChange(callback) {
89
+ this.listeners.add(callback);
90
+ void this.getSessionValue().then((session) => callback('INITIAL_SESSION', session));
91
+ return {
92
+ unsubscribe: () => {
93
+ this.listeners.delete(callback);
94
+ },
95
+ };
96
+ }
97
+ async signUp(options) {
98
+ try {
99
+ const session = await this.requestRaw('/auth/v1/signup', {
100
+ method: 'POST',
101
+ body: JSON.stringify({
102
+ email: options.email,
103
+ password: options.password,
104
+ data: options.data,
105
+ }),
106
+ apiKey: true,
107
+ });
108
+ await this.saveSession(normalizeSession(session), 'SIGNED_IN');
109
+ return (0, utils_1.ok)({ session: this.currentSession, user: this.currentSession.user });
110
+ }
111
+ catch (error) {
112
+ return (0, utils_1.err)('LUX_AUTH_SIGNUP_ERROR', 'Failed to sign up', (0, utils_1.toLuxError)(error));
113
+ }
114
+ }
115
+ async signInWithPassword(options) {
116
+ try {
117
+ const session = await this.requestRaw('/auth/v1/token', {
118
+ method: 'POST',
119
+ body: JSON.stringify({
120
+ grant_type: 'password',
121
+ email: options.email,
122
+ password: options.password,
123
+ }),
124
+ apiKey: true,
125
+ });
126
+ await this.saveSession(normalizeSession(session), 'SIGNED_IN');
127
+ return (0, utils_1.ok)({ session: this.currentSession, user: this.currentSession.user });
128
+ }
129
+ catch (error) {
130
+ return (0, utils_1.err)('LUX_AUTH_SIGNIN_ERROR', 'Failed to sign in', (0, utils_1.toLuxError)(error));
131
+ }
132
+ }
133
+ async signInWithOAuth(options) {
134
+ try {
135
+ if (!this.httpUrl) {
136
+ throw new Error('Lux auth requires httpUrl');
137
+ }
138
+ const redirectTo = options.redirectTo ?? browserLocation();
139
+ const url = new URL(`${this.httpUrl}/auth/v1/authorize`);
140
+ url.searchParams.set('provider', options.provider);
141
+ if (redirectTo)
142
+ url.searchParams.set('redirect_to', redirectTo);
143
+ const target = url.toString();
144
+ if (!options.skipRedirect && typeof globalThis !== 'undefined') {
145
+ const location = globalThis.location;
146
+ if (location?.assign)
147
+ location.assign(target);
148
+ }
149
+ return (0, utils_1.ok)({ url: target });
150
+ }
151
+ catch (error) {
152
+ return (0, utils_1.err)('LUX_AUTH_OAUTH_ERROR', 'Failed to start OAuth sign in', (0, utils_1.toLuxError)(error));
153
+ }
154
+ }
155
+ async consumeOAuthRedirect(url = browserLocation()) {
156
+ try {
157
+ if (!url)
158
+ return (0, utils_1.ok)({ session: null, user: null });
159
+ const parsed = new URL(url);
160
+ const params = new URLSearchParams(parsed.hash.replace(/^#/, ''));
161
+ const accessToken = params.get('access_token');
162
+ const refreshToken = params.get('refresh_token');
163
+ if (!accessToken || !refreshToken)
164
+ return (0, utils_1.ok)({ session: null, user: null });
165
+ const session = normalizeSession({
166
+ access_token: accessToken,
167
+ refresh_token: refreshToken,
168
+ expires_in: Number(params.get('expires_in') || 0),
169
+ token_type: 'bearer',
170
+ user: { id: '', email: '' },
171
+ });
172
+ if (this.httpUrl) {
173
+ const user = await this.getUser(accessToken);
174
+ if (user.error)
175
+ return user;
176
+ if (!user.data?.user) {
177
+ return (0, utils_1.err)('LUX_AUTH_USER_ERROR', 'OAuth redirect token did not resolve to a user');
178
+ }
179
+ session.user = user.data.user;
180
+ }
181
+ await this.saveSession(session, 'SIGNED_IN');
182
+ return (0, utils_1.ok)({ session: this.currentSession, user: this.currentSession?.user ?? null });
183
+ }
184
+ catch (error) {
185
+ return (0, utils_1.err)('LUX_AUTH_OAUTH_ERROR', 'Failed to consume OAuth redirect', (0, utils_1.toLuxError)(error));
186
+ }
187
+ }
188
+ async refreshSession(refreshToken) {
189
+ try {
190
+ const session = await this.requestRaw('/auth/v1/token', {
191
+ method: 'POST',
192
+ body: JSON.stringify({
193
+ grant_type: 'refresh_token',
194
+ refresh_token: refreshToken,
195
+ }),
196
+ apiKey: true,
197
+ });
198
+ await this.saveSession(normalizeSession(session), 'TOKEN_REFRESHED');
199
+ return (0, utils_1.ok)({ session: this.currentSession, user: this.currentSession.user });
200
+ }
201
+ catch (error) {
202
+ return (0, utils_1.err)('LUX_AUTH_REFRESH_ERROR', 'Failed to refresh auth session', (0, utils_1.toLuxError)(error));
203
+ }
204
+ }
205
+ async getUser(session) {
206
+ if (!session) {
207
+ await this.getSessionValue();
208
+ }
209
+ try {
210
+ return (0, utils_1.ok)(await this.requestRaw('/auth/v1/user', {
211
+ method: 'GET',
212
+ token: this.tokenFrom(session),
213
+ }));
214
+ }
215
+ catch (error) {
216
+ return (0, utils_1.err)('LUX_AUTH_USER_ERROR', 'Failed to get auth user', (0, utils_1.toLuxError)(error));
217
+ }
218
+ }
219
+ async logout(sessionOrRefreshToken) {
220
+ if (!sessionOrRefreshToken) {
221
+ sessionOrRefreshToken = await this.getSessionValue() ?? undefined;
222
+ }
223
+ const token = typeof sessionOrRefreshToken === 'string'
224
+ ? sessionOrRefreshToken
225
+ : sessionOrRefreshToken?.access_token;
226
+ const refreshToken = typeof sessionOrRefreshToken === 'string'
227
+ ? undefined
228
+ : sessionOrRefreshToken?.refresh_token;
229
+ try {
230
+ await this.requestRaw('/auth/v1/logout', {
231
+ method: 'POST',
232
+ token,
233
+ body: JSON.stringify(refreshToken ? { refresh_token: refreshToken } : {}),
234
+ });
235
+ await this.clearSessionValue();
236
+ return (0, utils_1.ok)(null);
237
+ }
238
+ catch (error) {
239
+ return (0, utils_1.err)('LUX_AUTH_LOGOUT_ERROR', 'Failed to log out', (0, utils_1.toLuxError)(error));
240
+ }
241
+ }
242
+ async signOut() {
243
+ return this.logout();
244
+ }
245
+ async listUsers() {
246
+ try {
247
+ const result = await this.requestRaw('/auth/v1/admin/users', {
248
+ method: 'GET',
249
+ secret: true,
250
+ });
251
+ return (0, utils_1.ok)(result.users);
252
+ }
253
+ catch (error) {
254
+ return (0, utils_1.err)('LUX_AUTH_ADMIN_ERROR', 'Failed to list auth users', (0, utils_1.toLuxError)(error));
255
+ }
256
+ }
257
+ async grantCapability(userId, capability) {
258
+ try {
259
+ const result = await this.requestRaw('/auth/v1/admin/grants', {
260
+ method: 'POST',
261
+ secret: true,
262
+ body: JSON.stringify({ user_id: userId, capability }),
263
+ });
264
+ return (0, utils_1.ok)(result.grant);
265
+ }
266
+ catch (error) {
267
+ return (0, utils_1.err)('LUX_AUTH_ADMIN_ERROR', 'Failed to grant capability', (0, utils_1.toLuxError)(error));
268
+ }
269
+ }
270
+ async listUserGrants(userId) {
271
+ try {
272
+ const result = await this.requestRaw(`/auth/v1/admin/users/${encodeURIComponent(userId)}/grants`, {
273
+ method: 'GET',
274
+ secret: true,
275
+ });
276
+ return (0, utils_1.ok)(result.grants);
277
+ }
278
+ catch (error) {
279
+ return (0, utils_1.err)('LUX_AUTH_ADMIN_ERROR', 'Failed to list user grants', (0, utils_1.toLuxError)(error));
280
+ }
281
+ }
282
+ async revokeGrant(grantId) {
283
+ try {
284
+ await this.requestRaw(`/auth/v1/admin/grants/${encodeURIComponent(grantId)}`, {
285
+ method: 'DELETE',
286
+ secret: true,
287
+ });
288
+ return (0, utils_1.ok)(null);
289
+ }
290
+ catch (error) {
291
+ return (0, utils_1.err)('LUX_AUTH_ADMIN_ERROR', 'Failed to revoke grant', (0, utils_1.toLuxError)(error));
292
+ }
293
+ }
294
+ async listApiKeys() {
295
+ try {
296
+ const result = await this.requestRaw('/auth/v1/admin/keys', {
297
+ method: 'GET',
298
+ secret: true,
299
+ });
300
+ return (0, utils_1.ok)(result.keys);
301
+ }
302
+ catch (error) {
303
+ return (0, utils_1.err)('LUX_AUTH_ADMIN_ERROR', 'Failed to list API keys', (0, utils_1.toLuxError)(error));
304
+ }
305
+ }
306
+ async listProviders() {
307
+ try {
308
+ const result = await this.requestRaw('/auth/v1/admin/providers', {
309
+ method: 'GET',
310
+ secret: true,
311
+ });
312
+ return (0, utils_1.ok)(result.providers);
313
+ }
314
+ catch (error) {
315
+ return (0, utils_1.err)('LUX_AUTH_ADMIN_ERROR', 'Failed to list auth providers', (0, utils_1.toLuxError)(error));
316
+ }
317
+ }
318
+ async upsertProvider(options) {
319
+ try {
320
+ const result = await this.requestRaw(`/auth/v1/admin/providers/${encodeURIComponent(options.provider)}`, {
321
+ method: 'PUT',
322
+ secret: true,
323
+ body: JSON.stringify(options),
324
+ });
325
+ return (0, utils_1.ok)(result.provider);
326
+ }
327
+ catch (error) {
328
+ return (0, utils_1.err)('LUX_AUTH_ADMIN_ERROR', 'Failed to update auth provider', (0, utils_1.toLuxError)(error));
329
+ }
330
+ }
331
+ async createApiKey(options) {
332
+ try {
333
+ return (0, utils_1.ok)(await this.requestRaw('/auth/v1/admin/keys', {
334
+ method: 'POST',
335
+ secret: true,
336
+ body: JSON.stringify(options),
337
+ }));
338
+ }
339
+ catch (error) {
340
+ return (0, utils_1.err)('LUX_AUTH_ADMIN_ERROR', 'Failed to create API key', (0, utils_1.toLuxError)(error));
341
+ }
342
+ }
343
+ async revokeApiKey(keyId) {
344
+ try {
345
+ await this.requestRaw(`/auth/v1/admin/keys/${encodeURIComponent(keyId)}`, {
346
+ method: 'DELETE',
347
+ secret: true,
348
+ });
349
+ return (0, utils_1.ok)(null);
350
+ }
351
+ catch (error) {
352
+ return (0, utils_1.err)('LUX_AUTH_ADMIN_ERROR', 'Failed to revoke API key', (0, utils_1.toLuxError)(error));
353
+ }
354
+ }
355
+ async get(path, session) {
356
+ return this.requestResult(path, { method: 'GET', token: this.tokenFrom(session) });
357
+ }
358
+ async post(path, body, session) {
359
+ return this.requestResult(path, {
360
+ method: 'POST',
361
+ token: this.tokenFrom(session),
362
+ body: body == null ? undefined : JSON.stringify(body),
363
+ });
364
+ }
365
+ async put(path, body, session) {
366
+ return this.requestResult(path, {
367
+ method: 'PUT',
368
+ token: this.tokenFrom(session),
369
+ body: body == null ? undefined : JSON.stringify(body),
370
+ });
371
+ }
372
+ async delete(path, session) {
373
+ return this.requestResult(path, { method: 'DELETE', token: this.tokenFrom(session) });
374
+ }
375
+ tokenFrom(session) {
376
+ return typeof session === 'string' ? session : session?.access_token ?? this.authToken;
377
+ }
378
+ async loadStoredSession() {
379
+ if (this.loadedSession)
380
+ return;
381
+ this.loadedSession = true;
382
+ if (!this.persistSession || !this.storage)
383
+ return;
384
+ const raw = await this.storage.getItem(this.storageKey);
385
+ if (!raw)
386
+ return;
387
+ try {
388
+ const session = normalizeSession(JSON.parse(raw));
389
+ this.currentSession = session;
390
+ this.authToken = session.access_token;
391
+ this.scheduleRefresh(session);
392
+ }
393
+ catch {
394
+ await this.storage.removeItem(this.storageKey);
395
+ }
396
+ }
397
+ async saveSession(session, event) {
398
+ this.currentSession = session;
399
+ this.authToken = session.access_token;
400
+ this.loadedSession = true;
401
+ if (this.persistSession && this.storage) {
402
+ await this.storage.setItem(this.storageKey, JSON.stringify(session));
403
+ }
404
+ this.scheduleRefresh(session);
405
+ this.emit(event, session);
406
+ }
407
+ scheduleRefresh(session) {
408
+ this.clearRefreshTimer();
409
+ if (!this.autoRefreshToken || !session.refresh_token || !session.expires_at)
410
+ return;
411
+ const delayMs = Math.max(0, (session.expires_at - this.refreshMarginSeconds) * 1000 - Date.now());
412
+ this.refreshTimer = setTimeout(() => {
413
+ void this.refreshSession(session.refresh_token).catch(() => {
414
+ void this.clearSessionValue();
415
+ });
416
+ }, delayMs);
417
+ }
418
+ clearRefreshTimer() {
419
+ if (this.refreshTimer) {
420
+ clearTimeout(this.refreshTimer);
421
+ this.refreshTimer = null;
422
+ }
423
+ }
424
+ emit(event, session) {
425
+ for (const listener of this.listeners) {
426
+ listener(event, session);
427
+ }
428
+ }
429
+ async requestResult(path, init) {
430
+ try {
431
+ return (0, utils_1.ok)(await this.requestRaw(path, init));
432
+ }
433
+ catch (error) {
434
+ return (0, utils_1.err)('LUX_AUTH_REQUEST_ERROR', 'Lux auth request failed', (0, utils_1.toLuxError)(error));
435
+ }
436
+ }
437
+ async requestRaw(path, init) {
438
+ if (!this.httpUrl) {
439
+ throw new Error('Lux auth requires httpUrl');
440
+ }
441
+ const headers = {
442
+ Accept: 'application/json',
443
+ };
444
+ if (init.body != null) {
445
+ headers['Content-Type'] = 'application/json';
446
+ }
447
+ const token = init.token ?? this.authToken;
448
+ if (token) {
449
+ headers.Authorization = `Bearer ${token}`;
450
+ }
451
+ if ((init.apiKey || init.secret) && this.apiKey) {
452
+ headers.apikey = this.apiKey;
453
+ }
454
+ const response = await this.fetchImpl(`${this.httpUrl}${path}`, {
455
+ method: init.method,
456
+ headers,
457
+ body: init.body,
458
+ });
459
+ const text = await response.text();
460
+ const payload = text ? JSON.parse(text) : {};
461
+ if (!response.ok) {
462
+ const message = payload?.error || `Lux auth request failed with HTTP ${response.status}`;
463
+ throw new Error(message);
464
+ }
465
+ return payload;
466
+ }
467
+ }
468
+ exports.LuxAuthClient = LuxAuthClient;
469
+ function normalizeSession(session) {
470
+ return {
471
+ ...session,
472
+ expires_at: session.expires_at ?? Math.floor(Date.now() / 1000) + Number(session.expires_in || 0),
473
+ };
474
+ }
475
+ function isExpired(session, marginSeconds = 0) {
476
+ return Boolean(session.expires_at && session.expires_at <= Math.floor(Date.now() / 1000) + marginSeconds);
477
+ }
478
+ function defaultBrowserStorage() {
479
+ if (typeof globalThis === 'undefined')
480
+ return null;
481
+ const storage = globalThis.localStorage;
482
+ if (!storage)
483
+ return null;
484
+ return {
485
+ getItem: (key) => storage.getItem(key),
486
+ setItem: (key, value) => storage.setItem(key, value),
487
+ removeItem: (key) => storage.removeItem(key),
488
+ };
489
+ }
490
+ function resolveFetch(fetchImpl) {
491
+ const candidate = fetchImpl ?? globalThis.fetch;
492
+ if (!candidate) {
493
+ throw new Error('Lux auth requires a fetch implementation');
494
+ }
495
+ if (typeof globalThis !== 'undefined' && candidate === globalThis.fetch) {
496
+ return candidate.bind(globalThis);
497
+ }
498
+ return candidate;
499
+ }
500
+ function browserLocation() {
501
+ if (typeof globalThis === 'undefined')
502
+ return '';
503
+ return String(globalThis.location?.href || '');
504
+ }
@@ -0,0 +1,14 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createBrowserClient = createBrowserClient;
4
+ const project_1 = require("./project");
5
+ function createBrowserClient(url, key, options = {}) {
6
+ return (0, project_1.createClient)(url, key, {
7
+ ...options,
8
+ auth: {
9
+ persistSession: true,
10
+ autoRefreshToken: true,
11
+ ...(options.auth ?? {}),
12
+ },
13
+ });
14
+ }