@basictech/react 0.7.0-beta.2 → 0.7.0-beta.4
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/.turbo/turbo-build.log +10 -10
- package/AUTH_IMPLEMENTATION_GUIDE.md +2009 -0
- package/changelog.md +12 -0
- package/dist/index.d.mts +19 -113
- package/dist/index.d.ts +19 -113
- package/dist/index.js +62 -387
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +62 -386
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/AuthContext.tsx +82 -18
- package/src/index.ts +4 -4
- package/src/utils/storage.ts +1 -0
package/dist/index.mjs
CHANGED
|
@@ -250,363 +250,8 @@ var BasicSync = class extends Dexie2 {
|
|
|
250
250
|
}
|
|
251
251
|
};
|
|
252
252
|
|
|
253
|
-
// src/db_ts.ts
|
|
254
|
-
var DBError = class extends Error {
|
|
255
|
-
constructor(message, status, response, originalError) {
|
|
256
|
-
super(message);
|
|
257
|
-
this.status = status;
|
|
258
|
-
this.response = response;
|
|
259
|
-
this.originalError = originalError;
|
|
260
|
-
this.name = "DBError";
|
|
261
|
-
}
|
|
262
|
-
};
|
|
263
|
-
var QueryBuilder = class {
|
|
264
|
-
constructor(tableClient, tableSchema) {
|
|
265
|
-
this.tableClient = tableClient;
|
|
266
|
-
this.tableSchema = tableSchema;
|
|
267
|
-
}
|
|
268
|
-
params = {};
|
|
269
|
-
// Reserved fields that are always allowed
|
|
270
|
-
reservedFields = ["created_at", "updated_at", "id"];
|
|
271
|
-
// Validate field existence in schema
|
|
272
|
-
validateField(field) {
|
|
273
|
-
if (this.tableSchema && !this.reservedFields.includes(field)) {
|
|
274
|
-
if (!this.tableSchema.fields || !(field in this.tableSchema.fields)) {
|
|
275
|
-
throw new Error(`Invalid field: "${field}". Field does not exist in table schema.`);
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
// Validate operator based on field type
|
|
280
|
-
validateOperator(field, operator, value) {
|
|
281
|
-
if (!this.tableSchema || this.reservedFields.includes(field)) {
|
|
282
|
-
return;
|
|
283
|
-
}
|
|
284
|
-
const fieldInfo = this.tableSchema.fields[field];
|
|
285
|
-
if (!fieldInfo)
|
|
286
|
-
return;
|
|
287
|
-
switch (operator) {
|
|
288
|
-
case "gt":
|
|
289
|
-
case "gte":
|
|
290
|
-
case "lt":
|
|
291
|
-
case "lte":
|
|
292
|
-
if (fieldInfo.type !== "number" && fieldInfo.type !== "string") {
|
|
293
|
-
throw new Error(`Operator "${operator}" can only be used with number or string fields. Field "${field}" is type "${fieldInfo.type}".`);
|
|
294
|
-
}
|
|
295
|
-
break;
|
|
296
|
-
case "like":
|
|
297
|
-
case "ilike":
|
|
298
|
-
if (fieldInfo.type !== "string") {
|
|
299
|
-
throw new Error(`Operator "${operator}" can only be used with string fields. Field "${field}" is type "${fieldInfo.type}".`);
|
|
300
|
-
}
|
|
301
|
-
if (typeof value !== "string") {
|
|
302
|
-
throw new Error(`Operator "${operator}" requires a string value. Received: ${typeof value}`);
|
|
303
|
-
}
|
|
304
|
-
break;
|
|
305
|
-
case "in":
|
|
306
|
-
if (!Array.isArray(value)) {
|
|
307
|
-
throw new Error(`Operator "in" requires an array value. Received: ${typeof value}`);
|
|
308
|
-
}
|
|
309
|
-
break;
|
|
310
|
-
case "is":
|
|
311
|
-
if (value !== null && typeof value !== "boolean") {
|
|
312
|
-
throw new Error(`Operator "is" requires null or boolean. Received: ${typeof value}`);
|
|
313
|
-
}
|
|
314
|
-
break;
|
|
315
|
-
}
|
|
316
|
-
}
|
|
317
|
-
// Add ordering to query with schema validation
|
|
318
|
-
order(field, direction = "asc") {
|
|
319
|
-
this.validateField(field);
|
|
320
|
-
this.params.order = `${field}.${direction}`;
|
|
321
|
-
return this;
|
|
322
|
-
}
|
|
323
|
-
// Add filtering to query
|
|
324
|
-
filter(conditions) {
|
|
325
|
-
if (!this.params.filters) {
|
|
326
|
-
this.params.filters = {};
|
|
327
|
-
}
|
|
328
|
-
for (const [field, condition] of Object.entries(conditions)) {
|
|
329
|
-
this.validateField(field);
|
|
330
|
-
if (condition === null || typeof condition !== "object") {
|
|
331
|
-
this.params.filters[field] = condition;
|
|
332
|
-
} else {
|
|
333
|
-
this.params.filters[field] = condition;
|
|
334
|
-
}
|
|
335
|
-
}
|
|
336
|
-
return this;
|
|
337
|
-
}
|
|
338
|
-
// Add limit to query
|
|
339
|
-
limit(count) {
|
|
340
|
-
this.params.limit = count;
|
|
341
|
-
return this;
|
|
342
|
-
}
|
|
343
|
-
// Add offset to query for pagination
|
|
344
|
-
offset(count) {
|
|
345
|
-
this.params.offset = count;
|
|
346
|
-
return this;
|
|
347
|
-
}
|
|
348
|
-
// Auto-execute when awaited
|
|
349
|
-
then(onfulfilled, onrejected) {
|
|
350
|
-
return this.tableClient.executeQuery(this.params).then(onfulfilled, onrejected);
|
|
351
|
-
}
|
|
352
|
-
// Auto-execute when awaited with catch
|
|
353
|
-
catch(onrejected) {
|
|
354
|
-
return this.tableClient.executeQuery(this.params).catch(onrejected);
|
|
355
|
-
}
|
|
356
|
-
// Auto-execute when awaited with finally
|
|
357
|
-
finally(onfinally) {
|
|
358
|
-
return this.tableClient.executeQuery(this.params).finally(onfinally);
|
|
359
|
-
}
|
|
360
|
-
};
|
|
361
|
-
var TableClient = class {
|
|
362
|
-
constructor(baseUrl, projectId, token, table, getToken, schema) {
|
|
363
|
-
this.baseUrl = baseUrl;
|
|
364
|
-
this.projectId = projectId;
|
|
365
|
-
this.token = token;
|
|
366
|
-
this.table = table;
|
|
367
|
-
this.getToken = getToken;
|
|
368
|
-
this.schema = schema;
|
|
369
|
-
if (schema && schema.tables && schema.tables[table]) {
|
|
370
|
-
this.tableSchema = schema.tables[table];
|
|
371
|
-
}
|
|
372
|
-
}
|
|
373
|
-
tableSchema;
|
|
374
|
-
async headers() {
|
|
375
|
-
const token = await this.getToken();
|
|
376
|
-
return {
|
|
377
|
-
Authorization: `Bearer ${token}`,
|
|
378
|
-
"Content-Type": "application/json"
|
|
379
|
-
};
|
|
380
|
-
}
|
|
381
|
-
async handleRequest(request) {
|
|
382
|
-
try {
|
|
383
|
-
const res = await request;
|
|
384
|
-
if (!res.ok) {
|
|
385
|
-
let errorMessage = `Request failed with status ${res.status}`;
|
|
386
|
-
let errorData;
|
|
387
|
-
try {
|
|
388
|
-
const json2 = await res.json();
|
|
389
|
-
errorData = json2;
|
|
390
|
-
if (json2.error || json2.message) {
|
|
391
|
-
const errorDetails = typeof json2.error === "object" ? JSON.stringify(json2.error) : json2.error;
|
|
392
|
-
const messageDetails = typeof json2.message === "object" ? JSON.stringify(json2.message) : json2.message;
|
|
393
|
-
errorMessage = `${res.status} ${res.statusText}: ${messageDetails || errorDetails || "Unknown error"}`;
|
|
394
|
-
}
|
|
395
|
-
} catch (e) {
|
|
396
|
-
console.log("Failed to parse error response:", e);
|
|
397
|
-
errorMessage = `${res.status} ${res.statusText}`;
|
|
398
|
-
}
|
|
399
|
-
throw new DBError(
|
|
400
|
-
errorMessage,
|
|
401
|
-
res.status,
|
|
402
|
-
errorData
|
|
403
|
-
);
|
|
404
|
-
}
|
|
405
|
-
const json = await res.json();
|
|
406
|
-
return json.data;
|
|
407
|
-
} catch (error) {
|
|
408
|
-
console.log("Caught error:", error);
|
|
409
|
-
if (error instanceof Error) {
|
|
410
|
-
console.log("Error type:", error.constructor.name);
|
|
411
|
-
console.log("Error stack:", error.stack);
|
|
412
|
-
}
|
|
413
|
-
if (error instanceof DBError) {
|
|
414
|
-
throw error;
|
|
415
|
-
}
|
|
416
|
-
if (error instanceof TypeError && error.message === "Network request failed") {
|
|
417
|
-
throw new DBError(
|
|
418
|
-
"Network request failed. Please check your internet connection and try again.",
|
|
419
|
-
void 0,
|
|
420
|
-
void 0,
|
|
421
|
-
error
|
|
422
|
-
);
|
|
423
|
-
}
|
|
424
|
-
throw new DBError(
|
|
425
|
-
`Unexpected error: ${error instanceof Error ? error.message : "Unknown error"}`,
|
|
426
|
-
void 0,
|
|
427
|
-
void 0,
|
|
428
|
-
error instanceof Error ? error : void 0
|
|
429
|
-
);
|
|
430
|
-
}
|
|
431
|
-
}
|
|
432
|
-
// Build query string from query options
|
|
433
|
-
buildQueryParams(query) {
|
|
434
|
-
if (!query)
|
|
435
|
-
return "";
|
|
436
|
-
const params = [];
|
|
437
|
-
if (query.id) {
|
|
438
|
-
params.push(`id=${query.id}`);
|
|
439
|
-
}
|
|
440
|
-
if (query.filters) {
|
|
441
|
-
for (const [field, condition] of Object.entries(query.filters)) {
|
|
442
|
-
this.addFilterParam(params, field, condition);
|
|
443
|
-
}
|
|
444
|
-
}
|
|
445
|
-
if (query.order) {
|
|
446
|
-
params.push(`order=${query.order}`);
|
|
447
|
-
}
|
|
448
|
-
if (query.limit !== void 0 && query.limit >= 0) {
|
|
449
|
-
params.push(`limit=${query.limit}`);
|
|
450
|
-
}
|
|
451
|
-
if (query.offset !== void 0 && query.offset >= 0) {
|
|
452
|
-
params.push(`offset=${query.offset}`);
|
|
453
|
-
}
|
|
454
|
-
return params.length > 0 ? `?${params.join("&")}` : "";
|
|
455
|
-
}
|
|
456
|
-
// Helper method to build filter parameters
|
|
457
|
-
addFilterParam(params, field, condition, negate = false) {
|
|
458
|
-
if (condition === null || typeof condition !== "object") {
|
|
459
|
-
if (condition === null) {
|
|
460
|
-
params.push(`${field}=${negate ? "not." : ""}is.null`);
|
|
461
|
-
} else if (typeof condition === "boolean") {
|
|
462
|
-
params.push(`${field}=${negate ? "not." : ""}is.${condition}`);
|
|
463
|
-
} else if (typeof condition === "number") {
|
|
464
|
-
params.push(`${field}=${negate ? "not." : ""}eq.${condition}`);
|
|
465
|
-
} else {
|
|
466
|
-
params.push(`${field}=${negate ? "not." : ""}eq.${encodeURIComponent(String(condition))}`);
|
|
467
|
-
}
|
|
468
|
-
return;
|
|
469
|
-
}
|
|
470
|
-
const operatorObj = condition;
|
|
471
|
-
if (operatorObj.not) {
|
|
472
|
-
this.addFilterParam(params, field, operatorObj.not, true);
|
|
473
|
-
return;
|
|
474
|
-
}
|
|
475
|
-
for (const [op, value] of Object.entries(operatorObj)) {
|
|
476
|
-
if (op === "not")
|
|
477
|
-
continue;
|
|
478
|
-
const operator = op;
|
|
479
|
-
if (value === null) {
|
|
480
|
-
params.push(`${field}=${negate ? "not." : ""}is.null`);
|
|
481
|
-
} else if (operator === "in" && Array.isArray(value)) {
|
|
482
|
-
params.push(`${field}=${negate ? "not." : ""}in.${value.join(",")}`);
|
|
483
|
-
} else if (operator === "is") {
|
|
484
|
-
if (typeof value === "boolean") {
|
|
485
|
-
params.push(`${field}=${negate ? "not." : ""}is.${value}`);
|
|
486
|
-
} else {
|
|
487
|
-
params.push(`${field}=${negate ? "not." : ""}is.null`);
|
|
488
|
-
}
|
|
489
|
-
} else {
|
|
490
|
-
const paramValue = typeof value === "string" ? encodeURIComponent(value) : String(value);
|
|
491
|
-
params.push(`${field}=${negate ? "not." : ""}${operator}.${paramValue}`);
|
|
492
|
-
}
|
|
493
|
-
}
|
|
494
|
-
}
|
|
495
|
-
// Internal method to execute a query with options
|
|
496
|
-
async executeQuery(options) {
|
|
497
|
-
const params = this.buildQueryParams(options);
|
|
498
|
-
const headers = await this.headers();
|
|
499
|
-
return this.handleRequest(
|
|
500
|
-
fetch(`${this.baseUrl}/account/${this.projectId}/db/${this.table}${params}`, {
|
|
501
|
-
headers
|
|
502
|
-
})
|
|
503
|
-
);
|
|
504
|
-
}
|
|
505
|
-
// Public method to start building a query
|
|
506
|
-
getAll() {
|
|
507
|
-
return new QueryBuilder(this, this.tableSchema);
|
|
508
|
-
}
|
|
509
|
-
// Get a specific item by ID
|
|
510
|
-
async get(id) {
|
|
511
|
-
const headers = await this.headers();
|
|
512
|
-
return this.handleRequest(
|
|
513
|
-
fetch(`${this.baseUrl}/account/${this.projectId}/db/${this.table}/${id}`, {
|
|
514
|
-
headers
|
|
515
|
-
})
|
|
516
|
-
);
|
|
517
|
-
}
|
|
518
|
-
async create(value) {
|
|
519
|
-
const headers = await this.headers();
|
|
520
|
-
return this.handleRequest(
|
|
521
|
-
fetch(`${this.baseUrl}/account/${this.projectId}/db/${this.table}`, {
|
|
522
|
-
method: "POST",
|
|
523
|
-
headers,
|
|
524
|
-
body: JSON.stringify({ value })
|
|
525
|
-
})
|
|
526
|
-
);
|
|
527
|
-
}
|
|
528
|
-
async update(id, value) {
|
|
529
|
-
const headers = await this.headers();
|
|
530
|
-
return this.handleRequest(
|
|
531
|
-
fetch(`${this.baseUrl}/account/${this.projectId}/db/${this.table}/${id}`, {
|
|
532
|
-
method: "PATCH",
|
|
533
|
-
headers,
|
|
534
|
-
body: JSON.stringify({ value })
|
|
535
|
-
})
|
|
536
|
-
);
|
|
537
|
-
}
|
|
538
|
-
async replace(id, value) {
|
|
539
|
-
const headers = await this.headers();
|
|
540
|
-
return this.handleRequest(
|
|
541
|
-
fetch(`${this.baseUrl}/account/${this.projectId}/db/${this.table}/${id}`, {
|
|
542
|
-
method: "PUT",
|
|
543
|
-
headers,
|
|
544
|
-
body: JSON.stringify({ value })
|
|
545
|
-
})
|
|
546
|
-
);
|
|
547
|
-
}
|
|
548
|
-
async delete(id) {
|
|
549
|
-
const token = await this.getToken();
|
|
550
|
-
const headers = {
|
|
551
|
-
Authorization: `Bearer ${token}`
|
|
552
|
-
};
|
|
553
|
-
return this.handleRequest(
|
|
554
|
-
fetch(`${this.baseUrl}/account/${this.projectId}/db/${this.table}/${id}`, {
|
|
555
|
-
method: "DELETE",
|
|
556
|
-
headers
|
|
557
|
-
})
|
|
558
|
-
);
|
|
559
|
-
}
|
|
560
|
-
};
|
|
561
|
-
var BasicDBSDK = class {
|
|
562
|
-
projectId;
|
|
563
|
-
getToken;
|
|
564
|
-
baseUrl;
|
|
565
|
-
schema;
|
|
566
|
-
tableNames;
|
|
567
|
-
constructor(config) {
|
|
568
|
-
this.projectId = config.project_id;
|
|
569
|
-
if (config.getToken) {
|
|
570
|
-
this.getToken = config.getToken;
|
|
571
|
-
} else if (config.token) {
|
|
572
|
-
this.getToken = async () => config.token;
|
|
573
|
-
} else {
|
|
574
|
-
throw new Error("Either token or getToken must be provided");
|
|
575
|
-
}
|
|
576
|
-
this.baseUrl = config.baseUrl || "https://api.basic.tech";
|
|
577
|
-
this.schema = config.schema;
|
|
578
|
-
this.tableNames = Object.keys(this.schema.tables);
|
|
579
|
-
}
|
|
580
|
-
// Primary method - table access
|
|
581
|
-
table(name) {
|
|
582
|
-
if (!this.tableNames.includes(name)) {
|
|
583
|
-
throw new Error(`Table '${name}' not found in schema. Available tables: ${this.tableNames.join(", ")}`);
|
|
584
|
-
}
|
|
585
|
-
return new TableClient(
|
|
586
|
-
this.baseUrl,
|
|
587
|
-
this.projectId,
|
|
588
|
-
"",
|
|
589
|
-
// Empty placeholder, will be replaced in headers() method
|
|
590
|
-
name,
|
|
591
|
-
this.getToken,
|
|
592
|
-
this.schema
|
|
593
|
-
// Pass the entire schema to the TableClient
|
|
594
|
-
);
|
|
595
|
-
}
|
|
596
|
-
get tables() {
|
|
597
|
-
return {};
|
|
598
|
-
}
|
|
599
|
-
fields(table) {
|
|
600
|
-
const tableSchema = this.schema.tables[table];
|
|
601
|
-
if (!tableSchema) {
|
|
602
|
-
throw new Error(`Table '${table}' not found in schema`);
|
|
603
|
-
}
|
|
604
|
-
return Object.keys(tableSchema.fields);
|
|
605
|
-
}
|
|
606
|
-
};
|
|
607
|
-
|
|
608
253
|
// package.json
|
|
609
|
-
var version = "0.7.0-beta.
|
|
254
|
+
var version = "0.7.0-beta.3";
|
|
610
255
|
|
|
611
256
|
// src/updater/versionUpdater.ts
|
|
612
257
|
var VersionUpdater = class {
|
|
@@ -749,6 +394,7 @@ var STORAGE_KEYS = {
|
|
|
749
394
|
REFRESH_TOKEN: "basic_refresh_token",
|
|
750
395
|
USER_INFO: "basic_user_info",
|
|
751
396
|
AUTH_STATE: "basic_auth_state",
|
|
397
|
+
REDIRECT_URI: "basic_redirect_uri",
|
|
752
398
|
DEBUG: "basic_debug"
|
|
753
399
|
};
|
|
754
400
|
function getCookie(name) {
|
|
@@ -946,6 +592,10 @@ async function validateAndCheckSchema(schema) {
|
|
|
946
592
|
|
|
947
593
|
// src/AuthContext.tsx
|
|
948
594
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
595
|
+
var DEFAULT_AUTH_CONFIG = {
|
|
596
|
+
scopes: "profile email app:admin",
|
|
597
|
+
server_url: "https://api.basic.tech"
|
|
598
|
+
};
|
|
949
599
|
var BasicContext = createContext({
|
|
950
600
|
unicorn: "\u{1F984}",
|
|
951
601
|
isAuthReady: false,
|
|
@@ -959,7 +609,6 @@ var BasicContext = createContext({
|
|
|
959
609
|
}),
|
|
960
610
|
getSignInLink: () => Promise.resolve(""),
|
|
961
611
|
db: {},
|
|
962
|
-
remoteDb: {},
|
|
963
612
|
dbStatus: "LOADING" /* LOADING */
|
|
964
613
|
});
|
|
965
614
|
function BasicProvider({
|
|
@@ -967,7 +616,8 @@ function BasicProvider({
|
|
|
967
616
|
project_id,
|
|
968
617
|
schema,
|
|
969
618
|
debug = false,
|
|
970
|
-
storage
|
|
619
|
+
storage,
|
|
620
|
+
auth
|
|
971
621
|
}) {
|
|
972
622
|
const [isAuthReady, setIsAuthReady] = useState(false);
|
|
973
623
|
const [isSignedIn, setIsSignedIn] = useState(false);
|
|
@@ -980,8 +630,12 @@ function BasicProvider({
|
|
|
980
630
|
const [isOnline, setIsOnline] = useState(navigator.onLine);
|
|
981
631
|
const [pendingRefresh, setPendingRefresh] = useState(false);
|
|
982
632
|
const syncRef = useRef(null);
|
|
983
|
-
const remoteDbRef = useRef(null);
|
|
984
633
|
const storageAdapter = storage || new LocalStorageAdapter();
|
|
634
|
+
const authConfig = {
|
|
635
|
+
scopes: auth?.scopes || DEFAULT_AUTH_CONFIG.scopes,
|
|
636
|
+
server_url: auth?.server_url || DEFAULT_AUTH_CONFIG.server_url
|
|
637
|
+
};
|
|
638
|
+
const scopesString = Array.isArray(authConfig.scopes) ? authConfig.scopes.join(" ") : authConfig.scopes;
|
|
985
639
|
const isDevMode = () => isDevelopment(debug);
|
|
986
640
|
const cleanOAuthParams = () => cleanOAuthParamsFromUrl();
|
|
987
641
|
useEffect(() => {
|
|
@@ -994,7 +648,7 @@ function BasicProvider({
|
|
|
994
648
|
if (token) {
|
|
995
649
|
const refreshToken = token.refresh_token || localStorage.getItem("basic_refresh_token");
|
|
996
650
|
if (refreshToken) {
|
|
997
|
-
fetchToken(refreshToken).catch((error2) => {
|
|
651
|
+
fetchToken(refreshToken, true).catch((error2) => {
|
|
998
652
|
log("Retry refresh failed:", error2);
|
|
999
653
|
});
|
|
1000
654
|
}
|
|
@@ -1076,17 +730,6 @@ function BasicProvider({
|
|
|
1076
730
|
}
|
|
1077
731
|
connectToDb();
|
|
1078
732
|
}, [isSignedIn, shouldConnect]);
|
|
1079
|
-
useEffect(() => {
|
|
1080
|
-
if (project_id && schema && token?.access_token && !remoteDbRef.current) {
|
|
1081
|
-
log("Initializing Remote DB SDK");
|
|
1082
|
-
remoteDbRef.current = new BasicDBSDK({
|
|
1083
|
-
project_id,
|
|
1084
|
-
schema,
|
|
1085
|
-
getToken: () => getToken(),
|
|
1086
|
-
baseUrl: "https://api.basic.tech"
|
|
1087
|
-
});
|
|
1088
|
-
}
|
|
1089
|
-
}, [token, project_id, schema]);
|
|
1090
733
|
useEffect(() => {
|
|
1091
734
|
const initializeAuth = async () => {
|
|
1092
735
|
await storageAdapter.set(STORAGE_KEYS.DEBUG, debug ? "true" : "false");
|
|
@@ -1117,14 +760,14 @@ function BasicProvider({
|
|
|
1117
760
|
}
|
|
1118
761
|
await storageAdapter.remove(STORAGE_KEYS.AUTH_STATE);
|
|
1119
762
|
cleanOAuthParams();
|
|
1120
|
-
fetchToken(code).catch((error2) => {
|
|
763
|
+
fetchToken(code, false).catch((error2) => {
|
|
1121
764
|
log("Error fetching token:", error2);
|
|
1122
765
|
});
|
|
1123
766
|
} else {
|
|
1124
767
|
const refreshToken = await storageAdapter.get(STORAGE_KEYS.REFRESH_TOKEN);
|
|
1125
768
|
if (refreshToken) {
|
|
1126
769
|
log("Found refresh token in storage, attempting to refresh access token");
|
|
1127
|
-
fetchToken(refreshToken).catch((error2) => {
|
|
770
|
+
fetchToken(refreshToken, true).catch((error2) => {
|
|
1128
771
|
log("Error fetching refresh token:", error2);
|
|
1129
772
|
});
|
|
1130
773
|
} else {
|
|
@@ -1160,7 +803,7 @@ function BasicProvider({
|
|
|
1160
803
|
useEffect(() => {
|
|
1161
804
|
async function fetchUser(acc_token) {
|
|
1162
805
|
console.info("fetching user");
|
|
1163
|
-
const user2 = await fetch(
|
|
806
|
+
const user2 = await fetch(`${authConfig.server_url}/auth/userInfo`, {
|
|
1164
807
|
method: "GET",
|
|
1165
808
|
headers: {
|
|
1166
809
|
"Authorization": `Bearer ${acc_token}`
|
|
@@ -1193,7 +836,7 @@ function BasicProvider({
|
|
|
1193
836
|
if (isExpired) {
|
|
1194
837
|
log("token is expired - refreshing ...");
|
|
1195
838
|
try {
|
|
1196
|
-
const newToken = await fetchToken(token?.refresh_token || "");
|
|
839
|
+
const newToken = await fetchToken(token?.refresh_token || "", true);
|
|
1197
840
|
fetchUser(newToken?.access_token || "");
|
|
1198
841
|
} catch (error2) {
|
|
1199
842
|
log("Failed to refresh token in checkToken:", error2);
|
|
@@ -1224,13 +867,15 @@ function BasicProvider({
|
|
|
1224
867
|
if (!redirectUrl || !redirectUrl.startsWith("http://") && !redirectUrl.startsWith("https://")) {
|
|
1225
868
|
throw new Error("Invalid redirect URI provided");
|
|
1226
869
|
}
|
|
1227
|
-
|
|
870
|
+
await storageAdapter.set(STORAGE_KEYS.REDIRECT_URI, redirectUrl);
|
|
871
|
+
log("Stored redirect_uri for token exchange:", redirectUrl);
|
|
872
|
+
let baseUrl = `${authConfig.server_url}/auth/authorize`;
|
|
1228
873
|
baseUrl += `?client_id=${project_id}`;
|
|
1229
874
|
baseUrl += `&redirect_uri=${encodeURIComponent(redirectUrl)}`;
|
|
1230
875
|
baseUrl += `&response_type=code`;
|
|
1231
|
-
baseUrl += `&scope
|
|
876
|
+
baseUrl += `&scope=${encodeURIComponent(scopesString)}`;
|
|
1232
877
|
baseUrl += `&state=${randomState}`;
|
|
1233
|
-
log("Generated sign-in link successfully");
|
|
878
|
+
log("Generated sign-in link successfully with scopes:", scopesString);
|
|
1234
879
|
return baseUrl;
|
|
1235
880
|
} catch (error2) {
|
|
1236
881
|
log("Error generating sign-in link:", error2);
|
|
@@ -1278,7 +923,7 @@ function BasicProvider({
|
|
|
1278
923
|
}
|
|
1279
924
|
await storageAdapter.remove(STORAGE_KEYS.AUTH_STATE);
|
|
1280
925
|
cleanOAuthParams();
|
|
1281
|
-
const token2 = await fetchToken(code);
|
|
926
|
+
const token2 = await fetchToken(code, false);
|
|
1282
927
|
if (token2) {
|
|
1283
928
|
log("signinWithCode successful");
|
|
1284
929
|
return { success: true };
|
|
@@ -1303,6 +948,7 @@ function BasicProvider({
|
|
|
1303
948
|
await storageAdapter.remove(STORAGE_KEYS.AUTH_STATE);
|
|
1304
949
|
await storageAdapter.remove(STORAGE_KEYS.REFRESH_TOKEN);
|
|
1305
950
|
await storageAdapter.remove(STORAGE_KEYS.USER_INFO);
|
|
951
|
+
await storageAdapter.remove(STORAGE_KEYS.REDIRECT_URI);
|
|
1306
952
|
if (syncRef.current) {
|
|
1307
953
|
(async () => {
|
|
1308
954
|
try {
|
|
@@ -1323,7 +969,7 @@ function BasicProvider({
|
|
|
1323
969
|
if (refreshToken) {
|
|
1324
970
|
log("No token in memory, attempting to refresh from storage");
|
|
1325
971
|
try {
|
|
1326
|
-
const newToken = await fetchToken(refreshToken);
|
|
972
|
+
const newToken = await fetchToken(refreshToken, true);
|
|
1327
973
|
if (newToken?.access_token) {
|
|
1328
974
|
return newToken.access_token;
|
|
1329
975
|
}
|
|
@@ -1350,7 +996,7 @@ function BasicProvider({
|
|
|
1350
996
|
const refreshToken = token?.refresh_token || await storageAdapter.get(STORAGE_KEYS.REFRESH_TOKEN);
|
|
1351
997
|
if (refreshToken) {
|
|
1352
998
|
try {
|
|
1353
|
-
const newToken = await fetchToken(refreshToken);
|
|
999
|
+
const newToken = await fetchToken(refreshToken, true);
|
|
1354
1000
|
return newToken?.access_token || "";
|
|
1355
1001
|
} catch (error2) {
|
|
1356
1002
|
log("Failed to refresh expired token:", error2);
|
|
@@ -1366,19 +1012,45 @@ function BasicProvider({
|
|
|
1366
1012
|
}
|
|
1367
1013
|
return token?.access_token || "";
|
|
1368
1014
|
};
|
|
1369
|
-
const fetchToken = async (
|
|
1015
|
+
const fetchToken = async (codeOrRefreshToken, isRefreshToken = false) => {
|
|
1370
1016
|
try {
|
|
1371
1017
|
if (!isOnline) {
|
|
1372
1018
|
log("Network is offline, marking refresh as pending");
|
|
1373
1019
|
setPendingRefresh(true);
|
|
1374
1020
|
throw new Error("Network offline - refresh will be retried when online");
|
|
1375
1021
|
}
|
|
1376
|
-
|
|
1022
|
+
let requestBody;
|
|
1023
|
+
if (isRefreshToken) {
|
|
1024
|
+
requestBody = {
|
|
1025
|
+
grant_type: "refresh_token",
|
|
1026
|
+
refresh_token: codeOrRefreshToken
|
|
1027
|
+
};
|
|
1028
|
+
if (project_id) {
|
|
1029
|
+
requestBody.client_id = project_id;
|
|
1030
|
+
}
|
|
1031
|
+
} else {
|
|
1032
|
+
requestBody = {
|
|
1033
|
+
grant_type: "authorization_code",
|
|
1034
|
+
code: codeOrRefreshToken
|
|
1035
|
+
};
|
|
1036
|
+
const storedRedirectUri = await storageAdapter.get(STORAGE_KEYS.REDIRECT_URI);
|
|
1037
|
+
if (storedRedirectUri) {
|
|
1038
|
+
requestBody.redirect_uri = storedRedirectUri;
|
|
1039
|
+
log("Including redirect_uri in token exchange:", storedRedirectUri);
|
|
1040
|
+
} else {
|
|
1041
|
+
log("Warning: No redirect_uri found in storage for token exchange");
|
|
1042
|
+
}
|
|
1043
|
+
if (project_id) {
|
|
1044
|
+
requestBody.client_id = project_id;
|
|
1045
|
+
}
|
|
1046
|
+
}
|
|
1047
|
+
log("Token exchange request body:", { ...requestBody, refresh_token: isRefreshToken ? "[REDACTED]" : void 0, code: !isRefreshToken ? "[REDACTED]" : void 0 });
|
|
1048
|
+
const token2 = await fetch(`${authConfig.server_url}/auth/token`, {
|
|
1377
1049
|
method: "POST",
|
|
1378
1050
|
headers: {
|
|
1379
1051
|
"Content-Type": "application/json"
|
|
1380
1052
|
},
|
|
1381
|
-
body: JSON.stringify(
|
|
1053
|
+
body: JSON.stringify(requestBody)
|
|
1382
1054
|
}).then((response) => response.json()).catch((error2) => {
|
|
1383
1055
|
log("Network error fetching token:", error2);
|
|
1384
1056
|
if (!isOnline) {
|
|
@@ -1395,6 +1067,7 @@ function BasicProvider({
|
|
|
1395
1067
|
}
|
|
1396
1068
|
await storageAdapter.remove(STORAGE_KEYS.REFRESH_TOKEN);
|
|
1397
1069
|
await storageAdapter.remove(STORAGE_KEYS.USER_INFO);
|
|
1070
|
+
await storageAdapter.remove(STORAGE_KEYS.REDIRECT_URI);
|
|
1398
1071
|
clearCookie("basic_token");
|
|
1399
1072
|
clearCookie("basic_access_token");
|
|
1400
1073
|
setUser({});
|
|
@@ -1409,6 +1082,10 @@ function BasicProvider({
|
|
|
1409
1082
|
await storageAdapter.set(STORAGE_KEYS.REFRESH_TOKEN, token2.refresh_token);
|
|
1410
1083
|
log("Updated refresh token in storage");
|
|
1411
1084
|
}
|
|
1085
|
+
if (!isRefreshToken) {
|
|
1086
|
+
await storageAdapter.remove(STORAGE_KEYS.REDIRECT_URI);
|
|
1087
|
+
log("Cleaned up redirect_uri from storage after successful exchange");
|
|
1088
|
+
}
|
|
1412
1089
|
setCookie("basic_access_token", token2.access_token, { httpOnly: false });
|
|
1413
1090
|
log("Updated access token in cookie");
|
|
1414
1091
|
}
|
|
@@ -1418,6 +1095,7 @@ function BasicProvider({
|
|
|
1418
1095
|
if (!error2.message.includes("offline") && !error2.message.includes("Network")) {
|
|
1419
1096
|
await storageAdapter.remove(STORAGE_KEYS.REFRESH_TOKEN);
|
|
1420
1097
|
await storageAdapter.remove(STORAGE_KEYS.USER_INFO);
|
|
1098
|
+
await storageAdapter.remove(STORAGE_KEYS.REDIRECT_URI);
|
|
1421
1099
|
clearCookie("basic_token");
|
|
1422
1100
|
clearCookie("basic_access_token");
|
|
1423
1101
|
setUser({});
|
|
@@ -1444,7 +1122,6 @@ function BasicProvider({
|
|
|
1444
1122
|
getToken,
|
|
1445
1123
|
getSignInLink,
|
|
1446
1124
|
db: syncRef.current ? syncRef.current : noDb,
|
|
1447
|
-
remoteDb: remoteDbRef.current ? remoteDbRef.current : noDb,
|
|
1448
1125
|
dbStatus
|
|
1449
1126
|
}, children: [
|
|
1450
1127
|
error && isDevMode() && /* @__PURE__ */ jsx(ErrorDisplay, { error }),
|
|
@@ -1481,7 +1158,6 @@ function useBasic() {
|
|
|
1481
1158
|
// src/index.ts
|
|
1482
1159
|
import { useLiveQuery as useQuery } from "dexie-react-hooks";
|
|
1483
1160
|
export {
|
|
1484
|
-
BasicDBSDK,
|
|
1485
1161
|
BasicProvider,
|
|
1486
1162
|
useBasic,
|
|
1487
1163
|
useQuery
|