@supabase/auth-js 2.61.0

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 (129) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +50 -0
  3. package/dist/main/AuthAdminApi.d.ts +4 -0
  4. package/dist/main/AuthAdminApi.d.ts.map +1 -0
  5. package/dist/main/AuthAdminApi.js +9 -0
  6. package/dist/main/AuthAdminApi.js.map +1 -0
  7. package/dist/main/AuthClient.d.ts +4 -0
  8. package/dist/main/AuthClient.d.ts.map +1 -0
  9. package/dist/main/AuthClient.js +9 -0
  10. package/dist/main/AuthClient.js.map +1 -0
  11. package/dist/main/GoTrueAdminApi.d.ts +99 -0
  12. package/dist/main/GoTrueAdminApi.d.ts.map +1 -0
  13. package/dist/main/GoTrueAdminApi.js +268 -0
  14. package/dist/main/GoTrueAdminApi.js.map +1 -0
  15. package/dist/main/GoTrueClient.d.ts +432 -0
  16. package/dist/main/GoTrueClient.d.ts.map +1 -0
  17. package/dist/main/GoTrueClient.js +1889 -0
  18. package/dist/main/GoTrueClient.js.map +1 -0
  19. package/dist/main/index.d.ts +9 -0
  20. package/dist/main/index.d.ts.map +1 -0
  21. package/dist/main/index.js +35 -0
  22. package/dist/main/index.js.map +1 -0
  23. package/dist/main/lib/constants.d.ts +12 -0
  24. package/dist/main/lib/constants.d.ts.map +1 -0
  25. package/dist/main/lib/constants.js +14 -0
  26. package/dist/main/lib/constants.js.map +1 -0
  27. package/dist/main/lib/errors.d.ts +96 -0
  28. package/dist/main/lib/errors.d.ts.map +1 -0
  29. package/dist/main/lib/errors.js +135 -0
  30. package/dist/main/lib/errors.js.map +1 -0
  31. package/dist/main/lib/fetch.d.ts +33 -0
  32. package/dist/main/lib/fetch.d.ts.map +1 -0
  33. package/dist/main/lib/fetch.js +162 -0
  34. package/dist/main/lib/fetch.js.map +1 -0
  35. package/dist/main/lib/helpers.d.ts +48 -0
  36. package/dist/main/lib/helpers.d.ts.map +1 -0
  37. package/dist/main/lib/helpers.js +292 -0
  38. package/dist/main/lib/helpers.js.map +1 -0
  39. package/dist/main/lib/local-storage.d.ts +13 -0
  40. package/dist/main/lib/local-storage.d.ts.map +1 -0
  41. package/dist/main/lib/local-storage.js +46 -0
  42. package/dist/main/lib/local-storage.js.map +1 -0
  43. package/dist/main/lib/locks.d.ts +44 -0
  44. package/dist/main/lib/locks.d.ts.map +1 -0
  45. package/dist/main/lib/locks.js +118 -0
  46. package/dist/main/lib/locks.js.map +1 -0
  47. package/dist/main/lib/polyfills.d.ts +5 -0
  48. package/dist/main/lib/polyfills.d.ts.map +1 -0
  49. package/dist/main/lib/polyfills.js +30 -0
  50. package/dist/main/lib/polyfills.js.map +1 -0
  51. package/dist/main/lib/types.d.ts +948 -0
  52. package/dist/main/lib/types.d.ts.map +1 -0
  53. package/dist/main/lib/types.js +3 -0
  54. package/dist/main/lib/types.js.map +1 -0
  55. package/dist/main/lib/version.d.ts +2 -0
  56. package/dist/main/lib/version.d.ts.map +1 -0
  57. package/dist/main/lib/version.js +6 -0
  58. package/dist/main/lib/version.js.map +1 -0
  59. package/dist/module/AuthAdminApi.d.ts +4 -0
  60. package/dist/module/AuthAdminApi.d.ts.map +1 -0
  61. package/dist/module/AuthAdminApi.js +4 -0
  62. package/dist/module/AuthAdminApi.js.map +1 -0
  63. package/dist/module/AuthClient.d.ts +4 -0
  64. package/dist/module/AuthClient.d.ts.map +1 -0
  65. package/dist/module/AuthClient.js +4 -0
  66. package/dist/module/AuthClient.js.map +1 -0
  67. package/dist/module/GoTrueAdminApi.d.ts +99 -0
  68. package/dist/module/GoTrueAdminApi.d.ts.map +1 -0
  69. package/dist/module/GoTrueAdminApi.js +265 -0
  70. package/dist/module/GoTrueAdminApi.js.map +1 -0
  71. package/dist/module/GoTrueClient.d.ts +432 -0
  72. package/dist/module/GoTrueClient.d.ts.map +1 -0
  73. package/dist/module/GoTrueClient.js +1883 -0
  74. package/dist/module/GoTrueClient.js.map +1 -0
  75. package/dist/module/index.d.ts +9 -0
  76. package/dist/module/index.d.ts.map +1 -0
  77. package/dist/module/index.js +9 -0
  78. package/dist/module/index.js.map +1 -0
  79. package/dist/module/lib/constants.d.ts +12 -0
  80. package/dist/module/lib/constants.d.ts.map +1 -0
  81. package/dist/module/lib/constants.js +11 -0
  82. package/dist/module/lib/constants.js.map +1 -0
  83. package/dist/module/lib/errors.d.ts +96 -0
  84. package/dist/module/lib/errors.d.ts.map +1 -0
  85. package/dist/module/lib/errors.js +117 -0
  86. package/dist/module/lib/errors.js.map +1 -0
  87. package/dist/module/lib/fetch.d.ts +33 -0
  88. package/dist/module/lib/fetch.d.ts.map +1 -0
  89. package/dist/module/lib/fetch.js +152 -0
  90. package/dist/module/lib/fetch.js.map +1 -0
  91. package/dist/module/lib/helpers.d.ts +48 -0
  92. package/dist/module/lib/helpers.d.ts.map +1 -0
  93. package/dist/module/lib/helpers.js +249 -0
  94. package/dist/module/lib/helpers.js.map +1 -0
  95. package/dist/module/lib/local-storage.d.ts +13 -0
  96. package/dist/module/lib/local-storage.d.ts.map +1 -0
  97. package/dist/module/lib/local-storage.js +42 -0
  98. package/dist/module/lib/local-storage.js.map +1 -0
  99. package/dist/module/lib/locks.d.ts +44 -0
  100. package/dist/module/lib/locks.d.ts.map +1 -0
  101. package/dist/module/lib/locks.js +112 -0
  102. package/dist/module/lib/locks.js.map +1 -0
  103. package/dist/module/lib/polyfills.d.ts +5 -0
  104. package/dist/module/lib/polyfills.d.ts.map +1 -0
  105. package/dist/module/lib/polyfills.js +26 -0
  106. package/dist/module/lib/polyfills.js.map +1 -0
  107. package/dist/module/lib/types.d.ts +948 -0
  108. package/dist/module/lib/types.d.ts.map +1 -0
  109. package/dist/module/lib/types.js +2 -0
  110. package/dist/module/lib/types.js.map +1 -0
  111. package/dist/module/lib/version.d.ts +2 -0
  112. package/dist/module/lib/version.d.ts.map +1 -0
  113. package/dist/module/lib/version.js +3 -0
  114. package/dist/module/lib/version.js.map +1 -0
  115. package/package.json +69 -0
  116. package/src/AuthAdminApi.ts +5 -0
  117. package/src/AuthClient.ts +5 -0
  118. package/src/GoTrueAdminApi.ts +333 -0
  119. package/src/GoTrueClient.ts +2470 -0
  120. package/src/index.ts +12 -0
  121. package/src/lib/constants.ts +10 -0
  122. package/src/lib/errors.ts +150 -0
  123. package/src/lib/fetch.ts +238 -0
  124. package/src/lib/helpers.ts +306 -0
  125. package/src/lib/local-storage.ts +49 -0
  126. package/src/lib/locks.ts +140 -0
  127. package/src/lib/polyfills.ts +23 -0
  128. package/src/lib/types.ts +1120 -0
  129. package/src/lib/version.ts +2 -0
