kingkont 0.20.31 → 0.20.32

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.
Files changed (2) hide show
  1. package/lib/r2-upload.js +26 -66
  2. package/package.json +1 -1
package/lib/r2-upload.js CHANGED
@@ -1,93 +1,53 @@
1
- // lib/r2-upload.js — загрузка файла в Cloudflare R2 через Cloudflare API (Bearer).
1
+ // lib/r2-upload.js — загрузка файла в R2 через Cloudflare Worker upload-proxy.
2
2
  //
3
- // Используется как fallback когда chatium-storage медленно / недоступно
4
- // (см. uploadFile в providers.js race с 5s timeout).
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
- // Credentials хардкодим (юзер: «дам наш ключ, не надо чтобы каждый
7
- // настраивал»). Если в будущем нужно будет дать юзерам кастомизировать —
8
- // перенесём в settings.json.
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 crypto = require('crypto');
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 !!(R2.accountId && R2.apiToken && R2.bucket && R2.publicBase);
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 finalExt = (ext || _extFromMime(mime) || 'bin').replace(/[^a-z0-9]/gi, '').slice(0, 8) || 'bin';
54
- // UUID-based юзер просил без content-hash дедупа.
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(url, {
61
- method: 'PUT',
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
- function _extFromMime(mime) {
83
- return ({
84
- 'image/jpeg': 'jpg', 'image/jpg': 'jpg',
85
- 'image/png': 'png', 'image/webp': 'webp', 'image/gif': 'gif', 'image/avif': 'avif',
86
- 'video/mp4': 'mp4', 'video/quicktime': 'mov', 'video/webm': 'webm',
87
- 'audio/mpeg': 'mp3', 'audio/wav': 'wav', 'audio/mp4': 'm4a', 'audio/x-m4a': 'm4a',
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.31",
3
+ "version": "0.20.32",
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": {