Commit 2c6757ff authored by Zach Pomerantz's avatar Zach Pomerantz Committed by GitHub

fix: set content-type on cached document (#3990)

* fix: set content-type on cached document

* fix: delete old content-types

* fix: avoid immutable headers

* test: content-type

* fix: do not destructure response

* test: serve from cache with vercel

* fix: inject cache marker into body
parent 0d03b09a
......@@ -84,12 +84,15 @@ describe('document', () => {
describe('with a thrown fetch', () => {
it('returns a cached response', async () => {
const cached = new Response('<html><head></head></html>')
const cached = new Response('<html><head></head><body>mock</body></html>')
matchPrecache.mockResolvedValueOnce(cached)
fetch.mockRejectedValueOnce(new Error())
const response = await handleDocument(options)
expect(response).toBeInstanceOf(CachedDocument)
expect((response as CachedDocument).response).toBe(cached)
expect(response.headers.get('Content-Type')).toBe('text/html; charset=utf-8')
expect(await response.text()).toBe(
'<html><head></head><body><script>window.__isDocumentCached=true</script>mock</body></html>'
)
})
it('rethrows with no cached response', async () => {
......@@ -116,7 +119,7 @@ describe('document', () => {
let cached: Response
beforeEach(() => {
cached = new Response('<html><head></head></html>', { headers: { etag: 'cached' } })
cached = new Response('<html><head></head><body>mock</body></html>', { headers: { etag: 'cached' } })
matchPrecache.mockResolvedValueOnce(cached)
})
......@@ -134,9 +137,9 @@ describe('document', () => {
it('returns the cached response', async () => {
const response = await handleDocument(options)
expect(response).toBeInstanceOf(CachedDocument)
expect((response as CachedDocument).response).toBe(cached)
expect(response.headers.get('Content-Type')).toBe('text/html; charset=utf-8')
expect(await response.text()).toBe(
'<html><head><script>window.__isDocumentCached=true</script></head></html>'
'<html><head></head><body><script>window.__isDocumentCached=true</script>mock</body></html>'
)
})
})
......
......@@ -2,7 +2,7 @@ import { RouteHandlerCallbackOptions, RouteMatchCallbackOptions } from 'workbox-
import { getCacheKeyForURL, matchPrecache } from 'workbox-precaching'
import { Route } from 'workbox-routing'
import { isLocalhost } from './utils'
import { isDevelopment } from './utils'
const fileExtensionRegexp = new RegExp('/[^/?]+\\.[^/]+$')
export const DOCUMENT = process.env.PUBLIC_URL + '/index.html'
......@@ -24,7 +24,7 @@ export function matchDocument({ request, url }: RouteMatchCallbackOptions) {
// If this isn't app.uniswap.org (or a local build), skip.
// IPFS gateways may not have domain separation, so they cannot use document caching.
if (url.hostname !== 'app.uniswap.org' && !isLocalhost()) {
if (url.hostname !== 'app.uniswap.org' && !isDevelopment()) {
return false
}
......@@ -98,12 +98,15 @@ export class CachedDocument extends Response {
static async from(response: Response) {
const text = await response.text()
// Some browsers (Android 12; Chrome 91) duplicate the content-type header, invalidating it.
response.headers.set('Content-Type', 'text/html; charset=utf-8')
// Injects a marker into the document so that client code knows it was served from cache.
// The marker should be injected immediately in the <head> so it is available to client code.
return new CachedDocument(text.replace('<head>', '<head><script>window.__isDocumentCached=true</script>'), response)
// The marker should be injected immediately in the <body> so it is available to client code.
return new CachedDocument(text.replace('<body>', '<body><script>window.__isDocumentCached=true</script>'), response)
}
private constructor(text: string, public response: Response) {
private constructor(text: string, response: Response) {
super(text, response)
}
}
......@@ -4,13 +4,15 @@ import { PrecacheEntry } from 'workbox-precaching/_types'
declare const self: ServiceWorkerGlobalScope
export function isLocalhost() {
export function isDevelopment() {
return Boolean(
self.location.hostname === 'localhost' ||
// [::1] is the IPv6 localhost address.
// [::1] is the IPv6 localhost address
self.location.hostname === '[::1]' ||
// 127.0.0.0/8 are considered localhost for IPv4.
self.location.hostname.match(/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/)
// 127.0.0.0/8 are considered localhost for IPv4
self.location.hostname.match(/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/) ||
// vercel previews
self.location.hostname.endsWith('.vercel.app')
)
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment