Ezmary commited on
Commit
0fd4af6
·
verified ·
1 Parent(s): c175dc8

Update server/index.js

Browse files
Files changed (1) hide show
  1. server/index.js +171 -231
server/index.js CHANGED
@@ -1,278 +1,218 @@
 
 
1
  const express = require('express');
2
  const path = require('node:path');
3
  const { WebSocketServer, WebSocket } = require('ws');
4
  const http = require('node:http');
5
- require('dotenv').config(); // برای تست محلی ممکن است نیاز باشد
6
 
7
  const app = express();
8
  const server = http.createServer(app);
9
  const wss = new WebSocketServer({ server });
10
 
11
- // --- START: منطق جدید برای چرخش API Key ---
12
 
13
- // ۱. خواندن تمام کلیدهای API از Secrets (متغیرهای محیطی در هاگینگ فیس)
14
- const apiKeys = [];
15
- let i = 1;
16
- while (process.env[`GEMINI_API_KEY_${i}`]) {
17
- apiKeys.push(process.env[`GEMINI_API_KEY_${i}`]);
18
- i++;
19
- }
20
 
21
  const numKeys = apiKeys.length;
22
 
23
  if (numKeys === 0) {
24
- // اگر هیچ کلیدی پیدا نشد، خطا داده و خارج شو
25
  console.error(
26
- 'خطای حیاتی: هیچ Secret با نام GEMINI_API_KEY_n یافت نشد!' +
27
- ' لطفاً Secret ها را مانند GEMINI_API_KEY_1, GEMINI_API_KEY_2, ... ' +
28
- 'در تنظیمات Space خود اضافه کنید.'
29
  );
30
- process.exit(1); // برنامه را متوقف کن
31
  } else {
32
- console.log(`تعداد ${numKeys} کلید API جیمینای بارگذاری شد.`);
33
  }
34
 
35
- // ۲. شمارنده سراسری برای انتخاب کلید بعدی (شروع از اندیس ۰)
36
  let currentKeyIndex = 0;
37
 
38
- // ۳. تابع برای گرفتن کلید بعدی به صورت چرخشی
39
- function getNextApiKey() {
40
- if (numKeys === 0) {
41
- console.error('تلاش برای گرفتن کلید API در حالی که هیچ کلیدی بارگذاری نشده است.');
42
- return null; // یا یک خطا پرتاب کنید
43
- }
44
- // محاسبه اندیس کلید با استفاده از باقیمانده تقسیم
45
- const keyIndexToUse = currentKeyIndex % numKeys;
46
- const selectedKey = apiKeys[keyIndexToUse];
47
- console.log(`اختصاص کلید API با اندیس: ${keyIndexToUse}`); // لاگ برای اشکال‌زدایی
48
-
49
- // افزایش شمارنده برای درخواست بعدی
50
  currentKeyIndex++;
51
-
52
- // اختیاری: جلوگیری از خیلی بزرگ شدن شمارنده (گرچه جاوااسکریپت اعداد بزرگ را مدیریت می‌کند)
53
- // if (currentKeyIndex >= Number.MAX_SAFE_INTEGER - 10) { // نزدیک به حداکثر عدد امن
54
- // currentKeyIndex = currentKeyIndex % numKeys;
55
- // }
56
-
57
- return selectedKey;
58
  }
59
 
60
- // --- END: منطق جدید برای چرخش API Key ---
61
 
62
- // سرو کردن فایل‌های استاتیک از پوشه بیلد React
63
- app.use(express.static(path.join(__dirname, '../build')));
64
 
65
- // این تابع حالا کلید API انتخاب شده را به عنوان ورودی می‌گیرد
66
- const createGeminiWebSocket = (clientWs, apiKey) => {
67
- // بررسی اینکه آیا کلید معتبری داده شده است
68
- if (!apiKey) {
69
- console.error('امکان ایجاد WebSocket جیمینای وجود ندارد: کلید API ارائه نشده است.');
70
- // می‌توانید یک پیام خطا به کلاینت بفرستید و اتصالش را ببندید
71
- if (clientWs.readyState === WebSocket.OPEN) {
72
  try {
73
- clientWs.send(JSON.stringify({ error: "خطای داخلی سرور: عدم امکان دریافت کلید API." }));
74
- } catch (sendError) {
75
- console.error("خطا در ارسال پیام خطا به کلاینت:", sendError);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
76
  }
77
- clientWs.close();
78
  }
79
- return null; // برگرداندن null نشان‌دهنده شکست است
80
- }
81
 
82
- console.log(`ایجاد اتصال به جیمینای با کلید: ...${apiKey.slice(-4)}`); // نمایش ۴ کاراکتر آخر کلید برای تایید
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
83
 
84
- // استفاده از کلید API اختصاص‌یافته به این کاربر
85
- const geminiWs = new WebSocket(
86
- `wss://generativelanguage.googleapis.com/ws/google.ai.generativelanguage.v1alpha.GenerativeService.BidiGenerateContent?key=${apiKey}`
87
- );
88
 
89
- // --- بقیه event handler های geminiWs (open, message, error, close) بدون تغییر باقی می‌مانند ---
90
- geminiWs.on('open', () => {
91
- console.log(`اتصال به جیمینای برای کلاینت با کلید ...${apiKey.slice(-4)} برقرار شد.`);
92
- // ارسال پیام setup معلق اگر وجود دارد
93
- if (geminiWs.pendingSetup) {
94
- console.log('ارسال پیام setup معلق:', geminiWs.pendingSetup);
95
- try {
96
- geminiWs.send(JSON.stringify(geminiWs.pendingSetup));
97
- } catch (sendError) {
98
- console.error("خطا در ارسال پیام setup معلق به جیمینای:", sendError);
99
- clientWs.close(); // بستن اتصال کلاینت اگر نتوانستیم با جیمینای ارتباط اولیه برقرار کنیم
100
- }
101
- geminiWs.pendingSetup = null; // پاک کردن پیام معلق
102
- }
103
- });
104
-
105
- geminiWs.on('message', (data) => {
106
- try {
107
- // تبدیل پیام به رشته قبل از ارسال به کلاینت (کلاینت شما انتظار Blob داشت، مطمئن شوید هنوز همینطور است)
108
- const message = data.toString();
109
- console.log(`دریافت از جیمینای (کلید ...${apiKey.slice(-4)}):`, message); // نمایش ۴ کاراکتر آخر کلید در لاگ
110
-
111
- // کلاینت شما انتظار Blob داشت. اگر هنوز نیاز به Blob دارید، آن را حفظ کنید:
112
- const blob = Buffer.from(message); // ایجاد Buffer (شبیه Blob در Node.js)
113
- if (clientWs.readyState === WebSocket.OPEN) {
114
- clientWs.send(blob, { binary: true }); // ارسال به عنوان داده باینری
115
- }
116
-
117
- // اگر کلاینت شما حالا انتظار JSON یا رشته دارد، کد بالا را تغییر دهید:
118
- // if (clientWs.readyState === WebSocket.OPEN) {
119
- // clientWs.send(message); // ارسال به عنوان رشته
120
- // }
121
-
122
- } catch (error) {
123
- console.error(`خطا در پردازش پیام جیمینای (کلید ...${apiKey.slice(-4)}):`, error);
124
- }
125
- });
126
-
127
- geminiWs.on('error', (error) => {
128
- console.error(`خطای WebSocket جیمینای (کلید ...${apiKey.slice(-4)}):`, error);
129
- // بستن اتصال کلاینت مرتبط در صورت خطای جیمینای
130
- if (clientWs.readyState === WebSocket.OPEN) {
131
- clientWs.close();
132
- }
133
- });
134
-
135
- geminiWs.on('close', (code, reason) => {
136
- console.log(`اتصال WebSocket جیمینای بسته شد (کلید ...${apiKey.slice(-4)}):`, code, reason.toString());
137
- // بستن اتصال کلاینت مرتبط وقتی اتصال جیمینای بسته می‌شود
138
- if (clientWs.readyState === WebSocket.OPEN) {
139
- clientWs.close();
140
- }
141
- });
142
-
143
- return geminiWs;
144
- };
145
 
