Skip to content

Commit 1397ace

Browse files
committed
0.13.0
对接FileBase的S3-IPFS-API
1 parent 839dfef commit 1397ace

File tree

2 files changed

+270
-1
lines changed

2 files changed

+270
-1
lines changed
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
async function handles3filebaseRequest(request) {
2+
if (request.method !== 'POST' || !request.headers.get('Content-Type').includes('multipart/form-data')) {
3+
return new Response('Invalid request', { status: 400 });
4+
}
5+
6+
try {
7+
// 从 KV 获取配置
8+
const config = await WORKER_IMGBED.get('s3filebase_config', 'json');
9+
if (!config || !config.accessKey || !config.secretKey || !config.bucket) {
10+
throw new Error('Invalid S3 configuration');
11+
}
12+
13+
const formData = await request.formData();
14+
const file = formData.get('image');
15+
if (!file) {
16+
return new Response('No file found', { status: 400 });
17+
}
18+
19+
const now = new Date();
20+
const timestamp = now.toISOString()
21+
.replace(/[-:]/g, '')
22+
.split('.')[0]
23+
.replace('T', '_');
24+
25+
const fileName = file.name.split('.')[0];
26+
const extension = file.name.split('.').pop() || '';
27+
const s3Key = `${fileName}_${timestamp}.${extension}`;
28+
const content = await file.arrayBuffer();
29+
30+
const amzdate = now.toISOString().replace(/[:-]|\.\d{3}/g, '');
31+
const dateStamp = amzdate.slice(0, 8);
32+
const contentHash = await crypto.subtle.digest('SHA-256', content)
33+
.then(buf => Array.from(new Uint8Array(buf))
34+
.map(b => b.toString(16).padStart(2, '0'))
35+
.join(''));
36+
37+
const canonicalUri = `/${config.bucket}/${s3Key}`;
38+
const uploadHeaders = {
39+
'Host': 's3.filebase.com',
40+
'Content-Type': file.type || 'application/octet-stream',
41+
'X-Amz-Content-SHA256': contentHash,
42+
'X-Amz-Date': amzdate
43+
};
44+
45+
const algorithm = 'AWS4-HMAC-SHA256';
46+
const region = 'us-east-1';
47+
const service = 's3';
48+
const scope = `${dateStamp}/${region}/${service}/aws4_request`;
49+
50+
const canonicalHeaders = Object.entries(uploadHeaders)
51+
.map(([k, v]) => `${k.toLowerCase()}:${v}\n`)
52+
.sort()
53+
.join('');
54+
const signedHeaders = Object.keys(uploadHeaders)
55+
.map(k => k.toLowerCase())
56+
.sort()
57+
.join(';');
58+
59+
const canonicalRequest = [
60+
'PUT',
61+
canonicalUri,
62+
'',
63+
canonicalHeaders,
64+
signedHeaders,
65+
contentHash
66+
].join('\n');
67+
68+
const stringToSign = [
69+
algorithm,
70+
amzdate,
71+
scope,
72+
await crypto.subtle.digest('SHA-256', new TextEncoder().encode(canonicalRequest))
73+
.then(buf => Array.from(new Uint8Array(buf))
74+
.map(b => b.toString(16).padStart(2, '0'))
75+
.join(''))
76+
].join('\n');
77+
78+
let key = await crypto.subtle.importKey(
79+
'raw',
80+
new TextEncoder().encode(`AWS4${config.secretKey}`),
81+
{ name: 'HMAC', hash: 'SHA-256' },
82+
false,
83+
['sign']
84+
);
85+
86+
for (const msg of [dateStamp, region, service, 'aws4_request']) {
87+
key = await crypto.subtle.importKey(
88+
'raw',
89+
await crypto.subtle.sign('HMAC', key, new TextEncoder().encode(msg)),
90+
{ name: 'HMAC', hash: 'SHA-256' },
91+
false,
92+
['sign']
93+
);
94+
}
95+
96+
const signature = await crypto.subtle.sign(
97+
'HMAC',
98+
key,
99+
new TextEncoder().encode(stringToSign)
100+
);
101+
102+
const authorization =
103+
`${algorithm} ` +
104+
`Credential=${config.accessKey}/${scope}, ` +
105+
`SignedHeaders=${signedHeaders}, ` +
106+
`Signature=${Array.from(new Uint8Array(signature))
107+
.map(b => b.toString(16).padStart(2, '0'))
108+
.join('')}`;
109+
110+
const uploadResponse = await fetch(`https://s3.filebase.com${canonicalUri}`, {
111+
method: 'PUT',
112+
headers: {
113+
...uploadHeaders,
114+
'Authorization': authorization
115+
},
116+
body: content
117+
});
118+
119+
if (!uploadResponse.ok) {
120+
throw new Error(`Upload failed with status ${uploadResponse.status}`);
121+
}
122+
123+
const cid = uploadResponse.headers.get('x-amz-meta-cid');
124+
if (!cid) {
125+
throw new Error('CID not found in response');
126+
}
127+
128+
return new Response(`https://i0.wp.com/i0.img2ipfs.com/ipfs/${cid}`);
129+
130+
} catch (error) {
131+
return new Response(`Upload failed: ${error.message}`, { status: 500 });
132+
}
133+
}

cloudflare-worker-js-api/worker.js

