@quiltt/core 3.5.5 → 3.6.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.
package/dist/index.js CHANGED
@@ -1,55 +1,12 @@
1
- import { G as GlobalStorage, e as endpointGraphQL, v as version, d as debugging, S as SubscriptionLink, a as endpointAuth } from './SubscriptionLink-client-B5Tmyqw7.js';
2
- export { L as LocalStorage, M as MemoryStorage, O as Observable, f as Storage, c as cdnBase, b as endpointWebsockets } from './SubscriptionLink-client-B5Tmyqw7.js';
3
1
  import { ApolloLink, ApolloClient } from '@apollo/client/index.js';
4
2
  export { InMemoryCache, gql, useMutation, useQuery, useSubscription } from '@apollo/client/index.js';
3
+ import { G as GlobalStorage, e as endpointGraphQL, v as version, d as debugging, S as SubscriptionLink, a as endpointAuth } from './SubscriptionLink-client-Bf6n9gzT.js';
4
+ export { L as LocalStorage, M as MemoryStorage, O as Observable, b as Storage, c as cdnBase, f as endpointWebsockets, g as getEnv } from './SubscriptionLink-client-Bf6n9gzT.js';
5
5
  import { BatchHttpLink as BatchHttpLink$1 } from '@apollo/client/link/batch-http/index.js';
6
- import fetch from 'cross-fetch';
6
+ import crossfetch from 'cross-fetch';
7
7
  import { onError } from '@apollo/client/link/error/index.js';
8
8
  import { HttpLink as HttpLink$1 } from '@apollo/client/link/http/index.js';
9
9
  import { RetryLink as RetryLink$1 } from '@apollo/client/link/retry/index.js';
10
- import Axios from 'axios';
11
-
12
- const MATCHER = /^(?:[\w-]+\.){2}[\w-]+$/;
13
- const JsonWebTokenParse = (token)=>{
14
- if (typeof token === 'undefined' || token === null) return token;
15
- if (!MATCHER.test(token)) {
16
- console.error(`Invalid Session Token: ${token}`);
17
- return;
18
- }
19
- const [_header, payload, _signature] = token.split('.');
20
- try {
21
- return {
22
- token: token,
23
- claims: JSON.parse(atob(payload))
24
- };
25
- } catch (error) {
26
- console.error(`Invalid Session Token: ${error}`);
27
- }
28
- };
29
-
30
- /**
31
- * This is designed to support singletons to timeouts that can broadcast
32
- * to any observers, preventing race conditions with multiple timeouts.
33
- */ class Timeoutable {
34
- constructor(){
35
- this.observers = [];
36
- this.set = (callback, delay)=>{
37
- if (this.timeout) {
38
- clearTimeout(this.timeout);
39
- }
40
- this.observers.push(callback);
41
- this.timeout = setTimeout(this.broadcast.bind(this), delay);
42
- };
43
- this.clear = (observer)=>{
44
- this.observers = this.observers.filter((callback)=>callback !== observer);
45
- };
46
- // Only sends to the 1st listener, but ensures that someone is notified
47
- this.broadcast = ()=>{
48
- if (this.observers.length === 0) return;
49
- this.observers[0](undefined);
50
- };
51
- }
52
- }
53
10
 
54
11
  var ConnectorSDKEventType;
55
12
  (function(ConnectorSDKEventType) {
@@ -81,9 +38,11 @@ var ConnectorSDKEventType;
81
38
  }
82
39
  }
83
40
 
41
+ // Use `cross-fetch` only if `fetch` is not available on the `globalThis` object
42
+ const effectiveFetch$2 = typeof fetch === 'undefined' ? crossfetch : fetch;
84
43
  const BatchHttpLink = new BatchHttpLink$1({
85
44
  uri: endpointGraphQL,
86
- fetch
45
+ fetch: effectiveFetch$2
87
46
  });
88
47
 