146
  wss.on('connection', (ws) => {
147
- console.log('یک کلاینت جدید متصل شد.');
148
-
149
- // --- اختصاص کلید API به محض اتصال کلاینت ---
150
- const assignedApiKey = getNextApiKey(); // گرفتن کلید بعدی برای *این* کلاینت
151
-
152
- // اگر به دلایلی کلید دریافت نشد (نباید اتفاق بیفتد اگر چک اولیه انجام شده)
153
- if (!assignedApiKey) {
154
- console.error("خطا: عدم موفقیت در اختصاص کلید API به کلاینت جدید. بستن اتصال.");
155
- ws.close();
156
- return; // ادامه نده برای این کلاینت
157
- }
158
- // لاگ کردن کلید اختصاص یافته (فقط ۴ کاراکتر آخر برای امنیت)
159
- console.log(`کلید API اختصاص یافته به کلاینت: ...${assignedApiKey.slice(-4)}`);
160
-
161
- let geminiWs = null; // اتصال جیمینای مخصوص *این* کلاینت
162
-
163
- ws.on('message', async (message) => {
164
- try {
165
- // بررسی کنیم پیام باینری است یا متنی
166
- let data;
167
- if (message instanceof Buffer) {
168
- // اگر کلاینت پیام باینری می‌فرستد، آن را به رشته تبدیل کنید (فرض بر JSON بودن)
169
- data = JSON.parse(message.toString());
170
- } else {
171
- data = JSON.parse(message); // اگر پیام متنی است
172
- }
173
 
174
- console.log('دریافت از کلاینت:', data);
175
-
176
- // مقداردهی اولیه اتصال جیمینای برای این کلاینت با استفاده از کلید اختصاص یافته
177
- if (data.setup) {
178
- // جلوگیری از مقداردهی مجدد اگر کلاینت دوباره پیام setup فرستاد
179
- if (geminiWs) {
180
- console.warn("کلاینت دوباره پیام setup ارسال کرد. نادیده گرفته شد.");
181
- return;
182
- }
183
- console.log('مقداردهی اولیه اتصال جیمینای با تنظیمات:', data.setup);
184
- // ارسال کلید اختصاص یافته این کلاینت به تابع ایجاد اتصال
185
- geminiWs = createGeminiWebSocket(ws, assignedApiKey);
186
-
187
- // ذخیره پیام setup برای ارسال پس از برقراری اتصال (اگر اتصال هنوز آماده نیست)
188
- if (geminiWs && geminiWs.readyState !== WebSocket.OPEN) {
189
- // مهم: پیام معلق را به نمونه WebSocket *این* کلاینت متصل کنید
190
- geminiWs.pendingSetup = data;
191
- } else if (geminiWs) {
192
- // اگر اتصال بلافاصله برقرار شد، پیام setup را بفرست
193
- try {
194
- geminiWs.send(JSON.stringify(data));
195
- } catch (sendError) {
196
- console.error("خطا در ارسال پیام setup اولیه به جیمینای:", sendError);
197
- ws.close(); // بستن اتصال کلاینت
198
- }
199
- } else {
200
- // اگر createGeminiWebSocket ناموفق بود (مثلا به خاطر خطای کلید در بالا)
201
- console.error("ایجاد اتصال WebSocket جیمینای پس از پیام setup ناموفق بود.");
202
- // ws قبلا باید بسته شده باشد توسط createGeminiWebSocket
203
- }
204
- return; // پردازش پیام setup تمام شد
205
- }
206
 
207
- // ارسال پیام به جیمینای اگر اتصال برای این کلاینت وجود دارد و باز است
208
- if (geminiWs && geminiWs.readyState === WebSocket.OPEN) {
209
- console.log('ارسال به جیمینای:', data);
210
- try {
211
- geminiWs.send(JSON.stringify(data));
212
- } catch (sendError) {
213
- console.error("خطا در ارسال پیام به جیمینای:", sendError);
214
- ws.close(); // بستن اتصال کلاینت
215
- }
216
- } else if (geminiWs) {
217
- // اتصال جیمینای هنوز برقرار نشده یا بسته شده
218
- console.log('اتصال جیمینای آماده نیست، امکان ارسال پیام وجود ندارد.');
219
- // TODO: می‌توانید پیام‌ها را در صف قرار دهید یا خطا به کلاینت برگردانید
220
- } else {
221
- // هنوز پیام setup دریافت نشده یا اتصال ناموفق بوده
222
- console.error('امکان ارسال پیام وجود ندارد: اتصال جیمینای برای این کلاینت برقرار نشده است.');
223
- // می‌توانید خطا به کلاینت برگردانید
224
- if (ws.readyState === WebSocket.OPEN) {
225
- try {
226
- ws.send(JSON.stringify({ error: "ارتباط با سرویس برقرار نیست. لطفاً دوباره تلاش کنید." }));
227
- } catch (sendError) {
228
- console.error("خطا در ارسال پیام خطا به کلاینت:", sendError);
229
- }
230
- }
 
 
 
 
 
 
231
  }
232
- } catch (error) {
233
- // خطای کلی در پردازش پیام (مثلا JSON نامعتبر)
234
- console.error('خطا در پردازش پیام کلاینت:', error);
235
- // از کرش کردن سرور جلوگیری کنید
236
- }
237
- });
238
 
239
- ws.on('close', () => {
240
- console.log(`کلاینت با کلید ...${assignedApiKey ? assignedApiKey.slice(-4) : 'N/A'} قطع شد.`);
241
- // بستن اتصال جیمینای مربوط به این کلاینت، اگر باز است
242
- if (geminiWs && geminiWs.readyState !== WebSocket.CLOSED && geminiWs.readyState !== WebSocket.CLOSING) {
243
- geminiWs.close();
244
- }
245
- });
246
-
247
- ws.on('error', (error) => {
248
- console.error(`خطای WebSocket کلاینت (کلید ...${assignedApiKey ? assignedApiKey.slice(-4) : 'N/A'}):`, error);
249
- // بستن اتصال جیمینای مربوطه در صورت خطای کلاینت
250
- if (geminiWs && geminiWs.readyState !== WebSocket.CLOSED && geminiWs.readyState !== WebSocket.CLOSING) {
251
- geminiWs.close();
252
- }
253
- // ws احتمالا به طور خودکار بسته می‌شود یا در شرف بسته شدن است
254
- });
255
 
 
 
 
 
 
 
256
  });
