@quiltt/react-native 3.9.5 → 3.9.7
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.d.ts +4 -1
- package/dist/index.js +122 -14
- package/package.json +4 -4
- package/src/components/QuilttConnector.tsx +91 -17
- package/src/utils/index.ts +1 -0
- package/src/utils/url.ts +89 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,25 @@
|
|
|
1
1
|
# @quiltt/react-native
|
|
2
2
|
|
|
3
|
+
## 3.9.7
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [#330](https://github.com/quiltt/quiltt-js/pull/330) [`e7b8e74`](https://github.com/quiltt/quiltt-js/commit/e7b8e74613f7725c6f2653be6d8ac0e06cce661d) Thanks [@zubairaziz](https://github.com/zubairaziz)! - Make OAuth Handling Safer
|
|
8
|
+
|
|
9
|
+
- Updated dependencies [[`e7b8e74`](https://github.com/quiltt/quiltt-js/commit/e7b8e74613f7725c6f2653be6d8ac0e06cce661d)]:
|
|
10
|
+
- @quiltt/core@3.9.7
|
|
11
|
+
- @quiltt/react@3.9.7
|
|
12
|
+
|
|
13
|
+
## 3.9.6
|
|
14
|
+
|
|
15
|
+
### Patch Changes
|
|
16
|
+
|
|
17
|
+
- [#328](https://github.com/quiltt/quiltt-js/pull/328) [`6b8751c`](https://github.com/quiltt/quiltt-js/commit/6b8751c981e9e74b347227bc9f427585d21870cd) Thanks [@zubairaziz](https://github.com/zubairaziz)! - Updated QuilttConnector OAuth handler
|
|
18
|
+
|
|
19
|
+
- Updated dependencies [[`6b8751c`](https://github.com/quiltt/quiltt-js/commit/6b8751c981e9e74b347227bc9f427585d21870cd)]:
|
|
20
|
+
- @quiltt/react@3.9.6
|
|
21
|
+
- @quiltt/core@3.9.6
|
|
22
|
+
|
|
3
23
|
## 3.9.5
|
|
4
24
|
|
|
5
25
|
### Patch Changes
|
package/dist/index.d.ts
CHANGED
|
@@ -9,7 +9,10 @@ type PreFlightCheck = {
|
|
|
9
9
|
error?: string;
|
|
10
10
|
};
|
|
11
11
|
declare const checkConnectorUrl: (connectorUrl: string, retryCount?: number) => Promise<PreFlightCheck>;
|
|
12
|
-
|
|
12
|
+
/**
|
|
13
|
+
* Handle opening OAuth URLs with proper encoding detection and normalization
|
|
14
|
+
*/
|
|
15
|
+
declare const handleOAuthUrl: (oauthUrl: URL | string | null | undefined) => void;
|
|
13
16
|
type QuilttConnectorProps = {
|
|
14
17
|
testId?: string;
|
|
15
18
|
connectorId: string;
|
package/dist/index.js
CHANGED
|
@@ -9,7 +9,7 @@ import { URL } from 'react-native-url-polyfill';
|
|
|
9
9
|
import { WebView } from 'react-native-webview';
|
|
10
10
|
import { generateStackTrace, makeBacktrace, getCauses } from '@honeybadger-io/core/build/src/util';
|
|
11
11
|
|
|
12
|
-
var version = "3.9.
|
|
12
|
+
var version = "3.9.7";
|
|
13
13
|
|
|
14
14
|
const ErrorReporterConfig = {
|
|
15
15
|
honeybadger_api_key: 'undefined'
|
|
@@ -99,6 +99,52 @@ const getErrorMessage = (responseStatus, error)=>{
|
|
|
99
99
|
return responseStatus ? `The URL is not routable. Response status: ${responseStatus}` : 'An error occurred while checking the connector URL';
|
|
100
100
|
};
|
|
101
101
|
|
|
102
|
+
/**
|
|
103
|
+
* Checks if a string appears to be already URL encoded
|
|
104
|
+
* @param str The string to check
|
|
105
|
+
* @returns boolean indicating if the string appears to be URL encoded
|
|
106
|
+
*/ const isEncoded = (str)=>{
|
|
107
|
+
// Check for typical URL encoding patterns like %20, %3A, etc.
|
|
108
|
+
const hasEncodedChars = /%[0-9A-F]{2}/i.test(str);
|
|
109
|
+
// Check if double encoding has occurred (e.g., %253A instead of %3A)
|
|
110
|
+
const hasDoubleEncoding = /%25[0-9A-F]{2}/i.test(str);
|
|
111
|
+
// If we have encoded chars but no double encoding, it's likely properly encoded
|
|
112
|
+
return hasEncodedChars && !hasDoubleEncoding;
|
|
113
|
+
};
|
|
114
|
+
/**
|
|
115
|
+
* Smart URL encoder that ensures a string is encoded exactly once
|
|
116
|
+
* @param str The string to encode
|
|
117
|
+
* @returns A properly URL encoded string
|
|
118
|
+
*/ const smartEncodeURIComponent = (str)=>{
|
|
119
|
+
if (!str) return str;
|
|
120
|
+
// If it's already encoded, return as is
|
|
121
|
+
if (isEncoded(str)) {
|
|
122
|
+
console.log('URL already encoded, skipping encoding:', str);
|
|
123
|
+
return str;
|
|
124
|
+
}
|
|
125
|
+
// Otherwise, encode it
|
|
126
|
+
const encoded = encodeURIComponent(str);
|
|
127
|
+
console.log('URL encoded from:', str, 'to:', encoded);
|
|
128
|
+
return encoded;
|
|
129
|
+
};
|
|
130
|
+
/**
|
|
131
|
+
* Checks if a string appears to be double-encoded
|
|
132
|
+
*/ const isDoubleEncoded = (str)=>{
|
|
133
|
+
if (!str) return false;
|
|
134
|
+
return /%25[0-9A-F]{2}/i.test(str);
|
|
135
|
+
};
|
|
136
|
+
/**
|
|
137
|
+
* Normalizes a URL string by decoding it once if it appears to be double-encoded
|
|
138
|
+
*/ const normalizeUrlEncoding = (urlStr)=>{
|
|
139
|
+
if (isDoubleEncoded(urlStr)) {
|
|
140
|
+
console.log('Detected double-encoded URL:', urlStr);
|
|
141
|
+
const normalized = decodeURIComponent(urlStr);
|
|
142
|
+
console.log('Normalized to:', normalized);
|
|
143
|
+
return normalized;
|
|
144
|
+
}
|
|
145
|
+
return urlStr;
|
|
146
|
+
};
|
|
147
|
+
|
|
102
148
|
const AndroidSafeAreaView = ({ testId, children })=>/*#__PURE__*/ jsx(SafeAreaView, {
|
|
103
149
|
testID: testId,
|
|
104
150
|
style: styles$2.AndroidSafeArea,
|
|
@@ -221,19 +267,18 @@ const checkConnectorUrl = async (connectorUrl, retryCount = 0)=>{
|
|
|
221
267
|
const response = await fetch(connectorUrl);
|
|
222
268
|
if (!response.ok) {
|
|
223
269
|
responseStatus = response.status;
|
|
224
|
-
throw new Error(
|
|
270
|
+
throw new Error('Connector URL is not routable.');
|
|
225
271
|
}
|
|
226
|
-
console.log(`The URL ${connectorUrl} is routable.`);
|
|
227
272
|
return {
|
|
228
273
|
checked: true
|
|
229
274
|
};
|
|
230
275
|
} catch (e) {
|
|
231
276
|
error = e;
|
|
232
|
-
console.error(
|
|
277
|
+
console.error('Failed to connect to connector URL');
|
|
233
278
|
if (retryCount < PREFLIGHT_RETRY_COUNT) {
|
|
234
279
|
const delay = 50 * 2 ** retryCount;
|
|
235
280
|
await new Promise((resolve)=>setTimeout(resolve, delay));
|
|
236
|
-
console.log(`Retrying... Attempt
|
|
281
|
+
console.log(`Retrying connection... Attempt ${retryCount + 1}`);
|
|
237
282
|
return checkConnectorUrl(connectorUrl, retryCount + 1);
|
|
238
283
|
}
|
|
239
284
|
const errorMessage = getErrorMessage(responseStatus, error);
|
|
@@ -249,9 +294,37 @@ const checkConnectorUrl = async (connectorUrl, retryCount = 0)=>{
|
|
|
249
294
|
};
|
|
250
295
|
}
|
|
251
296
|
};
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
297
|
+
/**
|
|
298
|
+
* Handle opening OAuth URLs with proper encoding detection and normalization
|
|
299
|
+
*/ const handleOAuthUrl = (oauthUrl)=>{
|
|
300
|
+
try {
|
|
301
|
+
// Throw error if oauthUrl is null or undefined
|
|
302
|
+
if (oauthUrl == null) {
|
|
303
|
+
throw new Error('OAuth URL missing');
|
|
304
|
+
}
|
|
305
|
+
// Convert to string if it's a URL object
|
|
306
|
+
const urlString = oauthUrl.toString();
|
|
307
|
+
// Throw error if the resulting string is empty
|
|
308
|
+
if (!urlString || urlString.trim() === '') {
|
|
309
|
+
throw new Error('Empty OAuth URL');
|
|
310
|
+
}
|
|
311
|
+
// Normalize the URL encoding
|
|
312
|
+
const normalizedUrl = normalizeUrlEncoding(urlString);
|
|
313
|
+
// Open the normalized URL
|
|
314
|
+
Linking.openURL(normalizedUrl);
|
|
315
|
+
} catch (error) {
|
|
316
|
+
console.error('OAuth URL handling error');
|
|
317
|
+
// Only try the fallback if oauthUrl is not null
|
|
318
|
+
if (oauthUrl != null) {
|
|
319
|
+
try {
|
|
320
|
+
const fallbackUrl = typeof oauthUrl === 'string' ? oauthUrl : oauthUrl.toString();
|
|
321
|
+
console.log('Attempting fallback OAuth opening');
|
|
322
|
+
Linking.openURL(fallbackUrl);
|
|
323
|
+
} catch (fallbackError) {
|
|
324
|
+
console.error('Fallback OAuth opening failed');
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
}
|
|
255
328
|
};
|
|
256
329
|
const QuilttConnector = ({ testId, connectorId, connectionId, institution, oauthRedirectUrl, onEvent, onLoad, onExit, onExitSuccess, onExitAbort, onExitError })=>{
|
|
257
330
|
const webViewRef = useRef(null);
|
|
@@ -277,18 +350,30 @@ const QuilttConnector = ({ testId, connectorId, connectionId, institution, oauth
|
|
|
277
350
|
webViewRef.current?.injectJavaScript(disableHeaderScrollScript);
|
|
278
351
|
}
|
|
279
352
|
}, []);
|
|
280
|
-
|
|
353
|
+
// Ensure oauthRedirectUrl is encoded properly - only once
|
|
354
|
+
const safeOAuthRedirectUrl = useMemo(()=>{
|
|
355
|
+
return smartEncodeURIComponent(oauthRedirectUrl);
|
|
356
|
+
}, [
|
|
281
357
|
oauthRedirectUrl
|
|
282
358
|
]);
|
|
283
359
|
const connectorUrl = useMemo(()=>{
|
|
284
360
|
const url = new URL(`https://${connectorId}.quiltt.app`);
|
|
361
|
+
// For normal parameters, just append them directly
|
|
285
362
|
url.searchParams.append('mode', 'webview');
|
|
286
|
-
url.searchParams.append('oauth_redirect_url', encodedOAuthRedirectUrl);
|
|
287
363
|
url.searchParams.append('agent', `react-native-${version}`);
|
|
364
|
+
// For the oauth_redirect_url, we need to be careful
|
|
365
|
+
// If it's already encoded, we need to decode it once to prevent
|
|
366
|
+
// the automatic encoding that happens with searchParams.append
|
|
367
|
+
if (isEncoded(safeOAuthRedirectUrl)) {
|
|
368
|
+
const decodedOnce = decodeURIComponent(safeOAuthRedirectUrl);
|
|
369
|
+
url.searchParams.append('oauth_redirect_url', decodedOnce);
|
|
370
|
+
} else {
|
|
371
|
+
url.searchParams.append('oauth_redirect_url', safeOAuthRedirectUrl);
|
|
372
|
+
}
|
|
288
373
|
return url.toString();
|
|
289
374
|
}, [
|
|
290
375
|
connectorId,
|
|
291
|
-
|
|
376
|
+
safeOAuthRedirectUrl
|
|
292
377
|
]);
|
|
293
378
|
useEffect(()=>{
|
|
294
379
|
if (preFlightCheck.checked) return;
|
|
@@ -342,35 +427,58 @@ const QuilttConnector = ({ testId, connectorId, connectionId, institution, oauth
|
|
|
342
427
|
const eventType = url.host;
|
|
343
428
|
switch(eventType){
|
|
344
429
|
case 'Load':
|
|
430
|
+
console.log('Event: Load');
|
|
345
431
|
initInjectedJavaScript();
|
|
346
432
|
onEvent?.(ConnectorSDKEventType.Load, metadata);
|
|
347
433
|
onLoad?.(metadata);
|
|
348
434
|
break;
|
|
349
435
|
case 'ExitAbort':
|
|
436
|
+
console.log('Event: ExitAbort');
|
|
350
437
|
clearLocalStorage();
|
|
351
438
|
onEvent?.(ConnectorSDKEventType.ExitAbort, metadata);
|
|
352
439
|
onExit?.(ConnectorSDKEventType.ExitAbort, metadata);
|
|
353
440
|
onExitAbort?.(metadata);
|
|
354
441
|
break;
|
|
355
442
|
case 'ExitError':
|
|
443
|
+
console.log('Event: ExitError');
|
|
356
444
|
clearLocalStorage();
|
|
357
445
|
onEvent?.(ConnectorSDKEventType.ExitError, metadata);
|
|
358
446
|
onExit?.(ConnectorSDKEventType.ExitError, metadata);
|
|
359
447
|
onExitError?.(metadata);
|
|
360
448
|
break;
|
|
361
449
|
case 'ExitSuccess':
|
|
450
|
+
console.log('Event: ExitSuccess');
|
|
362
451
|
clearLocalStorage();
|
|
363
452
|
onEvent?.(ConnectorSDKEventType.ExitSuccess, metadata);
|
|
364
453
|
onExit?.(ConnectorSDKEventType.ExitSuccess, metadata);
|
|
365
454
|
onExitSuccess?.(metadata);
|
|
366
455
|
break;
|
|
367
456
|
case 'Authenticate':
|
|
457
|
+
console.log('Event: Authenticate');
|
|
368
458
|
break;
|
|
369
459
|
case 'OauthRequested':
|
|
370
|
-
|
|
371
|
-
|
|
460
|
+
{
|
|
461
|
+
console.log('Event: OauthRequested');
|
|
462
|
+
const oauthUrl = url.searchParams.get('oauthUrl');
|
|
463
|
+
if (oauthUrl) {
|
|
464
|
+
if (isEncoded(oauthUrl)) {
|
|
465
|
+
try {
|
|
466
|
+
const decodedUrl = decodeURIComponent(oauthUrl);
|
|
467
|
+
handleOAuthUrl(decodedUrl);
|
|
468
|
+
} catch (error) {
|
|
469
|
+
console.error('OAuth URL decoding failed, using original');
|
|
470
|
+
handleOAuthUrl(oauthUrl);
|
|
471
|
+
}
|
|
472
|
+
} else {
|
|
473
|
+
handleOAuthUrl(oauthUrl);
|
|
474
|
+
}
|
|
475
|
+
} else {
|
|
476
|
+
console.error('OAuth URL missing from request');
|
|
477
|
+
}
|
|
478
|
+
break;
|
|
479
|
+
}
|
|
372
480
|
default:
|
|
373
|
-
console.log(
|
|
481
|
+
console.log(`Unhandled event: ${eventType}`);
|
|
374
482
|
break;
|
|
375
483
|
}
|
|
376
484
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@quiltt/react-native",
|
|
3
|
-
"version": "3.9.
|
|
3
|
+
"version": "3.9.7",
|
|
4
4
|
"description": "React Native Components for Quiltt Connector",
|
|
5
5
|
"homepage": "https://github.com/quiltt/quiltt-js/tree/main/packages/react-native#readme",
|
|
6
6
|
"repository": {
|
|
@@ -30,14 +30,14 @@
|
|
|
30
30
|
"dependencies": {
|
|
31
31
|
"@honeybadger-io/core": "6.6.0",
|
|
32
32
|
"lodash.debounce": "4.0.8",
|
|
33
|
-
"@quiltt/
|
|
34
|
-
"@quiltt/
|
|
33
|
+
"@quiltt/core": "3.9.7",
|
|
34
|
+
"@quiltt/react": "3.9.7"
|
|
35
35
|
},
|
|
36
36
|
"devDependencies": {
|
|
37
37
|
"@biomejs/biome": "1.9.4",
|
|
38
38
|
"@types/base-64": "1.0.2",
|
|
39
39
|
"@types/lodash.debounce": "4.0.9",
|
|
40
|
-
"@types/node": "22.13.
|
|
40
|
+
"@types/node": "22.13.10",
|
|
41
41
|
"@types/react": "18.3.12",
|
|
42
42
|
"base-64": "1.0.0",
|
|
43
43
|
"bunchee": "6.3.4",
|
|
@@ -8,7 +8,13 @@ import type { ShouldStartLoadRequest } from 'react-native-webview/lib/WebViewTyp
|
|
|
8
8
|
import { ConnectorSDKEventType, useQuilttSession } from '@quiltt/react'
|
|
9
9
|
import type { ConnectorSDKCallbackMetadata, ConnectorSDKCallbacks } from '@quiltt/react'
|
|
10
10
|
|
|
11
|
-
import {
|
|
11
|
+
import {
|
|
12
|
+
ErrorReporter,
|
|
13
|
+
getErrorMessage,
|
|
14
|
+
isEncoded,
|
|
15
|
+
normalizeUrlEncoding,
|
|
16
|
+
smartEncodeURIComponent,
|
|
17
|
+
} from '@/utils'
|
|
12
18
|
import { version } from '@/version'
|
|
13
19
|
import { AndroidSafeAreaView } from './AndroidSafeAreaView'
|
|
14
20
|
import { ErrorScreen } from './ErrorScreen'
|
|
@@ -32,18 +38,17 @@ export const checkConnectorUrl = async (
|
|
|
32
38
|
const response = await fetch(connectorUrl)
|
|
33
39
|
if (!response.ok) {
|
|
34
40
|
responseStatus = response.status
|
|
35
|
-
throw new Error(
|
|
41
|
+
throw new Error('Connector URL is not routable.')
|
|
36
42
|
}
|
|
37
|
-
console.log(`The URL ${connectorUrl} is routable.`)
|
|
38
43
|
return { checked: true }
|
|
39
44
|
} catch (e) {
|
|
40
45
|
error = e as Error
|
|
41
|
-
console.error(
|
|
46
|
+
console.error('Failed to connect to connector URL')
|
|
42
47
|
|
|
43
48
|
if (retryCount < PREFLIGHT_RETRY_COUNT) {
|
|
44
49
|
const delay = 50 * 2 ** retryCount
|
|
45
50
|
await new Promise((resolve) => setTimeout(resolve, delay))
|
|
46
|
-
console.log(`Retrying... Attempt
|
|
51
|
+
console.log(`Retrying connection... Attempt ${retryCount + 1}`)
|
|
47
52
|
return checkConnectorUrl(connectorUrl, retryCount + 1)
|
|
48
53
|
}
|
|
49
54
|
const errorMessage = getErrorMessage(responseStatus, error as Error)
|
|
@@ -54,9 +59,43 @@ export const checkConnectorUrl = async (
|
|
|
54
59
|
}
|
|
55
60
|
}
|
|
56
61
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
62
|
+
/**
|
|
63
|
+
* Handle opening OAuth URLs with proper encoding detection and normalization
|
|
64
|
+
*/
|
|
65
|
+
export const handleOAuthUrl = (oauthUrl: URL | string | null | undefined) => {
|
|
66
|
+
try {
|
|
67
|
+
// Throw error if oauthUrl is null or undefined
|
|
68
|
+
if (oauthUrl == null) {
|
|
69
|
+
throw new Error('OAuth URL missing')
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Convert to string if it's a URL object
|
|
73
|
+
const urlString = oauthUrl.toString()
|
|
74
|
+
|
|
75
|
+
// Throw error if the resulting string is empty
|
|
76
|
+
if (!urlString || urlString.trim() === '') {
|
|
77
|
+
throw new Error('Empty OAuth URL')
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Normalize the URL encoding
|
|
81
|
+
const normalizedUrl = normalizeUrlEncoding(urlString)
|
|
82
|
+
|
|
83
|
+
// Open the normalized URL
|
|
84
|
+
Linking.openURL(normalizedUrl)
|
|
85
|
+
} catch (error) {
|
|
86
|
+
console.error('OAuth URL handling error')
|
|
87
|
+
|
|
88
|
+
// Only try the fallback if oauthUrl is not null
|
|
89
|
+
if (oauthUrl != null) {
|
|
90
|
+
try {
|
|
91
|
+
const fallbackUrl = typeof oauthUrl === 'string' ? oauthUrl : oauthUrl.toString()
|
|
92
|
+
console.log('Attempting fallback OAuth opening')
|
|
93
|
+
Linking.openURL(fallbackUrl)
|
|
94
|
+
} catch (fallbackError) {
|
|
95
|
+
console.error('Fallback OAuth opening failed')
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
60
99
|
}
|
|
61
100
|
|
|
62
101
|
type QuilttConnectorProps = {
|
|
@@ -104,18 +143,30 @@ const QuilttConnector = ({
|
|
|
104
143
|
}
|
|
105
144
|
}, [])
|
|
106
145
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
)
|
|
146
|
+
// Ensure oauthRedirectUrl is encoded properly - only once
|
|
147
|
+
const safeOAuthRedirectUrl = useMemo(() => {
|
|
148
|
+
return smartEncodeURIComponent(oauthRedirectUrl)
|
|
149
|
+
}, [oauthRedirectUrl])
|
|
111
150
|
|
|
112
151
|
const connectorUrl = useMemo(() => {
|
|
113
152
|
const url = new URL(`https://${connectorId}.quiltt.app`)
|
|
153
|
+
|
|
154
|
+
// For normal parameters, just append them directly
|
|
114
155
|
url.searchParams.append('mode', 'webview')
|
|
115
|
-
url.searchParams.append('oauth_redirect_url', encodedOAuthRedirectUrl)
|
|
116
156
|
url.searchParams.append('agent', `react-native-${version}`)
|
|
157
|
+
|
|
158
|
+
// For the oauth_redirect_url, we need to be careful
|
|
159
|
+
// If it's already encoded, we need to decode it once to prevent
|
|
160
|
+
// the automatic encoding that happens with searchParams.append
|
|
161
|
+
if (isEncoded(safeOAuthRedirectUrl)) {
|
|
162
|
+
const decodedOnce = decodeURIComponent(safeOAuthRedirectUrl)
|
|
163
|
+
url.searchParams.append('oauth_redirect_url', decodedOnce)
|
|
164
|
+
} else {
|
|
165
|
+
url.searchParams.append('oauth_redirect_url', safeOAuthRedirectUrl)
|
|
166
|
+
}
|
|
167
|
+
|
|
117
168
|
return url.toString()
|
|
118
|
-
}, [connectorId,
|
|
169
|
+
}, [connectorId, safeOAuthRedirectUrl])
|
|
119
170
|
|
|
120
171
|
useEffect(() => {
|
|
121
172
|
if (preFlightCheck.checked) return
|
|
@@ -166,36 +217,59 @@ const QuilttConnector = ({
|
|
|
166
217
|
const eventType = url.host
|
|
167
218
|
switch (eventType) {
|
|
168
219
|
case 'Load':
|
|
220
|
+
console.log('Event: Load')
|
|
169
221
|
initInjectedJavaScript()
|
|
170
222
|
onEvent?.(ConnectorSDKEventType.Load, metadata)
|
|
171
223
|
onLoad?.(metadata)
|
|
172
224
|
break
|
|
173
225
|
case 'ExitAbort':
|
|
226
|
+
console.log('Event: ExitAbort')
|
|
174
227
|
clearLocalStorage()
|
|
175
228
|
onEvent?.(ConnectorSDKEventType.ExitAbort, metadata)
|
|
176
229
|
onExit?.(ConnectorSDKEventType.ExitAbort, metadata)
|
|
177
230
|
onExitAbort?.(metadata)
|
|
178
231
|
break
|
|
179
232
|
case 'ExitError':
|
|
233
|
+
console.log('Event: ExitError')
|
|
180
234
|
clearLocalStorage()
|
|
181
235
|
onEvent?.(ConnectorSDKEventType.ExitError, metadata)
|
|
182
236
|
onExit?.(ConnectorSDKEventType.ExitError, metadata)
|
|
183
237
|
onExitError?.(metadata)
|
|
184
238
|
break
|
|
185
239
|
case 'ExitSuccess':
|
|
240
|
+
console.log('Event: ExitSuccess')
|
|
186
241
|
clearLocalStorage()
|
|
187
242
|
onEvent?.(ConnectorSDKEventType.ExitSuccess, metadata)
|
|
188
243
|
onExit?.(ConnectorSDKEventType.ExitSuccess, metadata)
|
|
189
244
|
onExitSuccess?.(metadata)
|
|
190
245
|
break
|
|
191
246
|
case 'Authenticate':
|
|
247
|
+
console.log('Event: Authenticate')
|
|
192
248
|
// TODO: handle Authenticate
|
|
193
249
|
break
|
|
194
|
-
case 'OauthRequested':
|
|
195
|
-
|
|
250
|
+
case 'OauthRequested': {
|
|
251
|
+
console.log('Event: OauthRequested')
|
|
252
|
+
const oauthUrl = url.searchParams.get('oauthUrl')
|
|
253
|
+
|
|
254
|
+
if (oauthUrl) {
|
|
255
|
+
if (isEncoded(oauthUrl)) {
|
|
256
|
+
try {
|
|
257
|
+
const decodedUrl = decodeURIComponent(oauthUrl)
|
|
258
|
+
handleOAuthUrl(decodedUrl)
|
|
259
|
+
} catch (error) {
|
|
260
|
+
console.error('OAuth URL decoding failed, using original')
|
|
261
|
+
handleOAuthUrl(oauthUrl)
|
|
262
|
+
}
|
|
263
|
+
} else {
|
|
264
|
+
handleOAuthUrl(oauthUrl)
|
|
265
|
+
}
|
|
266
|
+
} else {
|
|
267
|
+
console.error('OAuth URL missing from request')
|
|
268
|
+
}
|
|
196
269
|
break
|
|
270
|
+
}
|
|
197
271
|
default:
|
|
198
|
-
console.log(
|
|
272
|
+
console.log(`Unhandled event: ${eventType}`)
|
|
199
273
|
break
|
|
200
274
|
}
|
|
201
275
|
})
|
package/src/utils/index.ts
CHANGED
package/src/utils/url.ts
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Checks if a string appears to be already URL encoded
|
|
3
|
+
* @param str The string to check
|
|
4
|
+
* @returns boolean indicating if the string appears to be URL encoded
|
|
5
|
+
*/
|
|
6
|
+
export const isEncoded = (str: string): boolean => {
|
|
7
|
+
// Check for typical URL encoding patterns like %20, %3A, etc.
|
|
8
|
+
const hasEncodedChars = /%[0-9A-F]{2}/i.test(str)
|
|
9
|
+
|
|
10
|
+
// Check if double encoding has occurred (e.g., %253A instead of %3A)
|
|
11
|
+
const hasDoubleEncoding = /%25[0-9A-F]{2}/i.test(str)
|
|
12
|
+
|
|
13
|
+
// If we have encoded chars but no double encoding, it's likely properly encoded
|
|
14
|
+
return hasEncodedChars && !hasDoubleEncoding
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Smart URL encoder that ensures a string is encoded exactly once
|
|
19
|
+
* @param str The string to encode
|
|
20
|
+
* @returns A properly URL encoded string
|
|
21
|
+
*/
|
|
22
|
+
export const smartEncodeURIComponent = (str: string): string => {
|
|
23
|
+
if (!str) return str
|
|
24
|
+
|
|
25
|
+
// If it's already encoded, return as is
|
|
26
|
+
if (isEncoded(str)) {
|
|
27
|
+
console.log('URL already encoded, skipping encoding:', str)
|
|
28
|
+
return str
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Otherwise, encode it
|
|
32
|
+
const encoded = encodeURIComponent(str)
|
|
33
|
+
console.log('URL encoded from:', str, 'to:', encoded)
|
|
34
|
+
return encoded
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Creates a URL with proper parameter encoding
|
|
39
|
+
* @param baseUrl The base URL string
|
|
40
|
+
* @param params Object containing key-value pairs to be added as search params
|
|
41
|
+
* @returns A properly formatted URL string
|
|
42
|
+
*/
|
|
43
|
+
export const createUrlWithParams = (baseUrl: string, params: Record<string, string>): string => {
|
|
44
|
+
try {
|
|
45
|
+
const url = new URL(baseUrl)
|
|
46
|
+
|
|
47
|
+
// Add each parameter without additional encoding
|
|
48
|
+
// (URLSearchParams.append will encode them automatically)
|
|
49
|
+
Object.entries(params).forEach(([key, value]) => {
|
|
50
|
+
// Skip undefined or null values
|
|
51
|
+
if (value == null) return
|
|
52
|
+
|
|
53
|
+
// For oauth_redirect_url specifically, ensure it's not double encoded
|
|
54
|
+
if (key === 'oauth_redirect_url' && isEncoded(value)) {
|
|
55
|
+
// Decode once to counteract the automatic encoding that will happen
|
|
56
|
+
const decodedOnce = decodeURIComponent(value)
|
|
57
|
+
url.searchParams.append(key, decodedOnce)
|
|
58
|
+
} else {
|
|
59
|
+
url.searchParams.append(key, value)
|
|
60
|
+
}
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
return url.toString()
|
|
64
|
+
} catch (error) {
|
|
65
|
+
console.error('Error creating URL with params:', error)
|
|
66
|
+
return baseUrl
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Checks if a string appears to be double-encoded
|
|
72
|
+
*/
|
|
73
|
+
const isDoubleEncoded = (str: string): boolean => {
|
|
74
|
+
if (!str) return false
|
|
75
|
+
return /%25[0-9A-F]{2}/i.test(str)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Normalizes a URL string by decoding it once if it appears to be double-encoded
|
|
80
|
+
*/
|
|
81
|
+
export const normalizeUrlEncoding = (urlStr: string): string => {
|
|
82
|
+
if (isDoubleEncoded(urlStr)) {
|
|
83
|
+
console.log('Detected double-encoded URL:', urlStr)
|
|
84
|
+
const normalized = decodeURIComponent(urlStr)
|
|
85
|
+
console.log('Normalized to:', normalized)
|
|
86
|
+
return normalized
|
|
87
|
+
}
|
|
88
|
+
return urlStr
|
|
89
|
+
}
|