Leon4gr45 commited on
Commit
2f71ff4
·
verified ·
1 Parent(s): 4537df0

CLEAN REDEPLOY: Secure Express state with Dashboard fix

Browse files
Files changed (2) hide show
  1. server.js +7 -15
  2. src/App.tsx +72 -50
server.js CHANGED
@@ -47,7 +47,7 @@ app.post('/api/login', (req, res) => {
47
  addLog("Successful login attempt.");
48
  res.json({ success: true });
49
  } else {
50
- addLog("Failed login attempt.");
51
  res.status(401).json({ success: false, error: 'Invalid passkey' });
52
  }
53
  });
@@ -90,11 +90,10 @@ app.post('/api/start', checkPasskey, async (req, res) => {
90
  wppClient = await wppconnect.create({
91
  session: 'gradio-session',
92
  autoClose: 0,
93
- updatesLog: false,
94
  catchQR: (base64Qr) => {
95
  currentStatus = 'QR_CODE';
96
  qrCodeBase64 = base64Qr;
97
- addLog("QR Code generated.");
98
  },
99
  statusFind: (statusSession) => {
100
  addLog(`Session Status: ${statusSession}`);
@@ -116,13 +115,6 @@ app.post('/api/start', checkPasskey, async (req, res) => {
116
  ]
117
  }
118
  });
119
-
120
- // Check initial status
121
- const isLogged = await wppClient.isLoggedIn();
122
- if (isLogged) {
123
- currentStatus = 'CONNECTED';
124
- addLog("Already logged in.");
125
- }
126
 
127
  } catch (error) {
128
  addLog(`WPPConnect Error: ${error.message}`);
@@ -139,8 +131,8 @@ app.post('/api/send', checkPasskey, async (req, res) => {
139
  if (!phone.includes('@')) recipient = isGroup ? `${phone}@g.us` : `${phone}@c.us`;
140
  addLog(`Sending message to ${recipient}...`);
141
  const result = await wppClient.sendText(recipient, message);
 
142
  res.json({ success: true, result });
143
- addLog("Message sent!");
144
  } catch (error) {
145
  addLog(`Send Error: ${error.message}`);
146
  res.status(500).json({ error: error.message });
@@ -155,10 +147,10 @@ if (fs.existsSync(distPath)) {
155
  res.sendFile(path.join(distPath, 'index.html'));
156
  });
157
  } else {
158
- app.get('*', (req, res) => {
159
- if (req.path.startsWith('/api/')) return res.status(404).json({ error: 'API route not found' });
160
- res.send("Frontend build not found. Please run 'npm run build'.");
161
- });
162
  }
163
 
164
  app.listen(PORT, "0.0.0.0", () => {
 
47
  addLog("Successful login attempt.");
48
  res.json({ success: true });
49
  } else {
50
+ addLog(`Failed login attempt with key: ${passkey}`);
51
  res.status(401).json({ success: false, error: 'Invalid passkey' });
52
  }
53
  });
 
90
  wppClient = await wppconnect.create({
91
  session: 'gradio-session',
92
  autoClose: 0,
93
+ updatesLog: true,
94
  catchQR: (base64Qr) => {
95
  currentStatus = 'QR_CODE';
96
  qrCodeBase64 = base64Qr;
 
97
  },
98
  statusFind: (statusSession) => {
99
  addLog(`Session Status: ${statusSession}`);
 
115
  ]
116
  }
117
  });
 
 
 
 
 
 
 
118
 
119
  } catch (error) {
120
  addLog(`WPPConnect Error: ${error.message}`);
 
131
  if (!phone.includes('@')) recipient = isGroup ? `${phone}@g.us` : `${phone}@c.us`;
132
  addLog(`Sending message to ${recipient}...`);
133
  const result = await wppClient.sendText(recipient, message);
134
+ addLog("Message sent successfully!");
135
  res.json({ success: true, result });
 
136
  } catch (error) {
137
  addLog(`Send Error: ${error.message}`);
138
  res.status(500).json({ error: error.message });
 
147
  res.sendFile(path.join(distPath, 'index.html'));
148
  });
