@quiltt/react-native 3.6.8 → 3.6.10

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,25 @@
1
1
  # @quiltt/react-native
2
2
 
3
+ ## 3.6.10
4
+
5
+ ### Patch Changes
6
+
7
+ - [#268](https://github.com/quiltt/quiltt-js/pull/268) [`8a82094`](https://github.com/quiltt/quiltt-js/commit/8a82094a709d0d7e1478ec32142be33825323708) Thanks [@zubairaziz](https://github.com/zubairaziz)! - Migrate linter to Biome
8
+
9
+ - Updated dependencies [[`8a82094`](https://github.com/quiltt/quiltt-js/commit/8a82094a709d0d7e1478ec32142be33825323708)]:
10
+ - @quiltt/react@3.6.10
11
+ - @quiltt/core@3.6.10
12
+
13
+ ## 3.6.9
14
+
15
+ ### Patch Changes
16
+
17
+ - [#260](https://github.com/quiltt/quiltt-js/pull/260) [`6e80930`](https://github.com/quiltt/quiltt-js/commit/6e80930f84013f483e2c75fcb37a28dc4996dadc) Thanks [@zubairaziz](https://github.com/zubairaziz)! - Refactor QuilttConnector to remove URL allow list
18
+
19
+ - Updated dependencies [[`6e80930`](https://github.com/quiltt/quiltt-js/commit/6e80930f84013f483e2c75fcb37a28dc4996dadc)]:
20
+ - @quiltt/core@3.6.9
21
+ - @quiltt/react@3.6.9
22
+
3
23
  ## 3.6.8
4
24
 
5
25
  ### Patch Changes
package/README.md CHANGED
@@ -27,11 +27,11 @@ Launch the [Quiltt Connector](https://www.quiltt.dev/connector) in a webview.
27
27
 
28
28
  `@quiltt/react-native` does not include any navigation library, you might want to navigate to a new "page" when using QuilttConnector to get the best result.
29
29
 
30
- For simple usage of `react-navigation`, please see [App.tsx](examples/expo/App.tsx) and [ConnectorScreen.tsx](examples/expo/screens/ConnectorScreen.tsx).
30
+ For simple usage of `react-navigation`, please see [App.tsx](../../examples/react-native-expo/App.tsx) and [ConnectorScreen.tsx](../../examples/react-native-expo/screens/ConnectorScreen.tsx).
31
31
 
32
32
  #### Example
33
33
 
34
- ```typescript
34
+ ```tsx
35
35
  import { useState } from 'react'
36
36
  import { QuilttProvider } from '@quiltt/react'
37
37
  import { QuilttConnector } from '@quiltt/react-native'
package/dist/index.cjs CHANGED
@@ -5,13 +5,101 @@ var core = require('@quiltt/core');
5
5
  var react = require('@quiltt/react');
6
6
  var jsxRuntime = require('react/jsx-runtime');
7
7
  var react$1 = require('react');
8
+ var reactNative = require('react-native');
8
9
  var reactNativeUrlPolyfill = require('react-native-url-polyfill');
9
10
  var reactNativeWebview = require('react-native-webview');
10
- var reactNative = require('react-native');
11
11
  var util = require('@honeybadger-io/core/build/src/util');
12
12
 
13
13
  // Generated by genversion.
14
- const version = '3.6.8';
14
+ const version = '3.6.10';
15
+
16
+ const ErrorReporterConfig = {
17
+ honeybadger_api_key: 'undefined'
18
+ };
19
+
20
+ // Quick hack to send error to Honeybadger to debug why the connector is not routable
21
+ const notifier = {
22
+ name: 'Quiltt React Native SDK Reporter',
23
+ url: 'https://www.quiltt.dev/guides/connector/react-native',
24
+ version: version
25
+ };
26
+ class ErrorReporter {
27
+ constructor(platform){
28
+ this.noticeUrl = 'https://api.honeybadger.io/v1/notices';
29
+ this.apiKey = ErrorReporterConfig.honeybadger_api_key;
30
+ this.clientName = 'react-native-sdk';
31
+ this.clientVersion = version;
32
+ this.platform = platform;
33
+ this.logger = console;
34
+ this.userAgent = `${this.clientName} ${this.clientVersion}; ${this.platform}`;
35
+ }
36
+ async send(error, context) {
37
+ const headers = {
38
+ 'X-API-Key': this.apiKey,
39
+ 'Content-Type': 'application/json',
40
+ Accept: 'application/json',
41
+ 'User-Agent': `${this.clientName} ${this.clientVersion}; ${this.platform}`
42
+ };
43
+ const payload = await this.buildPayload(error, context);
44
+ const method = 'POST';
45
+ const body = JSON.stringify(payload);
46
+ const mode = 'cors';
47
+ fetch(this.noticeUrl, {
48
+ headers,
49
+ method,
50
+ body,
51
+ mode
52
+ }).then((response)=>{
53
+ if (response.status !== 201) {
54
+ this.logger.warn(`Error report failed: unknown response from server. code=${response.status}`);
55
+ return;
56
+ }
57
+ return response.json();
58
+ }).then((data)=>{
59
+ if (data) {
60
+ this.logger.info(`Error report sent ⚡ https://app.honeybadger.io/notice/${data?.id}`);
61
+ }
62
+ });
63
+ }
64
+ async buildPayload(error, localContext = {}) {
65
+ const notice = error;
66
+ notice.stack = util.generateStackTrace();
67
+ notice.backtrace = util.makeBacktrace(notice.stack);
68
+ return {
69
+ notifier,
70
+ error: {
71
+ class: notice.name,
72
+ message: notice.message,
73
+ backtrace: notice.backtrace,
74
+ // fingerprint: this.calculateFingerprint(notice),
75
+ tags: notice.tags || [],
76
+ causes: util.getCauses(notice, this.logger)
77
+ },
78
+ request: {
79
+ url: notice.url,
80
+ component: notice.component,
81
+ action: notice.action,
82
+ context: localContext || {},
83
+ cgi_data: {},
84
+ params: {},
85
+ session: {}
86
+ },
87
+ server: {
88
+ project_root: notice.projectRoot,
89
+ environment_name: this.userAgent,
90
+ revision: version,
91
+ hostname: this.platform,
92
+ time: new Date().toUTCString()
93
+ },
94
+ details: notice.details || {}
95
+ };
96
+ }
97
+ }
98
+
99
+ const getErrorMessage = (responseStatus, error)=>{
100
+ if (error) return `An error occurred while checking the connector URL: ${error?.name} \n${error?.message}`;
101
+ return responseStatus ? `The URL is not routable. Response status: ${responseStatus}` : 'An error occurred while checking the connector URL';
102
+ };
15
103
 
16
104
  const AndroidSafeAreaView = ({ testId, children })=>/*#__PURE__*/ jsxRuntime.jsx(reactNative.SafeAreaView, {
17
105
  testID: testId,
@@ -126,144 +214,47 @@ const LoadingScreen = ({ testId })=>/*#__PURE__*/ jsxRuntime.jsx(AndroidSafeArea
126
214
  })
127
215
  });
128
216
 
129
- const ErrorReporterConfig = {
130
- honeybadger_api_key: 'undefined'
131
- };
132
-
133
- // Quick hack to send error to Honeybadger to debug why the connector is not routable
134
- const notifier = {
135
- name: 'Quiltt React Native SDK Reporter',
136
- url: 'https://www.quiltt.dev/guides/connector/react-native',
137
- version: version
138
- };
139
- class ErrorReporter {
140
- constructor(platform){
141
- this.noticeUrl = 'https://api.honeybadger.io/v1/notices';
142
- this.apiKey = ErrorReporterConfig.honeybadger_api_key;
143
- this.clientName = 'react-native-sdk';
144
- this.clientVersion = version;
145
- this.platform = platform;
146
- this.logger = console;
147
- this.userAgent = `${this.clientName} ${this.clientVersion}; ${this.platform}`;
148
- }
149
- async send(error, context) {
150
- const headers = {
151
- 'X-API-Key': this.apiKey,
152
- 'Content-Type': 'application/json',
153
- Accept: 'application/json',
154
- 'User-Agent': `${this.clientName} ${this.clientVersion}; ${this.platform}`
155
- };
156
- const payload = await this.buildPayload(error, context);
157
- const method = 'POST';
158
- const body = JSON.stringify(payload);
159
- const mode = 'cors';
160
- fetch(this.noticeUrl, {
161
- headers,
162
- method,
163
- body,
164
- mode
165
- }).then((response)=>{
166
- if (response.status !== 201) {
167
- this.logger.warn(`Error report failed: unknown response from server. code=${response.status}`);
168
- return;
169
- }
170
- return response.json();
171
- }).then((data)=>{
172
- if (data) {
173
- this.logger.info(`Error report sent ⚡ https://app.honeybadger.io/notice/${data?.id}`);
174
- }
175
- });
176
- }
177
- async buildPayload(error, localContext = {}) {
178
- const notice = error;
179
- notice.stack = util.generateStackTrace();
180
- notice.backtrace = util.makeBacktrace(notice.stack);
181
- return {
182
- notifier,
183
- error: {
184
- class: notice.name,
185
- message: notice.message,
186
- backtrace: notice.backtrace,
187
- // fingerprint: this.calculateFingerprint(notice),
188
- tags: notice.tags || [],
189
- causes: util.getCauses(notice, this.logger)
190
- },
191
- request: {
192
- url: notice.url,
193
- component: notice.component,
194
- action: notice.action,
195
- context: localContext || {},
196
- cgi_data: {},
197
- params: {},
198
- session: {}
199
- },
200
- server: {
201
- project_root: notice.projectRoot,
202
- environment_name: this.userAgent,
203
- revision: version,
204
- hostname: this.platform,
205
- time: new Date().toUTCString()
206
- },
207
- details: notice.details || {}
208
- };
209
- }
210
- }
211
-
212
- const getErrorMessage = (responseStatus, error)=>{
213
- if (error) return `An error occurred while checking the connector URL: ${error?.name} \n${error?.message}`;
214
- return responseStatus ? `The URL is not routable. Response status: ${responseStatus}` : 'An error occurred while checking the connector URL';
215
- };
216
-
217
217
  const errorReporter = new ErrorReporter(`${reactNative.Platform.OS} ${reactNative.Platform.Version}`);
218
218
  const PREFLIGHT_RETRY_COUNT = 3;
219
219
  const checkConnectorUrl = async (connectorUrl, retryCount = 0)=>{
220
220
  let responseStatus;
221
221
  let error;
222
- let errorOccurred = false;
223
222
  try {
224
223
  const response = await fetch(connectorUrl);
225
224
  if (!response.ok) {
226
- console.error(`The URL ${connectorUrl} is not routable.`);
227
225
  responseStatus = response.status;
228
- errorOccurred = true;
229
- } else {
230
- console.log(`The URL ${connectorUrl} is routable.`);
231
- return {
232
- checked: true
233
- };
226
+ throw new Error(`The URL ${connectorUrl} is not routable.`);
234
227
  }
228
+ console.log(`The URL ${connectorUrl} is routable.`);
229
+ return {
230
+ checked: true
231
+ };
235
232
  } catch (e) {
236
233
  error = e;
237
234
  console.error(`An error occurred while checking the connector URL: ${error}`);
238
- errorOccurred = true;
239
- }
240
- if (errorOccurred && retryCount < PREFLIGHT_RETRY_COUNT) {
241
- const delay = 50 * Math.pow(2, retryCount);
242
- await new Promise((resolve)=>setTimeout(resolve, delay));
243
- console.log(`Retrying... Attempt number ${retryCount + 1}`);
244
- return checkConnectorUrl(connectorUrl, retryCount + 1);
235
+ if (retryCount < PREFLIGHT_RETRY_COUNT) {
236
+ const delay = 50 * 2 ** retryCount;
237
+ await new Promise((resolve)=>setTimeout(resolve, delay));
238
+ console.log(`Retrying... Attempt number ${retryCount + 1}`);
239
+ return checkConnectorUrl(connectorUrl, retryCount + 1);
240
+ }
241
+ const errorMessage = getErrorMessage(responseStatus, error);
242
+ const errorToSend = error || new Error(errorMessage);
243
+ const context = {
244
+ connectorUrl,
245
+ responseStatus
246
+ };
247
+ if (responseStatus !== 404) await errorReporter.send(errorToSend, context);
248
+ return {
249
+ checked: true,
250
+ error: errorMessage
251
+ };
245
252
  }
246
- const errorMessage = getErrorMessage(responseStatus, error);
247
- const errorToSend = error || new Error(errorMessage);
248
- const context = {
249
- connectorUrl,
250
- responseStatus
251
- };
252
- if (responseStatus !== 404) await errorReporter.send(errorToSend, context);
253
- return {
254
- checked: true,
255
- error: errorMessage
256
- };
257
253
  };
258
-
259
254
  const handleOAuthUrl = (oauthUrl)=>{
260
- if (oauthUrl.protocol !== 'https:') {
261
- console.log(`handleOAuthUrl - Skipping non https url - ${oauthUrl.href}`);
262
- return;
263
- }
264
- reactNative.Linking.openURL(oauthUrl.href);
255
+ console.log(`handleOAuthUrl - Opening URL - ${oauthUrl.toString()}`);
256
+ reactNative.Linking.openURL(oauthUrl.toString());
265
257
  };
266
-
267
258
  const QuilttConnector = ({ testId, connectorId, connectionId, institution, oauthRedirectUrl, onEvent, onLoad, onExit, onExitSuccess, onExitAbort, onExitError })=>{
268
259
  const webViewRef = react$1.useRef(null);
269
260
  const { session } = react.useQuilttSession();
@@ -319,37 +310,14 @@ const QuilttConnector = ({ testId, connectorId, connectionId, institution, oauth
319
310
  institution,
320
311
  session?.token
321
312
  ]);
322
- // allowedListUrl & shouldRender ensure we are only rendering Quiltt, MX and Plaid content in Webview
323
- // For other urls, we assume those are bank urls, which need to be handled in external browser.
324
- // TODO: Convert it to a list from Quiltt Server to prevent MX/ Plaid changes.
325
- const allowedListUrl = react$1.useMemo(()=>[
326
- 'quiltt.io',
327
- 'quiltt.app',
328
- 'quiltt.dev',
329
- 'moneydesktop.com',
330
- 'plaid.com',
331
- 'https://cdn.plaid.com/link',
332
- 'https://www.google.com/recaptcha',
333
- 'https://challenges.cloudflare.com',
334
- 'https://api.stripe.com',
335
- 'https://cdn.jsdelivr.net',
336
- 'https://auth0.com'
337
- ], []);
338
313
  const isQuilttEvent = react$1.useCallback((url)=>url.protocol === 'quilttconnector:', []);
339
- const shouldRender = react$1.useCallback((url)=>{
340
- if (isQuilttEvent(url)) return false;
341
- if (url.protocol !== 'https:') {
342
- return false;
343
- }
344
- return allowedListUrl.some((href)=>url.href.includes(href));
345
- }, [
346
- allowedListUrl,
314
+ const shouldRender = react$1.useCallback((url)=>!isQuilttEvent(url), [
347
315
  isQuilttEvent
348
316
  ]);
349
- const clearLocalStorage = ()=>{
317
+ const clearLocalStorage = react$1.useCallback(()=>{
350
318
  const script = 'localStorage.clear();';
351
319
  webViewRef.current?.injectJavaScript(script);
352
- };
320
+ }, []);
353
321
  const handleQuilttEvent = react$1.useCallback((url)=>{
354
322
  url.searchParams.delete('source');
355
323
  url.searchParams.append('connectorId', connectorId);
@@ -389,6 +357,7 @@ const QuilttConnector = ({ testId, connectorId, connectionId, institution, oauth
389
357
  break;
390
358
  }
391
359
  }, [
360
+ clearLocalStorage,
392
361
  connectorId,
393
362
  initInjectedJavaScript,
394
363
  onEvent,
@@ -417,13 +386,15 @@ const QuilttConnector = ({ testId, connectorId, connectionId, institution, oauth
417
386
  if (!preFlightCheck.checked) return /*#__PURE__*/ jsxRuntime.jsx(LoadingScreen, {
418
387
  testId: "loading-screen"
419
388
  });
420
- if (preFlightCheck.error) return /*#__PURE__*/ jsxRuntime.jsx(ErrorScreen, {
421
- testId: "error-screen",
422
- error: preFlightCheck.error,
423
- cta: ()=>onExitError?.({
424
- connectorId
425
- })
426
- });
389
+ if (preFlightCheck.error) {
390
+ return /*#__PURE__*/ jsxRuntime.jsx(ErrorScreen, {
391
+ testId: "error-screen",
392
+ error: preFlightCheck.error,
393
+ cta: ()=>onExitError?.({
394
+ connectorId
395
+ })
396
+ });
397
+ }
427
398
  return /*#__PURE__*/ jsxRuntime.jsx(AndroidSafeAreaView, {
428
399
  testId: testId,
429
400
  children: /*#__PURE__*/ jsxRuntime.jsx(reactNativeWebview.WebView, {
@@ -489,6 +460,8 @@ Object.defineProperty(exports, "useStorage", {
489
460
  get: function () { return react.useStorage; }
490
461
  });
491
462
  exports.QuilttConnector = QuilttConnector;
463
+ exports.checkConnectorUrl = checkConnectorUrl;
464
+ exports.handleOAuthUrl = handleOAuthUrl;
492
465
  Object.keys(core).forEach(function (k) {
493
466
  if (k !== 'default' && !Object.prototype.hasOwnProperty.call(exports, k)) Object.defineProperty(exports, k, {
494
467
  enumerable: true,
package/dist/index.d.cts CHANGED
@@ -2,7 +2,14 @@ export * from '@quiltt/core';
2
2
  import { ConnectorSDKCallbacks } from '@quiltt/react';
3
3
  export { QuilttAuthProvider, QuilttProvider, QuilttSettingsProvider, useQuilttClient, useQuilttConnector, useQuilttSession, useQuilttSettings, useSession, useStorage } from '@quiltt/react';
4
4
  import * as react_jsx_runtime from 'react/jsx-runtime';
5
+ import { URL } from 'react-native-url-polyfill';
5
6
 
7
+ type PreFlightCheck = {
8
+ checked: boolean;
9
+ error?: string;
10
+ };
11
+ declare const checkConnectorUrl: (connectorUrl: string, retryCount?: number) => Promise<PreFlightCheck>;
12
+ declare const handleOAuthUrl: (oauthUrl: URL | string) => void;
6
13
  type QuilttConnectorProps = {
7
14
  testId?: string;
8
15
  connectorId: string;
@@ -15,4 +22,4 @@ declare const QuilttConnector: {
15
22
  displayName: string;
16
23
  };
17
24
 
18
- export { QuilttConnector };
25
+ export { type PreFlightCheck, QuilttConnector, checkConnectorUrl, handleOAuthUrl };
package/dist/index.d.ts CHANGED
@@ -2,7 +2,14 @@ export * from '@quiltt/core';
2
2
  import { ConnectorSDKCallbacks } from '@quiltt/react';
3
3
  export { QuilttAuthProvider, QuilttProvider, QuilttSettingsProvider, useQuilttClient, useQuilttConnector, useQuilttSession, useQuilttSettings, useSession, useStorage } from '@quiltt/react';
4
4
  import * as react_jsx_runtime from 'react/jsx-runtime';
5
+ import { URL } from 'react-native-url-polyfill';
5
6
 
7
+ type PreFlightCheck = {
8
+ checked: boolean;
9
+ error?: string;
10
+ };
11
+ declare const checkConnectorUrl: (connectorUrl: string, retryCount?: number) => Promise<PreFlightCheck>;
12
+ declare const handleOAuthUrl: (oauthUrl: URL | string) => void;
6
13
  type QuilttConnectorProps = {
7
14
  testId?: string;
8
15
  connectorId: string;
@@ -15,4 +22,4 @@ declare const QuilttConnector: {
15
22
  displayName: string;
16
23
  };
17
24
 
18
- export { QuilttConnector };
25
+ export { type PreFlightCheck, QuilttConnector, checkConnectorUrl, handleOAuthUrl };
package/dist/index.js CHANGED
@@ -4,13 +4,101 @@ import { useQuilttSession, ConnectorSDKEventType } from '@quiltt/react';
4
4
  export { QuilttAuthProvider, QuilttProvider, QuilttSettingsProvider, useQuilttClient, useQuilttConnector, useQuilttSession, useQuilttSettings, useSession, useStorage } from '@quiltt/react';
5
5
  import { jsx, jsxs } from 'react/jsx-runtime';
6
6
  import { useRef, useMemo, useState, useEffect, useCallback } from 'react';
7
+ import { StyleSheet, Platform, StatusBar, SafeAreaView, View, Text, Pressable, ActivityIndicator, Linking } from 'react-native';
7
8
  import { URL } from 'react-native-url-polyfill';
8
9
  import { WebView } from 'react-native-webview';
9
- import { StyleSheet, Platform, StatusBar, SafeAreaView, View, Text, Pressable, ActivityIndicator, Linking } from 'react-native';
10
10
  import { generateStackTrace, makeBacktrace, getCauses } from '@honeybadger-io/core/build/src/util';
11
11
 
12
12
  // Generated by genversion.
13
- const version = '3.6.8';
13
+ const version = '3.6.10';
14
+
15
+ const ErrorReporterConfig = {
16
+ honeybadger_api_key: 'undefined'
17
+ };
18
+
19
+ // Quick hack to send error to Honeybadger to debug why the connector is not routable
20
+ const notifier = {
21
+ name: 'Quiltt React Native SDK Reporter',
22
+ url: 'https://www.quiltt.dev/guides/connector/react-native',
23
+ version: version
24
+ };
25
+ class ErrorReporter {
26
+ constructor(platform){
27
+ this.noticeUrl = 'https://api.honeybadger.io/v1/notices';
28
+ this.apiKey = ErrorReporterConfig.honeybadger_api_key;
29
+ this.clientName = 'react-native-sdk';
30
+ this.clientVersion = version;
31
+ this.platform = platform;
32
+ this.logger = console;
33
+ this.userAgent = `${this.clientName} ${this.clientVersion}; ${this.platform}`;
34
+ }
35
+ async send(error, context) {
36
+ const headers = {
37
+ 'X-API-Key': this.apiKey,
38
+ 'Content-Type': 'application/json',
39
+ Accept: 'application/json',
40
+ 'User-Agent': `${this.clientName} ${this.clientVersion}; ${this.platform}`
41
+ };
42
+ const payload = await this.buildPayload(error, context);
43
+ const method = 'POST';
44
+ const body = JSON.stringify(payload);
45
+ const mode = 'cors';
46
+ fetch(this.noticeUrl, {
47
+ headers,
48
+ method,
49
+ body,
50
+ mode
51
+ }).then((response)=>{
52
+ if (response.status !== 201) {
53
+ this.logger.warn(`Error report failed: unknown response from server. code=${response.status}`);
54
+ return;
55
+ }
56
+ return response.json();
57
+ }).then((data)=>{
58
+ if (data) {
59
+ this.logger.info(`Error report sent ⚡ https://app.honeybadger.io/notice/${data?.id}`);
60
+ }
61
+ });
62
+ }
63
+ async buildPayload(error, localContext = {}) {
64
+ const notice = error;
65
+ notice.stack = generateStackTrace();
66
+ notice.backtrace = makeBacktrace(notice.stack);
67
+ return {
68
+ notifier,
69
+ error: {
70
+ class: notice.name,
71
+ message: notice.message,
72
+ backtrace: notice.backtrace,
73
+ // fingerprint: this.calculateFingerprint(notice),
74
+ tags: notice.tags || [],
75
+ causes: getCauses(notice, this.logger)
76
+ },
77
+ request: {
78
+ url: notice.url,
79
+ component: notice.component,
80
+ action: notice.action,
81
+ context: localContext || {},
82
+ cgi_data: {},
83
+ params: {},
84
+ session: {}
85
+ },
86
+ server: {
87
+ project_root: notice.projectRoot,
88
+ environment_name: this.userAgent,
89
+ revision: version,
90
+ hostname: this.platform,
91
+ time: new Date().toUTCString()
92
+ },
93
+ details: notice.details || {}
94
+ };
95
+ }
96
+ }
97
+
98
+ const getErrorMessage = (responseStatus, error)=>{
99
+ if (error) return `An error occurred while checking the connector URL: ${error?.name} \n${error?.message}`;
100
+ return responseStatus ? `The URL is not routable. Response status: ${responseStatus}` : 'An error occurred while checking the connector URL';
101
+ };
14
102
 
15
103
  const AndroidSafeAreaView = ({ testId, children })=>/*#__PURE__*/ jsx(SafeAreaView, {
16
104
  testID: testId,
@@ -125,144 +213,47 @@ const LoadingScreen = ({ testId })=>/*#__PURE__*/ jsx(AndroidSafeAreaView, {
125
213
  })
126
214
  });
127
215
 
128
- const ErrorReporterConfig = {
129
- honeybadger_api_key: 'undefined'
130
- };
131
-
132
- // Quick hack to send error to Honeybadger to debug why the connector is not routable
133
- const notifier = {
134
- name: 'Quiltt React Native SDK Reporter',
135
- url: 'https://www.quiltt.dev/guides/connector/react-native',
136
- version: version
137
- };
138
- class ErrorReporter {
139
- constructor(platform){
140
- this.noticeUrl = 'https://api.honeybadger.io/v1/notices';
141
- this.apiKey = ErrorReporterConfig.honeybadger_api_key;
142
- this.clientName = 'react-native-sdk';
143
- this.clientVersion = version;
144
- this.platform = platform;
145
- this.logger = console;
146
- this.userAgent = `${this.clientName} ${this.clientVersion}; ${this.platform}`;
147
- }
148
- async send(error, context) {
149
- const headers = {
150
- 'X-API-Key': this.apiKey,
151
- 'Content-Type': 'application/json',
152
- Accept: 'application/json',
153
- 'User-Agent': `${this.clientName} ${this.clientVersion}; ${this.platform}`
154
- };
155
- const payload = await this.buildPayload(error, context);
156
- const method = 'POST';
157
- const body = JSON.stringify(payload);
158
- const mode = 'cors';
159
- fetch(this.noticeUrl, {
160
- headers,
161
- method,
162
- body,
163
- mode
164
- }).then((response)=>{
165
- if (response.status !== 201) {
166
- this.logger.warn(`Error report failed: unknown response from server. code=${response.status}`);
167
- return;
168
- }
169
- return response.json();
170
- }).then((data)=>{
171
- if (data) {
172
- this.logger.info(`Error report sent ⚡ https://app.honeybadger.io/notice/${data?.id}`);
173
- }
174
- });
175
- }
176
- async buildPayload(error, localContext = {}) {
177
- const notice = error;
178
- notice.stack = generateStackTrace();
179
- notice.backtrace = makeBacktrace(notice.stack);
180
- return {
181
- notifier,
182
- error: {
183
- class: notice.name,
184
- message: notice.message,
185
- backtrace: notice.backtrace,
186
- // fingerprint: this.calculateFingerprint(notice),
187
- tags: notice.tags || [],
188
- causes: getCauses(notice, this.logger)
189
- },
190
- request: {
191
- url: notice.url,
192
- component: notice.component,
193
- action: notice.action,
194
- context: localContext || {},
195
- cgi_data: {},
196
- params: {},
197
- session: {}
198
- },
199
- server: {
200
- project_root: notice.projectRoot,
201
- environment_name: this.userAgent,
202
- revision: version,
203
- hostname: this.platform,
204
- time: new Date().toUTCString()
205
- },
206
- details: notice.details || {}
207
- };
208
- }
209
- }
210
-
211
- const getErrorMessage = (responseStatus, error)=>{
212
- if (error) return `An error occurred while checking the connector URL: ${error?.name} \n${error?.message}`;
213
- return responseStatus ? `The URL is not routable. Response status: ${responseStatus}` : 'An error occurred while checking the connector URL';
214
- };
215
-
216
216
  const errorReporter = new ErrorReporter(`${Platform.OS} ${Platform.Version}`);
217
217
  const PREFLIGHT_RETRY_COUNT = 3;
218
218
  const checkConnectorUrl = async (connectorUrl, retryCount = 0)=>{
219
219
  let responseStatus;
220
220
  let error;
221
- let errorOccurred = false;
222
221
  try {
223
222
  const response = await fetch(connectorUrl);
224
223
  if (!response.ok) {
225
- console.error(`The URL ${connectorUrl} is not routable.`);
226
224
  responseStatus = response.status;
227
- errorOccurred = true;
228
- } else {
229
- console.log(`The URL ${connectorUrl} is routable.`);
230
- return {
231
- checked: true
232
- };
225
+ throw new Error(`The URL ${connectorUrl} is not routable.`);
233
226
  }
227
+ console.log(`The URL ${connectorUrl} is routable.`);
228
+ return {
229
+ checked: true
230
+ };
234
231
  } catch (e) {
235
232
  error = e;
236
233
  console.error(`An error occurred while checking the connector URL: ${error}`);
237
- errorOccurred = true;
238
- }
239
- if (errorOccurred && retryCount < PREFLIGHT_RETRY_COUNT) {
240
- const delay = 50 * Math.pow(2, retryCount);
241
- await new Promise((resolve)=>setTimeout(resolve, delay));
242
- console.log(`Retrying... Attempt number ${retryCount + 1}`);
243
- return checkConnectorUrl(connectorUrl, retryCount + 1);
234
+ if (retryCount < PREFLIGHT_RETRY_COUNT) {
235
+ const delay = 50 * 2 ** retryCount;
236
+ await new Promise((resolve)=>setTimeout(resolve, delay));
237
+ console.log(`Retrying... Attempt number ${retryCount + 1}`);
238
+ return checkConnectorUrl(connectorUrl, retryCount + 1);
239
+ }
240
+ const errorMessage = getErrorMessage(responseStatus, error);
241
+ const errorToSend = error || new Error(errorMessage);
242
+ const context = {
243
+ connectorUrl,
244
+ responseStatus
245
+ };
246
+ if (responseStatus !== 404) await errorReporter.send(errorToSend, context);
247
+ return {
248
+ checked: true,
249
+ error: errorMessage
250
+ };
244
251
  }
245
- const errorMessage = getErrorMessage(responseStatus, error);
246
- const errorToSend = error || new Error(errorMessage);
247
- const context = {
248
- connectorUrl,
249
- responseStatus
250
- };
251
- if (responseStatus !== 404) await errorReporter.send(errorToSend, context);
252
- return {
253
- checked: true,
254
- error: errorMessage
255
- };
256
252
  };
257
-
258
253
  const handleOAuthUrl = (oauthUrl)=>{
259
- if (oauthUrl.protocol !== 'https:') {
260
- console.log(`handleOAuthUrl - Skipping non https url - ${oauthUrl.href}`);
261
- return;
262
- }
263
- Linking.openURL(oauthUrl.href);
254
+ console.log(`handleOAuthUrl - Opening URL - ${oauthUrl.toString()}`);
255
+ Linking.openURL(oauthUrl.toString());
264
256
  };
265
-
266
257
  const QuilttConnector = ({ testId, connectorId, connectionId, institution, oauthRedirectUrl, onEvent, onLoad, onExit, onExitSuccess, onExitAbort, onExitError })=>{
267
258
  const webViewRef = useRef(null);
268
259
  const { session } = useQuilttSession();
@@ -318,37 +309,14 @@ const QuilttConnector = ({ testId, connectorId, connectionId, institution, oauth
318
309
  institution,
319
310
  session?.token
320
311
  ]);
321
- // allowedListUrl & shouldRender ensure we are only rendering Quiltt, MX and Plaid content in Webview
322
- // For other urls, we assume those are bank urls, which need to be handled in external browser.
323
- // TODO: Convert it to a list from Quiltt Server to prevent MX/ Plaid changes.
324
- const allowedListUrl = useMemo(()=>[
325
- 'quiltt.io',
326
- 'quiltt.app',
327
- 'quiltt.dev',
328
- 'moneydesktop.com',
329
- 'plaid.com',
330
- 'https://cdn.plaid.com/link',
331
- 'https://www.google.com/recaptcha',
332
- 'https://challenges.cloudflare.com',
333
- 'https://api.stripe.com',
334
- 'https://cdn.jsdelivr.net',
335
- 'https://auth0.com'
336
- ], []);
337
312
  const isQuilttEvent = useCallback((url)=>url.protocol === 'quilttconnector:', []);
338
- const shouldRender = useCallback((url)=>{
339
- if (isQuilttEvent(url)) return false;
340
- if (url.protocol !== 'https:') {
341
- return false;
342
- }
343
- return allowedListUrl.some((href)=>url.href.includes(href));
344
- }, [
345
- allowedListUrl,
313
+ const shouldRender = useCallback((url)=>!isQuilttEvent(url), [
346
314
  isQuilttEvent
347
315
  ]);
348
- const clearLocalStorage = ()=>{
316
+ const clearLocalStorage = useCallback(()=>{
349
317
  const script = 'localStorage.clear();';
350
318
  webViewRef.current?.injectJavaScript(script);
351
- };
319
+ }, []);
352
320
  const handleQuilttEvent = useCallback((url)=>{
353
321
  url.searchParams.delete('source');
354
322
  url.searchParams.append('connectorId', connectorId);
@@ -388,6 +356,7 @@ const QuilttConnector = ({ testId, connectorId, connectionId, institution, oauth
388
356
  break;
389
357
  }
390
358
  }, [
359
+ clearLocalStorage,
391
360
  connectorId,
392
361
  initInjectedJavaScript,
393
362
  onEvent,
@@ -416,13 +385,15 @@ const QuilttConnector = ({ testId, connectorId, connectionId, institution, oauth
416
385
  if (!preFlightCheck.checked) return /*#__PURE__*/ jsx(LoadingScreen, {
417
386
  testId: "loading-screen"
418
387
  });
419
- if (preFlightCheck.error) return /*#__PURE__*/ jsx(ErrorScreen, {
420
- testId: "error-screen",
421
- error: preFlightCheck.error,
422
- cta: ()=>onExitError?.({
423
- connectorId
424
- })
425
- });
388
+ if (preFlightCheck.error) {
389
+ return /*#__PURE__*/ jsx(ErrorScreen, {
390
+ testId: "error-screen",
391
+ error: preFlightCheck.error,
392
+ cta: ()=>onExitError?.({
393
+ connectorId
394
+ })
395
+ });
396
+ }
426
397
  return /*#__PURE__*/ jsx(AndroidSafeAreaView, {
427
398
  testId: testId,
428
399
  children: /*#__PURE__*/ jsx(WebView, {
@@ -451,4 +422,4 @@ if (!global.atob) {
451
422
  global.atob = decode;
452
423
  }
453
424
 
454
- export { QuilttConnector };
425
+ export { QuilttConnector, checkConnectorUrl, handleOAuthUrl };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@quiltt/react-native",
3
- "version": "3.6.8",
3
+ "version": "3.6.10",
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": {
@@ -27,26 +27,20 @@
27
27
  ],
28
28
  "dependencies": {
29
29
  "@honeybadger-io/core": "6.6.0",
30
- "@quiltt/core": "3.6.8",
31
- "@quiltt/react": "3.6.8"
30
+ "base-64": "1.0.0",
31
+ "react-native-url-polyfill": "2.0.0",
32
+ "react-native-webview": "13.10.2",
33
+ "@quiltt/core": "3.6.10",
34
+ "@quiltt/react": "3.6.10"
32
35
  },
33
36
  "devDependencies": {
34
37
  "@apollo/client": "3.9.9",
35
- "@trivago/prettier-plugin-sort-imports": "4.1.1",
36
- "@types/base-64": "0.1.0",
37
- "@types/node": "20.12.7",
38
+ "@biomejs/biome": "1.8.1",
39
+ "@types/base-64": "1.0.2",
40
+ "@types/node": "20.14.5",
38
41
  "@types/react": "18.2.73",
39
42
  "@types/react-native": "0.72.5",
40
- "@typescript-eslint/eslint-plugin": "5.60.1",
41
- "@typescript-eslint/parser": "5.60.1",
42
43
  "bunchee": "4.4.8",
43
- "eslint": "8.43.0",
44
- "eslint-config-prettier": "8.8.0",
45
- "eslint-plugin-jsx-a11y": "6.7.1",
46
- "eslint-plugin-prettier": "4.2.1",
47
- "eslint-plugin-react": "7.32.2",
48
- "eslint-plugin-react-hooks": "4.6.0",
49
- "prettier": "2.8.8",
50
44
  "react": "18.2.0",
51
45
  "rimraf": "5.0.5",
52
46
  "typescript": "5.4.3"
@@ -67,7 +61,7 @@
67
61
  "build": "pnpm run addApiKey && pnpm run addVersion && bunchee",
68
62
  "clean": "rimraf .turbo dist",
69
63
  "dev": "bunchee --watch",
70
- "lint": "TIMING=1 eslint --ext .js,.jsx,.ts,.tsx src/ --fix",
64
+ "lint": "TIMING=1 biome check src/ --fix",
71
65
  "typecheck": "tsc --project tsconfig.json --noEmit"
72
66
  }
73
67
  }
@@ -1,6 +1,5 @@
1
- import { PropsWithChildren } from 'react'
2
-
3
- import { SafeAreaView, StyleSheet, Platform, StatusBar } from 'react-native'
1
+ import type { PropsWithChildren } from 'react'
2
+ import { Platform, SafeAreaView, StatusBar, StyleSheet } from 'react-native'
4
3
 
5
4
  type AndroidSafeAreaViewProps = PropsWithChildren & {
6
5
  testId?: string
@@ -1,4 +1,4 @@
1
- import { View, Text, Pressable, StyleSheet } from 'react-native'
1
+ import { Pressable, StyleSheet, Text, View } from 'react-native'
2
2
 
3
3
  import { AndroidSafeAreaView } from './AndroidSafeAreaView'
4
4
 
@@ -1,24 +1,66 @@
1
1
  import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
2
-
3
- // React Native's URL implementation is incomplete
4
- // https://github.com/facebook/react-native/issues/16434
5
- import { URL } from 'react-native-url-polyfill'
2
+ import { Linking, Platform } from 'react-native'
3
+ import { URL } from 'react-native-url-polyfill' // https://github.com/facebook/react-native/issues/16434
6
4
  import { WebView } from 'react-native-webview'
7
5
  import type { ShouldStartLoadRequest } from 'react-native-webview/lib/WebViewTypes'
8
6
 
9
7
  import {
10
- ConnectorSDKCallbackMetadata,
11
- ConnectorSDKCallbacks,
8
+ type ConnectorSDKCallbackMetadata,
9
+ type ConnectorSDKCallbacks,
12
10
  ConnectorSDKEventType,
13
11
  useQuilttSession,
14
12
  } from '@quiltt/react'
15
13
 
14
+ import { ErrorReporter, getErrorMessage } from '../utils'
16
15
  import { version } from '../version'
17
16
  import { AndroidSafeAreaView } from './AndroidSafeAreaView'
18
17
  import { ErrorScreen } from './ErrorScreen'
19
18
  import { LoadingScreen } from './LoadingScreen'
20
- import { checkConnectorUrl, handleOAuthUrl } from '../utils'
21
- import type { PreFlightCheck } from '../utils'
19
+
20
+ const errorReporter = new ErrorReporter(`${Platform.OS} ${Platform.Version}`)
21
+ const PREFLIGHT_RETRY_COUNT = 3
22
+
23
+ export type PreFlightCheck = {
24
+ checked: boolean
25
+ error?: string
26
+ }
27
+
28
+ export const checkConnectorUrl = async (
29
+ connectorUrl: string,
30
+ retryCount = 0
31
+ ): Promise<PreFlightCheck> => {
32
+ let responseStatus: number | undefined
33
+ let error: Error | undefined
34
+ try {
35
+ const response = await fetch(connectorUrl)
36
+ if (!response.ok) {
37
+ responseStatus = response.status
38
+ throw new Error(`The URL ${connectorUrl} is not routable.`)
39
+ }
40
+ console.log(`The URL ${connectorUrl} is routable.`)
41
+ return { checked: true }
42
+ } catch (e) {
43
+ error = e as Error
44
+ console.error(`An error occurred while checking the connector URL: ${error}`)
45
+
46
+ if (retryCount < PREFLIGHT_RETRY_COUNT) {
47
+ const delay = 50 * 2 ** retryCount
48
+ await new Promise((resolve) => setTimeout(resolve, delay))
49
+ console.log(`Retrying... Attempt number ${retryCount + 1}`)
50
+ return checkConnectorUrl(connectorUrl, retryCount + 1)
51
+ }
52
+ const errorMessage = getErrorMessage(responseStatus, error as Error)
53
+ const errorToSend = (error as Error) || new Error(errorMessage)
54
+ const context = { connectorUrl, responseStatus }
55
+ if (responseStatus !== 404) await errorReporter.send(errorToSend, context)
56
+ return { checked: true, error: errorMessage }
57
+ }
58
+ }
59
+
60
+ export const handleOAuthUrl = (oauthUrl: URL | string) => {
61
+ console.log(`handleOAuthUrl - Opening URL - ${oauthUrl.toString()}`)
62
+ Linking.openURL(oauthUrl.toString())
63
+ }
22
64
 
23
65
  type QuilttConnectorProps = {
24
66
  testId?: string
@@ -43,17 +85,20 @@ const QuilttConnector = ({
43
85
  }: QuilttConnectorProps) => {
44
86
  const webViewRef = useRef<WebView>(null)
45
87
  const { session } = useQuilttSession()
88
+
46
89
  const encodedOAuthRedirectUrl = useMemo(
47
90
  () => encodeURIComponent(oauthRedirectUrl),
48
91
  [oauthRedirectUrl]
49
92
  )
93
+
50
94
  const connectorUrl = useMemo(() => {
51
- const url: URL = new URL(`https://${connectorId}.quiltt.app`)
95
+ const url = new URL(`https://${connectorId}.quiltt.app`)
52
96
  url.searchParams.append('mode', 'webview')
53
97
  url.searchParams.append('oauth_redirect_url', encodedOAuthRedirectUrl)
54
98
  url.searchParams.append('agent', `react-native-${version}`)
55
99
  return url.toString()
56
100
  }, [connectorId, encodedOAuthRedirectUrl])
101
+
57
102
  const [preFlightCheck, setPreFlightCheck] = useState<PreFlightCheck>({ checked: false })
58
103
 
59
104
  useEffect(() => {
@@ -86,43 +131,14 @@ const QuilttConnector = ({
86
131
  webViewRef.current?.injectJavaScript(script)
87
132
  }, [connectionId, connectorId, institution, session?.token])
88
133
 
89
- // allowedListUrl & shouldRender ensure we are only rendering Quiltt, MX and Plaid content in Webview
90
- // For other urls, we assume those are bank urls, which need to be handled in external browser.
91
- // TODO: Convert it to a list from Quiltt Server to prevent MX/ Plaid changes.
92
- const allowedListUrl = useMemo(
93
- () => [
94
- 'quiltt.io',
95
- 'quiltt.app',
96
- 'quiltt.dev',
97
- 'moneydesktop.com',
98
- 'plaid.com',
99
- 'https://cdn.plaid.com/link',
100
- 'https://www.google.com/recaptcha',
101
- 'https://challenges.cloudflare.com',
102
- 'https://api.stripe.com',
103
- 'https://cdn.jsdelivr.net',
104
- 'https://auth0.com',
105
- ],
106
- []
107
- )
108
-
109
134
  const isQuilttEvent = useCallback((url: URL) => url.protocol === 'quilttconnector:', [])
110
135
 
111
- const shouldRender = useCallback(
112
- (url: URL) => {
113
- if (isQuilttEvent(url)) return false
114
- if (url.protocol !== 'https:') {
115
- return false
116
- }
117
- return allowedListUrl.some((href) => url.href.includes(href))
118
- },
119
- [allowedListUrl, isQuilttEvent]
120
- )
136
+ const shouldRender = useCallback((url: URL) => !isQuilttEvent(url), [isQuilttEvent])
121
137
 
122
- const clearLocalStorage = () => {
138
+ const clearLocalStorage = useCallback(() => {
123
139
  const script = 'localStorage.clear();'
124
140
  webViewRef.current?.injectJavaScript(script)
125
- }
141
+ }, [])
126
142
 
127
143
  const handleQuilttEvent = useCallback(
128
144
  (url: URL) => {
@@ -167,6 +183,7 @@ const QuilttConnector = ({
167
183
  }
168
184
  },
169
185
  [
186
+ clearLocalStorage,
170
187
  connectorId,
171
188
  initInjectedJavaScript,
172
189
  onEvent,
@@ -196,7 +213,7 @@ const QuilttConnector = ({
196
213
  )
197
214
 
198
215
  if (!preFlightCheck.checked) return <LoadingScreen testId="loading-screen" />
199
- if (preFlightCheck.error)
216
+ if (preFlightCheck.error) {
200
217
  return (
201
218
  <ErrorScreen
202
219
  testId="error-screen"
@@ -204,6 +221,7 @@ const QuilttConnector = ({
204
221
  cta={() => onExitError?.({ connectorId })}
205
222
  />
206
223
  )
224
+ }
207
225
 
208
226
  return (
209
227
  <AndroidSafeAreaView testId={testId}>
@@ -2,8 +2,8 @@
2
2
  import type { Notice, NoticeTransportPayload } from '@honeybadger-io/core/build/src/types'
3
3
  import { generateStackTrace, getCauses, makeBacktrace } from '@honeybadger-io/core/build/src/util'
4
4
 
5
- import { ErrorReporterConfig } from './ErrorReporterConfig'
6
5
  import { version } from '../../version'
6
+ import { ErrorReporterConfig } from './ErrorReporterConfig'
7
7
 
8
8
  const notifier = {
9
9
  name: 'Quiltt React Native SDK Reporter',
@@ -1,2 +1 @@
1
- export * from './connector'
2
1
  export * from './error'
package/src/version.ts CHANGED
@@ -1,2 +1,2 @@
1
1
  // Generated by genversion.
2
- export const version = '3.6.8'
2
+ export const version = '3.6.10'
@@ -1,47 +0,0 @@
1
- import { getErrorMessage, ErrorReporter } from '../error'
2
- import { Platform } from 'react-native'
3
-
4
- const errorReporter = new ErrorReporter(`${Platform.OS} ${Platform.Version}`)
5
- const PREFLIGHT_RETRY_COUNT = 3
6
-
7
- export type PreFlightCheck = {
8
- checked: boolean
9
- error?: string
10
- }
11
-
12
- export const checkConnectorUrl = async (
13
- connectorUrl: string,
14
- retryCount = 0
15
- ): Promise<PreFlightCheck> => {
16
- let responseStatus
17
- let error
18
- let errorOccurred = false
19
- try {
20
- const response = await fetch(connectorUrl)
21
- if (!response.ok) {
22
- console.error(`The URL ${connectorUrl} is not routable.`)
23
- responseStatus = response.status
24
- errorOccurred = true
25
- } else {
26
- console.log(`The URL ${connectorUrl} is routable.`)
27
- return { checked: true }
28
- }
29
- } catch (e) {
30
- error = e
31
- console.error(`An error occurred while checking the connector URL: ${error}`)
32
- errorOccurred = true
33
- }
34
-
35
- if (errorOccurred && retryCount < PREFLIGHT_RETRY_COUNT) {
36
- const delay = 50 * Math.pow(2, retryCount)
37
- await new Promise((resolve) => setTimeout(resolve, delay))
38
- console.log(`Retrying... Attempt number ${retryCount + 1}`)
39
- return checkConnectorUrl(connectorUrl, retryCount + 1)
40
- }
41
-
42
- const errorMessage = getErrorMessage(responseStatus, error as Error)
43
- const errorToSend = (error as Error) || new Error(errorMessage)
44
- const context = { connectorUrl, responseStatus }
45
- if (responseStatus !== 404) await errorReporter.send(errorToSend, context)
46
- return { checked: true, error: errorMessage }
47
- }
@@ -1,9 +0,0 @@
1
- import { Linking } from 'react-native'
2
-
3
- export const handleOAuthUrl = (oauthUrl: URL) => {
4
- if (oauthUrl.protocol !== 'https:') {
5
- console.log(`handleOAuthUrl - Skipping non https url - ${oauthUrl.href}`)
6
- return
7
- }
8
- Linking.openURL(oauthUrl.href)
9
- }
File without changes
@@ -1,2 +0,0 @@
1
- export * from './checkConnectorUrl'
2
- export * from './handleOAuthUrl'