Created
June 21, 2019 18:04
-
-
Save RiFi2k/c3c65d59ca7f225e1d7d56929e0275ad to your computer and use it in GitHub Desktop.
Cloudflare Worker Function to find and replace content on a page
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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