@quiltt/react-native 3.6.2 → 3.6.4
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/dist/index.cjs +490 -0
- package/dist/index.d.cts +18 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +158 -154
- package/package.json +9 -8
- package/src/components/AndroidSafeAreaView.tsx +8 -2
- package/src/components/ErrorScreen.tsx +4 -3
- package/src/components/LoadingScreen.tsx +7 -3
- package/src/components/QuilttConnector.tsx +21 -68
- package/src/index.ts +0 -13
- package/src/utils/connector/checkConnectorUrl.ts +47 -0
- package/src/utils/connector/handleOAuthUrl.ts +9 -0
- package/src/utils/connector/handleQuilttEvent.ts +0 -0
- package/src/utils/connector/index.ts +2 -0
- package/src/utils/{ErrorReporter.ts → error/ErrorReporter.ts} +1 -2
- package/src/utils/{ErrorReporterConfig.ts → error/ErrorReporterConfig.ts} +0 -1
- package/src/utils/{getErrorMessage.ts → error/getErrorMessage.ts} +1 -3
- package/src/utils/error/index.ts +2 -0
- package/src/utils/index.ts +2 -2
- package/src/version.ts +1 -1
package/dist/index.js
CHANGED
|
@@ -3,104 +3,17 @@ export * from '@quiltt/core';
|
|
|
3
3
|
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
|
-
import { useRef, useMemo, useState,
|
|
7
|
-
import { StyleSheet, Platform, StatusBar, SafeAreaView, View, Text, Pressable, ActivityIndicator, Linking } from 'react-native';
|
|
6
|
+
import { useRef, useMemo, useState, useEffect, useCallback } from 'react';
|
|
8
7
|
import { URL } from 'react-native-url-polyfill';
|
|
9
8
|
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
|
-
const ErrorReporterConfig = {
|
|
13
|
-
honeybadger_api_key: 'undefined'
|
|
14
|
-
};
|
|
15
|
-
|
|
16
12
|
// Generated by genversion.
|
|
17
|
-
const version = '3.6.
|
|
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
|
-
}
|
|
13
|
+
const version = '3.6.4';
|
|
97
14
|
|
|
98
|
-
const
|
|
99
|
-
|
|
100
|
-
return responseStatus ? `The URL is not routable. Response status: ${responseStatus}` : 'An error occurred while checking the connector URL';
|
|
101
|
-
};
|
|
102
|
-
|
|
103
|
-
const AndroidSafeAreaView = ({ children })=>/*#__PURE__*/ jsx(SafeAreaView, {
|
|
15
|
+
const AndroidSafeAreaView = ({ testId, children })=>/*#__PURE__*/ jsx(SafeAreaView, {
|
|
16
|
+
testID: testId,
|
|
104
17
|
style: styles$1.AndroidSafeArea,
|
|
105
18
|
children: children
|
|
106
19
|
});
|
|
@@ -112,7 +25,8 @@ const styles$1 = StyleSheet.create({
|
|
|
112
25
|
}
|
|
113
26
|
});
|
|
114
27
|
|
|
115
|
-
const ErrorScreen = ({ error, cta })=>/*#__PURE__*/ jsx(AndroidSafeAreaView, {
|
|
28
|
+
const ErrorScreen = ({ testId, error, cta })=>/*#__PURE__*/ jsx(AndroidSafeAreaView, {
|
|
29
|
+
testId: testId,
|
|
116
30
|
children: /*#__PURE__*/ jsxs(View, {
|
|
117
31
|
style: [
|
|
118
32
|
styles.container,
|
|
@@ -195,7 +109,8 @@ const styles = StyleSheet.create({
|
|
|
195
109
|
}
|
|
196
110
|
});
|
|
197
111
|
|
|
198
|
-
const LoadingScreen = ()=>/*#__PURE__*/ jsx(AndroidSafeAreaView, {
|
|
112
|
+
const LoadingScreen = ({ testId })=>/*#__PURE__*/ jsx(AndroidSafeAreaView, {
|
|
113
|
+
testId: testId,
|
|
199
114
|
children: /*#__PURE__*/ jsx(View, {
|
|
200
115
|
style: {
|
|
201
116
|
flex: 1,
|
|
@@ -203,15 +118,152 @@ const LoadingScreen = ()=>/*#__PURE__*/ jsx(AndroidSafeAreaView, {
|
|
|
203
118
|
alignItems: 'center'
|
|
204
119
|
},
|
|
205
120
|
children: /*#__PURE__*/ jsx(ActivityIndicator, {
|
|
121
|
+
testID: "activity-indicator",
|
|
206
122
|
size: "large",
|
|
207
|
-
color: "#
|
|
123
|
+
color: "#5928A3"
|
|
208
124
|
})
|
|
209
125
|
})
|
|
210
126
|
});
|
|
211
127
|
|
|
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
|
+
|
|
212
216
|
const errorReporter = new ErrorReporter(`${Platform.OS} ${Platform.Version}`);
|
|
213
217
|
const PREFLIGHT_RETRY_COUNT = 3;
|
|
214
|
-
const
|
|
218
|
+
const checkConnectorUrl = async (connectorUrl, retryCount = 0)=>{
|
|
219
|
+
let responseStatus;
|
|
220
|
+
let error;
|
|
221
|
+
let errorOccurred = false;
|
|
222
|
+
try {
|
|
223
|
+
const response = await fetch(connectorUrl);
|
|
224
|
+
if (!response.ok) {
|
|
225
|
+
console.error(`The URL ${connectorUrl} is not routable.`);
|
|
226
|
+
responseStatus = response.status;
|
|
227
|
+
errorOccurred = true;
|
|
228
|
+
} else {
|
|
229
|
+
console.log(`The URL ${connectorUrl} is routable.`);
|
|
230
|
+
return {
|
|
231
|
+
checked: true
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
} catch (e) {
|
|
235
|
+
error = e;
|
|
236
|
+
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);
|
|
244
|
+
}
|
|
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
|
+
};
|
|
257
|
+
|
|
258
|
+
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);
|
|
264
|
+
};
|
|
265
|
+
|
|
266
|
+
const QuilttConnector = ({ testId, connectorId, connectionId, institution, oauthRedirectUrl, onEvent, onLoad, onExit, onExitSuccess, onExitAbort, onExitError })=>{
|
|
215
267
|
const webViewRef = useRef(null);
|
|
216
268
|
const { session } = useQuilttSession();
|
|
217
269
|
const encodedOAuthRedirectUrl = useMemo(()=>encodeURIComponent(oauthRedirectUrl), [
|
|
@@ -230,59 +282,15 @@ const QuilttConnector = ({ connectorId, connectionId, institution, oauthRedirect
|
|
|
230
282
|
const [preFlightCheck, setPreFlightCheck] = useState({
|
|
231
283
|
checked: false
|
|
232
284
|
});
|
|
233
|
-
const checkConnectorUrl = useCallback(async (retryCount = 0)=>{
|
|
234
|
-
let responseStatus;
|
|
235
|
-
let error;
|
|
236
|
-
let errorOccurred = false;
|
|
237
|
-
try {
|
|
238
|
-
const response = await fetch(connectorUrl);
|
|
239
|
-
if (!response.ok) {
|
|
240
|
-
console.error(`The URL ${connectorUrl} is not routable.`);
|
|
241
|
-
responseStatus = response.status;
|
|
242
|
-
errorOccurred = true;
|
|
243
|
-
} else {
|
|
244
|
-
console.log(`The URL ${connectorUrl} is routable.`);
|
|
245
|
-
return {
|
|
246
|
-
checked: true
|
|
247
|
-
};
|
|
248
|
-
}
|
|
249
|
-
} catch (e) {
|
|
250
|
-
error = e;
|
|
251
|
-
console.error(`An error occurred while checking the connector URL: ${error}`);
|
|
252
|
-
errorOccurred = true;
|
|
253
|
-
}
|
|
254
|
-
// Retry logic in case of error or response not OK
|
|
255
|
-
if (errorOccurred && retryCount < PREFLIGHT_RETRY_COUNT) {
|
|
256
|
-
const delay = 50 * Math.pow(2, retryCount) // Exponential back-off
|
|
257
|
-
;
|
|
258
|
-
await new Promise((resolve)=>setTimeout(resolve, delay)) // delay with exponential back-off for each retry
|
|
259
|
-
;
|
|
260
|
-
console.log(`Retrying... Attempt number ${retryCount + 1}`);
|
|
261
|
-
return checkConnectorUrl(retryCount + 1);
|
|
262
|
-
}
|
|
263
|
-
const errorMessage = getErrorMessage(responseStatus, error);
|
|
264
|
-
const errorToSend = error || new Error(errorMessage);
|
|
265
|
-
const context = {
|
|
266
|
-
connectorUrl,
|
|
267
|
-
responseStatus
|
|
268
|
-
};
|
|
269
|
-
if (responseStatus !== 404) errorReporter.send(errorToSend, context);
|
|
270
|
-
return {
|
|
271
|
-
checked: true,
|
|
272
|
-
error: errorMessage
|
|
273
|
-
};
|
|
274
|
-
}, [
|
|
275
|
-
connectorUrl
|
|
276
|
-
]);
|
|
277
285
|
useEffect(()=>{
|
|
278
286
|
if (preFlightCheck.checked) return;
|
|
279
287
|
const fetchDataAndSetState = async ()=>{
|
|
280
|
-
const connectorUrlStatus = await checkConnectorUrl();
|
|
288
|
+
const connectorUrlStatus = await checkConnectorUrl(connectorUrl);
|
|
281
289
|
setPreFlightCheck(connectorUrlStatus);
|
|
282
290
|
};
|
|
283
291
|
fetchDataAndSetState();
|
|
284
292
|
}, [
|
|
285
|
-
|
|
293
|
+
connectorUrl,
|
|
286
294
|
preFlightCheck
|
|
287
295
|
]);
|
|
288
296
|
const initInjectedJavaScript = useCallback(()=>{
|
|
@@ -312,7 +320,7 @@ const QuilttConnector = ({ connectorId, connectionId, institution, oauthRedirect
|
|
|
312
320
|
]);
|
|
313
321
|
// allowedListUrl & shouldRender ensure we are only rendering Quiltt, MX and Plaid content in Webview
|
|
314
322
|
// For other urls, we assume those are bank urls, which needs to be handle in external browser.
|
|
315
|
-
//
|
|
323
|
+
// TODO: Convert it to a list from Quiltt Server to prevent MX/ Plaid changes.
|
|
316
324
|
const allowedListUrl = useMemo(()=>[
|
|
317
325
|
'quiltt.app',
|
|
318
326
|
'quiltt.dev',
|
|
@@ -334,13 +342,6 @@ const QuilttConnector = ({ connectorId, connectionId, institution, oauthRedirect
|
|
|
334
342
|
const script = 'localStorage.clear();';
|
|
335
343
|
webViewRef.current?.injectJavaScript(script);
|
|
336
344
|
};
|
|
337
|
-
const handleOAuthUrl = useCallback((oauthUrl)=>{
|
|
338
|
-
if (oauthUrl.protocol !== 'https:') {
|
|
339
|
-
console.log(`handleOAuthUrl - Skipping non https url - ${oauthUrl.href}`);
|
|
340
|
-
return;
|
|
341
|
-
}
|
|
342
|
-
Linking.openURL(oauthUrl.href);
|
|
343
|
-
}, []);
|
|
344
345
|
const handleQuilttEvent = useCallback((url)=>{
|
|
345
346
|
url.searchParams.delete('source');
|
|
346
347
|
url.searchParams.append('connectorId', connectorId);
|
|
@@ -381,7 +382,6 @@ const QuilttConnector = ({ connectorId, connectionId, institution, oauthRedirect
|
|
|
381
382
|
}
|
|
382
383
|
}, [
|
|
383
384
|
connectorId,
|
|
384
|
-
handleOAuthUrl,
|
|
385
385
|
initInjectedJavaScript,
|
|
386
386
|
onEvent,
|
|
387
387
|
onExit,
|
|
@@ -402,22 +402,26 @@ const QuilttConnector = ({ connectorId, connectionId, institution, oauthRedirect
|
|
|
402
402
|
handleOAuthUrl(url);
|
|
403
403
|
return false;
|
|
404
404
|
}, [
|
|
405
|
-
handleOAuthUrl,
|
|
406
405
|
handleQuilttEvent,
|
|
407
406
|
isQuilttEvent,
|
|
408
407
|
shouldRender
|
|
409
408
|
]);
|
|
410
|
-
if (!preFlightCheck.checked) return /*#__PURE__*/ jsx(LoadingScreen, {
|
|
409
|
+
if (!preFlightCheck.checked) return /*#__PURE__*/ jsx(LoadingScreen, {
|
|
410
|
+
testId: "loading-screen"
|
|
411
|
+
});
|
|
411
412
|
if (preFlightCheck.error) return /*#__PURE__*/ jsx(ErrorScreen, {
|
|
413
|
+
testId: "error-screen",
|
|
412
414
|
error: preFlightCheck.error,
|
|
413
415
|
cta: ()=>onExitError?.({
|
|
414
416
|
connectorId
|
|
415
417
|
})
|
|
416
418
|
});
|
|
417
419
|
return /*#__PURE__*/ jsx(AndroidSafeAreaView, {
|
|
420
|
+
testId: testId,
|
|
418
421
|
children: /*#__PURE__*/ jsx(WebView, {
|
|
422
|
+
testID: "webview",
|
|
419
423
|
ref: webViewRef,
|
|
420
|
-
// Plaid
|
|
424
|
+
// Plaid keeps sending window.location = 'about:srcdoc' and causes some noise in RN
|
|
421
425
|
// All whitelists are now handled in requestHandler, handleQuilttEvent and handleOAuthUrl
|
|
422
426
|
originWhitelist: [
|
|
423
427
|
'*'
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@quiltt/react-native",
|
|
3
|
-
"version": "3.6.
|
|
3
|
+
"version": "3.6.4",
|
|
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": {
|
|
@@ -11,13 +11,14 @@
|
|
|
11
11
|
"license": "MIT",
|
|
12
12
|
"type": "module",
|
|
13
13
|
"exports": {
|
|
14
|
-
"
|
|
15
|
-
"
|
|
16
|
-
|
|
14
|
+
"import": {
|
|
15
|
+
"default": "./dist/index.js"
|
|
16
|
+
},
|
|
17
|
+
"require": {
|
|
18
|
+
"default": "./dist/index.cjs"
|
|
17
19
|
}
|
|
18
20
|
},
|
|
19
21
|
"main": "./dist/index.js",
|
|
20
|
-
"module": "./dist/index.js",
|
|
21
22
|
"types": "./dist/index.d.ts",
|
|
22
23
|
"files": [
|
|
23
24
|
"dist/**",
|
|
@@ -26,14 +27,14 @@
|
|
|
26
27
|
],
|
|
27
28
|
"dependencies": {
|
|
28
29
|
"@honeybadger-io/core": "6.6.0",
|
|
29
|
-
"@quiltt/core": "3.6.
|
|
30
|
-
"@quiltt/react": "3.6.
|
|
30
|
+
"@quiltt/core": "3.6.4",
|
|
31
|
+
"@quiltt/react": "3.6.4"
|
|
31
32
|
},
|
|
32
33
|
"devDependencies": {
|
|
33
34
|
"@apollo/client": "3.9.9",
|
|
34
35
|
"@trivago/prettier-plugin-sort-imports": "4.1.1",
|
|
35
36
|
"@types/base-64": "0.1.0",
|
|
36
|
-
"@types/node": "20.12.
|
|
37
|
+
"@types/node": "20.12.7",
|
|
37
38
|
"@types/react": "18.2.73",
|
|
38
39
|
"@types/react-native": "0.72.5",
|
|
39
40
|
"@typescript-eslint/eslint-plugin": "5.60.1",
|
|
@@ -2,8 +2,14 @@ import { PropsWithChildren } from 'react'
|
|
|
2
2
|
|
|
3
3
|
import { SafeAreaView, StyleSheet, Platform, StatusBar } from 'react-native'
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
type AndroidSafeAreaViewProps = PropsWithChildren & {
|
|
6
|
+
testId?: string
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export const AndroidSafeAreaView = ({ testId, children }: AndroidSafeAreaViewProps) => (
|
|
10
|
+
<SafeAreaView testID={testId} style={styles.AndroidSafeArea}>
|
|
11
|
+
{children}
|
|
12
|
+
</SafeAreaView>
|
|
7
13
|
)
|
|
8
14
|
|
|
9
15
|
const styles = StyleSheet.create({
|
|
@@ -2,13 +2,14 @@ import { View, Text, Pressable, StyleSheet } from 'react-native'
|
|
|
2
2
|
|
|
3
3
|
import { AndroidSafeAreaView } from './AndroidSafeAreaView'
|
|
4
4
|
|
|
5
|
-
type
|
|
5
|
+
type ErrorScreenProps = {
|
|
6
|
+
testId?: string
|
|
6
7
|
error: string
|
|
7
8
|
cta: () => void
|
|
8
9
|
}
|
|
9
10
|
|
|
10
|
-
export const ErrorScreen = ({ error, cta }:
|
|
11
|
-
<AndroidSafeAreaView>
|
|
11
|
+
export const ErrorScreen = ({ testId, error, cta }: ErrorScreenProps) => (
|
|
12
|
+
<AndroidSafeAreaView testId={testId}>
|
|
12
13
|
<View style={[styles.container, styles.padding]}>
|
|
13
14
|
<View style={{ flex: 1, justifyContent: 'center' }}>
|
|
14
15
|
<View
|
|
@@ -2,10 +2,14 @@ import { ActivityIndicator, View } from 'react-native'
|
|
|
2
2
|
|
|
3
3
|
import { AndroidSafeAreaView } from './AndroidSafeAreaView'
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
type LoadingScreenProps = {
|
|
6
|
+
testId?: string
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export const LoadingScreen = ({ testId }: LoadingScreenProps) => (
|
|
10
|
+
<AndroidSafeAreaView testId={testId}>
|
|
7
11
|
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
|
|
8
|
-
<ActivityIndicator size="large" color="#
|
|
12
|
+
<ActivityIndicator testID="activity-indicator" size="large" color="#5928A3" />
|
|
9
13
|
</View>
|
|
10
14
|
</AndroidSafeAreaView>
|
|
11
15
|
)
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
|
2
2
|
|
|
3
|
-
import { Linking, Platform } from 'react-native'
|
|
4
3
|
// React Native's URL implementation is incomplete
|
|
5
4
|
// https://github.com/facebook/react-native/issues/16434
|
|
6
5
|
import { URL } from 'react-native-url-polyfill'
|
|
@@ -14,29 +13,23 @@ import {
|
|
|
14
13
|
useQuilttSession,
|
|
15
14
|
} from '@quiltt/react'
|
|
16
15
|
|
|
17
|
-
import {
|
|
18
|
-
import { version } from '../version'
|
|
16
|
+
import { version } from '@/version'
|
|
19
17
|
import { AndroidSafeAreaView } from './AndroidSafeAreaView'
|
|
20
18
|
import { ErrorScreen } from './ErrorScreen'
|
|
21
19
|
import { LoadingScreen } from './LoadingScreen'
|
|
22
|
-
|
|
23
|
-
|
|
20
|
+
import { checkConnectorUrl, handleOAuthUrl } from '@/utils'
|
|
21
|
+
import type { PreFlightCheck } from '@/utils'
|
|
24
22
|
|
|
25
23
|
type QuilttConnectorProps = {
|
|
24
|
+
testId?: string
|
|
26
25
|
connectorId: string
|
|
27
26
|
connectionId?: string
|
|
28
27
|
institution?: string
|
|
29
28
|
oauthRedirectUrl: string
|
|
30
29
|
} & ConnectorSDKCallbacks
|
|
31
30
|
|
|
32
|
-
type PreFlightCheck = {
|
|
33
|
-
checked: boolean
|
|
34
|
-
error?: string
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
const PREFLIGHT_RETRY_COUNT = 3
|
|
38
|
-
|
|
39
31
|
const QuilttConnector = ({
|
|
32
|
+
testId,
|
|
40
33
|
connectorId,
|
|
41
34
|
connectionId,
|
|
42
35
|
institution,
|
|
@@ -63,52 +56,14 @@ const QuilttConnector = ({
|
|
|
63
56
|
}, [connectorId, encodedOAuthRedirectUrl])
|
|
64
57
|
const [preFlightCheck, setPreFlightCheck] = useState<PreFlightCheck>({ checked: false })
|
|
65
58
|
|
|
66
|
-
const checkConnectorUrl = useCallback(
|
|
67
|
-
async (retryCount = 0): Promise<PreFlightCheck> => {
|
|
68
|
-
let responseStatus
|
|
69
|
-
let error
|
|
70
|
-
let errorOccurred = false
|
|
71
|
-
try {
|
|
72
|
-
const response = await fetch(connectorUrl)
|
|
73
|
-
if (!response.ok) {
|
|
74
|
-
console.error(`The URL ${connectorUrl} is not routable.`)
|
|
75
|
-
responseStatus = response.status
|
|
76
|
-
errorOccurred = true
|
|
77
|
-
} else {
|
|
78
|
-
console.log(`The URL ${connectorUrl} is routable.`)
|
|
79
|
-
return { checked: true }
|
|
80
|
-
}
|
|
81
|
-
} catch (e) {
|
|
82
|
-
error = e
|
|
83
|
-
console.error(`An error occurred while checking the connector URL: ${error}`)
|
|
84
|
-
errorOccurred = true
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
// Retry logic in case of error or response not OK
|
|
88
|
-
if (errorOccurred && retryCount < PREFLIGHT_RETRY_COUNT) {
|
|
89
|
-
const delay = 50 * Math.pow(2, retryCount) // Exponential back-off
|
|
90
|
-
await new Promise((resolve) => setTimeout(resolve, delay)) // delay with exponential back-off for each retry
|
|
91
|
-
console.log(`Retrying... Attempt number ${retryCount + 1}`)
|
|
92
|
-
return checkConnectorUrl(retryCount + 1)
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
const errorMessage = getErrorMessage(responseStatus, error as Error)
|
|
96
|
-
const errorToSend = (error as Error) || new Error(errorMessage)
|
|
97
|
-
const context = { connectorUrl, responseStatus }
|
|
98
|
-
if (responseStatus !== 404) errorReporter.send(errorToSend, context)
|
|
99
|
-
return { checked: true, error: errorMessage }
|
|
100
|
-
},
|
|
101
|
-
[connectorUrl]
|
|
102
|
-
)
|
|
103
|
-
|
|
104
59
|
useEffect(() => {
|
|
105
60
|
if (preFlightCheck.checked) return
|
|
106
61
|
const fetchDataAndSetState = async () => {
|
|
107
|
-
const connectorUrlStatus = await checkConnectorUrl()
|
|
62
|
+
const connectorUrlStatus = await checkConnectorUrl(connectorUrl)
|
|
108
63
|
setPreFlightCheck(connectorUrlStatus)
|
|
109
64
|
}
|
|
110
65
|
fetchDataAndSetState()
|
|
111
|
-
}, [
|
|
66
|
+
}, [connectorUrl, preFlightCheck])
|
|
112
67
|
|
|
113
68
|
const initInjectedJavaScript = useCallback(() => {
|
|
114
69
|
const script = `\
|
|
@@ -133,7 +88,7 @@ const QuilttConnector = ({
|
|
|
133
88
|
|
|
134
89
|
// allowedListUrl & shouldRender ensure we are only rendering Quiltt, MX and Plaid content in Webview
|
|
135
90
|
// For other urls, we assume those are bank urls, which needs to be handle in external browser.
|
|
136
|
-
//
|
|
91
|
+
// TODO: Convert it to a list from Quiltt Server to prevent MX/ Plaid changes.
|
|
137
92
|
const allowedListUrl = useMemo(
|
|
138
93
|
() => [
|
|
139
94
|
'quiltt.app',
|
|
@@ -162,14 +117,6 @@ const QuilttConnector = ({
|
|
|
162
117
|
webViewRef.current?.injectJavaScript(script)
|
|
163
118
|
}
|
|
164
119
|
|
|
165
|
-
const handleOAuthUrl = useCallback((oauthUrl: URL) => {
|
|
166
|
-
if (oauthUrl.protocol !== 'https:') {
|
|
167
|
-
console.log(`handleOAuthUrl - Skipping non https url - ${oauthUrl.href}`)
|
|
168
|
-
return
|
|
169
|
-
}
|
|
170
|
-
Linking.openURL(oauthUrl.href)
|
|
171
|
-
}, [])
|
|
172
|
-
|
|
173
120
|
const handleQuilttEvent = useCallback(
|
|
174
121
|
(url: URL) => {
|
|
175
122
|
url.searchParams.delete('source')
|
|
@@ -202,7 +149,7 @@ const QuilttConnector = ({
|
|
|
202
149
|
onExitSuccess?.(metadata)
|
|
203
150
|
break
|
|
204
151
|
case 'Authenticate':
|
|
205
|
-
//
|
|
152
|
+
// TODO: handle Authenticate
|
|
206
153
|
break
|
|
207
154
|
case 'OauthRequested':
|
|
208
155
|
handleOAuthUrl(new URL(url.searchParams.get('oauthUrl') as string))
|
|
@@ -214,7 +161,6 @@ const QuilttConnector = ({
|
|
|
214
161
|
},
|
|
215
162
|
[
|
|
216
163
|
connectorId,
|
|
217
|
-
handleOAuthUrl,
|
|
218
164
|
initInjectedJavaScript,
|
|
219
165
|
onEvent,
|
|
220
166
|
onExit,
|
|
@@ -239,18 +185,25 @@ const QuilttConnector = ({
|
|
|
239
185
|
handleOAuthUrl(url)
|
|
240
186
|
return false
|
|
241
187
|
},
|
|
242
|
-
[
|
|
188
|
+
[handleQuilttEvent, isQuilttEvent, shouldRender]
|
|
243
189
|
)
|
|
244
190
|
|
|
245
|
-
if (!preFlightCheck.checked) return <LoadingScreen />
|
|
191
|
+
if (!preFlightCheck.checked) return <LoadingScreen testId="loading-screen" />
|
|
246
192
|
if (preFlightCheck.error)
|
|
247
|
-
return
|
|
193
|
+
return (
|
|
194
|
+
<ErrorScreen
|
|
195
|
+
testId="error-screen"
|
|
196
|
+
error={preFlightCheck.error}
|
|
197
|
+
cta={() => onExitError?.({ connectorId })}
|
|
198
|
+
/>
|
|
199
|
+
)
|
|
248
200
|
|
|
249
201
|
return (
|
|
250
|
-
<AndroidSafeAreaView>
|
|
202
|
+
<AndroidSafeAreaView testId={testId}>
|
|
251
203
|
<WebView
|
|
204
|
+
testID="webview"
|
|
252
205
|
ref={webViewRef}
|
|
253
|
-
// Plaid
|
|
206
|
+
// Plaid keeps sending window.location = 'about:srcdoc' and causes some noise in RN
|
|
254
207
|
// All whitelists are now handled in requestHandler, handleQuilttEvent and handleOAuthUrl
|
|
255
208
|
originWhitelist={['*']}
|
|
256
209
|
source={{ uri: connectorUrl }}
|