257
 
258
  // رسیدگی به درخواست‌های باقیمانده با برگرداندن اپ React
259
  app.get('*', (req, res) => {
260
- // اطمینان حاصل کنید که مسیر درست است
261
- const indexPath = path.join(__dirname, '../build', 'index.html');
262
- res.sendFile(indexPath, (err) => {
263
- if (err) {
264
- console.error("خطا در ارسال فایل index.html:", err);
265
- // ارسال یک پاسخ خطای ساده اگر فایل پیدا نشد
266
- if (!res.headersSent) {
267
- res.status(err.status || 500).send('خطا در بارگذاری برنامه');
268
- }
269
- }
270
- });
271
  });
272
 
273
-
274
- const PORT = process.env.PORT || 3001; // در هاگینگ فیس معمولا پورت 7860 استفاده می‌شود، اما اینجا 3001 است
275
  server.listen(PORT, () => {
276
- console.log(`سرور در حال اجرا روی پورت ${PORT}`);
277
- console.log(`لطفاً ${numKeys} کلید API با نام‌های GEMINI_API_KEY_1 تا GEMINI_API_KEY_${numKeys} را در Secrets تنظیم کرده باشید.`);
 
 
278
  });
 
1
+ // server/index.js
2
+
3
  const express = require('express');
4
  const path = require('node:path');
