r2-explorer 1.0.6 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +1 -1
- package/README.md +9 -4
- package/dashboard/assets/AuthLayout.828e1213.js +1 -0
- package/dashboard/assets/EmailFilePage.148b4f84.js +2 -0
- package/dashboard/assets/EmailFilePage.c4890c9c.css +1 -0
- package/dashboard/assets/EmailFolderPage.0c5be156.js +1 -0
- package/dashboard/assets/EmailFolderPage.25044e0a.css +1 -0
- package/dashboard/assets/ErrorNotFound.a0d3ece6.js +1 -0
- package/dashboard/assets/FilesFolderPage.7cadc3fd.js +72 -0
- package/dashboard/assets/FilesFolderPage.ecacd99f.css +1 -0
- package/dashboard/assets/HomePage.fd1efdb0.js +1 -0
- package/dashboard/assets/KFOkCnqEu92Fr1MmgVxIIzQ.d240a9ae.woff +0 -0
- package/dashboard/assets/KFOlCnqEu92Fr1MmEU9fBBc-.6ba203eb.woff +0 -0
- package/dashboard/assets/KFOlCnqEu92Fr1MmSU5fBBc-.80684728.woff +0 -0
- package/dashboard/assets/KFOlCnqEu92Fr1MmWUlfBBc-.2df244f6.woff +0 -0
- package/dashboard/assets/KFOlCnqEu92Fr1MmYUtfBBc-.742ce02b.woff +0 -0
- package/dashboard/assets/KFOmCnqEu92Fr1Mu4mxM.f00fa16d.woff +0 -0
- package/dashboard/assets/LoginPage.5e2746c3.js +1 -0
- package/dashboard/assets/MainLayout.05d49d78.css +1 -0
- package/dashboard/assets/MainLayout.54c624c6.js +1 -0
- package/dashboard/assets/QCard.9ca85696.js +1 -0
- package/dashboard/assets/QCardActions.3d6ece78.js +1 -0
- package/dashboard/assets/QForm.1a0fa8bd.js +1 -0
- package/dashboard/assets/QInput.dbc14c53.js +1 -0
- package/dashboard/assets/QLayout.7c9341c3.js +1 -0
- package/dashboard/assets/QPage.1736cadc.js +1 -0
- package/dashboard/assets/QSeparator.d0c0fb0f.js +1 -0
- package/dashboard/assets/QSpace.3225ba0f.js +1 -0
- package/dashboard/assets/QTable.3fe6867d.js +1 -0
- package/dashboard/assets/QTd.32b217d9.js +1 -0
- package/dashboard/assets/auth-store.10a6215e.js +1 -0
- package/dashboard/assets/auth.3fb1cfd3.js +1 -0
- package/dashboard/assets/axios.d3fa833b.js +6 -0
- package/dashboard/assets/bus.def2db9e.js +1 -0
- package/dashboard/assets/flUhRq6tzZclQEJ-Vdg-IuiaDsNa.fd84f88b.woff +0 -0
- package/dashboard/assets/flUhRq6tzZclQEJ-Vdg-IuiaDsNcIhQ8tQ.4a4dbc62.woff2 +0 -0
- package/dashboard/assets/focus-manager.1ddae684.js +1 -0
- package/dashboard/assets/index.073c3cc5.css +5 -0
- package/dashboard/assets/index.31a4681e.js +25 -0
- package/dashboard/assets/main-store.ee636e78.js +1 -0
- package/dashboard/assets/scroll.6727d3ea.js +1 -0
- package/dashboard/assets/use-checkbox.687a19bf.js +1 -0
- package/dashboard/assets/use-dark.6e19ce43.js +1 -0
- package/dashboard/assets/use-quasar.0c0b766f.js +1 -0
- package/dashboard/assets/use-transition.19980487.js +1 -0
- package/dashboard/favicon.ico +0 -0
- package/dashboard/icons/favicon-128x128.png +0 -0
- package/dashboard/icons/favicon-16x16.png +0 -0
- package/dashboard/icons/favicon-32x32.png +0 -0
- package/dashboard/icons/favicon-96x96.png +0 -0
- package/dashboard/icons/logo.png +0 -0
- package/dashboard/index.html +3 -0
- package/dashboard/logo-white.svg +5 -0
- package/dashboard/logo.png +0 -0
- package/dashboard/robots.txt +2 -0
- package/dist/index.d.mts +33 -0
- package/dist/index.d.ts +33 -0
- package/dist/index.js +802 -1
- package/dist/index.mjs +771 -1
- package/package.json +22 -60
- package/dist/src/authentication/api/access.d.ts +0 -4
- package/dist/src/authentication/api/basic.d.ts +0 -3
- package/dist/src/buckets/api/createFolder.d.ts +0 -8
- package/dist/src/buckets/api/deleteObject.d.ts +0 -8
- package/dist/src/buckets/api/getObject.d.ts +0 -8
- package/dist/src/buckets/api/headObject.d.ts +0 -8
- package/dist/src/buckets/api/listBuckets.d.ts +0 -10
- package/dist/src/buckets/api/listObjects.d.ts +0 -8
- package/dist/src/buckets/api/moveObject.d.ts +0 -8
- package/dist/src/buckets/api/multipart/completeUpload.d.ts +0 -11
- package/dist/src/buckets/api/multipart/createUpload.d.ts +0 -8
- package/dist/src/buckets/api/multipart/partUpload.d.ts +0 -8
- package/dist/src/buckets/api/putMetadata.d.ts +0 -8
- package/dist/src/buckets/api/putObject.d.ts +0 -8
- package/dist/src/buckets/router.d.ts +0 -1
- package/dist/src/dashbord.d.ts +0 -2
- package/dist/src/dates.d.ts +0 -2
- package/dist/src/emails/receiveEmail.d.ts +0 -2
- package/dist/src/index.d.ts +0 -5
- package/dist/src/interfaces.d.ts +0 -22
- package/dist/src/server/api/getInfo.d.ts +0 -25
- package/dist/src/server/router.d.ts +0 -1
- package/dist/src/settings.d.ts +0 -5
package/dist/index.js
CHANGED
|
@@ -1 +1,802 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,"__esModule",{value:!0});var e=require("itty-router"),t=require("@cloudflare/itty-router-openapi"),a=require("zod");const s=!0,r=!1,o="1.0.6";class n extends t.OpenAPIRoute{static schema={operationId:"get-bucket-list",tags:["Buckets"],summary:"List buckets"};async handle(e,t,a,s){const r=[];for(const[e,a]of Object.entries(t))a.get&&a.put&&r.push({name:e});return{buckets:r}}}class c extends t.OpenAPIRoute{static schema={operationId:"get-bucket-list-objects",tags:["Buckets"],summary:"List objects",parameters:{bucket:t.Path(String),limit:t.Query(Number,{required:!1}),prefix:t.Query(a.z.string().optional().describe("base64 encoded prefix")),cursor:t.Query(a.z.string().optional()),delimiter:t.Query(a.z.string().optional()),include:t.Query(a.z.string().array().optional())}};async handle(e,t,a,s){const r=t[s.params.bucket];return await r.list({limit:s.query.limit,prefix:s.query.prefix?decodeURIComponent(escape(atob(s.query.prefix))):void 0,cursor:s.query.cursor,delimiter:s.query.delimiter,include:s.query.include})}}class i extends t.OpenAPIRoute{static schema={operationId:"post-bucket-move-object",tags:["Buckets"],summary:"Move object",parameters:{bucket:t.Path(String)},requestBody:{oldKey:a.z.string().describe("base64 encoded file key"),newKey:a.z.string().describe("base64 encoded file key")}};async handle(e,t,a,s){if(!0===a.config.readonly)return Response.json({msg:"unauthorized"},{status:401});const r=t[s.params.bucket],o=decodeURIComponent(escape(atob(s.body.oldKey))),n=decodeURIComponent(escape(atob(s.body.newKey))),c=await r.get(o),i=await r.put(n,c.body,{customMetadata:c.customMetadata,httpMetadata:c.httpMetadata});return await r.delete(o),i}}class u extends t.OpenAPIRoute{static schema={operationId:"post-bucket-create-folder",tags:["Buckets"],summary:"Create folder",parameters:{bucket:t.Path(String)},requestBody:{key:a.z.string().describe("base64 encoded file key")}};async handle(e,t,a,s){if(!0===a.config.readonly)return Response.json({msg:"unauthorized"},{status:401});const r=t[s.params.bucket],o=decodeURIComponent(escape(atob(s.body.key)));return await r.put(o,"R2 Explorer Folder")}}class d extends t.OpenAPIRoute{static schema={operationId:"post-bucket-upload-object",tags:["Buckets"],summary:"Upload object",requestBody:new t.RequestBody({content:{"application/octet-stream":{schema:{type:"string",format:"binary"}}}}),parameters:{bucket:t.Path(String),key:t.Query(a.z.string().describe("base64 encoded file key")),customMetadata:t.Query(a.z.string().optional().describe("base64 encoded json string")),httpMetadata:t.Query(a.z.string().optional().describe("base64 encoded json string"))}};async handle(e,t,a,s){if(!0===a.config.readonly)return Response.json({msg:"unauthorized"},{status:401});const r=t[s.params.bucket];let o,n,c=decodeURIComponent(escape(atob(s.query.key)));return s.query.customMetadata&&(o=JSON.parse(decodeURIComponent(escape(atob(s.query.customMetadata))))),s.query.httpMetadata&&(n=JSON.parse(decodeURIComponent(escape(atob(s.query.httpMetadata))))),await r.put(c,e.body,{customMetadata:o,httpMetadata:n})}}class p extends t.OpenAPIRoute{static schema={operationId:"post-bucket-delete-object",tags:["Buckets"],summary:"Delete object",parameters:{bucket:t.Path(String)},requestBody:{key:a.z.string().describe("base64 encoded file key")}};async handle(e,t,a,s){if(!0===a.config.readonly)return Response.json({msg:"unauthorized"},{status:401});const r=t[s.params.bucket],o=decodeURIComponent(escape(atob(s.body.key)));return await r.delete(o),{}}}class l extends t.OpenAPIRoute{static schema={operationId:"post-multipart-create-upload",tags:["Multipart"],summary:"Create upload",parameters:{bucket:t.Path(String),key:t.Query(a.z.string().describe("base64 encoded file key")),customMetadata:t.Query(a.z.string().optional().describe("base64 encoded json string")),httpMetadata:t.Query(a.z.string().optional().describe("base64 encoded json string"))}};async handle(e,t,a,s){if(!0===a.config.readonly)return Response.json({msg:"unauthorized"},{status:401});const r=t[s.params.bucket],o=decodeURIComponent(escape(atob(s.query.key)));let n,c;return s.query.customMetadata&&(n=JSON.parse(decodeURIComponent(escape(atob(s.query.customMetadata))))),s.query.httpMetadata&&(c=JSON.parse(decodeURIComponent(escape(atob(s.query.httpMetadata))))),await r.createMultipartUpload(o,{customMetadata:n,httpMetadata:c})}}class m extends t.OpenAPIRoute{static schema={operationId:"post-multipart-part-upload",tags:["Multipart"],summary:"Part upload",requestBody:new t.RequestBody({content:{"application/octet-stream":{schema:{type:"string",format:"binary"}}}}),parameters:{bucket:t.Path(String),key:t.Query(a.z.string().describe("base64 encoded file key")),uploadId:t.Query(String),partNumber:t.Query(t.Int)}};async handle(e,t,a,s){if(!0===a.config.readonly)return Response.json({msg:"unauthorized"},{status:401});const r=t[s.params.bucket],o=decodeURIComponent(escape(atob(s.query.key))),n=r.resumeMultipartUpload(o,s.query.uploadId);try{return await n.uploadPart(s.query.partNumber,e.body)}catch(e){return new Response(e.message,{status:400})}}}class y extends t.OpenAPIRoute{static schema={operationId:"post-multipart-complete-upload",tags:["Multipart"],summary:"Complete upload",parameters:{bucket:t.Path(String)},requestBody:{uploadId:String,key:a.z.string().describe("base64 encoded file key"),parts:[{etag:String,partNumber:t.Int}]}};async handle(e,t,a,s){if(!0===a.config.readonly)return Response.json({msg:"unauthorized"},{status:401});const r=t[s.params.bucket],o=s.body.uploadId,n=decodeURIComponent(escape(atob(s.body.key))),c=s.body.parts,i=await r.resumeMultipartUpload(n,o);try{return{success:!0,str:await i.complete(c)}}catch(e){return Response.json({msg:e.message},{status:400})}}}class b extends t.OpenAPIRoute{static schema={operationId:"get-bucket-object",tags:["Buckets"],summary:"Get Object",parameters:{bucket:t.Path(String),key:t.Path(a.z.string().describe("base64 encoded file key"))},responses:{200:{description:"File binary",schema:a.z.string().openapi({format:"binary"})}}};async handle(e,t,a,s){const r=t[s.params.bucket],o=decodeURIComponent(escape(atob(s.params.key))),n=await r.get(o);if(null===n)return Response.json({msg:"Object Not Found"},{status:404});const c=new Headers;return n.writeHttpMetadata(c),c.set("etag",n.httpEtag),c.set("Content-Disposition",`attachment; filename="${o.split("/").pop()}"`),new Response(n.body,{headers:c})}}class g extends t.OpenAPIRoute{static schema={operationId:"Head-bucket-object",tags:["Buckets"],summary:"Get Object",parameters:{bucket:t.Path(String),key:t.Query(a.z.string().describe("base64 encoded file key"))}};async handle(e,t,a,s){const r=t[s.params.bucket],o=decodeURIComponent(escape(atob(s.params.key))),n=await r.head(o);return null===n?Response.json({msg:"Object Not Found"},{status:404}):n}}class h extends t.OpenAPIRoute{static schema={operationId:"post-bucket-put-object-metadata",tags:["Buckets"],summary:"Update object metadata",parameters:{bucket:t.Path(String),key:t.Path(a.z.string().describe("base64 encoded file key"))},requestBody:{customMetadata:a.z.record(a.z.string(),a.z.any())}};async handle(e,t,a,s){if(!0===a.config.readonly)return Response.json({msg:"unauthorized"},{status:401});const r=t[s.params.bucket],o=decodeURIComponent(escape(atob(s.params.key))),n=await r.get(o);return await r.put(o,n.body,{customMetadata:s.body.customMetadata})}}const f=t.OpenAPIRouter({base:"/api/buckets",raiseUnknownParameters:s,generateOperationIds:r});f.get("",n),f.get("/:bucket",c),f.post("/:bucket/move",i),f.post("/:bucket/folder",u),f.post("/:bucket/upload",d),f.post("/:bucket/multipart/create",l),f.post("/:bucket/multipart/upload",m),f.post("/:bucket/multipart/complete",y),f.post("/:bucket/delete",p),f.head("/:bucket/:key",g),f.get("/:bucket/:key",b),f.post("/:bucket/:key",h);let k={},w=0;function R(e){return`https://${e}.cloudflareaccess.com`}async function I(e,t,a){let s=!1;try{s=await async function(e,t){const a=function(e){const t=e.headers.get("cf-access-jwt-assertion");if(!t)return null;return t.trim()}(e);if(null===a)return!1;(0===Object.keys(k).length||Math.floor(Date.now()/1e3)<w)&&(k=await async function(e){let t=`${R(e.cfAccessTeamName)}/cdn-cgi/access/certs`;const a=await fetch(t,{method:"GET",cf:{cacheTtlByStatus:{"200-299":30,"300-599":0}},headers:{"Content-Type":"application/json; charset=UTF-8"}}),s=await a.json();w=Math.floor(Date.now()/1e3)+3600;const r={};for(const e of s.keys)r[e.kid]=await crypto.subtle.importKey("jwk",e,{name:"RSASSA-PKCS1-v1_5",hash:"SHA-256"},!1,["verify"]);return r}(t));let s;try{s=function(e){const t=e.split("."),a=JSON.parse(atob(t[0])),s=JSON.parse(atob(t[1])),r=atob(t[2].replace(/_/g,"/").replace(/-/g,"+"));return{header:a,payload:s,signature:r,raw:{header:t[0],payload:t[1],signature:t[2]}}}(a)}catch(e){return!1}const r=new Date(1e3*s.payload.exp),o=new Date(Date.now());if(r<=o)return console.log("expired token"),!1;if(s.payload?.iss!==R(t.cfAccessTeamName))return console.log("invalid access signer"),!1;if(!async function(e){const t=(new TextEncoder).encode([e.raw.header,e.raw.payload].join(".")),a=new Uint8Array(Array.from(e.signature).map((e=>e.charCodeAt(0))));for(const e of Object.values(k)){if(await A(e,a,t))return!0}return!1}(s))return!1;return s}(e,a.config)}catch(e){}if(!1===s)return Response.json({success:!1,errors:[{code:1e4,message:"Authentication error! Verify that the Cloudflare Access Team name is correct"}]},{status:401});a.username=s.payload.email}async function A(e,t,a){return crypto.subtle.verify("RSASSA-PKCS1-v1_5",e,t,a)}async function j(e,t,a){const s=caches.default;let r,o=new URL(e.url).pathname;if(o.includes(".")||(o="/"),!1!==a.config.cacheAssets&&(r=await s.match(e),r))return r;let n="https://demo.r2explorer.dev";a.config?.dashboardUrl&&(n=a.config.dashboardUrl.endsWith("/")?a.config.dashboardUrl.slice(0,-1):a.config.dashboardUrl);const c=await fetch(`${n}${o}`);return r=new Response(await c.body,{status:c.status,headers:{"Content-Type":c.headers.get("Content-Type"),"Access-Control-Allow-Origin":"*","Cache-Control":"max-age: 300"}}),200===c.status&&!1!==a.config.cacheAssets&&a.executionContext.waitUntil(s.put(e,r.clone())),r}const x=require("postal-mime/dist/node").postalMime.default;async function M(e,t,a){let s;if(a.config?.emailRouting?.targetBucket&&t[a.config.emailRouting.targetBucket]&&(s=t[a.config.emailRouting.targetBucket]),!s)for(const[e,a]of Object.entries(t))if(a.get&&a.put){s=a;break}const r=await async function(e,t){let a=new Uint8Array(t),s=0;const r=e.getReader();for(;;){const{done:e,value:t}=await r.read();if(e)break;a.set(t,s),s+=t.length}return a}(e.raw,e.rawSize),o=new x,n=await o.parse(r),c=`${Math.floor(Date.now())}-${crypto.randomUUID()}`;await s.put(`.r2-explorer/emails/inbox/${c}.json`,JSON.stringify(n),{customMetadata:{subject:n.subject,from_address:n.from?.address,from_name:n.from?.name,to_address:n.to.length>0?n.to[0].address:null,to_name:n.to.length>0?n.to[0].name:null,has_attachments:n.attachments.length>0,read:!1,timestamp:Date.now()}});for(const e of n.attachments)await s.put(`.r2-explorer/emails/inbox/${c}/${e.filename}`,e.content)}class P extends t.OpenAPIRoute{static schema={operationId:"get-server-info",tags:["Server"],summary:"Get server info"};async handle(e,t,a,s){const r={...a.config};return delete r.basicAuth,{version:o,config:r,user:{username:a.username}}}}const C=t.OpenAPIRouter({base:"/api/server",raiseUnknownParameters:s,generateOperationIds:r});async function O(e,t,a){let s,r=!1;try{r=function(e){const t=e.headers.get("Authorization");if(!t)return null;return atob(t.replace("Basic","").trim())}(e).split(":"),Array.isArray(a.config.basicAuth)||(a.config.basicAuth=[a.config.basicAuth]);for(const e of a.config.basicAuth)if(e.username===r[0]&&e.password===r[1]){s=e.username;break}}catch(e){}if(!1===r||void 0===s)return Response.json({success:!1,errors:[{code:1e4,message:"Authentication error! This server requires Basic Auth"}]},{status:401});a.username=s}C.get("/config",P),exports.R2Explorer=function(a){!1!==(a=a||{}).readonly&&(a.readonly=!0);const s={info:{title:"R2 Explorer API",version:o}};a.basicAuth&&(s.security=[{basicAuth:[]}]);const r=t.OpenAPIRouter({schema:s}),{preflight:n,corsify:c}=e.createCors();return!0===a.cors&&r.all("/api*",n),a.cfAccessTeamName&&r.all("*",I),a.basicAuth&&(r.registry.registerComponent("securitySchemes","basicAuth",{type:"http",scheme:"basic"}),r.all("/api*",O)),r.all("/api/server/*",C),r.all("/api/buckets/*",f),r.original.get("*",j),r.all("*",(()=>Response.json({msg:"404, not found!"},{status:404}))),{async email(e,t,s){await M(e,t,{executionContext:s,config:a})},async fetch(e,t,s){let o=await r.handle(e,t,{executionContext:s,config:a});return!0===a.cors&&(o=c(o)),o}}};
|
|
1
|
+
var __create = Object.create;
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
6
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
var __export = (target, all) => {
|
|
8
|
+
for (var name in all)
|
|
9
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
|
+
};
|
|
11
|
+
var __copyProps = (to, from, except, desc) => {
|
|
12
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
13
|
+
for (let key of __getOwnPropNames(from))
|
|
14
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
15
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
16
|
+
}
|
|
17
|
+
return to;
|
|
18
|
+
};
|
|
19
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
20
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
21
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
22
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
23
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
24
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
25
|
+
mod
|
|
26
|
+
));
|
|
27
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
28
|
+
|
|
29
|
+
// src/index.ts
|
|
30
|
+
var src_exports = {};
|
|
31
|
+
__export(src_exports, {
|
|
32
|
+
R2Explorer: () => R2Explorer
|
|
33
|
+
});
|
|
34
|
+
module.exports = __toCommonJS(src_exports);
|
|
35
|
+
var import_cloudflare_access = require("@hono/cloudflare-access");
|
|
36
|
+
var import_chanfana15 = require("chanfana");
|
|
37
|
+
var import_hono = require("hono");
|
|
38
|
+
var import_basic_auth = require("hono/basic-auth");
|
|
39
|
+
var import_cors = require("hono/cors");
|
|
40
|
+
var import_zod13 = require("zod");
|
|
41
|
+
|
|
42
|
+
// src/foundation/middlewares/readonly.ts
|
|
43
|
+
async function readOnlyMiddleware(c, next) {
|
|
44
|
+
const config = c.get("config");
|
|
45
|
+
if (config.readonly === true && !["GET", "HEAD"].includes(c.req.method)) {
|
|
46
|
+
return Response.json(
|
|
47
|
+
{
|
|
48
|
+
success: false,
|
|
49
|
+
errors: [
|
|
50
|
+
{
|
|
51
|
+
code: 10005,
|
|
52
|
+
message: "This instance is in ReadOnly Mode, no changes are allowed!"
|
|
53
|
+
}
|
|
54
|
+
]
|
|
55
|
+
},
|
|
56
|
+
{ status: 401 }
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
await next();
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// package.json
|
|
63
|
+
var version = "1.1.0";
|
|
64
|
+
|
|
65
|
+
// src/foundation/settings.ts
|
|
66
|
+
var settings = {
|
|
67
|
+
version
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
// src/modules/buckets/createFolder.ts
|
|
71
|
+
var import_chanfana = require("chanfana");
|
|
72
|
+
var import_zod = require("zod");
|
|
73
|
+
var CreateFolder = class extends import_chanfana.OpenAPIRoute {
|
|
74
|
+
schema = {
|
|
75
|
+
operationId: "post-bucket-create-folder",
|
|
76
|
+
tags: ["Buckets"],
|
|
77
|
+
summary: "Create folder",
|
|
78
|
+
request: {
|
|
79
|
+
params: import_zod.z.object({
|
|
80
|
+
bucket: import_zod.z.string()
|
|
81
|
+
}),
|
|
82
|
+
body: {
|
|
83
|
+
content: {
|
|
84
|
+
"application/json": {
|
|
85
|
+
schema: import_zod.z.object({
|
|
86
|
+
key: import_zod.z.string().describe("base64 encoded file key")
|
|
87
|
+
})
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
async handle(c) {
|
|
94
|
+
const data = await this.getValidatedData();
|
|
95
|
+
const bucket = c.env[data.params.bucket];
|
|
96
|
+
const key = decodeURIComponent(escape(atob(data.body.key)));
|
|
97
|
+
return await bucket.put(key, "R2 Explorer Folder");
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
// src/modules/buckets/deleteObject.ts
|
|
102
|
+
var import_chanfana2 = require("chanfana");
|
|
103
|
+
var import_zod2 = require("zod");
|
|
104
|
+
var DeleteObject = class extends import_chanfana2.OpenAPIRoute {
|
|
105
|
+
schema = {
|
|
106
|
+
operationId: "post-bucket-delete-object",
|
|
107
|
+
tags: ["Buckets"],
|
|
108
|
+
summary: "Delete object",
|
|
109
|
+
request: {
|
|
110
|
+
params: import_zod2.z.object({
|
|
111
|
+
bucket: import_zod2.z.string()
|
|
112
|
+
}),
|
|
113
|
+
body: {
|
|
114
|
+
content: {
|
|
115
|
+
"application/json": {
|
|
116
|
+
schema: import_zod2.z.object({
|
|
117
|
+
key: import_zod2.z.string().describe("base64 encoded file key")
|
|
118
|
+
})
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
async handle(c) {
|
|
125
|
+
const data = await this.getValidatedData();
|
|
126
|
+
const bucket = c.env[data.params.bucket];
|
|
127
|
+
const key = decodeURIComponent(escape(atob(data.body.key)));
|
|
128
|
+
await bucket.delete(key);
|
|
129
|
+
return { success: true };
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
// src/modules/buckets/getObject.ts
|
|
134
|
+
var import_chanfana3 = require("chanfana");
|
|
135
|
+
var import_zod3 = require("zod");
|
|
136
|
+
var GetObject = class extends import_chanfana3.OpenAPIRoute {
|
|
137
|
+
schema = {
|
|
138
|
+
operationId: "get-bucket-object",
|
|
139
|
+
tags: ["Buckets"],
|
|
140
|
+
summary: "Get Object",
|
|
141
|
+
request: {
|
|
142
|
+
params: import_zod3.z.object({
|
|
143
|
+
bucket: import_zod3.z.string(),
|
|
144
|
+
key: import_zod3.z.string().describe("base64 encoded file key")
|
|
145
|
+
})
|
|
146
|
+
},
|
|
147
|
+
responses: {
|
|
148
|
+
"200": {
|
|
149
|
+
description: "File binary",
|
|
150
|
+
schema: import_zod3.z.string().openapi({ format: "binary" })
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
async handle(c) {
|
|
155
|
+
const data = await this.getValidatedData();
|
|
156
|
+
const bucket = c.env[data.params.bucket];
|
|
157
|
+
let filePath;
|
|
158
|
+
try {
|
|
159
|
+
filePath = decodeURIComponent(escape(atob(data.params.key)));
|
|
160
|
+
} catch (e) {
|
|
161
|
+
filePath = decodeURIComponent(
|
|
162
|
+
escape(atob(decodeURIComponent(data.params.key)))
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
const object = await bucket.get(filePath);
|
|
166
|
+
if (object === null) {
|
|
167
|
+
return Response.json({ msg: "Object Not Found" }, { status: 404 });
|
|
168
|
+
}
|
|
169
|
+
const headers = new Headers();
|
|
170
|
+
object.writeHttpMetadata(headers);
|
|
171
|
+
headers.set("etag", object.httpEtag);
|
|
172
|
+
headers.set(
|
|
173
|
+
"Content-Disposition",
|
|
174
|
+
`attachment; filename="${filePath.split("/").pop()}"`
|
|
175
|
+
);
|
|
176
|
+
return new Response(object.body, {
|
|
177
|
+
headers
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
// src/modules/buckets/headObject.ts
|
|
183
|
+
var import_chanfana4 = require("chanfana");
|
|
184
|
+
var import_zod4 = require("zod");
|
|
185
|
+
var HeadObject = class extends import_chanfana4.OpenAPIRoute {
|
|
186
|
+
schema = {
|
|
187
|
+
operationId: "Head-bucket-object",
|
|
188
|
+
tags: ["Buckets"],
|
|
189
|
+
summary: "Get Object",
|
|
190
|
+
request: {
|
|
191
|
+
params: import_zod4.z.object({
|
|
192
|
+
bucket: import_zod4.z.string(),
|
|
193
|
+
key: import_zod4.z.string().describe("base64 encoded file key")
|
|
194
|
+
})
|
|
195
|
+
}
|
|
196
|
+
};
|
|
197
|
+
async handle(c) {
|
|
198
|
+
const data = await this.getValidatedData();
|
|
199
|
+
const bucket = c.env[data.params.bucket];
|
|
200
|
+
let filePath;
|
|
201
|
+
try {
|
|
202
|
+
filePath = decodeURIComponent(escape(atob(data.params.key)));
|
|
203
|
+
} catch (e) {
|
|
204
|
+
filePath = decodeURIComponent(
|
|
205
|
+
escape(atob(decodeURIComponent(data.params.key)))
|
|
206
|
+
);
|
|
207
|
+
}
|
|
208
|
+
const object = await bucket.head(filePath);
|
|
209
|
+
if (object === null) {
|
|
210
|
+
return Response.json({ msg: "Object Not Found" }, { status: 404 });
|
|
211
|
+
}
|
|
212
|
+
return object;
|
|
213
|
+
}
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
// src/modules/buckets/listBuckets.ts
|
|
217
|
+
var import_chanfana5 = require("chanfana");
|
|
218
|
+
var ListBuckets = class extends import_chanfana5.OpenAPIRoute {
|
|
219
|
+
schema = {
|
|
220
|
+
operationId: "get-bucket-list",
|
|
221
|
+
tags: ["Buckets"],
|
|
222
|
+
summary: "List buckets"
|
|
223
|
+
};
|
|
224
|
+
async handle(c) {
|
|
225
|
+
const buckets = [];
|
|
226
|
+
for (const [key, value] of Object.entries(c.env)) {
|
|
227
|
+
if (value.get && value.put && value.get.toString().includes("function") && value.put.toString().includes("function")) {
|
|
228
|
+
buckets.push({ name: key });
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
return {
|
|
232
|
+
buckets
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
// src/modules/buckets/listObjects.ts
|
|
238
|
+
var import_chanfana6 = require("chanfana");
|
|
239
|
+
var import_zod5 = require("zod");
|
|
240
|
+
var ListObjects = class extends import_chanfana6.OpenAPIRoute {
|
|
241
|
+
schema = {
|
|
242
|
+
operationId: "get-bucket-list-objects",
|
|
243
|
+
tags: ["Buckets"],
|
|
244
|
+
summary: "List objects",
|
|
245
|
+
request: {
|
|
246
|
+
params: import_zod5.z.object({
|
|
247
|
+
bucket: import_zod5.z.string()
|
|
248
|
+
}),
|
|
249
|
+
query: import_zod5.z.object({
|
|
250
|
+
limit: import_zod5.z.number().optional(),
|
|
251
|
+
prefix: import_zod5.z.string().nullable().optional().describe("base64 encoded prefix"),
|
|
252
|
+
cursor: import_zod5.z.string().nullable().optional(),
|
|
253
|
+
delimiter: import_zod5.z.string().nullable().optional(),
|
|
254
|
+
startAfter: import_zod5.z.string().nullable().optional(),
|
|
255
|
+
include: import_zod5.z.enum(["httpMetadata", "customMetadata"]).array().optional()
|
|
256
|
+
})
|
|
257
|
+
}
|
|
258
|
+
};
|
|
259
|
+
async handle(c) {
|
|
260
|
+
const data = await this.getValidatedData();
|
|
261
|
+
const bucket = c.env[data.params.bucket];
|
|
262
|
+
c.header("Access-Control-Allow-Credentials", "asads");
|
|
263
|
+
return await bucket.list({
|
|
264
|
+
limit: data.query.limit,
|
|
265
|
+
prefix: data.query.prefix ? decodeURIComponent(escape(atob(data.query.prefix))) : void 0,
|
|
266
|
+
cursor: data.query.cursor,
|
|
267
|
+
startAfter: data.query.startAfter,
|
|
268
|
+
delimiter: data.query.delimiter ? data.query.delimiter : "",
|
|
269
|
+
// @ts-ignore
|
|
270
|
+
include: data.query.include
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
};
|
|
274
|
+
|
|
275
|
+
// src/modules/buckets/moveObject.ts
|
|
276
|
+
var import_chanfana7 = require("chanfana");
|
|
277
|
+
var import_zod6 = require("zod");
|
|
278
|
+
var MoveObject = class extends import_chanfana7.OpenAPIRoute {
|
|
279
|
+
schema = {
|
|
280
|
+
operationId: "post-bucket-move-object",
|
|
281
|
+
tags: ["Buckets"],
|
|
282
|
+
summary: "Move object",
|
|
283
|
+
request: {
|
|
284
|
+
params: import_zod6.z.object({
|
|
285
|
+
bucket: import_zod6.z.string()
|
|
286
|
+
}),
|
|
287
|
+
body: {
|
|
288
|
+
content: {
|
|
289
|
+
"application/json": {
|
|
290
|
+
schema: import_zod6.z.object({
|
|
291
|
+
oldKey: import_zod6.z.string().describe("base64 encoded file key"),
|
|
292
|
+
newKey: import_zod6.z.string().describe("base64 encoded file key")
|
|
293
|
+
})
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
};
|
|
299
|
+
async handle(c) {
|
|
300
|
+
const data = await this.getValidatedData();
|
|
301
|
+
const bucket = c.env[data.params.bucket];
|
|
302
|
+
const oldKey = decodeURIComponent(escape(atob(data.body.oldKey)));
|
|
303
|
+
const newKey = decodeURIComponent(escape(atob(data.body.newKey)));
|
|
304
|
+
const object = await bucket.get(oldKey);
|
|
305
|
+
const resp = await bucket.put(newKey, object.body, {
|
|
306
|
+
customMetadata: object.customMetadata,
|
|
307
|
+
httpMetadata: object.httpMetadata
|
|
308
|
+
});
|
|
309
|
+
await bucket.delete(oldKey);
|
|
310
|
+
return resp;
|
|
311
|
+
}
|
|
312
|
+
};
|
|
313
|
+
|
|
314
|
+
// src/modules/buckets/multipart/completeUpload.ts
|
|
315
|
+
var import_chanfana8 = require("chanfana");
|
|
316
|
+
var import_zod7 = require("zod");
|
|
317
|
+
var CompleteUpload = class extends import_chanfana8.OpenAPIRoute {
|
|
318
|
+
schema = {
|
|
319
|
+
operationId: "post-multipart-complete-upload",
|
|
320
|
+
tags: ["Multipart"],
|
|
321
|
+
summary: "Complete upload",
|
|
322
|
+
request: {
|
|
323
|
+
params: import_zod7.z.object({
|
|
324
|
+
bucket: import_zod7.z.string()
|
|
325
|
+
}),
|
|
326
|
+
body: {
|
|
327
|
+
content: {
|
|
328
|
+
"application/json": {
|
|
329
|
+
schema: import_zod7.z.object({
|
|
330
|
+
uploadId: import_zod7.z.string(),
|
|
331
|
+
parts: import_zod7.z.object({
|
|
332
|
+
etag: import_zod7.z.string(),
|
|
333
|
+
partNumber: import_zod7.z.number().int()
|
|
334
|
+
}).array(),
|
|
335
|
+
key: import_zod7.z.string().describe("base64 encoded file key")
|
|
336
|
+
})
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
};
|
|
342
|
+
async handle(c) {
|
|
343
|
+
const data = await this.getValidatedData();
|
|
344
|
+
const bucket = c.env[data.params.bucket];
|
|
345
|
+
const uploadId = data.body.uploadId;
|
|
346
|
+
const key = decodeURIComponent(escape(atob(data.body.key)));
|
|
347
|
+
const parts = data.body.parts;
|
|
348
|
+
const multipartUpload = await bucket.resumeMultipartUpload(key, uploadId);
|
|
349
|
+
try {
|
|
350
|
+
const resp = await multipartUpload.complete(parts);
|
|
351
|
+
return {
|
|
352
|
+
success: true,
|
|
353
|
+
str: resp
|
|
354
|
+
};
|
|
355
|
+
} catch (error) {
|
|
356
|
+
return Response.json({ msg: error.message }, { status: 400 });
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
};
|
|
360
|
+
|
|
361
|
+
// src/modules/buckets/multipart/createUpload.ts
|
|
362
|
+
var import_chanfana9 = require("chanfana");
|
|
363
|
+
var import_zod8 = require("zod");
|
|
364
|
+
var CreateUpload = class extends import_chanfana9.OpenAPIRoute {
|
|
365
|
+
schema = {
|
|
366
|
+
operationId: "post-multipart-create-upload",
|
|
367
|
+
tags: ["Multipart"],
|
|
368
|
+
summary: "Create upload",
|
|
369
|
+
request: {
|
|
370
|
+
params: import_zod8.z.object({
|
|
371
|
+
bucket: import_zod8.z.string()
|
|
372
|
+
}),
|
|
373
|
+
query: import_zod8.z.object({
|
|
374
|
+
key: import_zod8.z.string().describe("base64 encoded file key"),
|
|
375
|
+
customMetadata: import_zod8.z.string().nullable().optional().describe("base64 encoded json string"),
|
|
376
|
+
httpMetadata: import_zod8.z.string().nullable().optional().describe("base64 encoded json string")
|
|
377
|
+
})
|
|
378
|
+
}
|
|
379
|
+
};
|
|
380
|
+
async handle(c) {
|
|
381
|
+
const data = await this.getValidatedData();
|
|
382
|
+
const bucket = c.env[data.params.bucket];
|
|
383
|
+
const key = decodeURIComponent(escape(atob(data.query.key)));
|
|
384
|
+
let customMetadata = void 0;
|
|
385
|
+
if (data.query.customMetadata) {
|
|
386
|
+
customMetadata = JSON.parse(
|
|
387
|
+
decodeURIComponent(escape(atob(data.query.customMetadata)))
|
|
388
|
+
);
|
|
389
|
+
}
|
|
390
|
+
let httpMetadata = void 0;
|
|
391
|
+
if (data.query.httpMetadata) {
|
|
392
|
+
httpMetadata = JSON.parse(
|
|
393
|
+
decodeURIComponent(escape(atob(data.query.httpMetadata)))
|
|
394
|
+
);
|
|
395
|
+
}
|
|
396
|
+
return await bucket.createMultipartUpload(key, {
|
|
397
|
+
customMetadata,
|
|
398
|
+
httpMetadata
|
|
399
|
+
});
|
|
400
|
+
}
|
|
401
|
+
};
|
|
402
|
+
|
|
403
|
+
// src/modules/buckets/multipart/partUpload.ts
|
|
404
|
+
var import_chanfana10 = require("chanfana");
|
|
405
|
+
var import_zod9 = require("zod");
|
|
406
|
+
var PartUpload = class extends import_chanfana10.OpenAPIRoute {
|
|
407
|
+
schema = {
|
|
408
|
+
operationId: "post-multipart-part-upload",
|
|
409
|
+
tags: ["Multipart"],
|
|
410
|
+
summary: "Part upload",
|
|
411
|
+
request: {
|
|
412
|
+
body: {
|
|
413
|
+
content: {
|
|
414
|
+
"application/octet-stream": {
|
|
415
|
+
schema: import_zod9.z.object({}).openapi({
|
|
416
|
+
type: "string",
|
|
417
|
+
format: "binary"
|
|
418
|
+
})
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
},
|
|
422
|
+
params: import_zod9.z.object({
|
|
423
|
+
bucket: import_zod9.z.string()
|
|
424
|
+
}),
|
|
425
|
+
query: import_zod9.z.object({
|
|
426
|
+
key: import_zod9.z.string().describe("base64 encoded file key"),
|
|
427
|
+
uploadId: import_zod9.z.string(),
|
|
428
|
+
partNumber: import_zod9.z.number().int()
|
|
429
|
+
})
|
|
430
|
+
}
|
|
431
|
+
};
|
|
432
|
+
async handle(c) {
|
|
433
|
+
const data = await this.getValidatedData();
|
|
434
|
+
const bucket = c.env[data.params.bucket];
|
|
435
|
+
const key = decodeURIComponent(escape(atob(data.query.key)));
|
|
436
|
+
const multipartUpload = bucket.resumeMultipartUpload(
|
|
437
|
+
key,
|
|
438
|
+
data.query.uploadId
|
|
439
|
+
);
|
|
440
|
+
try {
|
|
441
|
+
return await multipartUpload.uploadPart(
|
|
442
|
+
data.query.partNumber,
|
|
443
|
+
c.req.raw.body
|
|
444
|
+
);
|
|
445
|
+
} catch (error) {
|
|
446
|
+
return new Response(error.message, { status: 400 });
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
};
|
|
450
|
+
|
|
451
|
+
// src/modules/buckets/putMetadata.ts
|
|
452
|
+
var import_chanfana11 = require("chanfana");
|
|
453
|
+
var import_zod10 = require("zod");
|
|
454
|
+
var PutMetadata = class extends import_chanfana11.OpenAPIRoute {
|
|
455
|
+
schema = {
|
|
456
|
+
operationId: "post-bucket-put-object-metadata",
|
|
457
|
+
tags: ["Buckets"],
|
|
458
|
+
summary: "Update object metadata",
|
|
459
|
+
request: {
|
|
460
|
+
params: import_zod10.z.object({
|
|
461
|
+
bucket: import_zod10.z.string(),
|
|
462
|
+
key: import_zod10.z.string().describe("base64 encoded file key")
|
|
463
|
+
}),
|
|
464
|
+
body: {
|
|
465
|
+
content: {
|
|
466
|
+
"application/json": {
|
|
467
|
+
schema: import_zod10.z.object({
|
|
468
|
+
customMetadata: import_zod10.z.record(import_zod10.z.string(), import_zod10.z.any())
|
|
469
|
+
}).openapi("Object metadata")
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
};
|
|
475
|
+
async handle(c) {
|
|
476
|
+
const data = await this.getValidatedData();
|
|
477
|
+
const bucket = c.env[data.params.bucket];
|
|
478
|
+
let filePath;
|
|
479
|
+
try {
|
|
480
|
+
filePath = decodeURIComponent(escape(atob(data.params.key)));
|
|
481
|
+
} catch (e) {
|
|
482
|
+
filePath = decodeURIComponent(
|
|
483
|
+
escape(atob(decodeURIComponent(data.params.key)))
|
|
484
|
+
);
|
|
485
|
+
}
|
|
486
|
+
const object = await bucket.get(filePath);
|
|
487
|
+
return await bucket.put(filePath, object.body, {
|
|
488
|
+
customMetadata: data.body.customMetadata
|
|
489
|
+
});
|
|
490
|
+
}
|
|
491
|
+
};
|
|
492
|
+
|
|
493
|
+
// src/modules/buckets/putObject.ts
|
|
494
|
+
var import_chanfana12 = require("chanfana");
|
|
495
|
+
var import_zod11 = require("zod");
|
|
496
|
+
var PutObject = class extends import_chanfana12.OpenAPIRoute {
|
|
497
|
+
schema = {
|
|
498
|
+
operationId: "post-bucket-upload-object",
|
|
499
|
+
tags: ["Buckets"],
|
|
500
|
+
summary: "Upload object",
|
|
501
|
+
request: {
|
|
502
|
+
body: {
|
|
503
|
+
content: {
|
|
504
|
+
"application/octet-stream": {
|
|
505
|
+
schema: import_zod11.z.object({}).openapi({
|
|
506
|
+
type: "string",
|
|
507
|
+
format: "binary"
|
|
508
|
+
})
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
},
|
|
512
|
+
params: import_zod11.z.object({
|
|
513
|
+
bucket: import_zod11.z.string()
|
|
514
|
+
}),
|
|
515
|
+
query: import_zod11.z.object({
|
|
516
|
+
key: import_zod11.z.string().describe("base64 encoded file key"),
|
|
517
|
+
customMetadata: import_zod11.z.string().nullable().optional().describe("base64 encoded json string"),
|
|
518
|
+
httpMetadata: import_zod11.z.string().nullable().optional().describe("base64 encoded json string")
|
|
519
|
+
})
|
|
520
|
+
}
|
|
521
|
+
};
|
|
522
|
+
async handle(c) {
|
|
523
|
+
const data = await this.getValidatedData();
|
|
524
|
+
const bucket = c.env[data.params.bucket];
|
|
525
|
+
const key = decodeURIComponent(escape(atob(data.query.key)));
|
|
526
|
+
let customMetadata = void 0;
|
|
527
|
+
if (data.query.customMetadata) {
|
|
528
|
+
customMetadata = JSON.parse(
|
|
529
|
+
decodeURIComponent(escape(atob(data.query.customMetadata)))
|
|
530
|
+
);
|
|
531
|
+
}
|
|
532
|
+
let httpMetadata = void 0;
|
|
533
|
+
if (data.query.httpMetadata) {
|
|
534
|
+
httpMetadata = JSON.parse(
|
|
535
|
+
decodeURIComponent(escape(atob(data.query.httpMetadata)))
|
|
536
|
+
);
|
|
537
|
+
}
|
|
538
|
+
return await bucket.put(key, c.req.raw.body, {
|
|
539
|
+
customMetadata,
|
|
540
|
+
httpMetadata
|
|
541
|
+
});
|
|
542
|
+
}
|
|
543
|
+
};
|
|
544
|
+
|
|
545
|
+
// src/modules/dashboard.ts
|
|
546
|
+
function dashboardIndex(c) {
|
|
547
|
+
if (c.env.ASSETS === void 0) {
|
|
548
|
+
return c.text(
|
|
549
|
+
"ASSETS binding is not defined, learn more here: https://r2explorer.dev/guides/migrating-to-1.1/",
|
|
550
|
+
500
|
|
551
|
+
);
|
|
552
|
+
}
|
|
553
|
+
return c.text(
|
|
554
|
+
"ASSETS binding is not pointing to a valid dashboard, learn more here: https://r2explorer.dev/guides/migrating-to-1.1/",
|
|
555
|
+
500
|
|
556
|
+
);
|
|
557
|
+
}
|
|
558
|
+
async function dashboardRedirect(c, next) {
|
|
559
|
+
if (c.env.ASSETS === void 0) {
|
|
560
|
+
return c.text(
|
|
561
|
+
"ASSETS binding is not defined, learn more here: https://r2explorer.dev/guides/migrating-to-1.1/",
|
|
562
|
+
500
|
|
563
|
+
);
|
|
564
|
+
}
|
|
565
|
+
const url = new URL(c.req.url);
|
|
566
|
+
if (!url.pathname.includes(".")) {
|
|
567
|
+
return c.env.ASSETS.fetch(new Request(url.origin));
|
|
568
|
+
}
|
|
569
|
+
await next();
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
// src/modules/emails/receiveEmail.ts
|
|
573
|
+
var import_postal_mime = __toESM(require("postal-mime"));
|
|
574
|
+
|
|
575
|
+
// src/foundation/dates.ts
|
|
576
|
+
function getCurrentTimestampMilliseconds() {
|
|
577
|
+
return Math.floor(Date.now());
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
// src/modules/emails/receiveEmail.ts
|
|
581
|
+
async function streamToArrayBuffer(stream, streamSize) {
|
|
582
|
+
const result = new Uint8Array(streamSize);
|
|
583
|
+
let bytesRead = 0;
|
|
584
|
+
const reader = stream.getReader();
|
|
585
|
+
while (true) {
|
|
586
|
+
const { done, value } = await reader.read();
|
|
587
|
+
if (done) {
|
|
588
|
+
break;
|
|
589
|
+
}
|
|
590
|
+
result.set(value, bytesRead);
|
|
591
|
+
bytesRead += value.length;
|
|
592
|
+
}
|
|
593
|
+
return result;
|
|
594
|
+
}
|
|
595
|
+
async function receiveEmail(event, env, ctx, config) {
|
|
596
|
+
let bucket;
|
|
597
|
+
if (config?.emailRouting?.targetBucket && env[config.emailRouting.targetBucket]) {
|
|
598
|
+
bucket = env[config.emailRouting.targetBucket];
|
|
599
|
+
}
|
|
600
|
+
if (!bucket) {
|
|
601
|
+
for (const [key, value] of Object.entries(env)) {
|
|
602
|
+
if (value.get && value.put) {
|
|
603
|
+
bucket = value;
|
|
604
|
+
break;
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
const rawEmail = await streamToArrayBuffer(event.raw, event.rawSize);
|
|
609
|
+
const parser = new import_postal_mime.default();
|
|
610
|
+
const parsedEmail = await parser.parse(rawEmail);
|
|
611
|
+
const emailPath = `${getCurrentTimestampMilliseconds()}-${crypto.randomUUID()}`;
|
|
612
|
+
await bucket.put(
|
|
613
|
+
`.r2-explorer/emails/inbox/${emailPath}.json`,
|
|
614
|
+
JSON.stringify(parsedEmail),
|
|
615
|
+
{
|
|
616
|
+
customMetadata: {
|
|
617
|
+
subject: parsedEmail.subject,
|
|
618
|
+
from_address: parsedEmail.from?.address,
|
|
619
|
+
from_name: parsedEmail.from?.name,
|
|
620
|
+
to_address: parsedEmail.to.length > 0 ? parsedEmail.to[0].address : null,
|
|
621
|
+
to_name: parsedEmail.to.length > 0 ? parsedEmail.to[0].name : null,
|
|
622
|
+
has_attachments: parsedEmail.attachments.length > 0,
|
|
623
|
+
read: false,
|
|
624
|
+
timestamp: Date.now()
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
);
|
|
628
|
+
for (const att of parsedEmail.attachments) {
|
|
629
|
+
await bucket.put(
|
|
630
|
+
`.r2-explorer/emails/inbox/${emailPath}/${att.filename}`,
|
|
631
|
+
att.content
|
|
632
|
+
);
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
// src/modules/emails/sendEmail.ts
|
|
637
|
+
var import_chanfana13 = require("chanfana");
|
|
638
|
+
var import_zod12 = require("zod");
|
|
639
|
+
var SendEmail = class extends import_chanfana13.OpenAPIRoute {
|
|
640
|
+
schema = {
|
|
641
|
+
operationId: "post-email-send",
|
|
642
|
+
tags: ["Emails"],
|
|
643
|
+
summary: "Send Email",
|
|
644
|
+
request: {
|
|
645
|
+
body: {
|
|
646
|
+
content: {
|
|
647
|
+
"application/json": {
|
|
648
|
+
schema: import_zod12.z.object({
|
|
649
|
+
subject: (0, import_chanfana13.Str)({ example: "Look! No servers" }),
|
|
650
|
+
from: import_zod12.z.object({
|
|
651
|
+
email: (0, import_chanfana13.Str)({ example: "sender@example.com" }),
|
|
652
|
+
name: (0, import_chanfana13.Str)({ example: "Workers - MailChannels integration" })
|
|
653
|
+
}),
|
|
654
|
+
to: import_zod12.z.object({
|
|
655
|
+
email: (0, import_chanfana13.Str)({ example: "test@example.com" }),
|
|
656
|
+
name: (0, import_chanfana13.Str)({ example: "Test Recipient" })
|
|
657
|
+
}).array(),
|
|
658
|
+
content: import_zod12.z.object({}).catchall(import_zod12.z.string())
|
|
659
|
+
})
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
};
|
|
665
|
+
async handle(c) {
|
|
666
|
+
if (c.get("config").readonly === true)
|
|
667
|
+
return Response.json({ msg: "unauthorized" }, { status: 401 });
|
|
668
|
+
return {
|
|
669
|
+
success: false,
|
|
670
|
+
error: "unavailable"
|
|
671
|
+
};
|
|
672
|
+
}
|
|
673
|
+
};
|
|
674
|
+
|
|
675
|
+
// src/modules/server/getInfo.ts
|
|
676
|
+
var import_chanfana14 = require("chanfana");
|
|
677
|
+
var GetInfo = class extends import_chanfana14.OpenAPIRoute {
|
|
678
|
+
schema = {
|
|
679
|
+
operationId: "get-server-info",
|
|
680
|
+
tags: ["Server"],
|
|
681
|
+
summary: "Get server info"
|
|
682
|
+
};
|
|
683
|
+
async handle(c) {
|
|
684
|
+
const { basicAuth: basicAuth2, ...config } = c.get("config");
|
|
685
|
+
return {
|
|
686
|
+
version: settings.version,
|
|
687
|
+
config,
|
|
688
|
+
auth: c.get("authentication_type") ? {
|
|
689
|
+
type: c.get("authentication_type"),
|
|
690
|
+
username: c.get("authentication_username")
|
|
691
|
+
} : void 0
|
|
692
|
+
};
|
|
693
|
+
}
|
|
694
|
+
};
|
|
695
|
+
|
|
696
|
+
// src/index.ts
|
|
697
|
+
function R2Explorer(config) {
|
|
698
|
+
(0, import_chanfana15.extendZodWithOpenApi)(import_zod13.z);
|
|
699
|
+
config = config || {};
|
|
700
|
+
if (config.readonly !== false) config.readonly = true;
|
|
701
|
+
const openapiSchema = {
|
|
702
|
+
openapi: "3.1.0",
|
|
703
|
+
info: {
|
|
704
|
+
title: "R2 Explorer API",
|
|
705
|
+
version: settings.version
|
|
706
|
+
}
|
|
707
|
+
};
|
|
708
|
+
if (config.basicAuth) {
|
|
709
|
+
openapiSchema["security"] = [
|
|
710
|
+
{
|
|
711
|
+
basicAuth: []
|
|
712
|
+
}
|
|
713
|
+
];
|
|
714
|
+
}
|
|
715
|
+
const app = new import_hono.Hono();
|
|
716
|
+
app.use("*", async (c, next) => {
|
|
717
|
+
c.set("config", config);
|
|
718
|
+
await next();
|
|
719
|
+
});
|
|
720
|
+
const openapi = (0, import_chanfana15.fromHono)(app, {
|
|
721
|
+
schema: openapiSchema,
|
|
722
|
+
raiseUnknownParameters: true,
|
|
723
|
+
generateOperationIds: false
|
|
724
|
+
});
|
|
725
|
+
if (config.cors === true) {
|
|
726
|
+
app.use(
|
|
727
|
+
"*",
|
|
728
|
+
(0, import_cors.cors)({
|
|
729
|
+
origin: "*",
|
|
730
|
+
allowMethods: ["*"],
|
|
731
|
+
credentials: true
|
|
732
|
+
})
|
|
733
|
+
);
|
|
734
|
+
}
|
|
735
|
+
if (config.readonly === true) {
|
|
736
|
+
app.use("*", readOnlyMiddleware);
|
|
737
|
+
}
|
|
738
|
+
if (config.cfAccessTeamName) {
|
|
739
|
+
app.use("*", (0, import_cloudflare_access.cloudflareAccess)(config.cfAccessTeamName));
|
|
740
|
+
app.use("*", async (c, next) => {
|
|
741
|
+
c.set("authentication_type", "cloudflare-access");
|
|
742
|
+
c.set("authentication_username", c.get("accessPayload").email);
|
|
743
|
+
await next();
|
|
744
|
+
});
|
|
745
|
+
}
|
|
746
|
+
if (config.basicAuth) {
|
|
747
|
+
openapi.registry.registerComponent("securitySchemes", "basicAuth", {
|
|
748
|
+
type: "http",
|
|
749
|
+
scheme: "basic"
|
|
750
|
+
});
|
|
751
|
+
app.use(
|
|
752
|
+
"*",
|
|
753
|
+
(0, import_basic_auth.basicAuth)({
|
|
754
|
+
verifyUser: (username, password, c) => {
|
|
755
|
+
const users = Array.isArray(c.get("config").basicAuth) ? c.get("config").basicAuth : [c.get("config").basicAuth];
|
|
756
|
+
for (const user of users) {
|
|
757
|
+
if (user.username === username && user.password === password) {
|
|
758
|
+
c.set("authentication_type", "basic-auth");
|
|
759
|
+
c.set("authentication_username", username);
|
|
760
|
+
return true;
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
return false;
|
|
764
|
+
}
|
|
765
|
+
})
|
|
766
|
+
);
|
|
767
|
+
}
|
|
768
|
+
openapi.get("/api/server/config", GetInfo);
|
|
769
|
+
openapi.get("/api/buckets", ListBuckets);
|
|
770
|
+
openapi.get("/api/buckets/:bucket", ListObjects);
|
|
771
|
+
openapi.post("/api/buckets/:bucket/move", MoveObject);
|
|
772
|
+
openapi.post("/api/buckets/:bucket/folder", CreateFolder);
|
|
773
|
+
openapi.post("/api/buckets/:bucket/upload", PutObject);
|
|
774
|
+
openapi.post("/api/buckets/:bucket/multipart/create", CreateUpload);
|
|
775
|
+
openapi.post("/api/buckets/:bucket/multipart/upload", PartUpload);
|
|
776
|
+
openapi.post("/api/buckets/:bucket/multipart/complete", CompleteUpload);
|
|
777
|
+
openapi.post("/api/buckets/:bucket/delete", DeleteObject);
|
|
778
|
+
openapi.on("head", "/api/buckets/:bucket/:key", HeadObject);
|
|
779
|
+
openapi.get("/api/buckets/:bucket/:key/head", HeadObject);
|
|
780
|
+
openapi.get("/api/buckets/:bucket/:key", GetObject);
|
|
781
|
+
openapi.post("/api/buckets/:bucket/:key", PutMetadata);
|
|
782
|
+
openapi.post("/api/emails/send", SendEmail);
|
|
783
|
+
openapi.get("/", dashboardIndex);
|
|
784
|
+
openapi.get("*", dashboardRedirect);
|
|
785
|
+
app.all(
|
|
786
|
+
"*",
|
|
787
|
+
() => Response.json({ msg: "404, not found!" }, { status: 404 })
|
|
788
|
+
);
|
|
789
|
+
return {
|
|
790
|
+
// TODO: improve event type
|
|
791
|
+
async email(event, env, context) {
|
|
792
|
+
await receiveEmail(event, env, context, config);
|
|
793
|
+
},
|
|
794
|
+
async fetch(request, env, context) {
|
|
795
|
+
return app.fetch(request, env, context);
|
|
796
|
+
}
|
|
797
|
+
};
|
|
798
|
+
}
|
|
799
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
800
|
+
0 && (module.exports = {
|
|
801
|
+
R2Explorer
|
|
802
|
+
});
|