initial release
This commit is contained in:
@@ -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 }}
|
||||
@@ -0,0 +1,11 @@
|
||||
# Cloudflare R2 Browser
|
||||
|
||||
[](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`
|
||||
Generated
+300
-1456
File diff suppressed because it is too large
Load Diff
+2
-6
@@ -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
@@ -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 })
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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!"`);
|
||||
});
|
||||
});
|
||||
@@ -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" }]
|
||||
}
|
||||
@@ -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"
|
||||
Reference in New Issue
Block a user