5
  const { WebSocketServer, WebSocket } = require('ws');
6
  const http = require('node:http');
7
+ require('dotenv').config();
8
 
9
  const app = express();
10
  const server = http.createServer(app);
11
  const wss = new WebSocketServer({ server });
12
 
13
+ // --- START: منطق جدید و بهبود یافته برای مدیریت API Key ---
14
 
15
+ // ۱. خواندن تمام کلیدها از یک Secret واحد (جدا شده با کاما)
16
+ const apiKeysEnv = process.env.ALL_GEMINI_API_KEYS;
17
+ const apiKeys = apiKeysEnv ? apiKeysEnv.split(',').map(key => key.trim()).filter(key => key) : [];
 
 
 
 
18
 
19
  const numKeys = apiKeys.length;
20
 
21
  if (numKeys === 0) {
 
22
  console.error(
23
+ 'خطای حیاتی: هیچ Secret با نام ALL_GEMINI_API_KEYS یافت نشد یا خالی است!' +
24
+ ' لطفاً کلیدهای خود را به صورت جدا شده با کاما در این Secret در تنظیمات Space خود اضافه کنید.'
 
25
  );
26
+ process.exit(1);
27
  } else {
28
+ console.log(`✅ تعداد ${numKeys} کلید API جیمینای با موفقیت بارگذاری شد.`);
29
  }
30
 
31
+ // ۲. شمارنده سراسری برای توزیع بار بین کاربران (انتخاب کلید شروع)
32
  let currentKeyIndex = 0;
33
 
34
+ function getStartingKeyIndex() {
35
+ const startingIndex = currentKeyIndex % numKeys;
 
 
 
 
 
 
 
 
 
 
36
  currentKeyIndex++;
37
+ return startingIndex;
 
 
 
 
 
 
38
  }
39
 
40
+ // --- END: منطق جدید مدیریت API Key ---
41
 
 
 
42
 
