@basictech/react 0.1.0 → 0.2.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@basictech/react",
3
- "version": "0.1.0",
3
+ "version": "0.2.0-beta.1",
4
4
  "description": "",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
@@ -17,7 +17,8 @@
17
17
  "author": "",
18
18
  "license": "ISC",
19
19
  "dependencies": {
20
- "jwt-decode": "^4.0.0"
20
+ "jwt-decode": "^4.0.0",
21
+ "@repo/sync": "0.1.0-beta.0"
21
22
  },
22
23
  "devDependencies": {
23
24
  "tsup": "^7.2.0",
package/readme.md ADDED
@@ -0,0 +1,107 @@
1
+ # @basictech/react
2
+
3
+ A React package for integrating Basic authentication and database functionality into your React applications.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @basictech/react
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ### 1. Wrap your application with BasicProvider
14
+
15
+ In your root component or App.tsx, wrap your application with the `BasicProvider`:
16
+
17
+ ```typescript
18
+ import { BasicProvider } from '@basictech/react';
19
+
20
+ function App() {
21
+ return (
22
+ <BasicProvider project_id="YOUR_PROJECT_ID">
23
+ {/* Your app components */}
24
+ </BasicProvider>
25
+ );
26
+ }
27
+
28
+ export default App;
29
+ ```
30
+
31
+ Replace `YOUR_PROJECT_ID` with your actual Basic project ID.
32
+
33
+ ### 2. Use the useBasic hook
34
+
35
+ In your components, you can use the `useBasic` hook to access authentication and database functionality:
36
+
37
+ ```typescript
38
+ import { useBasic } from '@basictech/react';
39
+
40
+ function MyComponent() {
41
+ const { user, isSignedIn, signin, signout, db } = useBasic();
42
+
43
+ if (!isSignedIn) {
44
+ return <button onClick={signin}>Sign In</button>;
45
+ }
46
+
47
+ return (
48
+ <div>
49
+ <h1>Welcome, {user.name}!</h1>
50
+ <button onClick={signout}>Sign Out</button>
51
+ </div>
52
+ );
53
+ }
54
+ ```
55
+
56
+ ### 3. Database Operations
57
+
58
+ You can perform database operations using the `db` object:
59
+
60
+ ```typescript
61
+ const { db } = useBasic();
62
+
63
+ // Get data
64
+ const getData = async () => {
65
+ const data = await db.table('myTable').get();
66
+ console.log(data);
67
+ };
68
+
69
+ // Add data
70
+ const addData = async () => {
71
+ const result = await db.table('myTable').add({ key: 'value' });
72
+ console.log(result);
73
+ };
74
+
75
+ // Update data
76
+ const updateData = async () => {
77
+ const result = await db.table('myTable').update('itemId', { key: 'newValue' });
78
+ console.log(result);
79
+ };
80
+ ```
81
+
82
+ ## API Reference
83
+
84
+ ### useBasic()
85
+
86
+ Returns an object with the following properties and methods:
87
+
88
+ - `user`: The current user object
89
+ - `isSignedIn`: Boolean indicating if the user is signed in
90
+ - `signin()`: Function to initiate the sign-in process
91
+ - `signout()`: Function to sign out the user
92
+ - `db`: Object for database operations
93
+
94
+ ### db
95
+
96
+ The `db` object provides the following methods:
97
+
98
+ - `table(tableName)`: Selects a table for operations
99
+ - `get()`: Retrieves all items from the table
100
+ - `add(value)`: Adds a new item to the table
101
+ - `update(id, value)`: Updates an item in the table
102
+
103
+ ## License
104
+
105
+ ISC
106
+
107
+ ---
@@ -1,64 +1,170 @@
1
- //@ts-nocheck
2
- import React, { createContext, useContext, useEffect, useState } from 'react'
3
- import { jwtDecode} from 'jwt-decode'
1
+ // @ts-nocheck
4
2
 
3
+ import React, { createContext, useContext, useEffect, useState, useRef } from 'react'
4
+ import { jwtDecode } from 'jwt-decode'
5
+
6
+
7
+
8
+ import { BasicSync } from '@repo/sync'
5
9
  import { get, add, update, deleteRecord } from './db'
6
10
 
7
- type User = {
11
+ type BasicSyncType = {
12
+ basic_schema: any;
13
+ connect: (options: { access_token: string }) => void;
14
+ debugeroo: () => void;
15
+ collection: (name: string) => {
16
+ ref: {
17
+ toArray: () => Promise<any[]>;
18
+ count: () => Promise<number>;
19
+ };
20
+ };
21
+ [key: string]: any; // For other potential methods and properties
22
+ };
23
+
24
+
25
+ enum DBStatus {
26
+ LOADING = "LOADING",
27
+ OFFLINE = "OFFLINE",
28
+ CONNECTING = "CONNECTING",
29
+ ONLINE = "ONLINE",
30
+ SYNCING = "SYNCING",
31
+ ERROR = "ERROR"
32
+ }
33
+
34
+ type User = {
8
35
  name?: string,
9
36
  email?: string,
10
37
  id?: string,
11
38
  primaryEmailAddress?: {
12
39
  emailAddress: string
13
- },
40
+ },
14
41
  fullName?: string
15
42
  }
43
+ type Token = {
44
+ access_token: string,
45
+ token_type: string,
46
+ expires_in: number,
47
+ refresh: string,
48
+ }
16
49
 
17
- export const BasicContext = createContext<{
18
- unicorn: string,
50
+ export const BasicContext = createContext<{
51
+ unicorn: string,
19
52
  isLoaded: boolean,
20
53
  isSignedIn: boolean,
21
54
  user: User | null,
22
55
  signout: () => void,
23
56
  signin: () => void,
24
57
  getToken: () => Promise<string>,
25
- getSignInLink: () => string,
26
- db: any
58
+ getSignInLink: () => string,
59
+ db: any,
60
+ dbStatus: DBStatus
27
61
  }>({
28
- unicorn: "🦄",
62
+ unicorn: "🦄",
29
63
  isLoaded: false,
30
64
  isSignedIn: false,
31
65
  user: null,
32
- signout: () => {},
33
- signin: () => {},
34
- getToken: () => new Promise(() => {}),
66
+ signout: () => { },
67
+ signin: () => { },
68
+ getToken: () => new Promise(() => { }),
35
69
  getSignInLink: () => "",
36
- db: {}
70
+ db: {},
71
+ dbStatus: DBStatus.OFFLINE
37
72
  });
38
73
 
39
- type Token = {
40
- access_token: string,
41
- token_type: string,
42
- expires_in: number,
43
- refresh: string,
44
- }
74
+ const EmptyDB: BasicSyncType = {
75
+ isOpen: false,
76
+ collection: () => {
77
+ return {
78
+ ref: {
79
+ toArray: () => [],
80
+ count: () => 0
81
+ }
82
+ }
83
+ }
84
+ }
45
85
 
46
- export function BasicProvider({children, project_id}: {children: React.ReactNode, project_id: string}) {
86
+
87
+ function getSyncStatus(statusCode: number): string {
88
+ switch (statusCode) {
89
+ case -1:
90
+ return "ERROR";
91
+ case 0:
92
+ return "OFFLINE";
93
+ case 1:
94
+ return "CONNECTING";
95
+ case 2:
96
+ return "ONLINE";
97
+ case 3:
98
+ return "SYNCING";
99
+ case 4:
100
+ return "ERROR_WILL_RETRY";
101
+ default:
102
+ return "UNKNOWN";
103
+ }
104
+ }
105
+
106
+ export function BasicProvider({ children, project_id, schema }: { children: React.ReactNode, project_id: string, schema: any }) {
47
107
  const [isLoaded, setIsLoaded] = useState(false)
48
- const [isSignedIn, setIsSignedIn] = useState(false)
108
+ const [isSignedIn, setIsSignedIn] = useState(false)
49
109
  const [token, setToken] = useState<Token | null>(null)
50
110
  const [authCode, setAuthCode] = useState<string | null>(null)
51
- const [user, setUser] = useState<User>({})
111
+ const [user, setUser] = useState<User>({})
112
+
113
+ const [dbStatus, setDbStatus] = useState<DBStatus>(DBStatus.OFFLINE)
114
+
115
+
116
+ const syncRef = useRef<BasicSync | null>(null);
117
+
118
+ useEffect(() => {
119
+ if (!syncRef.current) {
120
+ syncRef.current = new BasicSync('basicdb', { schema: schema });
121
+
122
+ // console.log('db is open', syncRef.current.isOpen())
123
+ // syncRef.current.open()
124
+ // .then(() => {
125
+ // console.log("is open now:", syncRef.current.isOpen())
126
+ // })
127
+
128
+ syncRef.current.handleStatusChange((status: number, url: string) => {
129
+ setDbStatus(getSyncStatus(status))
130
+ })
131
+
132
+ syncRef.current.syncable.getStatus().then((status) => {
133
+ console.log('sync status', getSyncStatus(status))
134
+ })
135
+
136
+ }
137
+ }, []);
138
+
52
139
 
53
140
  //todo:
54
141
  //add random state to signin link & verify random state
55
142
 
143
+ const connectToDb = async () => {
144
+
145
+ const tok = await getToken()
146
+
147
+ console.log('connecting to db...', tok.substring(0, 10))
148
+
149
+ syncRef.current.connect({ access_token: tok })
150
+ .catch((e) => {
151
+ console.log('error connecting to db', e)
152
+ })
153
+ }
154
+
155
+ useEffect(() => {
156
+ if (token) {
157
+ connectToDb()
158
+ }
159
+ }, [token])
160
+
56
161
  const getSignInLink = () => {
57
162
  console.log('getting sign in link...')
58
163
 
59
164
  const randomState = Math.random().toString(36).substring(7);
60
165
 
61
- let baseUrl = "https://api.basic.tech/auth/authorize"
166
+ // let baseUrl = "https://api.basic.tech/auth/authorize"
167
+ let baseUrl = "http://localhost:3003/auth/authorize"
62
168
  baseUrl += `?client_id=${project_id}`
63
169
  baseUrl += `&redirect_uri=${encodeURIComponent(window.location.href)}`
64
170
  baseUrl += `&response_type=code`
@@ -68,8 +174,8 @@ export function BasicProvider({children, project_id}: {children: React.ReactNode
68
174
  return baseUrl;
69
175
  }
70
176
 
71
- const signin = () => {
72
- console.log('signing in: ', getSignInLink())
177
+ const signin = () => {
178
+ console.log('signing in: ', getSignInLink())
73
179
  const signInLink = getSignInLink()
74
180
  //todo: change to the other thing?
75
181
  window.location.href = signInLink;
@@ -84,23 +190,23 @@ export function BasicProvider({children, project_id}: {children: React.ReactNode
84
190
  document.cookie = `basic_token=; Secure; SameSite=Strict`;
85
191
  }
86
192
 
87
- const getToken = async () : Promise<string> => {
193
+ const getToken = async (): Promise<string> => {
88
194
  console.log('getting token...')
89
-
195
+
90
196
  if (!token) {
91
197
  console.log('no token found')
92
- return ''
198
+ throw new Error('no token found')
93
199
  }
94
-
200
+
95
201
  const decoded = jwtDecode(token?.access_token)
96
202
  const isExpired = decoded.exp && decoded.exp < Date.now() / 1000
97
-
203
+
98
204
  if (isExpired) {
99
205
  console.log('token is expired - refreshing ...')
100
206
  const newToken = await fetchToken(token?.refresh)
101
207
  return newToken?.access_token || ''
102
208
  }
103
-
209
+
104
210
  return token?.access_token || ''
105
211
  }
106
212
 
@@ -119,21 +225,21 @@ export function BasicProvider({children, project_id}: {children: React.ReactNode
119
225
  return cookieValue;
120
226
  }
121
227
 
122
- const fetchToken = async (code: string) => {
228
+ const fetchToken = async (code: string) => {
123
229
  const token = await fetch('https://api.basic.tech/auth/token', {
124
230
  method: 'POST',
125
231
  headers: {
126
232
  'Content-Type': 'application/json'
127
233
  },
128
- body: JSON.stringify({code: code})
234
+ body: JSON.stringify({ code: code })
129
235
  })
130
- .then(response => response.json())
131
- .catch(error => console.error('Error:', error))
236
+ .then(response => response.json())
237
+ .catch(error => console.error('Error:', error))
132
238
 
133
239
  if (token.error) {
134
240
  console.log('error fetching token', token.error)
135
241
  return
136
- } else {
242
+ } else {
137
243
  // console.log('token', token)
138
244
  setToken(token)
139
245
  }
@@ -141,42 +247,46 @@ export function BasicProvider({children, project_id}: {children: React.ReactNode
141
247
  }
142
248
 
143
249
  useEffect(() => {
144
- let cookie_token = getCookie('basic_token')
145
- if (cookie_token !== '') {
146
- setToken(JSON.parse(cookie_token))
147
- }
148
-
149
- if (window.location.search.includes('code')) {
150
- let code = window.location.search.split('code=')[1].split('&')[0]
151
- // console.log('code found', code)
152
-
153
- // todo: check state is valid
154
- setAuthCode(code) // remove this? dont need to store code?
155
- fetchToken(code)
156
-
157
- window.history.pushState({}, document.title, "/");
158
-
159
- } else {
160
- setIsLoaded(true)
250
+ try {
251
+ let cookie_token = getCookie('basic_token')
252
+ if (cookie_token !== '') {
253
+ setToken(JSON.parse(cookie_token))
254
+ }
255
+
256
+ if (window.location.search.includes('code')) {
257
+ let code = window.location?.search?.split('code=')[1].split('&')[0]
258
+ // console.log('code found', code)
259
+
260
+ // todo: check state is valid
261
+ setAuthCode(code) // remove this? dont need to store code?
262
+ fetchToken(code)
263
+
264
+ window.history.pushState({}, document.title, "/");
265
+
266
+ } else {
267
+ setIsLoaded(true)
268
+ }
269
+ } catch (e) {
270
+ console.log('error getting cookie', e)
161
271
  }
162
272
  }, [])
163
273
 
164
274
  useEffect(() => {
165
- async function fetchUser (acc_token: string) {
275
+ async function fetchUser(acc_token: string) {
166
276
  const user = await fetch('https://api.basic.tech/auth/userInfo', {
167
277
  method: 'GET',
168
278
  headers: {
169
279
  'Authorization': `Bearer ${acc_token}`
170
280
  }
171
281
  })
172
- .then(response => response.json())
173
- .catch(error => console.error('Error:', error))
282
+ .then(response => response.json())
283
+ .catch(error => console.error('Error:', error))
174
284
 
175
285
  if (user.error) {
176
286
  console.log('error fetching user', user.error)
177
287
  // refreshToken()
178
288
  return
179
- } else {
289
+ } else {
180
290
  // console.log('user', user)
181
291
  document.cookie = `basic_token=${JSON.stringify(token)}; Secure; SameSite=Strict`;
182
292
  setUser(user)
@@ -185,7 +295,7 @@ export function BasicProvider({children, project_id}: {children: React.ReactNode
185
295
  }
186
296
  }
187
297
 
188
- async function checkToken () {
298
+ async function checkToken() {
189
299
  if (!token) {
190
300
  console.log('error: no user token found')
191
301
  return
@@ -198,71 +308,69 @@ export function BasicProvider({children, project_id}: {children: React.ReactNode
198
308
  console.log('token is expired - refreshing ...')
199
309
  const newToken = await fetchToken(token?.refresh)
200
310
  fetchUser(newToken.access_token)
201
- } else {
311
+ } else {
202
312
  fetchUser(token.access_token)
203
313
  }
204
314
  }
205
315
 
206
- if (token) {
316
+ if (token) {
207
317
  checkToken()
208
318
  setIsLoaded(true)
209
319
  }
210
320
  }, [token])
211
321
 
212
-
213
322
 
214
-
215
- const db = (tableName : string) => {
216
- const checkSignIn = () => {
323
+ const db_ = (tableName: string) => {
324
+ const checkSignIn = () => {
217
325
  if (!isSignedIn) {
218
326
  throw new Error('cannot use db. user not logged in.')
219
327
  }
220
328
  }
221
329
 
222
- return {
223
- get: async () => {
224
- checkSignIn()
330
+ return {
331
+ get: async () => {
332
+ checkSignIn()
225
333
  const tok = await getToken()
226
- return get({projectId: project_id, accountId: user.id, tableName: tableName, token: tok } )
334
+ return get({ projectId: project_id, accountId: user.id, tableName: tableName, token: tok })
227
335
  },
228
- add: async (value: any) => {
229
- checkSignIn()
336
+ add: async (value: any) => {
337
+ checkSignIn()
230
338
  const tok = await getToken()
231
- return add({projectId: project_id, accountId: user.id, tableName: tableName, value: value, token: tok } )
339
+ return add({ projectId: project_id, accountId: user.id, tableName: tableName, value: value, token: tok })
232
340
  },
233
- update: async (id: string,value : any) => {
234
- checkSignIn()
341
+ update: async (id: string, value: any) => {
342
+ checkSignIn()
235
343
  const tok = await getToken()
236
- return update({projectId: project_id, accountId: user.id, tableName: tableName, id: id, value: value, token: tok } )
344
+ return update({ projectId: project_id, accountId: user.id, tableName: tableName, id: id, value: value, token: tok })
237
345
  },
238
- delete: async (id: string) => {
239
- checkSignIn()
346
+ delete: async (id: string) => {
347
+ checkSignIn()
240
348
  const tok = await getToken()
241
- return deleteRecord({projectId: project_id, accountId: user.id, tableName: tableName, id: id, token: tok } )
349
+ return deleteRecord({ projectId: project_id, accountId: user.id, tableName: tableName, id: id, token: tok })
242
350
  }
243
-
351
+
244
352
  }
245
-
353
+
246
354
  }
247
355
 
248
356
  return (
249
- <BasicContext.Provider value={{
250
- unicorn: "🦄",
251
- isLoaded,
252
- isSignedIn,
253
- user,
254
- signout,
255
- signin,
256
- getToken,
257
- getSignInLink,
258
- db,
259
-
260
- }}>
261
- {children}
262
- </BasicContext.Provider>
357
+ <BasicContext.Provider value={{
358
+ unicorn: "🦄",
359
+ isLoaded,
360
+ isSignedIn,
361
+ user,
362
+ signout,
363
+ signin,
364
+ getToken,
365
+ getSignInLink,
366
+ db: syncRef.current,
367
+ dbStatus
368
+ }}>
369
+ {syncRef.current ? children : null}
370
+ </BasicContext.Provider>
263
371
  )
264
- }
372
+ }
265
373
 
266
374
  export function useBasic() {
267
375
  return useContext(BasicContext);
268
- }
376
+ }
package/src/index.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import { useBasic, BasicProvider } from "./AuthContext";
2
+ import { useLiveQuery as useQuery } from "dexie-react-hooks";
2
3
 
3
4
  export {
4
- useBasic, BasicProvider
5
+ useBasic, BasicProvider, useQuery
5
6
  }
package/tsup.config.ts CHANGED
@@ -7,4 +7,5 @@ export default defineConfig({
7
7
  splitting: false,
8
8
  sourcemap: true,
9
9
  clean: true,
10
+ noExternal: ['@repo/sync']
10
11
  })