initial release

This commit is contained in:
2025-01-27 10:23:54 +00:00
parent 776657b1c9
commit 5f37990209
8 changed files with 403 additions and 1543 deletions
+13
View File
@@ -0,0 +1,13 @@
name: Deploy
on:
repository_dispatch:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Publish
uses: cloudflare/wrangler-action@1.2.0
with:
apiToken: ${{ secrets.CF_API_TOKEN }}
env:
CF_ACCOUNT_ID: ${{ secrets.CF_ACCOUNT_ID }}
+11
View File
@@ -0,0 +1,11 @@
# Cloudflare R2 Browser
[![Deploy to Cloudflare Workers](https://deploy.workers.cloudflare.com/button)](https://deploy.workers.cloudflare.com/?url=https://github.com/sugoidogo/cf-r2-browser)
This worker allows you to browse and download files from R2 buckets in your web browser.
Supports multiple domains and buckets from a single worker.
Suppors configuration via `wrangler.toml` or Cloudflare dashboard.
# Setup
Deploy with the button above, or clone this repo and configure via `wrangler.toml` before deploying.
In your worker settings, you must set a custom domain (unless you want to use the default workers subdomain), bind an r2 bucket, and then create a variable binding where the key is the domain name and the value is the variable name of the bucket binding. If you don't understand what that means, check the comments in `wrangler.toml`
+300 -1456
View File
File diff suppressed because it is too large Load Diff
+2 -6
View File
@@ -4,13 +4,9 @@
"private": true,
"scripts": {
"deploy": "wrangler deploy",
"dev": "wrangler dev",
"start": "wrangler dev",
"test": "vitest"
"dev": "wrangler dev --ip=0.0.0.0"
},
"devDependencies": {
"@cloudflare/vitest-pool-workers": "^0.6.4",
"vitest": "2.1.8",
"wrangler": "^3.105.1"
"wrangler": "^3.99.0"
}
}
+63 -14
View File
@@ -1,15 +1,64 @@
/**
* Welcome to Cloudflare Workers! This is your first worker.
*
* - Run `npm run dev` in your terminal to start a development server
* - Open a browser tab at http://localhost:8787/ to see your worker in action
* - Run `npm run deploy` to publish your worker
*
* Learn more at https://developers.cloudflare.com/workers/
*/
export default {
async fetch(request, env, ctx) {
return new Response('Hello World!');
},
};
async fetch(request, env) {
const url = new URL(request.url)
const bucketName = env[url.hostname]
const bucket = env[bucketName]
const objectName = url.pathname.substring(1)
if (request.method === 'GET') {
if (!objectName || objectName.endsWith('/')) {
const options = { prefix: objectName }
let listing = null
const list = new Set()
do {
listing = await bucket.list(options)
options.cursor = listing.cursor
for (const object of listing.objects) {
/** @type {String} */
let itemName = object.key.slice(objectName.length)
itemName = itemName.substring(itemName.indexOf('/') + 1)
list.add(itemName)
}
} while (listing.truncated)
if (list.size === 0) {
return new Response(null, { status: 404 })
}
let body = '<h1>listing of /' + objectName + '</h1><br>'
for (const itemName of list) {
body += '<a href=' + itemName + '>' + itemName + '</a>'
}
return new Response(body, { headers: { 'content-type': 'text/html' } })
}
const object = await bucket.get(objectName, {
range: request.headers,
onlyIf: request.headers,
})
if (object === null) {
return new Response(null, { status: 404 })
}
const headers = new Headers()
object.writeHttpMetadata(headers)
headers.set('etag', object.httpEtag)
if (object.range) {
headers.set("content-range", `bytes ${object.range.offset}-${object.range.end ?? object.size - 1}/${object.size}`)
}
const status = object.body ? (request.headers.get("range") !== null ? 206 : 200) : 304
return new Response(object.body, { status: status, headers: headers })
}
if (request.method === 'HEAD') {
const object = await bucket.head(objectName)
if (object === null) {
return new Response(null, { status: 404 })
}
const headers = new Headers()
object.writeHttpMetadata(headers)
headers.set('etag', object.httpEtag)
return new Response(null, { headers: headers })
}
}
}
-20
View File
@@ -1,20 +0,0 @@
import { env, createExecutionContext, waitOnExecutionContext, SELF } from 'cloudflare:test';
import { describe, it, expect } from 'vitest';
import worker from '../src';
describe('Hello World worker', () => {
it('responds with Hello World! (unit style)', async () => {
const request = new Request('http://example.com');
// Create an empty context to pass to `worker.fetch()`.
const ctx = createExecutionContext();
const response = await worker.fetch(request, env, ctx);
// Wait for all `Promise`s passed to `ctx.waitUntil()` to settle before running test assertions
await waitOnExecutionContext(ctx);
expect(await response.text()).toMatchInlineSnapshot(`"Hello World!"`);
});
it('responds with Hello World! (integration style)', async () => {
const response = await SELF.fetch('http://example.com');
expect(await response.text()).toMatchInlineSnapshot(`"Hello World!"`);
});
});
-47
View File
@@ -1,47 +0,0 @@
/**
* 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": "cf-r2-browser",
"main": "src/index.js",
"compatibility_date": "2025-01-24",
"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/
*/
/**
* Environment Variables
* https://developers.cloudflare.com/workers/wrangler/configuration/#environment-variables
*/
// "vars": { "MY_VARIABLE": "production_value" },
/**
* Note: Use secrets to store sensitive data.
* https://developers.cloudflare.com/workers/configuration/secrets/
*/
/**
* Static Assets
* https://developers.cloudflare.com/workers/static-assets/binding/
*/
// "assets": { "directory": "./public/", "binding": "ASSETS" },
/**
* Service Bindings (communicate between multiple Workers)
* https://developers.cloudflare.com/workers/wrangler/configuration/#service-bindings
*/
// "services": [{ "binding": "MY_SERVICE", "service": "my-service" }]
}
+14
View File
@@ -0,0 +1,14 @@
name = "cf-r2-browser"
main = "src/index.js"
compatibility_date = "2024-12-18"
#[[r2_buckets]]
#binding = "bucketvar"
#bucket_name = "bucketname"
#[route]
#pattern="sub.domain.com/*"
#zone_name = "domain.com"
#[vars]
#"sub.domain.com"="bucketvar"