kingkont 0.20.31 → 0.20.33
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/lib/r2-upload.js +26 -66
- package/package.json +1 -1
package/lib/r2-upload.js
CHANGED
|
@@ -1,93 +1,53 @@
|
|
|
1
|
-
// lib/r2-upload.js — загрузка файла в
|
|
1
|
+
// lib/r2-upload.js — загрузка файла в R2 через Cloudflare Worker upload-proxy.
|
|
2
2
|
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
3
|
+
// Worker задеплоен в `cf-worker/` (см. README там). Он принимает POST,
|
|
4
|
+
// складывает body в R2-бакет `kingkont`, отвечает {url, key, size}.
|
|
5
|
+
// Public-domain бакета: pub-cd4114af9f7d44c9bf8c9442bc7dddc2.r2.dev — file
|
|
6
|
+
// доступен по {url}.
|
|
5
7
|
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
// Способ auth: Cloudflare Account API Token (cfat_...) → Bearer header.
|
|
11
|
-
// Это проще чем S3-protocol SigV4 (нет хитрой подписи), и token-based
|
|
12
|
-
// auth тривиальнее ротировать в случае утечки.
|
|
13
|
-
//
|
|
14
|
-
// API endpoint:
|
|
15
|
-
// PUT https://api.cloudflare.com/client/v4/accounts/<account>/r2/buckets/<bucket>/objects/<key>
|
|
16
|
-
// Authorization: Bearer <token>
|
|
17
|
-
// Content-Type: <mime>
|
|
18
|
-
// body: <bytes>
|
|
19
|
-
// → 200 OK { success:true, result:{key, size, etag} }
|
|
20
|
-
//
|
|
21
|
-
// Public read через managed-domain (pub-<bucket-id>.r2.dev), включён через
|
|
22
|
-
// API (PUT /r2/buckets/<b>/domains/managed { enabled:true }).
|
|
8
|
+
// Раньше тут была прямая запись через Cloudflare API с Account API token.
|
|
9
|
+
// Токен был закоммичен в публичный npm-пакет → CF auto-revoked secret
|
|
10
|
+
// scanner'ом за час. Worker-подход решает проблему: URL Worker'а публичный
|
|
11
|
+
// и не секретный, R2-credentials живут только в R2-binding на стороне CF.
|
|
23
12
|
|
|
24
|
-
const
|
|
25
|
-
|
|
26
|
-
const R2 = {
|
|
27
|
-
accountId: 'dd50e50ba5d89ce42553cfb2f5d6ee40',
|
|
28
|
-
apiToken: 'cfat_8HndHS0KMqecVBsjpM5Se2VbLQIvSw2ipCDnhtDm1502679f',
|
|
29
|
-
bucket: 'kingkont',
|
|
30
|
-
publicBase: 'https://pub-cd4114af9f7d44c9bf8c9442bc7dddc2.r2.dev',
|
|
31
|
-
};
|
|
32
|
-
// Env override (для dev / тестов / ротации без релиза).
|
|
33
|
-
if (process.env.R2_ACCOUNT_ID) R2.accountId = process.env.R2_ACCOUNT_ID;
|
|
34
|
-
if (process.env.R2_API_TOKEN) R2.apiToken = process.env.R2_API_TOKEN;
|
|
35
|
-
if (process.env.R2_BUCKET) R2.bucket = process.env.R2_BUCKET;
|
|
36
|
-
if (process.env.R2_PUBLIC_BASE) R2.publicBase = process.env.R2_PUBLIC_BASE;
|
|
13
|
+
const WORKER_URL = (process.env.R2_WORKER_URL || 'https://kingkont-r2-upload.timur-dd5.workers.dev').replace(/\/$/, '');
|
|
37
14
|
|
|
38
15
|
function isConfigured() {
|
|
39
|
-
return !!
|
|
16
|
+
return !!WORKER_URL && WORKER_URL !== '<NOT_CONFIGURED>';
|
|
40
17
|
}
|
|
41
18
|
|
|
42
19
|
/**
|
|
43
|
-
* Залить файл в R2 и вернуть публичную URL'ку.
|
|
20
|
+
* Залить файл в R2 через Worker и вернуть публичную URL'ку.
|
|
44
21
|
* @param {Buffer} buffer
|
|
45
22
|
* @param {string} mime
|
|
46
23
|
* @param {string} [ext]
|
|
47
24
|
* @returns {Promise<{ url: string, key: string, size: number }>}
|
|
48
25
|
*/
|
|
49
26
|
async function uploadToR2(buffer, mime = 'application/octet-stream', ext) {
|
|
50
|
-
if (!isConfigured()) throw new Error('R2 не настроен');
|
|
27
|
+
if (!isConfigured()) throw new Error('R2 Worker URL не настроен');
|
|
51
28
|
if (!buffer || !buffer.length) throw new Error('пустой файл');
|
|
52
29
|
|
|
53
|
-
const
|
|
54
|
-
|
|
55
|
-
const key = `${crypto.randomUUID()}.${finalExt}`;
|
|
56
|
-
|
|
57
|
-
const url = `https://api.cloudflare.com/client/v4/accounts/${R2.accountId}/r2/buckets/${R2.bucket}/objects/${encodeURIComponent(key)}`;
|
|
30
|
+
const headers = { 'Content-Type': mime };
|
|
31
|
+
if (ext) headers['X-Ext'] = String(ext).replace(/[^a-z0-9]/gi, '').slice(0, 8);
|
|
58
32
|
|
|
59
33
|
const t0 = Date.now();
|
|
60
|
-
const r = await fetch(
|
|
61
|
-
method: '
|
|
62
|
-
headers
|
|
63
|
-
'Authorization': `Bearer ${R2.apiToken}`,
|
|
64
|
-
'Content-Type': mime,
|
|
65
|
-
},
|
|
34
|
+
const r = await fetch(`${WORKER_URL}/upload`, {
|
|
35
|
+
method: 'POST',
|
|
36
|
+
headers,
|
|
66
37
|
body: buffer,
|
|
67
38
|
});
|
|
39
|
+
|
|
68
40
|
if (!r.ok) {
|
|
69
41
|
const text = await r.text().catch(() => '');
|
|
70
|
-
throw new Error(`R2 PUT ${r.status}: ${text.slice(0, 300)}`);
|
|
71
|
-
}
|
|
72
|
-
// CF API возвращает {success, result}. Проверяем флаг для надёжности.
|
|
73
|
-
const data = await r.json().catch(() => ({}));
|
|
74
|
-
if (data && data.success === false) {
|
|
75
|
-
throw new Error(`R2 PUT не success: ${JSON.stringify(data.errors || data).slice(0, 300)}`);
|
|
42
|
+
throw new Error(`R2 worker PUT ${r.status}: ${text.slice(0, 300)}`);
|
|
76
43
|
}
|
|
77
|
-
const publicUrl = `${R2.publicBase.replace(/\/$/, '')}/${key}`;
|
|
78
|
-
console.log(`[r2-upload] OK ${key} (${buffer.length}B, ${mime}) in ${Date.now() - t0}ms → ${publicUrl}`);
|
|
79
|
-
return { url: publicUrl, key, size: buffer.length };
|
|
80
|
-
}
|
|
81
44
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
'text/plain': 'txt', 'text/markdown': 'md',
|
|
89
|
-
'application/octet-stream': 'bin',
|
|
90
|
-
})[mime] || null;
|
|
45
|
+
const data = await r.json();
|
|
46
|
+
if (!data || !data.url) {
|
|
47
|
+
throw new Error(`R2 worker вернул без url: ${JSON.stringify(data).slice(0, 200)}`);
|
|
48
|
+
}
|
|
49
|
+
console.log(`[r2-upload] OK ${data.key} (${data.size}B, ${mime}) in ${Date.now() - t0}ms → ${data.url}`);
|
|
50
|
+
return { url: data.url, key: data.key, size: data.size };
|
|
91
51
|
}
|
|
92
52
|
|
|
93
53
|
module.exports = { uploadToR2, isConfigured };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "kingkont",
|
|
3
|
-
"version": "0.20.
|
|
3
|
+
"version": "0.20.33",
|
|
4
4
|
"description": "KingKont \u00b7 Chatium \u2014 \u043d\u043e\u0434-\u0440\u0435\u0434\u0430\u043a\u0442\u043e\u0440 \u0441\u0446\u0435\u043d \u0441 AI-\u0433\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u0435\u0439 (\u043a\u0430\u0440\u0442\u0438\u043d\u043a\u0438/\u0432\u0438\u0434\u0435\u043e/\u0433\u043e\u043b\u043e\u0441/SFX/\u043c\u0443\u0437\u044b\u043a\u0430/\u0442\u0435\u043a\u0441\u0442)",
|
|
5
5
|
"main": "main.js",
|
|
6
6
|
"bin": {
|