express-mesh
Version:
A Gentics Mesh frontend for Express.
362 lines (326 loc) • 14.8 kB
text/typescript
;
import https = require('https');
import http = require('http');
import Q = require('q');
import URL = require('url');
import u = require('./meshUtil');
import lang = require('./meshLanguages');
import {LoggingConfig} from "./config";
import {IMeshRequest} from "./mesh";
import {IMeshNodeListQueryParams} from "./mesh";
import {IMeshNav} from "./mesh";
import {IMeshNode} from "./mesh";
import {IMeshNodeListResponse} from "./mesh";
import {IMeshSearchQuery} from "./mesh";
import {IMeshTagFamily} from "./mesh";
import {IMeshTag} from "./mesh";
import {MeshQueryParams} from "./mesh";
/**
* Mesh Rest response object
*/
export class MeshRestResponse<T> {
public stream : http.IncomingMessage;
constructor(public status : number, public data : T, public isBinary : boolean){}
}
/**
* Possible authentication methods.
*/
export enum MeshAuthType {
BASIC
}
/**
* Mesh authentication obejct.
*/
export class MeshAuth {
public static MESH_USER_SESSION_KEY = 'meshusername';
public static MESH_PASSWORD_SESSION_KEY = 'meshpassword';
type : MeshAuthType;
header : string;
/**
* Initialize the Mesh authentication object.
* It generates the needed authentication header for making Mesh requests.
* @param request The MeshRequest
*/
constructor(request : IMeshRequest) {
this.type = MeshAuthType.BASIC;
if (u.isDefined(request.session[MeshAuth.MESH_USER_SESSION_KEY]) && u.isDefined(request.session[MeshAuth.MESH_PASSWORD_SESSION_KEY])) {
this.header = this.getBasicAuthHeader(request.session[MeshAuth.MESH_USER_SESSION_KEY],
request.session[MeshAuth.MESH_PASSWORD_SESSION_KEY]);
} else {
this.header = this.getBasicAuthHeader(request.meshConfig.publicUser.username, request.meshConfig.publicUser.password);
}
}
/**
* Generate basic auth header from username and password.
* @param username Username
* @param password Password
* @returns {string} Header string.
*/
private getBasicAuthHeader(username : string, password : string) : string {
return 'Basic ' + new Buffer(username + ':' + password).toString('base64');
}
}
/**
* Options for making Mesh Requests.
*/
export class MeshRequestOptions {
url : string;
method : string;
data : any;
params : IMeshNodeListQueryParams;
auth : MeshAuth;
logging : LoggingConfig;
/**
* Initialize request options and prefill it with the authentication object.
* @param request MeshRequest
*/
constructor(request : IMeshRequest) {
this.auth = new MeshAuth(request);
this.logging = request.meshConfig.logging;
}
}
/**
* MeshRestClient that can be used to make requests to the mesh backend.
*/
export class MeshRestClient {
private static NODES_ENDPOINT = '/nodes/';
private static NAVIGATION_ENDPOINT = '/navigation';
private static CHILDREN_ENDPOINT = '/children';
private static TAG_FAMILIES_ENDPOINT = '/tagFamilies';
private publicMeshCookieUserStore : any = {};
/**
*
* @param req
* @param path
* @param maxDepth
* @returns {Q.Promise<MeshRestResponse<IMeshNav>>}
*/
public getNavigationByPath(req : IMeshRequest, path : string, params? : MeshQueryParams) : Q.Promise<MeshRestResponse<IMeshNav>> {
var url = req.meshConfig.backendUrl + req.meshConfig.base + req.meshConfig.project + req.meshConfig.navroot + path,
params = params ? params : new MeshQueryParams();
params.maxDepth = params.maxDepth ? params.maxDepth : 10;
return this.meshSimpleGET(req, url, params).then((response : MeshRestResponse<IMeshNav>)=>{
return response;
});
}
public getNavigationByUUID(req : IMeshRequest, uuid : string, params? : MeshQueryParams) : Q.Promise<MeshRestResponse<IMeshNav>> {
var url = req.meshConfig.backendUrl + req.meshConfig.base + req.meshConfig.project + MeshRestClient.NODES_ENDPOINT + uuid + MeshRestClient.NAVIGATION_ENDPOINT,
params = params ? params : new MeshQueryParams();
params.maxDepth = params.maxDepth ? params.maxDepth : 10;
return this.meshSimpleGET(req,url, params).then((response : MeshRestResponse<IMeshNav>)=>{
return response;
});
}
public getWebrootNode<T>(req : IMeshRequest, params? : MeshQueryParams) : Q.Promise<MeshRestResponse<IMeshNode<T>>> {
var url = req.path;
if (typeof url === 'undefined' || u.getPath(url) === '/') {
url = req.meshConfig.index;
}
if (!params) {
params = req.query;
}
url = req.meshConfig.backendUrl + req.meshConfig.base + req.meshConfig.project + req.meshConfig.webroot + url;
return this.meshSimpleGET<IMeshNode<T>>(req, url, params).then((response : MeshRestResponse<IMeshNode<T>>)=>{
if (req.meshConfig.checkPublished && !response.isBinary && !response.data.published && response.status < 400) {
response.status = 404;
}
return response;
});
}
public getMeshNode<T>(req : IMeshRequest, uuid : string, params? : MeshQueryParams) : Q.Promise<MeshRestResponse<IMeshNode<T>>> {
var url = req.meshConfig.backendUrl + req.meshConfig.base + req.meshConfig.project + MeshRestClient.NODES_ENDPOINT + uuid;
return this.meshSimpleGET<IMeshNode<T>>(req, url, params).then((response : MeshRestResponse<IMeshNode<T>>)=>{
if (req.meshConfig.checkPublished && !response.isBinary && !response.data.published && response.status < 400) {
response.status = 404;
}
return response;
});
}
public getChildren<T>(req : IMeshRequest, uuid : string, params? : MeshQueryParams) : Q.Promise<MeshRestResponse<IMeshNodeListResponse<IMeshNode<T>>>> {
var url = req.meshConfig.backendUrl + req.meshConfig.base + req.meshConfig.project + MeshRestClient.NODES_ENDPOINT + uuid + "/children";
return this.meshSimpleGET<IMeshNodeListResponse<IMeshNode<T>>>(req, url, params);
}
public meshSearch<T>(req : IMeshRequest, query : IMeshSearchQuery, params? : IMeshNodeListQueryParams) : Q.Promise<MeshRestResponse<IMeshNodeListResponse<T>>> {
var opts = new MeshRequestOptions(req),
languages = lang.getLanguageArray(req);
opts.url = opts.url = req.meshConfig.backendUrl + req.meshConfig.base + 'search/nodes';
opts.params = params;
opts.params.resolveLinks = 'short';
if (params.lang) {
opts.params.lang = params.lang
} else if (u.isDefined(languages)) {
opts.params.lang = languages.join(',');
}
return this.meshPOST(opts, query);
}
public getTagFamilies(req : IMeshRequest, params? : MeshQueryParams) : Q.Promise<MeshRestResponse<IMeshNodeListResponse<IMeshTagFamily>>> {
var url = req.meshConfig.backendUrl + req.meshConfig.base + req.meshConfig.project + MeshRestClient.TAG_FAMILIES_ENDPOINT;
return this.meshSimpleGET<IMeshNodeListResponse<IMeshTagFamily>>(req, url, params);
}
public getTagsOfTagFamily(req : IMeshRequest, uuid : string, params? : MeshQueryParams) : Q.Promise<MeshRestResponse<IMeshNodeListResponse<IMeshTag>>> {
var url = req.meshConfig.backendUrl + req.meshConfig.base + req.meshConfig.project + MeshRestClient.TAG_FAMILIES_ENDPOINT + '/' + uuid + '/tags';
return this.meshSimpleGET<IMeshNodeListResponse<IMeshTag>>(req, url, params);
}
public login(req : IMeshRequest, username : string, password : string) : Q.Promise<boolean> {
var opts = new MeshRequestOptions(req);
opts.url = opts.url = req.meshConfig.backendUrl + req.meshConfig.base + 'auth/login';
return this.meshPOST(opts, {
"username" : username,
"password" : password
}).then((response) => {
if (response.status === 200) {
return true;
} else {
return false;
}
}).catch((err) => {
return false;
});
}
public logout(req : IMeshRequest) : Q.Promise<boolean> {
var opts = new MeshRequestOptions(req);
opts.url = opts.url = req.meshConfig.backendUrl + req.meshConfig.base + 'auth/logout';
return this.makeMeshRequest(opts).then((response) => {
return true;
}).catch((err) => {
return false;
});
}
public meshSimpleGET<T>(req : IMeshRequest, url : string, params? : MeshQueryParams) : Q.Promise<MeshRestResponse<T>> {
return this.meshSimpleRequest<T>('GET', req, url, params);
}
public meshSimpleRequest<T>(method : string, req : IMeshRequest, url : string, params? : MeshQueryParams, data? : any) : Q.Promise<MeshRestResponse<T>> {
var opts = new MeshRequestOptions(req),
languages = lang.getLanguageArray(req);
if (u.isDefined(url)) {
if (url.indexOf(req.meshConfig.base) <= -1) {
url = req.meshConfig.base + url;
}
if (url.indexOf(req.meshConfig.backendUrl) <= -1) {
url = req.meshConfig.backendUrl + url;
}
}
opts.url = url;
opts.params = params ? params : new MeshQueryParams();
opts.params.resolveLinks = 'short';
opts.method = method;
opts.data = data;
if (u.isDefined(languages) && !opts.params.lang) {
opts.params.lang = languages.join(',');
}
return this.makeMeshRequest(opts);
}
public meshPOST(requestOptions : MeshRequestOptions, data? : any) : Q.Promise<MeshRestResponse<any>> {
requestOptions.method = 'POST';
requestOptions.data = data;
return this.makeMeshRequest(requestOptions);
}
/**
* Convert a queryParams object into a URL-encoded string.
*/
private queryStringFromParams(queryParams: IMeshNodeListQueryParams) {
var queryString = '?';
for (var prop in queryParams) {
if (queryParams.hasOwnProperty(prop)) {
queryString += prop + '=' + encodeURIComponent(queryParams[prop]) + '&';
}
}
// return substring to remove trailing '&'
return queryString.substr(0, queryString.length - 1);
}
private makeMeshRequest(requestOptions : MeshRequestOptions) : Q.Promise<MeshRestResponse<any>> {
var deferred = Q.defer<MeshRestResponse<any>>(),
options : https.RequestOptions = {},
urlString : string = requestOptions.url,
url,
data;
if (u.isDefined(requestOptions.params)) {
urlString += this.queryStringFromParams(requestOptions.params);
}
url = URL.parse(urlString);
options.host = url.host;
options.port = Number(url.port);
options.path = url.path;
options.hostname = url.hostname;
options.method = requestOptions.method ? requestOptions.method : 'GET';
options.headers = {};
if (u.isDefined(this.publicMeshCookieUserStore[requestOptions.auth.header])) {
options.headers['Cookie'] = this.publicMeshCookieUserStore[requestOptions.auth.header];
}
options.headers['Accept'] = '*/*';
if (u.isDefined(requestOptions.data)) {
data = JSON.stringify(requestOptions.data);
options.headers['Content-Type'] = 'application/json';
options.headers['Content-Length'] = Buffer.byteLength(data);
}
if (requestOptions.auth && requestOptions.auth.type === MeshAuthType.BASIC) {
options.headers['Authorization'] = requestOptions.auth.header;
}
var starttime = Date.now(),
req = http.request(options, (res) => {
var data = '';
if (res.statusCode === 401 || res.statusCode === 403) {
console.error(res.statusCode, res.statusMessage, 'Check your mesh user.');
}
if (u.isDefined(res.headers) && u.isDefined(res.headers['set-cookie'])) {
res.headers['set-cookie'].forEach((cookie) => {
var meshCookie;
if (cookie.indexOf('mesh.session') > -1) {
meshCookie = cookie.substring(0, cookie.indexOf(';'));
this.publicMeshCookieUserStore[requestOptions.auth.header] = meshCookie;
}
});
}
if (u.isDefined(res.headers) && u.isDefined(res.headers['content-disposition'])) {
let response = new MeshRestResponse(res.statusCode, undefined, true);
response.stream = res;
deferred.resolve(response);
} else {
res.on('data', (chunk) => {
data += chunk;
});
res.on('error', (err)=>{
console.error(err);
});
res.on('end', () => {
try {
var result = JSON.parse(data);
if (requestOptions.logging.data) {
console.log(JSON.stringify(result, null, 4));
}
if (requestOptions.logging.timing) {
console.log('TIMING: ', options.path, (Date.now() - starttime) + 'ms');
}
if (result.error || result.success == 0) {
deferred.reject(new MeshRestResponse(res.statusCode, result.error || 'Unknown error', false));
} else {
//DEBUG LOG FOR JSON NODES
deferred.resolve(new MeshRestResponse(res.statusCode, result, false));
}
} catch (e) {
console.error('parse error');
e.parseError = 'Error while parsing json';
e.options = options;
deferred.reject(new MeshRestResponse(res.statusCode, e, false));
}
});
}
});
req.on('socket', (socket) => {
socket.setTimeout(30000);
socket.on('timeout', function() {
req.abort();
});
});
req.on('error', (err) => {
err.options = options;
console.error('ERROR IN REQUEST', err);
deferred.reject(new MeshRestResponse(u.STATUS_ERROR, err, false));
});
if (u.isDefined(data)) {
req.write(data);
}
req.end();
return deferred.promise;
}
}