| 148 | let _cachedCert = null; |
| 149 | |
| 150 | function getDevCert() { |
| 151 | if (_cachedCert) return _cachedCert; |
| 152 | |
| 153 | const certPath = path.join(ROOT, ".dev-cert.pem"); |
| 154 | const keyPath = path.join(ROOT, ".dev-key.pem"); |
| 155 | |
| 156 | // Reuse existing cert if available |
| 157 | if (fs.existsSync(certPath) && fs.existsSync(keyPath)) { |
| 158 | _cachedCert = { |
| 159 | cert: fs.readFileSync(certPath), |
| 160 | key: fs.readFileSync(keyPath), |
| 161 | }; |
| 162 | return _cachedCert; |
| 163 | } |
| 164 | |
| 165 | // Generate via openssl (available on macOS, Linux, and Git Bash on Windows) |
| 166 | try { |
| 167 | execSync( |
| 168 | `openssl req -x509 -newkey rsa:2048 -keyout "${keyPath}" -out "${certPath}" -days 365 -nodes -subj "/CN=acode-dev"`, |
| 169 | { stdio: "pipe" }, |
| 170 | ); |
| 171 | _cachedCert = { |
| 172 | cert: fs.readFileSync(certPath), |
| 173 | key: fs.readFileSync(keyPath), |
| 174 | }; |
| 175 | log("ok", "Generated self-signed dev certificate"); |
| 176 | return _cachedCert; |
| 177 | } catch (_e) { |
| 178 | // openssl not available |
| 179 | } |
| 180 | |
| 181 | log("warn", "openssl not found — falling back to HTTP"); |
| 182 | return null; |
| 183 | } |
| 184 | |
| 185 | // ─── HTTPS + WebSocket server ───────────────────────────────────────────────── |
| 186 | |