Skip to content

Commit ab8c32e

Browse files
authored
Revert "Remove redundant RSC fetch monkey patches (#603)" (#604)
This reverts commit 1e99ffc.
1 parent 1e99ffc commit ab8c32e

4 files changed

Lines changed: 140 additions & 0 deletions

File tree

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
import { describe, expect, it } from 'vitest'
2+
import { normalizeProxiedRscFetch } from '../pages/_layout'
3+
import { normalizeRscFetchUrl } from './rsc-route-normalization'
4+
5+
const currentHref = 'https://docs.tempo.xyz/docs/guide/payments/send-a-payment'
6+
const origin = 'https://docs.tempo.xyz'
7+
8+
describe('normalizeRscFetchUrl', () => {
9+
it.each([
10+
[
11+
'keeps cross-origin RSC requests on the current origin',
12+
'https://tempo.xyz/RSC/R/docs/guide/payments.txt?query=',
13+
'https://docs.tempo.xyz/RSC/R/docs/guide/payments.txt?query=',
14+
],
15+
[
16+
'normalizes proxied developers route payloads',
17+
'https://tempo.xyz/RSC/R/developers/docs/tools.txt?query=',
18+
'https://docs.tempo.xyz/RSC/R/docs/tools.txt?query=',
19+
],
20+
[
21+
'normalizes the proxied developers root payload',
22+
'https://tempo.xyz/RSC/R/developers.txt?query=',
23+
'https://docs.tempo.xyz/RSC/R/_root.txt?query=',
24+
],
25+
[
26+
'preserves search and hash fragments',
27+
'https://tempo.xyz/RSC/R/docs/tools.txt?query=abc#flight',
28+
'https://docs.tempo.xyz/RSC/R/docs/tools.txt?query=abc#flight',
29+
],
30+
[
31+
'leaves non-RSC asset requests alone',
32+
'https://tempo.xyz/assets/index.js',
33+
'https://tempo.xyz/assets/index.js',
34+
],
35+
['leaves relative non-RSC requests alone', '/api/og?title=Tools', '/api/og?title=Tools'],
36+
])('%s', (_name, input, expected) => {
37+
expect(normalizeRscFetchUrl(input, currentHref, origin)).toBe(expected)
38+
})
39+
})
40+
41+
describe('normalizeProxiedRscFetch', () => {
42+
it.each([
43+
[
44+
'rewrites cross-origin RSC requests to the current origin',
45+
'https://tempo.xyz/RSC/R/docs/tools.txt?query=',
46+
'https://docs.tempo.xyz/RSC/R/docs/tools.txt?query=',
47+
],
48+
[
49+
'rewrites proxied developers RSC requests',
50+
'https://tempo.xyz/RSC/R/developers/docs/tools.txt?query=',
51+
'https://docs.tempo.xyz/RSC/R/docs/tools.txt?query=',
52+
],
53+
[
54+
'leaves non-RSC requests unchanged',
55+
'https://tempo.xyz/assets/index.js',
56+
'https://tempo.xyz/assets/index.js',
57+
],
58+
])('%s', async (_name, input, expected) => {
59+
const requests: unknown[] = []
60+
const fetch = (request: unknown) => {
61+
requests.push(request)
62+
return Promise.resolve(new Response())
63+
}
64+
Object.defineProperty(globalThis, 'window', {
65+
configurable: true,
66+
value: {
67+
__tempoNormalizeProxiedRscFetch: false,
68+
fetch,
69+
location: {
70+
href: currentHref,
71+
origin,
72+
},
73+
},
74+
})
75+
76+
try {
77+
Function(normalizeProxiedRscFetch)()
78+
await globalThis.window.fetch(input)
79+
expect(requests).toEqual([expected])
80+
} finally {
81+
Reflect.deleteProperty(globalThis, 'window')
82+
}
83+
})
84+
})

src/lib/rsc-route-normalization.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
export function normalizeRscFetchUrl(url: string, currentHref: string, origin: string) {
2+
const requestUrl = new URL(url, currentHref)
3+
if (!requestUrl.pathname.startsWith('/RSC/R/')) return url
4+
5+
const pathname = requestUrl.pathname
6+
.replace(/\/RSC\/R\/developers\.txt$/, '/RSC/R/_root.txt')
7+
.replace(/\/RSC\/R\/developers\//, '/RSC/R/')
8+
9+
return new URL(pathname + requestUrl.search + requestUrl.hash, origin).toString()
10+
}

src/pages/_layout.tsx

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,36 @@
11
import type { PropsWithChildren } from 'react'
22

3+
export const normalizeProxiedRscFetch = `
4+
(() => {
5+
if (window.__tempoNormalizeProxiedRscFetch) return;
6+
window.__tempoNormalizeProxiedRscFetch = true;
7+
const originalFetch = window.fetch.bind(window);
8+
window.fetch = (input, init) => {
9+
const url = typeof input === 'string'
10+
? input
11+
: input instanceof URL
12+
? input.toString()
13+
: input.url;
14+
const requestUrl = new URL(url, window.location.href);
15+
let rewritten = url;
16+
if (requestUrl.pathname.startsWith('/RSC/R/')) {
17+
const pathname = requestUrl.pathname
18+
.replace(/\\/RSC\\/R\\/developers\\.txt$/, '/RSC/R/_root.txt')
19+
.replace(/\\/RSC\\/R\\/developers\\//, '/RSC/R/');
20+
rewritten = new URL(
21+
pathname + requestUrl.search + requestUrl.hash,
22+
window.location.origin,
23+
).toString();
24+
}
25+
26+
if (rewritten === url) return originalFetch(input, init);
27+
if (typeof input === 'string' || input instanceof URL) return originalFetch(rewritten, init);
28+
29+
return originalFetch(new Request(rewritten, input), init);
30+
};
31+
})();
32+
`
33+
334
export default function Layout(
435
props: PropsWithChildren<{
536
path: string
@@ -15,6 +46,8 @@ export default function Layout(
1546
type="font/woff2"
1647
crossOrigin="anonymous"
1748
/>
49+
{/* biome-ignore lint/security/noDangerouslySetInnerHtml: static bootstrap must run before the RSC client bundle. */}
50+
<script dangerouslySetInnerHTML={{ __html: normalizeProxiedRscFetch }} />
1851
{props.children}
1952
</>
2053
)

src/pages/docs/_layout.tsx

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import DocsHeader from '../../components/DocsHeader'
55
import DocsSectionNav from '../../components/DocsSectionNav'
66
import DocsSidebarDrawer from '../../components/DocsSidebarDrawer'
77
import { usePageSettled } from '../../lib/pageSettled'
8+
import { normalizeRscFetchUrl } from '../../lib/rsc-route-normalization'
89

910
const Analytics = lazy(() =>
1011
import('@vercel/analytics/react').then((module) => ({ default: module.Analytics })),
@@ -17,6 +18,18 @@ const GoogleAnalytics = lazy(() => import('../../components/GoogleAnalytics'))
1718
const PostHogSetup = lazy(() => import('../../components/PostHogSetup'))
1819

1920
if (typeof window !== 'undefined') {
21+
const originalFetch = window.fetch.bind(window)
22+
window.fetch = (input, init) => {
23+
const url =
24+
typeof input === 'string' ? input : input instanceof URL ? input.toString() : input.url
25+
const rewritten = normalizeRscFetchUrl(url, window.location.href, window.location.origin)
26+
27+
if (rewritten === url) return originalFetch(input, init)
28+
if (typeof input === 'string' || input instanceof URL) return originalFetch(rewritten, init)
29+
30+
return originalFetch(new Request(rewritten, input), init)
31+
}
32+
2033
window.addEventListener('vite:preloadError', (event) => {
2134
const key = `vite:preloadError:${(event as unknown as CustomEvent).detail?.message}`
2235
if (!sessionStorage.getItem(key)) {

0 commit comments

Comments
 (0)