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