@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.
@@ -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
- export interface BasicStorage {
11
- get(key: string): Promise<string | null>
12
- set(key: string, value: string): Promise<void>
13
- remove(key: string): Promise<void>
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
- async function checkForNewVersion(): Promise<{ hasNewVersion: boolean, latestVersion: string | null, currentVersion: string | null }> {
214
- try {
215
-
216
- const isBeta = currentVersion.includes('beta')
217
-
218
- const response = await fetch(`https://registry.npmjs.org/@basictech/react/${isBeta ? 'beta' : 'latest'}`);
219
- if (!response.ok) {
220
- throw new Error('Failed to fetch version from npm');
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 isDevelopment = () => {
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 cleanOAuthParamsFromUrl = () => {
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 valid = validateSchema(schema)
362
- if (!valid.valid) {
363
- log('Basic Schema is invalid!', valid.errors)
364
- console.group('Schema Errors')
177
+ const result = await validateAndCheckSchema(schema)
178
+
179
+ if (!result.isValid) {
365
180
  let errorMessage = ''
366
- valid.errors.forEach((error, index) => {
367
- log(`${index + 1}:`, error.message, ` - at ${error.instancePath}`)
368
- errorMessage += `${index + 1}: ${error.message} - at ${error.instancePath}\n`
369
- })
370
- console.groupEnd()
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
- if (token && syncRef.current && isSignedIn && shouldConnect) {
409
- connectToDb()
410
- }
411
- }, [isSignedIn, shouldConnect])
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
- const connectToDb = async () => {
414
- const tok = await getToken()
415
- if (!tok) {
416
- log('no token found')
417
- return
418
- }
221
+ log('connecting to db...')
419
222
 
420
- log('connecting to db...')
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
- syncRef.current?.connect({ access_token: tok })
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
- if (window.location.search.includes('code')) {
434
- let code = window.location?.search?.split('code=')[1]?.split('&')[0]
435
- if (!code) return
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
- const state = await storageAdapter.get(STORAGE_KEYS.AUTH_STATE)
438
- const urlState = window.location.search.split('state=')[1]?.split('&')[0]
439
- if (!state || state !== urlState) {
440
- log('error: auth state does not match')
441
- setIsAuthReady(true)
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
- // Clean OAuth parameters from URL
445
- cleanOAuthParamsFromUrl()
446
- return
447
- }
267
+ cleanOAuthParams()
448
268
 
449
- await storageAdapter.remove(STORAGE_KEYS.AUTH_STATE)
450
- // Clean OAuth parameters from URL
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
- let cookie_token = getCookie('basic_token')
465
- if (cookie_token !== '') {
466
- const tokenData = JSON.parse(cookie_token)
467
- setToken(tokenData)
468
- if (tokenData.refresh_token) {
469
- await storageAdapter.set(STORAGE_KEYS.REFRESH_TOKEN, tokenData.refresh_token)
470
- }
471
- } else {
472
- const cachedUserInfo = await storageAdapter.get(STORAGE_KEYS.USER_INFO)
473
- if (cachedUserInfo) {
474
- try {
475
- const userData = JSON.parse(cachedUserInfo)
476
- setUser(userData)
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
- document.cookie = `basic_access_token=${token?.access_token}; Secure; SameSite=Strict; HttpOnly=false`;
520
- document.cookie = `basic_token=${JSON.stringify(token)}; Secure; SameSite=Strict`;
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 (isDevelopment()) {
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
- cleanOAuthParamsFromUrl()
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
- document.cookie = `basic_token=; Secure; SameSite=Strict`;
675
- document.cookie = `basic_access_token=; Secure; SameSite=Strict`;
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
- function getCookie(name: string) {
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({ code: code })
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
- document.cookie = `basic_token=; Secure; SameSite=Strict`;
805
- document.cookie = `basic_access_token=; Secure; SameSite=Strict`;
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
- document.cookie = `basic_access_token=${token.access_token}; Secure; SameSite=Strict; HttpOnly=false`;
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
- document.cookie = `basic_token=; Secure; SameSite=Strict`;
833
- document.cookie = `basic_access_token=; Secure; SameSite=Strict`;
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 && isDevelopment() && <ErrorDisplay error={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
  }