1
- import React from 'react' ;
1
+ import React , {
2
+ useState ,
3
+ useMemo ,
4
+ useEffect ,
5
+ createContext ,
6
+ useContext ,
7
+ // types
8
+ Dispatch ,
9
+ SetStateAction ,
10
+ ReactNode ,
11
+ useCallback ,
12
+ } from 'react' ;
2
13
3
- import GoTrue , { User , Settings } from 'gotrue-js' ;
14
+ import GoTrue , {
15
+ User as GoTrueUser ,
16
+ Settings as GoTrueSettings ,
17
+ } from 'gotrue-js' ;
4
18
import { runRoutes } from './runRoutes' ;
19
+ import { TokenParam , defaultParam } from './token' ;
5
20
6
21
type authChangeParam = ( user ?: User ) => string | void ;
7
22
8
- export type Settings = Settings ;
9
- export type User = User ;
23
+ export type Settings = GoTrueSettings ;
24
+ export type User = GoTrueUser ;
25
+ type Provider = 'bitbucket' | 'github' | 'gitlab' | 'google' ;
10
26
11
27
const defaultSettings = {
12
28
autoconfirm : false ,
@@ -21,29 +37,34 @@ const defaultSettings = {
21
37
} ,
22
38
} ;
23
39
40
+ const errors = {
41
+ noUserFound : 'No current user found - are you logged in?' ,
42
+ noUserTokenFound : 'no user token found' ,
43
+ tokenMissingOrInvalid : 'either no token found or invalid for this purpose' ,
44
+ } ;
45
+
46
+ type MaybeUserPromise = Promise < User | undefined > ;
47
+
24
48
export type ReactNetlifyIdentityAPI = {
25
49
user : User | undefined ;
26
50
/** not meant for normal use! you should mostly use one of the other exported methods to update the user instance */
27
- setUser : React . Dispatch < React . SetStateAction < User | undefined > > ;
51
+ setUser : Dispatch < SetStateAction < User | undefined > > ;
28
52
isConfirmedUser : boolean ;
29
53
isLoggedIn : boolean ;
30
54
signupUser : (
31
55
email : string ,
32
56
password : string ,
33
57
data : Object
34
- ) => Promise < User | undefined > ;
58
+ ) => MaybeUserPromise ;
35
59
loginUser : (
36
60
email : string ,
37
61
password : string ,
38
62
remember ?: boolean
39
- ) => Promise < User | undefined > ;
40
- logoutUser : ( ) => Promise < User | undefined > ;
63
+ ) => MaybeUserPromise ;
64
+ logoutUser : ( ) => MaybeUserPromise ;
41
65
requestPasswordRecovery : ( email : string ) => Promise < void > ;
42
- recoverAccount : (
43
- token : string ,
44
- remember ?: boolean | undefined
45
- ) => Promise < User > ;
46
- updateUser : ( fields : { data : object } ) => Promise < User | undefined > ;
66
+ recoverAccount : ( remember ?: boolean ) => MaybeUserPromise ;
67
+ updateUser : ( fields : { data : object } ) => MaybeUserPromise ;
47
68
getFreshJWT : ( ) => Promise < string > ;
48
69
authedFetch : {
49
70
get : ( endpoint : string , obj ?: { } ) => Promise < any > ;
@@ -53,14 +74,10 @@ export type ReactNetlifyIdentityAPI = {
53
74
} ;
54
75
_goTrueInstance : GoTrue ;
55
76
_url : string ;
56
- loginProvider : (
57
- provider : 'bitbucket' | 'github' | 'gitlab' | 'google'
58
- ) => void ;
59
- acceptInviteExternalUrl : (
60
- provider : 'bitbucket' | 'github' | 'gitlab' | 'google' ,
61
- token : string
62
- ) => string ;
77
+ loginProvider : ( provider : Provider ) => void ;
78
+ acceptInviteExternalUrl : ( provider : Provider ) => string ;
63
79
settings : Settings ;
80
+ param : TokenParam ;
64
81
} ;
65
82
66
83
const [ _useIdentityContext , _IdentityCtxProvider ] = createCtx <
@@ -75,7 +92,7 @@ export function IdentityContextProvider({
75
92
onAuthChange = ( ) => { } ,
76
93
} : {
77
94
url : string ;
78
- children : React . ReactNode ;
95
+ children : ReactNode ;
79
96
onAuthChange ?: authChangeParam ;
80
97
} ) {
81
98
/******** SETUP */
@@ -99,7 +116,7 @@ export function useNetlifyIdentity(
99
116
onAuthChange : authChangeParam = ( ) => { } ,
100
117
enableRunRoutes : boolean = true
101
118
) : ReactNetlifyIdentityAPI {
102
- const goTrueInstance = React . useMemo (
119
+ const goTrueInstance = useMemo (
103
120
( ) =>
104
121
new GoTrue ( {
105
122
APIUrl : `${ url } /.netlify/identity` ,
@@ -108,87 +125,164 @@ export function useNetlifyIdentity(
108
125
[ url ]
109
126
) ;
110
127
111
- const [ user , setUser ] = React . useState < User | undefined > (
128
+ /******* STATE and EFFECTS */
129
+
130
+ const [ user , setUser ] = useState < User | undefined > (
112
131
goTrueInstance . currentUser ( ) || undefined
113
132
) ;
114
- const _setUser = ( _user : User | undefined ) => {
115
- setUser ( _user ) ;
116
- onAuthChange ( _user ) ; // if someone's subscribed to auth changes, let 'em know
117
- return _user ; // so that we can continue chaining
118
- } ;
119
133
120
- React . useEffect ( ( ) => {
134
+ const _setUser = useCallback (
135
+ ( _user : User | undefined ) => {
136
+ setUser ( _user ) ;
137
+ onAuthChange ( _user ) ; // if someone's subscribed to auth changes, let 'em know
138
+ return _user ; // so that we can continue chaining
139
+ } ,
140
+ [ onAuthChange ]
141
+ ) ;
142
+
143
+ const [ param , setParam ] = useState < TokenParam > ( defaultParam ) ;
144
+
145
+ useEffect ( ( ) => {
121
146
if ( enableRunRoutes ) {
122
- runRoutes ( goTrueInstance , _setUser ) ;
147
+ const param = runRoutes ( goTrueInstance , _setUser ) ;
148
+
149
+ if ( param . token || param . error ) {
150
+ setParam ( param ) ;
151
+ }
123
152
}
124
153
} , [ ] ) ;
125
154
155
+ const [ settings , setSettings ] = useState < Settings > ( defaultSettings ) ;
156
+
157
+ useEffect ( ( ) => {
158
+ goTrueInstance . settings
159
+ . bind ( goTrueInstance ) ( )
160
+ . then ( x => setSettings ( x ) ) ;
161
+ } , [ ] ) ;
162
+
126
163
/******* OPERATIONS */
127
164
// make sure the Registration preferences under Identity settings in your Netlify dashboard are set to Open.
128
165
// https://react-netlify-identity.netlify.com/login#access_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1NTY0ODY3MjEsInN1YiI6ImNiZjY5MTZlLTNlZGYtNGFkNS1iOTYzLTQ4ZTY2NDcyMDkxNyIsImVtYWlsIjoic2hhd250aGUxQGdtYWlsLmNvbSIsImFwcF9tZXRhZGF0YSI6eyJwcm92aWRlciI6ImdpdGh1YiJ9LCJ1c2VyX21ldGFkYXRhIjp7ImF2YXRhcl91cmwiOiJodHRwczovL2F2YXRhcnMxLmdpdGh1YnVzZXJjb250ZW50LmNvbS91LzY3NjQ5NTc_dj00IiwiZnVsbF9uYW1lIjoic3d5eCJ9fQ.E8RrnuCcqq-mLi1_Q5WHJ-9THIdQ3ha1mePBKGhudM0&expires_in=3600&refresh_token=OyA_EdRc7WOIVhY7RiRw5w&token_type=bearer
129
166
/******* external oauth */
130
- type Provider = 'bitbucket' | 'github' | 'gitlab' | 'google' ;
131
167
132
- const loginProvider = ( provider : Provider ) => {
133
- const url = goTrueInstance . loginExternalUrl ( provider ) ;
134
- window . location . href = url ;
135
- } ;
136
- const acceptInviteExternalUrl = ( provider : Provider , token : string ) =>
137
- goTrueInstance . acceptInviteExternalUrl ( provider , token ) ;
138
- const _settings = goTrueInstance . settings . bind ( goTrueInstance ) ;
139
- const [ settings , setSettings ] = React . useState < Settings > ( defaultSettings ) ;
140
- React . useEffect ( ( ) => {
141
- _settings ( ) . then ( x => setSettings ( x ) ) ;
142
- } , [ ] ) ;
168
+ const loginProvider = useCallback (
169
+ ( provider : Provider ) => {
170
+ const url = goTrueInstance . loginExternalUrl ( provider ) ;
171
+ window . location . href = url ;
172
+ } ,
173
+ [ goTrueInstance ]
174
+ ) ;
175
+
176
+ const acceptInviteExternalUrl = useCallback (
177
+ ( provider : Provider ) => {
178
+ if ( ! param . token || param . type !== 'invite' ) {
179
+ throw new Error ( errors . tokenMissingOrInvalid ) ;
180
+ }
181
+
182
+ const url = goTrueInstance . acceptInviteExternalUrl ( provider , param . token ) ;
183
+ // clean up consumed token
184
+ setParam ( defaultParam ) ;
185
+
186
+ return url ;
187
+ } ,
188
+ [ goTrueInstance , param ]
189
+ ) ;
143
190
144
191
/******* email auth */
145
- const signupUser = ( email : string , password : string , data : Object ) =>
146
- goTrueInstance . signup ( email , password , data ) . then ( _setUser ) ; // TODO: make setUser optional?
147
- const loginUser = (
148
- email : string ,
149
- password : string ,
150
- remember : boolean = true
151
- ) => goTrueInstance . login ( email , password , remember ) . then ( _setUser ) ;
152
- const requestPasswordRecovery = ( email : string ) =>
153
- goTrueInstance . requestPasswordRecovery ( email ) ;
154
- const recoverAccount = ( token : string , remember ?: boolean | undefined ) =>
155
- goTrueInstance . recover ( token , remember ) ;
156
- const updateUser = ( fields : { data : object } ) => {
157
- if ( user == null ) {
158
- throw new Error ( 'No current user found - are you logged in?' ) ;
159
- } else {
192
+ const signupUser = useCallback (
193
+ (
194
+ email : string ,
195
+ password : string ,
196
+ data : Object ,
197
+ directLogin : boolean = true
198
+ ) =>
199
+ goTrueInstance . signup ( email , password , data ) . then ( user => {
200
+ if ( directLogin ) {
201
+ return _setUser ( user ) ;
202
+ }
203
+
204
+ return user ;
205
+ } ) ,
206
+ [ goTrueInstance , _setUser ]
207
+ ) ;
208
+
209
+ const loginUser = useCallback (
210
+ ( email : string , password : string , remember : boolean = true ) =>
211
+ goTrueInstance . login ( email , password , remember ) . then ( _setUser ) ,
212
+ [ goTrueInstance , _setUser ]
213
+ ) ;
214
+
215
+ const requestPasswordRecovery = useCallback (
216
+ ( email : string ) => goTrueInstance . requestPasswordRecovery ( email ) ,
217
+ [ goTrueInstance ]
218
+ ) ;
219
+
220
+ const recoverAccount = useCallback (
221
+ ( remember ?: boolean | undefined ) => {
222
+ if ( ! param . token || param . type !== 'recovery' ) {
223
+ throw new Error ( errors . tokenMissingOrInvalid ) ;
224
+ }
225
+
226
+ return goTrueInstance . recover ( param . token , remember ) . then ( user => {
227
+ // clean up consumed token
228
+ setParam ( defaultParam ) ;
229
+ return _setUser ( user ) ;
230
+ } ) ;
231
+ } ,
232
+ [ goTrueInstance , _setUser , param ]
233
+ ) ;
234
+
235
+ const updateUser = useCallback (
236
+ ( fields : { data : object } ) => {
237
+ if ( ! user ) {
238
+ throw new Error ( errors . noUserFound ) ;
239
+ }
240
+
160
241
return user !
161
242
. update ( fields ) // e.g. { data: { email: "example@example.com", password: "password" } }
162
243
. then ( _setUser ) ;
244
+ } ,
245
+ [ user ]
246
+ ) ;
247
+
248
+ const getFreshJWT = useCallback ( ( ) => {
249
+ if ( ! user ) {
250
+ throw new Error ( errors . noUserFound ) ;
163
251
}
164
- } ;
165
- const getFreshJWT = ( ) => {
166
- if ( ! user ) throw new Error ( 'No current user found - are you logged in?' ) ;
252
+
167
253
return user . jwt ( ) ;
168
- } ;
169
- const logoutUser = ( ) => {
170
- if ( ! user ) throw new Error ( 'No current user found - are you logged in?' ) ;
254
+ } , [ user ] ) ;
255
+
256
+ const logoutUser = useCallback ( ( ) => {
257
+ if ( ! user ) {
258
+ throw new Error ( errors . noUserFound ) ;
259
+ }
260
+
171
261
return user . logout ( ) . then ( ( ) => _setUser ( undefined ) ) ;
172
- } ;
262
+ } , [ user ] ) ;
173
263
174
264
const genericAuthedFetch = ( method : string ) => (
175
265
endpoint : string ,
176
- obj = { }
266
+ options : RequestInit = { }
177
267
) => {
178
- if ( ! user || ! user . token || ! user . token . access_token )
179
- throw new Error ( 'no user token found' ) ;
268
+ if ( ! user ?. token ?. access_token ) {
269
+ throw new Error ( errors . noUserTokenFound ) ;
270
+ }
271
+
180
272
const defaultObj = {
181
273
headers : {
182
274
Accept : 'application/json' ,
183
275
'Content-Type' : 'application/json' ,
184
276
Authorization : 'Bearer ' + user . token . access_token ,
185
277
} ,
186
278
} ;
187
- const finalObj = Object . assign ( defaultObj , { method } , obj ) ;
279
+ const finalObj = Object . assign ( defaultObj , { method } , options ) ;
280
+
188
281
return fetch ( endpoint , finalObj ) . then ( res =>
189
282
finalObj . headers [ 'Content-Type' ] === 'application/json' ? res . json ( ) : res
190
283
) ;
191
284
} ;
285
+
192
286
const authedFetch = {
193
287
get : genericAuthedFetch ( 'GET' ) ,
194
288
post : genericAuthedFetch ( 'POST' ) ,
@@ -216,6 +310,7 @@ export function useNetlifyIdentity(
216
310
loginProvider,
217
311
acceptInviteExternalUrl,
218
312
settings,
313
+ param,
219
314
} ;
220
315
}
221
316
@@ -234,9 +329,9 @@ function validateUrl(value: string) {
234
329
235
330
// lazy initialize contexts without providing a Nullable type upfront
236
331
function createCtx < A > ( ) {
237
- const ctx = React . createContext < A | undefined > ( undefined ) ;
332
+ const ctx = createContext < A | undefined > ( undefined ) ;
238
333
function useCtx ( ) {
239
- const c = React . useContext ( ctx ) ;
334
+ const c = useContext ( ctx ) ;
240
335
if ( ! c ) throw new Error ( 'useCtx must be inside a Provider with a value' ) ;
241
336
return c ;
242
337
}
0 commit comments