Lines changed: 137 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,9 @@ async function handleRequest(request) {
5353
case '/upload/3001':
5454
response = await handle3001Request(request);
5555
break;
56+
case '/upload/s3ipfs':
57+
response = await handles3filebaseRequest(request);
58+
break;
5659
default:
5760
response = new Response('Not Found', { status: 404 });
5861
break;
@@ -658,4 +661,137 @@ async function handleRequest(request) {
658661
},
659662
})
660663
}
661-
664+
665+
async function handles3filebaseRequest(request) {
666+
if (request.method !== 'POST' || !request.headers.get('Content-Type').includes('multipart/form-data')) {
667+
return new Response('Invalid request', { status: 400 });
668+
}
669+
670+
try {
671+
// 从 KV 获取配置
672+
const config = await WORKER_IMGBED.get('s3filebase_config', 'json');
673+
if (!config || !config.accessKey || !config.secretKey || !config.bucket) {
674+
throw new Error('Invalid S3 configuration');
675+
}
676+
677+
const formData = await request.formData();
678+
const file = formData.get('image');
679+
if (!file) {
680+
return new Response('No file found', { status: 400 });
681+
}
682+
683+
const now = new Date();
684+
const timestamp = now.toISOString()
685+
.replace(/[-:]/g, '')
686+
.split('.')[0]
687+
.replace('T', '_');
688+
689+
const fileName = file.name.split('.')[0];
690+
const extension = file.name.split('.').pop() || '';
691+
const s3Key = `${fileName}_${timestamp}.${extension}`;
692+
const content = await file.arrayBuffer();
693+
694+
const amzdate = now.toISOString().replace(/[:-]|\.\d{3}/g, '');
695+
const dateStamp = amzdate.slice(0, 8);
696+
const contentHash = await crypto.subtle.digest('SHA-256', content)
697+
.then(buf => Array.from(new Uint8Array(buf))
698+
.map(b => b.toString(16).padStart(2, '0'))
699+
.join(''));
700+
701+
const canonicalUri = `/${config.bucket}/${s3Key}`;
702+
const uploadHeaders = {
703+
'Host': 's3.filebase.com',
704+
'Content-Type': file.type || 'application/octet-stream',
705+
'X-Amz-Content-SHA256': contentHash,
706+
'X-Amz-Date': amzdate
707+
};
708+
709+
const algorithm = 'AWS4-HMAC-SHA256';
710+
const region = 'us-east-1';
711+
const service = 's3';
712+
const scope = `${dateStamp}/${region}/${service}/aws4_request`;
713+
714+
const canonicalHeaders = Object.entries(uploadHeaders)
715+
.map(([k, v]) => `${k.toLowerCase()}:${v}\n`)
716+
.sort()
717+
.join('');
718+
const signedHeaders = Object.keys(uploadHeaders)
719+
.map(k => k.toLowerCase())
720+
.sort()
721+
.join(';');
722+
723+
const canonicalRequest = [
724+
'PUT',
725+
canonicalUri,
726+
'',
727+
canonicalHeaders,
728+
signedHeaders,
729+
contentHash
730+
].join('\n');
731+
732+
const stringToSign = [
733+
algorithm,
734+
amzdate,
735+
scope,
736+
await crypto.subtle.digest('SHA-256', new TextEncoder().encode(canonicalRequest))
737+
.then(buf => Array.from(new Uint8Array(buf))
738+
.map(b => b.toString(16).padStart(2, '0'))
739+
.join(''))
740+
].join('\n');
741+
742+
let key = await crypto.subtle.importKey(
743+
'raw',
744+
new TextEncoder().encode(`AWS4${config.secretKey}`),
745+
{ name: 'HMAC', hash: 'SHA-256' },
746+
false,
747+
['sign']
748+
);
749+
750+
for (const msg of [dateStamp, region, service, 'aws4_request']) {
751+
key = await crypto.subtle.importKey(
752+
'raw',
753+
await crypto.subtle.sign('HMAC', key, new TextEncoder().encode(msg)),
754+
{ name: 'HMAC', hash: 'SHA-256' },
755+
false,
756+
['sign']
757+
);
758+
}
759+
760+
const signature = await crypto.subtle.sign(
761+
'HMAC',
762+
key,
763+
new TextEncoder().encode(stringToSign)
764+
);
765+
766+
const authorization =
767+
`${algorithm} ` +
768+
`Credential=${config.accessKey}/${scope}, ` +
769+
`SignedHeaders=${signedHeaders}, ` +
770+
`Signature=${Array.from(new Uint8Array(signature))
771+
.map(b => b.toString(16).padStart(2, '0'))
772+
.join('')}`;
773+
774+
const uploadResponse = await fetch(`https://s3.filebase.com${canonicalUri}`, {
775+
method: 'PUT',
776+
headers: {
777+
...uploadHeaders,
778+
'Authorization': authorization
779+
},
780+
body: content
781+
});
782+
783+
if (!uploadResponse.ok) {
784+
throw new Error(`Upload failed with status ${uploadResponse.status}`);
785+
}
786+
787+
const cid = uploadResponse.headers.get('x-amz-meta-cid');
788+
if (!cid) {
789+
throw new Error('CID not found in response');
790+
}
791+
792+
return new Response(`https://i0.wp.com/i0.img2ipfs.com/ipfs/${cid}`);
793+
794+
} catch (error) {
795+
return new Response(`Upload failed: ${error.message}`, { status: 500 });
796+
}
797+
}

0 commit comments

Comments
 (0)