43
+ // این تابع رویدادهای لازم را به یک اتصال موفق جیمینای متصل می‌کند
44
+ function attachGeminiEventHandlers(clientWs, geminiWs, apiKeyUsed) {
45
+ // ارسال پیام از جیمینای به کلاینت
46
+ geminiWs.on('message', (data) => {
 
 
 
47
  try {
48
+ if (clientWs.readyState === WebSocket.OPEN) {
49
+ // پیام‌ها به صورت باینری (Blob) برای کلاینت ارسال می‌شوند
50
+ clientWs.send(data, { binary: true });
51
+ }
52
+ } catch (error) {
53
+ console.error(`🔴 خطا در پردازش پیام جیمینای (کلید ...${apiKeyUsed.slice(-4)}):`, error);
54
+ }
55
+ });
56
+
57
+ // رسیدگی به خطای اتصال جیمینای
58
+ geminiWs.on('error', (error) => {
59
+ console.error(`🔴 خطای WebSocket جیمینای (کلید ...${apiKeyUsed.slice(-4)}):`, error.message);
60
+ if (clientWs.readyState === WebSocket.OPEN) clientWs.close();
61
+ });
62
+
63
+ // رسیدگی به بسته شدن اتصال جیمینای
64
+ geminiWs.on('close', (code, reason) => {
65
+ // لاگ بسته شدن اتصال فقط در صورتی که غیرعادی باشد
66
+ if (code !== 1000) {
67
+ console.log(`🟡 اتصال WebSocket جیمینای بسته شد (کلید ...${apiKeyUsed.slice(-4)}). کد: ${code}, دلیل: ${reason.toString()}`);
68
+ }
69
+ if (clientWs.readyState === WebSocket.OPEN) clientWs.close();
70
+ });
71
+ }
72
+
73
+
74
+ // --- START: تابع هوشمند جدید برای اتصال با قابلیت Failover ---
75
+
76
+ /**
77
+ * تلاش برای اتصال به جیمینای با استفاده از لیست کلیدها.
78
+ * در صورت شکست، به صورت بازگشتی کلید بعدی را امتحان می‌کند.
79
+ * @param {WebSocket} clientWs - وب‌سوکت کلاینت کاربر.
80
+ * @param {object} setupData - داده‌های setup ارسال شده توسط کلاینت.
81
+ * @param {number} attemptIndex - اندیس کلیدی که باید امتحان شود.
82
+ * @returns {Promise<WebSocket|null>} - در صورت موفقیت، وب‌سوکت جیمینای را برمی‌گرداند.
83
+ */
84
+ async function tryConnectToGemini(clientWs, setupData, startIndex, attemptCount = 0) {
85
+ if (attemptCount >= numKeys) {
86
+ console.error(`🔴 تمام ${numKeys} کلید API امتحان شدند و ناموفق بودند. قطع اتصال کلاینت.`);
87
+ if (clientWs.readyState === WebSocket.OPEN) {
88
+ clientWs.send(JSON.stringify({ error: "خطای داخلی سرور: هیچ سرویس‌دهنده‌ای در دسترس نیست." }));
89
+ clientWs.close();
90
  }
91
+ return null;
92
  }
 
 
93
 
94
+ const keyIndexToTry = (startIndex + attemptCount) % numKeys;
95
+ const apiKey = apiKeys[keyIndexToTry];
96
+ // console.log(`⏳ تلاش برای اتصال با کلید اندیس ${keyIndexToTry} (کلید ...${apiKey.slice(-4)})`);
97
+
98
+ return new Promise((resolve) => {
99
+ const geminiWs = new WebSocket(
100
+ `wss://generativelanguage.googleapis.com/ws/google.ai.generativelanguage.v1alpha.GenerativeService.BidiGenerateContent?key=${apiKey}`
101
+ );
102
+
103
+ const timeout = setTimeout(() => {
104
+ geminiWs.removeAllListeners();
105
+ geminiWs.close();
106
+ console.warn(`🟡 تلاش برای اتصال با کلید ...${apiKey.slice(-4)} زمان‌بر شد. امتحان کلید بعدی...`);
107
+ resolve(tryConnectToGemini(clientWs, setupData, startIndex, attemptCount + 1));
108
+ }, 8000); // ۸ ثانیه مهلت برای اتصال
109
+
110
+ geminiWs.on('open', () => {
111
+ clearTimeout(timeout);
112
+ console.log(`✅ اتصال به جیمینای با کلید اندیس ${keyIndexToTry} (کلید ...${apiKey.slice(-4)}) موفقیت‌آمیز بود.`);
113
+
114
+ // ارسال پیام setup به محض برقراری اتصال
115
+ try {
116
+ geminiWs.send(JSON.stringify(setupData));
117
+ } catch (sendError) {
118
+ console.error(`🔴 خطا در ارسال پیام setup اولیه به جیمینای:`, sendError);
119
+ clientWs.close();
120
+ resolve(null); // اتصال ناموفق بود
121
+ return;
122
+ }
123
+
124
+ // اتصال سایر event handler ها
125
+ attachGeminiEventHandlers(clientWs, geminiWs, apiKey);
126
+ resolve(geminiWs); // اتصال موفق را برگردان
127
+ });
128
+
129
+ geminiWs.on('error', (error) => {
130
+ clearTimeout(timeout);
131
+ geminiWs.removeAllListeners();
132
+ console.warn(`🟡 اتصال با کلید اندیس ${keyIndexToTry} (کلید ...${apiKey.slice(-4)}) ناموفق بود. دلیل: ${error.message.split('Unexpected server response: ')[1] || 'خطای نامشخص'}. امتحان کلید بعدی...`);
133
+ // به صورت بازگشتی کلید بعدی را امتحان کن
134
+ resolve(tryConnectToGemini(clientWs, setupData, startIndex, attemptCount + 1));
135
+ });
136
+ });
137
+ }
138
 
