Skip to content

Instantly share code, notes, and snippets.

@jakejackson1
Created September 11, 2025 23:20
Show Gist options
  • Select an option

  • Save jakejackson1/d66c097a18ee72515ed626bc23ba0c96 to your computer and use it in GitHub Desktop.

Select an option

Save jakejackson1/d66c097a18ee72515ed626bc23ba0c96 to your computer and use it in GitHub Desktop.
Easy Digital Download Cloudflare Worker Proxy

This is the bare minimum steps required to get the proxy working. Adapt to your needs:

  1. Follow Cloudflare's guide for creating your first worker with the "Worker only" template and "Typescript" for the language.
  2. Copy the contents of index.ts to the local src/index.ts file
  3. Copy the contents of wrangler.jsonc to your local file
  4. Update the proxyUrl and proxyHostname in index.ts to point to your Easy Digital Downloads installation
  5. Run npx wrangler types --experimental-include-runtime to regenerate typescript support the the rate limit bindings
  6. Run npx wrangler dev and test the proxy works
  7. Run npx wrangler deploy to push the code to Cloudflare
/**
* A Licensing Server Proxy
*/
const proxyUrl = 'http://example.com';
const proxyHostname = 'example.com'
export default {
async fetch(
request: Request,
env: Env,
ctx: ExecutionContext,
): Promise<Response> {
// only handle POST requests
if (request.method.toUpperCase() !== "POST") {
return new Response('Method must be POST', {
status: 403,
});
}
// currently only handle formData requests (may support JSON later)
const acceptedContentType = ['application/x-www-form-urlencoded', 'multipart/form-data']
if (!acceptedContentType.includes(request.headers.get('content-type') || '')) {
console.error(
'Request Error',
request.headers.get('content-type'),
await request.text()
)
return new Response('Content Type must be "application/x-www-form-urlencoded"', {
status: 400,
});
}
// rate limit the endpoint
const ipAddress = request.headers.get("cf-connecting-ip") || ""
const {success} = await env.LIMITER.limit({key: ipAddress})
if (!success) {
return new Response('', {
status: 429
});
}
// get formData so we can route the request
const requestBody = await request.clone().formData()
if (!requestBody.has('edd_action')) {
return new Response('', {
status: 403,
});
}
try {
switch (requestBody.get('edd_action')) {
default:
return defaultRoute(request)
}
} catch (e: any) {
console.error(e)
return new Response('', {
status: 500,
});
}
}
} satisfies ExportedHandler<Env>;
async function defaultRoute(request: Request): Promise<Response> {
console.log('Getting response from API', await request.clone().text())
let response = await originApiRequest(request)
// Log response
const textResponse = await response.clone().text()
console.log('Origin API Response', textResponse, JSON.stringify(Object.fromEntries(response.headers)), response.status)
try {
JSON.parse(textResponse)
} catch (e) {
// Throw an error if an invalid response is received from the API server
return new Response(textResponse, {
status: 500,
});
}
return response
}
async function originApiRequest(request: Request): Promise<Response> {
/* Create a new request to the endpoint */
const url = new URL(proxyUrl);
url.hostname = proxyHostname;
const newRequest = new Request(
url.toString() as string,
new Request(request) as Request,
);
console.log('Origin API Request', JSON.stringify(Object.fromEntries(newRequest.headers)), newRequest.method)
return fetch(newRequest);
}
/**
* For more details on how to configure Wrangler, refer to:
* https://developers.cloudflare.com/workers/wrangler/configuration/
*/
{
"$schema": "node_modules/wrangler/config-schema.json",
"name": "cloudflare-edd-proxy",
"main": "src/index.ts",
"compatibility_date": "2025-07-12",
"observability": {
"enabled": true
},
/**
* Smart Placement
* Docs: https://developers.cloudflare.com/workers/configuration/smart-placement/#smart-placement
*/
"placement": {
"mode": "smart"
},
/**
* Bindings
* Bindings allow your Worker to interact with resources on the Cloudflare Developer Platform, including
* databases, object storage, AI inference, real-time communication and more.
* https://developers.cloudflare.com/workers/runtime-apis/bindings/
*/
"unsafe": {
"bindings": [
{
"name": "LIMITER",
"type": "ratelimit",
"namespace_id": "1001",
"simple": {
"limit": 20,
"period": 60
}
}
]
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment