mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2026-09-29 02:28:51 +00:00
* bots: SimpleX Calculator demo bot * refactor * use local server in test * simplify * icon * simplify * expression * replies * improve * log * update name in bot address * @simplex-chat/type: 0.12.0 * simplex-chat nodejs: 7.1.0-beta.4.2 * update dependencies * option to add domain to bot address --------- Co-authored-by: Evgeny @ SimpleX Chat <259188159+evgeny-simplex@users.noreply.github.com>
102 lines
3.9 KiB
TypeScript
102 lines
3.9 KiB
TypeScript
import {ChildProcess, spawn} from "child_process"
|
|
import {createHash} from "crypto"
|
|
import {once} from "events"
|
|
import {copyFileSync, existsSync, mkdirSync, mkdtempSync, readFileSync, renameSync, rmSync, writeFileSync} from "fs"
|
|
import {AddressInfo, createConnection, createServer} from "net"
|
|
import {tmpdir} from "os"
|
|
import {dirname, join} from "path"
|
|
|
|
const tlsFixtures = join(__dirname, "../../../tests/fixtures/tls")
|
|
const smpServerRelease = "v7.1.0-beta.3"
|
|
const releaseArch: Record<string, string | undefined> = {x64: "x86-64", arm64: "aarch64"}
|
|
|
|
export interface SmpServer {
|
|
address: string
|
|
stop: () => Promise<void>
|
|
}
|
|
|
|
export async function startSmpServer(): Promise<SmpServer> {
|
|
const port = await freePort()
|
|
const dir = mkdtempSync(join(tmpdir(), "smp-server-"))
|
|
const configDir = join(dir, "config")
|
|
mkdirSync(configDir)
|
|
for (const file of ["ca.crt", "server.crt", "server.key"]) copyFileSync(join(tlsFixtures, file), join(configDir, file))
|
|
const fingerprint = caFingerprint(join(configDir, "ca.crt"))
|
|
writeFileSync(join(configDir, "fingerprint"), `${fingerprint}\n`)
|
|
writeFileSync(join(configDir, "smp-server.ini"), `[STORE_LOG]\nenable: off\n\n[TRANSPORT]\nhost: localhost\nport: ${port}\nwebsockets: off\n`)
|
|
const server = spawn(await smpServerExecutable(), ["start"], {
|
|
env: {...process.env, SMP_SERVER_CFG_PATH: configDir, SMP_SERVER_LOG_PATH: join(dir, "logs")},
|
|
stdio: ["ignore", "ignore", "inherit"],
|
|
})
|
|
const stop = async () => {
|
|
if (server.exitCode === null) {
|
|
const exited = once(server, "exit")
|
|
server.kill()
|
|
await exited
|
|
}
|
|
rmSync(dir, {recursive: true, force: true})
|
|
}
|
|
try {
|
|
await once(server, "spawn")
|
|
await waitForServer(server, port)
|
|
} catch (e) {
|
|
await stop()
|
|
throw e
|
|
}
|
|
return {address: `smp://${fingerprint}@localhost:${port}`, stop}
|
|
}
|
|
|
|
async function smpServerExecutable(): Promise<string> {
|
|
if (process.env.SMP_SERVER) return process.env.SMP_SERVER
|
|
const path = join(__dirname, "../node_modules/.cache/smp-server", `smp-server-${smpServerRelease}`)
|
|
if (!existsSync(path)) await downloadSmpServer(path)
|
|
return path
|
|
}
|
|
|
|
async function downloadSmpServer(path: string): Promise<void> {
|
|
const arch = releaseArch[process.arch]
|
|
if (process.platform !== "linux" || !arch) {
|
|
throw new Error(`smp-server release binaries are not available for ${process.platform} ${process.arch}, set SMP_SERVER to the smp-server path`)
|
|
}
|
|
const url = `https://github.com/simplex-chat/simplexmq/releases/download/${smpServerRelease}/smp-server-ubuntu-22_04-${arch}`
|
|
const response = await fetch(url)
|
|
if (!response.ok) throw new Error(`error downloading ${url}: ${response.status}`)
|
|
mkdirSync(dirname(path), {recursive: true})
|
|
writeFileSync(`${path}.download`, Buffer.from(await response.arrayBuffer()), {mode: 0o755})
|
|
renameSync(`${path}.download`, path)
|
|
}
|
|
|
|
function caFingerprint(caFile: string): string {
|
|
const der = Buffer.from(readFileSync(caFile, "utf-8").replace(/-----[^-]+-----|\s/g, ""), "base64")
|
|
return createHash("sha256").update(der).digest("base64").replace(/\+/g, "-").replace(/\//g, "_")
|
|
}
|
|
|
|
function freePort(): Promise<number> {
|
|
return new Promise((resolve, reject) => {
|
|
const probe = createServer()
|
|
probe.on("error", reject)
|
|
probe.listen(0, "127.0.0.1", () => {
|
|
const {port} = probe.address() as AddressInfo
|
|
probe.close(() => resolve(port))
|
|
})
|
|
})
|
|
}
|
|
|
|
async function waitForServer(server: ChildProcess, port: number): Promise<void> {
|
|
const deadline = Date.now() + 15_000
|
|
while (!(await canConnect(port))) {
|
|
if (server.exitCode !== null || Date.now() > deadline) throw new Error(`smp-server did not start on port ${port}`)
|
|
await new Promise(resolve => setTimeout(resolve, 100))
|
|
}
|
|
}
|
|
|
|
function canConnect(port: number): Promise<boolean> {
|
|
return new Promise(resolve => {
|
|
const socket = createConnection({host: "localhost", port}, () => {
|
|
socket.destroy()
|
|
resolve(true)
|
|
})
|
|
socket.on("error", () => resolve(false))
|
|
})
|
|
}
|