1
0
mirror of https://github.com/laurent22/joplin.git synced 2024-12-12 08:54:00 +02:00
joplin/ReactNativeClient/lib/ClipperServer.js

217 lines
5.5 KiB
JavaScript

const { netUtils } = require('lib/net-utils');
const urlParser = require("url");
const Setting = require('lib/models/Setting');
const { Logger } = require('lib/logger.js');
const randomClipperPort = require('lib/randomClipperPort');
const enableServerDestroy = require('server-destroy');
const Api = require('lib/services/rest/Api');
const ApiResponse = require('lib/services/rest/ApiResponse');
const multiparty = require('multiparty');
class ClipperServer {
constructor() {
this.logger_ = new Logger();
this.startState_ = 'idle';
this.server_ = null;
this.port_ = null;
this.api_ = new Api(() => {
return Setting.value('api.token');
});
}
static instance() {
if (this.instance_) return this.instance_;
this.instance_ = new ClipperServer();
return this.instance_;
}
setLogger(l) {
this.logger_ = l;
this.api_.setLogger(l);
}
logger() {
return this.logger_;
}
setDispatch(d) {
this.dispatch_ = d;
}
dispatch(action) {
if (!this.dispatch_) throw new Error('dispatch not set!');
this.dispatch_(action);
}
setStartState(v) {
if (this.startState_ === v) return;
this.startState_ = v;
this.dispatch({
type: 'CLIPPER_SERVER_SET',
startState: v,
});
}
setPort(v) {
if (this.port_ === v) return;
this.port_ = v;
this.dispatch({
type: 'CLIPPER_SERVER_SET',
port: v,
});
}
async findAvailablePort() {
const tcpPortUsed = require('tcp-port-used');
let state = null;
for (let i = 0; i < 10000; i++) {
state = randomClipperPort(state, Setting.value('env'));
const inUse = await tcpPortUsed.check(state.port);
if (!inUse) return state.port;
}
throw new Error('All potential ports are in use or not available.')
}
async start() {
this.setPort(null);
this.setStartState('starting');
try {
const p = await this.findAvailablePort();
this.setPort(p);
} catch (error) {
this.setStartState('idle');
this.logger().error(error);
return;
}
this.server_ = require('http').createServer();
this.server_.on('request', async (request, response) => {
const writeCorsHeaders = (code, contentType = "application/json", additionalHeaders = null) => {
const headers = Object.assign({}, {
"Content-Type": contentType,
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET, POST, OPTIONS, PUT, PATCH, DELETE',
'Access-Control-Allow-Headers': 'X-Requested-With,content-type',
}, additionalHeaders ? additionalHeaders : {});
response.writeHead(code, headers);
}
const writeResponseJson = (code, object) => {
writeCorsHeaders(code);
response.write(JSON.stringify(object));
response.end();
}
const writeResponseText = (code, text) => {
writeCorsHeaders(code, 'text/plain');
response.write(text);
response.end();
}
const writeResponseInstance = (code, instance) => {
if (instance.type === 'attachment') {
const filename = instance.attachmentFilename ? instance.attachmentFilename : 'file';
writeCorsHeaders(code, instance.contentType ? instance.contentType : 'application/octet-stream', {
'Content-disposition': 'attachment; filename=' + filename,
'Content-Length': instance.body.length,
});
response.end(instance.body);
} else {
throw new Error('Not implemented');
}
}
const writeResponse = (code, response) => {
if (response instanceof ApiResponse) {
writeResponseInstance(code, response);
} else if (typeof response === 'string') {
writeResponseText(code, response);
} else {
writeResponseJson(code, response);
}
}
this.logger().info('Request: ' + request.method + ' ' + request.url);
const url = urlParser.parse(request.url, true);
const execRequest = async (request, body = '', files = []) => {
try {
const response = await this.api_.route(request.method, url.pathname, url.query, body, files);
writeResponse(200, response ? response : '');
} catch (error) {
this.logger().error(error);
const httpCode = error.httpCode ? error.httpCode : 500;
const msg = [];
if (httpCode >= 500) msg.push('Internal Server Error');
if (error.message) msg.push(error.message);
writeResponse(httpCode, { error: msg.join(': ') });
}
}
const contentType = request.headers['content-type'] ? request.headers['content-type'] : '';
if (request.method === 'OPTIONS') {
writeCorsHeaders(200);
response.end();
} else {
if (contentType.indexOf('multipart/form-data') === 0) {
const form = new multiparty.Form();
form.parse(request, function(error, fields, files) {
if (error) {
writeResponse(error.httpCode ? error.httpCode : 500, error.message);
return;
} else {
execRequest(
request,
fields && fields.props && fields.props.length ? fields.props[0] : '',
files && files.data ? files.data : []
);
}
});
} else {
if (request.method === 'POST' || request.method === 'PUT') {
let body = '';
request.on('data', (data) => {
body += data;
});
request.on('end', async () => {
execRequest(request, body);
});
} else {
execRequest(request);
}
}
}
});
enableServerDestroy(this.server_);
this.logger().info('Starting Clipper server on port ' + this.port_);
this.server_.listen(this.port_, '127.0.0.1');
this.setStartState('started');
}
async stop() {
this.server_.destroy();
this.server_ = null;
this.setStartState('idle');
this.setPort(null);
}
}
module.exports = ClipperServer;