package/src/index.ts ADDED
@@ -0,0 +1,12 @@
1
+ import GoTrueAdminApi from './GoTrueAdminApi'
2
+ import GoTrueClient from './GoTrueClient'
3
+ import AuthAdminApi from './AuthAdminApi'
4
+ import AuthClient from './AuthClient'
5
+ export { GoTrueAdminApi, GoTrueClient, AuthAdminApi, AuthClient }
6
+ export * from './lib/types'
7
+ export * from './lib/errors'
8
+ export {
9
+ navigatorLock,
10
+ NavigatorLockAcquireTimeoutError,
11
+ internals as lockInternals,
12
+ } from './lib/locks'
@@ -0,0 +1,10 @@
1
+ import { version } from './version'
2
+ export const GOTRUE_URL = 'http://localhost:9999'
3
+ export const STORAGE_KEY = 'supabase.auth.token'
4
+ export const AUDIENCE = ''
5
+ export const DEFAULT_HEADERS = { 'X-Client-Info': `gotrue-js/${version}` }
6
+ export const EXPIRY_MARGIN = 10 // in seconds
7
+ export const NETWORK_FAILURE = {
8
+ MAX_RETRIES: 10,
9
+ RETRY_INTERVAL: 2, // in deciseconds
10
+ }
@@ -0,0 +1,150 @@
1
+ import { WeakPasswordReasons } from './types'
2
+
3
+ export class AuthError extends Error {
4
+ status: number | undefined
5
+ protected __isAuthError = true
6
+
7
+ constructor(message: string, status?: number) {
8
+ super(message)
9
+ this.name = 'AuthError'
10
+ this.status = status
11
+ }
12
+ }
13
+
14
+ export function isAuthError(error: unknown): error is AuthError {
15
+ return typeof error === 'object' && error !== null && '__isAuthError' in error
16
+ }
17
+
18
+ export class AuthApiError extends AuthError {
19
+ status: number
20
+
21
+ constructor(message: string, status: number) {
22
+ super(message, status)
23
+ this.name = 'AuthApiError'
24
+ this.status = status
25
+ }
26
+
27
+ toJSON() {
28
+ return {
29
+ name: this.name,
30
+ message: this.message,
31
+ status: this.status,
32
+ }
33
+ }
34
+ }
35
+
36
+ export function isAuthApiError(error: unknown): error is AuthApiError {
37
+ return isAuthError(error) && error.name === 'AuthApiError'
38
+ }
39
+
40
+ export class AuthUnknownError extends AuthError {
41
+ originalError: unknown
42
+
43
+ constructor(message: string, originalError: unknown) {
44
+ super(message)
45
+ this.name = 'AuthUnknownError'
46
+ this.originalError = originalError
47
+ }
48
+ }
49
+
50
+ export class CustomAuthError extends AuthError {
51
+ name: string
52
+ status: number
53
+ constructor(message: string, name: string, status: number) {
54
+ super(message)
55
+ this.name = name
56
+ this.status = status
57
+ }
58
+
59
+ toJSON() {
60
+ return {
61
+ name: this.name,
62
+ message: this.message,
63
+ status: this.status,
64
+ }
65
+ }
66
+ }
67
+
68
+ export class AuthSessionMissingError extends CustomAuthError {
69
+ constructor() {
70
+ super('Auth session missing!', 'AuthSessionMissingError', 400)
71
+ }
72
+ }
73
+
74
+ export class AuthInvalidTokenResponseError extends CustomAuthError {
75
+ constructor() {
76
+ super('Auth session or user missing', 'AuthInvalidTokenResponseError', 500)
77
+ }
78
+ }
79
+
80
+ export class AuthInvalidCredentialsError extends CustomAuthError {
81
+ constructor(message: string) {
82
+ super(message, 'AuthInvalidCredentialsError', 400)
83
+ }
84
+ }
85
+
86
+ export class AuthImplicitGrantRedirectError extends CustomAuthError {
87
+ details: { error: string; code: string } | null = null
88
+ constructor(message: string, details: { error: string; code: string } | null = null) {
89
+ super(message, 'AuthImplicitGrantRedirectError', 500)
90
+ this.details = details
91
+ }
92
+
93
+ toJSON() {
94
+ return {
95
+ name: this.name,
96
+ message: this.message,
97
+ status: this.status,
98
+ details: this.details,
99
+ }
100
+ }
101
+ }
102
+
103
+ export class AuthPKCEGrantCodeExchangeError extends CustomAuthError {
104
+ details: { error: string; code: string } | null = null
105
+ constructor(message: string, details: { error: string; code: string } | null = null) {
106
+ super(message, 'AuthPKCEGrantCodeExchangeError', 500)
107
+ this.details = details
108
+ }
109
+
110
+ toJSON() {
111
+ return {
112
+ name: this.name,
113
+ message: this.message,
114
+ status: this.status,
115
+ details: this.details,
116
+ }
117
+ }
118
+ }
119
+
120
+ export class AuthRetryableFetchError extends CustomAuthError {
121
+ constructor(message: string, status: number) {
122
+ super(message, 'AuthRetryableFetchError', status)
123
+ }
124
+ }
125
+
126
+ export function isAuthRetryableFetchError(error: unknown): error is AuthRetryableFetchError {
127
+ return isAuthError(error) && error.name === 'AuthRetryableFetchError'
128
+ }
129
+
130
+ /**
131
+ * This error is thrown on certain methods when the password used is deemed
132
+ * weak. Inspect the reasons to identify what password strength rules are
133
+ * inadequate.
134
+ */
135
+ export class AuthWeakPasswordError extends CustomAuthError {
136
+ /**
137
+ * Reasons why the password is deemed weak.
138
+ */
139
+ reasons: WeakPasswordReasons[]
140
+
141
+ constructor(message: string, status: number, reasons: string[]) {
142
+ super(message, 'AuthWeakPasswordError', status)
143
+
144
+ this.reasons = reasons
145
+ }
146
+ }
147
+
148
+ export function isAuthWeakPasswordError(error: unknown): error is AuthWeakPasswordError {
149
+ return isAuthError(error) && error.name === 'AuthWeakPasswordError'
150
+ }
@@ -0,0 +1,238 @@
1
+ import { expiresAt, looksLikeFetchResponse } from './helpers'
2
+ import {
3
+ AuthResponse,
4
+ AuthResponsePassword,
5
+ SSOResponse,
6
+ GenerateLinkProperties,
7
+ GenerateLinkResponse,
8
+ User,
9
+ UserResponse,
10
+ } from './types'
11
+ import {
12
+ AuthApiError,
13
+ AuthRetryableFetchError,
14
+ AuthWeakPasswordError,
15
+ AuthUnknownError,
16
+ } from './errors'
17
+
18
+ export type Fetch = typeof fetch
19
+
20
+ export interface FetchOptions {
21
+ headers?: {
22
+ [key: string]: string
23
+ }
24
+ noResolveJson?: boolean
25
+ }
26
+
27
+ export interface FetchParameters {
28
+ signal?: AbortSignal
29
+ }
30
+
31
+ export type RequestMethodType = 'GET' | 'POST' | 'PUT' | 'DELETE'
32
+
33
+ const _getErrorMessage = (err: any): string =>
34
+ err.msg || err.message || err.error_description || err.error || JSON.stringify(err)
35
+
36
+ const NETWORK_ERROR_CODES = [502, 503, 504]
37
+
38
+ async function handleError(error: unknown) {
39
+ if (!looksLikeFetchResponse(error)) {
40
+ throw new AuthRetryableFetchError(_getErrorMessage(error), 0)
41
+ }
42
+
43
+ if (NETWORK_ERROR_CODES.includes(error.status)) {
44
+ // status in 500...599 range - server had an error, request might be retryed.
45
+ throw new AuthRetryableFetchError(_getErrorMessage(error), error.status)
46
+ }
47
+
48
+ let data: any
49
+ try {
50
+ data = await error.json()
51
+ } catch (e: any) {
52
+ throw new AuthUnknownError(_getErrorMessage(e), e)
53
+ }
54
+
55
+ if (
56
+ typeof data === 'object' &&
57
+ data &&
58
+ typeof data.weak_password === 'object' &&
59
+ data.weak_password &&
60
+ Array.isArray(data.weak_password.reasons) &&
61
+ data.weak_password.reasons.length &&
62
+ data.weak_password.reasons.reduce((a: boolean, i: any) => a && typeof i === 'string', true)
63
+ ) {
64
+ throw new AuthWeakPasswordError(
65
+ _getErrorMessage(data),
66
+ error.status,
67
+ data.weak_password.reasons
68
+ )
69
+ }
70
+
71
+ throw new AuthApiError(_getErrorMessage(data), error.status || 500)
72
+ }
73
+
74
+ const _getRequestParams = (
75
+ method: RequestMethodType,
76
+ options?: FetchOptions,
77
+ parameters?: FetchParameters,
78
+ body?: object
79
+ ) => {
80
+ const params: { [k: string]: any } = { method, headers: options?.headers || {} }
81
+
82
+ if (method === 'GET') {
83
+ return params
84
+ }
85
+
86
+ params.headers = { 'Content-Type': 'application/json;charset=UTF-8', ...options?.headers }
87
+ params.body = JSON.stringify(body)
88
+ return { ...params, ...parameters }
89
+ }
90
+
91
+ interface GotrueRequestOptions extends FetchOptions {
92
+ jwt?: string
93
+ redirectTo?: string
94
+ body?: object
95
+ query?: { [key: string]: string }
96
+ /**
97
+ * Function that transforms api response from gotrue into a desirable / standardised format
98
+ */
99
+ xform?: (data: any) => any
100
+ }
101
+
102
+ export async function _request(
103
+ fetcher: Fetch,
104
+ method: RequestMethodType,
105
+ url: string,
106
+ options?: GotrueRequestOptions
107
+ ) {
108
+ const headers = { ...options?.headers }
109
+ if (options?.jwt) {
110
+ headers['Authorization'] = `Bearer ${options.jwt}`
111
+ }
112
+ const qs = options?.query ?? {}
113
+ if (options?.redirectTo) {
114
+ qs['redirect_to'] = options.redirectTo
115
+ }
116
+ const queryString = Object.keys(qs).length ? '?' + new URLSearchParams(qs).toString() : ''
117
+ const data = await _handleRequest(
118
+ fetcher,
119
+ method,
120
+ url + queryString,
121
+ { headers, noResolveJson: options?.noResolveJson },
122
+ {},
123
+ options?.body
124
+ )
125
+ return options?.xform ? options?.xform(data) : { data: { ...data }, error: null }
126
+ }
127
+
128
+ async function _handleRequest(
129
+ fetcher: Fetch,
130
+ method: RequestMethodType,
131
+ url: string,
132
+ options?: FetchOptions,
133
+ parameters?: FetchParameters,
134
+ body?: object
135
+ ): Promise<any> {
136
+ const requestParams = _getRequestParams(method, options, parameters, body)
137
+
138
+ let result: any
139
+
140
+ try {
141
+ result = await fetcher(url, requestParams)
142
+ } catch (e) {
143
+ console.error(e)
144
+
145
+ // fetch failed, likely due to a network or CORS error
146
+ throw new AuthRetryableFetchError(_getErrorMessage(e), 0)
147
+ }
148
+
149
+ if (!result.ok) {
150
+ await handleError(result)
151
+ }
152
+
153
+ if (options?.noResolveJson) {
154
+ return result
155
+ }
156
+
157
+ try {
158
+ return await result.json()
159
+ } catch (e: any) {
160
+ await handleError(e)
161
+ }
162
+ }
163
+
164
+ export function _sessionResponse(data: any): AuthResponse {
165
+ let session = null
166
+ if (hasSession(data)) {
167
+ session = { ...data }
168
+
169
+ if (!data.expires_at) {
170
+ session.expires_at = expiresAt(data.expires_in)
171
+ }
172
+ }
173
+
174
+ const user: User = data.user ?? (data as User)
175
+ return { data: { session, user }, error: null }
176
+ }
177
+
178
+ export function _sessionResponsePassword(data: any): AuthResponsePassword {
179
+ const response = _sessionResponse(data) as AuthResponsePassword
180
+
181
+ if (
182
+ !response.error &&
183
+ data.weak_password &&
184
+ typeof data.weak_password === 'object' &&
185
+ Array.isArray(data.weak_password.reasons) &&
186
+ data.weak_password.reasons.length &&
187
+ data.weak_password.message &&
188
+ typeof data.weak_password.message === 'string' &&
189
+ data.weak_password.reasons.reduce((a: boolean, i: any) => a && typeof i === 'string', true)
190
+ ) {
191
+ response.data.weak_password = data.weak_password
192
+ }
193
+
194
+ return response
195
+ }
196
+
197
+ export function _userResponse(data: any): UserResponse {
198
+ const user: User = data.user ?? (data as User)
199
+ return { data: { user }, error: null }
200
+ }
201
+
202
+ export function _ssoResponse(data: any): SSOResponse {
203
+ return { data, error: null }
204
+ }
205
+
206
+ export function _generateLinkResponse(data: any): GenerateLinkResponse {
207
+ const { action_link, email_otp, hashed_token, redirect_to, verification_type, ...rest } = data
208
+
209
+ const properties: GenerateLinkProperties = {
210
+ action_link,
211
+ email_otp,
212
+ hashed_token,
213
+ redirect_to,
214
+ verification_type,
215
+ }
216
+
217
+ const user: User = { ...rest }
218
+ return {
219
+ data: {
220
+ properties,
221
+ user,
222
+ },
223
+ error: null,
224
+ }
225
+ }
226
+
227
+ export function _noResolveJsonResponse(data: any): Response {
228
+ return data
229
+ }
230
+
231
+ /**
232
+ * hasSession checks if the response object contains a valid session
233
+ * @param data A response object
234
+ * @returns true if a session is in the response
235
+ */
236
+ function hasSession(data: any): boolean {
237
+ return data.access_token && data.refresh_token && data.expires_in
238
+ }
@@ -0,0 +1,306 @@
1
+ import { SupportedStorage } from './types'
2
+ export function expiresAt(expiresIn: number) {
3
+ const timeNow = Math.round(Date.now() / 1000)
4
+ return timeNow + expiresIn
5
+ }
6
+
7
+ export function uuid() {
8
+ return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
9
+ const r = (Math.random() * 16) | 0,
10
+ v = c == 'x' ? r : (r & 0x3) | 0x8
11
+ return v.toString(16)
12
+ })
13
+ }
14
+
15
+ export const isBrowser = () => typeof document !== 'undefined'
16
+
17
+ const localStorageWriteTests = {
18
+ tested: false,
19
+ writable: false,
20
+ }
21
+
22
+ /**
23
+ * Checks whether localStorage is supported on this browser.
24
+ */
25
+ export const supportsLocalStorage = () => {
26
+ if (!isBrowser()) {
27
+ return false
28
+ }
29
+
30
+ try {
31
+ if (typeof globalThis.localStorage !== 'object') {
32
+ return false
33
+ }
34
+ } catch (e) {
35
+ // DOM exception when accessing `localStorage`
36
+ return false
37
+ }
38
+
39
+ if (localStorageWriteTests.tested) {
40
+ return localStorageWriteTests.writable
41
+ }
42
+
43
+ const randomKey = `lswt-${Math.random()}${Math.random()}`
44
+
45
+ try {
46
+ globalThis.localStorage.setItem(randomKey, randomKey)
47
+ globalThis.localStorage.removeItem(randomKey)
48
+
49
+ localStorageWriteTests.tested = true
50
+ localStorageWriteTests.writable = true
51
+ } catch (e) {
52
+ // localStorage can't be written to
53
+ // https://www.chromium.org/for-testers/bug-reporting-guidelines/uncaught-securityerror-failed-to-read-the-localstorage-property-from-window-access-is-denied-for-this-document
54
+
55
+ localStorageWriteTests.tested = true
56
+ localStorageWriteTests.writable = false
57
+ }
58
+
59
+ return localStorageWriteTests.writable
60
+ }
61
+
62
+ /**
63
+ * Extracts parameters encoded in the URL both in the query and fragment.
64
+ */
65
+ export function parseParametersFromURL(href: string) {
66
+ const result: { [parameter: string]: string } = {}
67
+
68
+ const url = new URL(href)
69
+
70
+ if (url.hash && url.hash[0] === '#') {
71
+ try {
72
+ const hashSearchParams = new URLSearchParams(url.hash.substring(1))
73
+ hashSearchParams.forEach((value, key) => {
74
+ result[key] = value
75
+ })
76
+ } catch (e: any) {
77
+ // hash is not a query string
78
+ }
79
+ }
80
+
81
+ // search parameters take precedence over hash parameters
82
+ url.searchParams.forEach((value, key) => {
83
+ result[key] = value
84
+ })
85
+
86
+ return result
87
+ }
88
+
89
+ type Fetch = typeof fetch
90
+
91
+ export const resolveFetch = (customFetch?: Fetch): Fetch => {
92
+ let _fetch: Fetch
93
+ if (customFetch) {
94
+ _fetch = customFetch
95
+ } else if (typeof fetch === 'undefined') {
96
+ _fetch = (...args) =>
97
+ import('@supabase/node-fetch' as any).then(({ default: fetch }) => fetch(...args))
98
+ } else {
99
+ _fetch = fetch
100
+ }
101
+ return (...args) => _fetch(...args)
102
+ }
103
+
104
+ export const looksLikeFetchResponse = (maybeResponse: unknown): maybeResponse is Response => {
105
+ return (
106
+ typeof maybeResponse === 'object' &&
107
+ maybeResponse !== null &&
108
+ 'status' in maybeResponse &&
109
+ 'ok' in maybeResponse &&
110
+ 'json' in maybeResponse &&
111
+ typeof (maybeResponse as any).json === 'function'
112
+ )
113
+ }
114
+
115
+ // Storage helpers
116
+ export const setItemAsync = async (
117
+ storage: SupportedStorage,
118
+ key: string,
119
+ data: any
120
+ ): Promise<void> => {
121
+ await storage.setItem(key, JSON.stringify(data))
122
+ }
123
+
124
+ export const getItemAsync = async (storage: SupportedStorage, key: string): Promise<unknown> => {
125
+ const value = await storage.getItem(key)
126
+
127
+ if (!value) {
128
+ return null
129
+ }
130
+
131
+ try {
132
+ return JSON.parse(value)
133
+ } catch {
134
+ return value
135
+ }
136
+ }
137
+
138
+ export const removeItemAsync = async (storage: SupportedStorage, key: string): Promise<void> => {
139
+ await storage.removeItem(key)
140
+ }
141
+
142
+ export function decodeBase64URL(value: string): string {
143
+ const key = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='
144
+ let base64 = ''
145
+ let chr1, chr2, chr3
146
+ let enc1, enc2, enc3, enc4
147
+ let i = 0
148
+ value = value.replace('-', '+').replace('_', '/')
149
+
150
+ while (i < value.length) {
151
+ enc1 = key.indexOf(value.charAt(i++))
152
+ enc2 = key.indexOf(value.charAt(i++))
153
+ enc3 = key.indexOf(value.charAt(i++))
154
+ enc4 = key.indexOf(value.charAt(i++))
155
+ chr1 = (enc1 << 2) | (enc2 >> 4)
156
+ chr2 = ((enc2 & 15) << 4) | (enc3 >> 2)
157
+ chr3 = ((enc3 & 3) << 6) | enc4
158
+ base64 = base64 + String.fromCharCode(chr1)
159
+
160
+ if (enc3 != 64 && chr2 != 0) {
161
+ base64 = base64 + String.fromCharCode(chr2)
162
+ }
163
+ if (enc4 != 64 && chr3 != 0) {
164
+ base64 = base64 + String.fromCharCode(chr3)
165
+ }
166
+ }
167
+ return base64
168
+ }
169
+
170
+ /**
171
+ * A deferred represents some asynchronous work that is not yet finished, which
172
+ * may or may not culminate in a value.
173
+ * Taken from: https://github.com/mike-north/types/blob/master/src/async.ts
174
+ */
175
+ export class Deferred<T = any> {
176
+ public static promiseConstructor: PromiseConstructor = Promise
177
+
178
+ public readonly promise!: PromiseLike<T>
179
+
180
+ public readonly resolve!: (value?: T | PromiseLike<T>) => void
181
+
182
+ public readonly reject!: (reason?: any) => any
183
+
184
+ public constructor() {
185
+ // eslint-disable-next-line @typescript-eslint/no-extra-semi
186
+ ;(this as any).promise = new Deferred.promiseConstructor((res, rej) => {
187
+ // eslint-disable-next-line @typescript-eslint/no-extra-semi
188
+ ;(this as any).resolve = res
189
+ // eslint-disable-next-line @typescript-eslint/no-extra-semi
190
+ ;(this as any).reject = rej
191
+ })
192
+ }
193
+ }
194
+
195
+ // Taken from: https://stackoverflow.com/questions/38552003/how-to-decode-jwt-token-in-javascript-without-using-a-library
196
+ export function decodeJWTPayload(token: string) {
197
+ // Regex checks for base64url format
198
+ const base64UrlRegex = /^([a-z0-9_-]{4})*($|[a-z0-9_-]{3}=?$|[a-z0-9_-]{2}(==)?$)$/i
199
+
200
+ const parts = token.split('.')
201
+
202
+ if (parts.length !== 3) {
203
+ throw new Error('JWT is not valid: not a JWT structure')
204
+ }
205
+
206
+ if (!base64UrlRegex.test(parts[1])) {
207
+ throw new Error('JWT is not valid: payload is not in base64url format')
208
+ }
209
+
210
+ const base64Url = parts[1]
211
+ return JSON.parse(decodeBase64URL(base64Url))
212
+ }
213
+
214
+ /**
215
+ * Creates a promise that resolves to null after some time.
216
+ */
217
+ export async function sleep(time: number): Promise<null> {
218
+ return await new Promise((accept) => {
219
+ setTimeout(() => accept(null), time)
220
+ })
221
+ }
222
+
223
+ /**
224
+ * Converts the provided async function into a retryable function. Each result
225
+ * or thrown error is sent to the isRetryable function which should return true
226
+ * if the function should run again.
227
+ */
228
+ export function retryable<T>(
229
+ fn: (attempt: number) => Promise<T>,
230
+ isRetryable: (attempt: number, error: any | null, result?: T) => boolean
231
+ ): Promise<T> {
232
+ const promise = new Promise<T>((accept, reject) => {
233
+ // eslint-disable-next-line @typescript-eslint/no-extra-semi
234
+ ;(async () => {
235
+ for (let attempt = 0; attempt < Infinity; attempt++) {
236
+ try {
237
+ const result = await fn(attempt)
238
+
239
+ if (!isRetryable(attempt, null, result)) {
240
+ accept(result)
241
+ return
242
+ }
243
+ } catch (e: any) {
244
+ if (!isRetryable(attempt, e)) {
245
+ reject(e)
246
+ return
247
+ }
248
+ }
249
+ }
250
+ })()
251
+ })
252
+
253
+ return promise
254
+ }
255
+
256
+ function dec2hex(dec: number) {
257
+ return ('0' + dec.toString(16)).substr(-2)
258
+ }
259
+
260
+ // Functions below taken from: https://stackoverflow.com/questions/63309409/creating-a-code-verifier-and-challenge-for-pkce-auth-on-spotify-api-in-reactjs
261
+ export function generatePKCEVerifier() {
262
+ const verifierLength = 56
263
+ const array = new Uint32Array(verifierLength)
264
+ if (typeof crypto === 'undefined') {
265
+ const charSet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~'
266
+ const charSetLen = charSet.length
267
+ let verifier = ''
268
+ for (let i = 0; i < verifierLength; i++) {
269
+ verifier += charSet.charAt(Math.floor(Math.random() * charSetLen))
270
+ }
271
+ return verifier
272
+ }
273
+ crypto.getRandomValues(array)
274
+ return Array.from(array, dec2hex).join('')
275
+ }
276
+
277
+ async function sha256(randomString: string) {
278
+ const encoder = new TextEncoder()
279
+ const encodedData = encoder.encode(randomString)
280
+ const hash = await crypto.subtle.digest('SHA-256', encodedData)
281
+ const bytes = new Uint8Array(hash)
282
+
283
+ return Array.from(bytes)
284
+ .map((c) => String.fromCharCode(c))
285
+ .join('')
286
+ }
287
+
288
+ function base64urlencode(str: string) {
289
+ return btoa(str).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '')
290
+ }
291
+
292
+ export async function generatePKCEChallenge(verifier: string) {
293
+ const hasCryptoSupport =
294
+ typeof crypto !== 'undefined' &&
295
+ typeof crypto.subtle !== 'undefined' &&
296
+ typeof TextEncoder !== 'undefined'
297
+
298
+ if (!hasCryptoSupport) {
299
+ console.warn(
300
+ 'WebCrypto API is not supported. Code challenge method will default to use plain instead of sha256.'
301
+ )
302
+ return verifier
303
+ }
304
+ const hashed = await sha256(verifier)
305
+ return base64urlencode(hashed)
306
+ }