-
Notifications
You must be signed in to change notification settings - Fork 9
/
Copy pathErrorDecoder.tsx
112 lines (98 loc) · 3.23 KB
/
ErrorDecoder.tsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
import {useEffect, useState} from 'react';
import {useErrorDecoderParams} from '../ErrorDecoderContext';
import cn from 'classnames';
function replaceArgs(
msg: string,
argList: Array<string | undefined>,
replacer = '[missing argument]'
): string {
let argIdx = 0;
return msg.replace(/%s/g, function () {
const arg = argList[argIdx++];
// arg can be an empty string: ?args[0]=&args[1]=count
return arg === undefined ? replacer : arg;
});
}
/**
* Sindre Sorhus <https://sindresorhus.com>
* Released under MIT license
* https://github.com/sindresorhus/linkify-urls/blob/edd75a64a9c36d7025f102f666ddbb6cf0afa7cd/index.js#L4C25-L4C137
*
* The regex is used to extract URL from the string for linkify.
*/
const urlRegex =
/((?<!\+)https?:\/\/(?:www\.)?(?:[-\w.]+?[.@][a-zA-Z\d]{2,}|localhost)(?:[-\w.:%+~#*$!?&/=@]*?(?:,(?!\s))*?)*)/g;
// When the message contains a URL (like https://fb.me/react-refs-must-have-owner),
// make it a clickable link.
function urlify(str: string): React.ReactNode[] {
const segments = str.split(urlRegex);
return segments.map((message, i) => {
if (i % 2 === 1) {
return (
<a
key={i}
target="_blank"
className="underline"
rel="noopener noreferrer"
href={message}>
{message}
</a>
);
}
return message;
});
}
// `?args[]=foo&args[]=bar`
// or `// ?args[0]=foo&args[1]=bar`
function parseQueryString(search: string): Array<string | undefined> {
const rawQueryString = search.substring(1);
if (!rawQueryString) {
return [];
}
const args: Array<string | undefined> = [];
const queries = rawQueryString.split('&');
for (let i = 0; i < queries.length; i++) {
const query = decodeURIComponent(queries[i]);
if (query.startsWith('args[')) {
args.push(query.slice(query.indexOf(']=') + 2));
}
}
return args;
}
export default function ErrorDecoder() {
const {errorMessage, errorCode} = useErrorDecoderParams();
/** error messages that contain %s require reading location.search */
const hasParams = errorMessage?.includes('%s');
const [message, setMessage] = useState<React.ReactNode | null>(() =>
errorMessage ? urlify(errorMessage) : null
);
const [isReady, setIsReady] = useState(errorMessage == null || !hasParams);
useEffect(() => {
if (errorMessage == null || !hasParams) {
return;
}
const args = parseQueryString(window.location.search);
let message = errorMessage;
if (errorCode === '418') {
// Hydration errors have a %s for the diff, but we don't add that to the args for security reasons.
message = message.replace(/%s$/, '');
// Before React 19.1, the error message didn't have an arg, and was always HTML.
if (args.length === 0) {
args.push('HTML');
} else if (args.length === 1 && args[0] === '') {
args[0] = 'HTML';
}
}
setMessage(urlify(replaceArgs(message, args, '[missing argument]')));
setIsReady(true);
}, [errorCode, hasParams, errorMessage]);
return (
<code
className={cn(
'whitespace-pre-line block bg-red-100 text-red-600 py-4 px-6 mt-5 rounded-lg',
isReady ? 'opacity-100' : 'opacity-0'
)}>
<b>{message}</b>
</code>
);
}