@basictech/react 0.2.0-beta.1 → 0.2.0-beta.10
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/.turbo/turbo-build.log +10 -10
- package/changelog.md +64 -0
- package/dist/index.d.mts +99 -4
- package/dist/index.d.ts +99 -4
- package/dist/index.js +345 -7935
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +315 -7930
- package/dist/index.mjs.map +1 -1
- package/package.json +11 -5
- package/readme.md +101 -29
- package/src/AuthContext.tsx +198 -76
- package/src/config.ts +27 -0
- package/src/index.ts +22 -3
- package/src/schema.ts +159 -0
- package/src/sync/index.ts +223 -0
- package/src/sync/syncProtocol.js +179 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@basictech/react",
|
|
3
|
-
"version": "0.2.0-beta.
|
|
3
|
+
"version": "0.2.0-beta.10",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.mjs",
|
|
@@ -17,15 +17,21 @@
|
|
|
17
17
|
"author": "",
|
|
18
18
|
"license": "ISC",
|
|
19
19
|
"dependencies": {
|
|
20
|
+
"ajv": "^8.17.1",
|
|
21
|
+
"dexie": "^4.0.8",
|
|
22
|
+
"dexie-observable": "^4.0.1-beta.13",
|
|
23
|
+
"dexie-react-hooks": "^1.1.7",
|
|
24
|
+
"dexie-syncable": "^4.0.1-beta.13",
|
|
20
25
|
"jwt-decode": "^4.0.0",
|
|
21
|
-
"
|
|
26
|
+
"uuid": "^10.0.0",
|
|
27
|
+
"@basictech/schema": "0.1.0-beta.0"
|
|
22
28
|
},
|
|
23
29
|
"devDependencies": {
|
|
30
|
+
"@repo/typescript-config": "*",
|
|
24
31
|
"tsup": "^7.2.0",
|
|
25
|
-
"typescript": "^5.0.0"
|
|
26
|
-
"@repo/typescript-config": "*"
|
|
32
|
+
"typescript": "^5.0.0"
|
|
27
33
|
},
|
|
28
34
|
"peerDependencies": {
|
|
29
35
|
"react": "^16.8.0 || ^17.0.0 || ^18.0.0"
|
|
30
36
|
}
|
|
31
|
-
}
|
|
37
|
+
}
|
package/readme.md
CHANGED
|
@@ -17,9 +17,31 @@ In your root component or App.tsx, wrap your application with the `BasicProvider
|
|
|
17
17
|
```typescript
|
|
18
18
|
import { BasicProvider } from '@basictech/react';
|
|
19
19
|
|
|
20
|
+
const schema = {
|
|
21
|
+
tables: {
|
|
22
|
+
todos: {
|
|
23
|
+
fields: {
|
|
24
|
+
id: {
|
|
25
|
+
type: "string",
|
|
26
|
+
primary: true
|
|
27
|
+
},
|
|
28
|
+
title: {
|
|
29
|
+
type: "string",
|
|
30
|
+
indexed: true
|
|
31
|
+
},
|
|
32
|
+
completed: {
|
|
33
|
+
type: "boolean",
|
|
34
|
+
indexed: true
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
|
|
20
42
|
function App() {
|
|
21
43
|
return (
|
|
22
|
-
<BasicProvider project_id="YOUR_PROJECT_ID">
|
|
44
|
+
<BasicProvider project_id="YOUR_PROJECT_ID" schema={schema} debug>
|
|
23
45
|
{/* Your app components */}
|
|
24
46
|
</BasicProvider>
|
|
25
47
|
);
|
|
@@ -53,33 +75,50 @@ function MyComponent() {
|
|
|
53
75
|
}
|
|
54
76
|
```
|
|
55
77
|
|
|
56
|
-
|
|
78
|
+
## API Reference
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
### <BasicProvider>
|
|
82
|
+
|
|
83
|
+
The `BasicProvider` component accepts the following props:
|
|
84
|
+
|
|
85
|
+
- `project_id` (required): String - Your Basic project ID.
|
|
86
|
+
- `schema` (required): Object - The schema definition for your database.
|
|
87
|
+
- `debug` (optional): Boolean - Enable debug mode for additional logging. Default is `false`.
|
|
88
|
+
- `children` (required): React.ReactNode - The child components to be wrapped by the provider.
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
|
|
57
92
|
|
|
58
|
-
|
|
93
|
+
### useQuery
|
|
94
|
+
|
|
95
|
+
returns a react hook that will automatically update data based on your query
|
|
96
|
+
|
|
97
|
+
usage:
|
|
59
98
|
|
|
60
99
|
```typescript
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
const
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
console.log(result);
|
|
79
|
-
};
|
|
100
|
+
import { useQuery } from '@basictech/react'
|
|
101
|
+
|
|
102
|
+
function MyComponent() {
|
|
103
|
+
const data = useQuery(db.collection('data').getAll())
|
|
104
|
+
|
|
105
|
+
return (
|
|
106
|
+
<div>
|
|
107
|
+
{
|
|
108
|
+
data.map((item: any) => {
|
|
109
|
+
<>
|
|
110
|
+
// render your data here
|
|
111
|
+
</>
|
|
112
|
+
})
|
|
113
|
+
}
|
|
114
|
+
</div>
|
|
115
|
+
);
|
|
116
|
+
}
|
|
80
117
|
```
|
|
118
|
+
Notes:
|
|
119
|
+
- must pass in a db function, ie `db.collection('todos').getAll()`
|
|
120
|
+
- default will be empty array (this might change in the future)
|
|
81
121
|
|
|
82
|
-
## API Reference
|
|
83
122
|
|
|
84
123
|
### useBasic()
|
|
85
124
|
|
|
@@ -91,14 +130,47 @@ Returns an object with the following properties and methods:
|
|
|
91
130
|
- `signout()`: Function to sign out the user
|
|
92
131
|
- `db`: Object for database operations
|
|
93
132
|
|
|
94
|
-
### db
|
|
95
133
|
|
|
96
|
-
The `db` object provides the following methods:
|
|
97
134
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
135
|
+
db methods:
|
|
136
|
+
|
|
137
|
+
- `collection(name: string)`: returns a collection object
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
db.collection(name) methods:
|
|
141
|
+
|
|
142
|
+
- `getAll()`: returns all items in the collection
|
|
143
|
+
- `get(id: string)`: returns a single item from the collection
|
|
144
|
+
- `add(data: any)`: adds a new item to the collection
|
|
145
|
+
- `put(data: any)`: updates an item in the collection
|
|
146
|
+
- `update(id: string, data: any)`: updates an item in the collection
|
|
147
|
+
- `delete(id: string)`: deletes an item from the collection
|
|
148
|
+
|
|
149
|
+
all db.collection() methods return a promise
|
|
150
|
+
|
|
151
|
+
example usage:
|
|
152
|
+
|
|
153
|
+
```typescript
|
|
154
|
+
import { useBasic } from '@basictech/react';
|
|
155
|
+
|
|
156
|
+
function MyComponent() {
|
|
157
|
+
const { db } = useBasic();
|
|
158
|
+
|
|
159
|
+
async function addTodo() {
|
|
160
|
+
await db.collection('todos').add({
|
|
161
|
+
title: 'test',
|
|
162
|
+
completed: false
|
|
163
|
+
})
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return (
|
|
167
|
+
<div>
|
|
168
|
+
<button onClick={addTodo}>Add Todo</button>
|
|
169
|
+
</div>
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
```
|
|
102
174
|
|
|
103
175
|
## License
|
|
104
176
|
|
package/src/AuthContext.tsx
CHANGED
|
@@ -3,23 +3,54 @@
|
|
|
3
3
|
import React, { createContext, useContext, useEffect, useState, useRef } from 'react'
|
|
4
4
|
import { jwtDecode } from 'jwt-decode'
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
import { BasicSync } from '@repo/sync'
|
|
6
|
+
import { BasicSync } from './sync'
|
|
9
7
|
import { get, add, update, deleteRecord } from './db'
|
|
8
|
+
import { validateSchema } from './schema'
|
|
9
|
+
|
|
10
|
+
import { log } from './config'
|
|
11
|
+
|
|
12
|
+
/*
|
|
13
|
+
schema todo:
|
|
14
|
+
field types
|
|
15
|
+
array types
|
|
16
|
+
relations
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
// const example = {
|
|
21
|
+
// project_id: '123',
|
|
22
|
+
// version: 0,
|
|
23
|
+
// tables: {
|
|
24
|
+
// example: {
|
|
25
|
+
// name: 'example',
|
|
26
|
+
// type: 'collection',
|
|
27
|
+
// fields: {
|
|
28
|
+
// id: {
|
|
29
|
+
// type: 'uuid',
|
|
30
|
+
// primary: true,
|
|
31
|
+
// },
|
|
32
|
+
// value: {
|
|
33
|
+
// type: 'string',
|
|
34
|
+
// indexed: true,
|
|
35
|
+
// },
|
|
36
|
+
// }
|
|
37
|
+
// }
|
|
38
|
+
// }
|
|
39
|
+
// }
|
|
40
|
+
|
|
10
41
|
|
|
11
42
|
type BasicSyncType = {
|
|
12
43
|
basic_schema: any;
|
|
13
44
|
connect: (options: { access_token: string }) => void;
|
|
14
45
|
debugeroo: () => void;
|
|
15
46
|
collection: (name: string) => {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
47
|
+
ref: {
|
|
48
|
+
toArray: () => Promise<any[]>;
|
|
49
|
+
count: () => Promise<number>;
|
|
50
|
+
};
|
|
20
51
|
};
|
|
21
52
|
[key: string]: any; // For other potential methods and properties
|
|
22
|
-
|
|
53
|
+
};
|
|
23
54
|
|
|
24
55
|
|
|
25
56
|
enum DBStatus {
|
|
@@ -49,18 +80,18 @@ type Token = {
|
|
|
49
80
|
|
|
50
81
|
export const BasicContext = createContext<{
|
|
51
82
|
unicorn: string,
|
|
52
|
-
|
|
83
|
+
isAuthReady: boolean,
|
|
53
84
|
isSignedIn: boolean,
|
|
54
85
|
user: User | null,
|
|
55
86
|
signout: () => void,
|
|
56
87
|
signin: () => void,
|
|
57
88
|
getToken: () => Promise<string>,
|
|
58
89
|
getSignInLink: () => string,
|
|
59
|
-
db: any,
|
|
90
|
+
db: any,
|
|
60
91
|
dbStatus: DBStatus
|
|
61
92
|
}>({
|
|
62
93
|
unicorn: "🦄",
|
|
63
|
-
|
|
94
|
+
isAuthReady: false,
|
|
64
95
|
isSignedIn: false,
|
|
65
96
|
user: null,
|
|
66
97
|
signout: () => { },
|
|
@@ -68,7 +99,7 @@ export const BasicContext = createContext<{
|
|
|
68
99
|
getToken: () => new Promise(() => { }),
|
|
69
100
|
getSignInLink: () => "",
|
|
70
101
|
db: {},
|
|
71
|
-
dbStatus: DBStatus.
|
|
102
|
+
dbStatus: DBStatus.LOADING
|
|
72
103
|
});
|
|
73
104
|
|
|
74
105
|
const EmptyDB: BasicSyncType = {
|
|
@@ -103,98 +134,143 @@ function getSyncStatus(statusCode: number): string {
|
|
|
103
134
|
}
|
|
104
135
|
}
|
|
105
136
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
137
|
+
type ErrorObject = {
|
|
138
|
+
code: string;
|
|
139
|
+
title: string;
|
|
140
|
+
message: string;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export function BasicProvider({ children, project_id, schema, debug = false }: { children: React.ReactNode, project_id: string, schema: any, debug?: boolean }) {
|
|
144
|
+
const [isAuthReady, setIsAuthReady] = useState(false)
|
|
145
|
+
const [isSignedIn, setIsSignedIn] = useState<boolean>(false)
|
|
109
146
|
const [token, setToken] = useState<Token | null>(null)
|
|
110
|
-
const [authCode, setAuthCode] = useState<string | null>(null)
|
|
111
147
|
const [user, setUser] = useState<User>({})
|
|
112
148
|
|
|
113
|
-
const [dbStatus, setDbStatus] = useState<DBStatus>(DBStatus.
|
|
114
|
-
|
|
149
|
+
const [dbStatus, setDbStatus] = useState<DBStatus>(DBStatus.LOADING)
|
|
150
|
+
const [error, setError] = useState<ErrorObject | null>(null)
|
|
115
151
|
|
|
116
152
|
const syncRef = useRef<BasicSync | null>(null);
|
|
117
153
|
|
|
154
|
+
|
|
118
155
|
useEffect(() => {
|
|
119
|
-
|
|
120
|
-
|
|
156
|
+
function initDb() {
|
|
157
|
+
const valid = validateSchema(schema)
|
|
158
|
+
if (!valid.valid) {
|
|
159
|
+
log('Basic Schema is invalid!', valid.errors)
|
|
160
|
+
console.group('Schema Errors')
|
|
161
|
+
let errorMessage = ''
|
|
162
|
+
valid.errors.forEach((error, index) => {
|
|
163
|
+
log(`${index + 1}:`, error.message, ` - at ${error.instancePath}`)
|
|
164
|
+
errorMessage += `${index + 1}: ${error.message} - at ${error.instancePath}\n`
|
|
165
|
+
})
|
|
166
|
+
console.groupEnd('Schema Errors')
|
|
167
|
+
setError({
|
|
168
|
+
code: 'schema_invalid',
|
|
169
|
+
title: 'Basic Schema is invalid!',
|
|
170
|
+
message: errorMessage
|
|
171
|
+
})
|
|
172
|
+
return null
|
|
173
|
+
}
|
|
121
174
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
// console.log("is open now:", syncRef.current.isOpen())
|
|
126
|
-
// })
|
|
175
|
+
if (!syncRef.current) {
|
|
176
|
+
log('Initializing BasicDB')
|
|
177
|
+
syncRef.current = new BasicSync('basicdb', { schema: schema });
|
|
127
178
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
179
|
+
// log('db is open', syncRef.current.isOpen())
|
|
180
|
+
// syncRef.current.open()
|
|
181
|
+
// .then(() => {
|
|
182
|
+
// log("is open now:", syncRef.current.isOpen())
|
|
183
|
+
// })
|
|
184
|
+
}
|
|
185
|
+
}
|
|
131
186
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
})
|
|
187
|
+
initDb()
|
|
188
|
+
}, []);
|
|
135
189
|
|
|
190
|
+
useEffect(() => {
|
|
191
|
+
if (!syncRef.current) {
|
|
192
|
+
return
|
|
136
193
|
}
|
|
137
|
-
}, []);
|
|
138
194
|
|
|
195
|
+
// syncRef.current.handleStatusChange((status: number, url: string) => {
|
|
196
|
+
// setDbStatus(getSyncStatus(status))
|
|
197
|
+
// })
|
|
139
198
|
|
|
140
|
-
|
|
141
|
-
|
|
199
|
+
syncRef.current.syncable.on('statusChanged', (status: number, url: string) => {
|
|
200
|
+
setDbStatus(getSyncStatus(status))
|
|
201
|
+
})
|
|
142
202
|
|
|
143
|
-
|
|
203
|
+
syncRef.current.syncable.getStatus().then((status) => {
|
|
204
|
+
setDbStatus(getSyncStatus(status))
|
|
205
|
+
})
|
|
206
|
+
}, [syncRef.current])
|
|
144
207
|
|
|
208
|
+
|
|
209
|
+
const connectToDb = async () => {
|
|
145
210
|
const tok = await getToken()
|
|
211
|
+
if (!tok) {
|
|
212
|
+
log('no token found')
|
|
213
|
+
return
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
log('connecting to db...')
|
|
146
217
|
|
|
147
|
-
|
|
218
|
+
// TODO: handle if signed out after connect() is already called
|
|
148
219
|
|
|
149
220
|
syncRef.current.connect({ access_token: tok })
|
|
150
221
|
.catch((e) => {
|
|
151
|
-
|
|
222
|
+
log('error connecting to db', e)
|
|
152
223
|
})
|
|
153
224
|
}
|
|
154
225
|
|
|
155
226
|
useEffect(() => {
|
|
156
|
-
if (token) {
|
|
227
|
+
if (token && syncRef.current && isSignedIn && isSignedIn) {
|
|
157
228
|
connectToDb()
|
|
158
229
|
}
|
|
159
|
-
}, [
|
|
230
|
+
}, [isSignedIn])
|
|
160
231
|
|
|
161
232
|
const getSignInLink = () => {
|
|
162
|
-
|
|
233
|
+
log('getting sign in link...')
|
|
163
234
|
|
|
164
|
-
const randomState = Math.random().toString(36).substring(
|
|
235
|
+
const randomState = Math.random().toString(36).substring(6);
|
|
236
|
+
localStorage.setItem('basic_auth_state', randomState)
|
|
165
237
|
|
|
166
|
-
|
|
167
|
-
let baseUrl = "http://localhost:3003/auth/authorize"
|
|
238
|
+
let baseUrl = "https://api.basic.tech/auth/authorize"
|
|
168
239
|
baseUrl += `?client_id=${project_id}`
|
|
169
240
|
baseUrl += `&redirect_uri=${encodeURIComponent(window.location.href)}`
|
|
170
241
|
baseUrl += `&response_type=code`
|
|
171
242
|
baseUrl += `&scope=openid`
|
|
172
|
-
baseUrl += `&state
|
|
243
|
+
baseUrl += `&state=${randomState}`
|
|
173
244
|
|
|
174
245
|
return baseUrl;
|
|
175
246
|
}
|
|
176
247
|
|
|
177
248
|
const signin = () => {
|
|
178
|
-
|
|
249
|
+
log('signing in: ', getSignInLink())
|
|
179
250
|
const signInLink = getSignInLink()
|
|
180
251
|
//todo: change to the other thing?
|
|
181
252
|
window.location.href = signInLink;
|
|
182
253
|
}
|
|
183
254
|
|
|
184
255
|
const signout = () => {
|
|
185
|
-
|
|
256
|
+
log('signing out!')
|
|
186
257
|
setUser({})
|
|
187
258
|
setIsSignedIn(false)
|
|
188
259
|
setToken(null)
|
|
189
|
-
setAuthCode(null)
|
|
190
260
|
document.cookie = `basic_token=; Secure; SameSite=Strict`;
|
|
261
|
+
localStorage.removeItem('basic_auth_state')
|
|
262
|
+
|
|
263
|
+
if (syncRef.current) {
|
|
264
|
+
// WIP - BUG - sometimes connects even after signout
|
|
265
|
+
syncRef.current.disconnect()
|
|
266
|
+
}
|
|
191
267
|
}
|
|
192
268
|
|
|
193
269
|
const getToken = async (): Promise<string> => {
|
|
194
|
-
|
|
270
|
+
log('getting token...')
|
|
195
271
|
|
|
196
272
|
if (!token) {
|
|
197
|
-
|
|
273
|
+
log('no token found')
|
|
198
274
|
throw new Error('no token found')
|
|
199
275
|
}
|
|
200
276
|
|
|
@@ -202,7 +278,7 @@ export function BasicProvider({ children, project_id, schema }: { children: Reac
|
|
|
202
278
|
const isExpired = decoded.exp && decoded.exp < Date.now() / 1000
|
|
203
279
|
|
|
204
280
|
if (isExpired) {
|
|
205
|
-
|
|
281
|
+
log('token is expired - refreshing ...')
|
|
206
282
|
const newToken = await fetchToken(token?.refresh)
|
|
207
283
|
return newToken?.access_token || ''
|
|
208
284
|
}
|
|
@@ -234,45 +310,56 @@ export function BasicProvider({ children, project_id, schema }: { children: Reac
|
|
|
234
310
|
body: JSON.stringify({ code: code })
|
|
235
311
|
})
|
|
236
312
|
.then(response => response.json())
|
|
237
|
-
.catch(error =>
|
|
313
|
+
.catch(error => log('Error:', error))
|
|
238
314
|
|
|
239
315
|
if (token.error) {
|
|
240
|
-
|
|
316
|
+
log('error fetching token', token.error)
|
|
241
317
|
return
|
|
242
318
|
} else {
|
|
243
|
-
//
|
|
319
|
+
// log('token', token)
|
|
244
320
|
setToken(token)
|
|
245
321
|
}
|
|
246
322
|
return token
|
|
247
323
|
}
|
|
248
324
|
|
|
249
325
|
useEffect(() => {
|
|
250
|
-
|
|
251
|
-
let cookie_token = getCookie('basic_token')
|
|
252
|
-
if (cookie_token !== '') {
|
|
253
|
-
setToken(JSON.parse(cookie_token))
|
|
254
|
-
}
|
|
326
|
+
localStorage.setItem('basic_debug', debug ? 'true' : 'false')
|
|
255
327
|
|
|
328
|
+
try {
|
|
256
329
|
if (window.location.search.includes('code')) {
|
|
257
330
|
let code = window.location?.search?.split('code=')[1].split('&')[0]
|
|
258
|
-
// console.log('code found', code)
|
|
259
331
|
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
332
|
+
const state = localStorage.getItem('basic_auth_state')
|
|
333
|
+
if (!state || state !== window.location.search.split('state=')[1].split('&')[0]) {
|
|
334
|
+
log('error: auth state does not match')
|
|
335
|
+
setIsAuthReady(true)
|
|
263
336
|
|
|
264
|
-
|
|
337
|
+
localStorage.removeItem('basic_auth_state')
|
|
338
|
+
window.history.pushState({}, document.title, "/");
|
|
339
|
+
return
|
|
340
|
+
}
|
|
265
341
|
|
|
266
|
-
|
|
267
|
-
|
|
342
|
+
localStorage.removeItem('basic_auth_state')
|
|
343
|
+
|
|
344
|
+
fetchToken(code)
|
|
345
|
+
} else {
|
|
346
|
+
let cookie_token = getCookie('basic_token')
|
|
347
|
+
if (cookie_token !== '') {
|
|
348
|
+
setToken(JSON.parse(cookie_token))
|
|
349
|
+
} else {
|
|
350
|
+
setIsAuthReady(true)
|
|
351
|
+
}
|
|
268
352
|
}
|
|
353
|
+
|
|
354
|
+
|
|
269
355
|
} catch (e) {
|
|
270
|
-
|
|
356
|
+
log('error getting cookie', e)
|
|
271
357
|
}
|
|
272
358
|
}, [])
|
|
273
359
|
|
|
274
360
|
useEffect(() => {
|
|
275
361
|
async function fetchUser(acc_token: string) {
|
|
362
|
+
console.info('fetching user')
|
|
276
363
|
const user = await fetch('https://api.basic.tech/auth/userInfo', {
|
|
277
364
|
method: 'GET',
|
|
278
365
|
headers: {
|
|
@@ -280,24 +367,32 @@ export function BasicProvider({ children, project_id, schema }: { children: Reac
|
|
|
280
367
|
}
|
|
281
368
|
})
|
|
282
369
|
.then(response => response.json())
|
|
283
|
-
.catch(error =>
|
|
370
|
+
.catch(error => log('Error:', error))
|
|
284
371
|
|
|
285
372
|
if (user.error) {
|
|
286
|
-
|
|
373
|
+
log('error fetching user', user.error)
|
|
287
374
|
// refreshToken()
|
|
288
375
|
return
|
|
289
376
|
} else {
|
|
290
|
-
//
|
|
377
|
+
// log('user', user)
|
|
291
378
|
document.cookie = `basic_token=${JSON.stringify(token)}; Secure; SameSite=Strict`;
|
|
379
|
+
|
|
380
|
+
if (window.location.search.includes('code')) {
|
|
381
|
+
window.history.pushState({}, document.title, "/");
|
|
382
|
+
}
|
|
383
|
+
|
|
292
384
|
setUser(user)
|
|
293
385
|
setIsSignedIn(true)
|
|
294
|
-
|
|
386
|
+
|
|
387
|
+
setIsAuthReady(true)
|
|
295
388
|
}
|
|
296
389
|
}
|
|
297
390
|
|
|
298
391
|
async function checkToken() {
|
|
299
392
|
if (!token) {
|
|
300
|
-
|
|
393
|
+
log('error: no user token found')
|
|
394
|
+
|
|
395
|
+
setIsAuthReady(true)
|
|
301
396
|
return
|
|
302
397
|
}
|
|
303
398
|
|
|
@@ -305,7 +400,7 @@ export function BasicProvider({ children, project_id, schema }: { children: Reac
|
|
|
305
400
|
const isExpired = decoded.exp && decoded.exp < Date.now() / 1000
|
|
306
401
|
|
|
307
402
|
if (isExpired) {
|
|
308
|
-
|
|
403
|
+
log('token is expired - refreshing ...')
|
|
309
404
|
const newToken = await fetchToken(token?.refresh)
|
|
310
405
|
fetchUser(newToken.access_token)
|
|
311
406
|
} else {
|
|
@@ -315,8 +410,7 @@ export function BasicProvider({ children, project_id, schema }: { children: Reac
|
|
|
315
410
|
|
|
316
411
|
if (token) {
|
|
317
412
|
checkToken()
|
|
318
|
-
|
|
319
|
-
}
|
|
413
|
+
}
|
|
320
414
|
}, [token])
|
|
321
415
|
|
|
322
416
|
|
|
@@ -356,7 +450,7 @@ export function BasicProvider({ children, project_id, schema }: { children: Reac
|
|
|
356
450
|
return (
|
|
357
451
|
<BasicContext.Provider value={{
|
|
358
452
|
unicorn: "🦄",
|
|
359
|
-
|
|
453
|
+
isAuthReady,
|
|
360
454
|
isSignedIn,
|
|
361
455
|
user,
|
|
362
456
|
signout,
|
|
@@ -366,11 +460,39 @@ export function BasicProvider({ children, project_id, schema }: { children: Reac
|
|
|
366
460
|
db: syncRef.current,
|
|
367
461
|
dbStatus
|
|
368
462
|
}}>
|
|
463
|
+
{error && <ErrorDisplay error={error} />}
|
|
369
464
|
{syncRef.current ? children : null}
|
|
370
465
|
</BasicContext.Provider>
|
|
371
466
|
)
|
|
372
467
|
}
|
|
373
468
|
|
|
469
|
+
function ErrorDisplay({ error }: { error: ErrorObject }) {
|
|
470
|
+
return <div style={{
|
|
471
|
+
position: 'absolute',
|
|
472
|
+
top: 20,
|
|
473
|
+
left: 20,
|
|
474
|
+
color: 'black',
|
|
475
|
+
backgroundColor: '#f8d7da',
|
|
476
|
+
border: '1px solid #f5c6cb',
|
|
477
|
+
borderRadius: '4px',
|
|
478
|
+
padding: '20px',
|
|
479
|
+
maxWidth: '400px',
|
|
480
|
+
margin: '20px auto',
|
|
481
|
+
boxShadow: '0 2px 4px rgba(0, 0, 0, 0.1)',
|
|
482
|
+
fontFamily: 'monospace',
|
|
483
|
+
}}>
|
|
484
|
+
<h3 style={{fontSize: '0.8rem', opacity: 0.8}}>code: {error.code}</h3>
|
|
485
|
+
<h1 style={{fontSize: '1.2rem', lineHeight: '1.5'}}>{error.title}</h1>
|
|
486
|
+
<p>{error.message}</p>
|
|
487
|
+
</div>
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
/*
|
|
491
|
+
possible errors:
|
|
492
|
+
- projectid missing / invalid
|
|
493
|
+
- schema missing / invalid
|
|
494
|
+
*/
|
|
495
|
+
|
|
374
496
|
export function useBasic() {
|
|
375
497
|
return useContext(BasicContext);
|
|
376
|
-
}
|
|
498
|
+
}
|
package/src/config.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
|
|
2
|
+
export const SERVER_URL = "https://api.basic.tech"
|
|
3
|
+
// export const SERVER_URL = "http://localhost:3000"
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
export const log = (...args: any[]) => {
|
|
7
|
+
try {
|
|
8
|
+
if (localStorage.getItem('basic_debug') === 'true') {
|
|
9
|
+
console.log('[basic]', ...args)
|
|
10
|
+
}
|
|
11
|
+
} catch (e) {
|
|
12
|
+
// console.log('error logging', e)
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// export const log = (message: string, ...args: any[]) => {
|
|
17
|
+
// try {
|
|
18
|
+
// if (process.env.NODE_ENV === 'development') {
|
|
19
|
+
// const stack = new Error().stack;
|
|
20
|
+
// const caller = stack?.split('\n')[2]?.trim();
|
|
21
|
+
// console.log(`[basic] ${message}`, ...args);
|
|
22
|
+
// // console.log(`[stack] ${caller}`);
|
|
23
|
+
// }
|
|
24
|
+
// } catch (e) {
|
|
25
|
+
// console.error('Error in logWithStack:', e);
|
|
26
|
+
// }
|
|
27
|
+
// }
|