Created
April 26, 2023 11:25
-
-
Save lewangdev/1c0c0ac82707f0a8a80472a14f1afc56 to your computer and use it in GitHub Desktop.
cf-reverse-proxy
This file contains hidden or 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
// node_modules/reflare/dist/src/database/workers-kv.js | |
var WorkersKV = class { | |
namespace; | |
constructor(namespace) { | |
this.namespace = namespace; | |
} | |
get = async (key) => { | |
const value = await this.namespace.get(key, { | |
type: "json", | |
cacheTtl: 60 | |
}); | |
return value; | |
}; | |
put = async (key, value) => { | |
await this.namespace.put(key, JSON.stringify(value)); | |
}; | |
delete = async (key) => { | |
await this.namespace.delete(key); | |
}; | |
}; | |
// node_modules/reflare/dist/src/middleware.js | |
var usePipeline = (...initMiddlewares) => { | |
const stack = [...initMiddlewares]; | |
const push = (...middlewares) => { | |
stack.push(...middlewares); | |
}; | |
const execute = async (context) => { | |
const runner = async (prevIndex, index) => { | |
if (index === prevIndex) { | |
throw new Error("next() called multiple times"); | |
} | |
if (index >= stack.length) { | |
return; | |
} | |
const middleware = stack[index]; | |
const next = async () => runner(index, index + 1); | |
await middleware(context, next); | |
}; | |
await runner(-1, 0); | |
}; | |
return { | |
push, | |
execute | |
}; | |
}; | |
// node_modules/reflare/dist/src/middlewares/cors.js | |
var useCORS = async (context, next) => { | |
await next(); | |
const { request, response, route } = context; | |
const corsOptions = route.cors; | |
if (corsOptions === void 0) { | |
return; | |
} | |
const { origin, methods, exposedHeaders, allowedHeaders, credentials, maxAge } = corsOptions; | |
const requestOrigin = request.headers.get("origin"); | |
if (requestOrigin === null || origin === false) { | |
return; | |
} | |
const corsHeaders = new Headers(response.headers); | |
if (origin === true) { | |
corsHeaders.set("Access-Control-Allow-Origin", requestOrigin); | |
} else if (Array.isArray(origin)) { | |
if (origin.includes(requestOrigin)) { | |
corsHeaders.set("Access-Control-Allow-Origin", requestOrigin); | |
} | |
} else if (origin === "*") { | |
corsHeaders.set("Access-Control-Allow-Origin", "*"); | |
} | |
if (Array.isArray(methods)) { | |
corsHeaders.set("Access-Control-Allow-Methods", methods.join(",")); | |
} else if (methods === "*") { | |
corsHeaders.set("Access-Control-Allow-Methods", "*"); | |
} else { | |
const requestMethod = request.headers.get("Access-Control-Request-Method"); | |
if (requestMethod !== null) { | |
corsHeaders.set("Access-Control-Allow-Methods", requestMethod); | |
} | |
} | |
if (Array.isArray(exposedHeaders)) { | |
corsHeaders.set("Access-Control-Expose-Headers", exposedHeaders.join(",")); | |
} else if (exposedHeaders === "*") { | |
corsHeaders.set("Access-Control-Expose-Headers", "*"); | |
} | |
if (Array.isArray(allowedHeaders)) { | |
corsHeaders.set("Access-Control-Allow-Headers", allowedHeaders.join(",")); | |
} else if (allowedHeaders === "*") { | |
corsHeaders.set("Access-Control-Allow-Headers", "*"); | |
} else { | |
const requestHeaders = request.headers.get("Access-Control-Request-Headers"); | |
if (requestHeaders !== null) { | |
corsHeaders.set("Access-Control-Allow-Headers", requestHeaders); | |
} | |
} | |
if (credentials === true) { | |
corsHeaders.set("Access-Control-Allow-Credentials", "true"); | |
} | |
if (maxAge !== void 0 && Number.isInteger(maxAge)) { | |
corsHeaders.set("Access-Control-Max-Age", maxAge.toString()); | |
} | |
context.response = new Response(response.body, { | |
status: response.status, | |
statusText: response.statusText, | |
headers: corsHeaders | |
}); | |
}; | |
// node_modules/reflare/dist/src/middlewares/firewall.js | |
var fields = /* @__PURE__ */ new Set([ | |
"country", | |
"continent", | |
"asn", | |
"ip", | |
"hostname", | |
"user-agent" | |
]); | |
var operators = /* @__PURE__ */ new Set([ | |
"equal", | |
"not equal", | |
"greater", | |
"less", | |
"in", | |
"not in", | |
"contain", | |
"not contain", | |
"match", | |
"not match" | |
]); | |
var validateFirewall = ({ field, operator, value }) => { | |
if (field === void 0 || operator === void 0 || value === void 0) { | |
throw new Error("Invalid 'firewall' field in the option object"); | |
} | |
if (fields.has(field) === false) { | |
throw new Error("Invalid 'firewall' field in the option object"); | |
} | |
if (operators.has(operator) === false) { | |
throw new Error("Invalid 'firewall' field in the option object"); | |
} | |
}; | |
var getFieldParam = (request, field) => { | |
const cfProperties = request.cf; | |
switch (field) { | |
case "asn": | |
return cfProperties?.asn; | |
case "continent": | |
return cfProperties?.continent; | |
case "country": | |
return cfProperties?.country; | |
case "hostname": | |
return request.headers.get("host") || ""; | |
case "ip": | |
return request.headers.get("cf-connecting-ip") || ""; | |
case "user-agent": | |
return request.headers.get("user-agent") || ""; | |
default: | |
return void 0; | |
} | |
}; | |
var matchOperator = (fieldParam, value) => { | |
if (!(value instanceof RegExp)) { | |
throw new Error("You must use 'new RegExp('...')' for 'value' in firewall configuration to use 'match' or 'not match' operator"); | |
} | |
return value.test(fieldParam.toString()); | |
}; | |
var notMatchOperator = (fieldParam, value) => !matchOperator(fieldParam, value); | |
var equalOperator = (fieldParam, value) => fieldParam === value; | |
var notEqualOperator = (fieldParam, value) => fieldParam !== value; | |
var greaterOperator = (fieldParam, value) => { | |
if (typeof fieldParam !== "number" || typeof value !== "number") { | |
throw new Error("You must use number for 'value' in firewall configuration to use 'greater' or 'less' operator"); | |
} | |
return fieldParam > value; | |
}; | |
var lessOperator = (fieldParam, value) => { | |
if (typeof fieldParam !== "number" || typeof value !== "number") { | |
throw new Error("You must use number for 'value' in firewall configuration to use 'greater' or 'less' operator"); | |
} | |
return fieldParam < value; | |
}; | |
var containOperator = (fieldParam, value) => { | |
if (typeof fieldParam !== "string" || typeof value !== "string") { | |
throw new Error("You must use string for 'value' in firewall configuration to use 'contain' or 'not contain' operator"); | |
} | |
return fieldParam.includes(value); | |
}; | |
var notContainOperator = (fieldParam, value) => !containOperator(fieldParam, value); | |
var inOperator = (fieldParam, value) => { | |
if (!Array.isArray(value)) { | |
throw new Error("You must use an Array for 'value' in firewall configuration to use 'in' or 'not in' operator"); | |
} | |
return value.some((item) => item === fieldParam); | |
}; | |
var notInOperator = (fieldParam, value) => !inOperator(fieldParam, value); | |
var operatorsMap = { | |
match: matchOperator, | |
contain: containOperator, | |
equal: equalOperator, | |
in: inOperator, | |
greater: greaterOperator, | |
less: lessOperator, | |
"not match": notMatchOperator, | |
"not contain": notContainOperator, | |
"not equal": notEqualOperator, | |
"not in": notInOperator | |
}; | |
var useFirewall = async (context, next) => { | |
const { request, route } = context; | |
if (route.firewall === void 0) { | |
await next(); | |
return; | |
} | |
route.firewall.forEach(validateFirewall); | |
for (const { field, operator, value } of route.firewall) { | |
const fieldParam = getFieldParam(request, field); | |
if (fieldParam !== void 0 && operatorsMap[operator](fieldParam, value)) { | |
throw new Error("You don't have permission to access this service."); | |
} | |
} | |
await next(); | |
}; | |
// node_modules/reflare/dist/src/middlewares/headers.js | |
var setForwardedHeaders = (headers) => { | |
headers.set("X-Forwarded-Proto", "https"); | |
const host = headers.get("Host"); | |
if (host !== null) { | |
headers.set("X-Forwarded-Host", host); | |
} | |
const ip = headers.get("cf-connecting-ip"); | |
const forwardedForHeader = headers.get("X-Forwarded-For"); | |
if (ip !== null && forwardedForHeader === null) { | |
headers.set("X-Forwarded-For", ip); | |
} | |
}; | |
var useHeaders = async (context, next) => { | |
const { request, route } = context; | |
const requestHeaders = new Headers(request.headers); | |
setForwardedHeaders(requestHeaders); | |
if (route.headers === void 0) { | |
context.request = new Request(request.url, { | |
body: request.body, | |
method: request.method, | |
headers: requestHeaders | |
}); | |
await next(); | |
return; | |
} | |
if (route.headers.request !== void 0) { | |
for (const [key, value] of Object.entries(route.headers.request)) { | |
if (value.length === 0) { | |
requestHeaders.delete(key); | |
} else { | |
requestHeaders.set(key, value); | |
} | |
} | |
} | |
context.request = new Request(request.url, { | |
body: request.body, | |
method: request.method, | |
headers: requestHeaders | |
}); | |
await next(); | |
const { response } = context; | |
const responseHeaders = new Headers(response.headers); | |
if (route.headers.response !== void 0) { | |
for (const [key, value] of Object.entries(route.headers.response)) { | |
if (value.length === 0) { | |
responseHeaders.delete(key); | |
} else { | |
responseHeaders.set(key, value); | |
} | |
} | |
} | |
context.response = new Response(response.body, { | |
status: response.status, | |
statusText: response.statusText, | |
headers: responseHeaders | |
}); | |
}; | |
// node_modules/reflare/dist/src/utils.js | |
var getHostname = (request) => { | |
const url = new URL(request.url); | |
return url.host; | |
}; | |
var castToIterable = (value) => Array.isArray(value) ? value : [value]; | |
// node_modules/reflare/dist/src/middlewares/load-balancing.js | |
var validateUpstream = (upstream) => { | |
if (upstream.domain === void 0) { | |
throw new Error("Invalid 'upstream' field in the option object"); | |
} | |
}; | |
var ipHashHandler = (upstream, request) => { | |
const ipString = request.headers.get("cf-connecting-ip") || "0.0.0.0"; | |
const userIP = ipString.split(".").map((octet, index, array) => parseInt(octet, 10) * 256 ** (array.length - index - 1)).reduce((accumulator, current) => accumulator + current); | |
return upstream[userIP % upstream.length]; | |
}; | |
var randomHandler = (upstream) => { | |
const weights = upstream.map((option) => option.weight === void 0 ? 1 : option.weight); | |
const totalWeight = weights.reduce((acc, num, index) => { | |
const sum = acc + num; | |
weights[index] = sum; | |
return sum; | |
}); | |
if (totalWeight === 0) { | |
throw new Error("Total weights should be greater than 0."); | |
} | |
const random = Math.random() * totalWeight; | |
for (const index of weights.keys()) { | |
if (weights[index] >= random) { | |
return upstream[index]; | |
} | |
} | |
return upstream[Math.floor(Math.random() * upstream.length)]; | |
}; | |
var handlersMap = { | |
random: randomHandler, | |
"ip-hash": ipHashHandler | |
}; | |
var useLoadBalancing = async (context, next) => { | |
const { request, route } = context; | |
const { upstream, loadBalancing } = route; | |
if (upstream === void 0) { | |
throw new Error("The required 'upstream' field in the option object is missing"); | |
} | |
const upstreamIterable = castToIterable(upstream); | |
upstreamIterable.forEach(validateUpstream); | |
if (loadBalancing === void 0) { | |
context.upstream = randomHandler(upstreamIterable, request); | |
await next(); | |
return; | |
} | |
const policy = loadBalancing.policy || "random"; | |
const policyHandler = handlersMap[policy]; | |
context.upstream = policyHandler(upstreamIterable, request); | |
await next(); | |
}; | |
// node_modules/reflare/dist/src/middlewares/upstream.js | |
var rewriteURL = (url, upstream) => { | |
const cloneURL = new URL(url); | |
const { domain, port, protocol } = upstream; | |
cloneURL.hostname = domain; | |
if (protocol !== void 0) { | |
cloneURL.protocol = `${protocol}:`; | |
} | |
if (port === void 0) { | |
cloneURL.port = ""; | |
} else { | |
cloneURL.port = port.toString(); | |
} | |
return cloneURL.href; | |
}; | |
var useUpstream = async (context, next) => { | |
const { request, upstream } = context; | |
if (upstream === null) { | |
await next(); | |
return; | |
} | |
const url = rewriteURL(request.url, upstream); | |
context.request = new Request(url, context.request); | |
if (upstream.onRequest) { | |
const onRequest = castToIterable(upstream.onRequest); | |
context.request = onRequest.reduce((reducedRequest, fn) => fn(reducedRequest, url), request); | |
} | |
context.response = (await fetch(context.request)).clone(); | |
if (upstream.onResponse) { | |
const onResponse = castToIterable(upstream.onResponse); | |
context.response = onResponse.reduce((reducedResponse, fn) => fn(reducedResponse, url), context.response); | |
} | |
await next(); | |
}; | |
// node_modules/reflare/dist/src/index.js | |
var filter = (request, routeList) => { | |
const url = new URL(request.url); | |
for (const route of routeList) { | |
if (route.methods === void 0 || route.methods.includes(request.method)) { | |
const match = castToIterable(route.path).some((path) => { | |
const re = RegExp(`^${path.replace(/(\/?)\*/g, "($1.*)?").replace(/\/$/, "").replace(/:(\w+)(\?)?(\.)?/g, "$2(?<$1>[^/]+)$2$3").replace(/\.(?=[\w(])/, "\\.").replace(/\)\.\?\(([^[]+)\[\^/g, "?)\\.?($1(?<=\\.)[^\\.")}/*$`); | |
return url.pathname.match(re); | |
}); | |
if (match) { | |
return route; | |
} | |
} | |
} | |
return void 0; | |
}; | |
var defaultOptions = { | |
provider: "static", | |
routeList: [] | |
}; | |
var useReflare = async (options = defaultOptions) => { | |
const pipeline = usePipeline(useFirewall, useLoadBalancing, useHeaders, useCORS, useUpstream); | |
const routeList = []; | |
if (options.provider === "static") { | |
for (const route of options.routeList) { | |
routeList.push(route); | |
} | |
} | |
if (options.provider === "kv") { | |
const database = new WorkersKV(options.namespace); | |
const routeListKV = await database.get("route-list") || []; | |
for (const routeKV of routeListKV) { | |
routeList.push(routeKV); | |
} | |
} | |
const handle = async (request) => { | |
const route = filter(request, routeList); | |
if (route === void 0) { | |
return new Response("Failed to find a route that matches the path and method of the current request", { | |
status: 500 | |
}); | |
} | |
const context = { | |
request: request.clone(), | |
route, | |
hostname: getHostname(request), | |
response: new Response("Unhandled response"), | |
upstream: null | |
}; | |
try { | |
await pipeline.execute(context); | |
} catch (error) { | |
if (error instanceof Error) { | |
context.response = new Response(error.message, { | |
status: 500 | |
}); | |
} | |
} | |
return context.response; | |
}; | |
const unshift = (route) => { | |
routeList.unshift(route); | |
}; | |
const push = (route) => { | |
routeList.push(route); | |
}; | |
return { | |
handle, | |
unshift, | |
push | |
}; | |
}; | |
var src_default = useReflare; | |
// src/index.ts | |
var src_default2 = { | |
async fetch(request) { | |
const reflare = await src_default(); | |
reflare.push({ | |
path: "/*", | |
upstream: { | |
domain: "quizizz.com", | |
protocol: "https" | |
} | |
}); | |
return reflare.handle(request); | |
} | |
}; | |
export { | |
src_default2 as default | |
}; | |
//# sourceMappingURL=index.js.map |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment