Files
meteor/packages/dynamic-import/server.js

177 lines
4.6 KiB
JavaScript

"use strict";
const assert = require("assert");
const { readFileSync } = require("fs");
const {
join: pathJoin,
normalize: pathNormalize,
} = require("path");
const { fetchURL } = require("./common.js");
const hasOwn = Object.prototype.hasOwnProperty;
require("./security.js");
const client = require("./client.js");
Meteor.startup(() => {
if (! Package.webapp) {
// If the webapp package is not in use, there's no way for the
// dynamic-import package to fetch dynamic modules, so we should
// abandon the rest of the logic in this module.
//
// If api.use("webapp") appeared in dynamic-import/package.js, then
// Package.webapp would always be defined here, of course, but that
// would be a bad idea, because the dynamic-import package should not
// single-handedly force a dependency on webapp if the program does
// not otherwise need a web server (e.g., when the program is an
// isopacket or build plugin instead of a web application).
//
// Note that the client.js module (imported above) still defines
// Module.prototype.dynamicImport, which will work as long as no
// modules need to be fetched.
return;
}
Object.keys(dynamicImportInfo).forEach(setUpPlatform);
Package.webapp.WebAppInternals.meteorInternalHandlers.use(
fetchURL,
middleware
);
});
function setUpPlatform(platform) {
const info = dynamicImportInfo[platform];
if (info.dynamicRoot) {
info.dynamicRoot = pathNormalize(info.dynamicRoot);
}
if (platform === "server") {
client.setSecretKey(info.key = randomId(40));
}
}
function randomId(n) {
let s = "";
while (s.length < n) {
s += Math.random().toString(36).slice(2);
}
return s.slice(0, n);
}
function middleware(request, response) {
assert.strictEqual(request.method, "POST");
const chunks = [];
request.on("data", chunk => chunks.push(chunk));
request.on("end", () => {
response.setHeader("Content-Type", "application/json");
response.end(JSON.stringify(readTree(
JSON.parse(Buffer.concat(chunks)),
getPlatform(request)
)));
});
}
function getPlatform(request) {
let platform = "web.browser";
// If the __dynamicImport request includes a secret key, and it matches
// dynamicImportInfo[platform].key, use platform instead of the default
// platform, web.browser.
const secretKey = request.query.key;
if (typeof secretKey === "string") {
Object.keys(dynamicImportInfo).some(p => {
if (secretKey === dynamicImportInfo[p].key) {
platform = p;
return true;
}
});
}
return platform;
}
function readTree(tree, platform) {
const pathParts = [];
function walk(node) {
if (! node) {
return null;
}
if (typeof node !== "object") {
return read(pathParts, platform);
}
let empty = true;
Object.keys(node).forEach(name => {
pathParts.push(name);
const result = walk(node[name]);
if (result === null) {
// If the read function returns null, omit this module from the
// resulting tree.
delete node[name];
} else {
node[name] = result;
empty = false;
}
assert.strictEqual(pathParts.pop(), name);
});
if (empty) {
// If every recursive call to walk(node[name]) returned null,
// remove this node from the resulting tree by returning null.
return null;
}
return node;
}
return walk(tree);
}
function read(pathParts, platform) {
const { dynamicRoot } = dynamicImportInfo[platform];
const absPath = pathNormalize(pathJoin(
dynamicRoot,
pathJoin(...pathParts).replace(/:/g, "_")
));
if (! absPath.startsWith(dynamicRoot)) {
console.error("bad dynamic import path:", absPath);
return null;
}
const cache = getCache(platform);
if (hasOwn.call(cache, absPath)) {
return cache[absPath];
}
try {
return cache[absPath] = readFileSync(absPath, "utf8");
} catch (e) {
console.error(e.stack || e);
return null;
}
}
const cachesByPlatform = Object.create(null);
function getCache(platform) {
return hasOwn.call(cachesByPlatform, platform)
? cachesByPlatform[platform]
: cachesByPlatform[platform] = Object.create(null);
}
process.on("message", msg => {
// The cache for the "web.browser" platform needs to be discarded
// whenever a client-only refresh occurs, so that new client code does
// not receive stale module data from __dynamicImport. This code handles
// the same message listened for by the autoupdate package.
if (msg && msg.refresh === "client") {
delete cachesByPlatform["web.browser"];
}
});