Server example
Any http server can be used of course to serve dynamic user-defined variable requests. And for this example nodejs is used, so make sure to install it first then follow the the next steps:
- Copy the following code and save as
httpServer.js
or download httpServer.js. - Open your terminal or command prompt if you are on Windows then run
node httpServer.js
.
You might notice that closing the terminal will also stop the server. To prevent it, if you are on Windows you can use runHiddenConsole. If you are on Linux or the like, I assume you know what to do or at least you have access to someone who can do something for you.
Be advised, the following codes are intended as an example only to demonstrate the use of dynamic user-defined variables. You should design your server properly or consult a professional if you are not sure you can do it yourself.
/* Copyright (c) 2024 Prasetyo S - MIT License */
const http = require('http');
const fileSystem = require('fs').promises;
const path = require('path');
const Format = { PLAIN_TEXT: 0, RICH_TEXT: 1, FILE: 2 }
const server = http.createServer(requestHandler);
server.timeout = 10000;
server.keepAliveTimeout = 5000;
server.requestTimeout = 5000;
server.headersTimeout = 3000;
server.listen(8080, 'localhost');
function requestHandler(request, serverResponse) {
request.on('error', error => console.error(error.message));
switch (request.method) {
case 'OPTIONS':
if (request.headers.origin.startsWith('moz-extension://')) {
allowCors(serverResponse);
} else {
allowNone(serverResponse);
}
break;
case 'GET':
handleGET(request, serverResponse);
break;
case 'POST':
handlePOST(request, serverResponse);
break;
default:
allowNone();
}
}
function allowNone(serverResponse) {
serverResponse.statusCode = 405;
serverResponse.setHeader('Allow', '');
serverResponse.end();
}
/** Preflight request response. */
function allowCors(serverResponse) {
serverResponse.statusCode = 200;
serverResponse.setHeader('Access-Control-Allow-Origin', '*');
serverResponse.setHeader('Access-Control-Allow-Headers', 'Content-Type');
serverResponse.setHeader('Access-Control-Max-Age', 86400);
serverResponse.end();
}
function corsHttpError(httpStatusCode, serverResponse) {
let responseBody = '500 Internal Server Error';
serverResponse.statusCode = 500;
if (httpStatusCode == 400 && httpStatusCode == 404 && httpStatusCode == 503) {
serverResponse.statusCode = httpStatusCode;
if (httpStatusCode == 400) {
responseBody = '400 Bad Request';
} else if (httpStatusCode == 404) {
responseBody = '404 Not Found';
} else {
responseBody = '503 Service Unavailable';
}
}
serverResponse.setHeader('Access-Control-Allow-Origin', '*');
serverResponse.setHeader('Content-Type', 'text/plain');
serverResponse.end(responseBody);
}
function handlePOST(request, serverResponse) {
let requestBody = [];
if (
'content-type' in request.headers &&
request.headers['content-type'].includes('application/json')
) {
request.on('data', chunk => requestBody.push(chunk));
request.on('end', _onEnd);
} else {
corsHttpError(503, serverResponse);
}
function _onEnd() {
try {
requestBody = JSON.parse(Buffer.concat(requestBody).toString());
if (requestBody.format == Format.FILE) {
processFileRequest(requestBody, serverResponse);
} else {
processTextualRequest(requestBody, serverResponse);
}
console.log(requestBody);
} catch (exception) {
console.error(exception.message);
corsHttpError(500, serverResponse);
}
}
}
function handleGET(request, serverResponse) {
let requestBody = {};
let url = new URL(request.url, `http://${request.headers.host}`);
for (const [key, value] of url.searchParams.entries()) {
requestBody[key] = (key == 'isArray') ? (value == 'true') : value;
}
console.log(requestBody);
if (requestBody.format == Format.FILE) {
processFileRequest(requestBody, serverResponse);
} else {
processTextualRequest(requestBody, serverResponse);
}
}
function sendResponse(responseBody, serverResponse) {
serverResponse.setHeader('Access-Control-Allow-Origin', '*');
serverResponse.setHeader('Content-Type', 'application/json');
serverResponse.end(JSON.stringify(responseBody));
}
async function processFileRequest(requestData, serverResponse) {
try {
// Response body must be a JSON array regardless of variable value
// type. Supplying more array elements while 'isArray' is false will
// only waste server resources as the AC only expects one.
let responseBody = [];
if (requestData.variable.toUpperCase() == 'ATTACHMENT') {
responseBody.push(await getAttachment(requestData));
} else {
throw new Error(`Unhandled variable: ${requestData.variable}`);
}
if (requestData.isArray == false && responseBody.length > 1) {
throw new Error('Scalar variable cannot have multiple values');
}
sendResponse(responseBody, serverResponse);
} catch (exception) {
console.error(exception.message);
corsHttpError(500, serverResponse);
}
}
async function getAttachment(requestData) {
let fileName = path.basename(requestData.path);
let buffer = await fileSystem.readFile(requestData.path);
return {
file: buffer.toString('base64'),
name: fileName
}
}
function processTextualRequest(requestData, serverResponse) {
try {
// Response body note is similar to file processor.
let responseBody = [];
let variable = requestData.variable.toUpperCase();
switch (variable) {
case 'GREETING':
responseBody.push(getGreeting());
break;
case 'RTLQUOTEHEADER':
if (requestData.messageType == 'reply' || requestData.messageType == 'forward') {
responseBody.push(buildQuoteHeader(requestData));
} else {
responseBody.push('');
}
break;
default:
throw new Error(`Unhandled variable: ${requestData.variable}`);
}
if (requestData.isArray == false && responseBody.length > 1) {
throw new Error('Scalar variable cannot have multiple values');
}
sendResponse(responseBody, serverResponse);
} catch (exception) {
console.error(exception.message);
corsHttpError(500, serverResponse);
}
}
function getGreeting() {
let greeting = 'Good ';
let hours = new Date().getHours();
if (hours < 12) {
greeting += 'morning';
} else if (hours > 12 && hours < 18) {
greeting += 'afternoon';
} else {
greeting += 'job';
}
return greeting;
}
class RtlQuoteHeader {
_from() {
return `<b>مِن:</b> ${this._info.sender}<br>`;
}
_date() {
let locale = 'ar';
let date = new Date(this._info.date);
let dateFormat = new Intl.DateTimeFormat(
locale,
{ year: 'numeric', month: 'long', weekday: 'long', day: '2-digit' }
).format(date);
let timeFormat = new Intl.DateTimeFormat(
locale,
{ hour: '2-digit', minute: '2-digit' }
).format(date);
return `<b>التاريخ:</b> ${dateFormat} ${timeFormat}<br>`;
}
_to() {
return `<b>إلى:</b> ${this._info.to.join('; ')}<br>`;
}
_cc() {
return this._info.cc.length ? `<b>ينسخ:</b> ${this._info.cc.join('; ')}<br>` : '';
}
_subject() {
return `<b>الموضوع:</b> ${this._info.subject} <br>`;
}
constructor(originalMsgInfo) {
this._info = originalMsgInfo;
}
build() {
let content = this._from() + this._date() + this._to() + this._cc() + this._subject();
let style = `font-size: 11pt; font-family: Calibri, sans-serif;
border-top: 1pt solid #e1e1e1; margin-top: 5pt; padding-bottom: 3pt`;
return `<div dir="rtl" style="${style}">${content}</div>`;
}
}
function buildQuoteHeader(requestData) {
const lt = '<', gt = '>';
let sender = (requestData.senderName == requestData.senderEmail)
? requestData.senderEmail
: `${requestData.senderName} ${lt}${requestData.senderEmail}${gt}`;
let to = [];
for (let i = 0, len = requestData.toName.length; i < len; i++) {
if (requestData.toName[i] == requestData.toEmail[i]) {
to.push(requestData.toEmail[i]);
} else {
to.push(`${requestData.toName[i]} ${lt}${requestData.toEmail[i]}${gt}`);
}
}
let cc = [];
for (let i = 0, len = requestData.ccName.length; i < len; i++) {
if (requestData.ccName[i] == requestData.ccEmail[i]) {
to.push(requestData.ccEmail[i]);
} else {
to.push(`${requestData.ccName[i]} ${lt}${requestData.ccEmail[i]}${gt}`);
}
}
let header = new RtlQuoteHeader({
sender: sender,
to: to,
cc: cc,
subject: requestData.subject,
date: requestData.date
});
return header.build();
}