You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
182 lines
4.7 KiB
JavaScript
182 lines
4.7 KiB
JavaScript
6 years ago
|
"use strict";
|
||
|
|
||
|
const Promise = require("bluebird");
|
||
|
const pathToRegexp = require("path-to-regexp");
|
||
|
const defaultValue = require("default-value");
|
||
|
const url = require("url");
|
||
|
|
||
|
const objectToFormData = require("../util/form-data/object-to-formdata");
|
||
|
const objectToURLSearchParams = require("../util/form-data/object-to-urlsearchparams");
|
||
|
|
||
|
module.exports = function createRouter(options = {}) {
|
||
|
let baseUrl = defaultValue(options.baseBackendUrl, "");
|
||
|
|
||
|
let router = {
|
||
|
_routes: [],
|
||
|
_getRoute: function getRoute(method, path) {
|
||
|
let matches;
|
||
|
let matchingRoute = router._routes.find((route) => route.method === method && (matches = route.regex.exec(path)));
|
||
|
|
||
|
if (matchingRoute == null) {
|
||
|
throw new Error(`No matching routes found for ${method.toUpperCase()} ${path}`);
|
||
|
} else {
|
||
|
let params = {};
|
||
|
|
||
|
matchingRoute.keys.forEach((key, i) => {
|
||
|
params[key] = matches[i + 1];
|
||
|
});
|
||
|
|
||
|
return {
|
||
|
handler: matchingRoute.handler,
|
||
|
params: params
|
||
|
};
|
||
|
}
|
||
|
},
|
||
|
get: function (...args) {
|
||
|
return this.addRoute("get", ...args);
|
||
|
},
|
||
|
post: function (...args) {
|
||
|
return this.addRoute("post", ...args);
|
||
|
},
|
||
|
put: function (...args) {
|
||
|
return this.addRoute("put", ...args);
|
||
|
},
|
||
|
delete: function (...args) {
|
||
|
return this.addRoute("delete", ...args);
|
||
|
},
|
||
|
head: function (...args) {
|
||
|
return this.addRoute("head", ...args);
|
||
|
},
|
||
|
patch: function (...args) {
|
||
|
return this.addRoute("patch", ...args);
|
||
|
},
|
||
|
addRoute: function addRoute(method, path, handler) {
|
||
|
/* Mutable arguments? WTF. */
|
||
|
let keys = [];
|
||
|
let regex = pathToRegexp(path, keys);
|
||
|
|
||
|
router._routes.push({ method, path, regex, keys, handler });
|
||
|
},
|
||
|
handle: function handleRequest(method, uri, data, handleOptions = {}) {
|
||
|
return Promise.try(() => {
|
||
|
/* TODO: Support relative paths? */
|
||
|
let {pathname, query} = url.parse(uri, true);
|
||
|
let route = this._getRoute(method, pathname);
|
||
|
let tasks = [];
|
||
|
let renderTaskAdded = false;
|
||
|
|
||
|
let req = {
|
||
|
path: pathname,
|
||
|
query: query,
|
||
|
body: data,
|
||
|
params: route.params,
|
||
|
pass: function(options = {}) {
|
||
|
return Promise.try(() => {
|
||
|
let body;
|
||
|
|
||
|
if (handleOptions.multipart) {
|
||
|
body = objectToFormData(this.body);
|
||
|
} else {
|
||
|
body = objectToURLSearchParams(this.body);
|
||
|
}
|
||
|
|
||
|
return window.fetch(url.resolve(baseUrl, uri), Object.assign({ // FIXME: Override URI but maintain query?
|
||
|
method: method,
|
||
|
credentials: "same-origin", /* FIXME: Allow sending them elsewhere as well? */
|
||
|
body: body
|
||
|
}, options));
|
||
|
}).then((response) => {
|
||
|
if (!response.ok) {
|
||
|
/* TODO: Is this what we want? */
|
||
|
throw new Error(`Got a non-200 response: ${response.status}`, {response: response});
|
||
|
} else {
|
||
|
return Promise.try(() => {
|
||
|
return response.json();
|
||
|
}).then((json) => {
|
||
|
return {
|
||
|
status: response.status,
|
||
|
body: json
|
||
|
};
|
||
|
});
|
||
|
}
|
||
|
});
|
||
|
},
|
||
|
/* TODO: passActions? */
|
||
|
passRender: function(viewName, options = {}) {
|
||
|
return Promise.try(() => {
|
||
|
return this.pass(options.requestOptions);
|
||
|
}).then((response) => {
|
||
|
let locals = defaultValue(options.locals, {});
|
||
|
let combinedLocals = Object.assign({}, locals, response.body);
|
||
|
|
||
|
res.render(viewName, combinedLocals, options.renderOptions);
|
||
|
});
|
||
|
}
|
||
|
};
|
||
|
|
||
|
let res = {
|
||
|
render: function(view, locals = {}, options = {}) {
|
||
|
if (renderTaskAdded === false) {
|
||
|
renderTaskAdded = true;
|
||
|
|
||
|
tasks.push({
|
||
|
type: "render",
|
||
|
view, locals, options
|
||
|
});
|
||
|
} else {
|
||
|
throw new Error("Can only render a view once per response");
|
||
|
}
|
||
|
},
|
||
|
redirect: function(path, redirectMethod) {
|
||
|
tasks.push({
|
||
|
type: "redirect",
|
||
|
path: path,
|
||
|
method: defaultValue(redirectMethod, method)
|
||
|
});
|
||
|
},
|
||
|
open: function(path, options = {}) {
|
||
|
tasks.push({
|
||
|
type: "open",
|
||
|
path, options
|
||
|
});
|
||
|
},
|
||
|
close: function(options = {}) {
|
||
|
tasks.push({
|
||
|
type: "close",
|
||
|
options
|
||
|
});
|
||
|
},
|
||
|
notify: function(message, options = {}) {
|
||
|
tasks.push({
|
||
|
type: "notify",
|
||
|
message: message,
|
||
|
options: {
|
||
|
type: defaultValue(options.type, "info"),
|
||
|
timeout: options.timeout,
|
||
|
title: options.title
|
||
|
}
|
||
|
});
|
||
|
},
|
||
|
error: function(error, context = {}) {
|
||
|
tasks.push({
|
||
|
type: "error",
|
||
|
error, context
|
||
|
});
|
||
|
}
|
||
|
};
|
||
|
|
||
|
return Promise.try(() => {
|
||
|
return route.handler(req, res);
|
||
|
}).then((result) => {
|
||
|
return {
|
||
|
result: result,
|
||
|
actions: tasks
|
||
|
};
|
||
|
});
|
||
|
});
|
||
|
}
|
||
|
};
|
||
|
|
||
|
return router;
|
||
|
};
|