diff --git a/.prettierrc b/.prettierrc index b2f59f51..7764b6c3 100644 --- a/.prettierrc +++ b/.prettierrc @@ -7,6 +7,7 @@ "^[^.].*.css$", "index.css$", ".css$", + "./polyfills", "", "/assets/", "^../", diff --git a/src/components/account-info.jsx b/src/components/account-info.jsx index 673dfaf6..1704e446 100644 --- a/src/components/account-info.jsx +++ b/src/components/account-info.jsx @@ -231,7 +231,7 @@ function AccountInfo({ const accountInstance = useMemo(() => { if (!url) return null; - const domain = punycode.toUnicode(new URL(url).hostname); + const domain = punycode.toUnicode(URL.parse(url).hostname); return domain; }, [url]); @@ -1655,7 +1655,7 @@ function lightenRGB([r, g, b]) { function niceAccountURL(url) { if (!url) return; - const urlObj = new URL(url); + const urlObj = URL.parse(url); const { host, pathname } = urlObj; const path = pathname.replace(/\/$/, '').replace(/^\//, ''); return ( diff --git a/src/components/account-sheet.jsx b/src/components/account-sheet.jsx index f0a3bae3..e0d693c4 100644 --- a/src/components/account-sheet.jsx +++ b/src/components/account-sheet.jsx @@ -58,7 +58,7 @@ function AccountSheet({ account, instance: propInstance, onClose }) { if (result.accounts.length) { return result.accounts[0]; } else if (/https?:\/\/[^/]+\/@/.test(account)) { - const accountURL = new URL(account); + const accountURL = URL.parse(account); const { hostname, pathname } = accountURL; const acct = pathname.replace(/^\//, '').replace(/\/$/, '') + diff --git a/src/components/compose.jsx b/src/components/compose.jsx index 60a0e011..d74fc4e5 100644 --- a/src/components/compose.jsx +++ b/src/components/compose.jsx @@ -3292,11 +3292,11 @@ function GIFPickerModal({ onClose = () => {}, onSelect = () => {} }) { width = (width / height) * 100; height = 100; } - const urlObj = new URL(url); + const urlObj = URL.parse(url); const strippedURL = urlObj.origin + urlObj.pathname; let strippedWebP; if (webp) { - const webpObj = new URL(webp); + const webpObj = URL.parse(webp); strippedWebP = webpObj.origin + webpObj.pathname; } return ( @@ -3306,7 +3306,7 @@ function GIFPickerModal({ onClose = () => {}, onSelect = () => {} }) { onClick={() => { const { mp4, url } = original; const theURL = mp4 || url; - const urlObj = new URL(theURL); + const urlObj = URL.parse(theURL); const strippedURL = urlObj.origin + urlObj.pathname; onClose(); onSelect({ diff --git a/src/components/link.jsx b/src/components/link.jsx index 34fe8f7e..1af3790e 100644 --- a/src/components/link.jsx +++ b/src/components/link.jsx @@ -22,15 +22,13 @@ const Link = forwardRef((props, ref) => { // Handle encodeURIComponent of searchParams values if (!!hash && hash !== '/' && hash.includes('?')) { - try { - const parsedHash = new URL(hash, location.origin); // Fake base URL - if (parsedHash.searchParams.size) { - const searchParamsStr = Array.from(parsedHash.searchParams.entries()) - .map(([key, value]) => `${key}=${encodeURIComponent(value)}`) - .join('&'); - hash = parsedHash.pathname + '?' + searchParamsStr; - } - } catch (e) {} + const parsedHash = URL.parse(hash, location.origin); // Fake base URL + if (parsedHash?.searchParams?.size) { + const searchParamsStr = Array.from(parsedHash.searchParams.entries()) + .map(([key, value]) => `${key}=${encodeURIComponent(value)}`) + .join('&'); + hash = parsedHash.pathname + '?' + searchParamsStr; + } } const isActive = hash === to || decodeURIComponent(hash) === to; diff --git a/src/components/media.jsx b/src/components/media.jsx index 2baba598..a757827f 100644 --- a/src/components/media.jsx +++ b/src/components/media.jsx @@ -674,12 +674,8 @@ function Media({ } function getURLObj(url) { - try { - // Fake base URL if url doesn't have https:// prefix - return new URL(url, location.origin); - } catch (e) { - return null; - } + // Fake base URL if url doesn't have https:// prefix + return URL.parse(url, location.origin); } export default Media; diff --git a/src/components/status.jsx b/src/components/status.jsx index f0ecc801..b9a10902 100644 --- a/src/components/status.jsx +++ b/src/components/status.jsx @@ -2535,7 +2535,9 @@ function Card({ card, selfReferential, instance }) { if (hasText && (image || (type === 'photo' && blurhash))) { const domain = punycode.toUnicode( - new URL(url).hostname.replace(/^www\./, '').replace(/\/$/, ''), + URL.parse(url) + .hostname.replace(/^www\./, '') + .replace(/\/$/, ''), ); let blurhashImage; const rgbAverageColor = @@ -2662,7 +2664,7 @@ function Card({ card, selfReferential, instance }) { } if (hasText && !image) { const domain = punycode.toUnicode( - new URL(url).hostname.replace(/^www\./, ''), + URL.parse(url).hostname.replace(/^www\./, ''), ); return ( { - const controller = new AbortController(); - setTimeout(() => controller.abort(), duration); - return controller.signal; - }); -} - render( diff --git a/src/pages/account-statuses.jsx b/src/pages/account-statuses.jsx index 04a5dc60..20306ccf 100644 --- a/src/pages/account-statuses.jsx +++ b/src/pages/account-statuses.jsx @@ -467,7 +467,7 @@ function AccountStatuses() { const accountInstance = useMemo(() => { if (!account?.url) return null; - const domain = new URL(account.url).hostname; + const domain = URL.parse(account.url).hostname; return domain; }, [account]); const sameInstance = instance === accountInstance; diff --git a/src/pages/catchup.jsx b/src/pages/catchup.jsx index d19775c5..c947cbe1 100644 --- a/src/pages/catchup.jsx +++ b/src/pages/catchup.jsx @@ -1111,8 +1111,8 @@ function Catchup() { publishedAt, } = card; const domain = punycode.toUnicode( - new URL(url).hostname - .replace(/^www\./, '') + URL.parse(url) + .hostname.replace(/^www\./, '') .replace(/\/$/, ''), ); let accentColor; diff --git a/src/pages/status.jsx b/src/pages/status.jsx index 79d05783..df236100 100644 --- a/src/pages/status.jsx +++ b/src/pages/status.jsx @@ -569,7 +569,7 @@ function StatusThread({ id, closeLink = '/', instance: propInstance }) { if (!heroStatus) return; const { url } = heroStatus; if (!url) return; - return new URL(url).hostname; + return URL.parse(url).hostname; }, [heroStatus]); const postSameInstance = useMemo(() => { if (!postInstance) return; diff --git a/src/pages/trending.jsx b/src/pages/trending.jsx index d3bd4575..2a284817 100644 --- a/src/pages/trending.jsx +++ b/src/pages/trending.jsx @@ -178,7 +178,9 @@ function Trending({ columnMode, ...props }) { width, } = link; const domain = punycode.toUnicode( - new URL(url).hostname.replace(/^www\./, '').replace(/\/$/, ''), + URL.parse(url) + .hostname.replace(/^www\./, '') + .replace(/\/$/, ''), ); let accentColor; if (blurhash) { diff --git a/src/polyfills.js b/src/polyfills.js new file mode 100644 index 00000000..514dad86 --- /dev/null +++ b/src/polyfills.js @@ -0,0 +1,24 @@ +// AbortSignal.timeout polyfill +// Temporary fix from https://github.com/mo/abortcontroller-polyfill/issues/73#issuecomment-1541180943 +// Incorrect implementation, but should be good enough for now +if ('AbortSignal' in window) { + AbortSignal.timeout = + AbortSignal.timeout || + ((duration) => { + const controller = new AbortController(); + setTimeout(() => controller.abort(), duration); + return controller.signal; + }); +} + +// URL.parse() polyfill +if ('URL' in window && typeof URL.parse !== 'function') { + URL.parse = function (url, base) { + if (!url) return null; + try { + return base ? new URL(url, base) : new URL(url); + } catch (e) { + return null; + } + }; +} diff --git a/src/utils/get-instance-status-url.js b/src/utils/get-instance-status-url.js index 33c05691..3f4cc6ee 100644 --- a/src/utils/get-instance-status-url.js +++ b/src/utils/get-instance-status-url.js @@ -11,7 +11,7 @@ const statusPostRegexes = [ export function getInstanceStatusObject(url) { // Regex /:username/:id, where username = @username or @username@domain, id = anything - const { hostname, pathname } = new URL(url); + const { hostname, pathname } = URL.parse(url); // const [, username, domain, id] = pathname.match(statusRegex) || []; for (const regex of statusPostRegexes) { const [, id] = pathname.match(regex) || []; diff --git a/src/utils/isMastodonLinkMaybe.jsx b/src/utils/isMastodonLinkMaybe.jsx index a4e87f80..f36e300c 100644 --- a/src/utils/isMastodonLinkMaybe.jsx +++ b/src/utils/isMastodonLinkMaybe.jsx @@ -1,6 +1,6 @@ export default function isMastodonLinkMaybe(url) { try { - const { pathname, hash } = new URL(url); + const { pathname, hash } = URL.parse(url); return ( /^\/.*\/\d+$/i.test(pathname) || /^\/(@[^/]+|users\/[^/]+)\/(statuses|posts)\/\w+\/?$/i.test(pathname) || // GoToSocial, Takahe diff --git a/src/utils/open-compose.js b/src/utils/open-compose.js index b8192876..9dd8e86e 100644 --- a/src/utils/open-compose.js +++ b/src/utils/open-compose.js @@ -1,5 +1,5 @@ export default function openCompose(opts) { - const url = new URL('/compose/', window.location); + const url = URL.parse('/compose/', window.location); const { width: screenWidth, height: screenHeight } = window.screen; const left = Math.max(0, (screenWidth - 600) / 2); const top = Math.max(0, (screenHeight - 450) / 2); diff --git a/src/utils/unfurl-link.jsx b/src/utils/unfurl-link.jsx index f1cd42fa..c4063a02 100644 --- a/src/utils/unfurl-link.jsx +++ b/src/utils/unfurl-link.jsx @@ -59,12 +59,8 @@ function _unfurlMastodonLink(instance, url) { theURL = `https://${finalURL}`; } - let urlObj; - try { - urlObj = new URL(theURL); - } catch (e) { - return; - } + const urlObj = URL.parse(theURL); + if (!urlObj) return; const domain = urlObj.hostname; const path = urlObj.pathname; // Regex /:username/:id, where username = @username or @username@domain, id = post ID