@basictech/react 0.7.0-beta.0 → 0.7.0-beta.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/changelog.md +6 -5
- package/dist/index.d.mts +14 -4
- package/dist/index.d.ts +14 -4
- package/dist/index.js +450 -79
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +440 -79
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
- package/readme.md +127 -1
- package/src/AuthContext.tsx +424 -120
- package/src/index.ts +2 -2
- package/src/schema.ts +159 -0
- package/src/sync/index.ts +1 -1
package/dist/index.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
2
3
|
var __defProp = Object.defineProperty;
|
|
3
4
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
5
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
5
7
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
8
|
var __export = (target, all) => {
|
|
7
9
|
for (var name in all)
|
|
@@ -15,6 +17,14 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
15
17
|
}
|
|
16
18
|
return to;
|
|
17
19
|
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
18
28
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
29
|
|
|
20
30
|
// src/index.ts
|
|
@@ -155,8 +165,116 @@ var syncProtocol = function() {
|
|
|
155
165
|
});
|
|
156
166
|
};
|
|
157
167
|
|
|
168
|
+
// src/schema.ts
|
|
169
|
+
var import_ajv = __toESM(require("ajv"));
|
|
170
|
+
var basicJsonSchema = {
|
|
171
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
172
|
+
"type": "object",
|
|
173
|
+
"properties": {
|
|
174
|
+
"project_id": {
|
|
175
|
+
"type": "string"
|
|
176
|
+
},
|
|
177
|
+
"namespace": {
|
|
178
|
+
"type": "string"
|
|
179
|
+
},
|
|
180
|
+
"version": {
|
|
181
|
+
"type": "integer",
|
|
182
|
+
"minimum": 0
|
|
183
|
+
},
|
|
184
|
+
"tables": {
|
|
185
|
+
"type": "object",
|
|
186
|
+
"patternProperties": {
|
|
187
|
+
"^[a-zA-Z0-9_]+$": {
|
|
188
|
+
"type": "object",
|
|
189
|
+
"properties": {
|
|
190
|
+
"name": {
|
|
191
|
+
"type": "string"
|
|
192
|
+
},
|
|
193
|
+
"type": {
|
|
194
|
+
"type": "string",
|
|
195
|
+
"enum": ["collection"]
|
|
196
|
+
},
|
|
197
|
+
"fields": {
|
|
198
|
+
"type": "object",
|
|
199
|
+
"patternProperties": {
|
|
200
|
+
"^[a-zA-Z0-9_]+$": {
|
|
201
|
+
"type": "object",
|
|
202
|
+
"properties": {
|
|
203
|
+
"type": {
|
|
204
|
+
"type": "string",
|
|
205
|
+
"enum": ["string", "boolean", "number", "json"]
|
|
206
|
+
},
|
|
207
|
+
"indexed": {
|
|
208
|
+
"type": "boolean"
|
|
209
|
+
},
|
|
210
|
+
"required": {
|
|
211
|
+
"type": "boolean"
|
|
212
|
+
}
|
|
213
|
+
},
|
|
214
|
+
"required": ["type"]
|
|
215
|
+
}
|
|
216
|
+
},
|
|
217
|
+
"additionalProperties": true
|
|
218
|
+
}
|
|
219
|
+
},
|
|
220
|
+
"required": ["fields"]
|
|
221
|
+
}
|
|
222
|
+
},
|
|
223
|
+
"additionalProperties": true
|
|
224
|
+
}
|
|
225
|
+
},
|
|
226
|
+
"required": ["project_id", "version", "tables"]
|
|
227
|
+
};
|
|
228
|
+
var ajv = new import_ajv.default();
|
|
229
|
+
var validator = ajv.compile(basicJsonSchema);
|
|
230
|
+
function validateSchema(schema) {
|
|
231
|
+
const v = validator(schema);
|
|
232
|
+
return {
|
|
233
|
+
valid: v,
|
|
234
|
+
errors: validator.errors || []
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
function validateData(schema, table, data, checkRequired = true) {
|
|
238
|
+
const valid = validateSchema(schema);
|
|
239
|
+
if (!valid.valid) {
|
|
240
|
+
return { valid: false, errors: valid.errors, message: "Schema is invalid" };
|
|
241
|
+
}
|
|
242
|
+
const tableSchema = schema.tables[table];
|
|
243
|
+
if (!tableSchema) {
|
|
244
|
+
return { valid: false, errors: [{ message: `Table ${table} not found in schema` }], message: "Table not found" };
|
|
245
|
+
}
|
|
246
|
+
for (const [fieldName, fieldValue] of Object.entries(data)) {
|
|
247
|
+
const fieldSchema = tableSchema.fields[fieldName];
|
|
248
|
+
if (!fieldSchema) {
|
|
249
|
+
return {
|
|
250
|
+
valid: false,
|
|
251
|
+
errors: [{ message: `Field ${fieldName} not found in schema` }],
|
|
252
|
+
message: "Invalid field"
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
const schemaType = fieldSchema.type;
|
|
256
|
+
const valueType = typeof fieldValue;
|
|
257
|
+
if (schemaType === "string" && valueType !== "string" || schemaType === "number" && valueType !== "number" || schemaType === "boolean" && valueType !== "boolean" || schemaType === "json" && valueType !== "object") {
|
|
258
|
+
return {
|
|
259
|
+
valid: false,
|
|
260
|
+
errors: [{
|
|
261
|
+
message: `Field ${fieldName} should be type ${schemaType}, got ${valueType}`
|
|
262
|
+
}],
|
|
263
|
+
message: "invalid type"
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
if (checkRequired) {
|
|
268
|
+
for (const [fieldName, fieldSchema] of Object.entries(tableSchema.fields)) {
|
|
269
|
+
if (fieldSchema.required && !data[fieldName]) {
|
|
270
|
+
return { valid: false, errors: [{ message: `Field ${fieldName} is required` }], message: "Required field missing" };
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
return { valid: true, errors: [] };
|
|
275
|
+
}
|
|
276
|
+
|
|
158
277
|
// src/sync/index.ts
|
|
159
|
-
var import_schema = require("@basictech/schema");
|
|
160
278
|
syncProtocol();
|
|
161
279
|
var BasicSync = class extends import_dexie2.Dexie {
|
|
162
280
|
basic_schema;
|
|
@@ -231,7 +349,7 @@ var BasicSync = class extends import_dexie2.Dexie {
|
|
|
231
349
|
ref: this.table(name),
|
|
232
350
|
// --- WRITE ---- //
|
|
233
351
|
add: (data) => {
|
|
234
|
-
const valid =
|
|
352
|
+
const valid = validateData(this.basic_schema, name, data);
|
|
235
353
|
if (!valid.valid) {
|
|
236
354
|
log("Invalid data", valid);
|
|
237
355
|
return Promise.reject({ ...valid });
|
|
@@ -242,7 +360,7 @@ var BasicSync = class extends import_dexie2.Dexie {
|
|
|
242
360
|
});
|
|
243
361
|
},
|
|
244
362
|
put: (data) => {
|
|
245
|
-
const valid =
|
|
363
|
+
const valid = validateData(this.basic_schema, name, data);
|
|
246
364
|
if (!valid.valid) {
|
|
247
365
|
log("Invalid data", valid);
|
|
248
366
|
return Promise.reject({ ...valid });
|
|
@@ -253,7 +371,7 @@ var BasicSync = class extends import_dexie2.Dexie {
|
|
|
253
371
|
});
|
|
254
372
|
},
|
|
255
373
|
update: (id, data) => {
|
|
256
|
-
const valid =
|
|
374
|
+
const valid = validateData(this.basic_schema, name, data, false);
|
|
257
375
|
if (!valid.valid) {
|
|
258
376
|
log("Invalid data", valid);
|
|
259
377
|
return Promise.reject({ ...valid });
|
|
@@ -332,18 +450,29 @@ var version = "0.6.0";
|
|
|
332
450
|
|
|
333
451
|
// src/AuthContext.tsx
|
|
334
452
|
var import_jsx_runtime = require("react/jsx-runtime");
|
|
453
|
+
var LocalStorageAdapter = class {
|
|
454
|
+
async get(key) {
|
|
455
|
+
return localStorage.getItem(key);
|
|
456
|
+
}
|
|
457
|
+
async set(key, value) {
|
|
458
|
+
localStorage.setItem(key, value);
|
|
459
|
+
}
|
|
460
|
+
async remove(key) {
|
|
461
|
+
localStorage.removeItem(key);
|
|
462
|
+
}
|
|
463
|
+
};
|
|
335
464
|
var BasicContext = (0, import_react.createContext)({
|
|
336
465
|
unicorn: "\u{1F984}",
|
|
337
466
|
isAuthReady: false,
|
|
338
467
|
isSignedIn: false,
|
|
339
468
|
user: null,
|
|
340
|
-
signout: () =>
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
},
|
|
469
|
+
signout: () => Promise.resolve(),
|
|
470
|
+
signin: () => Promise.resolve(),
|
|
471
|
+
signinWithCode: () => new Promise(() => {
|
|
472
|
+
}),
|
|
344
473
|
getToken: () => new Promise(() => {
|
|
345
474
|
}),
|
|
346
|
-
getSignInLink: () => "",
|
|
475
|
+
getSignInLink: () => Promise.resolve(""),
|
|
347
476
|
db: {},
|
|
348
477
|
dbStatus: "LOADING" /* LOADING */
|
|
349
478
|
});
|
|
@@ -460,7 +589,13 @@ run "npm install @basictech/react@${latestVersion}" to update`);
|
|
|
460
589
|
};
|
|
461
590
|
}
|
|
462
591
|
}
|
|
463
|
-
function BasicProvider({
|
|
592
|
+
function BasicProvider({
|
|
593
|
+
children,
|
|
594
|
+
project_id,
|
|
595
|
+
schema,
|
|
596
|
+
debug = false,
|
|
597
|
+
storage
|
|
598
|
+
}) {
|
|
464
599
|
const [isAuthReady, setIsAuthReady] = (0, import_react.useState)(false);
|
|
465
600
|
const [isSignedIn, setIsSignedIn] = (0, import_react.useState)(false);
|
|
466
601
|
const [token, setToken] = (0, import_react.useState)(null);
|
|
@@ -469,7 +604,56 @@ function BasicProvider({ children, project_id, schema, debug = false }) {
|
|
|
469
604
|
const [isReady, setIsReady] = (0, import_react.useState)(false);
|
|
470
605
|
const [dbStatus, setDbStatus] = (0, import_react.useState)("OFFLINE" /* OFFLINE */);
|
|
471
606
|
const [error, setError] = (0, import_react.useState)(null);
|
|
607
|
+
const [isOnline, setIsOnline] = (0, import_react.useState)(navigator.onLine);
|
|
608
|
+
const [pendingRefresh, setPendingRefresh] = (0, import_react.useState)(false);
|
|
472
609
|
const syncRef = (0, import_react.useRef)(null);
|
|
610
|
+
const storageAdapter = storage || new LocalStorageAdapter();
|
|
611
|
+
const STORAGE_KEYS = {
|
|
612
|
+
REFRESH_TOKEN: "basic_refresh_token",
|
|
613
|
+
USER_INFO: "basic_user_info",
|
|
614
|
+
AUTH_STATE: "basic_auth_state",
|
|
615
|
+
DEBUG: "basic_debug"
|
|
616
|
+
};
|
|
617
|
+
const isDevelopment = () => {
|
|
618
|
+
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;
|
|
619
|
+
};
|
|
620
|
+
const cleanOAuthParamsFromUrl = () => {
|
|
621
|
+
if (window.location.search.includes("code") || window.location.search.includes("state")) {
|
|
622
|
+
const url = new URL(window.location.href);
|
|
623
|
+
url.searchParams.delete("code");
|
|
624
|
+
url.searchParams.delete("state");
|
|
625
|
+
window.history.pushState({}, document.title, url.pathname + url.search);
|
|
626
|
+
log("Cleaned OAuth parameters from URL");
|
|
627
|
+
}
|
|
628
|
+
};
|
|
629
|
+
(0, import_react.useEffect)(() => {
|
|
630
|
+
const handleOnline = () => {
|
|
631
|
+
log("Network came back online");
|
|
632
|
+
setIsOnline(true);
|
|
633
|
+
if (pendingRefresh) {
|
|
634
|
+
log("Retrying pending token refresh");
|
|
635
|
+
setPendingRefresh(false);
|
|
636
|
+
if (token) {
|
|
637
|
+
const refreshToken = token.refresh_token || localStorage.getItem("basic_refresh_token");
|
|
638
|
+
if (refreshToken) {
|
|
639
|
+
fetchToken(refreshToken).catch((error2) => {
|
|
640
|
+
log("Retry refresh failed:", error2);
|
|
641
|
+
});
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
};
|
|
646
|
+
const handleOffline = () => {
|
|
647
|
+
log("Network went offline");
|
|
648
|
+
setIsOnline(false);
|
|
649
|
+
};
|
|
650
|
+
window.addEventListener("online", handleOnline);
|
|
651
|
+
window.addEventListener("offline", handleOffline);
|
|
652
|
+
return () => {
|
|
653
|
+
window.removeEventListener("online", handleOnline);
|
|
654
|
+
window.removeEventListener("offline", handleOffline);
|
|
655
|
+
};
|
|
656
|
+
}, [pendingRefresh, token]);
|
|
473
657
|
(0, import_react.useEffect)(() => {
|
|
474
658
|
function initDb(options) {
|
|
475
659
|
if (!syncRef.current) {
|
|
@@ -535,32 +719,68 @@ function BasicProvider({ children, project_id, schema, debug = false }) {
|
|
|
535
719
|
connectToDb();
|
|
536
720
|
}
|
|
537
721
|
}, [isSignedIn, shouldConnect]);
|
|
722
|
+
const connectToDb = async () => {
|
|
723
|
+
const tok = await getToken();
|
|
724
|
+
if (!tok) {
|
|
725
|
+
log("no token found");
|
|
726
|
+
return;
|
|
727
|
+
}
|
|
728
|
+
log("connecting to db...");
|
|
729
|
+
syncRef.current.connect({ access_token: tok }).catch((e) => {
|
|
730
|
+
log("error connecting to db", e);
|
|
731
|
+
});
|
|
732
|
+
};
|
|
538
733
|
(0, import_react.useEffect)(() => {
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
if (cookie_token !== "") {
|
|
556
|
-
setToken(JSON.parse(cookie_token));
|
|
734
|
+
const initializeAuth = async () => {
|
|
735
|
+
await storageAdapter.set(STORAGE_KEYS.DEBUG, debug ? "true" : "false");
|
|
736
|
+
try {
|
|
737
|
+
if (window.location.search.includes("code")) {
|
|
738
|
+
let code = window.location?.search?.split("code=")[1].split("&")[0];
|
|
739
|
+
const state = await storageAdapter.get(STORAGE_KEYS.AUTH_STATE);
|
|
740
|
+
if (!state || state !== window.location.search.split("state=")[1].split("&")[0]) {
|
|
741
|
+
log("error: auth state does not match");
|
|
742
|
+
setIsAuthReady(true);
|
|
743
|
+
await storageAdapter.remove(STORAGE_KEYS.AUTH_STATE);
|
|
744
|
+
cleanOAuthParamsFromUrl();
|
|
745
|
+
return;
|
|
746
|
+
}
|
|
747
|
+
await storageAdapter.remove(STORAGE_KEYS.AUTH_STATE);
|
|
748
|
+
cleanOAuthParamsFromUrl();
|
|
749
|
+
fetchToken(code);
|
|
557
750
|
} else {
|
|
558
|
-
|
|
751
|
+
const refreshToken = await storageAdapter.get(STORAGE_KEYS.REFRESH_TOKEN);
|
|
752
|
+
if (refreshToken) {
|
|
753
|
+
log("Found refresh token in storage, attempting to refresh access token");
|
|
754
|
+
fetchToken(refreshToken);
|
|
755
|
+
} else {
|
|
756
|
+
let cookie_token = getCookie("basic_token");
|
|
757
|
+
if (cookie_token !== "") {
|
|
758
|
+
const tokenData = JSON.parse(cookie_token);
|
|
759
|
+
setToken(tokenData);
|
|
760
|
+
if (tokenData.refresh_token) {
|
|
761
|
+
await storageAdapter.set(STORAGE_KEYS.REFRESH_TOKEN, tokenData.refresh_token);
|
|
762
|
+
}
|
|
763
|
+
} else {
|
|
764
|
+
const cachedUserInfo = await storageAdapter.get(STORAGE_KEYS.USER_INFO);
|
|
765
|
+
if (cachedUserInfo) {
|
|
766
|
+
try {
|
|
767
|
+
const userData = JSON.parse(cachedUserInfo);
|
|
768
|
+
setUser(userData);
|
|
769
|
+
setIsSignedIn(true);
|
|
770
|
+
log("Loaded cached user info for offline mode");
|
|
771
|
+
} catch (error2) {
|
|
772
|
+
log("Error parsing cached user info:", error2);
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
setIsAuthReady(true);
|
|
776
|
+
}
|
|
777
|
+
}
|
|
559
778
|
}
|
|
779
|
+
} catch (e) {
|
|
780
|
+
log("error getting token", e);
|
|
560
781
|
}
|
|
561
|
-
}
|
|
562
|
-
|
|
563
|
-
}
|
|
782
|
+
};
|
|
783
|
+
initializeAuth();
|
|
564
784
|
}, []);
|
|
565
785
|
(0, import_react.useEffect)(() => {
|
|
566
786
|
async function fetchUser(acc_token) {
|
|
@@ -575,10 +795,13 @@ function BasicProvider({ children, project_id, schema, debug = false }) {
|
|
|
575
795
|
log("error fetching user", user2.error);
|
|
576
796
|
return;
|
|
577
797
|
} else {
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
window.history.pushState({}, document.title, "/");
|
|
798
|
+
if (token?.refresh_token) {
|
|
799
|
+
await storageAdapter.set(STORAGE_KEYS.REFRESH_TOKEN, token.refresh_token);
|
|
581
800
|
}
|
|
801
|
+
await storageAdapter.set(STORAGE_KEYS.USER_INFO, JSON.stringify(user2));
|
|
802
|
+
log("Cached user info in storage");
|
|
803
|
+
document.cookie = `basic_access_token=${token.access_token}; Secure; SameSite=Strict; HttpOnly=false`;
|
|
804
|
+
document.cookie = `basic_token=${JSON.stringify(token)}; Secure; SameSite=Strict`;
|
|
582
805
|
setUser(user2);
|
|
583
806
|
setIsSignedIn(true);
|
|
584
807
|
setIsAuthReady(true);
|
|
@@ -594,8 +817,18 @@ function BasicProvider({ children, project_id, schema, debug = false }) {
|
|
|
594
817
|
const isExpired = decoded.exp && decoded.exp < Date.now() / 1e3;
|
|
595
818
|
if (isExpired) {
|
|
596
819
|
log("token is expired - refreshing ...");
|
|
597
|
-
|
|
598
|
-
|
|
820
|
+
try {
|
|
821
|
+
const newToken = await fetchToken(token?.refresh_token);
|
|
822
|
+
fetchUser(newToken.access_token);
|
|
823
|
+
} catch (error2) {
|
|
824
|
+
log("Failed to refresh token in checkToken:", error2);
|
|
825
|
+
if (error2.message.includes("offline") || error2.message.includes("Network")) {
|
|
826
|
+
log("Network issue - continuing with expired token until online");
|
|
827
|
+
fetchUser(token.access_token);
|
|
828
|
+
} else {
|
|
829
|
+
setIsAuthReady(true);
|
|
830
|
+
}
|
|
831
|
+
}
|
|
599
832
|
} else {
|
|
600
833
|
fetchUser(token.access_token);
|
|
601
834
|
}
|
|
@@ -604,41 +837,97 @@ function BasicProvider({ children, project_id, schema, debug = false }) {
|
|
|
604
837
|
checkToken();
|
|
605
838
|
}
|
|
606
839
|
}, [token]);
|
|
607
|
-
const
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
840
|
+
const getSignInLink = async (redirectUri) => {
|
|
841
|
+
try {
|
|
842
|
+
log("getting sign in link...");
|
|
843
|
+
if (!project_id) {
|
|
844
|
+
throw new Error("Project ID is required to generate sign-in link");
|
|
845
|
+
}
|
|
846
|
+
const randomState = Math.random().toString(36).substring(6);
|
|
847
|
+
await storageAdapter.set(STORAGE_KEYS.AUTH_STATE, randomState);
|
|
848
|
+
const redirectUrl = redirectUri || window.location.href;
|
|
849
|
+
if (!redirectUrl || !redirectUrl.startsWith("http://") && !redirectUrl.startsWith("https://")) {
|
|
850
|
+
throw new Error("Invalid redirect URI provided");
|
|
851
|
+
}
|
|
852
|
+
let baseUrl2 = "https://api.basic.tech/auth/authorize";
|
|
853
|
+
baseUrl2 += `?client_id=${project_id}`;
|
|
854
|
+
baseUrl2 += `&redirect_uri=${encodeURIComponent(redirectUrl)}`;
|
|
855
|
+
baseUrl2 += `&response_type=code`;
|
|
856
|
+
baseUrl2 += `&scope=profile`;
|
|
857
|
+
baseUrl2 += `&state=${randomState}`;
|
|
858
|
+
log("Generated sign-in link successfully");
|
|
859
|
+
return baseUrl2;
|
|
860
|
+
} catch (error2) {
|
|
861
|
+
log("Error generating sign-in link:", error2);
|
|
862
|
+
throw error2;
|
|
612
863
|
}
|
|
613
|
-
log("connecting to db...");
|
|
614
|
-
syncRef.current.connect({ access_token: tok }).catch((e) => {
|
|
615
|
-
log("error connecting to db", e);
|
|
616
|
-
});
|
|
617
864
|
};
|
|
618
|
-
const
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
865
|
+
const signin = async () => {
|
|
866
|
+
try {
|
|
867
|
+
log("signing in...");
|
|
868
|
+
if (!project_id) {
|
|
869
|
+
log("Error: project_id is required for sign-in");
|
|
870
|
+
throw new Error("Project ID is required for authentication");
|
|
871
|
+
}
|
|
872
|
+
const signInLink = await getSignInLink();
|
|
873
|
+
log("Generated sign-in link:", signInLink);
|
|
874
|
+
if (!signInLink || !signInLink.startsWith("https://")) {
|
|
875
|
+
log("Error: Invalid sign-in link generated");
|
|
876
|
+
throw new Error("Failed to generate valid sign-in URL");
|
|
877
|
+
}
|
|
878
|
+
window.location.href = signInLink;
|
|
879
|
+
} catch (error2) {
|
|
880
|
+
log("Error during sign-in:", error2);
|
|
881
|
+
if (isDevelopment()) {
|
|
882
|
+
setError({
|
|
883
|
+
code: "signin_error",
|
|
884
|
+
title: "Sign-in Failed",
|
|
885
|
+
message: error2.message || "An error occurred during sign-in. Please try again."
|
|
886
|
+
});
|
|
887
|
+
}
|
|
888
|
+
throw error2;
|
|
889
|
+
}
|
|
629
890
|
};
|
|
630
|
-
const
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
891
|
+
const signinWithCode = async (code, state) => {
|
|
892
|
+
try {
|
|
893
|
+
log("signinWithCode called with code:", code);
|
|
894
|
+
if (!code || typeof code !== "string") {
|
|
895
|
+
return { success: false, error: "Invalid authorization code" };
|
|
896
|
+
}
|
|
897
|
+
if (state) {
|
|
898
|
+
const storedState = await storageAdapter.get(STORAGE_KEYS.AUTH_STATE);
|
|
899
|
+
if (storedState && storedState !== state) {
|
|
900
|
+
log("State parameter mismatch:", { provided: state, stored: storedState });
|
|
901
|
+
return { success: false, error: "State parameter mismatch" };
|
|
902
|
+
}
|
|
903
|
+
}
|
|
904
|
+
await storageAdapter.remove(STORAGE_KEYS.AUTH_STATE);
|
|
905
|
+
cleanOAuthParamsFromUrl();
|
|
906
|
+
const token2 = await fetchToken(code);
|
|
907
|
+
if (token2) {
|
|
908
|
+
log("signinWithCode successful");
|
|
909
|
+
return { success: true };
|
|
910
|
+
} else {
|
|
911
|
+
return { success: false, error: "Failed to exchange code for token" };
|
|
912
|
+
}
|
|
913
|
+
} catch (error2) {
|
|
914
|
+
log("signinWithCode error:", error2);
|
|
915
|
+
return {
|
|
916
|
+
success: false,
|
|
917
|
+
error: error2.message || "Authentication failed"
|
|
918
|
+
};
|
|
919
|
+
}
|
|
634
920
|
};
|
|
635
|
-
const signout = () => {
|
|
921
|
+
const signout = async () => {
|
|
636
922
|
log("signing out!");
|
|
637
923
|
setUser({});
|
|
638
924
|
setIsSignedIn(false);
|
|
639
925
|
setToken(null);
|
|
640
926
|
document.cookie = `basic_token=; Secure; SameSite=Strict`;
|
|
641
|
-
|
|
927
|
+
document.cookie = `basic_access_token=; Secure; SameSite=Strict`;
|
|
928
|
+
await storageAdapter.remove(STORAGE_KEYS.AUTH_STATE);
|
|
929
|
+
await storageAdapter.remove(STORAGE_KEYS.REFRESH_TOKEN);
|
|
930
|
+
await storageAdapter.remove(STORAGE_KEYS.USER_INFO);
|
|
642
931
|
if (syncRef.current) {
|
|
643
932
|
(async () => {
|
|
644
933
|
try {
|
|
@@ -655,6 +944,27 @@ function BasicProvider({ children, project_id, schema, debug = false }) {
|
|
|
655
944
|
const getToken = async () => {
|
|
656
945
|
log("getting token...");
|
|
657
946
|
if (!token) {
|
|
947
|
+
const refreshToken = await storageAdapter.get(STORAGE_KEYS.REFRESH_TOKEN);
|
|
948
|
+
if (refreshToken) {
|
|
949
|
+
log("No token in memory, attempting to refresh from storage");
|
|
950
|
+
try {
|
|
951
|
+
const newToken = await fetchToken(refreshToken);
|
|
952
|
+
if (newToken?.access_token) {
|
|
953
|
+
return newToken.access_token;
|
|
954
|
+
}
|
|
955
|
+
} catch (error2) {
|
|
956
|
+
log("Failed to refresh token from storage:", error2);
|
|
957
|
+
if (error2.message.includes("offline") || error2.message.includes("Network")) {
|
|
958
|
+
log("Network issue - continuing with potentially expired token");
|
|
959
|
+
const lastToken = localStorage.getItem("basic_access_token");
|
|
960
|
+
if (lastToken) {
|
|
961
|
+
return lastToken;
|
|
962
|
+
}
|
|
963
|
+
throw new Error("Network offline - authentication will be retried when online");
|
|
964
|
+
}
|
|
965
|
+
throw new Error("Authentication expired. Please sign in again.");
|
|
966
|
+
}
|
|
967
|
+
}
|
|
658
968
|
log("no token found");
|
|
659
969
|
throw new Error("no token found");
|
|
660
970
|
}
|
|
@@ -662,8 +972,22 @@ function BasicProvider({ children, project_id, schema, debug = false }) {
|
|
|
662
972
|
const isExpired = decoded.exp && decoded.exp < Date.now() / 1e3;
|
|
663
973
|
if (isExpired) {
|
|
664
974
|
log("token is expired - refreshing ...");
|
|
665
|
-
const
|
|
666
|
-
|
|
975
|
+
const refreshToken = token?.refresh_token || await storageAdapter.get(STORAGE_KEYS.REFRESH_TOKEN);
|
|
976
|
+
if (refreshToken) {
|
|
977
|
+
try {
|
|
978
|
+
const newToken = await fetchToken(refreshToken);
|
|
979
|
+
return newToken?.access_token || "";
|
|
980
|
+
} catch (error2) {
|
|
981
|
+
log("Failed to refresh expired token:", error2);
|
|
982
|
+
if (error2.message.includes("offline") || error2.message.includes("Network")) {
|
|
983
|
+
log("Network issue - using expired token until network is restored");
|
|
984
|
+
return token.access_token;
|
|
985
|
+
}
|
|
986
|
+
throw new Error("Authentication expired. Please sign in again.");
|
|
987
|
+
}
|
|
988
|
+
} else {
|
|
989
|
+
throw new Error("no refresh token available");
|
|
990
|
+
}
|
|
667
991
|
}
|
|
668
992
|
return token?.access_token || "";
|
|
669
993
|
};
|
|
@@ -682,20 +1006,66 @@ function BasicProvider({ children, project_id, schema, debug = false }) {
|
|
|
682
1006
|
return cookieValue;
|
|
683
1007
|
}
|
|
684
1008
|
const fetchToken = async (code) => {
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
1009
|
+
try {
|
|
1010
|
+
if (!isOnline) {
|
|
1011
|
+
log("Network is offline, marking refresh as pending");
|
|
1012
|
+
setPendingRefresh(true);
|
|
1013
|
+
throw new Error("Network offline - refresh will be retried when online");
|
|
1014
|
+
}
|
|
1015
|
+
const token2 = await fetch("https://api.basic.tech/auth/token", {
|
|
1016
|
+
method: "POST",
|
|
1017
|
+
headers: {
|
|
1018
|
+
"Content-Type": "application/json"
|
|
1019
|
+
},
|
|
1020
|
+
body: JSON.stringify({ code })
|
|
1021
|
+
}).then((response) => response.json()).catch((error2) => {
|
|
1022
|
+
log("Network error fetching token:", error2);
|
|
1023
|
+
if (!isOnline) {
|
|
1024
|
+
setPendingRefresh(true);
|
|
1025
|
+
throw new Error("Network offline - refresh will be retried when online");
|
|
1026
|
+
}
|
|
1027
|
+
throw new Error("Network error during token refresh");
|
|
1028
|
+
});
|
|
1029
|
+
if (token2.error) {
|
|
1030
|
+
log("error fetching token", token2.error);
|
|
1031
|
+
if (token2.error.includes("network") || token2.error.includes("timeout")) {
|
|
1032
|
+
setPendingRefresh(true);
|
|
1033
|
+
throw new Error("Network issue - refresh will be retried when online");
|
|
1034
|
+
}
|
|
1035
|
+
await storageAdapter.remove(STORAGE_KEYS.REFRESH_TOKEN);
|
|
1036
|
+
await storageAdapter.remove(STORAGE_KEYS.USER_INFO);
|
|
1037
|
+
document.cookie = `basic_token=; Secure; SameSite=Strict`;
|
|
1038
|
+
document.cookie = `basic_access_token=; Secure; SameSite=Strict`;
|
|
1039
|
+
setUser({});
|
|
1040
|
+
setIsSignedIn(false);
|
|
1041
|
+
setToken(null);
|
|
1042
|
+
setIsAuthReady(true);
|
|
1043
|
+
throw new Error(`Token refresh failed: ${token2.error}`);
|
|
1044
|
+
} else {
|
|
1045
|
+
setToken(token2);
|
|
1046
|
+
setPendingRefresh(false);
|
|
1047
|
+
if (token2.refresh_token) {
|
|
1048
|
+
await storageAdapter.set(STORAGE_KEYS.REFRESH_TOKEN, token2.refresh_token);
|
|
1049
|
+
log("Updated refresh token in storage");
|
|
1050
|
+
}
|
|
1051
|
+
document.cookie = `basic_access_token=${token2.access_token}; Secure; SameSite=Strict; HttpOnly=false`;
|
|
1052
|
+
log("Updated access token in cookie");
|
|
1053
|
+
}
|
|
1054
|
+
return token2;
|
|
1055
|
+
} catch (error2) {
|
|
1056
|
+
log("Token refresh error:", error2);
|
|
1057
|
+
if (!error2.message.includes("offline") && !error2.message.includes("Network")) {
|
|
1058
|
+
await storageAdapter.remove(STORAGE_KEYS.REFRESH_TOKEN);
|
|
1059
|
+
await storageAdapter.remove(STORAGE_KEYS.USER_INFO);
|
|
1060
|
+
document.cookie = `basic_token=; Secure; SameSite=Strict`;
|
|
1061
|
+
document.cookie = `basic_access_token=; Secure; SameSite=Strict`;
|
|
1062
|
+
setUser({});
|
|
1063
|
+
setIsSignedIn(false);
|
|
1064
|
+
setToken(null);
|
|
1065
|
+
setIsAuthReady(true);
|
|
1066
|
+
}
|
|
1067
|
+
throw error2;
|
|
697
1068
|
}
|
|
698
|
-
return token2;
|
|
699
1069
|
};
|
|
700
1070
|
const db_ = (tableName) => {
|
|
701
1071
|
const checkSignIn = () => {
|
|
@@ -738,12 +1108,13 @@ function BasicProvider({ children, project_id, schema, debug = false }) {
|
|
|
738
1108
|
user,
|
|
739
1109
|
signout,
|
|
740
1110
|
signin,
|
|
1111
|
+
signinWithCode,
|
|
741
1112
|
getToken,
|
|
742
1113
|
getSignInLink,
|
|
743
1114
|
db: syncRef.current ? syncRef.current : noDb,
|
|
744
1115
|
dbStatus
|
|
745
1116
|
}, children: [
|
|
746
|
-
error && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ErrorDisplay, { error }),
|
|
1117
|
+
error && isDevelopment() && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ErrorDisplay, { error }),
|
|
747
1118
|
isReady && children
|
|
748
1119
|
] });
|
|
749
1120
|
}
|