-
Notifications
You must be signed in to change notification settings - Fork 87
/
Copy pathresponse.ts
100 lines (90 loc) · 3.1 KB
/
response.ts
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
import { NextResponse } from 'next/server'
import type { ElementHandlers } from './html-rewriter'
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export type NextDataTransform = <T extends { pageProps?: Record<string, any> }>(props: T) => T
// A NextResponse that wraps the Netlify origin response
// We can't pass it through directly, because Next disallows returning a response body
export class MiddlewareResponse extends NextResponse {
private readonly dataTransforms: NextDataTransform[]
private readonly elementHandlers: Array<[selector: string, handlers: ElementHandlers]>
constructor(public originResponse: Response) {
super()
// These are private in Node when compiling, but we access them in Deno at runtime
Object.defineProperty(this, 'dataTransforms', {
value: [],
enumerable: false,
writable: false,
})
Object.defineProperty(this, 'elementHandlers', {
value: [],
enumerable: false,
writable: false,
})
}
/**
* Transform the page props before they are passed to the client.
* This works for both HTML pages and JSON data
*/
transformData(transform: NextDataTransform) {
// The transforms are evaluated after the middleware is returned
this.dataTransforms.push(transform)
}
/**
* Rewrite the response HTML with the given selector and handlers
*/
rewriteHTML(selector: string, handlers: ElementHandlers) {
this.elementHandlers.push([selector, handlers])
}
/**
* Sets the value of a page prop.
* @see transformData if you need more control
*/
setPageProp(key: string, value: unknown) {
this.transformData((props) => {
props.pageProps ||= {}
props.pageProps[key] = value
return props
})
}
/**
* Replace the text of the given element. Takes either a string or a function
* that is passed the original string and returns new new string.
* @see rewriteHTML for more control
*/
replaceText(selector: string, valueOrReplacer: string | ((input: string) => string)): void {
// If it's a string then our job is simpler, because we don't need to collect the current text
if (typeof valueOrReplacer === 'string') {
this.rewriteHTML(selector, {
text(textChunk) {
if (textChunk.lastInTextNode) {
textChunk.replace(valueOrReplacer)
} else {
textChunk.remove()
}
},
})
} else {
let text = ''
this.rewriteHTML(selector, {
text(textChunk) {
text += textChunk.text
// We're finished, so we can replace the text
if (textChunk.lastInTextNode) {
textChunk.replace(valueOrReplacer(text))
} else {
// Remove the chunk, because we'll be adding it back later
textChunk.remove()
}
},
})
}
}
get headers(): Headers {
// If we have the origin response, we should use its headers
return this.originResponse?.headers || super.headers
}
get status(): number {
// If we have the origin status, we should use it
return this.originResponse?.status || super.status
}
}