@jbrowse/plugin-authentication 2.5.0 → 2.6.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.
Files changed (96) hide show
  1. package/dist/DropboxOAuthModel/configSchema.d.ts +3 -14
  2. package/dist/DropboxOAuthModel/configSchema.js +0 -8
  3. package/dist/DropboxOAuthModel/configSchema.js.map +1 -1
  4. package/dist/DropboxOAuthModel/model.d.ts +35 -33
  5. package/dist/DropboxOAuthModel/model.js +26 -35
  6. package/dist/DropboxOAuthModel/model.js.map +1 -1
  7. package/dist/DropboxOAuthModel/util.d.ts +1 -0
  8. package/dist/DropboxOAuthModel/util.js +28 -0
  9. package/dist/DropboxOAuthModel/util.js.map +1 -0
  10. package/dist/ExternalTokenModel/ExternalTokenEntryForm.d.ts +2 -1
  11. package/dist/ExternalTokenModel/ExternalTokenEntryForm.js +10 -11
  12. package/dist/ExternalTokenModel/ExternalTokenEntryForm.js.map +1 -1
  13. package/dist/GoogleDriveOAuthModel/GoogleDriveFilehandle.d.ts +15 -0
  14. package/dist/GoogleDriveOAuthModel/GoogleDriveFilehandle.js +20 -0
  15. package/dist/GoogleDriveOAuthModel/GoogleDriveFilehandle.js.map +1 -0
  16. package/dist/GoogleDriveOAuthModel/configSchema.d.ts +0 -5
  17. package/dist/GoogleDriveOAuthModel/model.d.ts +36 -121
  18. package/dist/GoogleDriveOAuthModel/model.js +38 -51
  19. package/dist/GoogleDriveOAuthModel/model.js.map +1 -1
  20. package/dist/GoogleDriveOAuthModel/util.d.ts +1 -0
  21. package/dist/GoogleDriveOAuthModel/util.js +17 -0
  22. package/dist/GoogleDriveOAuthModel/util.js.map +1 -0
  23. package/dist/HTTPBasicModel/HTTPBasicLoginForm.d.ts +2 -1
  24. package/dist/HTTPBasicModel/HTTPBasicLoginForm.js +17 -21
  25. package/dist/HTTPBasicModel/HTTPBasicLoginForm.js.map +1 -1
  26. package/dist/HTTPBasicModel/model.d.ts +36 -4
  27. package/dist/HTTPBasicModel/model.js +24 -9
  28. package/dist/HTTPBasicModel/model.js.map +1 -1
  29. package/dist/OAuthModel/configSchema.d.ts +0 -8
  30. package/dist/OAuthModel/configSchema.js +1 -9
  31. package/dist/OAuthModel/configSchema.js.map +1 -1
  32. package/dist/OAuthModel/model.d.ts +103 -18
  33. package/dist/OAuthModel/model.js +165 -113
  34. package/dist/OAuthModel/model.js.map +1 -1
  35. package/dist/OAuthModel/util.d.ts +7 -0
  36. package/dist/OAuthModel/util.js +60 -0
  37. package/dist/OAuthModel/util.js.map +1 -0
  38. package/dist/index.d.ts +7 -433
  39. package/dist/util.d.ts +6 -0
  40. package/dist/util.js +23 -0
  41. package/dist/util.js.map +1 -0
  42. package/esm/DropboxOAuthModel/configSchema.d.ts +3 -14
  43. package/esm/DropboxOAuthModel/configSchema.js +0 -8
  44. package/esm/DropboxOAuthModel/configSchema.js.map +1 -1
  45. package/esm/DropboxOAuthModel/model.d.ts +35 -33
  46. package/esm/DropboxOAuthModel/model.js +26 -35
  47. package/esm/DropboxOAuthModel/model.js.map +1 -1
  48. package/esm/DropboxOAuthModel/util.d.ts +1 -0
  49. package/esm/DropboxOAuthModel/util.js +24 -0
  50. package/esm/DropboxOAuthModel/util.js.map +1 -0
  51. package/esm/ExternalTokenModel/ExternalTokenEntryForm.d.ts +2 -1
  52. package/esm/ExternalTokenModel/ExternalTokenEntryForm.js +10 -11
  53. package/esm/ExternalTokenModel/ExternalTokenEntryForm.js.map +1 -1
  54. package/esm/GoogleDriveOAuthModel/GoogleDriveFilehandle.d.ts +15 -0
  55. package/esm/GoogleDriveOAuthModel/GoogleDriveFilehandle.js +16 -0
  56. package/esm/GoogleDriveOAuthModel/GoogleDriveFilehandle.js.map +1 -0
  57. package/esm/GoogleDriveOAuthModel/configSchema.d.ts +0 -5
  58. package/esm/GoogleDriveOAuthModel/model.d.ts +36 -121
  59. package/esm/GoogleDriveOAuthModel/model.js +37 -49
  60. package/esm/GoogleDriveOAuthModel/model.js.map +1 -1
  61. package/esm/GoogleDriveOAuthModel/util.d.ts +1 -0
  62. package/esm/GoogleDriveOAuthModel/util.js +13 -0
  63. package/esm/GoogleDriveOAuthModel/util.js.map +1 -0
  64. package/esm/HTTPBasicModel/HTTPBasicLoginForm.d.ts +2 -1
  65. package/esm/HTTPBasicModel/HTTPBasicLoginForm.js +18 -22
  66. package/esm/HTTPBasicModel/HTTPBasicLoginForm.js.map +1 -1
  67. package/esm/HTTPBasicModel/model.d.ts +36 -4
  68. package/esm/HTTPBasicModel/model.js +24 -9
  69. package/esm/HTTPBasicModel/model.js.map +1 -1
  70. package/esm/OAuthModel/configSchema.d.ts +0 -8
  71. package/esm/OAuthModel/configSchema.js +1 -9
  72. package/esm/OAuthModel/configSchema.js.map +1 -1
  73. package/esm/OAuthModel/model.d.ts +103 -18
  74. package/esm/OAuthModel/model.js +164 -86
  75. package/esm/OAuthModel/model.js.map +1 -1
  76. package/esm/OAuthModel/util.d.ts +7 -0
  77. package/esm/OAuthModel/util.js +30 -0
  78. package/esm/OAuthModel/util.js.map +1 -0
  79. package/esm/index.d.ts +7 -433
  80. package/esm/util.d.ts +6 -0
  81. package/esm/util.js +18 -0
  82. package/esm/util.js.map +1 -0
  83. package/package.json +3 -4
  84. package/src/DropboxOAuthModel/configSchema.ts +0 -8
  85. package/src/DropboxOAuthModel/model.tsx +35 -54
  86. package/src/DropboxOAuthModel/util.ts +36 -0
  87. package/src/ExternalTokenModel/ExternalTokenEntryForm.tsx +39 -41
  88. package/src/GoogleDriveOAuthModel/GoogleDriveFilehandle.ts +38 -0
  89. package/src/GoogleDriveOAuthModel/model.tsx +54 -104
  90. package/src/GoogleDriveOAuthModel/util.ts +29 -0
  91. package/src/HTTPBasicModel/HTTPBasicLoginForm.tsx +53 -56
  92. package/src/HTTPBasicModel/model.tsx +26 -11
  93. package/src/OAuthModel/configSchema.ts +2 -9
  94. package/src/OAuthModel/model.tsx +190 -108
  95. package/src/OAuthModel/util.ts +33 -0
  96. package/src/util.ts +25 -0
@@ -2,10 +2,16 @@ import { ConfigurationReference, getConf } from '@jbrowse/core/configuration'
2
2
  import { InternetAccount } from '@jbrowse/core/pluggableElementTypes/models'
3
3
  import { isElectron, UriLocation } from '@jbrowse/core/util'
4
4
  import { Instance, types } from 'mobx-state-tree'
5
- import jwtDecode, { JwtPayload } from 'jwt-decode'
6
5
 
7
6
  // locals
8
7
  import { OAuthInternetAccountConfigModel } from './configSchema'
8
+ import {
9
+ fixup,
10
+ generateChallenge,
11
+ processError,
12
+ processTokenResponse,
13
+ } from './util'
14
+ import { getResponseError } from '../util'
9
15
 
10
16
  interface OAuthData {
11
17
  client_id: string
@@ -18,19 +24,27 @@ interface OAuthData {
18
24
  state?: string
19
25
  }
20
26
 
21
- function fixup(buf: string) {
22
- return buf.replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '')
23
- }
24
-
27
+ /**
28
+ * #stateModel OAuthInternetAccount
29
+ */
25
30
  const stateModelFactory = (configSchema: OAuthInternetAccountConfigModel) => {
26
31
  return InternetAccount.named('OAuthInternetAccount')
27
32
  .props({
33
+ /**
34
+ * #property
35
+ */
28
36
  type: types.literal('OAuthInternetAccount'),
37
+ /**
38
+ * #property
39
+ */
29
40
  configuration: ConfigurationReference(configSchema),
30
41
  })
31
42
  .views(() => {
32
43
  let codeVerifier: string | undefined = undefined
33
44
  return {
45
+ /**
46
+ * #getter
47
+ */
34
48
  get codeVerifierPKCE() {
35
49
  if (codeVerifier) {
36
50
  return codeVerifier
@@ -43,61 +57,95 @@ const stateModelFactory = (configSchema: OAuthInternetAccountConfigModel) => {
43
57
  }
44
58
  })
45
59
  .views(self => ({
60
+ /**
61
+ * #getter
62
+ */
46
63
  get authEndpoint(): string {
47
64
  return getConf(self, 'authEndpoint')
48
65
  },
66
+ /**
67
+ * #getter
68
+ */
49
69
  get tokenEndpoint(): string {
50
70
  return getConf(self, 'tokenEndpoint')
51
71
  },
72
+ /**
73
+ * #getter
74
+ */
52
75
  get needsPKCE(): boolean {
53
76
  return getConf(self, 'needsPKCE')
54
77
  },
78
+ /**
79
+ * #getter
80
+ */
55
81
  get clientId(): string {
56
82
  return getConf(self, 'clientId')
57
83
  },
84
+ /**
85
+ * #getter
86
+ */
58
87
  get scopes(): string {
59
88
  return getConf(self, 'scopes')
60
89
  },
61
90
  /**
62
- * OAuth state parameter: https://www.rfc-editor.org/rfc/rfc6749#section-4.1.1
91
+ * #method
92
+ * OAuth state parameter:
93
+ * https://www.rfc-editor.org/rfc/rfc6749#section-4.1.1
94
+ *
63
95
  * Can override or extend if dynamic state is needed.
64
96
  */
65
97
  state(): string | undefined {
66
- return getConf(self, 'state') || undefined
98
+ return getConf(self, 'state')
67
99
  },
100
+ /**
101
+ * #getter
102
+ */
68
103
  get responseType(): 'token' | 'code' {
69
104
  return getConf(self, 'responseType')
70
105
  },
71
- get hasRefreshToken(): boolean {
72
- return getConf(self, 'hasRefreshToken')
73
- },
106
+ /**
107
+ * #getter
108
+ */
74
109
  get refreshTokenKey() {
75
110
  return `${self.internetAccountId}-refreshToken`
76
111
  },
77
112
  }))
113
+
78
114
  .actions(self => ({
115
+ /**
116
+ * #action
117
+ */
79
118
  storeRefreshToken(refreshToken: string) {
80
119
  localStorage.setItem(self.refreshTokenKey, refreshToken)
81
120
  },
121
+ /**
122
+ * #action
123
+ */
82
124
  removeRefreshToken() {
83
125
  localStorage.removeItem(self.refreshTokenKey)
84
126
  },
127
+ /**
128
+ * #method
129
+ */
85
130
  retrieveRefreshToken() {
86
131
  return localStorage.getItem(self.refreshTokenKey)
87
132
  },
133
+ /**
134
+ * #action
135
+ */
88
136
  async exchangeAuthorizationForAccessToken(
89
137
  token: string,
90
138
  redirectUri: string,
91
139
  ): Promise<string> {
92
- const data = {
93
- code: token,
94
- grant_type: 'authorization_code',
95
- client_id: self.clientId,
96
- code_verifier: self.codeVerifierPKCE,
97
- redirect_uri: redirectUri,
98
- }
99
-
100
- const params = new URLSearchParams(Object.entries(data))
140
+ const params = new URLSearchParams(
141
+ Object.entries({
142
+ code: token,
143
+ grant_type: 'authorization_code',
144
+ client_id: self.clientId,
145
+ redirect_uri: redirectUri,
146
+ ...(self.needsPKCE ? { code_verifier: self.codeVerifierPKCE } : {}),
147
+ }),
148
+ )
101
149
 
102
150
  const response = await fetch(self.tokenEndpoint, {
103
151
  method: 'POST',
@@ -106,74 +154,61 @@ const stateModelFactory = (configSchema: OAuthInternetAccountConfigModel) => {
106
154
  })
107
155
 
108
156
  if (!response.ok) {
109
- let errorMessage
110
- try {
111
- errorMessage = await response.text()
112
- } catch (error) {
113
- errorMessage = ''
114
- }
115
157
  throw new Error(
116
- `Failed to obtain token from endpoint: ${response.status} (${
117
- response.statusText
118
- })${errorMessage ? ` (${errorMessage})` : ''}`,
158
+ await getResponseError({
159
+ response,
160
+ reason: 'Failed to obtain token',
161
+ }),
119
162
  )
120
163
  }
121
164
 
122
- const accessToken = await response.json()
123
- if (accessToken.refresh_token) {
124
- this.storeRefreshToken(accessToken.refresh_token)
125
- }
126
- return accessToken.access_token
165
+ const data = await response.json()
166
+ return processTokenResponse(data, token =>
167
+ this.storeRefreshToken(token),
168
+ )
127
169
  },
170
+ /**
171
+ * #action
172
+ */
128
173
  async exchangeRefreshForAccessToken(
129
174
  refreshToken: string,
130
175
  ): Promise<string> {
131
- const data = {
132
- grant_type: 'refresh_token',
133
- refresh_token: refreshToken,
134
- client_id: self.clientId,
135
- }
136
-
137
- const params = new URLSearchParams(Object.entries(data))
138
-
139
176
  const response = await fetch(self.tokenEndpoint, {
140
177
  method: 'POST',
141
178
  headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
142
- body: params.toString(),
179
+ body: new URLSearchParams(
180
+ Object.entries({
181
+ grant_type: 'refresh_token',
182
+ refresh_token: refreshToken,
183
+ client_id: self.clientId,
184
+ }),
185
+ ).toString(),
143
186
  })
144
187
 
145
188
  if (!response.ok) {
146
189
  self.removeToken()
147
- let text = await response.text()
148
- try {
149
- const obj = JSON.parse(text)
150
- if (obj.error === 'invalid_grant') {
151
- this.removeRefreshToken()
152
- }
153
- text = obj?.error_description ?? text
154
- } catch (e) {
155
- /* just use original text as error */
156
- }
157
-
190
+ const text = await response.text()
158
191
  throw new Error(
159
- `Network response failure — ${response.status} (${
160
- response.statusText
161
- }) ${text ? ` (${text})` : ''}`,
192
+ await getResponseError({
193
+ response,
194
+ statusText: processError(text, () => this.removeRefreshToken()),
195
+ }),
162
196
  )
163
197
  }
164
-
165
- const accessToken = await response.json()
166
- if (accessToken.refresh_token) {
167
- this.storeRefreshToken(accessToken.refresh_token)
168
- }
169
- return accessToken.access_token
198
+ const data = await response.json()
199
+ return processTokenResponse(data, token =>
200
+ this.storeRefreshToken(token),
201
+ )
170
202
  },
171
203
  }))
172
204
  .actions(self => {
173
- let listener: (event: MessageEvent) => void
174
- let refreshTokenPromise: Promise<string> | undefined = undefined
205
+ let listener: (event: MessageEvent) => void | undefined
206
+ let exchangedTokenPromise: Promise<string> | undefined = undefined
175
207
  return {
176
- // used to listen to child window for auth code/token
208
+ /**
209
+ * #action
210
+ * used to listen to child window for auth code/token
211
+ */
177
212
  addMessageChannel(
178
213
  resolve: (token: string) => void,
179
214
  reject: (error: Error) => void,
@@ -185,9 +220,15 @@ const stateModelFactory = (configSchema: OAuthInternetAccountConfigModel) => {
185
220
  }
186
221
  window.addEventListener('message', listener)
187
222
  },
223
+ /**
224
+ * #action
225
+ */
188
226
  deleteMessageChannel() {
189
227
  window.removeEventListener('message', listener)
190
228
  },
229
+ /**
230
+ * #action
231
+ */
191
232
  async finishOAuthWindow(
192
233
  event: MessageEvent,
193
234
  resolve: (token: string) => void,
@@ -223,21 +264,25 @@ const stateModelFactory = (configSchema: OAuthInternetAccountConfigModel) => {
223
264
  )
224
265
  self.storeToken(token)
225
266
  return resolve(token)
226
- } catch (error) {
227
- return error instanceof Error
228
- ? reject(error)
229
- : reject(new Error(String(error)))
267
+ } catch (e) {
268
+ return e instanceof Error
269
+ ? reject(e)
270
+ : reject(new Error(String(e)))
230
271
  }
231
272
  }
232
273
  if (redirectUriWithInfo.includes('access_denied')) {
233
274
  return reject(new Error('OAuth flow was cancelled'))
234
275
  }
235
276
  if (redirectUriWithInfo.includes('error')) {
236
- return reject(new Error('Oauth flow error: ' + queryStringSearch))
277
+ return reject(new Error('OAuth flow error: ' + queryStringSearch))
237
278
  }
238
279
  this.deleteMessageChannel()
239
280
  },
240
- // opens external OAuth flow, popup for web and new browser window for desktop
281
+ /**
282
+ * #action
283
+ * opens external OAuth flow, popup for web and new browser window for
284
+ * desktop
285
+ */
241
286
  async useEndpointForAuthorization(
242
287
  resolve: (token: string) => void,
243
288
  reject: (error: Error) => void,
@@ -249,6 +294,7 @@ const stateModelFactory = (configSchema: OAuthInternetAccountConfigModel) => {
249
294
  client_id: self.clientId,
250
295
  redirect_uri: redirectUri,
251
296
  response_type: self.responseType || 'code',
297
+ token_access_type: 'offline',
252
298
  }
253
299
 
254
300
  if (self.state()) {
@@ -260,21 +306,10 @@ const stateModelFactory = (configSchema: OAuthInternetAccountConfigModel) => {
260
306
  }
261
307
 
262
308
  if (self.needsPKCE) {
263
- const { codeVerifierPKCE } = self
264
-
265
- const sha256 = await import('crypto-js/sha256').then(f => f.default)
266
- const Base64 = await import('crypto-js/enc-base64')
267
- const codeChallenge = fixup(
268
- Base64.stringify(sha256(codeVerifierPKCE)),
269
- )
270
- data.code_challenge = codeChallenge
309
+ data.code_challenge = await generateChallenge(self.codeVerifierPKCE)
271
310
  data.code_challenge_method = 'S256'
272
311
  }
273
312
 
274
- if (self.hasRefreshToken) {
275
- data.token_access_type = 'offline'
276
- }
277
-
278
313
  const params = new URLSearchParams(Object.entries(data))
279
314
 
280
315
  const url = new URL(self.authEndpoint)
@@ -296,51 +331,98 @@ const stateModelFactory = (configSchema: OAuthInternetAccountConfigModel) => {
296
331
  // eslint-disable-next-line @typescript-eslint/no-floating-promises
297
332
  this.finishOAuthWindow(eventFromDesktop, resolve, reject)
298
333
  } else {
299
- const options = `width=500,height=600,left=0,top=0`
300
- window.open(url, eventName, options)
334
+ window.open(url, eventName, `width=500,height=600,left=0,top=0`)
301
335
  }
302
336
  },
337
+ /**
338
+ * #action
339
+ */
303
340
  async getTokenFromUser(
304
341
  resolve: (token: string) => void,
305
342
  reject: (error: Error) => void,
306
- ): Promise<void> {
307
- const refreshToken =
308
- self.hasRefreshToken && self.retrieveRefreshToken()
343
+ ) {
344
+ const refreshToken = self.retrieveRefreshToken()
345
+ let doUserFlow = true
346
+
347
+ // if there is a refresh token, then try it out, and only if that
348
+ // refresh token succeeds, set doUserFlow to false
309
349
  if (refreshToken) {
310
- resolve(await self.exchangeRefreshForAccessToken(refreshToken))
350
+ try {
351
+ const token = await self.exchangeRefreshForAccessToken(
352
+ refreshToken,
353
+ )
354
+ resolve(token)
355
+ doUserFlow = false
356
+ } catch (e) {
357
+ console.error(e)
358
+ self.removeRefreshToken()
359
+ }
360
+ }
361
+ if (doUserFlow) {
362
+ this.addMessageChannel(resolve, reject)
363
+ // may want to improve handling
364
+ // eslint-disable-next-line @typescript-eslint/no-floating-promises
365
+ this.useEndpointForAuthorization(resolve, reject)
311
366
  }
312
- this.addMessageChannel(resolve, reject)
313
- // may want to improve handling
314
- // eslint-disable-next-line @typescript-eslint/no-floating-promises
315
- this.useEndpointForAuthorization(resolve, reject)
316
367
  },
317
- async validateToken(
318
- token: string,
319
- location: UriLocation,
320
- ): Promise<string> {
321
- const decoded = jwtDecode<JwtPayload>(token)
322
- if (decoded.exp && decoded.exp < Date.now() / 1000) {
323
- const refreshToken =
324
- self.hasRefreshToken && self.retrieveRefreshToken()
368
+ /**
369
+ * #action
370
+ */
371
+ async validateToken(token: string, location: UriLocation) {
372
+ const newInit = self.addAuthHeaderToInit({ method: 'HEAD' }, token)
373
+ const response = await fetch(location.uri, newInit)
374
+ if (!response.ok) {
375
+ self.removeToken()
376
+ const refreshToken = self.retrieveRefreshToken()
325
377
  if (refreshToken) {
326
378
  try {
327
- if (!refreshTokenPromise) {
328
- refreshTokenPromise =
379
+ if (!exchangedTokenPromise) {
380
+ exchangedTokenPromise =
329
381
  self.exchangeRefreshForAccessToken(refreshToken)
330
382
  }
331
- const newToken = await refreshTokenPromise
332
- return this.validateToken(newToken, location)
383
+ const newToken = await exchangedTokenPromise
384
+ exchangedTokenPromise = undefined
385
+ return newToken
333
386
  } catch (err) {
334
- throw new Error(`Token could not be refreshed. ${err}`)
387
+ console.error('Token could not be refreshed', err)
388
+ // let original error be thrown
335
389
  }
336
390
  }
337
- } else {
338
- refreshTokenPromise = undefined
391
+
392
+ throw new Error(
393
+ await getResponseError({
394
+ response,
395
+ reason: 'Error validating token',
396
+ }),
397
+ )
339
398
  }
340
399
  return token
341
400
  },
342
401
  }
343
402
  })
403
+ .actions(self => {
404
+ const superGetFetcher = self.getFetcher
405
+ return {
406
+ /**
407
+ * #action
408
+ * Get a fetch method that will add any needed authentication headers to
409
+ * the request before sending it. If location is provided, it will be
410
+ * checked to see if it includes a token in it's pre-auth information.
411
+ *
412
+ * @param loc - UriLocation of the resource
413
+ * @returns A function that can be used to fetch
414
+ */
415
+ getFetcher(loc?: UriLocation) {
416
+ const fetcher = superGetFetcher(loc)
417
+ return async (input: RequestInfo, init?: RequestInit) => {
418
+ if (loc) {
419
+ await self.validateToken(await self.getToken(loc), loc)
420
+ }
421
+ return fetcher(input, init)
422
+ }
423
+ },
424
+ }
425
+ })
344
426
  }
345
427
 
346
428
  export default stateModelFactory
@@ -0,0 +1,33 @@
1
+ export function fixup(buf: string) {
2
+ return buf.replaceAll('+', '-').replaceAll('/', '_').replaceAll('=', '')
3
+ }
4
+
5
+ export async function generateChallenge(val: string) {
6
+ const sha256 = await import('crypto-js/sha256').then(f => f.default)
7
+ const Base64 = await import('crypto-js/enc-base64')
8
+ return fixup(Base64.stringify(sha256(val)))
9
+ }
10
+
11
+ // if response is JSON, checks if it needs to remove tokens in error, or just plain throw
12
+ export function processError(text: string, invalidErrorCb: () => void) {
13
+ try {
14
+ const obj = JSON.parse(text)
15
+ if (obj.error === 'invalid_grant') {
16
+ invalidErrorCb()
17
+ }
18
+ return obj?.error_description ?? text
19
+ } catch (e) {
20
+ /* response text is not json, just use original text as error */
21
+ }
22
+ return text
23
+ }
24
+
25
+ export function processTokenResponse(
26
+ data: { refresh_token?: string; access_token: string },
27
+ storeRefreshTokenCb: (str: string) => void,
28
+ ) {
29
+ if (data.refresh_token) {
30
+ storeRefreshTokenCb(data.refresh_token)
31
+ }
32
+ return data.access_token
33
+ }
package/src/util.ts ADDED
@@ -0,0 +1,25 @@
1
+ export async function getResponseError({
2
+ response,
3
+ reason,
4
+ statusText,
5
+ }: {
6
+ response: Response
7
+ reason?: string
8
+ statusText?: string
9
+ }) {
10
+ return [
11
+ `HTTP ${response.status}`,
12
+ reason,
13
+ statusText ?? (await getError(response)),
14
+ ]
15
+ .filter(f => !!f)
16
+ .join(' - ')
17
+ }
18
+
19
+ export async function getError(response: Response) {
20
+ try {
21
+ return response.text()
22
+ } catch (e) {
23
+ return response.statusText
24
+ }
25
+ }