@basictech/react 0.7.0-beta.0 → 0.7.0-beta.2
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/changelog.md +12 -5
- package/dist/index.d.mts +121 -5
- package/dist/index.d.ts +121 -5
- package/dist/index.js +978 -244
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +977 -244
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
- package/readme.md +127 -1
- package/src/AuthContext.tsx +427 -347
- package/src/index.ts +6 -31
- package/src/updater/updateMigrations.ts +22 -0
- package/src/updater/versionUpdater.ts +160 -0
- package/src/utils/network.ts +82 -0
- package/src/utils/schema.ts +120 -0
- package/src/utils/storage.ts +62 -0
package/dist/index.js
CHANGED
|
@@ -20,6 +20,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
20
20
|
// src/index.ts
|
|
21
21
|
var src_exports = {};
|
|
22
22
|
__export(src_exports, {
|
|
23
|
+
BasicDBSDK: () => BasicDBSDK,
|
|
23
24
|
BasicProvider: () => BasicProvider,
|
|
24
25
|
useBasic: () => useBasic,
|
|
25
26
|
useQuery: () => import_dexie_react_hooks.useLiveQuery
|
|
@@ -278,78 +279,607 @@ var BasicSync = class extends import_dexie2.Dexie {
|
|
|
278
279
|
}
|
|
279
280
|
};
|
|
280
281
|
|
|
281
|
-
// src/
|
|
282
|
-
var
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
282
|
+
// src/db_ts.ts
|
|
283
|
+
var DBError = class extends Error {
|
|
284
|
+
constructor(message, status, response, originalError) {
|
|
285
|
+
super(message);
|
|
286
|
+
this.status = status;
|
|
287
|
+
this.response = response;
|
|
288
|
+
this.originalError = originalError;
|
|
289
|
+
this.name = "DBError";
|
|
290
|
+
}
|
|
291
|
+
};
|
|
292
|
+
var QueryBuilder = class {
|
|
293
|
+
constructor(tableClient, tableSchema) {
|
|
294
|
+
this.tableClient = tableClient;
|
|
295
|
+
this.tableSchema = tableSchema;
|
|
296
|
+
}
|
|
297
|
+
params = {};
|
|
298
|
+
// Reserved fields that are always allowed
|
|
299
|
+
reservedFields = ["created_at", "updated_at", "id"];
|
|
300
|
+
// Validate field existence in schema
|
|
301
|
+
validateField(field) {
|
|
302
|
+
if (this.tableSchema && !this.reservedFields.includes(field)) {
|
|
303
|
+
if (!this.tableSchema.fields || !(field in this.tableSchema.fields)) {
|
|
304
|
+
throw new Error(`Invalid field: "${field}". Field does not exist in table schema.`);
|
|
305
|
+
}
|
|
288
306
|
}
|
|
289
|
-
}
|
|
290
|
-
|
|
307
|
+
}
|
|
308
|
+
// Validate operator based on field type
|
|
309
|
+
validateOperator(field, operator, value) {
|
|
310
|
+
if (!this.tableSchema || this.reservedFields.includes(field)) {
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
313
|
+
const fieldInfo = this.tableSchema.fields[field];
|
|
314
|
+
if (!fieldInfo)
|
|
315
|
+
return;
|
|
316
|
+
switch (operator) {
|
|
317
|
+
case "gt":
|
|
318
|
+
case "gte":
|
|
319
|
+
case "lt":
|
|
320
|
+
case "lte":
|
|
321
|
+
if (fieldInfo.type !== "number" && fieldInfo.type !== "string") {
|
|
322
|
+
throw new Error(`Operator "${operator}" can only be used with number or string fields. Field "${field}" is type "${fieldInfo.type}".`);
|
|
323
|
+
}
|
|
324
|
+
break;
|
|
325
|
+
case "like":
|
|
326
|
+
case "ilike":
|
|
327
|
+
if (fieldInfo.type !== "string") {
|
|
328
|
+
throw new Error(`Operator "${operator}" can only be used with string fields. Field "${field}" is type "${fieldInfo.type}".`);
|
|
329
|
+
}
|
|
330
|
+
if (typeof value !== "string") {
|
|
331
|
+
throw new Error(`Operator "${operator}" requires a string value. Received: ${typeof value}`);
|
|
332
|
+
}
|
|
333
|
+
break;
|
|
334
|
+
case "in":
|
|
335
|
+
if (!Array.isArray(value)) {
|
|
336
|
+
throw new Error(`Operator "in" requires an array value. Received: ${typeof value}`);
|
|
337
|
+
}
|
|
338
|
+
break;
|
|
339
|
+
case "is":
|
|
340
|
+
if (value !== null && typeof value !== "boolean") {
|
|
341
|
+
throw new Error(`Operator "is" requires null or boolean. Received: ${typeof value}`);
|
|
342
|
+
}
|
|
343
|
+
break;
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
// Add ordering to query with schema validation
|
|
347
|
+
order(field, direction = "asc") {
|
|
348
|
+
this.validateField(field);
|
|
349
|
+
this.params.order = `${field}.${direction}`;
|
|
350
|
+
return this;
|
|
351
|
+
}
|
|
352
|
+
// Add filtering to query
|
|
353
|
+
filter(conditions) {
|
|
354
|
+
if (!this.params.filters) {
|
|
355
|
+
this.params.filters = {};
|
|
356
|
+
}
|
|
357
|
+
for (const [field, condition] of Object.entries(conditions)) {
|
|
358
|
+
this.validateField(field);
|
|
359
|
+
if (condition === null || typeof condition !== "object") {
|
|
360
|
+
this.params.filters[field] = condition;
|
|
361
|
+
} else {
|
|
362
|
+
this.params.filters[field] = condition;
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
return this;
|
|
366
|
+
}
|
|
367
|
+
// Add limit to query
|
|
368
|
+
limit(count) {
|
|
369
|
+
this.params.limit = count;
|
|
370
|
+
return this;
|
|
371
|
+
}
|
|
372
|
+
// Add offset to query for pagination
|
|
373
|
+
offset(count) {
|
|
374
|
+
this.params.offset = count;
|
|
375
|
+
return this;
|
|
376
|
+
}
|
|
377
|
+
// Auto-execute when awaited
|
|
378
|
+
then(onfulfilled, onrejected) {
|
|
379
|
+
return this.tableClient.executeQuery(this.params).then(onfulfilled, onrejected);
|
|
380
|
+
}
|
|
381
|
+
// Auto-execute when awaited with catch
|
|
382
|
+
catch(onrejected) {
|
|
383
|
+
return this.tableClient.executeQuery(this.params).catch(onrejected);
|
|
384
|
+
}
|
|
385
|
+
// Auto-execute when awaited with finally
|
|
386
|
+
finally(onfinally) {
|
|
387
|
+
return this.tableClient.executeQuery(this.params).finally(onfinally);
|
|
388
|
+
}
|
|
389
|
+
};
|
|
390
|
+
var TableClient = class {
|
|
391
|
+
constructor(baseUrl, projectId, token, table, getToken, schema) {
|
|
392
|
+
this.baseUrl = baseUrl;
|
|
393
|
+
this.projectId = projectId;
|
|
394
|
+
this.token = token;
|
|
395
|
+
this.table = table;
|
|
396
|
+
this.getToken = getToken;
|
|
397
|
+
this.schema = schema;
|
|
398
|
+
if (schema && schema.tables && schema.tables[table]) {
|
|
399
|
+
this.tableSchema = schema.tables[table];
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
tableSchema;
|
|
403
|
+
async headers() {
|
|
404
|
+
const token = await this.getToken();
|
|
405
|
+
return {
|
|
406
|
+
Authorization: `Bearer ${token}`,
|
|
407
|
+
"Content-Type": "application/json"
|
|
408
|
+
};
|
|
409
|
+
}
|
|
410
|
+
async handleRequest(request) {
|
|
411
|
+
try {
|
|
412
|
+
const res = await request;
|
|
413
|
+
if (!res.ok) {
|
|
414
|
+
let errorMessage = `Request failed with status ${res.status}`;
|
|
415
|
+
let errorData;
|
|
416
|
+
try {
|
|
417
|
+
const json2 = await res.json();
|
|
418
|
+
errorData = json2;
|
|
419
|
+
if (json2.error || json2.message) {
|
|
420
|
+
const errorDetails = typeof json2.error === "object" ? JSON.stringify(json2.error) : json2.error;
|
|
421
|
+
const messageDetails = typeof json2.message === "object" ? JSON.stringify(json2.message) : json2.message;
|
|
422
|
+
errorMessage = `${res.status} ${res.statusText}: ${messageDetails || errorDetails || "Unknown error"}`;
|
|
423
|
+
}
|
|
424
|
+
} catch (e) {
|
|
425
|
+
console.log("Failed to parse error response:", e);
|
|
426
|
+
errorMessage = `${res.status} ${res.statusText}`;
|
|
427
|
+
}
|
|
428
|
+
throw new DBError(
|
|
429
|
+
errorMessage,
|
|
430
|
+
res.status,
|
|
431
|
+
errorData
|
|
432
|
+
);
|
|
433
|
+
}
|
|
434
|
+
const json = await res.json();
|
|
435
|
+
return json.data;
|
|
436
|
+
} catch (error) {
|
|
437
|
+
console.log("Caught error:", error);
|
|
438
|
+
if (error instanceof Error) {
|
|
439
|
+
console.log("Error type:", error.constructor.name);
|
|
440
|
+
console.log("Error stack:", error.stack);
|
|
441
|
+
}
|
|
442
|
+
if (error instanceof DBError) {
|
|
443
|
+
throw error;
|
|
444
|
+
}
|
|
445
|
+
if (error instanceof TypeError && error.message === "Network request failed") {
|
|
446
|
+
throw new DBError(
|
|
447
|
+
"Network request failed. Please check your internet connection and try again.",
|
|
448
|
+
void 0,
|
|
449
|
+
void 0,
|
|
450
|
+
error
|
|
451
|
+
);
|
|
452
|
+
}
|
|
453
|
+
throw new DBError(
|
|
454
|
+
`Unexpected error: ${error instanceof Error ? error.message : "Unknown error"}`,
|
|
455
|
+
void 0,
|
|
456
|
+
void 0,
|
|
457
|
+
error instanceof Error ? error : void 0
|
|
458
|
+
);
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
// Build query string from query options
|
|
462
|
+
buildQueryParams(query) {
|
|
463
|
+
if (!query)
|
|
464
|
+
return "";
|
|
465
|
+
const params = [];
|
|
466
|
+
if (query.id) {
|
|
467
|
+
params.push(`id=${query.id}`);
|
|
468
|
+
}
|
|
469
|
+
if (query.filters) {
|
|
470
|
+
for (const [field, condition] of Object.entries(query.filters)) {
|
|
471
|
+
this.addFilterParam(params, field, condition);
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
if (query.order) {
|
|
475
|
+
params.push(`order=${query.order}`);
|
|
476
|
+
}
|
|
477
|
+
if (query.limit !== void 0 && query.limit >= 0) {
|
|
478
|
+
params.push(`limit=${query.limit}`);
|
|
479
|
+
}
|
|
480
|
+
if (query.offset !== void 0 && query.offset >= 0) {
|
|
481
|
+
params.push(`offset=${query.offset}`);
|
|
482
|
+
}
|
|
483
|
+
return params.length > 0 ? `?${params.join("&")}` : "";
|
|
484
|
+
}
|
|
485
|
+
// Helper method to build filter parameters
|
|
486
|
+
addFilterParam(params, field, condition, negate = false) {
|
|
487
|
+
if (condition === null || typeof condition !== "object") {
|
|
488
|
+
if (condition === null) {
|
|
489
|
+
params.push(`${field}=${negate ? "not." : ""}is.null`);
|
|
490
|
+
} else if (typeof condition === "boolean") {
|
|
491
|
+
params.push(`${field}=${negate ? "not." : ""}is.${condition}`);
|
|
492
|
+
} else if (typeof condition === "number") {
|
|
493
|
+
params.push(`${field}=${negate ? "not." : ""}eq.${condition}`);
|
|
494
|
+
} else {
|
|
495
|
+
params.push(`${field}=${negate ? "not." : ""}eq.${encodeURIComponent(String(condition))}`);
|
|
496
|
+
}
|
|
497
|
+
return;
|
|
498
|
+
}
|
|
499
|
+
const operatorObj = condition;
|
|
500
|
+
if (operatorObj.not) {
|
|
501
|
+
this.addFilterParam(params, field, operatorObj.not, true);
|
|
502
|
+
return;
|
|
503
|
+
}
|
|
504
|
+
for (const [op, value] of Object.entries(operatorObj)) {
|
|
505
|
+
if (op === "not")
|
|
506
|
+
continue;
|
|
507
|
+
const operator = op;
|
|
508
|
+
if (value === null) {
|
|
509
|
+
params.push(`${field}=${negate ? "not." : ""}is.null`);
|
|
510
|
+
} else if (operator === "in" && Array.isArray(value)) {
|
|
511
|
+
params.push(`${field}=${negate ? "not." : ""}in.${value.join(",")}`);
|
|
512
|
+
} else if (operator === "is") {
|
|
513
|
+
if (typeof value === "boolean") {
|
|
514
|
+
params.push(`${field}=${negate ? "not." : ""}is.${value}`);
|
|
515
|
+
} else {
|
|
516
|
+
params.push(`${field}=${negate ? "not." : ""}is.null`);
|
|
517
|
+
}
|
|
518
|
+
} else {
|
|
519
|
+
const paramValue = typeof value === "string" ? encodeURIComponent(value) : String(value);
|
|
520
|
+
params.push(`${field}=${negate ? "not." : ""}${operator}.${paramValue}`);
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
// Internal method to execute a query with options
|
|
525
|
+
async executeQuery(options) {
|
|
526
|
+
const params = this.buildQueryParams(options);
|
|
527
|
+
const headers = await this.headers();
|
|
528
|
+
return this.handleRequest(
|
|
529
|
+
fetch(`${this.baseUrl}/account/${this.projectId}/db/${this.table}${params}`, {
|
|
530
|
+
headers
|
|
531
|
+
})
|
|
532
|
+
);
|
|
533
|
+
}
|
|
534
|
+
// Public method to start building a query
|
|
535
|
+
getAll() {
|
|
536
|
+
return new QueryBuilder(this, this.tableSchema);
|
|
537
|
+
}
|
|
538
|
+
// Get a specific item by ID
|
|
539
|
+
async get(id) {
|
|
540
|
+
const headers = await this.headers();
|
|
541
|
+
return this.handleRequest(
|
|
542
|
+
fetch(`${this.baseUrl}/account/${this.projectId}/db/${this.table}/${id}`, {
|
|
543
|
+
headers
|
|
544
|
+
})
|
|
545
|
+
);
|
|
546
|
+
}
|
|
547
|
+
async create(value) {
|
|
548
|
+
const headers = await this.headers();
|
|
549
|
+
return this.handleRequest(
|
|
550
|
+
fetch(`${this.baseUrl}/account/${this.projectId}/db/${this.table}`, {
|
|
551
|
+
method: "POST",
|
|
552
|
+
headers,
|
|
553
|
+
body: JSON.stringify({ value })
|
|
554
|
+
})
|
|
555
|
+
);
|
|
556
|
+
}
|
|
557
|
+
async update(id, value) {
|
|
558
|
+
const headers = await this.headers();
|
|
559
|
+
return this.handleRequest(
|
|
560
|
+
fetch(`${this.baseUrl}/account/${this.projectId}/db/${this.table}/${id}`, {
|
|
561
|
+
method: "PATCH",
|
|
562
|
+
headers,
|
|
563
|
+
body: JSON.stringify({ value })
|
|
564
|
+
})
|
|
565
|
+
);
|
|
566
|
+
}
|
|
567
|
+
async replace(id, value) {
|
|
568
|
+
const headers = await this.headers();
|
|
569
|
+
return this.handleRequest(
|
|
570
|
+
fetch(`${this.baseUrl}/account/${this.projectId}/db/${this.table}/${id}`, {
|
|
571
|
+
method: "PUT",
|
|
572
|
+
headers,
|
|
573
|
+
body: JSON.stringify({ value })
|
|
574
|
+
})
|
|
575
|
+
);
|
|
576
|
+
}
|
|
577
|
+
async delete(id) {
|
|
578
|
+
const token = await this.getToken();
|
|
579
|
+
const headers = {
|
|
580
|
+
Authorization: `Bearer ${token}`
|
|
581
|
+
};
|
|
582
|
+
return this.handleRequest(
|
|
583
|
+
fetch(`${this.baseUrl}/account/${this.projectId}/db/${this.table}/${id}`, {
|
|
584
|
+
method: "DELETE",
|
|
585
|
+
headers
|
|
586
|
+
})
|
|
587
|
+
);
|
|
588
|
+
}
|
|
589
|
+
};
|
|
590
|
+
var BasicDBSDK = class {
|
|
591
|
+
projectId;
|
|
592
|
+
getToken;
|
|
593
|
+
baseUrl;
|
|
594
|
+
schema;
|
|
595
|
+
tableNames;
|
|
596
|
+
constructor(config) {
|
|
597
|
+
this.projectId = config.project_id;
|
|
598
|
+
if (config.getToken) {
|
|
599
|
+
this.getToken = config.getToken;
|
|
600
|
+
} else if (config.token) {
|
|
601
|
+
this.getToken = async () => config.token;
|
|
602
|
+
} else {
|
|
603
|
+
throw new Error("Either token or getToken must be provided");
|
|
604
|
+
}
|
|
605
|
+
this.baseUrl = config.baseUrl || "https://api.basic.tech";
|
|
606
|
+
this.schema = config.schema;
|
|
607
|
+
this.tableNames = Object.keys(this.schema.tables);
|
|
608
|
+
}
|
|
609
|
+
// Primary method - table access
|
|
610
|
+
table(name) {
|
|
611
|
+
if (!this.tableNames.includes(name)) {
|
|
612
|
+
throw new Error(`Table '${name}' not found in schema. Available tables: ${this.tableNames.join(", ")}`);
|
|
613
|
+
}
|
|
614
|
+
return new TableClient(
|
|
615
|
+
this.baseUrl,
|
|
616
|
+
this.projectId,
|
|
617
|
+
"",
|
|
618
|
+
// Empty placeholder, will be replaced in headers() method
|
|
619
|
+
name,
|
|
620
|
+
this.getToken,
|
|
621
|
+
this.schema
|
|
622
|
+
// Pass the entire schema to the TableClient
|
|
623
|
+
);
|
|
624
|
+
}
|
|
625
|
+
get tables() {
|
|
626
|
+
return {};
|
|
627
|
+
}
|
|
628
|
+
fields(table) {
|
|
629
|
+
const tableSchema = this.schema.tables[table];
|
|
630
|
+
if (!tableSchema) {
|
|
631
|
+
throw new Error(`Table '${table}' not found in schema`);
|
|
632
|
+
}
|
|
633
|
+
return Object.keys(tableSchema.fields);
|
|
634
|
+
}
|
|
635
|
+
};
|
|
636
|
+
|
|
637
|
+
// package.json
|
|
638
|
+
var version = "0.7.0-beta.1";
|
|
639
|
+
|
|
640
|
+
// src/updater/versionUpdater.ts
|
|
641
|
+
var VersionUpdater = class {
|
|
642
|
+
storage;
|
|
643
|
+
currentVersion;
|
|
644
|
+
migrations;
|
|
645
|
+
versionKey = "basic_app_version";
|
|
646
|
+
constructor(storage, currentVersion, migrations = []) {
|
|
647
|
+
this.storage = storage;
|
|
648
|
+
this.currentVersion = currentVersion;
|
|
649
|
+
this.migrations = migrations.sort((a, b) => this.compareVersions(a.fromVersion, b.fromVersion));
|
|
650
|
+
}
|
|
651
|
+
/**
|
|
652
|
+
* Check current stored version and run migrations if needed
|
|
653
|
+
* Only compares major.minor versions, ignoring beta/prerelease parts
|
|
654
|
+
* Example: "0.7.0-beta.1" and "0.7.0" are treated as the same version
|
|
655
|
+
*/
|
|
656
|
+
async checkAndUpdate() {
|
|
657
|
+
const storedVersion = await this.getStoredVersion();
|
|
658
|
+
if (!storedVersion) {
|
|
659
|
+
await this.setStoredVersion(this.currentVersion);
|
|
660
|
+
return { updated: false, toVersion: this.currentVersion };
|
|
661
|
+
}
|
|
662
|
+
if (storedVersion === this.currentVersion) {
|
|
663
|
+
return { updated: false, toVersion: this.currentVersion };
|
|
664
|
+
}
|
|
665
|
+
const migrationsToRun = this.getMigrationsToRun(storedVersion, this.currentVersion);
|
|
666
|
+
if (migrationsToRun.length === 0) {
|
|
667
|
+
await this.setStoredVersion(this.currentVersion);
|
|
668
|
+
return { updated: true, fromVersion: storedVersion, toVersion: this.currentVersion };
|
|
669
|
+
}
|
|
670
|
+
for (const migration of migrationsToRun) {
|
|
671
|
+
try {
|
|
672
|
+
console.log(`Running migration from ${migration.fromVersion} to ${migration.toVersion}`);
|
|
673
|
+
await migration.migrate(this.storage);
|
|
674
|
+
} catch (error) {
|
|
675
|
+
console.error(`Migration failed from ${migration.fromVersion} to ${migration.toVersion}:`, error);
|
|
676
|
+
throw new Error(`Migration failed: ${error}`);
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
await this.setStoredVersion(this.currentVersion);
|
|
680
|
+
return { updated: true, fromVersion: storedVersion, toVersion: this.currentVersion };
|
|
681
|
+
}
|
|
682
|
+
async getStoredVersion() {
|
|
683
|
+
try {
|
|
684
|
+
const versionData = await this.storage.get(this.versionKey);
|
|
685
|
+
if (!versionData)
|
|
686
|
+
return null;
|
|
687
|
+
const versionInfo = JSON.parse(versionData);
|
|
688
|
+
return versionInfo.version;
|
|
689
|
+
} catch (error) {
|
|
690
|
+
console.warn("Failed to get stored version:", error);
|
|
691
|
+
return null;
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
async setStoredVersion(version2) {
|
|
695
|
+
const versionInfo = {
|
|
696
|
+
version: version2,
|
|
697
|
+
lastUpdated: Date.now()
|
|
698
|
+
};
|
|
699
|
+
await this.storage.set(this.versionKey, JSON.stringify(versionInfo));
|
|
700
|
+
}
|
|
701
|
+
getMigrationsToRun(fromVersion, toVersion) {
|
|
702
|
+
return this.migrations.filter((migration) => {
|
|
703
|
+
const storedLessThanMigrationTo = this.compareVersions(fromVersion, migration.toVersion) < 0;
|
|
704
|
+
const currentGreaterThanOrEqualMigrationTo = this.compareVersions(toVersion, migration.toVersion) >= 0;
|
|
705
|
+
console.log(`Checking migration ${migration.fromVersion} \u2192 ${migration.toVersion}:`);
|
|
706
|
+
console.log(` stored ${fromVersion} < migration.to ${migration.toVersion}: ${storedLessThanMigrationTo}`);
|
|
707
|
+
console.log(` current ${toVersion} >= migration.to ${migration.toVersion}: ${currentGreaterThanOrEqualMigrationTo}`);
|
|
708
|
+
const shouldRun = storedLessThanMigrationTo && currentGreaterThanOrEqualMigrationTo;
|
|
709
|
+
console.log(` Should run: ${shouldRun}`);
|
|
710
|
+
return shouldRun;
|
|
711
|
+
});
|
|
712
|
+
}
|
|
713
|
+
/**
|
|
714
|
+
* Simple semantic version comparison (major.minor only, ignoring beta/prerelease)
|
|
715
|
+
* Returns: -1 if a < b, 0 if a === b, 1 if a > b
|
|
716
|
+
*/
|
|
717
|
+
compareVersions(a, b) {
|
|
718
|
+
const aMajorMinor = this.extractMajorMinor(a);
|
|
719
|
+
const bMajorMinor = this.extractMajorMinor(b);
|
|
720
|
+
if (aMajorMinor.major !== bMajorMinor.major) {
|
|
721
|
+
return aMajorMinor.major - bMajorMinor.major;
|
|
722
|
+
}
|
|
723
|
+
return aMajorMinor.minor - bMajorMinor.minor;
|
|
724
|
+
}
|
|
725
|
+
/**
|
|
726
|
+
* Extract major.minor from version string, ignoring beta/prerelease
|
|
727
|
+
* Examples: "0.7.0-beta.1" -> {major: 0, minor: 7}
|
|
728
|
+
* "1.2.3" -> {major: 1, minor: 2}
|
|
729
|
+
*/
|
|
730
|
+
extractMajorMinor(version2) {
|
|
731
|
+
const cleanVersion = version2.split("-")[0]?.split("+")[0] || version2;
|
|
732
|
+
const parts = cleanVersion.split(".").map(Number);
|
|
733
|
+
return {
|
|
734
|
+
major: parts[0] || 0,
|
|
735
|
+
minor: parts[1] || 0
|
|
736
|
+
};
|
|
737
|
+
}
|
|
738
|
+
/**
|
|
739
|
+
* Add a migration to the updater
|
|
740
|
+
*/
|
|
741
|
+
addMigration(migration) {
|
|
742
|
+
this.migrations.push(migration);
|
|
743
|
+
this.migrations.sort((a, b) => this.compareVersions(a.fromVersion, b.fromVersion));
|
|
744
|
+
}
|
|
745
|
+
};
|
|
746
|
+
function createVersionUpdater(storage, currentVersion, migrations = []) {
|
|
747
|
+
return new VersionUpdater(storage, currentVersion, migrations);
|
|
291
748
|
}
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
749
|
+
|
|
750
|
+
// src/updater/updateMigrations.ts
|
|
751
|
+
var addMigrationTimestamp = {
|
|
752
|
+
fromVersion: "0.6.0",
|
|
753
|
+
toVersion: "0.7.0",
|
|
754
|
+
async migrate(storage) {
|
|
755
|
+
console.log("Running test migration");
|
|
756
|
+
storage.set("test_migration", "true");
|
|
757
|
+
}
|
|
758
|
+
};
|
|
759
|
+
function getMigrations() {
|
|
760
|
+
return [
|
|
761
|
+
addMigrationTimestamp
|
|
762
|
+
];
|
|
303
763
|
}
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
764
|
+
|
|
765
|
+
// src/utils/storage.ts
|
|
766
|
+
var LocalStorageAdapter = class {
|
|
767
|
+
async get(key) {
|
|
768
|
+
return localStorage.getItem(key);
|
|
769
|
+
}
|
|
770
|
+
async set(key, value) {
|
|
771
|
+
localStorage.setItem(key, value);
|
|
772
|
+
}
|
|
773
|
+
async remove(key) {
|
|
774
|
+
localStorage.removeItem(key);
|
|
775
|
+
}
|
|
776
|
+
};
|
|
777
|
+
var STORAGE_KEYS = {
|
|
778
|
+
REFRESH_TOKEN: "basic_refresh_token",
|
|
779
|
+
USER_INFO: "basic_user_info",
|
|
780
|
+
AUTH_STATE: "basic_auth_state",
|
|
781
|
+
DEBUG: "basic_debug"
|
|
782
|
+
};
|
|
783
|
+
function getCookie(name) {
|
|
784
|
+
let cookieValue = "";
|
|
785
|
+
if (document.cookie && document.cookie !== "") {
|
|
786
|
+
const cookies = document.cookie.split(";");
|
|
787
|
+
for (let i = 0; i < cookies.length; i++) {
|
|
788
|
+
const cookie = cookies[i]?.trim();
|
|
789
|
+
if (cookie && cookie.substring(0, name.length + 1) === name + "=") {
|
|
790
|
+
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
|
|
791
|
+
break;
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
return cookieValue;
|
|
796
|
+
}
|
|
797
|
+
function setCookie(name, value, options) {
|
|
798
|
+
const opts = {
|
|
799
|
+
secure: true,
|
|
800
|
+
sameSite: "Strict",
|
|
801
|
+
httpOnly: false,
|
|
802
|
+
...options
|
|
803
|
+
};
|
|
804
|
+
let cookieString = `${name}=${value}`;
|
|
805
|
+
if (opts.secure)
|
|
806
|
+
cookieString += "; Secure";
|
|
807
|
+
if (opts.sameSite)
|
|
808
|
+
cookieString += `; SameSite=${opts.sameSite}`;
|
|
809
|
+
if (opts.httpOnly)
|
|
810
|
+
cookieString += "; HttpOnly";
|
|
811
|
+
document.cookie = cookieString;
|
|
812
|
+
}
|
|
813
|
+
function clearCookie(name) {
|
|
814
|
+
document.cookie = `${name}=; Secure; SameSite=Strict`;
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
// src/utils/network.ts
|
|
818
|
+
function isDevelopment(debug) {
|
|
819
|
+
return window.location.hostname === "localhost" || window.location.hostname === "127.0.0.1" || window.location.hostname.includes("localhost") || window.location.hostname.includes("127.0.0.1") || window.location.hostname.includes(".local") || process.env.NODE_ENV === "development" || debug === true;
|
|
315
820
|
}
|
|
316
|
-
async function
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
"
|
|
821
|
+
async function checkForNewVersion() {
|
|
822
|
+
try {
|
|
823
|
+
const isBeta = version.includes("beta");
|
|
824
|
+
const response = await fetch(`https://registry.npmjs.org/@basictech/react/${isBeta ? "beta" : "latest"}`);
|
|
825
|
+
if (!response.ok) {
|
|
826
|
+
throw new Error("Failed to fetch version from npm");
|
|
322
827
|
}
|
|
323
|
-
|
|
324
|
-
|
|
828
|
+
const data = await response.json();
|
|
829
|
+
const latestVersion = data.version;
|
|
830
|
+
if (latestVersion !== version) {
|
|
831
|
+
console.warn("[basic] New version available:", latestVersion, `
|
|
832
|
+
run "npm install @basictech/react@${latestVersion}" to update`);
|
|
833
|
+
}
|
|
834
|
+
if (isBeta) {
|
|
835
|
+
log("thank you for being on basictech/react beta :)");
|
|
836
|
+
}
|
|
837
|
+
return {
|
|
838
|
+
hasNewVersion: version !== latestVersion,
|
|
839
|
+
latestVersion,
|
|
840
|
+
currentVersion: version
|
|
841
|
+
};
|
|
842
|
+
} catch (error) {
|
|
843
|
+
log("Error checking for new version:", error);
|
|
844
|
+
return {
|
|
845
|
+
hasNewVersion: false,
|
|
846
|
+
latestVersion: null,
|
|
847
|
+
currentVersion: null
|
|
848
|
+
};
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
function cleanOAuthParamsFromUrl() {
|
|
852
|
+
if (window.location.search.includes("code") || window.location.search.includes("state")) {
|
|
853
|
+
const url = new URL(window.location.href);
|
|
854
|
+
url.searchParams.delete("code");
|
|
855
|
+
url.searchParams.delete("state");
|
|
856
|
+
window.history.pushState({}, document.title, url.pathname + url.search);
|
|
857
|
+
log("Cleaned OAuth parameters from URL");
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
function getSyncStatus(statusCode) {
|
|
861
|
+
switch (statusCode) {
|
|
862
|
+
case -1:
|
|
863
|
+
return "ERROR";
|
|
864
|
+
case 0:
|
|
865
|
+
return "OFFLINE";
|
|
866
|
+
case 1:
|
|
867
|
+
return "CONNECTING";
|
|
868
|
+
case 2:
|
|
869
|
+
return "ONLINE";
|
|
870
|
+
case 3:
|
|
871
|
+
return "SYNCING";
|
|
872
|
+
case 4:
|
|
873
|
+
return "ERROR_WILL_RETRY";
|
|
874
|
+
default:
|
|
875
|
+
return "UNKNOWN";
|
|
876
|
+
}
|
|
325
877
|
}
|
|
326
878
|
|
|
327
|
-
// src/
|
|
879
|
+
// src/utils/schema.ts
|
|
328
880
|
var import_schema2 = require("@basictech/schema");
|
|
329
|
-
|
|
330
|
-
// package.json
|
|
331
|
-
var version = "0.6.0";
|
|
332
|
-
|
|
333
|
-
// src/AuthContext.tsx
|
|
334
|
-
var import_jsx_runtime = require("react/jsx-runtime");
|
|
335
|
-
var BasicContext = (0, import_react.createContext)({
|
|
336
|
-
unicorn: "\u{1F984}",
|
|
337
|
-
isAuthReady: false,
|
|
338
|
-
isSignedIn: false,
|
|
339
|
-
user: null,
|
|
340
|
-
signout: () => {
|
|
341
|
-
},
|
|
342
|
-
signin: () => {
|
|
343
|
-
},
|
|
344
|
-
getToken: () => new Promise(() => {
|
|
345
|
-
}),
|
|
346
|
-
getSignInLink: () => "",
|
|
347
|
-
db: {},
|
|
348
|
-
dbStatus: "LOADING" /* LOADING */
|
|
349
|
-
});
|
|
350
881
|
async function getSchemaStatus(schema) {
|
|
351
882
|
const projectId = schema.project_id;
|
|
352
|
-
let status = "";
|
|
353
883
|
const valid = (0, import_schema2.validateSchema)(schema);
|
|
354
884
|
if (!valid.valid) {
|
|
355
885
|
console.warn("BasicDB Error: your local schema is invalid. Please fix errors and try again - sync is disabled");
|
|
@@ -412,55 +942,62 @@ async function getSchemaStatus(schema) {
|
|
|
412
942
|
};
|
|
413
943
|
}
|
|
414
944
|
}
|
|
415
|
-
function
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
case 4:
|
|
428
|
-
return "ERROR_WILL_RETRY";
|
|
429
|
-
default:
|
|
430
|
-
return "UNKNOWN";
|
|
431
|
-
}
|
|
432
|
-
}
|
|
433
|
-
async function checkForNewVersion() {
|
|
434
|
-
try {
|
|
435
|
-
const isBeta = version.includes("beta");
|
|
436
|
-
const response = await fetch(`https://registry.npmjs.org/@basictech/react/${isBeta ? "beta" : "latest"}`);
|
|
437
|
-
if (!response.ok) {
|
|
438
|
-
throw new Error("Failed to fetch version from npm");
|
|
439
|
-
}
|
|
440
|
-
const data = await response.json();
|
|
441
|
-
const latestVersion = data.version;
|
|
442
|
-
if (latestVersion !== version) {
|
|
443
|
-
console.warn("[basic] New version available:", latestVersion, `
|
|
444
|
-
run "npm install @basictech/react@${latestVersion}" to update`);
|
|
445
|
-
}
|
|
446
|
-
if (isBeta) {
|
|
447
|
-
log("thank you for being on basictech/react beta :)");
|
|
448
|
-
}
|
|
945
|
+
async function validateAndCheckSchema(schema) {
|
|
946
|
+
const valid = (0, import_schema2.validateSchema)(schema);
|
|
947
|
+
if (!valid.valid) {
|
|
948
|
+
log("Basic Schema is invalid!", valid.errors);
|
|
949
|
+
console.group("Schema Errors");
|
|
950
|
+
let errorMessage = "";
|
|
951
|
+
valid.errors.forEach((error, index) => {
|
|
952
|
+
log(`${index + 1}:`, error.message, ` - at ${error.instancePath}`);
|
|
953
|
+
errorMessage += `${index + 1}: ${error.message} - at ${error.instancePath}
|
|
954
|
+
`;
|
|
955
|
+
});
|
|
956
|
+
console.groupEnd();
|
|
449
957
|
return {
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
};
|
|
454
|
-
} catch (error) {
|
|
455
|
-
log("Error checking for new version:", error);
|
|
456
|
-
return {
|
|
457
|
-
hasNewVersion: false,
|
|
458
|
-
latestVersion: null,
|
|
459
|
-
currentVersion: null
|
|
958
|
+
isValid: false,
|
|
959
|
+
schemaStatus: { valid: false },
|
|
960
|
+
errors: valid.errors
|
|
460
961
|
};
|
|
461
962
|
}
|
|
963
|
+
let schemaStatus = { valid: false };
|
|
964
|
+
if (schema.version !== 0) {
|
|
965
|
+
schemaStatus = await getSchemaStatus(schema);
|
|
966
|
+
log("schemaStatus", schemaStatus);
|
|
967
|
+
} else {
|
|
968
|
+
log("schema not published - at version 0");
|
|
969
|
+
}
|
|
970
|
+
return {
|
|
971
|
+
isValid: true,
|
|
972
|
+
schemaStatus
|
|
973
|
+
};
|
|
462
974
|
}
|
|
463
|
-
|
|
975
|
+
|
|
976
|
+
// src/AuthContext.tsx
|
|
977
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
978
|
+
var BasicContext = (0, import_react.createContext)({
|
|
979
|
+
unicorn: "\u{1F984}",
|
|
980
|
+
isAuthReady: false,
|
|
981
|
+
isSignedIn: false,
|
|
982
|
+
user: null,
|
|
983
|
+
signout: () => Promise.resolve(),
|
|
984
|
+
signin: () => Promise.resolve(),
|
|
985
|
+
signinWithCode: () => new Promise(() => {
|
|
986
|
+
}),
|
|
987
|
+
getToken: () => new Promise(() => {
|
|
988
|
+
}),
|
|
989
|
+
getSignInLink: () => Promise.resolve(""),
|
|
990
|
+
db: {},
|
|
991
|
+
remoteDb: {},
|
|
992
|
+
dbStatus: "LOADING" /* LOADING */
|
|
993
|
+
});
|
|
994
|
+
function BasicProvider({
|
|
995
|
+
children,
|
|
996
|
+
project_id,
|
|
997
|
+
schema,
|
|
998
|
+
debug = false,
|
|
999
|
+
storage
|
|
1000
|
+
}) {
|
|
464
1001
|
const [isAuthReady, setIsAuthReady] = (0, import_react.useState)(false);
|
|
465
1002
|
const [isSignedIn, setIsSignedIn] = (0, import_react.useState)(false);
|
|
466
1003
|
const [token, setToken] = (0, import_react.useState)(null);
|
|
@@ -469,7 +1006,41 @@ function BasicProvider({ children, project_id, schema, debug = false }) {
|
|
|
469
1006
|
const [isReady, setIsReady] = (0, import_react.useState)(false);
|
|
470
1007
|
const [dbStatus, setDbStatus] = (0, import_react.useState)("OFFLINE" /* OFFLINE */);
|
|
471
1008
|
const [error, setError] = (0, import_react.useState)(null);
|
|
1009
|
+
const [isOnline, setIsOnline] = (0, import_react.useState)(navigator.onLine);
|
|
1010
|
+
const [pendingRefresh, setPendingRefresh] = (0, import_react.useState)(false);
|
|
472
1011
|
const syncRef = (0, import_react.useRef)(null);
|
|
1012
|
+
const remoteDbRef = (0, import_react.useRef)(null);
|
|
1013
|
+
const storageAdapter = storage || new LocalStorageAdapter();
|
|
1014
|
+
const isDevMode = () => isDevelopment(debug);
|
|
1015
|
+
const cleanOAuthParams = () => cleanOAuthParamsFromUrl();
|
|
1016
|
+
(0, import_react.useEffect)(() => {
|
|
1017
|
+
const handleOnline = () => {
|
|
1018
|
+
log("Network came back online");
|
|
1019
|
+
setIsOnline(true);
|
|
1020
|
+
if (pendingRefresh) {
|
|
1021
|
+
log("Retrying pending token refresh");
|
|
1022
|
+
setPendingRefresh(false);
|
|
1023
|
+
if (token) {
|
|
1024
|
+
const refreshToken = token.refresh_token || localStorage.getItem("basic_refresh_token");
|
|
1025
|
+
if (refreshToken) {
|
|
1026
|
+
fetchToken(refreshToken).catch((error2) => {
|
|
1027
|
+
log("Retry refresh failed:", error2);
|
|
1028
|
+
});
|
|
1029
|
+
}
|
|
1030
|
+
}
|
|
1031
|
+
}
|
|
1032
|
+
};
|
|
1033
|
+
const handleOffline = () => {
|
|
1034
|
+
log("Network went offline");
|
|
1035
|
+
setIsOnline(false);
|
|
1036
|
+
};
|
|
1037
|
+
window.addEventListener("online", handleOnline);
|
|
1038
|
+
window.addEventListener("offline", handleOffline);
|
|
1039
|
+
return () => {
|
|
1040
|
+
window.removeEventListener("online", handleOnline);
|
|
1041
|
+
window.removeEventListener("offline", handleOffline);
|
|
1042
|
+
};
|
|
1043
|
+
}, [pendingRefresh, token]);
|
|
473
1044
|
(0, import_react.useEffect)(() => {
|
|
474
1045
|
function initDb(options) {
|
|
475
1046
|
if (!syncRef.current) {
|
|
@@ -478,9 +1049,6 @@ function BasicProvider({ children, project_id, schema, debug = false }) {
|
|
|
478
1049
|
syncRef.current.syncable.on("statusChanged", (status, url) => {
|
|
479
1050
|
setDbStatus(getSyncStatus(status));
|
|
480
1051
|
});
|
|
481
|
-
syncRef.current.syncable.getStatus().then((status) => {
|
|
482
|
-
setDbStatus(getSyncStatus(status));
|
|
483
|
-
});
|
|
484
1052
|
if (options.shouldConnect) {
|
|
485
1053
|
setShouldConnect(true);
|
|
486
1054
|
} else {
|
|
@@ -490,17 +1058,15 @@ function BasicProvider({ children, project_id, schema, debug = false }) {
|
|
|
490
1058
|
}
|
|
491
1059
|
}
|
|
492
1060
|
async function checkSchema() {
|
|
493
|
-
const
|
|
494
|
-
if (!
|
|
495
|
-
log("Basic Schema is invalid!", valid.errors);
|
|
496
|
-
console.group("Schema Errors");
|
|
1061
|
+
const result = await validateAndCheckSchema(schema);
|
|
1062
|
+
if (!result.isValid) {
|
|
497
1063
|
let errorMessage = "";
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
1064
|
+
if (result.errors) {
|
|
1065
|
+
result.errors.forEach((error2, index) => {
|
|
1066
|
+
errorMessage += `${index + 1}: ${error2.message} - at ${error2.instancePath}
|
|
501
1067
|
`;
|
|
502
|
-
|
|
503
|
-
|
|
1068
|
+
});
|
|
1069
|
+
}
|
|
504
1070
|
setError({
|
|
505
1071
|
code: "schema_invalid",
|
|
506
1072
|
title: "Basic Schema is invalid!",
|
|
@@ -509,17 +1075,10 @@ function BasicProvider({ children, project_id, schema, debug = false }) {
|
|
|
509
1075
|
setIsReady(true);
|
|
510
1076
|
return null;
|
|
511
1077
|
}
|
|
512
|
-
|
|
513
|
-
if (schema.version !== 0) {
|
|
514
|
-
schemaStatus = await getSchemaStatus(schema);
|
|
515
|
-
log("schemaStatus", schemaStatus);
|
|
516
|
-
} else {
|
|
517
|
-
log("schema not published - at version 0");
|
|
518
|
-
}
|
|
519
|
-
if (schemaStatus.valid) {
|
|
1078
|
+
if (result.schemaStatus.valid) {
|
|
520
1079
|
initDb({ shouldConnect: true });
|
|
521
1080
|
} else {
|
|
522
|
-
log("Schema is invalid!", schemaStatus);
|
|
1081
|
+
log("Schema is invalid!", result.schemaStatus);
|
|
523
1082
|
initDb({ shouldConnect: false });
|
|
524
1083
|
}
|
|
525
1084
|
checkForNewVersion();
|
|
@@ -531,36 +1090,101 @@ function BasicProvider({ children, project_id, schema, debug = false }) {
|
|
|
531
1090
|
}
|
|
532
1091
|
}, []);
|
|
533
1092
|
(0, import_react.useEffect)(() => {
|
|
534
|
-
|
|
535
|
-
|
|
1093
|
+
async function connectToDb() {
|
|
1094
|
+
if (token && syncRef.current && isSignedIn && shouldConnect) {
|
|
1095
|
+
const tok = await getToken();
|
|
1096
|
+
if (!tok) {
|
|
1097
|
+
log("no token found");
|
|
1098
|
+
return;
|
|
1099
|
+
}
|
|
1100
|
+
log("connecting to db...");
|
|
1101
|
+
syncRef.current?.connect({ access_token: tok }).catch((e) => {
|
|
1102
|
+
log("error connecting to db", e);
|
|
1103
|
+
});
|
|
1104
|
+
}
|
|
536
1105
|
}
|
|
1106
|
+
connectToDb();
|
|
537
1107
|
}, [isSignedIn, shouldConnect]);
|
|
538
1108
|
(0, import_react.useEffect)(() => {
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
1109
|
+
if (project_id && schema && token?.access_token && !remoteDbRef.current) {
|
|
1110
|
+
log("Initializing Remote DB SDK");
|
|
1111
|
+
remoteDbRef.current = new BasicDBSDK({
|
|
1112
|
+
project_id,
|
|
1113
|
+
schema,
|
|
1114
|
+
getToken: () => getToken(),
|
|
1115
|
+
baseUrl: "https://api.basic.tech"
|
|
1116
|
+
});
|
|
1117
|
+
}
|
|
1118
|
+
}, [token, project_id, schema]);
|
|
1119
|
+
(0, import_react.useEffect)(() => {
|
|
1120
|
+
const initializeAuth = async () => {
|
|
1121
|
+
await storageAdapter.set(STORAGE_KEYS.DEBUG, debug ? "true" : "false");
|
|
1122
|
+
try {
|
|
1123
|
+
const versionUpdater = createVersionUpdater(storageAdapter, version, getMigrations());
|
|
1124
|
+
const updateResult = await versionUpdater.checkAndUpdate();
|
|
1125
|
+
if (updateResult.updated) {
|
|
1126
|
+
log(`App updated from ${updateResult.fromVersion} to ${updateResult.toVersion}`);
|
|
1127
|
+
} else {
|
|
1128
|
+
log(`App version ${updateResult.toVersion} is current`);
|
|
550
1129
|
}
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
}
|
|
554
|
-
|
|
555
|
-
if (
|
|
556
|
-
|
|
1130
|
+
} catch (error2) {
|
|
1131
|
+
log("Version update failed:", error2);
|
|
1132
|
+
}
|
|
1133
|
+
try {
|
|
1134
|
+
if (window.location.search.includes("code")) {
|
|
1135
|
+
let code = window.location?.search?.split("code=")[1]?.split("&")[0];
|
|
1136
|
+
if (!code)
|
|
1137
|
+
return;
|
|
1138
|
+
const state = await storageAdapter.get(STORAGE_KEYS.AUTH_STATE);
|
|
1139
|
+
const urlState = window.location.search.split("state=")[1]?.split("&")[0];
|
|
1140
|
+
if (!state || state !== urlState) {
|
|
1141
|
+
log("error: auth state does not match");
|
|
1142
|
+
setIsAuthReady(true);
|
|
1143
|
+
await storageAdapter.remove(STORAGE_KEYS.AUTH_STATE);
|
|
1144
|
+
cleanOAuthParams();
|
|
1145
|
+
return;
|
|
1146
|
+
}
|
|
1147
|
+
await storageAdapter.remove(STORAGE_KEYS.AUTH_STATE);
|
|
1148
|
+
cleanOAuthParams();
|
|
1149
|
+
fetchToken(code).catch((error2) => {
|
|
1150
|
+
log("Error fetching token:", error2);
|
|
1151
|
+
});
|
|
557
1152
|
} else {
|
|
558
|
-
|
|
1153
|
+
const refreshToken = await storageAdapter.get(STORAGE_KEYS.REFRESH_TOKEN);
|
|
1154
|
+
if (refreshToken) {
|
|
1155
|
+
log("Found refresh token in storage, attempting to refresh access token");
|
|
1156
|
+
fetchToken(refreshToken).catch((error2) => {
|
|
1157
|
+
log("Error fetching refresh token:", error2);
|
|
1158
|
+
});
|
|
1159
|
+
} else {
|
|
1160
|
+
let cookie_token = getCookie("basic_token");
|
|
1161
|
+
if (cookie_token !== "") {
|
|
1162
|
+
const tokenData = JSON.parse(cookie_token);
|
|
1163
|
+
setToken(tokenData);
|
|
1164
|
+
if (tokenData.refresh_token) {
|
|
1165
|
+
await storageAdapter.set(STORAGE_KEYS.REFRESH_TOKEN, tokenData.refresh_token);
|
|
1166
|
+
}
|
|
1167
|
+
} else {
|
|
1168
|
+
const cachedUserInfo = await storageAdapter.get(STORAGE_KEYS.USER_INFO);
|
|
1169
|
+
if (cachedUserInfo) {
|
|
1170
|
+
try {
|
|
1171
|
+
const userData = JSON.parse(cachedUserInfo);
|
|
1172
|
+
setUser(userData);
|
|
1173
|
+
setIsSignedIn(true);
|
|
1174
|
+
log("Loaded cached user info for offline mode");
|
|
1175
|
+
} catch (error2) {
|
|
1176
|
+
log("Error parsing cached user info:", error2);
|
|
1177
|
+
}
|
|
1178
|
+
}
|
|
1179
|
+
setIsAuthReady(true);
|
|
1180
|
+
}
|
|
1181
|
+
}
|
|
559
1182
|
}
|
|
1183
|
+
} catch (e) {
|
|
1184
|
+
log("error getting token", e);
|
|
560
1185
|
}
|
|
561
|
-
}
|
|
562
|
-
|
|
563
|
-
}
|
|
1186
|
+
};
|
|
1187
|
+
initializeAuth();
|
|
564
1188
|
}, []);
|
|
565
1189
|
(0, import_react.useEffect)(() => {
|
|
566
1190
|
async function fetchUser(acc_token) {
|
|
@@ -575,10 +1199,13 @@ function BasicProvider({ children, project_id, schema, debug = false }) {
|
|
|
575
1199
|
log("error fetching user", user2.error);
|
|
576
1200
|
return;
|
|
577
1201
|
} else {
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
window.history.pushState({}, document.title, "/");
|
|
1202
|
+
if (token?.refresh_token) {
|
|
1203
|
+
await storageAdapter.set(STORAGE_KEYS.REFRESH_TOKEN, token.refresh_token);
|
|
581
1204
|
}
|
|
1205
|
+
await storageAdapter.set(STORAGE_KEYS.USER_INFO, JSON.stringify(user2));
|
|
1206
|
+
log("Cached user info in storage");
|
|
1207
|
+
setCookie("basic_access_token", token?.access_token || "", { httpOnly: false });
|
|
1208
|
+
setCookie("basic_token", JSON.stringify(token));
|
|
582
1209
|
setUser(user2);
|
|
583
1210
|
setIsSignedIn(true);
|
|
584
1211
|
setIsAuthReady(true);
|
|
@@ -594,56 +1221,122 @@ function BasicProvider({ children, project_id, schema, debug = false }) {
|
|
|
594
1221
|
const isExpired = decoded.exp && decoded.exp < Date.now() / 1e3;
|
|
595
1222
|
if (isExpired) {
|
|
596
1223
|
log("token is expired - refreshing ...");
|
|
597
|
-
|
|
598
|
-
|
|
1224
|
+
try {
|
|
1225
|
+
const newToken = await fetchToken(token?.refresh_token || "");
|
|
1226
|
+
fetchUser(newToken?.access_token || "");
|
|
1227
|
+
} catch (error2) {
|
|
1228
|
+
log("Failed to refresh token in checkToken:", error2);
|
|
1229
|
+
if (error2.message.includes("offline") || error2.message.includes("Network")) {
|
|
1230
|
+
log("Network issue - continuing with expired token until online");
|
|
1231
|
+
fetchUser(token?.access_token || "");
|
|
1232
|
+
} else {
|
|
1233
|
+
setIsAuthReady(true);
|
|
1234
|
+
}
|
|
1235
|
+
}
|
|
599
1236
|
} else {
|
|
600
|
-
fetchUser(token
|
|
1237
|
+
fetchUser(token?.access_token || "");
|
|
601
1238
|
}
|
|
602
1239
|
}
|
|
603
1240
|
if (token) {
|
|
604
1241
|
checkToken();
|
|
605
1242
|
}
|
|
606
1243
|
}, [token]);
|
|
607
|
-
const
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
1244
|
+
const getSignInLink = async (redirectUri) => {
|
|
1245
|
+
try {
|
|
1246
|
+
log("getting sign in link...");
|
|
1247
|
+
if (!project_id) {
|
|
1248
|
+
throw new Error("Project ID is required to generate sign-in link");
|
|
1249
|
+
}
|
|
1250
|
+
const randomState = Math.random().toString(36).substring(6);
|
|
1251
|
+
await storageAdapter.set(STORAGE_KEYS.AUTH_STATE, randomState);
|
|
1252
|
+
const redirectUrl = redirectUri || window.location.href;
|
|
1253
|
+
if (!redirectUrl || !redirectUrl.startsWith("http://") && !redirectUrl.startsWith("https://")) {
|
|
1254
|
+
throw new Error("Invalid redirect URI provided");
|
|
1255
|
+
}
|
|
1256
|
+
let baseUrl = "https://api.basic.tech/auth/authorize";
|
|
1257
|
+
baseUrl += `?client_id=${project_id}`;
|
|
1258
|
+
baseUrl += `&redirect_uri=${encodeURIComponent(redirectUrl)}`;
|
|
1259
|
+
baseUrl += `&response_type=code`;
|
|
1260
|
+
baseUrl += `&scope=profile`;
|
|
1261
|
+
baseUrl += `&state=${randomState}`;
|
|
1262
|
+
log("Generated sign-in link successfully");
|
|
1263
|
+
return baseUrl;
|
|
1264
|
+
} catch (error2) {
|
|
1265
|
+
log("Error generating sign-in link:", error2);
|
|
1266
|
+
throw error2;
|
|
612
1267
|
}
|
|
613
|
-
log("connecting to db...");
|
|
614
|
-
syncRef.current.connect({ access_token: tok }).catch((e) => {
|
|
615
|
-
log("error connecting to db", e);
|
|
616
|
-
});
|
|
617
1268
|
};
|
|
618
|
-
const
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
1269
|
+
const signin = async () => {
|
|
1270
|
+
try {
|
|
1271
|
+
log("signing in...");
|
|
1272
|
+
if (!project_id) {
|
|
1273
|
+
log("Error: project_id is required for sign-in");
|
|
1274
|
+
throw new Error("Project ID is required for authentication");
|
|
1275
|
+
}
|
|
1276
|
+
const signInLink = await getSignInLink();
|
|
1277
|
+
log("Generated sign-in link:", signInLink);
|
|
1278
|
+
if (!signInLink || !signInLink.startsWith("https://")) {
|
|
1279
|
+
log("Error: Invalid sign-in link generated");
|
|
1280
|
+
throw new Error("Failed to generate valid sign-in URL");
|
|
1281
|
+
}
|
|
1282
|
+
window.location.href = signInLink;
|
|
1283
|
+
} catch (error2) {
|
|
1284
|
+
log("Error during sign-in:", error2);
|
|
1285
|
+
if (isDevMode()) {
|
|
1286
|
+
setError({
|
|
1287
|
+
code: "signin_error",
|
|
1288
|
+
title: "Sign-in Failed",
|
|
1289
|
+
message: error2.message || "An error occurred during sign-in. Please try again."
|
|
1290
|
+
});
|
|
1291
|
+
}
|
|
1292
|
+
throw error2;
|
|
1293
|
+
}
|
|
629
1294
|
};
|
|
630
|
-
const
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
1295
|
+
const signinWithCode = async (code, state) => {
|
|
1296
|
+
try {
|
|
1297
|
+
log("signinWithCode called with code:", code);
|
|
1298
|
+
if (!code || typeof code !== "string") {
|
|
1299
|
+
return { success: false, error: "Invalid authorization code" };
|
|
1300
|
+
}
|
|
1301
|
+
if (state) {
|
|
1302
|
+
const storedState = await storageAdapter.get(STORAGE_KEYS.AUTH_STATE);
|
|
1303
|
+
if (storedState && storedState !== state) {
|
|
1304
|
+
log("State parameter mismatch:", { provided: state, stored: storedState });
|
|
1305
|
+
return { success: false, error: "State parameter mismatch" };
|
|
1306
|
+
}
|
|
1307
|
+
}
|
|
1308
|
+
await storageAdapter.remove(STORAGE_KEYS.AUTH_STATE);
|
|
1309
|
+
cleanOAuthParams();
|
|
1310
|
+
const token2 = await fetchToken(code);
|
|
1311
|
+
if (token2) {
|
|
1312
|
+
log("signinWithCode successful");
|
|
1313
|
+
return { success: true };
|
|
1314
|
+
} else {
|
|
1315
|
+
return { success: false, error: "Failed to exchange code for token" };
|
|
1316
|
+
}
|
|
1317
|
+
} catch (error2) {
|
|
1318
|
+
log("signinWithCode error:", error2);
|
|
1319
|
+
return {
|
|
1320
|
+
success: false,
|
|
1321
|
+
error: error2.message || "Authentication failed"
|
|
1322
|
+
};
|
|
1323
|
+
}
|
|
634
1324
|
};
|
|
635
|
-
const signout = () => {
|
|
1325
|
+
const signout = async () => {
|
|
636
1326
|
log("signing out!");
|
|
637
1327
|
setUser({});
|
|
638
1328
|
setIsSignedIn(false);
|
|
639
1329
|
setToken(null);
|
|
640
|
-
|
|
641
|
-
|
|
1330
|
+
clearCookie("basic_token");
|
|
1331
|
+
clearCookie("basic_access_token");
|
|
1332
|
+
await storageAdapter.remove(STORAGE_KEYS.AUTH_STATE);
|
|
1333
|
+
await storageAdapter.remove(STORAGE_KEYS.REFRESH_TOKEN);
|
|
1334
|
+
await storageAdapter.remove(STORAGE_KEYS.USER_INFO);
|
|
642
1335
|
if (syncRef.current) {
|
|
643
1336
|
(async () => {
|
|
644
1337
|
try {
|
|
645
|
-
await syncRef.current
|
|
646
|
-
await syncRef.current
|
|
1338
|
+
await syncRef.current?.close();
|
|
1339
|
+
await syncRef.current?.delete({ disableAutoOpen: false });
|
|
647
1340
|
syncRef.current = null;
|
|
648
1341
|
window?.location?.reload();
|
|
649
1342
|
} catch (error2) {
|
|
@@ -655,6 +1348,27 @@ function BasicProvider({ children, project_id, schema, debug = false }) {
|
|
|
655
1348
|
const getToken = async () => {
|
|
656
1349
|
log("getting token...");
|
|
657
1350
|
if (!token) {
|
|
1351
|
+
const refreshToken = await storageAdapter.get(STORAGE_KEYS.REFRESH_TOKEN);
|
|
1352
|
+
if (refreshToken) {
|
|
1353
|
+
log("No token in memory, attempting to refresh from storage");
|
|
1354
|
+
try {
|
|
1355
|
+
const newToken = await fetchToken(refreshToken);
|
|
1356
|
+
if (newToken?.access_token) {
|
|
1357
|
+
return newToken.access_token;
|
|
1358
|
+
}
|
|
1359
|
+
} catch (error2) {
|
|
1360
|
+
log("Failed to refresh token from storage:", error2);
|
|
1361
|
+
if (error2.message.includes("offline") || error2.message.includes("Network")) {
|
|
1362
|
+
log("Network issue - continuing with potentially expired token");
|
|
1363
|
+
const lastToken = localStorage.getItem("basic_access_token");
|
|
1364
|
+
if (lastToken) {
|
|
1365
|
+
return lastToken;
|
|
1366
|
+
}
|
|
1367
|
+
throw new Error("Network offline - authentication will be retried when online");
|
|
1368
|
+
}
|
|
1369
|
+
throw new Error("Authentication expired. Please sign in again.");
|
|
1370
|
+
}
|
|
1371
|
+
}
|
|
658
1372
|
log("no token found");
|
|
659
1373
|
throw new Error("no token found");
|
|
660
1374
|
}
|
|
@@ -662,69 +1376,86 @@ function BasicProvider({ children, project_id, schema, debug = false }) {
|
|
|
662
1376
|
const isExpired = decoded.exp && decoded.exp < Date.now() / 1e3;
|
|
663
1377
|
if (isExpired) {
|
|
664
1378
|
log("token is expired - refreshing ...");
|
|
665
|
-
const
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
|
|
678
|
-
break;
|
|
1379
|
+
const refreshToken = token?.refresh_token || await storageAdapter.get(STORAGE_KEYS.REFRESH_TOKEN);
|
|
1380
|
+
if (refreshToken) {
|
|
1381
|
+
try {
|
|
1382
|
+
const newToken = await fetchToken(refreshToken);
|
|
1383
|
+
return newToken?.access_token || "";
|
|
1384
|
+
} catch (error2) {
|
|
1385
|
+
log("Failed to refresh expired token:", error2);
|
|
1386
|
+
if (error2.message.includes("offline") || error2.message.includes("Network")) {
|
|
1387
|
+
log("Network issue - using expired token until network is restored");
|
|
1388
|
+
return token.access_token;
|
|
1389
|
+
}
|
|
1390
|
+
throw new Error("Authentication expired. Please sign in again.");
|
|
679
1391
|
}
|
|
1392
|
+
} else {
|
|
1393
|
+
throw new Error("no refresh token available");
|
|
680
1394
|
}
|
|
681
1395
|
}
|
|
682
|
-
return
|
|
683
|
-
}
|
|
684
|
-
const fetchToken = async (code) => {
|
|
685
|
-
const token2 = await fetch("https://api.basic.tech/auth/token", {
|
|
686
|
-
method: "POST",
|
|
687
|
-
headers: {
|
|
688
|
-
"Content-Type": "application/json"
|
|
689
|
-
},
|
|
690
|
-
body: JSON.stringify({ code })
|
|
691
|
-
}).then((response) => response.json()).catch((error2) => log("Error:", error2));
|
|
692
|
-
if (token2.error) {
|
|
693
|
-
log("error fetching token", token2.error);
|
|
694
|
-
return;
|
|
695
|
-
} else {
|
|
696
|
-
setToken(token2);
|
|
697
|
-
}
|
|
698
|
-
return token2;
|
|
1396
|
+
return token?.access_token || "";
|
|
699
1397
|
};
|
|
700
|
-
const
|
|
701
|
-
|
|
702
|
-
if (!
|
|
703
|
-
|
|
1398
|
+
const fetchToken = async (code) => {
|
|
1399
|
+
try {
|
|
1400
|
+
if (!isOnline) {
|
|
1401
|
+
log("Network is offline, marking refresh as pending");
|
|
1402
|
+
setPendingRefresh(true);
|
|
1403
|
+
throw new Error("Network offline - refresh will be retried when online");
|
|
704
1404
|
}
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
}
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
1405
|
+
const token2 = await fetch("https://api.basic.tech/auth/token", {
|
|
1406
|
+
method: "POST",
|
|
1407
|
+
headers: {
|
|
1408
|
+
"Content-Type": "application/json"
|
|
1409
|
+
},
|
|
1410
|
+
body: JSON.stringify({ code })
|
|
1411
|
+
}).then((response) => response.json()).catch((error2) => {
|
|
1412
|
+
log("Network error fetching token:", error2);
|
|
1413
|
+
if (!isOnline) {
|
|
1414
|
+
setPendingRefresh(true);
|
|
1415
|
+
throw new Error("Network offline - refresh will be retried when online");
|
|
1416
|
+
}
|
|
1417
|
+
throw new Error("Network error during token refresh");
|
|
1418
|
+
});
|
|
1419
|
+
if (token2.error) {
|
|
1420
|
+
log("error fetching token", token2.error);
|
|
1421
|
+
if (token2.error.includes("network") || token2.error.includes("timeout")) {
|
|
1422
|
+
setPendingRefresh(true);
|
|
1423
|
+
throw new Error("Network issue - refresh will be retried when online");
|
|
1424
|
+
}
|
|
1425
|
+
await storageAdapter.remove(STORAGE_KEYS.REFRESH_TOKEN);
|
|
1426
|
+
await storageAdapter.remove(STORAGE_KEYS.USER_INFO);
|
|
1427
|
+
clearCookie("basic_token");
|
|
1428
|
+
clearCookie("basic_access_token");
|
|
1429
|
+
setUser({});
|
|
1430
|
+
setIsSignedIn(false);
|
|
1431
|
+
setToken(null);
|
|
1432
|
+
setIsAuthReady(true);
|
|
1433
|
+
throw new Error(`Token refresh failed: ${token2.error}`);
|
|
1434
|
+
} else {
|
|
1435
|
+
setToken(token2);
|
|
1436
|
+
setPendingRefresh(false);
|
|
1437
|
+
if (token2.refresh_token) {
|
|
1438
|
+
await storageAdapter.set(STORAGE_KEYS.REFRESH_TOKEN, token2.refresh_token);
|
|
1439
|
+
log("Updated refresh token in storage");
|
|
1440
|
+
}
|
|
1441
|
+
setCookie("basic_access_token", token2.access_token, { httpOnly: false });
|
|
1442
|
+
log("Updated access token in cookie");
|
|
726
1443
|
}
|
|
727
|
-
|
|
1444
|
+
return token2;
|
|
1445
|
+
} catch (error2) {
|
|
1446
|
+
log("Token refresh error:", error2);
|
|
1447
|
+
if (!error2.message.includes("offline") && !error2.message.includes("Network")) {
|
|
1448
|
+
await storageAdapter.remove(STORAGE_KEYS.REFRESH_TOKEN);
|
|
1449
|
+
await storageAdapter.remove(STORAGE_KEYS.USER_INFO);
|
|
1450
|
+
clearCookie("basic_token");
|
|
1451
|
+
clearCookie("basic_access_token");
|
|
1452
|
+
setUser({});
|
|
1453
|
+
setIsSignedIn(false);
|
|
1454
|
+
setToken(null);
|
|
1455
|
+
setIsAuthReady(true);
|
|
1456
|
+
}
|
|
1457
|
+
throw error2;
|
|
1458
|
+
}
|
|
728
1459
|
};
|
|
729
1460
|
const noDb = {
|
|
730
1461
|
collection: () => {
|
|
@@ -738,12 +1469,14 @@ function BasicProvider({ children, project_id, schema, debug = false }) {
|
|
|
738
1469
|
user,
|
|
739
1470
|
signout,
|
|
740
1471
|
signin,
|
|
1472
|
+
signinWithCode,
|
|
741
1473
|
getToken,
|
|
742
1474
|
getSignInLink,
|
|
743
1475
|
db: syncRef.current ? syncRef.current : noDb,
|
|
1476
|
+
remoteDb: remoteDbRef.current ? remoteDbRef.current : noDb,
|
|
744
1477
|
dbStatus
|
|
745
1478
|
}, children: [
|
|
746
|
-
error && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ErrorDisplay, { error }),
|
|
1479
|
+
error && isDevMode() && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ErrorDisplay, { error }),
|
|
747
1480
|
isReady && children
|
|
748
1481
|
] });
|
|
749
1482
|
}
|
|
@@ -778,6 +1511,7 @@ function useBasic() {
|
|
|
778
1511
|
var import_dexie_react_hooks = require("dexie-react-hooks");
|
|
779
1512
|
// Annotate the CommonJS export names for ESM import in node:
|
|
780
1513
|
0 && (module.exports = {
|
|
1514
|
+
BasicDBSDK,
|
|
781
1515
|
BasicProvider,
|
|
782
1516
|
useBasic,
|
|
783
1517
|
useQuery
|