89
48
  const ErrorLink = onError(({ graphQLErrors, networkError })=>{
@@ -104,9 +63,11 @@ const ErrorLink = onError(({ graphQLErrors, networkError })=>{
104
63
 
105
64
  const ForwardableLink = new ApolloLink((operation, forward)=>forward(operation));
106
65
 
66
+ // Use `cross-fetch` only if `fetch` is not available on the `globalThis` object
67
+ const effectiveFetch$1 = typeof fetch === 'undefined' ? crossfetch : fetch;
107
68
  const HttpLink = new HttpLink$1({
108
69
  uri: endpointGraphQL,
109
- fetch
70
+ fetch: effectiveFetch$1
110
71
  });
111
72
 
112
73
  const RetryLink = new RetryLink$1({
@@ -152,35 +113,51 @@ class QuilttClient extends ApolloClient {
152
113
  }
153
114
  }
154
115
 
116
+ // Use `cross-fetch` only if `fetch` is not available on the `globalThis` object
117
+ const effectiveFetch = typeof fetch === 'undefined' ? crossfetch : fetch;
155
118
  const RETRY_DELAY = 150 // ms
156
119
  ;
157
120
  const RETRIES = 10 // 150, 300, 450, 600, 750, 900, 1050, 1200, 1350, 1500 = 8.250s
158
121
  ;
159
- // Create an axios singleton for Quiltt, to prevent mutating other instances
160
- const axios = Axios.create();
161
- // Example: axios.get(url, { retry: true })
162
- axios.interceptors.response.use(undefined, (error)=>{
163
- const { config, message, response } = error;
164
- const messageLower = message.toLowerCase();
165
- if (!config || !config.retry) {
166
- return Promise.reject(error);
167
- }
168
- // Retry Network timeout, Network errors, and Too Many Requests
169
- if (!(messageLower.includes('timeout') || messageLower.includes('network error') || response?.status === 429)) {
170
- return Promise.reject(error);
171
- }
172
- if (config.retriesRemaining === undefined) {
173
- config.retriesRemaining = RETRIES - 1;
174
- } else if (config.retriesRemaining === 1) {
122
+ /**
123
+ * A wrapper around the native `fetch` function that adds automatic retries on failure, including network errors and HTTP 429 responses.
124
+ * Now treats any response with status < 500 as valid.
125
+ */ const fetchWithRetry = async (url, options = {
126
+ retry: false
127
+ })=>{
128
+ const { retry, retriesRemaining, validateStatus = (status)=>status >= 200 && status < 300, ...fetchOptions } = options;
129
+ try {
130
+ const response = await effectiveFetch(url, fetchOptions);
131
+ const isResponseOk = validateStatus(response.status);
132
+ if (isResponseOk) {
133
+ return {
134
+ data: await response.json().catch(()=>null),
135
+ status: response.status,
136
+ statusText: response.statusText,
137
+ headers: response.headers,
138
+ ok: isResponseOk
139
+ };
140
+ }
141
+ // If validateStatus fails, and retry is enabled, prepare to retry for eligible status codes
142
+ if (retry && (response.status >= 500 || response.status === 429)) {
143
+ throw new Error('Retryable failure');
144
+ }
145
+ throw new Error('HTTP error with status ' + response.status);
146
+ } catch (error) {
147
+ if (retry) {
148
+ const currentRetriesRemaining = retriesRemaining !== undefined ? retriesRemaining : RETRIES;
149
+ if (currentRetriesRemaining > 0) {
150
+ const delayTime = RETRY_DELAY * (RETRIES - currentRetriesRemaining);
151
+ await new Promise((resolve)=>setTimeout(resolve, delayTime));
152
+ return fetchWithRetry(url, {
153
+ ...options,
154
+ retriesRemaining: currentRetriesRemaining - 1
155
+ });
156
+ }
157
+ }
175
158
  return Promise.reject(error);
176
- } else {
177
- config.retriesRemaining -= 1;
178
159
  }
179
- const delay = new Promise((resolve)=>{
180
- setTimeout(()=>resolve(), RETRY_DELAY * (RETRIES - config.retriesRemaining));
181
- });
182
- return delay.then(()=>axios(config));
183
- });
160
+ };
184
161
 
185
162
  var AuthStrategies;
186
163
  (function(AuthStrategies) {
@@ -194,47 +171,64 @@ class AuthAPI {
194
171
  * Response Statuses:
195
172
  * - 200: OK -> Session is Valid
196
173
  * - 401: Unauthorized -> Session is Invalid
197
- */ this.ping = (token)=>{
198
- return axios.get(endpointAuth, this.config(token));
174
+ */ this.ping = async (token)=>{
175
+ const response = await fetchWithRetry(endpointAuth, {
176
+ method: 'GET',
177
+ ...this.config(token)
178
+ });
179
+ return response;
199
180
  };
200
181
  /**
201
182
  * Response Statuses:
202
183
  * - 201: Created -> Profile Created, New Session Returned
203
184
  * - 202: Accepted -> Profile Found, MFA Code Sent for `authenticate`
204
185
  * - 422: Unprocessable Entity -> Invalid Payload
205
- */ this.identify = (payload)=>{
206
- return axios.post(endpointAuth, this.body(payload), this.config());
186
+ */ this.identify = async (payload)=>{
187
+ const response = await fetchWithRetry(endpointAuth, {
188
+ method: 'POST',
189
+ body: JSON.stringify(this.body(payload)),
190
+ ...this.config()
191
+ });
192
+ return response;
207
193
  };
208
194
  /**
209
195
  * Response Statuses:
210
196
  * - 201: Created -> MFA Validated, New Session Returned
211
197
  * - 401: Unauthorized -> MFA Invalid
212
198
  * - 422: Unprocessable Entity -> Invalid Payload
213
- */ this.authenticate = (payload)=>{
214
- return axios.put(endpointAuth, this.body(payload), this.config());
199
+ */ this.authenticate = async (payload)=>{
200
+ const response = await fetchWithRetry(endpointAuth, {
201
+ method: 'PUT',
202
+ body: JSON.stringify(this.body(payload)),
203
+ ...this.config()
204
+ });
205
+ return response;
215
206
  };
216
207
  /**
217
208
  * Response Statuses:
218
209
  * - 204: No Content -> Session Revoked
219
210
  * - 401: Unauthorized -> Session Not Found
220
- */ this.revoke = (token)=>{
221
- return axios.delete(endpointAuth, this.config(token));
211
+ */ this.revoke = async (token)=>{
212
+ const response = await fetchWithRetry(endpointAuth, {
213
+ method: 'DELETE',
214
+ ...this.config(token)
215
+ });
216
+ return response;
222
217
  };
223
218
  this.config = (token)=>{
224
- const headers = {
225
- 'Content-Type': 'application/json',
226
- Accept: 'application/json'
227
- };
219
+ const headers = new Headers();
220
+ headers.set('Content-Type', 'application/json');
221
+ headers.set('Accept', 'application/json');
228
222
  if (token) {
229
- headers.Authorization = `Bearer ${token}`;
223
+ headers.set('Authorization', `Bearer ${token}`);
230
224
  }
231
225
  return {
232
- headers: headers,
226
+ headers,
233
227
  validateStatus: this.validateStatus,
234
228
  retry: true
235
229
  };
236
230
  };
237
- this.validateStatus = (status)=>status < 500;
231
+ this.validateStatus = (status)=>status < 500 && status !== 429;
238
232
  this.body = (payload)=>{
239
233
  if (!this.clientId) {
240
234
  console.error('Quiltt Client ID is not set. Unable to identify & authenticate');
@@ -250,4 +244,46 @@ class AuthAPI {
250
244
  }
251
245
  }
252
246
 
247
+ const MATCHER = /^(?:[\w-]+\.){2}[\w-]+$/;
248
+ const JsonWebTokenParse = (token)=>{
249
+ if (typeof token === 'undefined' || token === null) return token;
250
+ if (!MATCHER.test(token)) {
251
+ console.error(`Invalid Session Token: ${token}`);
252
+ return;
253
+ }
254
+ const [_header, payload, _signature] = token.split('.');
255
+ try {
256
+ return {
257
+ token: token,
258
+ claims: JSON.parse(atob(payload))
259
+ };
260
+ } catch (error) {
261
+ console.error(`Invalid Session Token: ${error}`);
262
+ }
263
+ };
264
+
265
+ /**
266
+ * This is designed to support singletons to timeouts that can broadcast
267
+ * to any observers, preventing race conditions with multiple timeouts.
268
+ */ class Timeoutable {
269
+ constructor(){
270
+ this.observers = [];
271
+ this.set = (callback, delay)=>{
272
+ if (this.timeout) {
273
+ clearTimeout(this.timeout);
274
+ }
275
+ this.observers.push(callback);
276
+ this.timeout = setTimeout(this.broadcast.bind(this), delay);
277
+ };
278
+ this.clear = (observer)=>{
279
+ this.observers = this.observers.filter((callback)=>callback !== observer);
280
+ };
281
+ // Only sends to the 1st listener, but ensures that someone is notified
282
+ this.broadcast = ()=>{
283
+ if (this.observers.length === 0) return;
284
+ this.observers[0](undefined);
285
+ };
286
+ }
287
+ }
288
+
253
289
  export { AuthAPI, AuthLink, AuthStrategies, BatchHttpLink, ConnectorSDKEventType, ErrorLink, ForwardableLink, GlobalStorage, HttpLink, JsonWebTokenParse, QuilttClient, RetryLink, SubscriptionLink, TerminatingLink, Timeoutable, VersionLink, debugging, endpointAuth, endpointGraphQL, version };
package/package.json CHANGED
@@ -1,17 +1,17 @@
1
1
  {
2
2
  "name": "@quiltt/core",
3
- "version": "3.5.5",
3
+ "version": "3.6.0",
4
4
  "description": "Javascript API client and utilities for Quiltt",
5
- "repository": {
6
- "type": "git",
7
- "url": "https://github.com/quiltt/quiltt-public.git",
8
- "directory": "packages/core"
9
- },
10
- "homepage": "https://github.com/quiltt/quiltt-public/tree/main/packages/core#readme",
11
5
  "keywords": [
12
6
  "quiltt",
13
7
  "typescript"
14
8
  ],
9
+ "homepage": "https://github.com/quiltt/quiltt-js/tree/main/packages/core#readme",
10
+ "repository": {
11
+ "type": "git",
12
+ "url": "https://github.com/quiltt/quiltt-js.git",
13
+ "directory": "packages/core"
14
+ },
15
15
  "license": "MIT",
16
16
  "sideEffects": [
17
17
  "./src/Storage/Local.ts",
@@ -33,16 +33,15 @@
33
33
  "CHANGELOG.md"
34
34
  ],
35
35
  "dependencies": {
36
- "@apollo/client": "^3.7.16",
37
- "axios": "^1.6.0",
38
- "cross-fetch": "^3.1.8",
36
+ "@apollo/client": "^3.9.9",
37
+ "cross-fetch": "^4.0.0",
39
38
  "graphql": "^16.8.1",
40
- "graphql-ruby-client": "^1.11.8"
39
+ "graphql-ruby-client": "^1.13.3"
41
40
  },
42
41
  "devDependencies": {
43
42
  "@trivago/prettier-plugin-sort-imports": "4.1.1",
44
- "@types/node": "20.11.25",
45
- "@types/react": "18.2.64",
43
+ "@types/node": "20.12.2",
44
+ "@types/react": "18.2.73",
46
45
  "@typescript-eslint/eslint-plugin": "5.60.1",
47
46
  "@typescript-eslint/parser": "5.60.1",
48
47
  "bunchee": "4.4.8",
@@ -51,7 +50,7 @@
51
50
  "eslint-plugin-prettier": "4.2.1",
52
51
  "prettier": "2.8.8",
53
52
  "rimraf": "5.0.5",
54
- "typescript": "5.4.2"
53
+ "typescript": "5.4.3"
55
54
  },
56
55
  "publishConfig": {
57
56
  "access": "public"
@@ -1,4 +1,4 @@
1
- import { GlobalStorage } from '@/Storage'
1
+ import { GlobalStorage } from '@/storage'
2
2
  import type { FetchResult, NextLink, Operation } from '@apollo/client/core/index.js'
3
3
  import { ApolloLink, Observable } from '@apollo/client/core/index.js'
4
4
  import { print } from 'graphql'
@@ -1,7 +1,7 @@
1
1
  import type { FetchResult, NextLink, Observable, Operation } from '@apollo/client/index.js'
2
2
  import { ApolloLink } from '@apollo/client/index.js'
3
3
 
4
- import { GlobalStorage } from '@/Storage'
4
+ import { GlobalStorage } from '@/storage'
5
5
 
6
6
  /**
7
7
  * unauthorizedCallback only triggers in the event the token is present, and
@@ -1,12 +1,14 @@
1
1
  import { BatchHttpLink as ApolloHttpLink } from '@apollo/client/link/batch-http/index.js'
2
+ import crossfetch from 'cross-fetch'
2
3
 
3
- import fetch from 'cross-fetch'
4
+ // Use `cross-fetch` only if `fetch` is not available on the `globalThis` object
5
+ const effectiveFetch = typeof fetch === 'undefined' ? crossfetch : fetch
4
6
 
5
7
  import { endpointGraphQL } from '../../../configuration'
6
8
 
7
9
  export const BatchHttpLink = new ApolloHttpLink({
8
10
  uri: endpointGraphQL,
9
- fetch,
11
+ fetch: effectiveFetch,
10
12
  })
11
13
 
12
14
  export default BatchHttpLink
@@ -1,4 +1,4 @@
1
- import { GlobalStorage } from '@/Storage'
1
+ import { GlobalStorage } from '@/storage'
2
2
 
3
3
  import type { ServerError } from '@apollo/client/index.js'
4
4
  import { onError } from '@apollo/client/link/error/index.js'
@@ -1,12 +1,14 @@
1
1
  import { HttpLink as ApolloHttpLink } from '@apollo/client/link/http/index.js'
2
+ import crossfetch from 'cross-fetch'
2
3
 
3
- import fetch from 'cross-fetch'
4
+ // Use `cross-fetch` only if `fetch` is not available on the `globalThis` object
5
+ const effectiveFetch = typeof fetch === 'undefined' ? crossfetch : fetch
4
6
 
5
7
  import { endpointGraphQL } from '../../../configuration'
6
8
 
7
9
  export const HttpLink = new ApolloHttpLink({
8
10
  uri: endpointGraphQL,
9
- fetch,
11
+ fetch: effectiveFetch,
10
12
  })
11
13
 
12
14
  export default HttpLink
@@ -1,7 +1,6 @@
1
- import type { AxiosRequestConfig, AxiosResponse } from './axios'
2
- import { axios } from './axios'
3
-
4
- import { endpointAuth } from '../../configuration'
1
+ import { endpointAuth } from '@/configuration'
2
+ import type { FetchResponse } from './fetchWithRetry'
3
+ import { fetchWithRetry } from './fetchWithRetry'
5
4
 
6
5
  export enum AuthStrategies {
7
6
  Email = 'email',
@@ -22,7 +21,7 @@ export type UsernamePayload = EmailInput | PhoneInput
22
21
  export type PasscodePayload = UsernamePayload & { passcode: string }
23
22
 
24
23
  type SessionData = { token: string }
25
- type NoContentData = void
24
+ type NoContentData = null
26
25
  type UnauthorizedData = { message: string; instruction: string }
27
26
  export type UnprocessableData = { [attribute: string]: Array<string> }
28
27
 
@@ -31,8 +30,8 @@ type Identify = SessionData | NoContentData | UnprocessableData
31
30
  type Authenticate = SessionData | UnauthorizedData | UnprocessableData
32
31
  type Revoke = NoContentData | UnauthorizedData
33
32
 
34
- export type SessionResponse = AxiosResponse<SessionData>
35
- export type UnprocessableResponse = AxiosResponse<UnprocessableData>
33
+ export type SessionResponse = FetchResponse<SessionData>
34
+ export type UnprocessableResponse = FetchResponse<UnprocessableData>
36
35
 
37
36
  // https://www.quiltt.dev/api-reference/rest/auth#
38
37
  export class AuthAPI {
@@ -47,8 +46,12 @@ export class AuthAPI {
47
46
  * - 200: OK -> Session is Valid
48
47
  * - 401: Unauthorized -> Session is Invalid
49
48
  */
50
- ping = (token: string) => {
51
- return axios.get<Ping>(endpointAuth, this.config(token))
49
+ ping = async (token: string) => {
50
+ const response = await fetchWithRetry<Ping>(endpointAuth, {
51
+ method: 'GET',
52
+ ...this.config(token),
53
+ })
54
+ return response
52
55
  }
53
56
 
54
57
  /**
@@ -57,8 +60,13 @@ export class AuthAPI {
57
60
  * - 202: Accepted -> Profile Found, MFA Code Sent for `authenticate`
58
61
  * - 422: Unprocessable Entity -> Invalid Payload
59
62
  */
60
- identify = (payload: UsernamePayload) => {
61
- return axios.post<Identify>(endpointAuth, this.body(payload), this.config())
63
+ identify = async (payload: UsernamePayload) => {
64
+ const response = await fetchWithRetry<Identify>(endpointAuth, {
65
+ method: 'POST',
66
+ body: JSON.stringify(this.body(payload)),
67
+ ...this.config(),
68
+ })
69
+ return response
62
70
  }
63
71
 
64
72
  /**
@@ -67,8 +75,13 @@ export class AuthAPI {
67
75
  * - 401: Unauthorized -> MFA Invalid
68
76
  * - 422: Unprocessable Entity -> Invalid Payload
69
77
  */
70
- authenticate = (payload: PasscodePayload) => {
71
- return axios.put<Authenticate>(endpointAuth, this.body(payload), this.config())
78
+ authenticate = async (payload: PasscodePayload): Promise<FetchResponse<Authenticate>> => {
79
+ const response = await fetchWithRetry<Authenticate>(endpointAuth, {
80
+ method: 'PUT',
81
+ body: JSON.stringify(this.body(payload)),
82
+ ...this.config(),
83
+ })
84
+ return response
72
85
  }
73
86
 
74
87
  /**
@@ -76,28 +89,30 @@ export class AuthAPI {
76
89
  * - 204: No Content -> Session Revoked
77
90
  * - 401: Unauthorized -> Session Not Found
78
91
  */
79
- revoke = (token: string) => {
80
- return axios.delete<Revoke>(endpointAuth, this.config(token))
92
+ revoke = async (token: string): Promise<FetchResponse<Revoke>> => {
93
+ const response = await fetchWithRetry<Revoke>(endpointAuth, {
94
+ method: 'DELETE',
95
+ ...this.config(token),
96
+ })
97
+ return response
81
98
  }
82
99
 
83
- private config = (token?: string): AxiosRequestConfig => {
84
- const headers: { [id: string]: string } = {
85
- 'Content-Type': 'application/json',
86
- Accept: 'application/json',
87
- }
100
+ private config = (token?: string) => {
101
+ const headers = new Headers()
102
+ headers.set('Content-Type', 'application/json')
103
+ headers.set('Accept', 'application/json')
88
104
 
89
105
  if (token) {
90
- headers.Authorization = `Bearer ${token}`
106
+ headers.set('Authorization', `Bearer ${token}`)
91
107
  }
92
108
 
93
109
  return {
94
- headers: headers,
110
+ headers,
95
111
  validateStatus: this.validateStatus,
96
112
  retry: true,
97
113
  }
98
114
  }
99
-
100
- private validateStatus = (status: number) => status < 500
115
+ private validateStatus = (status: number) => status < 500 && status !== 429
101
116
 
102
117
  private body = (payload: any) => {
103
118
  if (!this.clientId) {
@@ -112,5 +127,3 @@ export class AuthAPI {
112
127
  }
113
128
  }
114
129
  }
115
-
116
- export default AuthAPI
@@ -0,0 +1,73 @@
1
+ import crossfetch from 'cross-fetch'
2
+
3
+ // Use `cross-fetch` only if `fetch` is not available on the `globalThis` object
4
+ const effectiveFetch = typeof fetch === 'undefined' ? crossfetch : fetch
5
+
6
+ type FetchWithRetryOptions = RequestInit & {
7
+ retry?: boolean
8
+ retriesRemaining?: number
9
+ validateStatus?: (status: number) => boolean
10
+ }
11
+
12
+ export type FetchResponse<T> = {
13
+ data: T
14
+ status: number
15
+ statusText: string
16
+ headers: Headers
17
+ ok: boolean
18
+ }
19
+
20
+ const RETRY_DELAY = 150 // ms
21
+ const RETRIES = 10 // 150, 300, 450, 600, 750, 900, 1050, 1200, 1350, 1500 = 8.250s
22
+
23
+ /**
24
+ * A wrapper around the native `fetch` function that adds automatic retries on failure, including network errors and HTTP 429 responses.
25
+ * Now treats any response with status < 500 as valid.
26
+ */
27
+ export const fetchWithRetry = async <T>(
28
+ url: RequestInfo,
29
+ options: FetchWithRetryOptions = { retry: false }
30
+ ): Promise<FetchResponse<T>> => {
31
+ const {
32
+ retry,
33
+ retriesRemaining,
34
+ validateStatus = (status) => status >= 200 && status < 300, // Default to success for 2xx responses
35
+ ...fetchOptions
36
+ } = options
37
+
38
+ try {
39
+ const response = await effectiveFetch(url, fetchOptions)
40
+
41
+ const isResponseOk = validateStatus(response.status)
42
+
43
+ if (isResponseOk) {
44
+ return {
45
+ data: await response.json().catch(() => null),
46
+ status: response.status,
47
+ statusText: response.statusText,
48
+ headers: response.headers,
49
+ ok: isResponseOk,
50
+ }
51
+ }
52
+
53
+ // If validateStatus fails, and retry is enabled, prepare to retry for eligible status codes
54
+ if (retry && (response.status >= 500 || response.status === 429)) {
55
+ throw new Error('Retryable failure')
56
+ }
57
+
58
+ throw new Error('HTTP error with status ' + response.status)
59
+ } catch (error) {
60
+ if (retry) {
61
+ const currentRetriesRemaining = retriesRemaining !== undefined ? retriesRemaining : RETRIES
62
+ if (currentRetriesRemaining > 0) {
63
+ const delayTime = RETRY_DELAY * (RETRIES - currentRetriesRemaining)
64
+ await new Promise((resolve) => setTimeout(resolve, delayTime))
65
+ return fetchWithRetry(url, {
66
+ ...options,
67
+ retriesRemaining: currentRetriesRemaining - 1,
68
+ })
69
+ }
70
+ }
71
+ return Promise.reject(error)
72
+ }
73
+ }
@@ -1 +1 @@
1
- export * from './AuthAPI'
1
+ export * from './auth'
@@ -1,32 +1,46 @@
1
1
  import { name as packageName, version as packageVersion } from '../package.json'
2
2
 
3
- const QUILTT_API_INSECURE = (() => {
3
+ /**
4
+ * Retrieves the environment variable by key, with fallback and type conversion,
5
+ * supporting Node.js, Vite, and potentially other runtime environments.
6
+ */
7
+ export const getEnv = (key: string, fallback: any = undefined): any => {
4
8
  try {
5
- return process.env.QUILTT_API_INSECURE
6
- } catch {
7
- return undefined
8
- }
9
- })()
9
+ let value: string | undefined
10
10
 
11
- const QUILTT_API_DOMAIN = (() => {
12
- try {
13
- return process.env.QUILTT_API_DOMAIN
14
- } catch {
11
+ // Check if running under Node.js and use process.env
12
+ if (typeof process !== 'undefined' && process.env) {
13
+ value = process.env[key]
14
+ }
15
+
16
+ // Return the value after type conversion if necessary or use fallback
17
+ if (value === undefined || value === null) {
18
+ return fallback
19
+ }
20
+
21
+ // Convert to boolean if the value is 'true' or 'false'
22
+ if (value === 'true' || value === 'false') {
23
+ return value === 'true'
24
+ }
25
+
26
+ // Convert to number if it's numeric
27
+ if (!isNaN(Number(value))) {
28
+ return Number(value)
29
+ }
30
+
31
+ return value
32
+ } catch (error) {
15
33
  return undefined
16
34
  }
17
- })()
35
+ }
18
36
 
19
- const QUILTT_DEBUG = (() => {
20
- try {
21
- return !!process.env.QUILTT_DEBUG || process.env.NODE_ENV !== 'production'
22
- } catch {
23
- return false
24
- }
25
- })()
37
+ const QUILTT_API_INSECURE = getEnv('QUILTT_API_INSECURE', false)
38
+ const QUILTT_API_DOMAIN = getEnv('QUILTT_API_DOMAIN', 'quiltt.io')
39
+ const QUILTT_DEBUG = getEnv('QUILTT_DEBUG', process?.env?.NODE_ENV !== 'production')
26
40
 
27
41
  const domain = QUILTT_API_DOMAIN || 'quiltt.io'
28
42
  const protocolHttp = `http${QUILTT_API_INSECURE ? '' : 's'}`
29
- const protocolWebsockets = `ws${QUILTT_API_DOMAIN ? '' : 's'}`
43
+ const protocolWebsockets = `ws${QUILTT_API_INSECURE ? '' : 's'}`
30
44
 
31
45
  export const debugging = QUILTT_DEBUG
32
46
  export const version = `${packageName}: v${packageVersion}`