-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #4 from ultracodez/auth
bugfixes as preparation for `Auth`
- Loading branch information
Showing
9 changed files
with
322 additions
and
54 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,23 +1,27 @@ | ||
var finalhandler = require("finalhandler"); | ||
var http = require("http"); | ||
var serveStatic = require("serve-static"); | ||
const { fork } = require("child_process"); | ||
const path = require("path"); | ||
let { getPorts } = require("./helpers/get-port"); | ||
|
||
function setupFrontendServer(servePath) { | ||
// Serve up public/ftp folder | ||
var serve = serveStatic(servePath, { | ||
index: ["index.html", "index.htm"], | ||
extensions: ["html", "htm"], | ||
async function setupFrontendServer(servePath) { | ||
const port = await getPorts(); | ||
const process = fork(servePath, { | ||
env: { | ||
PORT: port, | ||
}, | ||
stdio: "pipe", | ||
}); | ||
|
||
// Create server | ||
var server = http.createServer(function onRequest(req, res) { | ||
serve(req, res, finalhandler(req, res)); | ||
}); | ||
console.log("Waiting for UI thread to start..."); | ||
|
||
// Listen | ||
server.listen(0); | ||
await new Promise((resolve, reject) => { | ||
process.stdout.on("data", (chunk) => { | ||
line = chunk.toString(); | ||
console.log("Recieved data from child process:", line); | ||
if (line.includes("Listening on port")) resolve(); | ||
}); | ||
}); | ||
|
||
return server; | ||
return port; | ||
} | ||
|
||
module.exports = { setupFrontendServer }; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,192 @@ | ||
const net = require("node:net"); | ||
const os = require("node:os"); | ||
|
||
class Locked extends Error { | ||
constructor(port) { | ||
super(`${port} is locked`); | ||
} | ||
} | ||
|
||
const lockedPorts = { | ||
old: new Set(), | ||
young: new Set(), | ||
}; | ||
|
||
// On this interval, the old locked ports are discarded, | ||
// the young locked ports are moved to old locked ports, | ||
// and a new young set for locked ports are created. | ||
const releaseOldLockedPortsIntervalMs = 1000 * 15; | ||
|
||
const minPort = 1024; | ||
const maxPort = 65_535; | ||
|
||
// Lazily create timeout on first use | ||
let timeout; | ||
|
||
const getLocalHosts = () => { | ||
const interfaces = os.networkInterfaces(); | ||
|
||
// Add undefined value for createServer function to use default host, | ||
// and default IPv4 host in case createServer defaults to IPv6. | ||
const results = new Set([undefined, "0.0.0.0"]); | ||
|
||
for (const _interface of Object.values(interfaces)) { | ||
for (const config of _interface) { | ||
results.add(config.address); | ||
} | ||
} | ||
|
||
return results; | ||
}; | ||
|
||
const checkAvailablePort = (options) => | ||
new Promise((resolve, reject) => { | ||
const server = net.createServer(); | ||
server.unref(); | ||
server.on("error", reject); | ||
|
||
server.listen(options, () => { | ||
const { port } = server.address(); | ||
server.close(() => { | ||
resolve(port); | ||
}); | ||
}); | ||
}); | ||
|
||
const getAvailablePort = async (options, hosts) => { | ||
if (options.host || options.port === 0) { | ||
return checkAvailablePort(options); | ||
} | ||
|
||
for (const host of hosts) { | ||
try { | ||
await checkAvailablePort({ port: options.port, host }); // eslint-disable-line no-await-in-loop | ||
} catch (error) { | ||
if (!["EADDRNOTAVAIL", "EINVAL"].includes(error.code)) { | ||
throw error; | ||
} | ||
} | ||
} | ||
|
||
return options.port; | ||
}; | ||
|
||
const portCheckSequence = function* (ports) { | ||
if (ports) { | ||
yield* ports; | ||
} | ||
|
||
yield 0; // Fall back to 0 if anything else failed | ||
}; | ||
|
||
async function getPorts(options) { | ||
let ports; | ||
let exclude = new Set(); | ||
|
||
if (options) { | ||
if (options.port) { | ||
ports = typeof options.port === "number" ? [options.port] : options.port; | ||
} | ||
|
||
if (options.exclude) { | ||
const excludeIterable = options.exclude; | ||
|
||
if (typeof excludeIterable[Symbol.iterator] !== "function") { | ||
throw new TypeError("The `exclude` option must be an iterable."); | ||
} | ||
|
||
for (const element of excludeIterable) { | ||
if (typeof element !== "number") { | ||
throw new TypeError( | ||
"Each item in the `exclude` option must be a number corresponding to the port you want excluded." | ||
); | ||
} | ||
|
||
if (!Number.isSafeInteger(element)) { | ||
throw new TypeError( | ||
`Number ${element} in the exclude option is not a safe integer and can't be used` | ||
); | ||
} | ||
} | ||
|
||
exclude = new Set(excludeIterable); | ||
} | ||
} | ||
|
||
if (timeout === undefined) { | ||
timeout = setTimeout(() => { | ||
timeout = undefined; | ||
|
||
lockedPorts.old = lockedPorts.young; | ||
lockedPorts.young = new Set(); | ||
}, releaseOldLockedPortsIntervalMs); | ||
|
||
// Does not exist in some environments (Electron, Jest jsdom env, browser, etc). | ||
if (timeout.unref) { | ||
timeout.unref(); | ||
} | ||
} | ||
|
||
const hosts = getLocalHosts(); | ||
|
||
for (const port of portCheckSequence(ports)) { | ||
try { | ||
if (exclude.has(port)) { | ||
continue; | ||
} | ||
|
||
let availablePort = await getAvailablePort({ ...options, port }, hosts); // eslint-disable-line no-await-in-loop | ||
while ( | ||
lockedPorts.old.has(availablePort) || | ||
lockedPorts.young.has(availablePort) | ||
) { | ||
if (port !== 0) { | ||
throw new Locked(port); | ||
} | ||
|
||
availablePort = await getAvailablePort({ ...options, port }, hosts); // eslint-disable-line no-await-in-loop | ||
} | ||
|
||
lockedPorts.young.add(availablePort); | ||
|
||
return availablePort; | ||
} catch (error) { | ||
if ( | ||
!["EADDRINUSE", "EACCES"].includes(error.code) && | ||
!(error instanceof Locked) | ||
) { | ||
throw error; | ||
} | ||
} | ||
} | ||
|
||
throw new Error("No available ports found"); | ||
} | ||
|
||
function portNumbers(from, to) { | ||
if (!Number.isInteger(from) || !Number.isInteger(to)) { | ||
throw new TypeError("`from` and `to` must be integer numbers"); | ||
} | ||
|
||
if (from < minPort || from > maxPort) { | ||
throw new RangeError(`'from' must be between ${minPort} and ${maxPort}`); | ||
} | ||
|
||
if (to < minPort || to > maxPort) { | ||
throw new RangeError(`'to' must be between ${minPort} and ${maxPort}`); | ||
} | ||
|
||
if (from > to) { | ||
throw new RangeError("`to` must be greater than or equal to `from`"); | ||
} | ||
|
||
const generator = function* (from, to) { | ||
for (let port = from; port <= to; port++) { | ||
yield port; | ||
} | ||
}; | ||
|
||
return generator(from, to); | ||
} | ||
|
||
module.exports = { getPorts, portNumbers }; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.