139
+ // --- END: تابع هوشمند جدید ---
 
 
 
140
 
141
+
142
+ // سرو کردن فایل‌های استاتیک از پوشه build ری‌اکت
143
+ app.use(express.static(path.join(__dirname, '../build')));
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
144
 
145
  wss.on('connection', (ws) => {
146
+ // به هر کلاینت یک کلید شروع اختصاص داده می‌شود تا بار توزیع شود
147
+ const startingKeyIndex = getStartingKeyIndex();
148
+ console.log(`🔌 یک کلاینت جدید متصل شد. کلید شروع اختصاص ��افته: اندیس ${startingKeyIndex} (کلید ...${apiKeys[startingKeyIndex].slice(-4)})`);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
149
 
150
+ let geminiWs = null; // اتصال جیمینای برای این کلاینت
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
151
 
152
+ ws.on('message', async (message) => {
153
+ try {
154
+ const data = JSON.parse(message.toString());
155
+
156
+ // اولین پیام باید `setup` باشد
157
+ if (data.setup) {
158
+ if (geminiWs) {
159
+ // console.warn("کلاینت دوباره پیام setup ارسال کرد. نادیده گرفته شد.");
160
+ return;
161
+ }
162
+
163
+ // شروع فرآیند اتصال با قابلیت Failover
164
+ geminiWs = await tryConnectToGemini(ws, data, startingKeyIndex);
165
+
166
+ if (!geminiWs) {
167
+ console.error("🔴 اتصال به سرویس جیمینای برای این کلاینت پس از امتحان همه کلیدها ناموفق بود.");
168
+ }
169
+ return;
170
+ }
171
+
172
+ // ارسال پیام‌های بعدی به جیمینای
173
+ if (geminiWs && geminiWs.readyState === WebSocket.OPEN) {
174
+ geminiWs.send(JSON.stringify(data));
175
+ } else if (!geminiWs) {
176
+ console.warn("🟡 پیام از کلاینت دریافت شد اما اتصال جیمینای هنوز برقرار نشده یا ناموفق بوده است.");
177
+ // می توان یک پیام خطا به کلاینت فرستاد
178
+ }
179
+
180
+ } catch (error) {
181
+ console.error('🔴 خطا در پردازش پیام کلاینت (احتمالا JSON نامعتبر):', error);
182
  }
183
+ });
 
 
 
 
 
184
 
185
+ ws.on('close', () => {
186
+ console.log(`🔌 کلاینت با کلید شروع اندیس ${startingKeyIndex} قطع شد.`);
187
+ if (geminiWs && geminiWs.readyState !== WebSocket.CLOSED) {
188
+ geminiWs.close();
189
+ }
190
+ });
 
 
 
 
 
 
 
 
 
 
191
 
192
+ ws.on('error', (error) => {
193
+ console.error(`🔴 خطای WebSocket کلاینت (کلید شروع اندیس ${startingKeyIndex}):`, error);
194
+ if (geminiWs && geminiWs.readyState !== WebSocket.CLOSED) {
195
+ geminiWs.close();
196
+ }
197
+ });
198
  });
199
 
200
  // رسیدگی به درخواست‌های باقیمانده با برگرداندن اپ React
201
  app.get('*', (req, res) => {
202
+ const indexPath = path.join(__dirname, '../build', 'index.html');
203
+ res.sendFile(indexPath, (err) => {
204
+ if (err) {
205
+ if (!res.headersSent) {
206
+ res.status(500).send('خطا در بارگذاری برنامه');
207
+ }
208
+ }
209
+ });
 
 
 
210
  });
211
 
212
+ const PORT = process.env.PORT || 3001;
 
213
  server.listen(PORT, () => {
214
+ console.log(`🚀 سرور در حال اجرا روی پورت ${PORT}`);
215
+ if (numKeys > 0) {
216
+ console.log(`🔑 برای استفاده، Secret با نام 'ALL_GEMINI_API_KEYS' را با کلید(های) خود تنظیم کنید.`);
217
+ }
218
  });