Skip to content

Instantly share code, notes, and snippets.

@sjelfull
Forked from RiFi2k/removeContent.js
Created October 2, 2022 21:30
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save sjelfull/b9a410ebb21c281c6bc4c3d6659132e0 to your computer and use it in GitHub Desktop.
Save sjelfull/b9a410ebb21c281c6bc4c3d6659132e0 to your computer and use it in GitHub Desktop.
Cloudflare Worker Function to find and replace content on a page
addEventListener("fetch", event => {
event.respondWith(handle(event.request))
})
async function handle(request) {
// Fetch from origin server.
let response = await fetch(request)
// Make sure we only modify text, not images.
let type = response.headers.get("Content-Type") || ""
if (!type.startsWith("text/")) {
// Not text. Don't modify.
return response
}
// Create a pipe. The readable side will become our
// new response body.
let { readable, writable } = new TransformStream()
// Start processing the body. NOTE: No await!
streamTransformBody(response.body, writable)
// ... and create our Response while that's running.
return new Response(readable, response)
}
// A map of template keys to URLs.
const templateMap = {
"BREADCRUMBS": "https://example.com/bread-crumbs"
}
async function translate(chunks) {
const decoder = new TextDecoder()
const encoder = new TextEncoder()
// Our chunks are in UTF-8, so we need to decode them before
// looking them up in our template map. TextDecoder's streaming
// API makes this easy to perform in a reduction.
let templateKey = chunks.reduce(
(accumulator, chunk) =>
accumulator + decoder.decode(chunk, { stream: true }),
"")
// We need one last call to decoder.decode() to flush
// decoder's buffer. If there's anything left in there, it'll
// come out as Unicode replacement characters.
templateKey += decoder.decode()
if (!templateMap.hasOwnProperty(templateKey)) {
// We encountered a template key we weren't expecting.
// Just leave its place in the document blank.
return new Uint8Array(0)
}
// We're expecting this template key and know where to find
// its resource.
let response = await fetch(templateMap[templateKey])
return response.arrayBuffer()
}
async function streamTransformBody(readable, writable) {
const leftBrace = '{'.charCodeAt(0)
const rightBrace = '}'.charCodeAt(0)
let reader = readable.getReader()
let writer = writable.getWriter()
// We need to track our state outside the loop in case we
// encounter a template that crosses a chunk boundary.
// Instead of tracking a separate inTemplate boolean, we can
// use the nullity of templateChunks to signal whether we're
// currently in a template.
let templateChunks = null
while (true) {
let { done, value } = await reader.read()
if (done) break
// Each chunk may have zero or more templates, so we'll
// need to loop until we're done processing this chunk.
while (value.byteLength > 0) {
if (templateChunks) {
// We're in the middle of a template. Search for the
// terminal brace.
let end = value.indexOf(rightBrace)
if (end === -1) {
// This entire chunk is part of a template. No further
// processing of this chunk is necessary.
templateChunks.push(value)
break
} else {
// We found the termination of a template.
templateChunks.push(value.subarray(0, end))
// Now that we have one complete template, translate it.
await writer.write(await translate(templateChunks))
templateChunks = null
value = value.subarray(end + 1)
}
}
// We're not currently in a template. Search for the
// initial brace.
let start = value.indexOf(leftBrace)
if (start === -1) {
// This entire chunk is template-free. We can write
// it and go straight to reading the next one.
await writer.write(value)
break
} else {
// We found the start of a template -- write the
// chunk up to that point, then continue processing
// the rest of the chunk.
await writer.write(value.subarray(0, start))
value = value.subarray(start + 1)
templateChunks = []
}
}
}
// NOTE: If templateChunks is non-null at this point, we
// encountered an unterminated template. This may or may
// not be a problem, depending on your use case.
await writer.close()
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment