@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 +20 -0
- package/README.md +2 -2
- package/dist/index.cjs +129 -156
- package/dist/index.d.cts +8 -1
- package/dist/index.d.ts +8 -1
- package/dist/index.js +128 -157
- package/package.json +10 -16
- package/src/components/AndroidSafeAreaView.tsx +2 -3
- package/src/components/ErrorScreen.tsx +1 -1
- package/src/components/QuilttConnector.tsx +60 -42
- package/src/utils/error/ErrorReporter.ts +1 -1
- package/src/utils/index.ts +0 -1
- package/src/version.ts +1 -1
- package/src/utils/connector/checkConnectorUrl.ts +0 -47
- package/src/utils/connector/handleOAuthUrl.ts +0 -9
- package/src/utils/connector/handleQuilttEvent.ts +0 -0
- package/src/utils/connector/index.ts +0 -2
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
|
-
```
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
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
|
-
|
|
261
|
-
|
|
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)
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
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
|
-
|
|
260
|
-
|
|
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)
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
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.
|
|
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
|
-
"
|
|
31
|
-
"
|
|
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
|
-
"@
|
|
36
|
-
"@types/base-64": "
|
|
37
|
-
"@types/node": "20.
|
|
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
|
|
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,24 +1,66 @@
|
|
|
1
1
|
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
|
2
|
-
|
|
3
|
-
|
|
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
|
-
|
|
21
|
-
|
|
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
|
|
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',
|
package/src/utils/index.ts
CHANGED
package/src/version.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
// Generated by genversion.
|
|
2
|
-
export const version = '3.6.
|
|
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
|
-
}
|
|
File without changes
|