Hamed744 commited on
Commit
f0896a3
·
verified ·
1 Parent(s): 9e404a1

Update server/index.js

Browse files
Files changed (1) hide show
  1. server/index.js +168 -57
server/index.js CHANGED
@@ -6,122 +6,233 @@ const http = require('node:http');
6
  require('dotenv').config();
7
 
8
  const app = express();
9
- const server = http.createServer(app);
10
- const wss = new WebSocketServer({ server });
 
 
11
 
12
- // بخش خواندن دستورالعمل‌های شخصیت از Secrets
 
 
 
 
 
13
  const instructionSecretNames = {
14
  default: 'PERSONALITY_DEFAULT',
15
  teacher: 'PERSONALITY_TEACHER',
16
  poetic: 'PERSONALITY_POETIC',
17
  funny: 'PERSONALITY_FUNNY',
18
  };
 
19
  const personalityInstructions = {};
20
- console.log('در حال خواندن دستورالعمل‌های شخصیت از Secrets...');
21
  Object.keys(instructionSecretNames).forEach(key => {
22
  const secretName = instructionSecretNames[key];
23
  const instruction = process.env[secretName];
24
  if (instruction) {
25
  personalityInstructions[key] = instruction;
26
- console.log(`- دستورالعمل '${key}' با موفقیت خوانده شد.`);
27
  } else {
28
- personalityInstructions[key] = `دستورالعمل '${key}' از سرور بارگذاری نشد.`;
29
- console.warn(`** هشدار: Secret با نام '${secretName}' یافت نشد! **`);
30
  }
31
  });
32
 
33
- // بخش مدیریت کلیدهای API (کد هوشمند شما بدون تغییر)
34
  const apiKeysEnv = process.env.ALL_GEMINI_API_KEYS;
35
  const apiKeys = apiKeysEnv ? apiKeysEnv.split(',').map(key => key.trim()).filter(key => key) : [];
 
36
  if (apiKeys.length === 0) {
37
- console.error('🔴 خطای حیاتی: Secret با نام ALL_GEMINI_API_KEYS یافت نشد!');
38
  process.exit(1);
39
  }
40
- console.log(`✅ تعداد ${apiKeys.length} کلید API بارگذاری شد.`);
 
41
  let currentKeyIndex = 0;
42
- const getStartingKeyIndex = () => {
43
- const index = currentKeyIndex % apiKeys.length;
44
- currentKeyIndex++;
45
- return index;
 
46
  };
47
- // توابع attachGeminiEventHandlers و tryConnectToGemini شما اینجا بدون تغییر قرار می‌گیرند.
48
- // ... (برای کوتاهی اینجا حذف شده، اما شما باید آنها را نگه دارید) ...
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
49
  function attachGeminiEventHandlers(clientWs, geminiWs, apiKeyUsed) {
50
- geminiWs.on('message', data => clientWs.readyState === WebSocket.OPEN && clientWs.send(data, { binary: true }));
51
- geminiWs.on('error', error => {
52
- console.error(`🔴 خطای WebSocket جیمینای (کلید ...${apiKeyUsed.slice(-4)}):`, error.message);
53
- clientWs.readyState === WebSocket.OPEN && clientWs.close();
 
54
  });
55
- geminiWs.on('close', code => {
56
- if (code !== 1000) console.log(`🟡 اتصال جیمینای بسته شد (کلید ...${apiKeyUsed.slice(-4)}). کد: ${code}`);
57
- clientWs.readyState === WebSocket.OPEN && clientWs.close();
 
 
 
 
 
 
 
 
58
  });
 
 
 
59
  }
 
 
 
 
60
  async function tryConnectToGemini(clientWs, setupData, startIndex, attemptCount = 0) {
 
 
 
61
  if (attemptCount >= apiKeys.length) {
62
- console.error(`🔴 تمام ${apiKeys.length} کلید API ناموفق بودند.`);
63
- if (clientWs.readyState === WebSocket.OPEN) clientWs.send(JSON.stringify({ error: "خطای داخلی سرور." })) && clientWs.close();
 
 
 
64
  return null;
65
  }
 
66
  const keyIndexToTry = (startIndex + attemptCount) % apiKeys.length;
67
  const apiKey = apiKeys[keyIndexToTry];
68
- return new Promise(resolve => {
69
- const geminiWs = new WebSocket(`wss://generativelanguage.googleapis.com/ws/google.ai.generativelanguage.v1alpha.GenerativeService.BidiGenerateContent?key=${apiKey}`);
 
 
 
 
70
  const timeout = setTimeout(() => {
71
- geminiWs.removeAllListeners() && geminiWs.close();
72
- console.warn(`🟡 اتصال با کلید ...${apiKey.slice(-4)} زمان‌بر شد. امتحان کلید بعدی...`);
73
  resolve(tryConnectToGemini(clientWs, setupData, startIndex, attemptCount + 1));
74
  }, 8000);
 
75
  geminiWs.on('open', () => {
76
  clearTimeout(timeout);
77
- console.log(`✅ اتصال با کلید اندیس ${keyIndexToTry} موفق بود.`);
 
 
 
 
 
 
 
78
  try {
79
  geminiWs.send(JSON.stringify(setupData));
 
 
80
  } catch (e) {
81
- clientWs.close();
82
- resolve(null);
83
- return;
84
  }
85
- attachGeminiEventHandlers(clientWs, geminiWs, apiKey);
86
- resolve(geminiWs);
87
  });
88
- geminiWs.on('error', () => {
 
89
  clearTimeout(timeout);
90
- geminiWs.removeAllListeners();
91
- console.warn(`🟡 اتصال با کلید ...${apiKey.slice(-4)} ناموفق بود. امتحان کلید بعدی...`);
92
  resolve(tryConnectToGemini(clientWs, setupData, startIndex, attemptCount + 1));
93
  });
94
  });
95
  }
