@basictech/react 0.7.0-beta.1 → 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 +6 -0
- package/dist/index.d.mts +107 -1
- package/dist/index.d.ts +107 -1
- package/dist/index.js +719 -356
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +716 -344
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/AuthContext.tsx +178 -402
- package/src/index.ts +6 -31
- package/src/sync/index.ts +1 -1
- 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/src/schema.ts +0 -159
package/src/AuthContext.tsx
CHANGED
|
@@ -2,30 +2,17 @@ import React, { createContext, useContext, useEffect, useState, useRef } from 'r
|
|
|
2
2
|
import { jwtDecode } from 'jwt-decode'
|
|
3
3
|
|
|
4
4
|
import { BasicSync } from './sync'
|
|
5
|
-
import { get, add, update, deleteRecord } from './db'
|
|
6
|
-
import { validateSchema, compareSchemas } from '@basictech/schema'
|
|
7
5
|
|
|
8
6
|
import { log } from './config'
|
|
9
|
-
import {version as currentVersion} from '../package.json'
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
}
|
|
7
|
+
import { version as currentVersion } from '../package.json'
|
|
8
|
+
import { createVersionUpdater } from './updater/versionUpdater'
|
|
9
|
+
import { getMigrations } from './updater/updateMigrations'
|
|
10
|
+
import { BasicStorage, LocalStorageAdapter, STORAGE_KEYS, getCookie, setCookie, clearCookie } from './utils/storage'
|
|
11
|
+
import { isDevelopment, checkForNewVersion, cleanOAuthParamsFromUrl, getSyncStatus } from './utils/network'
|
|
12
|
+
import { getSchemaStatus, validateAndCheckSchema } from './utils/schema'
|
|
13
|
+
|
|
14
|
+
export type { BasicStorage, LocalStorageAdapter } from './utils/storage'
|
|
15
15
|
|
|
16
|
-
export class LocalStorageAdapter implements BasicStorage {
|
|
17
|
-
async get(key: string): Promise<string | null> {
|
|
18
|
-
return localStorage.getItem(key)
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
async set(key: string, value: string): Promise<void> {
|
|
22
|
-
localStorage.setItem(key, value)
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
async remove(key: string): Promise<void> {
|
|
26
|
-
localStorage.removeItem(key)
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
16
|
|
|
30
17
|
type BasicSyncType = {
|
|
31
18
|
basic_schema: any;
|
|
@@ -92,171 +79,24 @@ export const BasicContext = createContext<{
|
|
|
92
79
|
dbStatus: DBStatus.LOADING
|
|
93
80
|
});
|
|
94
81
|
|
|
95
|
-
const EmptyDB: BasicSyncType = {
|
|
96
|
-
basic_schema: {},
|
|
97
|
-
connect: () => {},
|
|
98
|
-
debugeroo: () => {},
|
|
99
|
-
isOpen: false,
|
|
100
|
-
collection: () => {
|
|
101
|
-
return {
|
|
102
|
-
ref: {
|
|
103
|
-
toArray: () => Promise.resolve([]),
|
|
104
|
-
count: () => Promise.resolve(0)
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
async function getSchemaStatus(schema: any) {
|
|
111
|
-
const projectId = schema.project_id
|
|
112
|
-
let status = ''
|
|
113
|
-
const valid = validateSchema(schema)
|
|
114
|
-
|
|
115
|
-
if (!valid.valid) {
|
|
116
|
-
console.warn('BasicDB Error: your local schema is invalid. Please fix errors and try again - sync is disabled')
|
|
117
|
-
return {
|
|
118
|
-
valid: false,
|
|
119
|
-
status: 'invalid',
|
|
120
|
-
latest: null
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
const latestSchema = await fetch(`https://api.basic.tech/project/${projectId}/schema`)
|
|
125
|
-
.then(res => res.json())
|
|
126
|
-
.then(data => data.data[0].schema)
|
|
127
|
-
.catch(err => {
|
|
128
|
-
return {
|
|
129
|
-
valid: false,
|
|
130
|
-
status: 'error',
|
|
131
|
-
latest: null
|
|
132
|
-
}
|
|
133
|
-
})
|
|
134
|
-
|
|
135
|
-
console.log('latestSchema', latestSchema)
|
|
136
|
-
|
|
137
|
-
if (!latestSchema.version) {
|
|
138
|
-
return {
|
|
139
|
-
valid: false,
|
|
140
|
-
status: 'error',
|
|
141
|
-
latest: null
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
if (latestSchema.version > schema.version) {
|
|
146
|
-
// error_code: schema_behind
|
|
147
|
-
console.warn('BasicDB Error: your local schema version is behind the latest. Found version:', schema.version, 'but expected', latestSchema.version, " - sync is disabled")
|
|
148
|
-
return {
|
|
149
|
-
valid: false,
|
|
150
|
-
status: 'behind',
|
|
151
|
-
latest: latestSchema
|
|
152
|
-
}
|
|
153
|
-
} else if (latestSchema.version < schema.version) {
|
|
154
|
-
// error_code: schema_ahead
|
|
155
|
-
console.warn('BasicDB Error: your local schema version is ahead of the latest. Found version:', schema.version, 'but expected', latestSchema.version, " - sync is disabled")
|
|
156
|
-
return {
|
|
157
|
-
valid: false,
|
|
158
|
-
status: 'ahead',
|
|
159
|
-
latest: latestSchema
|
|
160
|
-
}
|
|
161
|
-
} else if (latestSchema.version === schema.version) {
|
|
162
|
-
const changes = compareSchemas(schema, latestSchema)
|
|
163
|
-
if (changes.valid) {
|
|
164
|
-
return {
|
|
165
|
-
valid: true,
|
|
166
|
-
status: 'current',
|
|
167
|
-
latest: latestSchema
|
|
168
|
-
}
|
|
169
|
-
} else {
|
|
170
|
-
// error_code: schema_conflict
|
|
171
|
-
console.warn('BasicDB Error: your local schema is conflicting with the latest. Your version:', schema.version, 'does not match origin version', latestSchema.version, " - sync is disabled")
|
|
172
|
-
return {
|
|
173
|
-
valid: false,
|
|
174
|
-
status: 'conflict',
|
|
175
|
-
latest: latestSchema
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
} else {
|
|
179
|
-
return {
|
|
180
|
-
valid: false,
|
|
181
|
-
status: 'error',
|
|
182
|
-
latest: null
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
function getSyncStatus(statusCode: number): string {
|
|
189
|
-
switch (statusCode) {
|
|
190
|
-
case -1:
|
|
191
|
-
return "ERROR";
|
|
192
|
-
case 0:
|
|
193
|
-
return "OFFLINE";
|
|
194
|
-
case 1:
|
|
195
|
-
return "CONNECTING";
|
|
196
|
-
case 2:
|
|
197
|
-
return "ONLINE";
|
|
198
|
-
case 3:
|
|
199
|
-
return "SYNCING";
|
|
200
|
-
case 4:
|
|
201
|
-
return "ERROR_WILL_RETRY";
|
|
202
|
-
default:
|
|
203
|
-
return "UNKNOWN";
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
|
|
207
82
|
type ErrorObject = {
|
|
208
83
|
code: string;
|
|
209
84
|
title: string;
|
|
210
85
|
message: string;
|
|
211
86
|
}
|
|
212
87
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
const data = await response.json();
|
|
224
|
-
const latestVersion = data.version;
|
|
225
|
-
|
|
226
|
-
if (latestVersion !== currentVersion) {
|
|
227
|
-
console.warn('[basic] New version available:', latestVersion, `\nrun "npm install @basictech/react@${latestVersion}" to update`);
|
|
228
|
-
}
|
|
229
|
-
if (isBeta) {
|
|
230
|
-
log('thank you for being on basictech/react beta :)')
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
return {
|
|
234
|
-
hasNewVersion: currentVersion !== latestVersion,
|
|
235
|
-
latestVersion,
|
|
236
|
-
currentVersion
|
|
237
|
-
};
|
|
238
|
-
} catch (error) {
|
|
239
|
-
log('Error checking for new version:', error);
|
|
240
|
-
return {
|
|
241
|
-
hasNewVersion: false,
|
|
242
|
-
latestVersion: null,
|
|
243
|
-
currentVersion: null
|
|
244
|
-
};
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
export function BasicProvider({
|
|
249
|
-
children,
|
|
250
|
-
project_id,
|
|
251
|
-
schema,
|
|
252
|
-
debug = false,
|
|
253
|
-
storage
|
|
254
|
-
}: {
|
|
255
|
-
children: React.ReactNode,
|
|
256
|
-
project_id?: string,
|
|
257
|
-
schema?: any,
|
|
88
|
+
export function BasicProvider({
|
|
89
|
+
children,
|
|
90
|
+
project_id,
|
|
91
|
+
schema,
|
|
92
|
+
debug = false,
|
|
93
|
+
storage
|
|
94
|
+
}: {
|
|
95
|
+
children: React.ReactNode,
|
|
96
|
+
project_id?: string,
|
|
97
|
+
schema?: any,
|
|
258
98
|
debug?: boolean,
|
|
259
|
-
storage?: BasicStorage
|
|
99
|
+
storage?: BasicStorage
|
|
260
100
|
}) {
|
|
261
101
|
const [isAuthReady, setIsAuthReady] = useState(false)
|
|
262
102
|
const [isSignedIn, setIsSignedIn] = useState<boolean>(false)
|
|
@@ -272,34 +112,10 @@ export function BasicProvider({
|
|
|
272
112
|
|
|
273
113
|
const syncRef = useRef<BasicSync | null>(null);
|
|
274
114
|
const storageAdapter = storage || new LocalStorageAdapter();
|
|
275
|
-
const STORAGE_KEYS = {
|
|
276
|
-
REFRESH_TOKEN: 'basic_refresh_token',
|
|
277
|
-
USER_INFO: 'basic_user_info',
|
|
278
|
-
AUTH_STATE: 'basic_auth_state',
|
|
279
|
-
DEBUG: 'basic_debug'
|
|
280
|
-
}
|
|
281
115
|
|
|
282
|
-
const
|
|
283
|
-
return (
|
|
284
|
-
window.location.hostname === 'localhost' ||
|
|
285
|
-
window.location.hostname === '127.0.0.1' ||
|
|
286
|
-
window.location.hostname.includes('localhost') ||
|
|
287
|
-
window.location.hostname.includes('127.0.0.1') ||
|
|
288
|
-
window.location.hostname.includes('.local') ||
|
|
289
|
-
process.env.NODE_ENV === 'development' ||
|
|
290
|
-
debug === true
|
|
291
|
-
)
|
|
292
|
-
}
|
|
116
|
+
const isDevMode = () => isDevelopment(debug)
|
|
293
117
|
|
|
294
|
-
const
|
|
295
|
-
if (window.location.search.includes('code') || window.location.search.includes('state')) {
|
|
296
|
-
const url = new URL(window.location.href)
|
|
297
|
-
url.searchParams.delete('code')
|
|
298
|
-
url.searchParams.delete('state')
|
|
299
|
-
window.history.pushState({}, document.title, url.pathname + url.search)
|
|
300
|
-
log('Cleaned OAuth parameters from URL')
|
|
301
|
-
}
|
|
302
|
-
}
|
|
118
|
+
const cleanOAuthParams = () => cleanOAuthParamsFromUrl()
|
|
303
119
|
|
|
304
120
|
useEffect(() => {
|
|
305
121
|
const handleOnline = () => {
|
|
@@ -311,14 +127,14 @@ export function BasicProvider({
|
|
|
311
127
|
if (token) {
|
|
312
128
|
const refreshToken = token.refresh_token || localStorage.getItem('basic_refresh_token')
|
|
313
129
|
if (refreshToken) {
|
|
314
|
-
fetchToken(refreshToken).catch(error => {
|
|
130
|
+
fetchToken(refreshToken, true).catch(error => {
|
|
315
131
|
log('Retry refresh failed:', error)
|
|
316
132
|
})
|
|
317
133
|
}
|
|
318
134
|
}
|
|
319
135
|
}
|
|
320
136
|
}
|
|
321
|
-
|
|
137
|
+
|
|
322
138
|
const handleOffline = () => {
|
|
323
139
|
log('Network went offline')
|
|
324
140
|
setIsOnline(false)
|
|
@@ -338,18 +154,18 @@ export function BasicProvider({
|
|
|
338
154
|
if (!syncRef.current) {
|
|
339
155
|
log('Initializing Basic DB')
|
|
340
156
|
syncRef.current = new BasicSync('basicdb', { schema: schema });
|
|
341
|
-
|
|
157
|
+
|
|
342
158
|
syncRef.current.syncable.on('statusChanged', (status: number, url: string) => {
|
|
343
159
|
setDbStatus(getSyncStatus(status) as DBStatus)
|
|
344
160
|
})
|
|
345
|
-
|
|
161
|
+
|
|
346
162
|
// syncRef.current.syncable.getStatus().then((status: number) => {
|
|
347
163
|
// setDbStatus(getSyncStatus(status) as DBStatus)
|
|
348
164
|
// })
|
|
349
165
|
|
|
350
|
-
if (options.shouldConnect) {
|
|
166
|
+
if (options.shouldConnect) {
|
|
351
167
|
setShouldConnect(true)
|
|
352
|
-
} else {
|
|
168
|
+
} else {
|
|
353
169
|
log('Sync is disabled')
|
|
354
170
|
}
|
|
355
171
|
|
|
@@ -358,16 +174,15 @@ export function BasicProvider({
|
|
|
358
174
|
}
|
|
359
175
|
|
|
360
176
|
async function checkSchema() {
|
|
361
|
-
const
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
console.group('Schema Errors')
|
|
177
|
+
const result = await validateAndCheckSchema(schema)
|
|
178
|
+
|
|
179
|
+
if (!result.isValid) {
|
|
365
180
|
let errorMessage = ''
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
181
|
+
if (result.errors) {
|
|
182
|
+
result.errors.forEach((error, index) => {
|
|
183
|
+
errorMessage += `${index + 1}: ${error.message} - at ${error.instancePath}\n`
|
|
184
|
+
})
|
|
185
|
+
}
|
|
371
186
|
setError({
|
|
372
187
|
code: 'schema_invalid',
|
|
373
188
|
title: 'Basic Schema is invalid!',
|
|
@@ -377,22 +192,13 @@ export function BasicProvider({
|
|
|
377
192
|
return null
|
|
378
193
|
}
|
|
379
194
|
|
|
380
|
-
|
|
381
|
-
let schemaStatus = { valid: false }
|
|
382
|
-
if (schema.version !== 0) {
|
|
383
|
-
schemaStatus = await getSchemaStatus(schema)
|
|
384
|
-
log('schemaStatus', schemaStatus)
|
|
385
|
-
}else {
|
|
386
|
-
log("schema not published - at version 0")
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
if (schemaStatus.valid) {
|
|
195
|
+
if (result.schemaStatus.valid) {
|
|
390
196
|
initDb({ shouldConnect: true })
|
|
391
197
|
} else {
|
|
392
|
-
log('Schema is invalid!', schemaStatus)
|
|
198
|
+
log('Schema is invalid!', result.schemaStatus)
|
|
393
199
|
initDb({ shouldConnect: false })
|
|
394
200
|
}
|
|
395
|
-
|
|
201
|
+
|
|
396
202
|
checkForNewVersion()
|
|
397
203
|
}
|
|
398
204
|
|
|
@@ -403,93 +209,103 @@ export function BasicProvider({
|
|
|
403
209
|
}
|
|
404
210
|
}, []);
|
|
405
211
|
|
|
406
|
-
|
|
407
212
|
useEffect(() => {
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
213
|
+
async function connectToDb() {
|
|
214
|
+
if (token && syncRef.current && isSignedIn && shouldConnect) {
|
|
215
|
+
const tok = await getToken()
|
|
216
|
+
if (!tok) {
|
|
217
|
+
log('no token found')
|
|
218
|
+
return
|
|
219
|
+
}
|
|
412
220
|
|
|
413
|
-
|
|
414
|
-
const tok = await getToken()
|
|
415
|
-
if (!tok) {
|
|
416
|
-
log('no token found')
|
|
417
|
-
return
|
|
418
|
-
}
|
|
221
|
+
log('connecting to db...')
|
|
419
222
|
|
|
420
|
-
|
|
223
|
+
syncRef.current?.connect({ access_token: tok })
|
|
224
|
+
.catch((e) => {
|
|
225
|
+
log('error connecting to db', e)
|
|
226
|
+
})
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
connectToDb()
|
|
421
230
|
|
|
422
|
-
|
|
423
|
-
.catch((e) => {
|
|
424
|
-
log('error connecting to db', e)
|
|
425
|
-
})
|
|
426
|
-
}
|
|
231
|
+
}, [isSignedIn, shouldConnect])
|
|
427
232
|
|
|
428
233
|
useEffect(() => {
|
|
429
234
|
const initializeAuth = async () => {
|
|
430
235
|
await storageAdapter.set(STORAGE_KEYS.DEBUG, debug ? 'true' : 'false')
|
|
431
236
|
|
|
432
237
|
try {
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
238
|
+
const versionUpdater = createVersionUpdater(storageAdapter, currentVersion, getMigrations())
|
|
239
|
+
const updateResult = await versionUpdater.checkAndUpdate()
|
|
240
|
+
|
|
241
|
+
if (updateResult.updated) {
|
|
242
|
+
log(`App updated from ${updateResult.fromVersion} to ${updateResult.toVersion}`)
|
|
243
|
+
} else {
|
|
244
|
+
log(`App version ${updateResult.toVersion} is current`)
|
|
245
|
+
}
|
|
246
|
+
} catch (error) {
|
|
247
|
+
log('Version update failed:', error)
|
|
248
|
+
}
|
|
436
249
|
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
250
|
+
try {
|
|
251
|
+
if (window.location.search.includes('code')) {
|
|
252
|
+
let code = window.location?.search?.split('code=')[1]?.split('&')[0]
|
|
253
|
+
if (!code) return
|
|
254
|
+
|
|
255
|
+
const state = await storageAdapter.get(STORAGE_KEYS.AUTH_STATE)
|
|
256
|
+
const urlState = window.location.search.split('state=')[1]?.split('&')[0]
|
|
257
|
+
if (!state || state !== urlState) {
|
|
258
|
+
log('error: auth state does not match')
|
|
259
|
+
setIsAuthReady(true)
|
|
260
|
+
|
|
261
|
+
await storageAdapter.remove(STORAGE_KEYS.AUTH_STATE)
|
|
262
|
+
cleanOAuthParams()
|
|
263
|
+
return
|
|
264
|
+
}
|
|
442
265
|
|
|
443
266
|
await storageAdapter.remove(STORAGE_KEYS.AUTH_STATE)
|
|
444
|
-
|
|
445
|
-
cleanOAuthParamsFromUrl()
|
|
446
|
-
return
|
|
447
|
-
}
|
|
267
|
+
cleanOAuthParams()
|
|
448
268
|
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
cleanOAuthParamsFromUrl()
|
|
452
|
-
|
|
453
|
-
fetchToken(code).catch((error) => {
|
|
454
|
-
log('Error fetching token:', error)
|
|
455
|
-
})
|
|
456
|
-
} else {
|
|
457
|
-
const refreshToken = await storageAdapter.get(STORAGE_KEYS.REFRESH_TOKEN)
|
|
458
|
-
if (refreshToken) {
|
|
459
|
-
log('Found refresh token in storage, attempting to refresh access token')
|
|
460
|
-
fetchToken(refreshToken).catch((error) => {
|
|
461
|
-
log('Error fetching refresh token:', error)
|
|
269
|
+
fetchToken(code, false).catch((error) => {
|
|
270
|
+
log('Error fetching token:', error)
|
|
462
271
|
})
|
|
463
272
|
} else {
|
|
464
|
-
|
|
465
|
-
if (
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
setIsSignedIn(true)
|
|
478
|
-
log('Loaded cached user info for offline mode')
|
|
479
|
-
} catch (error) {
|
|
480
|
-
log('Error parsing cached user info:', error)
|
|
273
|
+
const refreshToken = await storageAdapter.get(STORAGE_KEYS.REFRESH_TOKEN)
|
|
274
|
+
if (refreshToken) {
|
|
275
|
+
log('Found refresh token in storage, attempting to refresh access token')
|
|
276
|
+
fetchToken(refreshToken, true).catch((error) => {
|
|
277
|
+
log('Error fetching refresh token:', error)
|
|
278
|
+
})
|
|
279
|
+
} else {
|
|
280
|
+
let cookie_token = getCookie('basic_token')
|
|
281
|
+
if (cookie_token !== '') {
|
|
282
|
+
const tokenData = JSON.parse(cookie_token)
|
|
283
|
+
setToken(tokenData)
|
|
284
|
+
if (tokenData.refresh_token) {
|
|
285
|
+
await storageAdapter.set(STORAGE_KEYS.REFRESH_TOKEN, tokenData.refresh_token)
|
|
481
286
|
}
|
|
287
|
+
} else {
|
|
288
|
+
const cachedUserInfo = await storageAdapter.get(STORAGE_KEYS.USER_INFO)
|
|
289
|
+
if (cachedUserInfo) {
|
|
290
|
+
try {
|
|
291
|
+
const userData = JSON.parse(cachedUserInfo)
|
|
292
|
+
setUser(userData)
|
|
293
|
+
setIsSignedIn(true)
|
|
294
|
+
log('Loaded cached user info for offline mode')
|
|
295
|
+
} catch (error) {
|
|
296
|
+
log('Error parsing cached user info:', error)
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
setIsAuthReady(true)
|
|
482
300
|
}
|
|
483
|
-
setIsAuthReady(true)
|
|
484
301
|
}
|
|
485
302
|
}
|
|
486
|
-
}
|
|
487
303
|
|
|
488
304
|
} catch (e) {
|
|
489
305
|
log('error getting token', e)
|
|
490
306
|
}
|
|
491
307
|
}
|
|
492
|
-
|
|
308
|
+
|
|
493
309
|
initializeAuth()
|
|
494
310
|
}, [])
|
|
495
311
|
|
|
@@ -512,13 +328,13 @@ export function BasicProvider({
|
|
|
512
328
|
if (token?.refresh_token) {
|
|
513
329
|
await storageAdapter.set(STORAGE_KEYS.REFRESH_TOKEN, token.refresh_token)
|
|
514
330
|
}
|
|
515
|
-
|
|
331
|
+
|
|
516
332
|
await storageAdapter.set(STORAGE_KEYS.USER_INFO, JSON.stringify(user))
|
|
517
333
|
log('Cached user info in storage')
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
334
|
+
|
|
335
|
+
setCookie('basic_access_token', token?.access_token || '', { httpOnly: false });
|
|
336
|
+
setCookie('basic_token', JSON.stringify(token));
|
|
337
|
+
|
|
522
338
|
setUser(user)
|
|
523
339
|
setIsSignedIn(true)
|
|
524
340
|
|
|
@@ -540,11 +356,11 @@ export function BasicProvider({
|
|
|
540
356
|
if (isExpired) {
|
|
541
357
|
log('token is expired - refreshing ...')
|
|
542
358
|
try {
|
|
543
|
-
const newToken = await fetchToken(token?.refresh_token || '')
|
|
359
|
+
const newToken = await fetchToken(token?.refresh_token || '', true)
|
|
544
360
|
fetchUser(newToken?.access_token || '')
|
|
545
361
|
} catch (error) {
|
|
546
362
|
log('Failed to refresh token in checkToken:', error)
|
|
547
|
-
|
|
363
|
+
|
|
548
364
|
if ((error as Error).message.includes('offline') || (error as Error).message.includes('Network')) {
|
|
549
365
|
log('Network issue - continuing with expired token until online')
|
|
550
366
|
fetchUser(token?.access_token || '')
|
|
@@ -559,7 +375,7 @@ export function BasicProvider({
|
|
|
559
375
|
|
|
560
376
|
if (token) {
|
|
561
377
|
checkToken()
|
|
562
|
-
}
|
|
378
|
+
}
|
|
563
379
|
}, [token])
|
|
564
380
|
|
|
565
381
|
const getSignInLink = async (redirectUri?: string) => {
|
|
@@ -598,33 +414,33 @@ export function BasicProvider({
|
|
|
598
414
|
const signin = async () => {
|
|
599
415
|
try {
|
|
600
416
|
log('signing in...')
|
|
601
|
-
|
|
417
|
+
|
|
602
418
|
if (!project_id) {
|
|
603
419
|
log('Error: project_id is required for sign-in')
|
|
604
420
|
throw new Error('Project ID is required for authentication')
|
|
605
421
|
}
|
|
606
|
-
|
|
422
|
+
|
|
607
423
|
const signInLink = await getSignInLink()
|
|
608
424
|
log('Generated sign-in link:', signInLink)
|
|
609
|
-
|
|
425
|
+
|
|
610
426
|
if (!signInLink || !signInLink.startsWith('https://')) {
|
|
611
427
|
log('Error: Invalid sign-in link generated')
|
|
612
428
|
throw new Error('Failed to generate valid sign-in URL')
|
|
613
429
|
}
|
|
614
|
-
|
|
430
|
+
|
|
615
431
|
window.location.href = signInLink
|
|
616
|
-
|
|
432
|
+
|
|
617
433
|
} catch (error) {
|
|
618
434
|
log('Error during sign-in:', error)
|
|
619
|
-
|
|
620
|
-
if (
|
|
435
|
+
|
|
436
|
+
if (isDevMode()) {
|
|
621
437
|
setError({
|
|
622
438
|
code: 'signin_error',
|
|
623
439
|
title: 'Sign-in Failed',
|
|
624
440
|
message: (error as Error).message || 'An error occurred during sign-in. Please try again.'
|
|
625
441
|
})
|
|
626
442
|
}
|
|
627
|
-
|
|
443
|
+
|
|
628
444
|
throw error
|
|
629
445
|
}
|
|
630
446
|
}
|
|
@@ -632,7 +448,7 @@ export function BasicProvider({
|
|
|
632
448
|
const signinWithCode = async (code: string, state?: string): Promise<{ success: boolean, error?: string }> => {
|
|
633
449
|
try {
|
|
634
450
|
log('signinWithCode called with code:', code)
|
|
635
|
-
|
|
451
|
+
|
|
636
452
|
if (!code || typeof code !== 'string') {
|
|
637
453
|
return { success: false, error: 'Invalid authorization code' }
|
|
638
454
|
}
|
|
@@ -646,10 +462,10 @@ export function BasicProvider({
|
|
|
646
462
|
}
|
|
647
463
|
|
|
648
464
|
await storageAdapter.remove(STORAGE_KEYS.AUTH_STATE)
|
|
649
|
-
|
|
465
|
+
cleanOAuthParams()
|
|
466
|
+
|
|
467
|
+
const token = await fetchToken(code, false)
|
|
650
468
|
|
|
651
|
-
const token = await fetchToken(code)
|
|
652
|
-
|
|
653
469
|
if (token) {
|
|
654
470
|
log('signinWithCode successful')
|
|
655
471
|
return { success: true }
|
|
@@ -658,9 +474,9 @@ export function BasicProvider({
|
|
|
658
474
|
}
|
|
659
475
|
} catch (error) {
|
|
660
476
|
log('signinWithCode error:', error)
|
|
661
|
-
return {
|
|
662
|
-
success: false,
|
|
663
|
-
error: (error as Error).message || 'Authentication failed'
|
|
477
|
+
return {
|
|
478
|
+
success: false,
|
|
479
|
+
error: (error as Error).message || 'Authentication failed'
|
|
664
480
|
}
|
|
665
481
|
}
|
|
666
482
|
}
|
|
@@ -670,9 +486,9 @@ export function BasicProvider({
|
|
|
670
486
|
setUser({})
|
|
671
487
|
setIsSignedIn(false)
|
|
672
488
|
setToken(null)
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
489
|
+
|
|
490
|
+
clearCookie('basic_token');
|
|
491
|
+
clearCookie('basic_access_token');
|
|
676
492
|
await storageAdapter.remove(STORAGE_KEYS.AUTH_STATE)
|
|
677
493
|
await storageAdapter.remove(STORAGE_KEYS.REFRESH_TOKEN)
|
|
678
494
|
await storageAdapter.remove(STORAGE_KEYS.USER_INFO)
|
|
@@ -680,7 +496,7 @@ export function BasicProvider({
|
|
|
680
496
|
(async () => {
|
|
681
497
|
try {
|
|
682
498
|
await syncRef.current?.close()
|
|
683
|
-
await syncRef.current?.delete({disableAutoOpen: false})
|
|
499
|
+
await syncRef.current?.delete({ disableAutoOpen: false })
|
|
684
500
|
syncRef.current = null
|
|
685
501
|
window?.location?.reload()
|
|
686
502
|
} catch (error) {
|
|
@@ -693,20 +509,19 @@ export function BasicProvider({
|
|
|
693
509
|
const getToken = async (): Promise<string> => {
|
|
694
510
|
log('getting token...')
|
|
695
511
|
|
|
696
|
-
|
|
697
512
|
if (!token) {
|
|
698
513
|
// Try to recover from storage refresh token
|
|
699
514
|
const refreshToken = await storageAdapter.get(STORAGE_KEYS.REFRESH_TOKEN)
|
|
700
515
|
if (refreshToken) {
|
|
701
516
|
log('No token in memory, attempting to refresh from storage')
|
|
702
517
|
try {
|
|
703
|
-
const newToken = await fetchToken(refreshToken)
|
|
518
|
+
const newToken = await fetchToken(refreshToken, true)
|
|
704
519
|
if (newToken?.access_token) {
|
|
705
520
|
return newToken.access_token
|
|
706
521
|
}
|
|
707
522
|
} catch (error) {
|
|
708
523
|
log('Failed to refresh token from storage:', error)
|
|
709
|
-
|
|
524
|
+
|
|
710
525
|
if ((error as Error).message.includes('offline') || (error as Error).message.includes('Network')) {
|
|
711
526
|
log('Network issue - continuing with potentially expired token')
|
|
712
527
|
const lastToken = localStorage.getItem('basic_access_token')
|
|
@@ -715,7 +530,7 @@ export function BasicProvider({
|
|
|
715
530
|
}
|
|
716
531
|
throw new Error('Network offline - authentication will be retried when online')
|
|
717
532
|
}
|
|
718
|
-
|
|
533
|
+
|
|
719
534
|
throw new Error('Authentication expired. Please sign in again.')
|
|
720
535
|
}
|
|
721
536
|
}
|
|
@@ -731,16 +546,16 @@ export function BasicProvider({
|
|
|
731
546
|
const refreshToken = token?.refresh_token || await storageAdapter.get(STORAGE_KEYS.REFRESH_TOKEN)
|
|
732
547
|
if (refreshToken) {
|
|
733
548
|
try {
|
|
734
|
-
const newToken = await fetchToken(refreshToken)
|
|
549
|
+
const newToken = await fetchToken(refreshToken, true)
|
|
735
550
|
return newToken?.access_token || ''
|
|
736
551
|
} catch (error) {
|
|
737
552
|
log('Failed to refresh expired token:', error)
|
|
738
|
-
|
|
553
|
+
|
|
739
554
|
if ((error as Error).message.includes('offline') || (error as Error).message.includes('Network')) {
|
|
740
555
|
log('Network issue - using expired token until network is restored')
|
|
741
556
|
return token.access_token
|
|
742
557
|
}
|
|
743
|
-
|
|
558
|
+
|
|
744
559
|
throw new Error('Authentication expired. Please sign in again.')
|
|
745
560
|
}
|
|
746
561
|
} else {
|
|
@@ -751,22 +566,7 @@ export function BasicProvider({
|
|
|
751
566
|
return token?.access_token || ''
|
|
752
567
|
}
|
|
753
568
|
|
|
754
|
-
|
|
755
|
-
let cookieValue = '';
|
|
756
|
-
if (document.cookie && document.cookie !== '') {
|
|
757
|
-
const cookies = document.cookie.split(';');
|
|
758
|
-
for (let i = 0; i < cookies.length; i++) {
|
|
759
|
-
const cookie = cookies[i]?.trim();
|
|
760
|
-
if (cookie && cookie.substring(0, name.length + 1) === (name + '=')) {
|
|
761
|
-
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
|
|
762
|
-
break;
|
|
763
|
-
}
|
|
764
|
-
}
|
|
765
|
-
}
|
|
766
|
-
return cookieValue;
|
|
767
|
-
}
|
|
768
|
-
|
|
769
|
-
const fetchToken = async (code: string) => {
|
|
569
|
+
const fetchToken = async (codeOrRefreshToken: string, isRefreshToken: boolean = false) => {
|
|
770
570
|
try {
|
|
771
571
|
if (!isOnline) {
|
|
772
572
|
log('Network is offline, marking refresh as pending')
|
|
@@ -774,12 +574,22 @@ export function BasicProvider({
|
|
|
774
574
|
throw new Error('Network offline - refresh will be retried when online')
|
|
775
575
|
}
|
|
776
576
|
|
|
577
|
+
const requestBody = isRefreshToken
|
|
578
|
+
? {
|
|
579
|
+
grant_type: 'refresh_token',
|
|
580
|
+
refresh_token: codeOrRefreshToken
|
|
581
|
+
}
|
|
582
|
+
: {
|
|
583
|
+
grant_type: 'authorization_code',
|
|
584
|
+
code: codeOrRefreshToken
|
|
585
|
+
}
|
|
586
|
+
|
|
777
587
|
const token = await fetch('https://api.basic.tech/auth/token', {
|
|
778
588
|
method: 'POST',
|
|
779
589
|
headers: {
|
|
780
590
|
'Content-Type': 'application/json'
|
|
781
591
|
},
|
|
782
|
-
body: JSON.stringify(
|
|
592
|
+
body: JSON.stringify(requestBody)
|
|
783
593
|
})
|
|
784
594
|
.then(response => response.json())
|
|
785
595
|
.catch(error => {
|
|
@@ -793,90 +603,56 @@ export function BasicProvider({
|
|
|
793
603
|
|
|
794
604
|
if (token.error) {
|
|
795
605
|
log('error fetching token', token.error)
|
|
796
|
-
|
|
606
|
+
|
|
797
607
|
if (token.error.includes('network') || token.error.includes('timeout')) {
|
|
798
608
|
setPendingRefresh(true)
|
|
799
609
|
throw new Error('Network issue - refresh will be retried when online')
|
|
800
610
|
}
|
|
801
|
-
|
|
611
|
+
|
|
802
612
|
await storageAdapter.remove(STORAGE_KEYS.REFRESH_TOKEN)
|
|
803
613
|
await storageAdapter.remove(STORAGE_KEYS.USER_INFO)
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
614
|
+
clearCookie('basic_token');
|
|
615
|
+
clearCookie('basic_access_token');
|
|
616
|
+
|
|
807
617
|
setUser({})
|
|
808
618
|
setIsSignedIn(false)
|
|
809
619
|
setToken(null)
|
|
810
620
|
setIsAuthReady(true)
|
|
811
|
-
|
|
621
|
+
|
|
812
622
|
throw new Error(`Token refresh failed: ${token.error}`)
|
|
813
623
|
} else {
|
|
814
624
|
setToken(token)
|
|
815
625
|
setPendingRefresh(false)
|
|
816
|
-
|
|
626
|
+
|
|
817
627
|
if (token.refresh_token) {
|
|
818
628
|
await storageAdapter.set(STORAGE_KEYS.REFRESH_TOKEN, token.refresh_token)
|
|
819
629
|
log('Updated refresh token in storage')
|
|
820
630
|
}
|
|
821
|
-
|
|
822
|
-
|
|
631
|
+
|
|
632
|
+
setCookie('basic_access_token', token.access_token, { httpOnly: false });
|
|
823
633
|
log('Updated access token in cookie')
|
|
824
634
|
}
|
|
825
635
|
return token
|
|
826
636
|
} catch (error) {
|
|
827
637
|
log('Token refresh error:', error)
|
|
828
|
-
|
|
638
|
+
|
|
829
639
|
if (!(error as Error).message.includes('offline') && !(error as Error).message.includes('Network')) {
|
|
830
640
|
await storageAdapter.remove(STORAGE_KEYS.REFRESH_TOKEN)
|
|
831
641
|
await storageAdapter.remove(STORAGE_KEYS.USER_INFO)
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
642
|
+
clearCookie('basic_token');
|
|
643
|
+
clearCookie('basic_access_token');
|
|
644
|
+
|
|
835
645
|
setUser({})
|
|
836
646
|
setIsSignedIn(false)
|
|
837
647
|
setToken(null)
|
|
838
648
|
setIsAuthReady(true)
|
|
839
649
|
}
|
|
840
|
-
|
|
841
|
-
throw error
|
|
842
|
-
}
|
|
843
|
-
}
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
const db_ = (tableName: string) => {
|
|
847
|
-
const checkSignIn = () => {
|
|
848
|
-
if (!isSignedIn) {
|
|
849
|
-
throw new Error('cannot use db. user not logged in.')
|
|
850
|
-
}
|
|
851
|
-
}
|
|
852
|
-
|
|
853
|
-
return {
|
|
854
|
-
get: async () => {
|
|
855
|
-
checkSignIn()
|
|
856
|
-
const tok = await getToken()
|
|
857
|
-
return get({ projectId: project_id, accountId: user?.id, tableName: tableName, token: tok })
|
|
858
|
-
},
|
|
859
|
-
add: async (value: any) => {
|
|
860
|
-
checkSignIn()
|
|
861
|
-
const tok = await getToken()
|
|
862
|
-
return add({ projectId: project_id, accountId: user?.id, tableName: tableName, value: value, token: tok })
|
|
863
|
-
},
|
|
864
|
-
update: async (id: string, value: any) => {
|
|
865
|
-
checkSignIn()
|
|
866
|
-
const tok = await getToken()
|
|
867
|
-
return update({ projectId: project_id, accountId: user?.id, tableName: tableName, id: id, value: value, token: tok })
|
|
868
|
-
},
|
|
869
|
-
delete: async (id: string) => {
|
|
870
|
-
checkSignIn()
|
|
871
|
-
const tok = await getToken()
|
|
872
|
-
return deleteRecord({ projectId: project_id, accountId: user?.id, tableName: tableName, id: id, token: tok })
|
|
873
|
-
}
|
|
874
650
|
|
|
651
|
+
throw error
|
|
875
652
|
}
|
|
876
|
-
|
|
877
653
|
}
|
|
878
654
|
|
|
879
|
-
const noDb = ({
|
|
655
|
+
const noDb = ({
|
|
880
656
|
collection: () => {
|
|
881
657
|
throw new Error('no basicdb found - initialization failed. double check your schema.')
|
|
882
658
|
}
|
|
@@ -896,17 +672,17 @@ export function BasicProvider({
|
|
|
896
672
|
db: syncRef.current ? syncRef.current : noDb,
|
|
897
673
|
dbStatus
|
|
898
674
|
}}>
|
|
899
|
-
|
|
900
|
-
{error &&
|
|
675
|
+
|
|
676
|
+
{error && isDevMode() && <ErrorDisplay error={error} />}
|
|
901
677
|
{isReady && children}
|
|
902
678
|
</BasicContext.Provider>
|
|
903
679
|
)
|
|
904
680
|
}
|
|
905
681
|
|
|
906
682
|
function ErrorDisplay({ error }: { error: ErrorObject }) {
|
|
907
|
-
return <div style={{
|
|
683
|
+
return <div style={{
|
|
908
684
|
position: 'absolute',
|
|
909
|
-
top: 20,
|
|
685
|
+
top: 20,
|
|
910
686
|
left: 20,
|
|
911
687
|
color: 'black',
|
|
912
688
|
backgroundColor: '#f8d7da',
|
|
@@ -916,10 +692,10 @@ function ErrorDisplay({ error }: { error: ErrorObject }) {
|
|
|
916
692
|
maxWidth: '400px',
|
|
917
693
|
margin: '20px auto',
|
|
918
694
|
boxShadow: '0 2px 4px rgba(0, 0, 0, 0.1)',
|
|
919
|
-
fontFamily: 'monospace',
|
|
920
|
-
|
|
921
|
-
<h3 style={{fontSize: '0.8rem', opacity: 0.8}}>code: {error.code}</h3>
|
|
922
|
-
<h1 style={{fontSize: '1.2rem', lineHeight: '1.5'}}>{error.title}</h1>
|
|
695
|
+
fontFamily: 'monospace',
|
|
696
|
+
}}>
|
|
697
|
+
<h3 style={{ fontSize: '0.8rem', opacity: 0.8 }}>code: {error.code}</h3>
|
|
698
|
+
<h1 style={{ fontSize: '1.2rem', lineHeight: '1.5' }}>{error.title}</h1>
|
|
923
699
|
<p>{error.message}</p>
|
|
924
700
|
</div>
|
|
925
701
|
}
|