You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
437 lines
15 KiB
437 lines
15 KiB
"use strict"; |
|
var fs = require("fs"); |
|
var path = require("path"); |
|
var join = require("path").join; |
|
var connect = require("connect"); |
|
var Immutable = require("immutable"); |
|
var http = require("http"); |
|
var https = require("https"); |
|
var Map = require("immutable").Map; |
|
var fromJS = require("immutable").fromJS; |
|
var List = require("immutable").List; |
|
var snippet = require("./../snippet").utils; |
|
var _ = require("../lodash.custom"); |
|
var serveStatic = require("serve-static"); |
|
var serveIndex = require("serve-index"); |
|
var logger = require("../logger"); |
|
var snippetUtils = require("../snippet").utils; |
|
var lrSnippet = require("resp-modifier"); |
|
var certPath = join(__dirname, "..", "..", "certs"); |
|
function getCa(options) { |
|
var caOption = options.getIn(["https", "ca"]); |
|
// if not provided, use Browsersync self-signed |
|
if (typeof caOption === "undefined") { |
|
return fs.readFileSync(join(certPath, "server.csr")); |
|
} |
|
// if a string was given, read that file from disk |
|
if (typeof caOption === "string") { |
|
return fs.readFileSync(caOption); |
|
} |
|
// if an array was given, read all |
|
if (List.isList(caOption)) { |
|
return caOption.toArray().map(function (x) { |
|
return fs.readFileSync(x); |
|
}); |
|
} |
|
} |
|
function getKey(options) { |
|
return fs.readFileSync(options.getIn(["https", "key"]) || join(certPath, "server.key")); |
|
} |
|
function getCert(options) { |
|
return fs.readFileSync(options.getIn(["https", "cert"]) || join(certPath, "server.crt")); |
|
} |
|
function getHttpsServerDefaults(options) { |
|
return fromJS({ |
|
key: getKey(options), |
|
cert: getCert(options), |
|
ca: getCa(options), |
|
passphrase: "" |
|
}); |
|
} |
|
function getPFXDefaults(options) { |
|
return fromJS({ |
|
pfx: fs.readFileSync(options.getIn(["https", "pfx"])) |
|
}); |
|
} |
|
var serverUtils = { |
|
/** |
|
* @param options |
|
* @returns {{key, cert}} |
|
*/ |
|
getHttpsOptions: function (options) { |
|
var userOption = options.get("https"); |
|
if (Map.isMap(userOption)) { |
|
if (userOption.has("pfx")) { |
|
return userOption.mergeDeep(getPFXDefaults(options)); |
|
} |
|
return userOption.mergeDeep(getHttpsServerDefaults(options)); |
|
} |
|
return getHttpsServerDefaults(options); |
|
}, |
|
/** |
|
* Get either http or https server |
|
* or use the httpModule provided in options if present |
|
*/ |
|
getServer: function (app, options) { |
|
return { |
|
server: (function () { |
|
var httpModule = serverUtils.getHttpModule(options); |
|
if (options.get("scheme") === "https" || |
|
options.get("httpModule") === "http2") { |
|
var opts = serverUtils.getHttpsOptions(options); |
|
return httpModule.createServer(opts.toJS(), app); |
|
} |
|
return httpModule.createServer(app); |
|
})(), |
|
app: app |
|
}; |
|
}, |
|
getHttpModule: function (options) { |
|
/** |
|
* Users may provide a string to be used by nodes |
|
* require lookup. |
|
*/ |
|
var httpModule = options.get("httpModule"); |
|
if (typeof httpModule === "string") { |
|
/** |
|
* Note, this could throw, but let that happen as |
|
* the error message good enough. |
|
*/ |
|
var maybe = path.resolve(options.get("cwd"), "node_modules", httpModule); |
|
return require(maybe); |
|
} |
|
if (options.get("scheme") === "https") { |
|
return https; |
|
} |
|
return http; |
|
}, |
|
getMiddlewares: function (bs) { |
|
var clientJs = bs.pluginManager.hook("client:js", { |
|
port: bs.options.get("port"), |
|
options: bs.options |
|
}); |
|
var scripts = bs.pluginManager.get("client:script")(bs.options.toJS(), clientJs, "middleware"); |
|
var defaultMiddlewares = [ |
|
{ |
|
id: "Browsersync HTTP Protocol", |
|
route: require("../config").httpProtocol.path, |
|
handle: require("../http-protocol").middleware(bs) |
|
}, |
|
{ |
|
id: "Browsersync IE8 Support", |
|
route: "", |
|
handle: snippet.isOldIe(bs.options.get("excludedFileTypes").toJS()) |
|
}, |
|
{ |
|
id: "Browsersync Response Modifier", |
|
route: "", |
|
handle: serverUtils.getSnippetMiddleware(bs) |
|
}, |
|
{ |
|
id: "Browsersync Client - versioned", |
|
route: bs.options.getIn(["scriptPaths", "versioned"]), |
|
handle: scripts |
|
}, |
|
{ |
|
id: "Browsersync Client", |
|
route: bs.options.getIn(["scriptPaths", "path"]), |
|
handle: scripts |
|
} |
|
]; |
|
/** |
|
* Add cors middleware to the front of the stack |
|
* if a user provided a 'cors' flag |
|
*/ |
|
if (bs.options.get("cors")) { |
|
defaultMiddlewares.unshift({ |
|
id: "Browsersync CORS support", |
|
route: "", |
|
handle: serverUtils.getCorsMiddlewware() |
|
}); |
|
} |
|
/** |
|
* Add connect-history-api-fallback if 'single' argument given |
|
*/ |
|
if (bs.options.get("single")) { |
|
defaultMiddlewares.unshift({ |
|
id: "Browsersync SPA support", |
|
route: "", |
|
handle: require("connect-history-api-fallback")() |
|
}); |
|
} |
|
/** |
|
* Add serve static middleware |
|
*/ |
|
if (bs.options.get("serveStatic")) { |
|
var ssMiddlewares = serverUtils.getServeStaticMiddlewares(bs.options.get("serveStatic"), bs.options.get("serveStaticOptions", Immutable.Map({})).toJS()); |
|
var withErrors = ssMiddlewares.filter(function (x) { |
|
return x.get("errors").size > 0; |
|
}); |
|
var withoutErrors = ssMiddlewares.filter(function (x) { |
|
return x.get("errors").size === 0; |
|
}); |
|
if (withErrors.size) { |
|
withErrors.forEach(function (item) { |
|
logger.logger.error("{red:Warning!} %s", item.getIn(["errors", 0, "data", "message"])); |
|
}); |
|
} |
|
if (withoutErrors.size) { |
|
withoutErrors.forEach(function (item) { |
|
defaultMiddlewares.push.apply(defaultMiddlewares, item.get("items").toJS()); |
|
}); |
|
} |
|
} |
|
/** |
|
* Add user-provided middlewares |
|
*/ |
|
var userMiddlewares = bs.options |
|
.get("middleware") |
|
.map(normaliseMiddleware) |
|
.toArray(); |
|
var beforeMiddlewares = userMiddlewares.filter(function (x) { |
|
return x.override; |
|
}); |
|
var afterMiddlewares = userMiddlewares |
|
.filter(function (x) { |
|
return !x.override; |
|
}) |
|
.concat(bs.options.get("mode") !== "proxy" && |
|
userMiddlewares.length === 0 && { |
|
id: "Browsersync 404/index support", |
|
route: "", |
|
handle: serveIndex(bs.options.get("cwd"), { |
|
icons: true, |
|
view: "details" |
|
}) |
|
}); |
|
var mwStack = [] |
|
.concat(beforeMiddlewares, defaultMiddlewares, afterMiddlewares) |
|
.filter(Boolean); |
|
return mwStack; |
|
function normaliseMiddleware(item) { |
|
/** |
|
* Object given in options, which |
|
* ended up being a Map |
|
*/ |
|
if (Map.isMap(item)) { |
|
return item.toJS(); |
|
} |
|
/** |
|
* Single function |
|
*/ |
|
if (typeof item === "function") { |
|
return { |
|
route: "", |
|
handle: item |
|
}; |
|
} |
|
/** |
|
* Plain obj |
|
*/ |
|
if (item.route !== undefined && item.handle) { |
|
return item; |
|
} |
|
} |
|
}, |
|
getBaseApp: function (bs) { |
|
var app = connect(); |
|
var middlewares = serverUtils.getMiddlewares(bs); |
|
/** |
|
* Add all internal middlewares |
|
*/ |
|
middlewares.forEach(function (item) { |
|
app.stack.push(item); |
|
}); |
|
return app; |
|
}, |
|
getSnippetMiddleware: function (bs) { |
|
var rules = []; |
|
var blacklist = List([]) |
|
.concat(bs.options.getIn(["snippetOptions", "ignorePaths"])) |
|
.concat(bs.options.getIn(["snippetOptions", "blacklist"])) |
|
.filter(Boolean); |
|
var whitelist = List([]).concat(bs.options.getIn(["snippetOptions", "whitelist"])); |
|
// Snippet |
|
rules.push(snippetUtils.getRegex(bs.options.get("snippet"), bs.options.get("snippetOptions"))); |
|
// User |
|
bs.options.get("rewriteRules").forEach(function (rule) { |
|
if (Map.isMap(rule)) { |
|
rules.push(rule.toJS()); |
|
} |
|
if (_.isPlainObject(rule)) { |
|
rules.push(rule); |
|
} |
|
}); |
|
// Proxy |
|
if (bs.options.get("proxy")) { |
|
var proxyRule = require("./proxy-utils").rewriteLinks(bs.options.getIn(["proxy", "url"]).toJS()); |
|
rules.push(proxyRule); |
|
} |
|
var lr = lrSnippet.create({ |
|
rules: rules, |
|
blacklist: blacklist.toArray(), |
|
whitelist: whitelist.toArray() |
|
}); |
|
return lr.middleware; |
|
}, |
|
getCorsMiddlewware: function () { |
|
return function (req, res, next) { |
|
// Website you wish to allow to connect |
|
res.setHeader("Access-Control-Allow-Origin", "*"); |
|
// Request methods you wish to allow |
|
res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS, PUT, PATCH, DELETE"); |
|
// Request headers you wish to allow |
|
res.setHeader("Access-Control-Allow-Headers", "X-Requested-With,content-type"); |
|
// Set to true if you need the website to include cookies in the requests sent |
|
// to the API (e.g. in case you use sessions) |
|
res.setHeader("Access-Control-Allow-Credentials", true); |
|
next(); |
|
}; |
|
}, |
|
/** |
|
* @param ssOption |
|
* @param serveStaticOptions |
|
* @returns {*} |
|
*/ |
|
getServeStaticMiddlewares: function (ssOption, serveStaticOptions) { |
|
return ssOption.map(function (dir, i) { |
|
/** |
|
* When a user gives a plain string only, eg: |
|
* serveStatic: ['./temp'] |
|
* -> |
|
* This means a middleware will be created with |
|
* route: '' |
|
* handle: serveStatic('./temp', options) |
|
*/ |
|
if (_.isString(dir)) { |
|
return getFromString(dir); |
|
} |
|
/** |
|
* If a user gave an object eg: |
|
* serveStatic: [{route: "", dir: ["test", "./tmp"]}] |
|
* -> |
|
* This means we need to create a middle for each route + dir combo |
|
*/ |
|
if (Immutable.Map.isMap(dir)) { |
|
return getFromMap(dir, i); |
|
} |
|
/** |
|
* At this point, an item in the serveStatic array was not a string |
|
* or an object so we return an error that can be logged |
|
*/ |
|
return fromJS({ |
|
items: [], |
|
errors: [ |
|
{ |
|
type: "Invalid Type", |
|
data: { |
|
message: "Only strings and Objects (with route+dir) are supported for the ServeStatic option" |
|
} |
|
} |
|
] |
|
}); |
|
}); |
|
/** |
|
* @param {string} x |
|
* @returns {string} |
|
*/ |
|
function getRoute(x) { |
|
if (x === "") |
|
return ""; |
|
return x[0] === "/" ? x : "/" + x; |
|
} |
|
/** |
|
* @param dir |
|
* @returns {Map} |
|
*/ |
|
function getFromString(dir) { |
|
return fromJS({ |
|
items: [ |
|
{ |
|
route: "", |
|
handle: serveStatic(dir, serveStaticOptions) |
|
} |
|
], |
|
errors: [] |
|
}); |
|
} |
|
/** |
|
* @param dir |
|
* @returns {Map} |
|
*/ |
|
function getFromMap(dir) { |
|
var ssOptions = (function () { |
|
if (dir.get("options")) { |
|
return dir.get("options").toJS(); |
|
} |
|
return {}; |
|
})(); |
|
var route = Immutable.List([]) |
|
.concat(dir.get("route")) |
|
.filter(_.isString); |
|
var _dir = Immutable.List([]) |
|
.concat(dir.get("dir")) |
|
.filter(_.isString); |
|
if (_dir.size === 0) { |
|
return fromJS({ |
|
items: [], |
|
errors: [ |
|
{ |
|
type: "Invalid Object", |
|
data: { |
|
message: "Serve Static requires a 'dir' property when using an Object" |
|
} |
|
} |
|
] |
|
}); |
|
} |
|
var ssItems = (function () { |
|
/** |
|
* iterate over every 'route' item |
|
* @type {Immutable.List<any>|Immutable.List<*>|Immutable.List<any>|*} |
|
*/ |
|
var routeItems = (function () { |
|
/** |
|
* If no 'route' was given, assume we want to match all |
|
* paths |
|
*/ |
|
if (route.size === 0) { |
|
return _dir.map(function (dirString) { |
|
return Map({ |
|
route: "", |
|
dir: dirString |
|
}); |
|
}); |
|
} |
|
return route.reduce(function (acc, routeString) { |
|
/** |
|
* For each 'route' item, also iterate through 'dirs' |
|
* @type {Immutable.Iterable<K, M>} |
|
*/ |
|
var perDir = _dir.map(function (dirString) { |
|
return Map({ |
|
route: getRoute(routeString), |
|
dir: dirString |
|
}); |
|
}); |
|
return acc.concat(perDir); |
|
}, List([])); |
|
})(); |
|
/** |
|
* Now create a serverStatic Middleware for each item |
|
*/ |
|
return routeItems.map(function (routeItem) { |
|
return routeItem.merge({ |
|
handle: serveStatic(routeItem.get("dir"), ssOptions) |
|
}); |
|
}); |
|
})(); |
|
return fromJS({ |
|
items: ssItems, |
|
errors: [] |
|
}); |
|
} |
|
} |
|
}; |
|
module.exports = serverUtils; |
|
//# sourceMappingURL=utils.js.map
|