149
  } else {
150
+ app.get('*', (req, res) => {
151
+ if (req.path.startsWith('/api/')) return res.status(404).json({ error: 'API route not found' });
152
+ res.send("Frontend build not found. Please run 'npm run build'.");
153
+ });
154
  }
155
 
156
  app.listen(PORT, "0.0.0.0", () => {
src/App.tsx CHANGED
@@ -9,11 +9,9 @@ import {
9
  Terminal,
10
  LogOut,
11
  AlertCircle,
12
- Clock,
13
- Link as LinkIcon,
14
- Plus,
15
  Unlock,
16
- ShieldCheck
 
17
  } from 'lucide-react';
18
 
19
  type Status = 'DISCONNECTED' | 'INITIALIZING' | 'QR_CODE' | 'CONNECTED' | 'ERROR';
@@ -22,6 +20,7 @@ function App() {
22
  const [isAuthenticated, setIsAuthenticated] = useState<boolean>(false);
23
  const [passkey, setPasskey] = useState('');
24
  const [loginError, setLoginError] = useState('');
 
25
 
26
  const [status, setStatus] = useState<Status>('DISCONNECTED');
27
  const [qrCode, setQrCode] = useState('');
@@ -44,19 +43,23 @@ function App() {
44
 
45
  const checkInitialAuth = async (key: string) => {
46
  try {
47
- const response = await fetch(`/api/status?passkey=${key}`);
 
 
48
  if (response.ok) {
49
  setIsAuthenticated(true);
50
  } else {
51
  localStorage.removeItem('passkey');
52
  }
53
  } catch (e) {
54
- // Offline or server error
55
  }
56
  };
57
 
58
  const handleLogin = async (e: React.FormEvent) => {
59
  e.preventDefault();
 
 
60
  setLoginError('');
61
  try {
62
  const response = await fetch('/api/login', {
@@ -72,7 +75,9 @@ function App() {
72
  setLoginError('Invalid passkey. Please try again.');
73
  }
74
  } catch (err) {
75
- setLoginError('Could not connect to authentication server.');
 
 
76
  }
77
  };
78
 
@@ -86,9 +91,11 @@ function App() {
86
  if (!isAuthenticated) return;
87
 
88
  const pollStatus = async () => {
89
- const savedPasskey = localStorage.getItem('passkey');
90
  try {
91
- const response = await fetch(`/api/status?passkey=${savedPasskey}`);
 
 
92
  if (response.status === 401) {
93
  handleLogout();
94
  return;
@@ -114,9 +121,11 @@ function App() {
114
  }, [isAuthenticated, groups.length]);
115
 
116
  const fetchGroups = async () => {
117
- const savedPasskey = localStorage.getItem('passkey');
118
  try {
119
- const response = await fetch(`/api/groups?passkey=${savedPasskey}`);
 
 
120
  if (response.ok) {
121
  const data = await response.json();
122
  if (data.groups) setGroups(data.groups);
@@ -127,24 +136,30 @@ function App() {
127
  };
128
 
129
  const startService = async () => {
130
- const savedPasskey = localStorage.getItem('passkey');
131
  try {
132
- await fetch(`/api/start?passkey=${savedPasskey}`, { method: 'POST' });
 
 
 
133
  } catch (err) {
134
- setLogs(prev => [...prev, `Error starting service: ${err}`]);
135
  }
136
  };
137
 
138
  const sendMessage = async (e: React.FormEvent) => {
139
  e.preventDefault();
140
  setIsSending(true);
141
- const savedPasskey = localStorage.getItem('passkey');
142
  const recipient = activeTab === 'direct' ? phone : selectedGroup;
143
 
144
  try {
145
- const response = await fetch(`/api/send?passkey=${savedPasskey}`, {
146
  method: 'POST',
147
- headers: { 'Content-Type': 'application/json' },
 
 
 
148
  body: JSON.stringify({
149
  phone: recipient,
150
  message,
@@ -153,6 +168,7 @@ function App() {
153
  });
154
  if (response.ok) {
155
  setMessage('');
 
156
  } else {
157
  const data = await response.json();
158
  alert(`Error: ${data.error}`);
@@ -170,26 +186,24 @@ function App() {
170
 
171
  if (!isAuthenticated) {
172
  return (
173
- <div className="min-h-screen bg-[#0f172a] flex items-center justify-center p-4">
174
  <div className="max-w-md w-full bg-[#1e293b] rounded-3xl shadow-2xl border border-white/5 overflow-hidden">
175
  <div className="p-8 text-center">
176
  <div className="w-20 h-20 bg-orange-500/20 rounded-2xl flex items-center justify-center mx-auto mb-6 border border-orange-500/30 shadow-inner">
177
  <ShieldCheck className="w-10 h-10 text-orange-500" />
178
  </div>
179
- <h1 className="text-3xl font-bold text-white mb-2">Private Access</h1>
180
  <p className="text-slate-400 mb-8">Enter your security passkey to manage the WPPConnect server.</p>
181
 
182
  <form onSubmit={handleLogin} className="space-y-4">
183
- <div className="relative group">
184
- <input
185
- type="password"
186
- placeholder="Security Passkey..."
187
- value={passkey}
188
- onChange={(e) => setPasskey(e.target.value)}
189
- className="w-full px-6 py-4 bg-slate-900/50 border border-slate-700 rounded-2xl focus:ring-4 focus:ring-orange-500/20 focus:border-orange-500 outline-none transition-all text-center text-xl font-mono tracking-widest text-white placeholder:tracking-normal placeholder:font-sans placeholder:text-slate-500"
190
- autoFocus
191
- />
192
- </div>
193
 
194
  {loginError && (
195
  <div className="flex items-center justify-center gap-2 text-red-400 text-sm font-medium bg-red-500/10 py-3 rounded-xl border border-red-500/20">
@@ -200,9 +214,10 @@ function App() {
200
 
201
  <button
202
  type="submit"
203
- className="w-full bg-orange-500 hover:bg-orange-600 text-white font-bold py-4 rounded-2xl transition-all shadow-lg shadow-orange-500/25 flex items-center justify-center gap-3 active:scale-[0.98]"
 
204
  >
205
- <Unlock className="w-5 h-5" />
206
  Unlock Dashboard
207
  </button>
208
  </form>
@@ -216,9 +231,8 @@ function App() {
216
  }
217
 
218
  return (
219
- <div className="min-h-screen bg-[#f8f9fa] text-gray-900 font-sans">
220
  <div className="max-w-6xl mx-auto px-4 py-8">
221
- {/* Header */}
222
  <header className="flex flex-col md:flex-row md:items-center justify-between gap-4 mb-8 bg-white p-6 rounded-2xl shadow-sm border border-gray-100">
223
  <div className="flex items-center gap-4">
224
  <div className="w-12 h-12 bg-gradient-to-br from-orange-500 to-red-500 rounded-xl flex items-center justify-center shadow-lg shadow-orange-200">
@@ -253,7 +267,6 @@ function App() {
253
  </header>
254
 
255
  <div className="grid grid-cols-1 lg:grid-cols-2 gap-8">
256
- {/* Left Column: Messaging */}
257
  <div className="space-y-6">
258
  <div className="bg-white rounded-2xl shadow-sm border border-gray-100 p-6">
259
  <div className="flex gap-1 p-1 bg-gray-50 rounded-xl mb-6">
@@ -261,13 +274,13 @@ function App() {
261
  onClick={() => setActiveTab('direct')}
262
  className={`flex-1 flex items-center justify-center gap-2 py-2 text-sm font-semibold rounded-lg transition-all ${activeTab === 'direct' ? 'bg-white shadow-sm text-orange-600' : 'text-gray-500 hover:text-gray-700'}`}
263
  >
264
- <MessageSquare className="w-4 h-4" /> Direct Message
265
  </button>
266
  <button
267
  onClick={() => setActiveTab('group')}
268
  className={`flex-1 flex items-center justify-center gap-2 py-2 text-sm font-semibold rounded-lg transition-all ${activeTab === 'group' ? 'bg-white shadow-sm text-orange-600' : 'text-gray-500 hover:text-gray-700'}`}
269
  >
270
- <Users className="w-4 h-4" /> Group Message
271
  </button>
272
  </div>
273
 
@@ -277,8 +290,8 @@ function App() {
277
  <QrCode className="w-8 h-8" />
278
  </div>
279
  <div>
280
- <h3 className="font-semibold text-gray-900">Not Connected</h3>
281
- <p className="text-sm text-gray-500 max-w-[200px] mx-auto mt-1">Please authenticate with WhatsApp to start messaging.</p>
282
  </div>
283
  </div>
284
  ) : (
@@ -297,16 +310,26 @@ function App() {
297
  ) : (
298
  <div className="space-y-2">
299
  <label className="block text-sm font-medium text-gray-700">Select Group</label>
300
- <select
301
- value={selectedGroup}
302
- onChange={(e) => setSelectedGroup(e.target.value)}
303
- className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-orange-500 focus:border-orange-500 outline-none disabled:bg-gray-100"
304
- >
305
- <option value="">-- Choose a group --</option>
306
- {groups.map((g) => (
307
- <option key={g.id._serialized} value={g.id._serialized}>{g.name || g.id.user}</option>
308
- ))}
309
- </select>
 
 
 
 
 
 
 
 
 
 
310
  </div>
311
  )}
312
 
@@ -334,14 +357,13 @@ function App() {
334
  </div>
335
  </div>
336
 
337
- {/* Right Column: QR Code & Logs */}
338
  <div className="space-y-6 flex flex-col">
339
  <div className="bg-white rounded-xl shadow-sm border border-gray-200 p-6 flex flex-col items-center justify-center min-h-[300px]">
340
  <h2 className="text-lg font-semibold mb-4 border-b pb-2 w-full text-left text-gray-900">Authentication</h2>
341
  {status === 'QR_CODE' && qrCode ? (
342
  <div className="flex flex-col items-center animate-in fade-in zoom-in duration-300">
343
  <img src={qrCode} alt="WhatsApp QR Code" className="w-64 h-64 border-4 border-white shadow-sm rounded-lg" />
344
- <p className="text-sm text-gray-500 mt-4 text-center">Scan this QR code with your WhatsApp app.</p>
345
  </div>
346
  ) : status === 'CONNECTED' ? (
347
  <div className="flex flex-col items-center text-green-500">
 
9
  Terminal,
10
  LogOut,
11
  AlertCircle,
 
 
 
12
  Unlock,
13
+ ShieldCheck,
14
+ RefreshCw
15
  } from 'lucide-react';
16
 
17
  type Status = 'DISCONNECTED' | 'INITIALIZING' | 'QR_CODE' | 'CONNECTED' | 'ERROR';
 
20
  const [isAuthenticated, setIsAuthenticated] = useState<boolean>(false);
21
  const [passkey, setPasskey] = useState('');
22
  const [loginError, setLoginError] = useState('');
23
+ const [isLoggingIn, setIsLoggingIn] = useState(false);
24
 
25
  const [status, setStatus] = useState<Status>('DISCONNECTED');
26
  const [qrCode, setQrCode] = useState('');
 
43
 
44
  const checkInitialAuth = async (key: string) => {
45
  try {
46
+ const response = await fetch('/api/status', {
47
+ headers: { 'x-passkey': key }
48
+ });
49
  if (response.ok) {
50
  setIsAuthenticated(true);
51
  } else {
52
  localStorage.removeItem('passkey');
53
  }
54
  } catch (e) {
55
+ console.error('Initial auth check failed:', e);
56
  }
57
  };
58
 
59
  const handleLogin = async (e: React.FormEvent) => {
60
  e.preventDefault();
61
+ if (!passkey) return;
62
+ setIsLoggingIn(true);
63
  setLoginError('');
64
  try {
65
  const response = await fetch('/api/login', {
 
75
  setLoginError('Invalid passkey. Please try again.');
76
  }
77
  } catch (err) {
78
+ setLoginError('Could not connect to server.');
79
+ } finally {
80
+ setIsLoggingIn(false);
81
  }
82
  };
83
 
 
91
  if (!isAuthenticated) return;
92
 
93
  const pollStatus = async () => {
94
+ const savedPasskey = localStorage.getItem('passkey') || '';
95
  try {
96
+ const response = await fetch('/api/status', {
97
+ headers: { 'x-passkey': savedPasskey }
98
+ });
99
  if (response.status === 401) {
100
  handleLogout();
101
  return;
 
121
  }, [isAuthenticated, groups.length]);
122
 
123
  const fetchGroups = async () => {
124
+ const savedPasskey = localStorage.getItem('passkey') || '';
125
  try {
126
+ const response = await fetch('/api/groups', {
127
+ headers: { 'x-passkey': savedPasskey }
128
+ });
129
  if (response.ok) {
130
  const data = await response.json();
131
  if (data.groups) setGroups(data.groups);
 
136
  };
137
 
138
  const startService = async () => {
139
+ const savedPasskey = localStorage.getItem('passkey') || '';
140
  try {
141
+ await fetch('/api/start', {
142
+ method: 'POST',
143
+ headers: { 'x-passkey': savedPasskey }
144
+ });
145
  } catch (err) {
146
+ console.error('Start service error:', err);
147
  }
148
  };
149
 
150
  const sendMessage = async (e: React.FormEvent) => {
151
  e.preventDefault();
152
  setIsSending(true);
153
+ const savedPasskey = localStorage.getItem('passkey') || '';
154
  const recipient = activeTab === 'direct' ? phone : selectedGroup;
155
 
156
  try {
157
+ const response = await fetch('/api/send', {
158
  method: 'POST',
159
+ headers: {
160
+ 'Content-Type': 'application/json',
161
+ 'x-passkey': savedPasskey
162
+ },
163
  body: JSON.stringify({
164
  phone: recipient,
165
  message,
 
168
  });
169
  if (response.ok) {
170
  setMessage('');
171
+ alert('Message sent successfully!');
172
  } else {
173
  const data = await response.json();
174
  alert(`Error: ${data.error}`);
 
186
 
187
  if (!isAuthenticated) {
188
  return (
189
+ <div className="min-h-screen bg-[#0f172a] flex items-center justify-center p-4 font-sans">
190
  <div className="max-w-md w-full bg-[#1e293b] rounded-3xl shadow-2xl border border-white/5 overflow-hidden">
191
  <div className="p-8 text-center">
192
  <div className="w-20 h-20 bg-orange-500/20 rounded-2xl flex items-center justify-center mx-auto mb-6 border border-orange-500/30 shadow-inner">
193
  <ShieldCheck className="w-10 h-10 text-orange-500" />
194
  </div>
195
+ <h1 className="text-3xl font-bold text-white mb-2">Restricted Access</h1>
196
  <p className="text-slate-400 mb-8">Enter your security passkey to manage the WPPConnect server.</p>
197
 
198
  <form onSubmit={handleLogin} className="space-y-4">
199
+ <input
200
+ type="password"
201
+ placeholder="Security Passkey..."
202
+ value={passkey}
203
+ onChange={(e) => setPasskey(e.target.value)}
204
+ className="w-full px-6 py-4 bg-slate-900/50 border border-slate-700 rounded-2xl focus:ring-4 focus:ring-orange-500/20 focus:border-orange-500 outline-none transition-all text-center text-xl font-mono tracking-widest text-white placeholder:tracking-normal placeholder:font-sans placeholder:text-slate-500"
205
+ autoFocus
206
+ />
 
 
207
 
208
  {loginError && (
209
  <div className="flex items-center justify-center gap-2 text-red-400 text-sm font-medium bg-red-500/10 py-3 rounded-xl border border-red-500/20">
 
214
 
215
  <button
216
  type="submit"
217
+ disabled={isLoggingIn}
218
+ className="w-full bg-orange-500 hover:bg-orange-600 disabled:bg-orange-500/50 text-white font-bold py-4 rounded-2xl transition-all shadow-lg shadow-orange-500/25 flex items-center justify-center gap-3 active:scale-[0.98]"
219
  >
220
+ {isLoggingIn ? <RefreshCw className="w-5 h-5 animate-spin" /> : <Unlock className="w-5 h-5" />}
221
  Unlock Dashboard
222
  </button>
223
  </form>
 
231
  }
232
 
233
  return (
234
+ <div className="min-h-screen bg-[#f8f9fa] text-gray-900 font-sans selection:bg-orange-100 selection:text-orange-900">
235
  <div className="max-w-6xl mx-auto px-4 py-8">
 
236
  <header className="flex flex-col md:flex-row md:items-center justify-between gap-4 mb-8 bg-white p-6 rounded-2xl shadow-sm border border-gray-100">
237
  <div className="flex items-center gap-4">
238
  <div className="w-12 h-12 bg-gradient-to-br from-orange-500 to-red-500 rounded-xl flex items-center justify-center shadow-lg shadow-orange-200">
 
267
  </header>
268
 
269
  <div className="grid grid-cols-1 lg:grid-cols-2 gap-8">
 
270
  <div className="space-y-6">
271
  <div className="bg-white rounded-2xl shadow-sm border border-gray-100 p-6">
272
  <div className="flex gap-1 p-1 bg-gray-50 rounded-xl mb-6">
 
274
  onClick={() => setActiveTab('direct')}
275
  className={`flex-1 flex items-center justify-center gap-2 py-2 text-sm font-semibold rounded-lg transition-all ${activeTab === 'direct' ? 'bg-white shadow-sm text-orange-600' : 'text-gray-500 hover:text-gray-700'}`}
276
  >
277
+ <MessageSquare className="w-4 h-4" /> Direct
278
  </button>
279
  <button
280
  onClick={() => setActiveTab('group')}
281
  className={`flex-1 flex items-center justify-center gap-2 py-2 text-sm font-semibold rounded-lg transition-all ${activeTab === 'group' ? 'bg-white shadow-sm text-orange-600' : 'text-gray-500 hover:text-gray-700'}`}
282
  >
283
+ <Users className="w-4 h-4" /> Groups
284
  </button>
285
  </div>
286
 
 
290
  <QrCode className="w-8 h-8" />
291
  </div>
292
  <div>
293
+ <h3 className="font-semibold text-gray-900">WhatsApp Offline</h3>
294
+ <p className="text-sm text-gray-500 max-w-[200px] mx-auto mt-1">Authenticate to enable messaging features.</p>
295
  </div>
296
  </div>
297
  ) : (
 
310
  ) : (
311
  <div className="space-y-2">
312
  <label className="block text-sm font-medium text-gray-700">Select Group</label>
313
+ <div className="flex gap-2">
314
+ <select
315
+ value={selectedGroup}
316
+ onChange={(e) => setSelectedGroup(e.target.value)}
317
+ className="flex-1 px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-orange-500 focus:border-orange-500 outline-none"
318
+ >
319
+ <option value="">-- Choose a group --</option>
320
+ {groups.map((g) => (
321
+ <option key={g.id._serialized} value={g.id._serialized}>{g.name || g.id.user}</option>
322
+ ))}
323
+ </select>
324
+ <button
325
+ type="button"
326
+ onClick={fetchGroups}
327
+ className="p-2 bg-gray-100 hover:bg-gray-200 rounded-lg transition-colors"
328
+ title="Refresh groups"
329
+ >
330
+ <RefreshCw className="w-4 h-4 text-gray-600" />
331
+ </button>
332
+ </div>
333
  </div>
334
  )}
335
 
 
357
  </div>
358
  </div>
359
 
 
360
  <div className="space-y-6 flex flex-col">
361
  <div className="bg-white rounded-xl shadow-sm border border-gray-200 p-6 flex flex-col items-center justify-center min-h-[300px]">
362
  <h2 className="text-lg font-semibold mb-4 border-b pb-2 w-full text-left text-gray-900">Authentication</h2>
363
  {status === 'QR_CODE' && qrCode ? (
364
  <div className="flex flex-col items-center animate-in fade-in zoom-in duration-300">
365
  <img src={qrCode} alt="WhatsApp QR Code" className="w-64 h-64 border-4 border-white shadow-sm rounded-lg" />
366
+ <p className="text-sm text-gray-500 mt-4 text-center">Scan this QR code with WhatsApp.</p>
367
  </div>
368
  ) : status === 'CONNECTED' ? (
369
  <div className="flex flex-col items-center text-green-500">