@quiltt/react-native 3.9.6 → 4.0.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,27 @@
1
1
  # @quiltt/react-native
2
2
 
3
+ ## 4.0.0
4
+
5
+ ### Major Changes
6
+
7
+ - [#333](https://github.com/quiltt/quiltt-js/pull/333) [`5afca4a`](https://github.com/quiltt/quiltt-js/commit/5afca4a45f357afbb7f6af02088f86230f351e18) Thanks [@zubairaziz](https://github.com/zubairaziz)! - Migrate to 'Navigate' message for URL handling
8
+
9
+ ### Patch Changes
10
+
11
+ - Updated dependencies [[`5afca4a`](https://github.com/quiltt/quiltt-js/commit/5afca4a45f357afbb7f6af02088f86230f351e18)]:
12
+ - @quiltt/core@4.0.0
13
+ - @quiltt/react@4.0.0
14
+
15
+ ## 3.9.7
16
+
17
+ ### Patch Changes
18
+
19
+ - [#330](https://github.com/quiltt/quiltt-js/pull/330) [`e7b8e74`](https://github.com/quiltt/quiltt-js/commit/e7b8e74613f7725c6f2653be6d8ac0e06cce661d) Thanks [@zubairaziz](https://github.com/zubairaziz)! - Make OAuth Handling Safer
20
+
21
+ - Updated dependencies [[`e7b8e74`](https://github.com/quiltt/quiltt-js/commit/e7b8e74613f7725c6f2653be6d8ac0e06cce661d)]:
22
+ - @quiltt/core@3.9.7
23
+ - @quiltt/react@3.9.7
24
+
3
25
  ## 3.9.6
4
26
 
5
27
  ### Patch Changes
package/dist/index.js CHANGED
@@ -9,7 +9,7 @@ import { URL } from 'react-native-url-polyfill';
9
9
  import { WebView } from 'react-native-webview';
10
10
  import { generateStackTrace, makeBacktrace, getCauses } from '@honeybadger-io/core/build/src/util';
11
11
 
12
- var version = "3.9.6";
12
+ var version = "4.0.0";
13
13
 
14
14
  const ErrorReporterConfig = {
15
15
  honeybadger_api_key: 'undefined'
@@ -103,7 +103,7 @@ const getErrorMessage = (responseStatus, error)=>{
103
103
  * Checks if a string appears to be already URL encoded
104
104
  * @param str The string to check
105
105
  * @returns boolean indicating if the string appears to be URL encoded
106
- */ const isAlreadyEncoded = (str)=>{
106
+ */ const isEncoded = (str)=>{
107
107
  // Check for typical URL encoding patterns like %20, %3A, etc.
108
108
  const hasEncodedChars = /%[0-9A-F]{2}/i.test(str);
109
109
  // Check if double encoding has occurred (e.g., %253A instead of %3A)
@@ -118,7 +118,7 @@ const getErrorMessage = (responseStatus, error)=>{
118
118
  */ const smartEncodeURIComponent = (str)=>{
119
119
  if (!str) return str;
120
120
  // If it's already encoded, return as is
121
- if (isAlreadyEncoded(str)) {
121
+ if (isEncoded(str)) {
122
122
  console.log('URL already encoded, skipping encoding:', str);
123
123
  return str;
124
124
  }
@@ -267,19 +267,18 @@ const checkConnectorUrl = async (connectorUrl, retryCount = 0)=>{
267
267
  const response = await fetch(connectorUrl);
268
268
  if (!response.ok) {
269
269
  responseStatus = response.status;
270
- throw new Error(`The URL ${connectorUrl} is not routable.`);
270
+ throw new Error('Connector URL is not routable.');
271
271
  }
272
- console.log(`The URL ${connectorUrl} is routable.`);
273
272
  return {
274
273
  checked: true
275
274
  };
276
275
  } catch (e) {
277
276
  error = e;
278
- console.error(`An error occurred while checking the connector URL: ${error}`);
277
+ console.error('Failed to connect to connector URL');
279
278
  if (retryCount < PREFLIGHT_RETRY_COUNT) {
280
279
  const delay = 50 * 2 ** retryCount;
281
280
  await new Promise((resolve)=>setTimeout(resolve, delay));
282
- console.log(`Retrying... Attempt number ${retryCount + 1}`);
281
+ console.log(`Retrying connection... Attempt ${retryCount + 1}`);
283
282
  return checkConnectorUrl(connectorUrl, retryCount + 1);
284
283
  }
285
284
  const errorMessage = getErrorMessage(responseStatus, error);
@@ -301,30 +300,28 @@ const checkConnectorUrl = async (connectorUrl, retryCount = 0)=>{
301
300
  try {
302
301
  // Throw error if oauthUrl is null or undefined
303
302
  if (oauthUrl == null) {
304
- throw new Error('handleOAuthUrl - Received null or undefined URL');
303
+ throw new Error('OAuth URL missing');
305
304
  }
306
305
  // Convert to string if it's a URL object
307
306
  const urlString = oauthUrl.toString();
308
307
  // Throw error if the resulting string is empty
309
308
  if (!urlString || urlString.trim() === '') {
310
- throw new Error('handleOAuthUrl - Received empty URL string');
309
+ throw new Error('Empty OAuth URL');
311
310
  }
312
311
  // Normalize the URL encoding
313
312
  const normalizedUrl = normalizeUrlEncoding(urlString);
314
- // Log the URL we're about to open
315
- console.log(`handleOAuthUrl - Opening URL - ${normalizedUrl}`);
316
313
  // Open the normalized URL
317
314
  Linking.openURL(normalizedUrl);
318
315
  } catch (error) {
319
- console.error('Error handling OAuth URL:', error);
316
+ console.error('OAuth URL handling error');
320
317
  // Only try the fallback if oauthUrl is not null
321
318
  if (oauthUrl != null) {
322
319
  try {
323
320
  const fallbackUrl = typeof oauthUrl === 'string' ? oauthUrl : oauthUrl.toString();
324
- console.log(`handleOAuthUrl - Fallback opening URL - ${fallbackUrl}`);
321
+ console.log('Attempting fallback OAuth opening');
325
322
  Linking.openURL(fallbackUrl);
326
323
  } catch (fallbackError) {
327
- console.error('Failed even with fallback approach:', fallbackError);
324
+ console.error('Fallback OAuth opening failed');
328
325
  }
329
326
  }
330
327
  }
@@ -355,7 +352,6 @@ const QuilttConnector = ({ testId, connectorId, connectionId, institution, oauth
355
352
  }, []);
356
353
  // Ensure oauthRedirectUrl is encoded properly - only once
357
354
  const safeOAuthRedirectUrl = useMemo(()=>{
358
- console.log('Original oauthRedirectUrl:', oauthRedirectUrl);
359
355
  return smartEncodeURIComponent(oauthRedirectUrl);
360
356
  }, [
361
357
  oauthRedirectUrl
@@ -368,17 +364,13 @@ const QuilttConnector = ({ testId, connectorId, connectionId, institution, oauth
368
364
  // For the oauth_redirect_url, we need to be careful
369
365
  // If it's already encoded, we need to decode it once to prevent
370
366
  // the automatic encoding that happens with searchParams.append
371
- if (isAlreadyEncoded(safeOAuthRedirectUrl)) {
367
+ if (isEncoded(safeOAuthRedirectUrl)) {
372
368
  const decodedOnce = decodeURIComponent(safeOAuthRedirectUrl);
373
369
  url.searchParams.append('oauth_redirect_url', decodedOnce);
374
- console.log('Using decoded oauth_redirect_url:', decodedOnce);
375
370
  } else {
376
371
  url.searchParams.append('oauth_redirect_url', safeOAuthRedirectUrl);
377
- console.log('Using original oauth_redirect_url:', safeOAuthRedirectUrl);
378
372
  }
379
- const finalUrl = url.toString();
380
- console.log('Final connectorUrl:', finalUrl);
381
- return finalUrl;
373
+ return url.toString();
382
374
  }, [
383
375
  connectorId,
384
376
  safeOAuthRedirectUrl
@@ -435,50 +427,59 @@ const QuilttConnector = ({ testId, connectorId, connectionId, institution, oauth
435
427
  const eventType = url.host;
436
428
  switch(eventType){
437
429
  case 'Load':
430
+ console.log('Event: Load');
438
431
  initInjectedJavaScript();
439
432
  onEvent?.(ConnectorSDKEventType.Load, metadata);
440
433
  onLoad?.(metadata);
441
434
  break;
442
435
  case 'ExitAbort':
436
+ console.log('Event: ExitAbort');
443
437
  clearLocalStorage();
444
438
  onEvent?.(ConnectorSDKEventType.ExitAbort, metadata);
445
439
  onExit?.(ConnectorSDKEventType.ExitAbort, metadata);
446
440
  onExitAbort?.(metadata);
447
441
  break;
448
442
  case 'ExitError':
443
+ console.log('Event: ExitError');
449
444
  clearLocalStorage();
450
445
  onEvent?.(ConnectorSDKEventType.ExitError, metadata);
451
446
  onExit?.(ConnectorSDKEventType.ExitError, metadata);
452
447
  onExitError?.(metadata);
453
448
  break;
454
449
  case 'ExitSuccess':
450
+ console.log('Event: ExitSuccess');
455
451
  clearLocalStorage();
456
452
  onEvent?.(ConnectorSDKEventType.ExitSuccess, metadata);
457
453
  onExit?.(ConnectorSDKEventType.ExitSuccess, metadata);
458
454
  onExitSuccess?.(metadata);
459
455
  break;
460
456
  case 'Authenticate':
457
+ console.log('Event: Authenticate');
461
458
  break;
462
- case 'OauthRequested':
459
+ case 'Navigate':
463
460
  {
464
- // Log available search parameters
465
- console.log('Available search params:', Array.from(url.searchParams.keys()));
466
- // Now we should be getting the oauthUrl parameter directly
467
- const oauthUrl = url.searchParams.get('oauthUrl');
468
- console.log('Received oauthUrl:', oauthUrl);
469
- // Check if oauthUrl exists before proceeding
470
- if (oauthUrl) {
471
- // Create a new URL from the normalized oauthUrl
472
- handleOAuthUrl(oauthUrl);
461
+ console.log('Event: Navigate');
462
+ const navigateUrl = url.searchParams.get('url');
463
+ if (navigateUrl) {
464
+ if (isEncoded(navigateUrl)) {
465
+ try {
466
+ const decodedUrl = decodeURIComponent(navigateUrl);
467
+ handleOAuthUrl(decodedUrl);
468
+ } catch (error) {
469
+ console.error('Navigate URL decoding failed, using original');
470
+ handleOAuthUrl(navigateUrl);
471
+ }
472
+ } else {
473
+ handleOAuthUrl(navigateUrl);
474
+ }
473
475
  } else {
474
- // Log an error if oauthUrl is missing
475
- console.error('OauthRequested event missing oauthUrl parameter');
476
- console.log('All available params:', Object.fromEntries(url.searchParams.entries()));
476
+ console.error('Navigate URL missing from request');
477
477
  }
478
478
  break;
479
479
  }
480
+ // NOTE: The `OauthRequested` is deprecated and should not be used
480
481
  default:
481
- console.log('unhandled event', url);
482
+ console.log(`Unhandled event: ${eventType}`);
482
483
  break;
483
484
  }
484
485
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@quiltt/react-native",
3
- "version": "3.9.6",
3
+ "version": "4.0.0",
4
4
  "description": "React Native Components for Quiltt Connector",
5
5
  "homepage": "https://github.com/quiltt/quiltt-js/tree/main/packages/react-native#readme",
6
6
  "repository": {
@@ -30,8 +30,8 @@
30
30
  "dependencies": {
31
31
  "@honeybadger-io/core": "6.6.0",
32
32
  "lodash.debounce": "4.0.8",
33
- "@quiltt/react": "3.9.6",
34
- "@quiltt/core": "3.9.6"
33
+ "@quiltt/react": "4.0.0",
34
+ "@quiltt/core": "4.0.0"
35
35
  },
36
36
  "devDependencies": {
37
37
  "@biomejs/biome": "1.9.4",
@@ -11,7 +11,7 @@ import type { ConnectorSDKCallbackMetadata, ConnectorSDKCallbacks } from '@quilt
11
11
  import {
12
12
  ErrorReporter,
13
13
  getErrorMessage,
14
- isAlreadyEncoded,
14
+ isEncoded,
15
15
  normalizeUrlEncoding,
16
16
  smartEncodeURIComponent,
17
17
  } from '@/utils'
@@ -38,18 +38,17 @@ export const checkConnectorUrl = async (
38
38
  const response = await fetch(connectorUrl)
39
39
  if (!response.ok) {
40
40
  responseStatus = response.status
41
- throw new Error(`The URL ${connectorUrl} is not routable.`)
41
+ throw new Error('Connector URL is not routable.')
42
42
  }
43
- console.log(`The URL ${connectorUrl} is routable.`)
44
43
  return { checked: true }
45
44
  } catch (e) {
46
45
  error = e as Error
47
- console.error(`An error occurred while checking the connector URL: ${error}`)
46
+ console.error('Failed to connect to connector URL')
48
47
 
49
48
  if (retryCount < PREFLIGHT_RETRY_COUNT) {
50
49
  const delay = 50 * 2 ** retryCount
51
50
  await new Promise((resolve) => setTimeout(resolve, delay))
52
- console.log(`Retrying... Attempt number ${retryCount + 1}`)
51
+ console.log(`Retrying connection... Attempt ${retryCount + 1}`)
53
52
  return checkConnectorUrl(connectorUrl, retryCount + 1)
54
53
  }
55
54
  const errorMessage = getErrorMessage(responseStatus, error as Error)
@@ -67,7 +66,7 @@ export const handleOAuthUrl = (oauthUrl: URL | string | null | undefined) => {
67
66
  try {
68
67
  // Throw error if oauthUrl is null or undefined
69
68
  if (oauthUrl == null) {
70
- throw new Error('handleOAuthUrl - Received null or undefined URL')
69
+ throw new Error('OAuth URL missing')
71
70
  }
72
71
 
73
72
  // Convert to string if it's a URL object
@@ -75,28 +74,25 @@ export const handleOAuthUrl = (oauthUrl: URL | string | null | undefined) => {
75
74
 
76
75
  // Throw error if the resulting string is empty
77
76
  if (!urlString || urlString.trim() === '') {
78
- throw new Error('handleOAuthUrl - Received empty URL string')
77
+ throw new Error('Empty OAuth URL')
79
78
  }
80
79
 
81
80
  // Normalize the URL encoding
82
81
  const normalizedUrl = normalizeUrlEncoding(urlString)
83
82
 
84
- // Log the URL we're about to open
85
- console.log(`handleOAuthUrl - Opening URL - ${normalizedUrl}`)
86
-
87
83
  // Open the normalized URL
88
84
  Linking.openURL(normalizedUrl)
89
85
  } catch (error) {
90
- console.error('Error handling OAuth URL:', error)
86
+ console.error('OAuth URL handling error')
91
87
 
92
88
  // Only try the fallback if oauthUrl is not null
93
89
  if (oauthUrl != null) {
94
90
  try {
95
91
  const fallbackUrl = typeof oauthUrl === 'string' ? oauthUrl : oauthUrl.toString()
96
- console.log(`handleOAuthUrl - Fallback opening URL - ${fallbackUrl}`)
92
+ console.log('Attempting fallback OAuth opening')
97
93
  Linking.openURL(fallbackUrl)
98
94
  } catch (fallbackError) {
99
- console.error('Failed even with fallback approach:', fallbackError)
95
+ console.error('Fallback OAuth opening failed')
100
96
  }
101
97
  }
102
98
  }
@@ -149,7 +145,6 @@ const QuilttConnector = ({
149
145
 
150
146
  // Ensure oauthRedirectUrl is encoded properly - only once
151
147
  const safeOAuthRedirectUrl = useMemo(() => {
152
- console.log('Original oauthRedirectUrl:', oauthRedirectUrl)
153
148
  return smartEncodeURIComponent(oauthRedirectUrl)
154
149
  }, [oauthRedirectUrl])
155
150
 
@@ -163,18 +158,14 @@ const QuilttConnector = ({
163
158
  // For the oauth_redirect_url, we need to be careful
164
159
  // If it's already encoded, we need to decode it once to prevent
165
160
  // the automatic encoding that happens with searchParams.append
166
- if (isAlreadyEncoded(safeOAuthRedirectUrl)) {
161
+ if (isEncoded(safeOAuthRedirectUrl)) {
167
162
  const decodedOnce = decodeURIComponent(safeOAuthRedirectUrl)
168
163
  url.searchParams.append('oauth_redirect_url', decodedOnce)
169
- console.log('Using decoded oauth_redirect_url:', decodedOnce)
170
164
  } else {
171
165
  url.searchParams.append('oauth_redirect_url', safeOAuthRedirectUrl)
172
- console.log('Using original oauth_redirect_url:', safeOAuthRedirectUrl)
173
166
  }
174
167
 
175
- const finalUrl = url.toString()
176
- console.log('Final connectorUrl:', finalUrl)
177
- return finalUrl
168
+ return url.toString()
178
169
  }, [connectorId, safeOAuthRedirectUrl])
179
170
 
180
171
  useEffect(() => {
@@ -226,52 +217,60 @@ const QuilttConnector = ({
226
217
  const eventType = url.host
227
218
  switch (eventType) {
228
219
  case 'Load':
220
+ console.log('Event: Load')
229
221
  initInjectedJavaScript()
230
222
  onEvent?.(ConnectorSDKEventType.Load, metadata)
231
223
  onLoad?.(metadata)
232
224
  break
233
225
  case 'ExitAbort':
226
+ console.log('Event: ExitAbort')
234
227
  clearLocalStorage()
235
228
  onEvent?.(ConnectorSDKEventType.ExitAbort, metadata)
236
229
  onExit?.(ConnectorSDKEventType.ExitAbort, metadata)
237
230
  onExitAbort?.(metadata)
238
231
  break
239
232
  case 'ExitError':
233
+ console.log('Event: ExitError')
240
234
  clearLocalStorage()
241
235
  onEvent?.(ConnectorSDKEventType.ExitError, metadata)
242
236
  onExit?.(ConnectorSDKEventType.ExitError, metadata)
243
237
  onExitError?.(metadata)
244
238
  break
245
239
  case 'ExitSuccess':
240
+ console.log('Event: ExitSuccess')
246
241
  clearLocalStorage()
247
242
  onEvent?.(ConnectorSDKEventType.ExitSuccess, metadata)
248
243
  onExit?.(ConnectorSDKEventType.ExitSuccess, metadata)
249
244
  onExitSuccess?.(metadata)
250
245
  break
251
246
  case 'Authenticate':
247
+ console.log('Event: Authenticate')
252
248
  // TODO: handle Authenticate
253
249
  break
254
- case 'OauthRequested': {
255
- // Log available search parameters
256
- console.log('Available search params:', Array.from(url.searchParams.keys()))
257
-
258
- // Now we should be getting the oauthUrl parameter directly
259
- const oauthUrl = url.searchParams.get('oauthUrl')
260
- console.log('Received oauthUrl:', oauthUrl)
261
-
262
- // Check if oauthUrl exists before proceeding
263
- if (oauthUrl) {
264
- // Create a new URL from the normalized oauthUrl
265
- handleOAuthUrl(oauthUrl)
250
+ case 'Navigate': {
251
+ console.log('Event: Navigate')
252
+ const navigateUrl = url.searchParams.get('url')
253
+
254
+ if (navigateUrl) {
255
+ if (isEncoded(navigateUrl)) {
256
+ try {
257
+ const decodedUrl = decodeURIComponent(navigateUrl)
258
+ handleOAuthUrl(decodedUrl)
259
+ } catch (error) {
260
+ console.error('Navigate URL decoding failed, using original')
261
+ handleOAuthUrl(navigateUrl)
262
+ }
263
+ } else {
264
+ handleOAuthUrl(navigateUrl)
265
+ }
266
266
  } else {
267
- // Log an error if oauthUrl is missing
268
- console.error('OauthRequested event missing oauthUrl parameter')
269
- console.log('All available params:', Object.fromEntries(url.searchParams.entries()))
267
+ console.error('Navigate URL missing from request')
270
268
  }
271
269
  break
272
270
  }
271
+ // NOTE: The `OauthRequested` is deprecated and should not be used
273
272
  default:
274
- console.log('unhandled event', url)
273
+ console.log(`Unhandled event: ${eventType}`)
275
274
  break
276
275
  }
277
276
  })
package/src/utils/url.ts CHANGED
@@ -3,7 +3,7 @@
3
3
  * @param str The string to check
4
4
  * @returns boolean indicating if the string appears to be URL encoded
5
5
  */
6
- export const isAlreadyEncoded = (str: string): boolean => {
6
+ export const isEncoded = (str: string): boolean => {
7
7
  // Check for typical URL encoding patterns like %20, %3A, etc.
8
8
  const hasEncodedChars = /%[0-9A-F]{2}/i.test(str)
9
9
 
@@ -23,7 +23,7 @@ export const smartEncodeURIComponent = (str: string): string => {
23
23
  if (!str) return str
24
24
 
25
25
  // If it's already encoded, return as is
26
- if (isAlreadyEncoded(str)) {
26
+ if (isEncoded(str)) {
27
27
  console.log('URL already encoded, skipping encoding:', str)
28
28
  return str
29
29
  }
@@ -51,7 +51,7 @@ export const createUrlWithParams = (baseUrl: string, params: Record<string, stri
51
51
  if (value == null) return
52
52
 
53
53
  // For oauth_redirect_url specifically, ensure it's not double encoded
54
- if (key === 'oauth_redirect_url' && isAlreadyEncoded(value)) {
54
+ if (key === 'oauth_redirect_url' && isEncoded(value)) {
55
55
  // Decode once to counteract the automatic encoding that will happen
56
56
  const decodedOnce = decodeURIComponent(value)
57
57
  url.searchParams.append(key, decodedOnce)