Files
smsgw-tester/main.js
David Majdandžić b8e9642507 Implement closing and destroying center sessions
Although this currently serves little to no purpose because the sessions can not be opened again
2023-03-25 17:40:44 +01:00

1328 lines
40 KiB
JavaScript

const smpp = require("smpp");
const fs = require("fs");
const path = require("path");
const EventEmitter = require("events");
const NanoTimer = require('nanotimer');
const express = require("express");
const app = express();
const bodyParser = require("body-parser");
const WebSocket = require("ws");
const SERVER_PORT = process.env.SERVER_PORT || 8190;
const WS_SERVER_PORT = process.env.WS_SERVER_PORT || 8191;
const CLIENT_SESSIONS_FILE = process.env.CLIENT_SESSIONS_FILE || "client_sessions.json";
const CENTER_SESSIONS_FILE = process.env.CENTER_SESSIONS_FILE || "center_sessions.json";
const MESSAGE_SEND_UPDATE_DELAY = process.env.MESSAGE_SEND_UPDATE_DELAY || 500;
[
'debug',
'log',
'warn',
'error'
].forEach((methodName) => {
const originalLoggingMethod = console[methodName];
console[methodName] = (firstArgument, ...otherArguments) => {
const originalPrepareStackTrace = Error.prepareStackTrace;
Error.prepareStackTrace = (_, stack) => stack;
const callee = new Error().stack[2];
Error.prepareStackTrace = originalPrepareStackTrace;
const relativeFileName = path.relative(process.cwd(), callee.getFileName());
const prefix = `${relativeFileName}:${callee.getLineNumber()}:`;
if (typeof firstArgument === 'string') {
originalLoggingMethod(prefix + ' ' + firstArgument, ...otherArguments);
} else {
originalLoggingMethod(prefix, firstArgument, ...otherArguments);
}
};
});
class Logger {
constructor(clazz) {
this.clazz = clazz;
this.logLevel = typeof LOG_LEVEL !== "undefined" ? LOG_LEVEL : 6;
this.logFile = typeof LOG_FILE !== "undefined" ? LOG_FILE : null;
this.logFileWriteStream = null;
if (this.logFile != null) {
this.logFileWriteStream = fs.createWriteStream(this.logFile, {flags: 'a'});
}
}
leftPad(str, len, char) {
str = String(str);
let i = -1;
len = len - str.length;
if (char === undefined) {
char = " ";
}
while (++i < len) {
str = char + str;
}
return str;
}
log(...args) {
let logLevel = args[0];
let data = args[1];
if (typeof data === "object") {
data = JSON.stringify(data);
}
let date = new Date();
let year = this.leftPad(date.getFullYear(), 4);
let month = this.leftPad(date.getMonth() + 1, 2, 0);
let day = this.leftPad(date.getDate(), 2, 0);
let hours = this.leftPad(date.getHours(), 2, 0);
let minutes = this.leftPad(date.getMinutes(), 2, 0);
let seconds = this.leftPad(date.getSeconds(), 2, 0);
let milliseconds = this.leftPad(date.getMilliseconds(), 3, 0);
let datePrefix = `[${day}/${month}/${year}-${hours}:${minutes}:${seconds}:${milliseconds}]`
// let out = `${datePrefix} [${this.clazz}] (${logLevel}) ${data}`;
let out = datePrefix.padEnd(30, ' ') + `[${this.clazz}]`.padEnd(28, ' ') + `(${logLevel})`.padEnd(8, ' ') + data;
if (args[0] <= this.logLevel || 6) {
console.log(out);
}
if (this.logFileWriteStream != null) {
this.logFileWriteStream.write(out + "\n");
}
}
log1 = this.log.bind(this, 1);
log2 = this.log.bind(this, 2);
log3 = this.log.bind(this, 3);
log4 = this.log.bind(this, 4);
log5 = this.log.bind(this, 5);
log6 = this.log.bind(this, 6);
}
let logger = new Logger("main");
class ClientSessionStatus {
static CONNECTING = "CONNECTING";
static CONNECTED = "CONNECTED";
static BINDING = "BINDING";
static BOUND = "BOUND";
static NOT_CONNECTED = "NOT_CONNECTED";
}
class ClientSession {
auto_enquire_link_period = 500;
eventEmitter = new EventEmitter();
busy = false;
connectingPromise = {
promise: null,
resolve: null,
reject: null
}
disconnectingPromise = {
promise: null,
resolve: null,
reject: null
}
bindingPromise = {
promise: null,
resolve: null,
reject: null
}
static STATUS_CHANGED_EVENT = "statusChanged";
static ANY_PDU_EVENT = "*";
static MESSAGE_SEND_COUNTER_UPDATE_EVENT = "messageSendCounterUpdate";
constructor(id, url, username, password) {
this.id = id;
this.logger = new Logger(`ClientSession-${this.id}`);
this.url = url;
this.username = username;
this.password = password;
this.logger.log1(`Session created with url ${this.url}, username ${this.username}, password ${this.password} and ID ${this.id}`);
this.status = ClientSessionStatus.NOT_CONNECTED;
}
setUsername(username) {
this.username = username;
this.refresh();
}
setPassword(password) {
this.password = password;
this.refresh();
}
refresh() {
let status = this.status;
this.close();
if (status === ClientSessionStatus.CONNECTED) {
this.connect().catch(err => this.logger.log1(err));
}
if (status === ClientSessionStatus.BOUND) {
this.connect().then(() => {
this.bind().catch((err => this.logger.log1(err)));
}).catch((err => this.logger.log1(err)));
}
}
setStatus(newStatus) {
this.status = newStatus;
this.eventEmitter.emit(ClientSession.STATUS_CHANGED_EVENT, newStatus);
}
connect() {
this.connectingPromise.promise = new Promise((resolve, reject) => {
if (this.status !== ClientSessionStatus.NOT_CONNECTED) {
this.logger.log1("ClientSession already connected");
reject("ClientSession already connected");
return;
}
this.logger.log1("Connecting to " + this.url);
this.setStatus(ClientSessionStatus.CONNECTING);
try {
this.session = smpp.connect({
url: this.url,
auto_enquire_link_period: this.auto_enquire_link_period,
}, this.connected.bind(this));
this.session.on('error', this.error.bind(this));
} catch (e) {
this.logger.log1("Connection failed to " + this.url);
this.setStatus(ClientSessionStatus.NOT_CONNECTED);
this.session.close();
reject("Connection failed to " + this.url);
}
this.connectingPromise.resolve = resolve;
this.connectingPromise.reject = reject;
});
return this.connectingPromise.promise;
}
error(error) {
if (error.code === "ETIMEOUT") {
this.logger.log1("Connection timed out to " + this.url);
} else if (error.code === "ECONNREFUSED") {
this.logger.log1("Connection refused to " + this.url);
} else {
this.logger.log1("Connection failed to " + this.url);
}
this.session.close();
this.setStatus(ClientSessionStatus.NOT_CONNECTED);
}
connected() {
this.logger.log1("Connected to " + this.url);
this.setStatus(ClientSessionStatus.CONNECTED);
this.session.on('debug', (type, msg, payload) => {
if (type.includes('pdu.')) {
this.eventEmitter.emit(msg, payload);
this.eventEmitter.emit(ClientSession.ANY_PDU_EVENT, payload);
}
});
this.session.on('pdu', this.sessionPdu.bind(this));
this.connectingPromise.resolve();
}
sessionPdu(pdu) {
if (pdu.command === 'deliver_sm') {
this.session.send(pdu.response());
}
}
bind() {
this.bindingPromise.promise = new Promise((resolve, reject) => {
if (this.status !== ClientSessionStatus.CONNECTED) {
this.logger.log1(`Cannot bind, not connected to ${this.url}`);
reject(`Cannot bind, not connected to ${this.url}`);
return;
}
this.logger.log1("Trying to bind to " + this.url)
if (this.status !== ClientSessionStatus.CONNECTED) {
this.logger.log1(`Cannot bind, not connected to ${this.url}`);
return;
}
if (!!!this.username || !!!this.password) {
this.logger.log1(`Cannot bind, username or password not set`);
return;
}
this.setStatus(ClientSessionStatus.BINDING);
this.logger.log1(`Binding to ${this.url} with username ${this.username} and password ${this.password}`);
this.session.bind_transceiver({
system_id: this.username,
password: this.password,
}, this.bindReply.bind(this));
this.bindingPromise.resolve = resolve;
this.bindingPromise.reject = reject;
});
return this.bindingPromise.promise;
}
bindReply(pdu) {
if (pdu.command_status === 0) {
this.logger.log1(`Bound to ${this.url} with username ${this.username} and password ${this.password}`);
this.setStatus(ClientSessionStatus.BOUND);
this.bindingPromise.resolve();
} else {
this.logger.log1(`Bind failed to ${this.url} with username ${this.username} and password ${this.password}`);
this.setStatus(ClientSessionStatus.CONNECTED);
this.bindingPromise.reject();
}
}
send(source, destination, message) {
return new Promise((resolve, reject) => {
if (!this.canSend()) {
this.logger.log1(`Cannot send message, not bound to ${this.url} or busy`);
reject(`Cannot send message, not bound to ${this.url} or busy`);
return;
}
this.logger.log1(`Sending message from ${source} to ${destination} with message ${message}`);
this.session.submit_sm({
source_addr: source,
destination_addr: destination,
short_message: message
}, pdu => {
resolve(pdu);
});
});
}
sendOnInterval(source, destination, message, interval, count) {
return new Promise((resolve, reject) => {
if (!this.canSend() || this.busy) {
this.logger.log1(`Cannot send many message, not bound to ${this.url} or busy`);
reject(`Cannot send many message, not bound to ${this.url} or busy`);
return;
}
this.busy = true;
this.timer = new NanoTimer();
let counter = 0;
let previousUpdateCounter = 0;
this.updateTimer = new NanoTimer();
this.updateTimer.setInterval(() => {
if (previousUpdateCounter !== counter) {
this.eventEmitter.emit(ClientSession.MESSAGE_SEND_COUNTER_UPDATE_EVENT, counter);
previousUpdateCounter = counter;
}
}, '', `${MESSAGE_SEND_UPDATE_DELAY / 1000} s`);
this.timer.setInterval(() => {
if (count > 0 && counter >= count) {
this.cancelSendInterval();
} else {
this.send(source, destination, message)
.catch(e => this.logger.log1(`Error sending message: ${e}`));
counter++;
}
}, '', `${interval} s`);
resolve();
});
}
cancelSendInterval() {
if (!!this.timer) {
this.timer.clearInterval();
this.updateTimer.clearInterval();
this.timer = null;
this.updateTimer = null;
}
this.busy = false;
}
close() {
this.disconnectingPromise.promise = new Promise((resolve, reject) => {
if (this.status !== ClientSessionStatus.BOUND && this.status !== ClientSessionStatus.CONNECTED) {
this.logger.log1(`Cannot close session, not bound to ${this.url}`);
reject(`Cannot close session, not bound to ${this.url}`);
return;
}
this.session.close();
this.setStatus(ClientSessionStatus.NOT_CONNECTED);
resolve();
});
return this.disconnectingPromise.promise;
}
on(event, callback) {
this.eventEmitter.on(event, callback);
}
serialize() {
return {
id: this.id,
url: this.url,
username: this.username,
password: this.password,
status: this.status
}
}
canSend() {
return this.status === ClientSessionStatus.BOUND;
}
}
class ClientSessionManager {
sessionIdCounter = 0;
logger = new Logger("ClientSessionManager");
constructor() {
this.sessions = {};
}
createSession(url, username, password) {
let urlB64 = btoa(url);
if (this.sessions[urlB64]) {
this.logger.log1(`Session to ${url} already exists`);
return this.sessions[urlB64];
}
this.logger.log1(`Creating session to ${url} with username ${username} and password ${password}`);
let session = new ClientSession(this.sessionIdCounter++, url, username, password);
this.addSession(session);
return session;
}
addSession(session) {
this.logger.log1(`Adding session with ID ${session.id}`);
this.sessions[btoa(session.url)] = session;
}
deleteSession(session) {
this.logger.log1(`Deleting session with ID ${session.id}`);
if (session.status === ClientSessionStatus.BOUND || session.status === ClientSessionStatus.CONNECTED) {
session.close();
}
delete this.sessions[btoa(session.url)];
}
getSession(id) {
return Object.values(this.sessions).find((session) => {
return session.id == id;
});
}
serialize() {
return Object.values(this.sessions).map((session) => {
return session.serialize();
});
}
cleanup() {
this.logger.log1(`Saving sessions to ${CLIENT_SESSIONS_FILE}...`);
fs.writeFileSync(CLIENT_SESSIONS_FILE, JSON.stringify(this.serialize(), null, 4));
}
startup() {
try {
let sessions = fs.readFileSync(CLIENT_SESSIONS_FILE);
sessions = JSON.parse(sessions);
this.logger.log1(`Loaded ${sessions.length} sessions from ${CLIENT_SESSIONS_FILE}...`);
sessions.forEach(session => {
this.createSession(session.url, session.username, session.password);
});
} catch (e) {
this.logger.log1(`Error loading sessions from ${CLIENT_SESSIONS_FILE}: ${e}`);
}
}
}
class CenterSessionStatus {
static CONNECTED = "CONNECTED";
static WAITING_CONNECTION = "WAITING_CONNECTION";
static CONNECTION_PENDING = "CONNECTION_PENDING";
}
class CenterMode {
static DEBUG = "DEBUG";
static ECHO = "ECHO";
static DR = "DR";
}
class CenterSession {
// TODO: If the port is in use this throws an exception, catch it and log it
eventEmitter = new EventEmitter();
busy = false;
sessions = [];
nextSession = 0;
mode = CenterMode.DEBUG;
disconnectingPromise = {
promise: null,
resolve: null,
reject: null
}
static STATUS_CHANGED_EVENT = "statusChanged";
static MODE_CHANGED_EVENT = "modeChanged";
static SESSION_CHANGED_EVENT = "sessionChanged";
static ANY_PDU_EVENT = "*";
static MESSAGE_SEND_COUNTER_UPDATE_EVENT = "messageSendCounterUpdate";
constructor(id, port, username, password) {
this.id = id;
this.logger = new Logger(`CenterSession-${this.id}`);
this.port = port;
this.username = username;
this.password = password;
this.server = smpp.createServer({}, this.connected.bind(this));
this.server.on('debug', (type, msg, payload) => {
if (type.includes('pdu.')) {
this.eventEmitter.emit(msg, payload);
this.eventEmitter.emit(CenterSession.ANY_PDU_EVENT, payload);
}
});
this.server.listen(this.port);
this.logger.log1(`Session created with port ${this.port}, username ${this.username}, password ${this.password} and ID ${this.id}`);
this.status = CenterSessionStatus.WAITING_CONNECTION;
}
setStatus(newStatus) {
this.status = newStatus;
this.eventEmitter.emit(CenterSession.STATUS_CHANGED_EVENT, newStatus);
}
setUsername(username) {
this.username = username;
this.refresh();
}
setPassword(password) {
this.password = password;
this.refresh();
}
setMode(mode) {
this.mode = Object.values(CenterMode)[mode];
this.eventEmitter.emit(CenterSession.MODE_CHANGED_EVENT, mode);
}
refresh() {
this.close().catch(err => {
});
}
error(error) {
if (error.code === "ETIMEOUT") {
this.logger.log1("Connection timed out to " + this.port);
} else if (error.code === "ECONNREFUSED") {
this.logger.log1("Connection refused to " + this.port);
} else {
this.logger.log1("Connection failed to " + this.port);
}
this.setStatus(CenterSessionStatus.CONNECTION_PENDING);
}
connected(session) {
this.logger.log1("Center got a connection on port " + this.port);
this.setStatus(CenterSessionStatus.CONNECTION_PENDING);
session.on('error', this.error.bind(this));
function bind_transciever(pdu) {
this.logger.log1(`Center got a bind_transciever on port ${this.port} with system_id ${pdu.system_id} and password ${pdu.password}`);
session.pause();
if (pdu.system_id === this.username && pdu.password === this.password) {
this.logger.log1(`Connection successful`);
session.send(pdu.response());
session.resume();
session.on('pdu', this.sessionPdu.bind(this, session));
this.addSession(session);
this.setStatus(CenterSessionStatus.CONNECTED);
session.on('debug', (type, msg, payload) => {
if (type.includes('pdu.')) {
this.eventEmitter.emit(msg, payload);
this.eventEmitter.emit(CenterSession.ANY_PDU_EVENT, payload);
}
});
} else {
this.logger.log1(`Connection failed, invalid credentials`);
session.send(pdu.response({
command_status: smpp.ESME_RBINDFAIL
}));
this.setStatus(CenterSessionStatus.WAITING_CONNECTION);
session.close();
this.session = null;
}
}
session.on('bind_transceiver', bind_transciever.bind(this));
}
sessionPdu(session, pdu) {
if (pdu.command === 'submit_sm') {
session.send(pdu.response());
if (this.mode === CenterMode.ECHO) {
this.notify(pdu.source_addr, pdu.destination_addr, pdu.short_message);
}
// TODO: Figure out how DRs work
// if (this.mode === CenterMode.DR) {
// this.notify(pdu.source_addr, pdu.destination_addr, pdu.short_message);
// }
}
if (pdu.command === 'enquire_link') {
session.send(pdu.response());
}
}
notify(source, destination, message) {
return new Promise((resolve, reject) => {
if (!this.canSend()) {
this.logger.log1(`Cannot send message, no client connected on ${this.port} or busy`);
reject(`Cannot send message, no client connected on ${this.port} or busy`);
return;
}
this.logger.log1(`Sending notify message from ${source} to ${destination} with message ${message}`);
this.getNextSession().deliver_sm({
source_addr: source,
destination_addr: destination,
short_message: message
}, pdu => {
resolve(pdu);
});
});
}
notifyOnInterval(source, destination, message, interval, count) {
return new Promise((resolve, reject) => {
if (!this.canSend() || this.busy) {
this.logger.log1(`Cannot send many message, no clients connected to ${this.port} or busy`);
reject(`Cannot send many message, no clients connected to ${this.port} or busy`);
return;
}
this.busy = true;
this.timer = new NanoTimer();
let counter = 0;
let previousUpdateCounter = 0;
this.updateTimer = new NanoTimer();
this.updateTimer.setInterval(() => {
if (previousUpdateCounter !== counter) {
this.eventEmitter.emit(CenterSession.MESSAGE_SEND_COUNTER_UPDATE_EVENT, counter);
previousUpdateCounter = counter;
}
}, '', `${MESSAGE_SEND_UPDATE_DELAY / 1000} s`);
this.timer.setInterval(() => {
if (count > 0 && counter >= count) {
this.cancelNotifyInterval();
} else {
this.notify(source, destination, message)
.catch(e => this.logger.log1(`Error sending message: ${e}`));
counter++;
}
}, '', `${interval} s`);
resolve();
});
}
cancelNotifyInterval() {
if (!!this.timer) {
this.timer.clearInterval();
this.updateTimer.clearInterval();
this.timer = null;
this.updateTimer = null;
}
this.busy = false;
}
getNextSession() {
if (this.sessions.length === 0) {
return null;
}
let session = this.sessions[this.nextSession];
this.nextSession = (this.nextSession + 1) % this.sessions.length;
return session;
}
getSessions() {
return this.sessions.map(session => {
return this.mapSession(session);
})
}
mapSession(session) {
return {
closed: session.closed,
paused: session.paused,
remoteAddress: session.remoteAddress,
remotePort: session.remotePort,
_id: session._id,
deleted: session.deleted || false
}
}
closeSession(sessionId) {
let session = this.sessions.find(session => session._id == sessionId);
if (!!session) {
session.close();
this.eventEmitter.emit(CenterSession.SESSION_CHANGED_EVENT, this.mapSession(session));
}
}
deleteSession(sessionId) {
let session = this.sessions.find(session => session._id == sessionId);
if (!!session) {
session.close();
session.destroy();
session.deleted = true;
this.eventEmitter.emit(CenterSession.SESSION_CHANGED_EVENT, this.mapSession(session));
delete this.sessions[this.sessions.indexOf(session)];
this.sessions = this.sessions.filter(Boolean);
}
}
addSession(session) {
let sessionInfo = this.mapSession(session);
this.eventEmitter.emit(CenterSession.SESSION_CHANGED_EVENT, sessionInfo);
this.sessions.push(session);
}
close() {
this.disconnectingPromise.promise = new Promise((resolve, reject) => {
if (this.status !== CenterSessionStatus.CONNECTED) {
this.logger.log1(`Cannot close session, no clients bound to ${this.port}`);
reject(`Cannot close session, no clients bound to ${this.port}`);
return;
}
this.sessions.forEach(session => {
session.close();
});
this.sessions = [];
this.setStatus(CenterSessionStatus.WAITING_CONNECTION);
resolve();
});
return this.disconnectingPromise.promise;
}
on(event, callback) {
this.eventEmitter.on(event, callback);
}
serialize() {
return {
id: this.id,
port: this.port,
username: this.username,
password: this.password,
status: this.status,
activeSessions: this.sessions.length,
mode: this.mode
}
}
canSend() {
return this.status === CenterSessionStatus.CONNECTED;
}
}
class CenterSessionManager {
sessionIdCounter = 0;
logger = new Logger("CenterSessionManager");
constructor() {
this.servers = {};
}
createSession(port, username, password) {
if (this.servers[port]) {
this.logger.log1(`Center listening on ${port} already exists`);
return this.servers[port];
}
this.logger.log1(`Creating center listening on ${port} with username ${username} and password ${password}`);
let session = new CenterSession(this.sessionIdCounter++, port, username, password);
this.addSession(session);
return session;
}
addSession(server) {
this.logger.log1(`Adding session with ID ${server.id}`);
this.servers[server.port] = server;
}
deleteSession(server) {
this.logger.log1(`Deleting server with ID ${server.id}`);
if (server.status === CenterSessionStatus.CONNECTED) {
server.close();
}
delete this.servers[server.port];
}
getSession(id) {
return Object.values(this.servers).find((session) => {
return session.id == id;
});
}
serialize() {
return Object.values(this.servers).map((servers) => {
return servers.serialize();
});
}
cleanup() {
this.logger.log1(`Saving sessions to ${CENTER_SESSIONS_FILE}...`);
fs.writeFileSync(CENTER_SESSIONS_FILE, JSON.stringify(this.serialize(), null, 4));
}
startup() {
try {
let servers = fs.readFileSync(CENTER_SESSIONS_FILE);
servers = JSON.parse(servers);
this.logger.log1(`Loaded ${servers.length} sessions from ${CENTER_SESSIONS_FILE}...`);
servers.forEach(server => {
let createdServer = this.createSession(server.port, server.username, server.password);
if (!!server.mode) {
createdServer.mode = server.mode;
}
});
} catch (e) {
this.logger.log1(`Error loading sessions from ${CLIENT_SESSIONS_FILE}: ${e}`);
}
}
getAvailableCenterModes() {
let modes = Object.values(CenterMode);
return modes.reduce((acc, curr, idx) => {
acc[idx] = curr;
return acc;
}, {});
}
}
class HTTPServer {
logger = new Logger("HTTPServer");
constructor() {
app.use(bodyParser.json());
app.get('/api/client', this.getClientSessions.bind(this));
app.post('/api/client', this.createClientSession.bind(this));
app.get('/api/client/:id', this.getClientSessionById.bind(this));
app.patch('/api/client/:id', this.patchClientSession.bind(this));
app.post('/api/client/:id/send', this.send.bind(this));
app.post('/api/client/:id/sendMany', this.sendMany.bind(this));
app.delete('/api/client/:id/sendMany', this.cancelSendMany.bind(this));
app.post('/api/client/:id/bind', this.bindClientSession.bind(this));
app.post('/api/client/:id/connect', this.connectClientSession.bind(this));
app.delete('/api/client/:id/connect', this.disconnectClientSession.bind(this));
app.delete('/api/client/:id', this.deleteClientSession.bind(this));
app.get('/api/center', this.getCenterSessions.bind(this));
app.post('/api/center', this.createCenterSession.bind(this));
app.get('/api/center/modes', this.getAvailableModes.bind(this));
app.get('/api/center/:id', this.getCenterServerById.bind(this));
app.get('/api/center/:id/session', this.getCenterServerSessionsById.bind(this));
app.delete('/api/center/:id/session/:sessionId', this.closeCenterServerSessionById.bind(this));
app.delete('/api/center/:id/session/:sessionId/destroy', this.deleteCenterServerSessionById.bind(this));
app.patch('/api/center/:id', this.patchCenterServer.bind(this));
app.post('/api/center/:id/send', this.notify.bind(this));
app.post('/api/center/:id/sendMany', this.notifyMany.bind(this));
app.delete('/api/center/:id/sendMany', this.cancelNotifyMany.bind(this));
app.delete('/api/center/:id/connect', this.disconnectCenterSession.bind(this));
app.delete('/api/center/:id', this.deleteCenterServer.bind(this));
this.server = app.listen(SERVER_PORT, function() {
this.logger.log1(`HTTPServer listening at http://localhost:${SERVER_PORT}`)
}.bind(this));
}
// TODO: These requests deserve error handling
getClientSessions(req, res) {
this.logger.log1("Getting client sessions");
res.send(clientSessionManager.serialize());
}
createClientSession(req, res) {
this.logger.log1("Creating client session");
let session = clientSessionManager.createSession(req.body.url, req.body.username, req.body.password);
res.send(session.serialize());
}
getClientSessionById(req, res) {
let session = clientSessionManager.getSession(req.params.id);
this.logger.log1(`Getting client session by ID ${req.params.id}`);
if (session) {
this.logger.log1(`Client session found with ID ${req.params.id}`)
res.send(session.serialize());
} else {
this.logger.log1(`No client session found with ID ${req.params.id}`);
res.status(404).send();
}
}
patchClientSession(req, res) {
let session = clientSessionManager.getSession(req.params.id);
if (session) {
this.logger.log1(`Client session found with ID ${req.params.id}`)
if (!!req.body.username && req.body.username !== session.username) {
session.setUsername(req.body.username);
}
if (!!req.body.password && req.body.password !== session.password) {
session.setPassword(req.body.password);
}
res.send(session.serialize());
} else {
this.logger.log1(`No client session found with ID ${req.params.id}`);
res.status(404).send();
}
}
send(req, res) {
let session = clientSessionManager.getSession(req.params.id);
let source = req.body.source;
let destination = req.body.destination;
let message = req.body.message;
this.logger.log1(`Sending message from ${source} to ${destination} with message ${message} on session with ID ${req.params.id}`)
if (session) {
session.send(source, destination, message)
.then(pdu => res.send(pdu))
.catch(err => res.status(400).send(JSON.stringify(err)));
} else {
this.logger.log1(`No session found with ID ${req.params.id}`);
res.status(404).send();
}
}
sendMany(req, res) {
let session = clientSessionManager.getSession(req.params.id);
let source = req.body.source;
let destination = req.body.destination;
let message = req.body.message;
let interval = req.body.interval / 1000;
let count = req.body.count;
if (!!req.body.perSecond) {
interval = 1 / req.body.perSecond;
}
let perSecond = 1 / interval;
this.logger.log1(
`Sending ${count} messages from ${source} to ${destination} with message ${message} on session with ID ${req.params.id} at a rate of ${perSecond} per second.`);
if (session) {
session.sendOnInterval(source, destination, message, interval, count)
.then(pdu => res.send(pdu))
.catch(err => res.status(400).send((err)));
} else {
this.logger.log1(`No session found with ID ${req.params.id}`);
res.status(404).send();
}
}
cancelSendMany(req, res) {
let session = clientSessionManager.getSession(req.params.id);
if (!session.busy) {
res.status(400).send({
err: true,
msg: `Session with ID ${req.params.id} is not sending messages`
});
return;
}
this.logger.log1(`Cancelling send timer for session with ID ${req.params.id}`);
if (session) {
session.cancelSendInterval();
res.send();
} else {
this.logger.log1(`No session found with ID ${req.params.id}`);
res.status(404).send();
}
}
bindClientSession(req, res) {
this.logger.log1(`Binding client session with ID ${req.params.id}`)
// Maybe make this async?
let session = clientSessionManager.getSession(req.params.id);
if (session) {
session.bind()
.then(() => res.send(session.serialize()))
.catch(err => res.status(400).send({
err: true,
msg: err
}));
} else {
this.logger.log1(`No session found with ID ${req.params.id}`);
res.status(404).send();
}
}
connectClientSession(req, res) {
this.logger.log1(`Connecting client session with ID ${req.params.id}`)
let session = clientSessionManager.getSession(req.params.id);
if (session) {
session.connect()
.then(() => res.send(session.serialize()))
.catch(err => res.status(400).send({
err: true,
msg: err
}));
} else {
this.logger.log1(`No session found with ID ${req.params.id}`);
res.status(404).send();
}
}
disconnectClientSession(req, res) {
this.logger.log1(`Disconnecting client session with ID ${req.params.id}`)
let session = clientSessionManager.getSession(req.params.id);
if (session) {
session.close()
.then(() => res.send(session.serialize()))
.catch(err => res.status(400).send({
err: true,
msg: err
}));
} else {
this.logger.log1(`No session found with ID ${req.params.id}`);
res.status(404).send();
}
}
deleteClientSession(req, res) {
this.logger.log1(`Deleting client session with ID ${req.params.id}`);
let session = clientSessionManager.getSession(req.params.id);
if (session) {
clientSessionManager.deleteSession(session);
res.send();
} else {
this.logger.log1(`No session found with ID ${req.params.id}`);
res.status(404).send();
}
}
getCenterSessions(req, res) {
this.logger.log1("Getting center sessions");
res.send(centerSessionManager.serialize());
}
createCenterSession(req, res) {
this.logger.log1("Creating center session");
let session = centerSessionManager.createSession(req.body.port, req.body.username, req.body.password);
res.send(session.serialize());
}
getCenterServerById(req, res) {
let session = centerSessionManager.getSession(req.params.id);
this.logger.log1(`Getting center session by ID ${req.params.id}`);
if (session) {
this.logger.log1(`Center session found with ID ${req.params.id}`)
res.send(session.serialize());
} else {
this.logger.log1(`No center session found with ID ${req.params.id}`);
res.status(404).send();
}
}
getCenterServerSessionsById(req, res) {
let server = centerSessionManager.getSession(req.params.id);
this.logger.log1(`Getting center session by ID ${req.params.id}`);
if (server) {
this.logger.log1(`Center session found with ID ${req.params.id}`)
res.send(server.getSessions());
} else {
this.logger.log1(`No center session found with ID ${req.params.id}`);
res.status(404).send();
}
}
closeCenterServerSessionById(req, res) {
let server = centerSessionManager.getSession(req.params.id);
this.logger.log1(`Getting center session by ID ${req.params.id}`);
if (server) {
this.logger.log1(`Center session found with ID ${req.params.id}`)
server.closeSession(req.params.sessionId)
res.send();
} else {
this.logger.log1(`No center session found with ID ${req.params.id}`);
res.status(404).send();
}
}
deleteCenterServerSessionById(req, res) {
let server = centerSessionManager.getSession(req.params.id);
this.logger.log1(`Getting center session by ID ${req.params.id}`);
if (server) {
this.logger.log1(`Center session found with ID ${req.params.id}`)
server.deleteSession(req.params.sessionId)
res.send();
} else {
this.logger.log1(`No center session found with ID ${req.params.id}`);
res.status(404).send();
}
}
patchCenterServer(req, res) {
let server = centerSessionManager.getSession(req.params.id);
if (server) {
this.logger.log1(`Center server found with ID ${req.params.id}`)
if (!!req.body.username && req.body.username !== server.username) {
server.setUsername(req.body.username);
}
if (!!req.body.password && req.body.password !== server.password) {
server.setPassword(req.body.password);
}
if (!!req.body.mode) {
server.setMode(req.body.mode);
}
res.send(server.serialize());
} else {
this.logger.log1(`No center server found with ID ${req.params.id}`);
res.status(404).send();
}
}
getAvailableModes(req, res) {
this.logger.log1("Getting available modes");
res.send(centerSessionManager.getAvailableCenterModes());
}
notify(req, res) {
let server = centerSessionManager.getSession(req.params.id);
let source = req.body.source;
let destination = req.body.destination;
let message = req.body.message;
this.logger.log1(`Sending notify message from ${source} to ${destination} with message ${message} on session with ID ${req.params.id}`)
if (server) {
server.notify(source, destination, message)
.then(pdu => res.send(pdu))
.catch(err => res.status(400).send(err));
} else {
this.logger.log1(`No session found with ID ${req.params.id}`);
res.status(404).send();
}
}
notifyMany(req, res) {
let server = centerSessionManager.getSession(req.params.id);
let source = req.body.source;
let destination = req.body.destination;
let message = req.body.message;
let interval = req.body.interval / 1000;
let count = req.body.count;
if (!!req.body.perSecond) {
interval = 1 / req.body.perSecond;
}
let perSecond = 1 / interval;
this.logger.log1(
`Sending ${count} notify messages from ${source} to ${destination} with message ${message} on session with ID ${req.params.id} at a rate of ${perSecond} per second.`);
if (server) {
server.notifyOnInterval(source, destination, message, interval, count)
.then(pdu => res.send(pdu))
.catch(err => res.status(400).send(err));
} else {
this.logger.log1(`No session found with ID ${req.params.id}`);
res.status(404).send();
}
}
cancelNotifyMany(req, res) {
let server = centerSessionManager.getSession(req.params.id);
if (!server.busy) {
res.status(400).send({
err: true,
msg: `Session with ID ${req.params.id} is not sending messages`
});
return;
}
this.logger.log1(`Cancelling send timer for server with ID ${req.params.id}`);
if (server) {
server.cancelNotifyInterval();
res.send();
} else {
this.logger.log1(`No session found with ID ${req.params.id}`);
res.status(404).send();
}
}
disconnectCenterSession(req, res) {
this.logger.log1(`Disconnecting center session with ID ${req.params.id}`)
let server = centerSessionManager.getSession(req.params.id);
if (server) {
server.close()
.then(() => res.send(server.serialize()))
.catch(err => res.status(400).send({
err: true,
msg: err
}));
} else {
this.logger.log1(`No session found with ID ${req.params.id}`);
res.status(404).send();
}
}
deleteCenterServer(req, res) {
this.logger.log1(`Deleting center session with ID ${req.params.id}`);
let server = centerSessionManager.getSession(req.params.id);
if (server) {
centerSessionManager.deleteSession(server);
res.send();
} else {
this.logger.log1(`No session found with ID ${req.params.id}`);
res.status(404).send();
}
}
}
class WSServer {
// TODO: Implement center adding and removing...
// TODO: This will probably have to be reworked a little to accommodate centers.
clients = {};
constructor() {
this.server = new WebSocket.Server({port: WS_SERVER_PORT});
this.logger = new Logger("WSServer");
this.server.on('connection', this.onConnection.bind(this));
this.logger.log1(`WSServer listening at ws://localhost:${WS_SERVER_PORT}`);
}
onConnection(ws) {
this.logger.log1("New connection");
this.addClient(ws, -1);
ws.on('message', this.onMessage.bind(this, ws));
ws.on('close', this.onClose.bind(this, ws));
}
addClient(ws, sessionId) {
if (!this.clients[sessionId]) {
this.clients[sessionId] = [];
let session = clientSessionManager.getSession(sessionId);
if (session) {
session.on(ClientSession.STATUS_CHANGED_EVENT, this.onSessionChange.bind(this, sessionId));
session.on(ClientSession.ANY_PDU_EVENT, this.pduEvent.bind(this, sessionId));
session.on(ClientSession.MESSAGE_SEND_COUNTER_UPDATE_EVENT, this.onMessageSendCounterUpdate.bind(this, sessionId));
}
}
this.logger.log1(`Added client to session ID: ${sessionId}`);
this.clients[sessionId].push(ws);
this.logger.log1(`Now active ${this.clients[sessionId].length} clients in session ID: ${sessionId}`);
}
onMessage(ws, message) {
this.logger.log1("New message");
let sessionId = String(message);
this.logger.log1(`Moving client to session ID: ${sessionId}`);
this.removeClient(ws);
this.addClient(ws, sessionId);
this.logger.log1(`Now active ${this.clients[sessionId].length} clients in session ID: ${sessionId}`);
}
onClose(ws) {
this.removeClient(ws);
this.logger.log6(this.clients);
this.logger.log1("Connection closed");
}
removeClient(ws) {
for (let sessionId in this.clients) {
let index = this.clients[sessionId].indexOf(ws);
if (index > -1) {
delete this.clients[sessionId][index];
}
this.clients[sessionId] = this.clients[sessionId].filter(Boolean);
if (this.clients[sessionId].length === 0) {
delete this.clients[sessionId];
}
}
}
onSessionChange(sessionId, newStatus) {
this.logger.log1(`Session with ID ${sessionId} changed`);
let payload = {
type: 'status',
sessionId: sessionId,
value: newStatus
}
let clients = this.clients[sessionId];
if (!!clients) {
this.logger.log1(`Broadcasting session with ID ${sessionId} to ${clients.length} clients`);
clients.forEach(client => {
client.send(JSON.stringify(payload));
});
}
}
pduEvent(sessionId, pdu) {
let clients = this.clients[sessionId];
if (!!clients) {
this.logger.log2(`Session with ID ${sessionId} fired PDU`);
let payload = {
type: 'pdu',
sessionId: sessionId,
value: pdu
}
this.logger.log2(`Broadcasting session with ID ${sessionId} to ${clients.length} clients`);
clients.forEach(client => {
client.send(JSON.stringify(payload));
});
}
}
onMessageSendCounterUpdate(sessionId, counter) {
this.logger.log2(`Session with ID ${sessionId} updating message send counter`);
let payload = {
type: 'counterUpdate',
sessionId: sessionId,
value: counter
}
let clients = this.clients[sessionId];
if (!!clients) {
this.logger.log2(`Broadcasting session with ID ${sessionId} to ${clients.length} clients`);
clients.forEach(client => {
client.send(JSON.stringify(payload));
});
}
}
}
let clientSessionManager = new ClientSessionManager();
let centerSessionManager = new CenterSessionManager();
clientSessionManager.startup();
centerSessionManager.startup();
// let session = clientSessionManager.createSession('smpp://localhost:7001', 'test', 'test');
// let server = centerSessionManager.createSession(7001, 'test', 'test');
let session = clientSessionManager.getSession(0);
let server = centerSessionManager.getSession(0);
// session.connect()
// .then(() => {
// session.bind().then(() => {
// // server.notify('src', 'dst', 'msg');
// }).catch(err => console.log(err));
// }).catch(err => console.log(err));
//
// setTimeout(() => session.setUsername("test123"), 2000);
// setTimeout(() => session.setPassword("test123"), 4000);
// session.on(CenterSession.ANY_PDU_EVENT, (pdu) => {
// console.log(pdu);
// });
// session.on(ClientSession.ANY_PDU_EVENT, (pdu) => {
// if (pdu.command.includes('enquire')) {
// return;
// }
// console.log(pdu);
// });
new WSServer();
new HTTPServer();
function cleanup() {
clientSessionManager.cleanup();
centerSessionManager.cleanup();
process.exit(0);
}
// process.on('exit', cleanup);
// process.on('SIGINT', cleanup);
// process.on('SIGUSR1', cleanup);
// process.on('SIGUSR2', cleanup);
// process.on('uncaughtException', cleanup);