r2-explorer 0.6.0 → 1.0.1
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 +21 -0
- package/README.md +18 -103
- package/dist/index.js +1 -0
- package/dist/index.mjs +1 -0
- package/dist/src/authentication/api/access.d.ts +4 -0
- package/dist/src/buckets/api/createFolder.d.ts +8 -0
- package/dist/src/buckets/api/deleteObject.d.ts +8 -0
- package/dist/src/buckets/api/getObject.d.ts +8 -0
- package/dist/src/buckets/api/headObject.d.ts +8 -0
- package/dist/src/buckets/api/listBuckets.d.ts +10 -0
- package/dist/src/buckets/api/listObjects.d.ts +8 -0
- package/dist/src/buckets/api/moveObject.d.ts +8 -0
- package/dist/src/buckets/api/multipart/completeUpload.d.ts +11 -0
- package/dist/src/buckets/api/multipart/createUpload.d.ts +8 -0
- package/dist/src/buckets/api/multipart/partUpload.d.ts +8 -0
- package/dist/src/buckets/api/putMetadata.d.ts +8 -0
- package/dist/src/buckets/api/putObject.d.ts +8 -0
- package/dist/src/buckets/router.d.ts +1 -0
- package/dist/src/dashbord.d.ts +2 -0
- package/dist/src/dates.d.ts +2 -0
- package/dist/src/emails/receiveEmail.d.ts +2 -0
- package/dist/src/index.d.ts +5 -0
- package/dist/src/interfaces.d.ts +22 -0
- package/dist/src/server/api/getInfo.d.ts +14 -0
- package/dist/src/server/router.d.ts +1 -0
- package/dist/src/settings.d.ts +5 -0
- package/package.json +48 -29
- package/bin/r2-explorer.js +0 -70
- package/dist/umd/index.js +0 -679
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2023 Gabriel Massadas
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,89 +1,41 @@
|
|
|
1
1
|
# R2-Explorer
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
<p align="center">
|
|
4
|
+
<em>A Google Drive Interface for your Cloudflare R2 Buckets!</em>
|
|
5
|
+
</p>
|
|
4
6
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
+
<p>
|
|
8
|
+
This project is deployed/self-hosted in your own Cloudflare Account as a Worker, and no credential/token is required to
|
|
9
|
+
start using it.
|
|
10
|
+
</p>
|
|
7
11
|
|
|
8
|
-
|
|
12
|
+
---
|
|
9
13
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
14
|
+
**Documentation**: <a href="https://r2explorer.dev" target="_blank">https://r2explorer.dev</a>
|
|
15
|
+
|
|
16
|
+
**Live Demo**: <a href="https://demo.r2explorer.dev" target="_blank">https://demo.r2explorer.dev</a>
|
|
17
|
+
|
|
18
|
+
---
|
|
13
19
|
|
|
14
20
|
## Features
|
|
15
21
|
|
|
22
|
+
- [Email Explorer](https://r2explorer.dev/guides/setup-email-explorer/) (using Cloudflare Email Routing)
|
|
23
|
+
- [Cloudflare Access Authentication](https://r2explorer.dev/getting-started/security/)
|
|
16
24
|
- Very quick bucket/folder navigation
|
|
17
25
|
- pdf, image, txt, markdown, csv, etc in-browser preview
|
|
18
26
|
- Drag-and-Drop upload
|
|
19
27
|
- Multiple files and folder uploads
|
|
20
28
|
- Create folders
|
|
21
|
-
- Rename files
|
|
22
|
-
- Download files
|
|
23
|
-
- Delete files
|
|
29
|
+
- Upload/Rename/Download/Delete files
|
|
24
30
|
- Right click in file for extra options
|
|
25
31
|
- Multipart upload for big files
|
|
26
|
-
- Cloudflare Access validation using jwt
|
|
27
|
-
|
|
28
|
-
## Configurations
|
|
29
|
-
|
|
30
|
-
These options are defined in the `index.js` file, in the `R2Explorer({ readonly: false, ... })`.
|
|
31
|
-
|
|
32
|
-
| Name | Type(s) | Description | Examples |
|
|
33
|
-
|--------------------|--------------------------|-----------------------------------------------------------------------|-----------------------------------------------------------|
|
|
34
|
-
| `readonly` | `boolean` or `undefined` | Controls the write access globally, default: `true` | `true` |
|
|
35
|
-
| `cors` | `boolean` or `undefined` | Enables or disables CORS access to the internal API, default: `false` | `true` |
|
|
36
|
-
| `cfAccessTeamName` | `string` or `undefined` | When set enforces Cloudflare Access in all requests | `radar` (taken from https://radar.cloudflareaccess.com/) |
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
## FAQ
|
|
40
|
-
|
|
41
|
-
Q. Is there any Authentication for r2-explorer?
|
|
42
|
-
|
|
43
|
-
A. No. If you want authenticated access, you must
|
|
44
|
-
setup [Cloudflare Access](https://www.cloudflare.com/products/zero-trust/access/) in your account.
|
|
45
|
-
Access is free up to 50 users.
|
|
46
|
-
|
|
47
|
-
___
|
|
48
|
-
|
|
49
|
-
Q. Can i upload files bigger than 100MB?
|
|
50
|
-
|
|
51
|
-
A. Yes! R2-Explorer now
|
|
52
|
-
support's [Multipart Upload](https://developers.cloudflare.com/r2/data-access/workers-api/workers-multipart-usage/),
|
|
53
|
-
that splits the files you are uploading in about 95MB chunks for uploading within the Cloudflare 100MB uploading limit.
|
|
54
32
|
|
|
55
33
|
## Getting Started
|
|
56
34
|
|
|
57
35
|
Run this command to get an example project setup
|
|
58
36
|
|
|
59
37
|
```bash
|
|
60
|
-
|
|
61
|
-
```
|
|
62
|
-
|
|
63
|
-
Change into the newly created directory and install the packages
|
|
64
|
-
|
|
65
|
-
```bash
|
|
66
|
-
cd my-r2-explorer
|
|
67
|
-
npm install
|
|
68
|
-
```
|
|
69
|
-
|
|
70
|
-
Update the `wrangler.toml` with your R2 Buckets (tip: you can setup as many Buckets as your want)
|
|
71
|
-
|
|
72
|
-
```
|
|
73
|
-
- wrangler.toml -
|
|
74
|
-
...
|
|
75
|
-
[[r2_buckets]]
|
|
76
|
-
binding = 'my-bucket-name'
|
|
77
|
-
bucket_name = 'my-bucket-name'
|
|
78
|
-
preview_bucket_name = 'my-bucket-name'
|
|
79
|
-
```
|
|
80
|
-
|
|
81
|
-
If you want to be able to upload/modify your buckets, you must update the `readonly` flag in `src/index.ts` file.
|
|
82
|
-
|
|
83
|
-
After that just run publish and the project will be up and running for you and everyone you invite to use the Buckets
|
|
84
|
-
|
|
85
|
-
```bash
|
|
86
|
-
wrangler publish
|
|
38
|
+
npm create r2-explorer@latest
|
|
87
39
|
```
|
|
88
40
|
|
|
89
41
|
## Upgrading your installation
|
|
@@ -108,46 +60,9 @@ wrangler publish
|
|
|
108
60
|
- Image thumbnail's using Cloudflare workers
|
|
109
61
|
- Tooltip when hovering a file with absolute time in "x days time ago" format
|
|
110
62
|
- Automatically load more files, when the bottom is reached (current limit is 1000 files)
|
|
111
|
-
-
|
|
112
|
-
- set file navigation in the url to allow direct share of a specific file
|
|
113
|
-
- only support previews to files under 100mb
|
|
63
|
+
- bundle bootstrap icons instead of importing
|
|
114
64
|
|
|
115
65
|
## Known issues
|
|
116
66
|
|
|
117
67
|
- Rename files with special characters is not possible with
|
|
118
68
|
current [sdk issue here](https://github.com/aws/aws-sdk-js/issues/1949)
|
|
119
|
-
|
|
120
|
-
## Images
|
|
121
|
-
|
|
122
|
-
Home Page
|
|
123
|
-

|
|
124
|
-
|
|
125
|
-
Image Previewer
|
|
126
|
-

|
|
127
|
-
|
|
128
|
-
Pdf Previewer
|
|
129
|
-

|
|
130
|
-
|
|
131
|
-
New Folder
|
|
132
|
-

|
|
133
|
-
|
|
134
|
-
Uploading Files
|
|
135
|
-

|
|
136
|
-
|
|
137
|
-
### Compiles and hot-reloads for development
|
|
138
|
-
|
|
139
|
-
```
|
|
140
|
-
npm run serve
|
|
141
|
-
```
|
|
142
|
-
|
|
143
|
-
### Compiles and minifies for production
|
|
144
|
-
|
|
145
|
-
```
|
|
146
|
-
npm run build
|
|
147
|
-
```
|
|
148
|
-
|
|
149
|
-
### Lints and fixes files
|
|
150
|
-
|
|
151
|
-
```
|
|
152
|
-
npm run lint
|
|
153
|
-
```
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
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,o=!1,n="1.0.1";class r extends t.OpenAPIRoute{static schema={operationId:"get-bucket-list",tags:["Buckets"],summary:"List buckets"};async handle(e,t,a,s){const o=[];for(const[e,a]of Object.entries(t))a.get&&a.put&&o.push({name:e});return{buckets:o}}}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(a.z.number().optional()),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 o=t[s.params.bucket];return await o.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().optional().describe("base64 encoded file key"),newKey:a.z.string().optional().describe("base64 encoded file key")}};async handle(e,t,a,s){if(!0===a.config.readonly)return Response.json({msg:"unauthorized"},{status:401});const o=t[s.params.bucket],n=decodeURIComponent(escape(atob(s.body.oldKey))),r=decodeURIComponent(escape(atob(s.body.newKey))),c=await o.get(n),i=await o.put(r,c.body,{customMetadata:c.customMetadata,httpMetadata:c.httpMetadata});return await o.delete(n),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().optional().describe("base64 encoded file key")}};async handle(e,t,a,s){if(!0===a.config.readonly)return Response.json({msg:"unauthorized"},{status:401});const o=t[s.params.bucket],n=decodeURIComponent(escape(atob(s.body.key)));return await o.put(n,"R2 Explorer Folder")}}class d extends t.OpenAPIRoute{static schema={operationId:"post-bucket-upload-object",tags:["Buckets"],summary:"Upload object",parameters:{bucket:t.Path(String),key:t.Query(a.z.string().optional().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 o=t[s.params.bucket];let n,r,c=decodeURIComponent(escape(atob(s.query.key)));return s.query.customMetadata&&(n=JSON.parse(decodeURIComponent(escape(atob(s.query.customMetadata))))),s.query.httpMetadata&&(r=JSON.parse(decodeURIComponent(escape(atob(s.query.httpMetadata))))),await o.put(c,e.body,{customMetadata:n,httpMetadata:r})}}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().optional().describe("base64 encoded file key")}};async handle(e,t,a,s){if(!0===a.config.readonly)return Response.json({msg:"unauthorized"},{status:401});const o=t[s.params.bucket],n=decodeURIComponent(escape(atob(s.body.key)));return await o.delete(n)}}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().optional().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 o=t[s.params.bucket],n=decodeURIComponent(escape(atob(s.query.key)));let r,c;return s.query.customMetadata&&(r=JSON.parse(decodeURIComponent(escape(atob(s.query.customMetadata))))),s.query.httpMetadata&&(c=JSON.parse(decodeURIComponent(escape(atob(s.query.httpMetadata))))),await o.createMultipartUpload(n,{customMetadata:r,httpMetadata:c})}}class m extends t.OpenAPIRoute{static schema={operationId:"post-multipart-part-upload",tags:["Multipart"],summary:"Part upload",parameters:{bucket:t.Path(String),key:t.Query(a.z.string().optional().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 o=t[s.params.bucket],n=decodeURIComponent(escape(atob(s.query.key))),r=o.resumeMultipartUpload(n,s.query.uploadId);try{return await r.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().optional().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 o=t[s.params.bucket],n=s.body.uploadId,r=decodeURIComponent(escape(atob(s.body.key))),c=s.body.parts,i=await o.resumeMultipartUpload(r,n);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().optional().describe("base64 encoded file key"))},responses:{200:{description:"File binary",schema:a.z.string().openapi({format:"binary"})}}};async handle(e,t,a,s){const o=t[s.params.bucket],n=decodeURIComponent(escape(atob(s.params.key))),r=await o.get(n);if(null===r)return Response.json({msg:"Object Not Found"},{status:404});const c=new Headers;return r.writeHttpMetadata(c),c.set("etag",r.httpEtag),c.set("Content-Disposition",`attachment; filename="${n.split("/").pop()}"`),new Response(r.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().optional().describe("base64 encoded file key"))}};async handle(e,t,a,s){const o=t[s.params.bucket],n=decodeURIComponent(escape(atob(s.params.key))),r=await o.head(n);return null===r?Response.json({msg:"Object Not Found"},{status:404}):r}}class f 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 o=t[s.params.bucket],n=decodeURIComponent(escape(atob(s.params.key))),r=await o.get(n);return await o.put(n,r.body,{customMetadata:s.body.customMetadata})}}const h=t.OpenAPIRouter({base:"/api/buckets",raiseUnknownParameters:s,generateOperationIds:o});h.get("",r),h.get("/:bucket",c),h.post("/:bucket/move",i),h.post("/:bucket/folder",u),h.post("/:bucket/upload",d),h.post("/:bucket/multipart/create",l),h.post("/:bucket/multipart/upload",m),h.post("/:bucket/multipart/complete",y),h.post("/:bucket/delete",p),h.head("/:bucket/:key",g),h.get("/:bucket/:key",b),h.post("/:bucket/:key",f);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 o={};for(const e of s.keys)o[e.kid]=await crypto.subtle.importKey("jwk",e,{name:"RSASSA-PKCS1-v1_5",hash:"SHA-256"},!1,["verify"]);return o}(t));let s;try{s=function(e){const t=e.split("."),a=JSON.parse(atob(t[0])),s=JSON.parse(atob(t[1])),o=atob(t[2].replace(/_/g,"/").replace(/-/g,"+"));return{header:a,payload:s,signature:o,raw:{header:t[0],payload:t[1],signature:t[2]}}}(a)}catch(e){return!1}const o=new Date(1e3*s.payload.exp),n=new Date(Date.now());if(o<=n)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 j(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 j(e,t,a){return crypto.subtle.verify("RSASSA-PKCS1-v1_5",e,t,a)}async function x(e,t,a){const s=caches.default;let o,n=new URL(e.url).pathname;if(n.includes(".")||(n="/"),!1!==a.config.cacheAssets&&(o=await s.match(e),o))return o;let r="https://demo.r2explorer.dev";a.config?.dashboardUrl&&(r=a.config.dashboardUrl.endsWith("/")?a.config.dashboardUrl.slice(0,-1):a.config.dashboardUrl);const c=await fetch(`${r}${n}`);return o=new Response(await c.text(),{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,o.clone())),o}const M=require("postal-mime/dist/node").postalMime.default;async function P(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 o=await async function(e,t){let a=new Uint8Array(t),s=0;const o=e.getReader();for(;;){const{done:e,value:t}=await o.read();if(e)break;a.set(t,s),s+=t.length}return a}(e.raw,e.rawSize),n=new M,r=await n.parse(o),c=`${Math.floor(Date.now())}-${crypto.randomUUID()}`;await s.put(`.r2-explorer/emails/inbox/${c}.json`,JSON.stringify(r),{customMetadata:{subject:r.subject,from_address:r.from?.address,from_name:r.from?.name,to_address:r.to.length>0?r.to[0].address:null,to_name:r.to.length>0?r.to[0].name:null,has_attachments:r.attachments.length>0,read:!1}});for(const e of r.attachments)await s.put(`.r2-explorer/emails/inbox/${c}/${e.filename}`,e.content)}class A extends t.OpenAPIRoute{static schema={operationId:"get-server-info",tags:["Server"],summary:"Get server info"};async handle(e,t,a,s){return{version:n,config:a.config,user:{username:a.username}}}}const O=t.OpenAPIRouter({base:"/api/server",raiseUnknownParameters:s,generateOperationIds:o});O.get("/config",A),exports.R2Explorer=function(a){!1!==(a=a||{}).readonly&&(a.readonly=!0);const s=t.OpenAPIRouter({schema:{info:{title:"R2 Explorer API",version:n}}}),{preflight:o,corsify:r}=e.createCors();return a.cfAccessTeamName&&s.all("*",I),!0===a.cors&&s.all("*",o),s.all("/api/server/*",O),s.all("/api/buckets/*",h),s.original.get("*",x),s.all("*",(()=>Response.json({msg:"404, not found!"},{status:404}))),{async email(e,t,s){await P(e,t,{executionContext:s,config:a})},async fetch(e,t,o){let n=await s.handle(e,t,{executionContext:o,config:a});return!0===a.cors&&(n=r(n)),n}}};
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{createCors as e}from"itty-router";import{OpenAPIRoute as t,Path as a,Query as s,Int as o,OpenAPIRouter as n}from"@cloudflare/itty-router-openapi";import{z as r}from"zod";const c=!0,i=!1,d="1.0.1";class u extends t{static schema={operationId:"get-bucket-list-objects",tags:["Buckets"],summary:"List objects",parameters:{bucket:a(String),limit:s(r.number().optional()),prefix:s(r.string().optional().describe("base64 encoded prefix")),cursor:s(r.string().optional()),delimiter:s(r.string().optional()),include:s(r.string().array().optional())}};async handle(e,t,a,s){const o=t[s.params.bucket];return await o.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 p extends t{static schema={operationId:"post-bucket-move-object",tags:["Buckets"],summary:"Move object",parameters:{bucket:a(String)},requestBody:{oldKey:r.string().optional().describe("base64 encoded file key"),newKey:r.string().optional().describe("base64 encoded file key")}};async handle(e,t,a,s){if(!0===a.config.readonly)return Response.json({msg:"unauthorized"},{status:401});const o=t[s.params.bucket],n=decodeURIComponent(escape(atob(s.body.oldKey))),r=decodeURIComponent(escape(atob(s.body.newKey))),c=await o.get(n),i=await o.put(r,c.body,{customMetadata:c.customMetadata,httpMetadata:c.httpMetadata});return await o.delete(n),i}}class l extends t{static schema={operationId:"post-bucket-create-folder",tags:["Buckets"],summary:"Create folder",parameters:{bucket:a(String)},requestBody:{key:r.string().optional().describe("base64 encoded file key")}};async handle(e,t,a,s){if(!0===a.config.readonly)return Response.json({msg:"unauthorized"},{status:401});const o=t[s.params.bucket],n=decodeURIComponent(escape(atob(s.body.key)));return await o.put(n,"R2 Explorer Folder")}}class m extends t{static schema={operationId:"post-bucket-upload-object",tags:["Buckets"],summary:"Upload object",parameters:{bucket:a(String),key:s(r.string().optional().describe("base64 encoded file key")),customMetadata:s(r.string().optional().describe("base64 encoded json string")),httpMetadata:s(r.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 o=t[s.params.bucket];let n,r,c=decodeURIComponent(escape(atob(s.query.key)));return s.query.customMetadata&&(n=JSON.parse(decodeURIComponent(escape(atob(s.query.customMetadata))))),s.query.httpMetadata&&(r=JSON.parse(decodeURIComponent(escape(atob(s.query.httpMetadata))))),await o.put(c,e.body,{customMetadata:n,httpMetadata:r})}}class y extends t{static schema={operationId:"post-bucket-delete-object",tags:["Buckets"],summary:"Delete object",parameters:{bucket:a(String)},requestBody:{key:r.string().optional().describe("base64 encoded file key")}};async handle(e,t,a,s){if(!0===a.config.readonly)return Response.json({msg:"unauthorized"},{status:401});const o=t[s.params.bucket],n=decodeURIComponent(escape(atob(s.body.key)));return await o.delete(n)}}class b extends t{static schema={operationId:"post-multipart-create-upload",tags:["Multipart"],summary:"Create upload",parameters:{bucket:a(String),key:s(r.string().optional().describe("base64 encoded file key")),customMetadata:s(r.string().optional().describe("base64 encoded json string")),httpMetadata:s(r.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 o=t[s.params.bucket],n=decodeURIComponent(escape(atob(s.query.key)));let r,c;return s.query.customMetadata&&(r=JSON.parse(decodeURIComponent(escape(atob(s.query.customMetadata))))),s.query.httpMetadata&&(c=JSON.parse(decodeURIComponent(escape(atob(s.query.httpMetadata))))),await o.createMultipartUpload(n,{customMetadata:r,httpMetadata:c})}}class g extends t{static schema={operationId:"post-multipart-part-upload",tags:["Multipart"],summary:"Part upload",parameters:{bucket:a(String),key:s(r.string().optional().describe("base64 encoded file key")),uploadId:s(String),partNumber:s(o)}};async handle(e,t,a,s){if(!0===a.config.readonly)return Response.json({msg:"unauthorized"},{status:401});const o=t[s.params.bucket],n=decodeURIComponent(escape(atob(s.query.key))),r=o.resumeMultipartUpload(n,s.query.uploadId);try{return await r.uploadPart(s.query.partNumber,e.body)}catch(e){return new Response(e.message,{status:400})}}}class f extends t{static schema={operationId:"post-multipart-complete-upload",tags:["Multipart"],summary:"Complete upload",parameters:{bucket:a(String)},requestBody:{uploadId:String,key:r.string().optional().describe("base64 encoded file key"),parts:[{etag:String,partNumber:o}]}};async handle(e,t,a,s){if(!0===a.config.readonly)return Response.json({msg:"unauthorized"},{status:401});const o=t[s.params.bucket],n=s.body.uploadId,r=decodeURIComponent(escape(atob(s.body.key))),c=s.body.parts,i=await o.resumeMultipartUpload(r,n);try{return{success:!0,str:await i.complete(c)}}catch(e){return Response.json({msg:e.message},{status:400})}}}class h extends t{static schema={operationId:"get-bucket-object",tags:["Buckets"],summary:"Get Object",parameters:{bucket:a(String),key:a(r.string().optional().describe("base64 encoded file key"))},responses:{200:{description:"File binary",schema:r.string().openapi({format:"binary"})}}};async handle(e,t,a,s){const o=t[s.params.bucket],n=decodeURIComponent(escape(atob(s.params.key))),r=await o.get(n);if(null===r)return Response.json({msg:"Object Not Found"},{status:404});const c=new Headers;return r.writeHttpMetadata(c),c.set("etag",r.httpEtag),c.set("Content-Disposition",`attachment; filename="${n.split("/").pop()}"`),new Response(r.body,{headers:c})}}class k extends t{static schema={operationId:"Head-bucket-object",tags:["Buckets"],summary:"Get Object",parameters:{bucket:a(String),key:s(r.string().optional().describe("base64 encoded file key"))}};async handle(e,t,a,s){const o=t[s.params.bucket],n=decodeURIComponent(escape(atob(s.params.key))),r=await o.head(n);return null===r?Response.json({msg:"Object Not Found"},{status:404}):r}}class w extends t{static schema={operationId:"post-bucket-put-object-metadata",tags:["Buckets"],summary:"Update object metadata",parameters:{bucket:a(String),key:a(r.string().describe("base64 encoded file key"))},requestBody:{customMetadata:r.record(r.string(),r.any())}};async handle(e,t,a,s){if(!0===a.config.readonly)return Response.json({msg:"unauthorized"},{status:401});const o=t[s.params.bucket],n=decodeURIComponent(escape(atob(s.params.key))),r=await o.get(n);return await o.put(n,r.body,{customMetadata:s.body.customMetadata})}}const j=n({base:"/api/buckets",raiseUnknownParameters:c,generateOperationIds:i});j.get("",class extends t{static schema={operationId:"get-bucket-list",tags:["Buckets"],summary:"List buckets"};async handle(e,t,a,s){const o=[];for(const[e,a]of Object.entries(t))a.get&&a.put&&o.push({name:e});return{buckets:o}}}),j.get("/:bucket",u),j.post("/:bucket/move",p),j.post("/:bucket/folder",l),j.post("/:bucket/upload",m),j.post("/:bucket/multipart/create",b),j.post("/:bucket/multipart/upload",g),j.post("/:bucket/multipart/complete",f),j.post("/:bucket/delete",y),j.head("/:bucket/:key",k),j.get("/:bucket/:key",h),j.post("/:bucket/:key",w);let R={},x=0;function I(e){return`https://${e}.cloudflareaccess.com`}async function M(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(R).length||Math.floor(Date.now()/1e3)<x)&&(R=await async function(e){let t=`${I(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();x=Math.floor(Date.now()/1e3)+3600;const o={};for(const e of s.keys)o[e.kid]=await crypto.subtle.importKey("jwk",e,{name:"RSASSA-PKCS1-v1_5",hash:"SHA-256"},!1,["verify"]);return o}(t));let s;try{s=function(e){const t=e.split("."),a=JSON.parse(atob(t[0])),s=JSON.parse(atob(t[1])),o=atob(t[2].replace(/_/g,"/").replace(/-/g,"+"));return{header:a,payload:s,signature:o,raw:{header:t[0],payload:t[1],signature:t[2]}}}(a)}catch(e){return!1}const o=new Date(1e3*s.payload.exp),n=new Date(Date.now());if(o<=n)return console.log("expired token"),!1;if(s.payload?.iss!==I(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(R)){if(await U(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 U(e,t,a){return crypto.subtle.verify("RSASSA-PKCS1-v1_5",e,t,a)}async function C(e,t,a){const s=caches.default;let o,n=new URL(e.url).pathname;if(n.includes(".")||(n="/"),!1!==a.config.cacheAssets&&(o=await s.match(e),o))return o;let r="https://demo.r2explorer.dev";a.config?.dashboardUrl&&(r=a.config.dashboardUrl.endsWith("/")?a.config.dashboardUrl.slice(0,-1):a.config.dashboardUrl);const c=await fetch(`${r}${n}`);return o=new Response(await c.text(),{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,o.clone())),o}const S=require("postal-mime/dist/node").postalMime.default;async function q(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 o=await async function(e,t){let a=new Uint8Array(t),s=0;const o=e.getReader();for(;;){const{done:e,value:t}=await o.read();if(e)break;a.set(t,s),s+=t.length}return a}(e.raw,e.rawSize),n=new S,r=await n.parse(o),c=`${Math.floor(Date.now())}-${crypto.randomUUID()}`;await s.put(`.r2-explorer/emails/inbox/${c}.json`,JSON.stringify(r),{customMetadata:{subject:r.subject,from_address:r.from?.address,from_name:r.from?.name,to_address:r.to.length>0?r.to[0].address:null,to_name:r.to.length>0?r.to[0].name:null,has_attachments:r.attachments.length>0,read:!1}});for(const e of r.attachments)await s.put(`.r2-explorer/emails/inbox/${c}/${e.filename}`,e.content)}const v=n({base:"/api/server",raiseUnknownParameters:c,generateOperationIds:i});function A(t){!1!==(t=t||{}).readonly&&(t.readonly=!0);const a=n({schema:{info:{title:"R2 Explorer API",version:d}}}),{preflight:s,corsify:o}=e();return t.cfAccessTeamName&&a.all("*",M),!0===t.cors&&a.all("*",s),a.all("/api/server/*",v),a.all("/api/buckets/*",j),a.original.get("*",C),a.all("*",(()=>Response.json({msg:"404, not found!"},{status:404}))),{async email(e,a,s){await q(e,a,{executionContext:s,config:t})},async fetch(e,s,n){let r=await a.handle(e,s,{executionContext:n,config:t});return!0===t.cors&&(r=o(r)),r}}}v.get("/config",class extends t{static schema={operationId:"get-server-info",tags:["Server"],summary:"Get server info"};async handle(e,t,a,s){return{version:d,config:a.config,user:{username:a.username}}}});export{A as R2Explorer};
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
/// <reference types="@cloudflare/workers-types" />
|
|
2
|
+
import { Context, R2ExplorerConfig } from "../../interfaces";
|
|
3
|
+
export declare function authenticateUser(request: any, env: any, context: Context): Promise<Response>;
|
|
4
|
+
export declare function isValidJwt(request: any, config: R2ExplorerConfig): Promise<any>;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/// <reference types="@cloudflare/workers-types" />
|
|
2
|
+
import { OpenAPIRoute } from "@cloudflare/itty-router-openapi";
|
|
3
|
+
import { Context } from "../../interfaces";
|
|
4
|
+
import { OpenAPIRouteSchema } from "@cloudflare/itty-router-openapi/dist/src/types";
|
|
5
|
+
export declare class CreateFolder extends OpenAPIRoute {
|
|
6
|
+
static schema: OpenAPIRouteSchema;
|
|
7
|
+
handle(request: Request, env: any, context: Context, data: any): Promise<any>;
|
|
8
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/// <reference types="@cloudflare/workers-types" />
|
|
2
|
+
import { OpenAPIRoute } from "@cloudflare/itty-router-openapi";
|
|
3
|
+
import { Context } from "../../interfaces";
|
|
4
|
+
import { OpenAPIRouteSchema } from "@cloudflare/itty-router-openapi/dist/src/types";
|
|
5
|
+
export declare class DeleteObject extends OpenAPIRoute {
|
|
6
|
+
static schema: OpenAPIRouteSchema;
|
|
7
|
+
handle(request: Request, env: any, context: Context, data: any): Promise<any>;
|
|
8
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/// <reference types="@cloudflare/workers-types" />
|
|
2
|
+
import { OpenAPIRoute } from "@cloudflare/itty-router-openapi";
|
|
3
|
+
import { Context } from "../../interfaces";
|
|
4
|
+
import { OpenAPIRouteSchema } from "@cloudflare/itty-router-openapi/dist/src/types";
|
|
5
|
+
export declare class GetObject extends OpenAPIRoute {
|
|
6
|
+
static schema: OpenAPIRouteSchema;
|
|
7
|
+
handle(request: Request, env: any, context: Context, data: any): Promise<Response>;
|
|
8
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/// <reference types="@cloudflare/workers-types" />
|
|
2
|
+
import { OpenAPIRoute } from "@cloudflare/itty-router-openapi";
|
|
3
|
+
import { Context } from "../../interfaces";
|
|
4
|
+
import { OpenAPIRouteSchema } from "@cloudflare/itty-router-openapi/dist/src/types";
|
|
5
|
+
export declare class HeadObject extends OpenAPIRoute {
|
|
6
|
+
static schema: OpenAPIRouteSchema;
|
|
7
|
+
handle(request: Request, env: any, context: Context, data: any): Promise<any>;
|
|
8
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/// <reference types="@cloudflare/workers-types" />
|
|
2
|
+
import { OpenAPIRoute } from "@cloudflare/itty-router-openapi";
|
|
3
|
+
import { Context } from "../../interfaces";
|
|
4
|
+
import { OpenAPIRouteSchema } from "@cloudflare/itty-router-openapi/dist/src/types";
|
|
5
|
+
export declare class ListBuckets extends OpenAPIRoute {
|
|
6
|
+
static schema: OpenAPIRouteSchema;
|
|
7
|
+
handle(request: Request, env: any, context: Context, data: any): Promise<{
|
|
8
|
+
buckets: any[];
|
|
9
|
+
}>;
|
|
10
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/// <reference types="@cloudflare/workers-types" />
|
|
2
|
+
import { OpenAPIRoute } from "@cloudflare/itty-router-openapi";
|
|
3
|
+
import { Context } from "../../interfaces";
|
|
4
|
+
import { OpenAPIRouteSchema } from "@cloudflare/itty-router-openapi/dist/src/types";
|
|
5
|
+
export declare class ListObjects extends OpenAPIRoute {
|
|
6
|
+
static schema: OpenAPIRouteSchema;
|
|
7
|
+
handle(request: Request, env: any, context: Context, data: any): Promise<any>;
|
|
8
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/// <reference types="@cloudflare/workers-types" />
|
|
2
|
+
import { OpenAPIRoute } from "@cloudflare/itty-router-openapi";
|
|
3
|
+
import { Context } from "../../interfaces";
|
|
4
|
+
import { OpenAPIRouteSchema } from "@cloudflare/itty-router-openapi/dist/src/types";
|
|
5
|
+
export declare class MoveObject extends OpenAPIRoute {
|
|
6
|
+
static schema: OpenAPIRouteSchema;
|
|
7
|
+
handle(request: Request, env: any, context: Context, data: any): Promise<any>;
|
|
8
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/// <reference types="@cloudflare/workers-types" />
|
|
2
|
+
import { OpenAPIRoute } from "@cloudflare/itty-router-openapi";
|
|
3
|
+
import { Context } from "../../../interfaces";
|
|
4
|
+
import { OpenAPIRouteSchema } from "@cloudflare/itty-router-openapi/dist/src/types";
|
|
5
|
+
export declare class CompleteUpload extends OpenAPIRoute {
|
|
6
|
+
static schema: OpenAPIRouteSchema;
|
|
7
|
+
handle(request: Request, env: any, context: Context, data: any): Promise<Response | {
|
|
8
|
+
success: boolean;
|
|
9
|
+
str: any;
|
|
10
|
+
}>;
|
|
11
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/// <reference types="@cloudflare/workers-types" />
|
|
2
|
+
import { OpenAPIRoute } from "@cloudflare/itty-router-openapi";
|
|
3
|
+
import { Context } from "../../../interfaces";
|
|
4
|
+
import { OpenAPIRouteSchema } from "@cloudflare/itty-router-openapi/dist/src/types";
|
|
5
|
+
export declare class CreateUpload extends OpenAPIRoute {
|
|
6
|
+
static schema: OpenAPIRouteSchema;
|
|
7
|
+
handle(request: Request, env: any, context: Context, data: any): Promise<any>;
|
|
8
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/// <reference types="@cloudflare/workers-types" />
|
|
2
|
+
import { OpenAPIRoute } from "@cloudflare/itty-router-openapi";
|
|
3
|
+
import { Context } from "../../../interfaces";
|
|
4
|
+
import { OpenAPIRouteSchema } from "@cloudflare/itty-router-openapi/dist/src/types";
|
|
5
|
+
export declare class PartUpload extends OpenAPIRoute {
|
|
6
|
+
static schema: OpenAPIRouteSchema;
|
|
7
|
+
handle(request: Request, env: any, context: Context, data: any): Promise<any>;
|
|
8
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/// <reference types="@cloudflare/workers-types" />
|
|
2
|
+
import { OpenAPIRoute } from "@cloudflare/itty-router-openapi";
|
|
3
|
+
import { Context } from "../../interfaces";
|
|
4
|
+
import { OpenAPIRouteSchema } from "@cloudflare/itty-router-openapi/dist/src/types";
|
|
5
|
+
export declare class PutMetadata extends OpenAPIRoute {
|
|
6
|
+
static schema: OpenAPIRouteSchema;
|
|
7
|
+
handle(request: Request, env: any, context: Context, data: any): Promise<any>;
|
|
8
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/// <reference types="@cloudflare/workers-types" />
|
|
2
|
+
import { OpenAPIRoute } from "@cloudflare/itty-router-openapi";
|
|
3
|
+
import { Context } from "../../interfaces";
|
|
4
|
+
import { OpenAPIRouteSchema } from "@cloudflare/itty-router-openapi/dist/src/types";
|
|
5
|
+
export declare class PutObject extends OpenAPIRoute {
|
|
6
|
+
static schema: OpenAPIRouteSchema;
|
|
7
|
+
handle(request: Request, env: any, context: Context, data: any): Promise<any>;
|
|
8
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const bucketsRouter: import("@cloudflare/itty-router-openapi").OpenAPIRouterType<import("@cloudflare/itty-router-openapi").Route, any[]>;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/// <reference types="@cloudflare/workers-types" />
|
|
2
|
+
export interface BasicAuth {
|
|
3
|
+
username: string;
|
|
4
|
+
password: string;
|
|
5
|
+
}
|
|
6
|
+
export interface R2ExplorerConfig {
|
|
7
|
+
readonly?: boolean;
|
|
8
|
+
cors?: boolean;
|
|
9
|
+
cfAccessTeamName?: string;
|
|
10
|
+
dashboardUrl?: string;
|
|
11
|
+
emailRouting?: {
|
|
12
|
+
targetBucket: string;
|
|
13
|
+
};
|
|
14
|
+
showHiddenFiles?: string;
|
|
15
|
+
cacheAssets?: boolean;
|
|
16
|
+
basicAuth?: BasicAuth | BasicAuth[];
|
|
17
|
+
}
|
|
18
|
+
export interface Context {
|
|
19
|
+
config: R2ExplorerConfig;
|
|
20
|
+
username?: string;
|
|
21
|
+
executionContext: ExecutionContext;
|
|
22
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/// <reference types="@cloudflare/workers-types" />
|
|
2
|
+
import { OpenAPIRoute } from "@cloudflare/itty-router-openapi";
|
|
3
|
+
import { Context } from "../../interfaces";
|
|
4
|
+
import { OpenAPIRouteSchema } from "@cloudflare/itty-router-openapi/dist/src/types";
|
|
5
|
+
export declare class GetInfo extends OpenAPIRoute {
|
|
6
|
+
static schema: OpenAPIRouteSchema;
|
|
7
|
+
handle(request: Request, env: any, context: Context, data: any): Promise<{
|
|
8
|
+
version: string;
|
|
9
|
+
config: import("../../interfaces").R2ExplorerConfig;
|
|
10
|
+
user: {
|
|
11
|
+
username: string;
|
|
12
|
+
};
|
|
13
|
+
}>;
|
|
14
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const serverRouter: import("@cloudflare/itty-router-openapi").OpenAPIRouterType<import("@cloudflare/itty-router-openapi").Route, any[]>;
|
package/package.json
CHANGED
|
@@ -1,28 +1,48 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "r2-explorer",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"description": "A Google Drive Interface for your Cloudflare R2 Buckets",
|
|
5
|
-
"main": "dist/
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
|
+
"module": "./dist/index.mjs",
|
|
7
|
+
"types": "./dist/src/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/src/index.d.ts",
|
|
11
|
+
"import": "./dist/index.mjs",
|
|
12
|
+
"require": "./dist/index.js"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"bin",
|
|
17
|
+
"dist"
|
|
18
|
+
],
|
|
6
19
|
"scripts": {
|
|
7
20
|
"postinstallDisabled": "husky install",
|
|
8
21
|
"prepublishOnly": "pinst --disable",
|
|
9
22
|
"postpublish": "pinst --enable",
|
|
10
|
-
"build": "
|
|
11
|
-
"build:umd": "node tools/cleanup umd && rollup --config config/rollup.config.js",
|
|
23
|
+
"build": "node tools/cleanup umd && rollup --config rollup.config.js",
|
|
12
24
|
"clean": "node tools/cleanup",
|
|
13
25
|
"package": "npm run build && npm pack",
|
|
14
26
|
"test": "jest --no-cache --runInBand",
|
|
15
27
|
"test:cov": "jest --coverage --no-cache --runInBand",
|
|
16
28
|
"addscope": "node tools/packagejson name @g4brym/r2-explorer",
|
|
17
29
|
"prettify": "prettier . --write --ignore-unknown",
|
|
18
|
-
"prepareDisabled": "husky install"
|
|
30
|
+
"prepareDisabled": "husky install",
|
|
31
|
+
"watch": "npm-watch build"
|
|
32
|
+
},
|
|
33
|
+
"watch": {
|
|
34
|
+
"build": {
|
|
35
|
+
"patterns": [
|
|
36
|
+
"src"
|
|
37
|
+
],
|
|
38
|
+
"extensions": "js,ts",
|
|
39
|
+
"legacyWatch": true,
|
|
40
|
+
"runOnChangeOnly": false
|
|
41
|
+
}
|
|
19
42
|
},
|
|
20
43
|
"publishConfig": {
|
|
21
44
|
"access": "public"
|
|
22
45
|
},
|
|
23
|
-
"files": [
|
|
24
|
-
"dist"
|
|
25
|
-
],
|
|
26
46
|
"keywords": [
|
|
27
47
|
"cloudflare",
|
|
28
48
|
"worker",
|
|
@@ -45,7 +65,7 @@
|
|
|
45
65
|
],
|
|
46
66
|
"author": "Gabriel Massadas",
|
|
47
67
|
"license": "MIT",
|
|
48
|
-
"homepage": "https://
|
|
68
|
+
"homepage": "https://r2explorer.dev",
|
|
49
69
|
"repository": {
|
|
50
70
|
"type": "git",
|
|
51
71
|
"url": "git@github.com:G4brym/R2-Explorer.git"
|
|
@@ -54,30 +74,29 @@
|
|
|
54
74
|
"url": "https://github.com/G4brym/R2-Explorer/issues"
|
|
55
75
|
},
|
|
56
76
|
"devDependencies": {
|
|
57
|
-
"@
|
|
58
|
-
"@
|
|
59
|
-
"@rollup/plugin-
|
|
60
|
-
"@
|
|
61
|
-
"@
|
|
62
|
-
"
|
|
63
|
-
"
|
|
64
|
-
"
|
|
65
|
-
"
|
|
66
|
-
"
|
|
67
|
-
"eslint-plugin-prettier": "^4.0.0",
|
|
68
|
-
"husky": "^7.0.2",
|
|
69
|
-
"jest": "^28.1.2",
|
|
77
|
+
"@cloudflare/workers-types": "^4.20230518.0",
|
|
78
|
+
"@rollup/plugin-terser": "^0.4.3",
|
|
79
|
+
"@rollup/plugin-typescript": "^11.1.2",
|
|
80
|
+
"@types/node": "^18.11.18",
|
|
81
|
+
"@types/service-worker-mock": "^2.0.1",
|
|
82
|
+
"eslint": "^8.18.0",
|
|
83
|
+
"eslint-config-prettier": "^8.5.0",
|
|
84
|
+
"eslint-config-typescript": "^3.0.0",
|
|
85
|
+
"husky": "^8.0.0",
|
|
86
|
+
"npm-watch": "^0.11.0",
|
|
70
87
|
"pinst": "^2.1.6",
|
|
71
|
-
"prettier": "^2.
|
|
88
|
+
"prettier": "^2.7.1",
|
|
72
89
|
"rollup": "^2.36.1",
|
|
73
|
-
"rollup-plugin-
|
|
74
|
-
"
|
|
75
|
-
"
|
|
76
|
-
"
|
|
77
|
-
"
|
|
90
|
+
"rollup-plugin-bundle-size": "^1.0.3",
|
|
91
|
+
"rollup-plugin-copy": "^3.4.0",
|
|
92
|
+
"service-worker-mock": "^2.0.5",
|
|
93
|
+
"ts-node": "^10.9.1",
|
|
94
|
+
"typescript": "^4.7.4",
|
|
95
|
+
"wrangler": "^3.6.0"
|
|
78
96
|
},
|
|
79
97
|
"dependencies": {
|
|
80
|
-
"itty-router": "^
|
|
98
|
+
"@cloudflare/itty-router-openapi": "^1.0.1",
|
|
99
|
+
"postal-mime": "^1.0.16"
|
|
81
100
|
},
|
|
82
101
|
"bin": {
|
|
83
102
|
"r2-explorer": "bin/r2-explorer.js"
|
package/bin/r2-explorer.js
DELETED
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
#! /usr/bin/env node
|
|
2
|
-
var fs = require('fs')
|
|
3
|
-
|
|
4
|
-
const args = process.argv.slice(2)
|
|
5
|
-
const projectName = args[0] || 'r2-explorer'
|
|
6
|
-
|
|
7
|
-
const dir = `./${projectName}`
|
|
8
|
-
if (!fs.existsSync(dir)) {
|
|
9
|
-
fs.mkdirSync(dir)
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
fs.writeFileSync(
|
|
13
|
-
`${dir}/wrangler.toml`,
|
|
14
|
-
`name = "${projectName}"
|
|
15
|
-
compatibility_date = "2022-08-09"
|
|
16
|
-
main = "src/index.js"
|
|
17
|
-
|
|
18
|
-
[[r2_buckets]]
|
|
19
|
-
binding = 'my-bucket-name'
|
|
20
|
-
bucket_name = 'my-bucket-name'
|
|
21
|
-
preview_bucket_name = 'my-bucket-name'
|
|
22
|
-
`
|
|
23
|
-
)
|
|
24
|
-
fs.writeFileSync(
|
|
25
|
-
`${dir}/package.json`,
|
|
26
|
-
`{
|
|
27
|
-
"name": "${projectName}",
|
|
28
|
-
"version": "0.1.0",
|
|
29
|
-
"private": true,
|
|
30
|
-
"devDependencies": {
|
|
31
|
-
"wrangler": "^2.4.2"
|
|
32
|
-
},
|
|
33
|
-
"scripts": {
|
|
34
|
-
"publish": "wrangler publish"
|
|
35
|
-
},
|
|
36
|
-
"dependencies": {
|
|
37
|
-
"r2-explorer": "^0.6.0"
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
`
|
|
42
|
-
)
|
|
43
|
-
|
|
44
|
-
const srcDir = `${dir}/src`
|
|
45
|
-
if (!fs.existsSync(srcDir)) {
|
|
46
|
-
fs.mkdirSync(srcDir)
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
fs.writeFileSync(
|
|
50
|
-
`${srcDir}/index.js`,
|
|
51
|
-
`import { R2Explorer } from 'r2-explorer';
|
|
52
|
-
|
|
53
|
-
const explorer = R2Explorer({ readonly: true })
|
|
54
|
-
|
|
55
|
-
export default {
|
|
56
|
-
async fetch(request, env, context) {
|
|
57
|
-
return explorer(request, env, context)
|
|
58
|
-
}
|
|
59
|
-
};
|
|
60
|
-
`
|
|
61
|
-
)
|
|
62
|
-
|
|
63
|
-
console.log(`Project ${projectName} successfully created!`)
|
|
64
|
-
console.log('----------------------------')
|
|
65
|
-
console.log('Next steps:')
|
|
66
|
-
console.log(` 1. Run 'cd ${projectName} && npm install'`)
|
|
67
|
-
console.log(" 2. Update the 'wrangler.toml' file with your R2 Buckets")
|
|
68
|
-
console.log(" 3. Run 'wrangler publish' to deploy your own R2-Explorer!")
|
|
69
|
-
|
|
70
|
-
process.exit(0) //no errors occurred
|