@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.mjs
CHANGED
|
@@ -127,8 +127,116 @@ var syncProtocol = function() {
|
|
|
127
127
|
});
|
|
128
128
|
};
|
|
129
129
|
|
|
130
|
+
// src/schema.ts
|
|
131
|
+
import Ajv from "ajv";
|
|
132
|
+
var basicJsonSchema = {
|
|
133
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
134
|
+
"type": "object",
|
|
135
|
+
"properties": {
|
|
136
|
+
"project_id": {
|
|
137
|
+
"type": "string"
|
|
138
|
+
},
|
|
139
|
+
"namespace": {
|
|
140
|
+
"type": "string"
|
|
141
|
+
},
|
|
142
|
+
"version": {
|
|
143
|
+
"type": "integer",
|
|
144
|
+
"minimum": 0
|
|
145
|
+
},
|
|
146
|
+
"tables": {
|
|
147
|
+
"type": "object",
|
|
148
|
+
"patternProperties": {
|
|
149
|
+
"^[a-zA-Z0-9_]+$": {
|
|
150
|
+
"type": "object",
|
|
151
|
+
"properties": {
|
|
152
|
+
"name": {
|
|
153
|
+
"type": "string"
|
|
154
|
+
},
|
|
155
|
+
"type": {
|
|
156
|
+
"type": "string",
|
|
157
|
+
"enum": ["collection"]
|
|
158
|
+
},
|
|
159
|
+
"fields": {
|
|
160
|
+
"type": "object",
|
|
161
|
+
"patternProperties": {
|
|
162
|
+
"^[a-zA-Z0-9_]+$": {
|
|
163
|
+
"type": "object",
|
|
164
|
+
"properties": {
|
|
165
|
+
"type": {
|
|
166
|
+
"type": "string",
|
|
167
|
+
"enum": ["string", "boolean", "number", "json"]
|
|
168
|
+
},
|
|
169
|
+
"indexed": {
|
|
170
|
+
"type": "boolean"
|
|
171
|
+
},
|
|
172
|
+
"required": {
|
|
173
|
+
"type": "boolean"
|
|
174
|
+
}
|
|
175
|
+
},
|
|
176
|
+
"required": ["type"]
|
|
177
|
+
}
|
|
178
|
+
},
|
|
179
|
+
"additionalProperties": true
|
|
180
|
+
}
|
|
181
|
+
},
|
|
182
|
+
"required": ["fields"]
|
|
183
|
+
}
|
|
184
|
+
},
|
|
185
|
+
"additionalProperties": true
|
|
186
|
+
}
|
|
187
|
+
},
|
|
188
|
+
"required": ["project_id", "version", "tables"]
|
|
189
|
+
};
|
|
190
|
+
var ajv = new Ajv();
|
|
191
|
+
var validator = ajv.compile(basicJsonSchema);
|
|
192
|
+
function validateSchema(schema) {
|
|
193
|
+
const v = validator(schema);
|
|
194
|
+
return {
|
|
195
|
+
valid: v,
|
|
196
|
+
errors: validator.errors || []
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
function validateData(schema, table, data, checkRequired = true) {
|
|
200
|
+
const valid = validateSchema(schema);
|
|
201
|
+
if (!valid.valid) {
|
|
202
|
+
return { valid: false, errors: valid.errors, message: "Schema is invalid" };
|
|
203
|
+
}
|
|
204
|
+
const tableSchema = schema.tables[table];
|
|
205
|
+
if (!tableSchema) {
|
|
206
|
+
return { valid: false, errors: [{ message: `Table ${table} not found in schema` }], message: "Table not found" };
|
|
207
|
+
}
|
|
208
|
+
for (const [fieldName, fieldValue] of Object.entries(data)) {
|
|
209
|
+
const fieldSchema = tableSchema.fields[fieldName];
|
|
210
|
+
if (!fieldSchema) {
|
|
211
|
+
return {
|
|
212
|
+
valid: false,
|
|
213
|
+
errors: [{ message: `Field ${fieldName} not found in schema` }],
|
|
214
|
+
message: "Invalid field"
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
const schemaType = fieldSchema.type;
|
|
218
|
+
const valueType = typeof fieldValue;
|
|
219
|
+
if (schemaType === "string" && valueType !== "string" || schemaType === "number" && valueType !== "number" || schemaType === "boolean" && valueType !== "boolean" || schemaType === "json" && valueType !== "object") {
|
|
220
|
+
return {
|
|
221
|
+
valid: false,
|
|
222
|
+
errors: [{
|
|
223
|
+
message: `Field ${fieldName} should be type ${schemaType}, got ${valueType}`
|
|
224
|
+
}],
|
|
225
|
+
message: "invalid type"
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
if (checkRequired) {
|
|
230
|
+
for (const [fieldName, fieldSchema] of Object.entries(tableSchema.fields)) {
|
|
231
|
+
if (fieldSchema.required && !data[fieldName]) {
|
|
232
|
+
return { valid: false, errors: [{ message: `Field ${fieldName} is required` }], message: "Required field missing" };
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
return { valid: true, errors: [] };
|
|
237
|
+
}
|
|
238
|
+
|
|
130
239
|
// src/sync/index.ts
|
|
131
|
-
import { validateData } from "@basictech/schema";
|
|
132
240
|
syncProtocol();
|
|
133
241
|
var BasicSync = class extends Dexie2 {
|
|
134
242
|
basic_schema;
|
|
@@ -297,32 +405,43 @@ async function deleteRecord({ projectId, accountId, tableName, id, token }) {
|
|
|
297
405
|
}
|
|
298
406
|
|
|
299
407
|
// src/AuthContext.tsx
|
|
300
|
-
import { validateSchema as
|
|
408
|
+
import { validateSchema as validateSchema3, compareSchemas } from "@basictech/schema";
|
|
301
409
|
|
|
302
410
|
// package.json
|
|
303
411
|
var version = "0.6.0";
|
|
304
412
|
|
|
305
413
|
// src/AuthContext.tsx
|
|
306
414
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
415
|
+
var LocalStorageAdapter = class {
|
|
416
|
+
async get(key) {
|
|
417
|
+
return localStorage.getItem(key);
|
|
418
|
+
}
|
|
419
|
+
async set(key, value) {
|
|
420
|
+
localStorage.setItem(key, value);
|
|
421
|
+
}
|
|
422
|
+
async remove(key) {
|
|
423
|
+
localStorage.removeItem(key);
|
|
424
|
+
}
|
|
425
|
+
};
|
|
307
426
|
var BasicContext = createContext({
|
|
308
427
|
unicorn: "\u{1F984}",
|
|
309
428
|
isAuthReady: false,
|
|
310
429
|
isSignedIn: false,
|
|
311
430
|
user: null,
|
|
312
|
-
signout: () =>
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
},
|
|
431
|
+
signout: () => Promise.resolve(),
|
|
432
|
+
signin: () => Promise.resolve(),
|
|
433
|
+
signinWithCode: () => new Promise(() => {
|
|
434
|
+
}),
|
|
316
435
|
getToken: () => new Promise(() => {
|
|
317
436
|
}),
|
|
318
|
-
getSignInLink: () => "",
|
|
437
|
+
getSignInLink: () => Promise.resolve(""),
|
|
319
438
|
db: {},
|
|
320
439
|
dbStatus: "LOADING" /* LOADING */
|
|
321
440
|
});
|
|
322
441
|
async function getSchemaStatus(schema) {
|
|
323
442
|
const projectId = schema.project_id;
|
|
324
443
|
let status = "";
|
|
325
|
-
const valid =
|
|
444
|
+
const valid = validateSchema3(schema);
|
|
326
445
|
if (!valid.valid) {
|
|
327
446
|
console.warn("BasicDB Error: your local schema is invalid. Please fix errors and try again - sync is disabled");
|
|
328
447
|
return {
|
|
@@ -432,7 +551,13 @@ run "npm install @basictech/react@${latestVersion}" to update`);
|
|
|
432
551
|
};
|
|
433
552
|
}
|
|
434
553
|
}
|
|
435
|
-
function BasicProvider({
|
|
554
|
+
function BasicProvider({
|
|
555
|
+
children,
|
|
556
|
+
project_id,
|
|
557
|
+
schema,
|
|
558
|
+
debug = false,
|
|
559
|
+
storage
|
|
560
|
+
}) {
|
|
436
561
|
const [isAuthReady, setIsAuthReady] = useState(false);
|
|
437
562
|
const [isSignedIn, setIsSignedIn] = useState(false);
|
|
438
563
|
const [token, setToken] = useState(null);
|
|
@@ -441,7 +566,56 @@ function BasicProvider({ children, project_id, schema, debug = false }) {
|
|
|
441
566
|
const [isReady, setIsReady] = useState(false);
|
|
442
567
|
const [dbStatus, setDbStatus] = useState("OFFLINE" /* OFFLINE */);
|
|
443
568
|
const [error, setError] = useState(null);
|
|
569
|
+
const [isOnline, setIsOnline] = useState(navigator.onLine);
|
|
570
|
+
const [pendingRefresh, setPendingRefresh] = useState(false);
|
|
444
571
|
const syncRef = useRef(null);
|
|
572
|
+
const storageAdapter = storage || new LocalStorageAdapter();
|
|
573
|
+
const STORAGE_KEYS = {
|
|
574
|
+
REFRESH_TOKEN: "basic_refresh_token",
|
|
575
|
+
USER_INFO: "basic_user_info",
|
|
576
|
+
AUTH_STATE: "basic_auth_state",
|
|
577
|
+
DEBUG: "basic_debug"
|
|
578
|
+
};
|
|
579
|
+
const isDevelopment = () => {
|
|
580
|
+
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;
|
|
581
|
+
};
|
|
582
|
+
const cleanOAuthParamsFromUrl = () => {
|
|
583
|
+
if (window.location.search.includes("code") || window.location.search.includes("state")) {
|
|
584
|
+
const url = new URL(window.location.href);
|
|
585
|
+
url.searchParams.delete("code");
|
|
586
|
+
url.searchParams.delete("state");
|
|
587
|
+
window.history.pushState({}, document.title, url.pathname + url.search);
|
|
588
|
+
log("Cleaned OAuth parameters from URL");
|
|
589
|
+
}
|
|
590
|
+
};
|
|
591
|
+
useEffect(() => {
|
|
592
|
+
const handleOnline = () => {
|
|
593
|
+
log("Network came back online");
|
|
594
|
+
setIsOnline(true);
|
|
595
|
+
if (pendingRefresh) {
|
|
596
|
+
log("Retrying pending token refresh");
|
|
597
|
+
setPendingRefresh(false);
|
|
598
|
+
if (token) {
|
|
599
|
+
const refreshToken = token.refresh_token || localStorage.getItem("basic_refresh_token");
|
|
600
|
+
if (refreshToken) {
|
|
601
|
+
fetchToken(refreshToken).catch((error2) => {
|
|
602
|
+
log("Retry refresh failed:", error2);
|
|
603
|
+
});
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
};
|
|
608
|
+
const handleOffline = () => {
|
|
609
|
+
log("Network went offline");
|
|
610
|
+
setIsOnline(false);
|
|
611
|
+
};
|
|
612
|
+
window.addEventListener("online", handleOnline);
|
|
613
|
+
window.addEventListener("offline", handleOffline);
|
|
614
|
+
return () => {
|
|
615
|
+
window.removeEventListener("online", handleOnline);
|
|
616
|
+
window.removeEventListener("offline", handleOffline);
|
|
617
|
+
};
|
|
618
|
+
}, [pendingRefresh, token]);
|
|
445
619
|
useEffect(() => {
|
|
446
620
|
function initDb(options) {
|
|
447
621
|
if (!syncRef.current) {
|
|
@@ -462,7 +636,7 @@ function BasicProvider({ children, project_id, schema, debug = false }) {
|
|
|
462
636
|
}
|
|
463
637
|
}
|
|
464
638
|
async function checkSchema() {
|
|
465
|
-
const valid =
|
|
639
|
+
const valid = validateSchema3(schema);
|
|
466
640
|
if (!valid.valid) {
|
|
467
641
|
log("Basic Schema is invalid!", valid.errors);
|
|
468
642
|
console.group("Schema Errors");
|
|
@@ -507,32 +681,68 @@ function BasicProvider({ children, project_id, schema, debug = false }) {
|
|
|
507
681
|
connectToDb();
|
|
508
682
|
}
|
|
509
683
|
}, [isSignedIn, shouldConnect]);
|
|
684
|
+
const connectToDb = async () => {
|
|
685
|
+
const tok = await getToken();
|
|
686
|
+
if (!tok) {
|
|
687
|
+
log("no token found");
|
|
688
|
+
return;
|
|
689
|
+
}
|
|
690
|
+
log("connecting to db...");
|
|
691
|
+
syncRef.current.connect({ access_token: tok }).catch((e) => {
|
|
692
|
+
log("error connecting to db", e);
|
|
693
|
+
});
|
|
694
|
+
};
|
|
510
695
|
useEffect(() => {
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
if (cookie_token !== "") {
|
|
528
|
-
setToken(JSON.parse(cookie_token));
|
|
696
|
+
const initializeAuth = async () => {
|
|
697
|
+
await storageAdapter.set(STORAGE_KEYS.DEBUG, debug ? "true" : "false");
|
|
698
|
+
try {
|
|
699
|
+
if (window.location.search.includes("code")) {
|
|
700
|
+
let code = window.location?.search?.split("code=")[1].split("&")[0];
|
|
701
|
+
const state = await storageAdapter.get(STORAGE_KEYS.AUTH_STATE);
|
|
702
|
+
if (!state || state !== window.location.search.split("state=")[1].split("&")[0]) {
|
|
703
|
+
log("error: auth state does not match");
|
|
704
|
+
setIsAuthReady(true);
|
|
705
|
+
await storageAdapter.remove(STORAGE_KEYS.AUTH_STATE);
|
|
706
|
+
cleanOAuthParamsFromUrl();
|
|
707
|
+
return;
|
|
708
|
+
}
|
|
709
|
+
await storageAdapter.remove(STORAGE_KEYS.AUTH_STATE);
|
|
710
|
+
cleanOAuthParamsFromUrl();
|
|
711
|
+
fetchToken(code);
|
|
529
712
|
} else {
|
|
530
|
-
|
|
713
|
+
const refreshToken = await storageAdapter.get(STORAGE_KEYS.REFRESH_TOKEN);
|
|
714
|
+
if (refreshToken) {
|
|
715
|
+
log("Found refresh token in storage, attempting to refresh access token");
|
|
716
|
+
fetchToken(refreshToken);
|
|
717
|
+
} else {
|
|
718
|
+
let cookie_token = getCookie("basic_token");
|
|
719
|
+
if (cookie_token !== "") {
|
|
720
|
+
const tokenData = JSON.parse(cookie_token);
|
|
721
|
+
setToken(tokenData);
|
|
722
|
+
if (tokenData.refresh_token) {
|
|
723
|
+
await storageAdapter.set(STORAGE_KEYS.REFRESH_TOKEN, tokenData.refresh_token);
|
|
724
|
+
}
|
|
725
|
+
} else {
|
|
726
|
+
const cachedUserInfo = await storageAdapter.get(STORAGE_KEYS.USER_INFO);
|
|
727
|
+
if (cachedUserInfo) {
|
|
728
|
+
try {
|
|
729
|
+
const userData = JSON.parse(cachedUserInfo);
|
|
730
|
+
setUser(userData);
|
|
731
|
+
setIsSignedIn(true);
|
|
732
|
+
log("Loaded cached user info for offline mode");
|
|
733
|
+
} catch (error2) {
|
|
734
|
+
log("Error parsing cached user info:", error2);
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
setIsAuthReady(true);
|
|
738
|
+
}
|
|
739
|
+
}
|
|
531
740
|
}
|
|
741
|
+
} catch (e) {
|
|
742
|
+
log("error getting token", e);
|
|
532
743
|
}
|
|
533
|
-
}
|
|
534
|
-
|
|
535
|
-
}
|
|
744
|
+
};
|
|
745
|
+
initializeAuth();
|
|
536
746
|
}, []);
|
|
537
747
|
useEffect(() => {
|
|
538
748
|
async function fetchUser(acc_token) {
|
|
@@ -547,10 +757,13 @@ function BasicProvider({ children, project_id, schema, debug = false }) {
|
|
|
547
757
|
log("error fetching user", user2.error);
|
|
548
758
|
return;
|
|
549
759
|
} else {
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
window.history.pushState({}, document.title, "/");
|
|
760
|
+
if (token?.refresh_token) {
|
|
761
|
+
await storageAdapter.set(STORAGE_KEYS.REFRESH_TOKEN, token.refresh_token);
|
|
553
762
|
}
|
|
763
|
+
await storageAdapter.set(STORAGE_KEYS.USER_INFO, JSON.stringify(user2));
|
|
764
|
+
log("Cached user info in storage");
|
|
765
|
+
document.cookie = `basic_access_token=${token.access_token}; Secure; SameSite=Strict; HttpOnly=false`;
|
|
766
|
+
document.cookie = `basic_token=${JSON.stringify(token)}; Secure; SameSite=Strict`;
|
|
554
767
|
setUser(user2);
|
|
555
768
|
setIsSignedIn(true);
|
|
556
769
|
setIsAuthReady(true);
|
|
@@ -566,8 +779,18 @@ function BasicProvider({ children, project_id, schema, debug = false }) {
|
|
|
566
779
|
const isExpired = decoded.exp && decoded.exp < Date.now() / 1e3;
|
|
567
780
|
if (isExpired) {
|
|
568
781
|
log("token is expired - refreshing ...");
|
|
569
|
-
|
|
570
|
-
|
|
782
|
+
try {
|
|
783
|
+
const newToken = await fetchToken(token?.refresh_token);
|
|
784
|
+
fetchUser(newToken.access_token);
|
|
785
|
+
} catch (error2) {
|
|
786
|
+
log("Failed to refresh token in checkToken:", error2);
|
|
787
|
+
if (error2.message.includes("offline") || error2.message.includes("Network")) {
|
|
788
|
+
log("Network issue - continuing with expired token until online");
|
|
789
|
+
fetchUser(token.access_token);
|
|
790
|
+
} else {
|
|
791
|
+
setIsAuthReady(true);
|
|
792
|
+
}
|
|
793
|
+
}
|
|
571
794
|
} else {
|
|
572
795
|
fetchUser(token.access_token);
|
|
573
796
|
}
|
|
@@ -576,41 +799,97 @@ function BasicProvider({ children, project_id, schema, debug = false }) {
|
|
|
576
799
|
checkToken();
|
|
577
800
|
}
|
|
578
801
|
}, [token]);
|
|
579
|
-
const
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
802
|
+
const getSignInLink = async (redirectUri) => {
|
|
803
|
+
try {
|
|
804
|
+
log("getting sign in link...");
|
|
805
|
+
if (!project_id) {
|
|
806
|
+
throw new Error("Project ID is required to generate sign-in link");
|
|
807
|
+
}
|
|
808
|
+
const randomState = Math.random().toString(36).substring(6);
|
|
809
|
+
await storageAdapter.set(STORAGE_KEYS.AUTH_STATE, randomState);
|
|
810
|
+
const redirectUrl = redirectUri || window.location.href;
|
|
811
|
+
if (!redirectUrl || !redirectUrl.startsWith("http://") && !redirectUrl.startsWith("https://")) {
|
|
812
|
+
throw new Error("Invalid redirect URI provided");
|
|
813
|
+
}
|
|
814
|
+
let baseUrl2 = "https://api.basic.tech/auth/authorize";
|
|
815
|
+
baseUrl2 += `?client_id=${project_id}`;
|
|
816
|
+
baseUrl2 += `&redirect_uri=${encodeURIComponent(redirectUrl)}`;
|
|
817
|
+
baseUrl2 += `&response_type=code`;
|
|
818
|
+
baseUrl2 += `&scope=profile`;
|
|
819
|
+
baseUrl2 += `&state=${randomState}`;
|
|
820
|
+
log("Generated sign-in link successfully");
|
|
821
|
+
return baseUrl2;
|
|
822
|
+
} catch (error2) {
|
|
823
|
+
log("Error generating sign-in link:", error2);
|
|
824
|
+
throw error2;
|
|
584
825
|
}
|
|
585
|
-
log("connecting to db...");
|
|
586
|
-
syncRef.current.connect({ access_token: tok }).catch((e) => {
|
|
587
|
-
log("error connecting to db", e);
|
|
588
|
-
});
|
|
589
826
|
};
|
|
590
|
-
const
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
827
|
+
const signin = async () => {
|
|
828
|
+
try {
|
|
829
|
+
log("signing in...");
|
|
830
|
+
if (!project_id) {
|
|
831
|
+
log("Error: project_id is required for sign-in");
|
|
832
|
+
throw new Error("Project ID is required for authentication");
|
|
833
|
+
}
|
|
834
|
+
const signInLink = await getSignInLink();
|
|
835
|
+
log("Generated sign-in link:", signInLink);
|
|
836
|
+
if (!signInLink || !signInLink.startsWith("https://")) {
|
|
837
|
+
log("Error: Invalid sign-in link generated");
|
|
838
|
+
throw new Error("Failed to generate valid sign-in URL");
|
|
839
|
+
}
|
|
840
|
+
window.location.href = signInLink;
|
|
841
|
+
} catch (error2) {
|
|
842
|
+
log("Error during sign-in:", error2);
|
|
843
|
+
if (isDevelopment()) {
|
|
844
|
+
setError({
|
|
845
|
+
code: "signin_error",
|
|
846
|
+
title: "Sign-in Failed",
|
|
847
|
+
message: error2.message || "An error occurred during sign-in. Please try again."
|
|
848
|
+
});
|
|
849
|
+
}
|
|
850
|
+
throw error2;
|
|
851
|
+
}
|
|
601
852
|
};
|
|
602
|
-
const
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
853
|
+
const signinWithCode = async (code, state) => {
|
|
854
|
+
try {
|
|
855
|
+
log("signinWithCode called with code:", code);
|
|
856
|
+
if (!code || typeof code !== "string") {
|
|
857
|
+
return { success: false, error: "Invalid authorization code" };
|
|
858
|
+
}
|
|
859
|
+
if (state) {
|
|
860
|
+
const storedState = await storageAdapter.get(STORAGE_KEYS.AUTH_STATE);
|
|
861
|
+
if (storedState && storedState !== state) {
|
|
862
|
+
log("State parameter mismatch:", { provided: state, stored: storedState });
|
|
863
|
+
return { success: false, error: "State parameter mismatch" };
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
await storageAdapter.remove(STORAGE_KEYS.AUTH_STATE);
|
|
867
|
+
cleanOAuthParamsFromUrl();
|
|
868
|
+
const token2 = await fetchToken(code);
|
|
869
|
+
if (token2) {
|
|
870
|
+
log("signinWithCode successful");
|
|
871
|
+
return { success: true };
|
|
872
|
+
} else {
|
|
873
|
+
return { success: false, error: "Failed to exchange code for token" };
|
|
874
|
+
}
|
|
875
|
+
} catch (error2) {
|
|
876
|
+
log("signinWithCode error:", error2);
|
|
877
|
+
return {
|
|
878
|
+
success: false,
|
|
879
|
+
error: error2.message || "Authentication failed"
|
|
880
|
+
};
|
|
881
|
+
}
|
|
606
882
|
};
|
|
607
|
-
const signout = () => {
|
|
883
|
+
const signout = async () => {
|
|
608
884
|
log("signing out!");
|
|
609
885
|
setUser({});
|
|
610
886
|
setIsSignedIn(false);
|
|
611
887
|
setToken(null);
|
|
612
888
|
document.cookie = `basic_token=; Secure; SameSite=Strict`;
|
|
613
|
-
|
|
889
|
+
document.cookie = `basic_access_token=; Secure; SameSite=Strict`;
|
|
890
|
+
await storageAdapter.remove(STORAGE_KEYS.AUTH_STATE);
|
|
891
|
+
await storageAdapter.remove(STORAGE_KEYS.REFRESH_TOKEN);
|
|
892
|
+
await storageAdapter.remove(STORAGE_KEYS.USER_INFO);
|
|
614
893
|
if (syncRef.current) {
|
|
615
894
|
(async () => {
|
|
616
895
|
try {
|
|
@@ -627,6 +906,27 @@ function BasicProvider({ children, project_id, schema, debug = false }) {
|
|
|
627
906
|
const getToken = async () => {
|
|
628
907
|
log("getting token...");
|
|
629
908
|
if (!token) {
|
|
909
|
+
const refreshToken = await storageAdapter.get(STORAGE_KEYS.REFRESH_TOKEN);
|
|
910
|
+
if (refreshToken) {
|
|
911
|
+
log("No token in memory, attempting to refresh from storage");
|
|
912
|
+
try {
|
|
913
|
+
const newToken = await fetchToken(refreshToken);
|
|
914
|
+
if (newToken?.access_token) {
|
|
915
|
+
return newToken.access_token;
|
|
916
|
+
}
|
|
917
|
+
} catch (error2) {
|
|
918
|
+
log("Failed to refresh token from storage:", error2);
|
|
919
|
+
if (error2.message.includes("offline") || error2.message.includes("Network")) {
|
|
920
|
+
log("Network issue - continuing with potentially expired token");
|
|
921
|
+
const lastToken = localStorage.getItem("basic_access_token");
|
|
922
|
+
if (lastToken) {
|
|
923
|
+
return lastToken;
|
|
924
|
+
}
|
|
925
|
+
throw new Error("Network offline - authentication will be retried when online");
|
|
926
|
+
}
|
|
927
|
+
throw new Error("Authentication expired. Please sign in again.");
|
|
928
|
+
}
|
|
929
|
+
}
|
|
630
930
|
log("no token found");
|
|
631
931
|
throw new Error("no token found");
|
|
632
932
|
}
|
|
@@ -634,8 +934,22 @@ function BasicProvider({ children, project_id, schema, debug = false }) {
|
|
|
634
934
|
const isExpired = decoded.exp && decoded.exp < Date.now() / 1e3;
|
|
635
935
|
if (isExpired) {
|
|
636
936
|
log("token is expired - refreshing ...");
|
|
637
|
-
const
|
|
638
|
-
|
|
937
|
+
const refreshToken = token?.refresh_token || await storageAdapter.get(STORAGE_KEYS.REFRESH_TOKEN);
|
|
938
|
+
if (refreshToken) {
|
|
939
|
+
try {
|
|
940
|
+
const newToken = await fetchToken(refreshToken);
|
|
941
|
+
return newToken?.access_token || "";
|
|
942
|
+
} catch (error2) {
|
|
943
|
+
log("Failed to refresh expired token:", error2);
|
|
944
|
+
if (error2.message.includes("offline") || error2.message.includes("Network")) {
|
|
945
|
+
log("Network issue - using expired token until network is restored");
|
|
946
|
+
return token.access_token;
|
|
947
|
+
}
|
|
948
|
+
throw new Error("Authentication expired. Please sign in again.");
|
|
949
|
+
}
|
|
950
|
+
} else {
|
|
951
|
+
throw new Error("no refresh token available");
|
|
952
|
+
}
|
|
639
953
|
}
|
|
640
954
|
return token?.access_token || "";
|
|
641
955
|
};
|
|
@@ -654,20 +968,66 @@ function BasicProvider({ children, project_id, schema, debug = false }) {
|
|
|
654
968
|
return cookieValue;
|
|
655
969
|
}
|
|
656
970
|
const fetchToken = async (code) => {
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
971
|
+
try {
|
|
972
|
+
if (!isOnline) {
|
|
973
|
+
log("Network is offline, marking refresh as pending");
|
|
974
|
+
setPendingRefresh(true);
|
|
975
|
+
throw new Error("Network offline - refresh will be retried when online");
|
|
976
|
+
}
|
|
977
|
+
const token2 = await fetch("https://api.basic.tech/auth/token", {
|
|
978
|
+
method: "POST",
|
|
979
|
+
headers: {
|
|
980
|
+
"Content-Type": "application/json"
|
|
981
|
+
},
|
|
982
|
+
body: JSON.stringify({ code })
|
|
983
|
+
}).then((response) => response.json()).catch((error2) => {
|
|
984
|
+
log("Network error fetching token:", error2);
|
|
985
|
+
if (!isOnline) {
|
|
986
|
+
setPendingRefresh(true);
|
|
987
|
+
throw new Error("Network offline - refresh will be retried when online");
|
|
988
|
+
}
|
|
989
|
+
throw new Error("Network error during token refresh");
|
|
990
|
+
});
|
|
991
|
+
if (token2.error) {
|
|
992
|
+
log("error fetching token", token2.error);
|
|
993
|
+
if (token2.error.includes("network") || token2.error.includes("timeout")) {
|
|
994
|
+
setPendingRefresh(true);
|
|
995
|
+
throw new Error("Network issue - refresh will be retried when online");
|
|
996
|
+
}
|
|
997
|
+
await storageAdapter.remove(STORAGE_KEYS.REFRESH_TOKEN);
|
|
998
|
+
await storageAdapter.remove(STORAGE_KEYS.USER_INFO);
|
|
999
|
+
document.cookie = `basic_token=; Secure; SameSite=Strict`;
|
|
1000
|
+
document.cookie = `basic_access_token=; Secure; SameSite=Strict`;
|
|
1001
|
+
setUser({});
|
|
1002
|
+
setIsSignedIn(false);
|
|
1003
|
+
setToken(null);
|
|
1004
|
+
setIsAuthReady(true);
|
|
1005
|
+
throw new Error(`Token refresh failed: ${token2.error}`);
|
|
1006
|
+
} else {
|
|
1007
|
+
setToken(token2);
|
|
1008
|
+
setPendingRefresh(false);
|
|
1009
|
+
if (token2.refresh_token) {
|
|
1010
|
+
await storageAdapter.set(STORAGE_KEYS.REFRESH_TOKEN, token2.refresh_token);
|
|
1011
|
+
log("Updated refresh token in storage");
|
|
1012
|
+
}
|
|
1013
|
+
document.cookie = `basic_access_token=${token2.access_token}; Secure; SameSite=Strict; HttpOnly=false`;
|
|
1014
|
+
log("Updated access token in cookie");
|
|
1015
|
+
}
|
|
1016
|
+
return token2;
|
|
1017
|
+
} catch (error2) {
|
|
1018
|
+
log("Token refresh error:", error2);
|
|
1019
|
+
if (!error2.message.includes("offline") && !error2.message.includes("Network")) {
|
|
1020
|
+
await storageAdapter.remove(STORAGE_KEYS.REFRESH_TOKEN);
|
|
1021
|
+
await storageAdapter.remove(STORAGE_KEYS.USER_INFO);
|
|
1022
|
+
document.cookie = `basic_token=; Secure; SameSite=Strict`;
|
|
1023
|
+
document.cookie = `basic_access_token=; Secure; SameSite=Strict`;
|
|
1024
|
+
setUser({});
|
|
1025
|
+
setIsSignedIn(false);
|
|
1026
|
+
setToken(null);
|
|
1027
|
+
setIsAuthReady(true);
|
|
1028
|
+
}
|
|
1029
|
+
throw error2;
|
|
669
1030
|
}
|
|
670
|
-
return token2;
|
|
671
1031
|
};
|
|
672
1032
|
const db_ = (tableName) => {
|
|
673
1033
|
const checkSignIn = () => {
|
|
@@ -710,12 +1070,13 @@ function BasicProvider({ children, project_id, schema, debug = false }) {
|
|
|
710
1070
|
user,
|
|
711
1071
|
signout,
|
|
712
1072
|
signin,
|
|
1073
|
+
signinWithCode,
|
|
713
1074
|
getToken,
|
|
714
1075
|
getSignInLink,
|
|
715
1076
|
db: syncRef.current ? syncRef.current : noDb,
|
|
716
1077
|
dbStatus
|
|
717
1078
|
}, children: [
|
|
718
|
-
error && /* @__PURE__ */ jsx(ErrorDisplay, { error }),
|
|
1079
|
+
error && isDevelopment() && /* @__PURE__ */ jsx(ErrorDisplay, { error }),
|
|
719
1080
|
isReady && children
|
|
720
1081
|
] });
|
|
721
1082
|
}
|