@quiltt/core 5.1.2 → 5.2.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/CHANGELOG.md CHANGED
@@ -1,5 +1,52 @@
1
1
  # @quiltt/core
2
2
 
3
+ ## 5.2.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#427](https://github.com/quiltt/quiltt-js/pull/427) [`6d4b768`](https://github.com/quiltt/quiltt-js/commit/6d4b7683f49d0a6e649a4bdfaff0398669102a63) Thanks [@zubairaziz](https://github.com/zubairaziz)! - Bump minor version to be consistent with SemVer standards
8
+
9
+ ## 5.1.3
10
+
11
+ ### Patch Changes
12
+
13
+ - [#425](https://github.com/quiltt/quiltt-js/pull/425) [`c684b3b`](https://github.com/quiltt/quiltt-js/commit/c684b3b5f6ea2829e2abfa2a75c0d430edad66a5) Thanks [@zubairaziz](https://github.com/zubairaziz)! - Add @quiltt/capacitor package for Ionic and Capacitor apps
14
+
15
+ - Framework-agnostic by default — works with Vue, Angular, Svelte, or vanilla JS
16
+ - Vue 3 components via `@quiltt/capacitor/vue` subpath
17
+ - React components via `@quiltt/capacitor/react` subpath
18
+ - Native iOS (Swift) and Android (Kotlin) plugins for OAuth deep linking
19
+ - Supports Capacitor 6, 7, and 8
20
+
21
+ Add @quiltt/vue package for Vue 3 applications
22
+
23
+ - `QuilttPlugin` for session management via Vue's provide/inject
24
+ - `useQuilttSession` composable for authentication
25
+ - `useQuilttConnector` composable for programmatic control
26
+ - `QuilttButton`, `QuilttConnector`, `QuilttContainer` components
27
+ - Add `@quiltt/capacitor/vue` entry point for Capacitor apps
28
+
29
+ Rename `oauthRedirectUrl` to `appLauncherUrl` for mobile OAuth flows
30
+
31
+ This change introduces `appLauncherUrl` as the new preferred property name for specifying the Universal Link (iOS) or App Link (Android) that redirects users back to your app after OAuth authentication.
32
+
33
+ **Deprecation Warning:** The `oauthRedirectUrl` property is now deprecated but remains fully functional for backwards compatibility. Existing code using `oauthRedirectUrl` will continue to work without modifications.
34
+
35
+ **Migration:**
36
+
37
+ - Replace `oauthRedirectUrl` with `appLauncherUrl` in your component props
38
+ - The behavior remains identical; only the property name has changed
39
+
40
+ **Example:**
41
+
42
+ ```tsx
43
+ // Before (deprecated, still works)
44
+ <QuilttConnector oauthRedirectUrl="https://myapp.com/callback" />
45
+
46
+ // After (recommended)
47
+ <QuilttConnector appLauncherUrl="https://myapp.com/callback" />
48
+ ```
49
+
3
50
  ## 5.1.2
4
51
 
5
52
  ### Patch Changes
package/README.md CHANGED
@@ -5,7 +5,7 @@
5
5
 
6
6
  `@quiltt/core` provides essential primitives for building Javascript-based applications with Quiltt. It provides an Auth API client and modules for handling JSON Web Tokens (JWT), observables, storage management, timeouts, API handling, and Typescript types.
7
7
 
8
- This package is used by both [`@quiltt/react`](../react#readme) and [`@quiltt/react-native`](../react-native#readme). If you bundle it separately, we recommend keeping versions in sync to avoid issues with mismatched dependencies.
8
+ This package is used by [`@quiltt/react`](../react#readme), [`@quiltt/vue`](../vue#readme), and [`@quiltt/react-native`](../react-native#readme). If you bundle it separately, we recommend keeping versions in sync to avoid issues with mismatched dependencies.
9
9
 
10
10
  For general project information and contributing guidelines, see the [main repository README](../../README.md).
11
11
 
@@ -112,4 +112,6 @@ For information on how to contribute to this project, please refer to the [repos
112
112
  ## Related Packages
113
113
 
114
114
  - [`@quiltt/react`](../react#readme) - React components and hooks
115
+ - [`@quiltt/vue`](../vue#readme) - Vue 3 components and composables
115
116
  - [`@quiltt/react-native`](../react-native#readme) - React Native and Expo components
117
+ - [`@quiltt/capacitor`](../capacitor#readme) - Capacitor plugin and mobile framework adapters
@@ -101,7 +101,15 @@ type ConnectorSDKCallbackMetadata = {
101
101
  type ConnectorSDKConnectOptions = ConnectorSDKCallbacks & {
102
102
  /** The Institution ID or search term to connect */
103
103
  institution?: string;
104
- /** The OAuth redirect URL for mobile or embedded webview flows */
104
+ /**
105
+ * The app launcher URL for mobile OAuth flows.
106
+ * This URL should be a Universal Link (iOS) or App Link (Android) that redirects back to your app.
107
+ */
108
+ appLauncherUrl?: string;
109
+ /**
110
+ * @deprecated Use `appLauncherUrl` instead. This property will be removed in a future version.
111
+ * The OAuth redirect URL for mobile or embedded webview flows.
112
+ */
105
113
  oauthRedirectUrl?: string;
106
114
  };
107
115
  /**
@@ -111,7 +119,15 @@ type ConnectorSDKConnectOptions = ConnectorSDKCallbacks & {
111
119
  type ConnectorSDKReconnectOptions = ConnectorSDKCallbacks & {
112
120
  /** The ID of the Connection to reconnect */
113
121
  connectionId: string;
114
- /** The OAuth redirect URL for mobile or embedded webview flows */
122
+ /**
123
+ * The app launcher URL for mobile OAuth flows.
124
+ * This URL should be a Universal Link (iOS) or App Link (Android) that redirects back to your app.
125
+ */
126
+ appLauncherUrl?: string;
127
+ /**
128
+ * @deprecated Use `appLauncherUrl` instead. This property will be removed in a future version.
129
+ * The OAuth redirect URL for mobile or embedded webview flows.
130
+ */
115
131
  oauthRedirectUrl?: string;
116
132
  };
117
133
  /** Options to initialize Connector
@@ -126,7 +142,15 @@ type ConnectorSDKConnectorOptions = ConnectorSDKCallbacks & {
126
142
  connectionId?: string;
127
143
  /** The nonce to use for the script tag */
128
144
  nonce?: string;
129
- /** The OAuth redirect URL for mobile or embedded webview flows */
145
+ /**
146
+ * The app launcher URL for mobile OAuth flows.
147
+ * This URL should be a Universal Link (iOS) or App Link (Android) that redirects back to your app.
148
+ */
149
+ appLauncherUrl?: string;
150
+ /**
151
+ * @deprecated Use `appLauncherUrl` instead. This property will be removed in a future version.
152
+ * The OAuth redirect URL for mobile or embedded webview flows.
153
+ */
130
154
  oauthRedirectUrl?: string;
131
155
  };
132
156
 
@@ -37,15 +37,20 @@ var crossfetch__default = /*#__PURE__*/_interopDefault(crossfetch);
37
37
  const effectiveFetch = typeof fetch === 'undefined' ? crossfetch__default.default : fetch;
38
38
  const RETRY_DELAY = 150 // ms
39
39
  ;
40
- const RETRIES = 10 // 150, 300, 450, 600, 750, 900, 1050, 1200, 1350, 1500 = 8.250s
40
+ const MAX_RETRY_DELAY = 1500 // ms
41
41
  ;
42
+ const RETRIES = 10;
43
+ const getRetryDelay = (attemptNumber)=>{
44
+ const exponentialDelay = RETRY_DELAY * 2 ** (attemptNumber - 1);
45
+ return Math.min(exponentialDelay, MAX_RETRY_DELAY);
46
+ };
42
47
  /**
43
48
  * A wrapper around the native `fetch` function that adds automatic retries on failure, including network errors and HTTP 429 responses.
44
49
  * Now treats any response with status < 500 as valid.
45
50
  */ const fetchWithRetry = async (url, options = {
46
51
  retry: false
47
52
  })=>{
48
- const { retry, retriesRemaining, validateStatus = (status)=>status >= 200 && status < 300, ...fetchOptions } = options;
53
+ const { retry, retriesRemaining, initialRetries, validateStatus = (status)=>status >= 200 && status < 300, ...fetchOptions } = options;
49
54
  try {
50
55
  const response = await effectiveFetch(url, fetchOptions);
51
56
  const isResponseOk = validateStatus(response.status);
@@ -60,18 +65,27 @@ const RETRIES = 10 // 150, 300, 450, 600, 750, 900, 1050, 1200, 1350, 1500 = 8.2
60
65
  }
61
66
  // If validateStatus fails, and retry is enabled, prepare to retry for eligible status codes
62
67
  if (retry && (response.status >= 500 || response.status === 429)) {
63
- throw new Error('Retryable failure');
68
+ const error = new Error(`HTTP error with status ${response.status}`);
69
+ error.retryable = true;
70
+ throw error;
64
71
  }
65
- throw new Error(`HTTP error with status ${response.status}`);
72
+ const error = new Error(`HTTP error with status ${response.status}`);
73
+ error.retryable = false;
74
+ throw error;
66
75
  } catch (error) {
67
- if (retry) {
76
+ const retryableError = error;
77
+ const shouldRetry = retry && retryableError.retryable !== false;
78
+ if (shouldRetry) {
68
79
  const currentRetriesRemaining = retriesRemaining !== undefined ? retriesRemaining : RETRIES;
80
+ const totalRetries = initialRetries ?? currentRetriesRemaining;
69
81
  if (currentRetriesRemaining > 0) {
70
- const delayTime = RETRY_DELAY * (RETRIES - currentRetriesRemaining);
82
+ const attemptNumber = totalRetries - currentRetriesRemaining + 1;
83
+ const delayTime = getRetryDelay(attemptNumber);
71
84
  await new Promise((resolve)=>setTimeout(resolve, delayTime));
72
85
  return fetchWithRetry(url, {
73
86
  ...options,
74
- retriesRemaining: currentRetriesRemaining - 1
87
+ retriesRemaining: currentRetriesRemaining - 1,
88
+ initialRetries: totalRetries
75
89
  });
76
90
  }
77
91
  }
@@ -31,15 +31,20 @@ import crossfetch from 'cross-fetch';
31
31
  const effectiveFetch = typeof fetch === 'undefined' ? crossfetch : fetch;
32
32
  const RETRY_DELAY = 150 // ms
33
33
  ;
34
- const RETRIES = 10 // 150, 300, 450, 600, 750, 900, 1050, 1200, 1350, 1500 = 8.250s
34
+ const MAX_RETRY_DELAY = 1500 // ms
35
35
  ;
36
+ const RETRIES = 10;
37
+ const getRetryDelay = (attemptNumber)=>{
38
+ const exponentialDelay = RETRY_DELAY * 2 ** (attemptNumber - 1);
39
+ return Math.min(exponentialDelay, MAX_RETRY_DELAY);
40
+ };
36
41
  /**
37
42
  * A wrapper around the native `fetch` function that adds automatic retries on failure, including network errors and HTTP 429 responses.
38
43
  * Now treats any response with status < 500 as valid.
39
44
  */ const fetchWithRetry = async (url, options = {
40
45
  retry: false
41
46
  })=>{
42
- const { retry, retriesRemaining, validateStatus = (status)=>status >= 200 && status < 300, ...fetchOptions } = options;
47
+ const { retry, retriesRemaining, initialRetries, validateStatus = (status)=>status >= 200 && status < 300, ...fetchOptions } = options;
43
48
  try {
44
49
  const response = await effectiveFetch(url, fetchOptions);
45
50
  const isResponseOk = validateStatus(response.status);
@@ -54,18 +59,27 @@ const RETRIES = 10 // 150, 300, 450, 600, 750, 900, 1050, 1200, 1350, 1500 = 8.2
54
59
  }
55
60
  // If validateStatus fails, and retry is enabled, prepare to retry for eligible status codes
56
61
  if (retry && (response.status >= 500 || response.status === 429)) {
57
- throw new Error('Retryable failure');
62
+ const error = new Error(`HTTP error with status ${response.status}`);
63
+ error.retryable = true;
64
+ throw error;
58
65
  }
59
- throw new Error(`HTTP error with status ${response.status}`);
66
+ const error = new Error(`HTTP error with status ${response.status}`);
67
+ error.retryable = false;
68
+ throw error;
60
69
  } catch (error) {
61
- if (retry) {
70
+ const retryableError = error;
71
+ const shouldRetry = retry && retryableError.retryable !== false;
72
+ if (shouldRetry) {
62
73
  const currentRetriesRemaining = retriesRemaining !== undefined ? retriesRemaining : RETRIES;
74
+ const totalRetries = initialRetries ?? currentRetriesRemaining;
63
75
  if (currentRetriesRemaining > 0) {
64
- const delayTime = RETRY_DELAY * (RETRIES - currentRetriesRemaining);
76
+ const attemptNumber = totalRetries - currentRetriesRemaining + 1;
77
+ const delayTime = getRetryDelay(attemptNumber);
65
78
  await new Promise((resolve)=>setTimeout(resolve, delayTime));
66
79
  return fetchWithRetry(url, {
67
80
  ...options,
68
- retriesRemaining: currentRetriesRemaining - 1
81
+ retriesRemaining: currentRetriesRemaining - 1,
82
+ initialRetries: totalRetries
69
83
  });
70
84
  }
71
85
  }
@@ -1,7 +1,7 @@
1
1
  Object.defineProperty(exports, '__esModule', { value: true });
2
2
 
3
3
  var name = "@quiltt/core";
4
- var version$1 = "5.1.2";
4
+ var version$1 = "5.2.0";
5
5
 
6
6
  const QUILTT_API_INSECURE = (()=>{
7
7
  try {
@@ -1,5 +1,5 @@
1
1
  var name = "@quiltt/core";
2
- var version$1 = "5.1.2";
2
+ var version$1 = "5.2.0";
3
3
 
4
4
  const QUILTT_API_INSECURE = (()=>{
5
5
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@quiltt/core",
3
- "version": "5.1.2",
3
+ "version": "5.2.0",
4
4
  "description": "Javascript API client and utilities for Quiltt",
5
5
  "keywords": [
6
6
  "quiltt",
@@ -88,16 +88,16 @@
88
88
  ],
89
89
  "main": "dist/index.js",
90
90
  "dependencies": {
91
- "@apollo/client": "^4.1.4",
91
+ "@apollo/client": "^4.1.6",
92
92
  "@rails/actioncable": "^8.1.200",
93
93
  "braces": "^3.0.3",
94
94
  "cross-fetch": "^4.1.0",
95
- "graphql": "^16.12.0",
95
+ "graphql": "^16.13.0",
96
96
  "rxjs": "^7.8.2"
97
97
  },
98
98
  "devDependencies": {
99
- "@biomejs/biome": "2.4.0",
100
- "@types/node": "24.10.13",
99
+ "@biomejs/biome": "2.4.5",
100
+ "@types/node": "24.11.0",
101
101
  "@types/rails__actioncable": "8.0.3",
102
102
  "@types/react": "19.2.14",
103
103
  "bunchee": "6.9.4",
@@ -125,7 +125,15 @@ export type ConnectorSDKCallbackMetadata = {
125
125
  export type ConnectorSDKConnectOptions = ConnectorSDKCallbacks & {
126
126
  /** The Institution ID or search term to connect */
127
127
  institution?: string
128
- /** The OAuth redirect URL for mobile or embedded webview flows */
128
+ /**
129
+ * The app launcher URL for mobile OAuth flows.
130
+ * This URL should be a Universal Link (iOS) or App Link (Android) that redirects back to your app.
131
+ */
132
+ appLauncherUrl?: string
133
+ /**
134
+ * @deprecated Use `appLauncherUrl` instead. This property will be removed in a future version.
135
+ * The OAuth redirect URL for mobile or embedded webview flows.
136
+ */
129
137
  oauthRedirectUrl?: string
130
138
  }
131
139
 
@@ -136,7 +144,15 @@ export type ConnectorSDKConnectOptions = ConnectorSDKCallbacks & {
136
144
  export type ConnectorSDKReconnectOptions = ConnectorSDKCallbacks & {
137
145
  /** The ID of the Connection to reconnect */
138
146
  connectionId: string
139
- /** The OAuth redirect URL for mobile or embedded webview flows */
147
+ /**
148
+ * The app launcher URL for mobile OAuth flows.
149
+ * This URL should be a Universal Link (iOS) or App Link (Android) that redirects back to your app.
150
+ */
151
+ appLauncherUrl?: string
152
+ /**
153
+ * @deprecated Use `appLauncherUrl` instead. This property will be removed in a future version.
154
+ * The OAuth redirect URL for mobile or embedded webview flows.
155
+ */
140
156
  oauthRedirectUrl?: string
141
157
  }
142
158
 
@@ -152,6 +168,14 @@ export type ConnectorSDKConnectorOptions = ConnectorSDKCallbacks & {
152
168
  connectionId?: string
153
169
  /** The nonce to use for the script tag */
154
170
  nonce?: string
155
- /** The OAuth redirect URL for mobile or embedded webview flows */
171
+ /**
172
+ * The app launcher URL for mobile OAuth flows.
173
+ * This URL should be a Universal Link (iOS) or App Link (Android) that redirects back to your app.
174
+ */
175
+ appLauncherUrl?: string
176
+ /**
177
+ * @deprecated Use `appLauncherUrl` instead. This property will be removed in a future version.
178
+ * The OAuth redirect URL for mobile or embedded webview flows.
179
+ */
156
180
  oauthRedirectUrl?: string
157
181
  }
@@ -16,7 +16,7 @@ type SubscriptionCallbacks = {
16
16
  received?: (payload: unknown) => void
17
17
  }
18
18
 
19
- class ActionCableLink extends ApolloLink {
19
+ export class ActionCableLink extends ApolloLink {
20
20
  cables: { [id: string]: Consumer }
21
21
  channelName: string
22
22
  actionName: string
@@ -119,5 +119,3 @@ class ActionCableLink extends ApolloLink {
119
119
  })
120
120
  }
121
121
  }
122
-
123
- export default ActionCableLink
@@ -37,5 +37,3 @@ export class AuthLink extends ApolloLink {
37
37
  return forward(operation)
38
38
  }
39
39
  }
40
-
41
- export default AuthLink
@@ -10,5 +10,3 @@ export const BatchHttpLink = new ApolloBatchHttpLink({
10
10
  uri: endpointGraphQL,
11
11
  fetch: effectiveFetch,
12
12
  })
13
-
14
- export default BatchHttpLink
@@ -38,5 +38,3 @@ export const ErrorLink = new ApolloErrorLink(({ error, result }) => {
38
38
  }
39
39
  }
40
40
  })
41
-
42
- export default ErrorLink
@@ -1,5 +1,3 @@
1
1
  import { ApolloLink } from '@apollo/client/core'
2
2
 
3
3
  export const ForwardableLink = new ApolloLink((operation, forward) => forward(operation))
4
-
5
- export default ForwardableLink
@@ -10,5 +10,3 @@ export const HttpLink = new ApolloHttpLink({
10
10
  uri: endpointGraphQL,
11
11
  fetch: effectiveFetch,
12
12
  })
13
-
14
- export default HttpLink
@@ -9,5 +9,3 @@ export const RetryLink = new ApolloRetryLink({
9
9
  },
10
10
  },
11
11
  })
12
-
13
- export default RetryLink
@@ -1,11 +1,9 @@
1
1
  'use client'
2
2
 
3
- import ActionCableLink from './ActionCableLink'
3
+ import { ActionCableLink } from './ActionCableLink'
4
4
 
5
5
  export class SubscriptionLink extends ActionCableLink {
6
6
  constructor() {
7
7
  super({ channelName: 'GraphQLChannel' })
8
8
  }
9
9
  }
10
-
11
- export default SubscriptionLink
@@ -6,5 +6,3 @@ export const TerminatingLink = new ApolloLink(() => {
6
6
  observer.complete()
7
7
  })
8
8
  })
9
-
10
- export default TerminatingLink
@@ -6,6 +6,7 @@ const effectiveFetch = typeof fetch === 'undefined' ? crossfetch : fetch
6
6
  type FetchWithRetryOptions = RequestInit & {
7
7
  retry?: boolean
8
8
  retriesRemaining?: number
9
+ initialRetries?: number
9
10
  validateStatus?: (status: number) => boolean
10
11
  }
11
12
 
@@ -18,7 +19,15 @@ export type FetchResponse<T> = {
18
19
  }
19
20
 
20
21
  const RETRY_DELAY = 150 // ms
21
- const RETRIES = 10 // 150, 300, 450, 600, 750, 900, 1050, 1200, 1350, 1500 = 8.250s
22
+ const MAX_RETRY_DELAY = 1500 // ms
23
+ const RETRIES = 10
24
+
25
+ const getRetryDelay = (attemptNumber: number): number => {
26
+ const exponentialDelay = RETRY_DELAY * 2 ** (attemptNumber - 1)
27
+ return Math.min(exponentialDelay, MAX_RETRY_DELAY)
28
+ }
29
+
30
+ type RetryableError = Error & { retryable?: boolean }
22
31
 
23
32
  /**
24
33
  * A wrapper around the native `fetch` function that adds automatic retries on failure, including network errors and HTTP 429 responses.
@@ -31,6 +40,7 @@ export const fetchWithRetry = async <T>(
31
40
  const {
32
41
  retry,
33
42
  retriesRemaining,
43
+ initialRetries,
34
44
  validateStatus = (status) => status >= 200 && status < 300, // Default to success for 2xx responses
35
45
  ...fetchOptions
36
46
  } = options
@@ -52,19 +62,29 @@ export const fetchWithRetry = async <T>(
52
62
 
53
63
  // If validateStatus fails, and retry is enabled, prepare to retry for eligible status codes
54
64
  if (retry && (response.status >= 500 || response.status === 429)) {
55
- throw new Error('Retryable failure')
65
+ const error = new Error(`HTTP error with status ${response.status}`) as RetryableError
66
+ error.retryable = true
67
+ throw error
56
68
  }
57
69
 
58
- throw new Error(`HTTP error with status ${response.status}`)
70
+ const error = new Error(`HTTP error with status ${response.status}`) as RetryableError
71
+ error.retryable = false
72
+ throw error
59
73
  } catch (error) {
60
- if (retry) {
74
+ const retryableError = error as RetryableError
75
+ const shouldRetry = retry && retryableError.retryable !== false
76
+
77
+ if (shouldRetry) {
61
78
  const currentRetriesRemaining = retriesRemaining !== undefined ? retriesRemaining : RETRIES
79
+ const totalRetries = initialRetries ?? currentRetriesRemaining
62
80
  if (currentRetriesRemaining > 0) {
63
- const delayTime = RETRY_DELAY * (RETRIES - currentRetriesRemaining)
81
+ const attemptNumber = totalRetries - currentRetriesRemaining + 1
82
+ const delayTime = getRetryDelay(attemptNumber)
64
83
  await new Promise((resolve) => setTimeout(resolve, delayTime))
65
84
  return fetchWithRetry(url, {
66
85
  ...options,
67
86
  retriesRemaining: currentRetriesRemaining - 1,
87
+ initialRetries: totalRetries,
68
88
  })
69
89
  }
70
90
  }
@@ -38,5 +38,3 @@ export class Observable<T> {
38
38
  this.observers = this.observers.filter((update) => update !== observer)
39
39
  }
40
40
  }
41
-
42
- export default Observable
@@ -29,5 +29,3 @@ export class Timeoutable {
29
29
  this.observers[0](undefined)
30
30
  }
31
31
  }
32
-
33
- export default Timeoutable