in cli/server.ts [940:1269]
export function serveAsync(options: ServeOptions) {
serveOptions = options;
if (!serveOptions.port) serveOptions.port = 3232;
if (!serveOptions.wsPort) serveOptions.wsPort = 3233;
if (!serveOptions.hostname) serveOptions.hostname = "localhost";
setupRootDir();
const wsServerPromise = initSocketServer(serveOptions.wsPort, serveOptions.hostname);
if (serveOptions.serial)
initSerialMonitor();
const server = http.createServer(async (req, res) => {
const error = (code: number, msg: string = null) => {
res.writeHead(code, { "Content-Type": "text/plain" })
res.end(msg || "Error " + code)
}
const sendJson = (v: any) => {
if (typeof v == "string") {
res.writeHead(200, { 'Content-Type': 'text/plain; charset=utf8' })
res.end(v)
} else {
res.writeHead(200, { 'Content-Type': 'application/json; charset=utf8' })
res.end(JSON.stringify(v))
}
}
const sendHtml = (s: string, code = 200) => {
res.writeHead(code, { 'Content-Type': 'text/html; charset=utf8' })
res.end(s.replace(
/(<img [^>]* src=")(?:\/docs|\.)\/static\/([^">]+)"/g,
function (f, pref, addr) { return pref + '/static/' + addr + '"'; }
))
}
const sendFile = (filename: string) => {
try {
let stat = fs.statSync(filename);
res.writeHead(200, {
'Content-Type': U.getMime(filename),
'Content-Length': stat.size
});
fs.createReadStream(filename).pipe(res);
} catch (e) {
error(404, "File missing: " + filename)
}
}
let pathname = decodeURI(url.parse(req.url).pathname);
const opts: pxt.Map<string | string[]> = querystring.parse(url.parse(req.url).query);
const htmlParams: pxt.Map<string> = {};
if (opts["lang"] || opts["forcelang"])
htmlParams["locale"] = (opts["lang"] as string || opts["forcelang"] as string);
if (pathname == "/") {
res.writeHead(301, { location: '/index.html' })
res.end()
return
}
if (pathname == "/oauth-redirect") {
res.writeHead(301, { location: '/oauth-redirect.html' })
res.end()
return
}
let elts = pathname.split("/").filter(s => !!s)
if (elts.some(s => s[0] == ".")) {
return error(400, "Bad path :-(\n")
}
if (elts[0] == "api") {
if (elts[1] == "streams") {
let trg = Cloud.apiRoot + req.url.slice(5)
res.setHeader("Location", trg)
error(302, "Redir: " + trg)
return
}
if (elts[1] == "immreader") {
let trg = Cloud.apiRoot + elts[1];
res.setHeader("Location", trg)
error(302, "Redir: " + trg)
return
}
if (elts[1] == "store") {
return await handleApiStoreRequestAsync(req, res, elts.slice(2));
}
if (/^\d\d\d[\d\-]*$/.test(elts[1]) && elts[2] == "js") {
return compileScriptAsync(elts[1])
.then(data => {
res.writeHead(200, { 'Content-Type': 'application/javascript' })
res.end(data)
}, err => {
error(500)
console.log(err.stack)
})
}
if (!isAuthorizedLocalRequest(req)) {
error(403);
return null;
}
return handleApiAsync(req, res, elts)
.then(sendJson, err => {
if (err.statusCode) {
error(err.statusCode, err.message || "");
console.log("Error " + err.statusCode);
}
else {
error(500)
console.log(err.stack)
}
})
}
if (elts[0] == "icon") {
const name = path.join(userProjectsDir, elts[1], "icon.jpeg");
return existsAsync(name)
.then(exists => exists ? sendFile(name) : error(404));
}
if (elts[0] == "assets") {
if (/^[a-z0-9\-_]/.test(elts[1]) && !/[\/\\]/.test(elts[1]) && !/^[.]/.test(elts[2])) {
let filename = path.join(userProjectsDir, elts[1], "assets", elts[2])
if (nodeutil.fileExistsSync(filename)) {
return sendFile(filename)
} else {
return error(404, "Asset not found")
}
} else {
return error(400, "Invalid asset path")
}
}
if (options.packaged) {
let filename = path.resolve(path.join(packagedDir, pathname))
if (nodeutil.fileExistsSync(filename)) {
return sendFile(filename)
} else {
return error(404, "Packaged file not found")
}
}
if (pathname.slice(0, pxt.appTarget.id.length + 2) == "/" + pxt.appTarget.id + "/") {
res.writeHead(301, { location: req.url.slice(pxt.appTarget.id.length + 1) })
res.end()
return
}
let publicDir = path.join(nodeutil.pxtCoreDir, "webapp/public")
if (pathname == "/--embed") {
sendFile(path.join(publicDir, 'embed.js'));
return
}
if (pathname == "/--run") {
sendFile(path.join(publicDir, 'run.html'));
return
}
if (pathname == "/--multi") {
sendFile(path.join(publicDir, 'multi.html'));
return
}
if (pathname == "/--asseteditor") {
sendFile(path.join(publicDir, 'asseteditor.html'));
return
}
if (pathname == "/--skillmap") {
sendFile(path.join(publicDir, 'skillmap.html'));
return
}
if (/\/-[-]*docs.*$/.test(pathname)) {
sendFile(path.join(publicDir, 'docs.html'));
return
}
if (pathname == "/--codeembed") {
// http://localhost:3232/--codeembed#pub:20467-26471-70207-51013
sendFile(path.join(publicDir, 'codeembed.html'));
return
}
if (/^\/(\d\d\d\d[\d-]+)$/.test(pathname)) {
scriptPageTestAsync(pathname.slice(1))
.then(sendHtml)
return
}
if (/^\/(pkg|package)\/.*$/.test(pathname)) {
pkgPageTestAsync(pathname.replace(/^\/[^\/]+\//, ""))
.then(sendHtml)
.catch(() => error(404, "Packaged file not found"));
return
}
if (elts[0] == "streams") {
streamPageTestAsync(elts[0] + "/" + elts[1])
.then(sendHtml)
return
}
if (elts[0] == "certificates") {
certificateTestAsync().then(sendHtml);
return;
}
if (/\.js\.map$/.test(pathname)) {
error(404, "map files disabled")
return;
}
let dd = dirs
let mm = /^\/(cdn|parts|sim|doccdn|blb)(\/.*)/.exec(pathname)
if (mm) {
pathname = mm[2]
} else if (U.startsWith(pathname, "/docfiles/")) {
pathname = pathname.slice(10)
dd = docfilesdirs
}
for (let dir of dd) {
let filename = path.resolve(path.join(dir, pathname))
if (nodeutil.fileExistsSync(filename)) {
if (/\.html$/.test(filename)) {
let html = expandHtml(fs.readFileSync(filename, "utf8"), htmlParams)
sendHtml(html)
} else {
sendFile(filename)
}
return;
}
}
if (/simulator\.html/.test(pathname)) {
// Special handling for missing simulator: redirect to the live sim
res.writeHead(302, { location: `https://trg-${pxt.appTarget.id}.userpxt.io/---simulator` });
res.end();
return;
}
// redirect
let redirectFile = path.join(docsDir, pathname + "-ref.json");
if (nodeutil.fileExistsSync(redirectFile)) {
const redir = nodeutil.readJson(redirectFile);
res.writeHead(301, { location: redir["redirect"] })
res.end()
return;
}
let webFile = path.join(docsDir, pathname)
if (!nodeutil.fileExistsSync(webFile)) {
if (nodeutil.fileExistsSync(webFile + ".html")) {
webFile += ".html"
pathname += ".html"
} else {
webFile = ""
}
}
if (webFile) {
if (/\.html$/.test(webFile)) {
let html = expandHtml(fs.readFileSync(webFile, "utf8"), htmlParams)
sendHtml(html)
} else {
sendFile(webFile)
}
} else {
const m = /^\/(v\d+)(.*)/.exec(pathname);
if (m) pathname = m[2];
const lang = (opts["translate"] && ts.pxtc.Util.TRANSLATION_LOCALE)
|| opts["lang"] as string
|| opts["forcelang"] as string;
readMdAsync(pathname, lang)
.then(md => {
const mdopts = <pxt.docs.RenderOptions>{
template: expandDocFileTemplate("docs.html"),
markdown: md,
theme: pxt.appTarget.appTheme,
filepath: pathname,
TOC: resolveTOC(pathname),
pubinfo: {
locale: lang,
crowdinproject: pxt.appTarget.appTheme.crowdinProject
}
};
if (opts["translate"])
mdopts.pubinfo["incontexttranslations"] = "1";
const html = pxt.docs.renderMarkdown(mdopts)
sendHtml(html, U.startsWith(md, "# Not found") ? 404 : 200)
});
}
return
});
// if user has a server.js file, require it
const serverjs = path.resolve(path.join(root, 'built', 'server.js'))
if (nodeutil.fileExistsSync(serverjs)) {
console.log('loading ' + serverjs)
require(serverjs);
}
const serverPromise = new Promise<void>((resolve, reject) => {
server.on("error", reject);
server.listen(serveOptions.port, serveOptions.hostname, () => resolve());
});
return Promise.all([wsServerPromise, serverPromise])
.then(() => {
const start = `http://${serveOptions.hostname}:${serveOptions.port}/#local_token=${options.localToken}&wsport=${serveOptions.wsPort}`;
console.log(`---------------------------------------------`);
console.log(``);
console.log(`To launch the editor, open this URL:`);
console.log(start);
console.log(``);
console.log(`---------------------------------------------`);
if (options.autoStart) {
nodeutil.openUrl(start, options.browser);
}
});
}