96
 
97
- // سرو کردن فایل‌های استاتیک
98
  app.use(express.static(path.join(__dirname, '../build')));
99
 
100
- // API Endpoint جدید برای ارسال دستورالعمل‌ها
101
- app.get('/api/instructions', (req, res) => res.json(personalityInstructions));
 
 
 
 
 
 
 
 
102
 
103
- // مدیریت اتصال WebSocket کلاینت (بدون تغییر)
104
- wss.on('connection', ws => {
105
- const startingKeyIndex = getStartingKeyIndex();
106
- console.log(`🔌 کلاینت جدید متصل شد. کلید شروع: اندیس ${startingKeyIndex}`);
107
  let geminiWs = null;
108
- ws.on('message', async message => {
 
 
109
  try {
110
- const data = JSON.parse(message.toString());
111
- if (data.setup) {
112
- if (geminiWs) return;
113
- geminiWs = await tryConnectToGemini(ws, data, startingKeyIndex);
114
- } else if (geminiWs?.readyState === WebSocket.OPEN) {
115
- geminiWs.send(JSON.stringify(data));
 
 
 
 
 
 
 
 
 
 
 
 
 
116
  }
117
- } catch (e) { /* خطا */ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
118
  });
119
- ws.on('close', () => console.log(`🔌 کلاینت با کلید شروع اندیس ${startingKeyIndex} قطع شد.`) && geminiWs?.close());
120
- ws.on('error', error => console.error(`🔴 خطای WebSocket کلاینت:`, error) && geminiWs?.close());
121
  });
122
 
123
- // رسیدگی به سایر درخواست‌ها
124
  app.get('*', (req, res) => res.sendFile(path.join(__dirname, '../build', 'index.html')));
125
 
126
  const PORT = process.env.PORT || 3001;
127
- server.listen(PORT, () => console.log(`🚀 سرور در حال اجرا روی پورت ${PORT}`));
 
6
  require('dotenv').config();
7
 
8
  const app = express();
9
+ // افزایش محدودیت‌های سرور برای هندل کردن تعداد بالای درخواست
10
+ const server = http.createServer({
11
+ maxHeaderSize: 16384 // 16KB
12
+ }, app);
13
 
14
+ const wss = new WebSocketServer({
15
+ server,
16
+ clientTracking: true // برای مدیریت هارت‌بیت ضروری است
17
+ });
18
+
19
+ // --- بخش مدیریت تنظیمات شخصیت‌ها ---
20
  const instructionSecretNames = {
21
  default: 'PERSONALITY_DEFAULT',
22
  teacher: 'PERSONALITY_TEACHER',
23
  poetic: 'PERSONALITY_POETIC',
24
  funny: 'PERSONALITY_FUNNY',
25
  };
26
+
27
  const personalityInstructions = {};
28
+ console.log('🔄 در حال خواندن دستورالعمل‌های شخصیت از Secrets...');
29
  Object.keys(instructionSecretNames).forEach(key => {
30
  const secretName = instructionSecretNames[key];
31
  const instruction = process.env[secretName];
32
  if (instruction) {
33
  personalityInstructions[key] = instruction;
34
+ console.log(`✅ دستورالعمل '${key}' با موفقیت خوانده شد.`);
35
  } else {
36
+ personalityInstructions[key] = `دستورالعمل '${key}' یافت نشد.`;
37
+ console.warn(`⚠️ هشدار: Secret با نام '${secretName}' تنظیم نشده است.`);
38
  }
39
  });
40
 
41
+ // --- بخش مدیریت کلیدهای API (Load Balancing) ---
42
  const apiKeysEnv = process.env.ALL_GEMINI_API_KEYS;
43
  const apiKeys = apiKeysEnv ? apiKeysEnv.split(',').map(key => key.trim()).filter(key => key) : [];
44
+
45
  if (apiKeys.length === 0) {
46
+ console.error('🔴 خطای حیاتی: هیچ کلید API در متغیر ALL_GEMINI_API_KEYS یافت نشد!');
47
  process.exit(1);
48
  }
49
+ console.log(`🚀 سرور با ${apiKeys.length} کلید API آماده‌سازی شد.`);
50
+
51
  let currentKeyIndex = 0;
52
+ const getNextKeyIndex = () => {
53
+ // چرخش ساده بین کلیدها (Round Robin)
54
+ const index = currentKeyIndex;
55
+ currentKeyIndex = (currentKeyIndex + 1) % apiKeys.length;
56
+ return index;
57
  };
58
+
59
+ // --- مکانیزم Heartbeat برای جلوگیری از قطعی سرور ---
60
+ // این تابع اتصالات مرده را شناسایی و حذف می‌کند
61
+ function heartbeat() {
62
+ this.isAlive = true;
63
+ }
64
+
65
+ const interval = setInterval(function ping() {
66
+ wss.clients.forEach(function each(ws) {
67
+ if (ws.isAlive === false) {
68
+ console.log('💀 قطع اتصال مرده (Zombie Connection) برای آزادسازی حافظه.');
69
+ return ws.terminate();
70
+ }
71
+
72
+ ws.isAlive = false;
73
+ ws.ping();
74
+ });
75
+ }, 30000); // چک کردن هر ۳۰ ثانیه
76
+
77
+ wss.on('close', function close() {
78
+ clearInterval(interval);
79
+ });
80
+
81
+ // --- توابع اتصال به گوگل ---
82
+
83
+ /**
84
+ * اتصال کلاینت و جمینای را به هم متصل می‌کند
85
+ */
86
  function attachGeminiEventHandlers(clientWs, geminiWs, apiKeyUsed) {
87
+ // انتقال پیام از گوگل به کلاینت
88
+ geminiWs.on('message', (data) => {
89
+ if (clientWs.readyState === WebSocket.OPEN) {
90
+ clientWs.send(data, { binary: true });
91
+ }
92
  });
93
+
94
+ geminiWs.on('error', (error) => {
95
+ console.error(`🔴 خطای جمینای (کلید ...${apiKeyUsed.slice(-4)}):`, error.message);
96
+ if (clientWs.readyState === WebSocket.OPEN) clientWs.close();
97
+ });
98
+
99
+ geminiWs.on('close', (code) => {
100
+ if (code !== 1000 && code !== 1005) {
101
+ console.log(`🟡 گوگل اتصال را بست (کلید ...${apiKeyUsed.slice(-4)}). کد: ${code}`);
102
+ }
103
+ if (clientWs.readyState === WebSocket.OPEN) clientWs.close();
104
  });
105
+
106
+ // هندل کردن پونگ برای زنده نگه داشتن اتصال گوگل (اختیاری ولی مفید)
107
+ geminiWs.on('ping', () => geminiWs.pong());
108
  }
109
+
110
+ /**
111
+ * تلاش برای اتصال به جمینای با قابلیت امتحان مجدد (Retry Logic)
112
+ */
113
  async function tryConnectToGemini(clientWs, setupData, startIndex, attemptCount = 0) {
114
+ // اگر کلاینت در حین تلاش قطع شد، ادامه نده
115
+ if (clientWs.readyState !== WebSocket.OPEN) return null;
116
+
117
  if (attemptCount >= apiKeys.length) {
118
+ console.error(`⛔ تمام ${apiKeys.length} کلید API با شکست مواجه شدند.`);
119
+ if (clientWs.readyState === WebSocket.OPEN) {
120
+ clientWs.send(JSON.stringify({ error: "سرویس موقتاً در دسترس نیست (ظرفیت تکمیل)." }));
121
+ clientWs.close();
122
+ }
123
  return null;
124
  }
125
+
126
  const keyIndexToTry = (startIndex + attemptCount) % apiKeys.length;
127
  const apiKey = apiKeys[keyIndexToTry];
128
+ const url = `wss://generativelanguage.googleapis.com/ws/google.ai.generativelanguage.v1alpha.GenerativeService.BidiGenerateContent?key=${apiKey}`;
129
+
130
+ return new Promise((resolve) => {
131
+ const geminiWs = new WebSocket(url);
132
+
133
+ // تایم‌اوت اتصال (اگر گوگل تا ۸ ثانیه جواب نداد، برو کلید بعدی)
134
  const timeout = setTimeout(() => {
135
+ console.warn(`⏳ تایم‌اوت اتصال (کلید ...${apiKey.slice(-4)}). تلاش مجدد...`);
136
+ geminiWs.terminate(); // کشتن اتصال گیر کرده
137
  resolve(tryConnectToGemini(clientWs, setupData, startIndex, attemptCount + 1));
138
  }, 8000);
139
+
140
  geminiWs.on('open', () => {
141
  clearTimeout(timeout);
142
+ // بررسی مجدد وضعیت کلاینت قبل از ارسال
143
+ if (clientWs.readyState !== WebSocket.OPEN) {
144
+ geminiWs.close();
145
+ return resolve(null);
146
+ }
147
+
148
+ console.log(`🔗 متصل شد به گوگل با کلید اندیس ${keyIndexToTry}`);
149
+
150
  try {
151
  geminiWs.send(JSON.stringify(setupData));
152
+ attachGeminiEventHandlers(clientWs, geminiWs, apiKey);
153
+ resolve(geminiWs);
154
  } catch (e) {
155
+ console.error("خطا در ارسال تنظیمات اولیه:", e);
156
+ geminiWs.close();
157
+ resolve(tryConnectToGemini(clientWs, setupData, startIndex, attemptCount + 1));
158
  }
 
 
159
  });
160
+
161
+ geminiWs.on('error', (err) => {
162
  clearTimeout(timeout);
163
+ // فقط لاگ کن و برو بعدی، هندلرهای اصلی هنوز ست نشدن
164
+ console.warn(`⚠️ خطای اتصال (کلید ...${apiKey.slice(-4)}): ${err.message || 'Unknown'}`);
165
  resolve(tryConnectToGemini(clientWs, setupData, startIndex, attemptCount + 1));
166
  });
167
  });
168
  }
169
 
170
+ // --- سرو کردن فایل‌های استاتیک ---
171
  app.use(express.static(path.join(__dirname, '../build')));
172
 
173
+ // API Endpoint دستورالعمل‌ها
174
+ app.get('/api/instructions', (req, res) => {
175
+ res.setHeader('Cache-Control', 'no-store'); // جلوگیری از کش شدن قدیمی
176
+ res.json(personalityInstructions);
177
+ });
178
+
179
+ // --- مدیریت WebSocket کلاینت ---
180
+ wss.on('connection', (ws, req) => {
181
+ ws.isAlive = true; // نشان‌گذاری به عنوان زنده
182
+ ws.on('pong', heartbeat); // پاسخ به پینگ سرور
183
 
184
+ const startingKeyIndex = getNextKeyIndex();
185
+ // const clientIp = req.socket.remoteAddress; // برای لاگ کردن IP در صورت نیاز
186
+
 
187
  let geminiWs = null;
188
+ let isConnecting = false;
189
+
190
+ ws.on('message', async (message) => {
191
  try {
192
+ // اگر پیام متنی است (JSON)
193
+ if (!Buffer.isBuffer(message) || message[0] === 123) {
194
+ const msgStr = message.toString();
195
+ // چک کردن ساده برای تشخیص JSON
196
+ if (msgStr.startsWith('{')) {
197
+ const data = JSON.parse(msgStr);
198
+ if (data.setup) {
199
+ if (isConnecting || geminiWs) return; // جلوگیری از دوبار وصل شدن
200
+ isConnecting = true;
201
+ geminiWs = await tryConnectToGemini(ws, data, startingKeyIndex);
202
+ isConnecting = false;
203
+ return;
204
+ }
205
+ }
206
+ }
207
+
208
+ // اگر به گوگل وصلیم، پیام را بفرست
209
+ if (geminiWs && geminiWs.readyState === WebSocket.OPEN) {
210
+ geminiWs.send(message);
211
  }
212
+ } catch (e) {
213
+ console.error("خطا در پردازش پیام کلاینت:", e.message);
214
+ }
215
+ });
216
+
217
+ // وقتی کاربر مرورگر را می‌بندد یا اینترنتش قطع می‌شود
218
+ ws.on('close', () => {
219
+ // console.log('👋 کلاینت قطع شد.');
220
+ if (geminiWs) {
221
+ if (geminiWs.readyState === WebSocket.OPEN || geminiWs.readyState === WebSocket.CONNECTING) {
222
+ geminiWs.close();
223
+ }
224
+ geminiWs = null;
225
+ }
226
+ });
227
+
228
+ ws.on('error', (error) => {
229
+ console.error(`خطای سوکت کلاینت: ${error.message}`);
230
+ if (geminiWs) geminiWs.close();
231
  });
 
 
232
  });
233
 
234
+ // ارسال تمام درخواست‌های دیگر به React
235
  app.get('*', (req, res) => res.sendFile(path.join(__dirname, '../build', 'index.html')));
236
 
237
  const PORT = process.env.PORT || 3001;
238
+ server.listen(PORT, () => console.log(`🚀 سرور (نسخه پایدار) روی پورت ${PORT} اجرا شد.`));