Merge branch 'typescript-rework' into dev
# Conflicts: # package.json
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="main.js" type="NodeJSConfigurationType" nameIsGenerated="true" path-to-js-file="main.js" working-dir="$PROJECT_DIR$">
|
||||
<configuration default="false" name="main.js" type="NodeJSConfigurationType" nameIsGenerated="true" path-to-js-file="$PROJECT_DIR$/main.js" working-dir="$PROJECT_DIR$">
|
||||
<method v="2" />
|
||||
</configuration>
|
||||
</component>
|
18
.run/main.ts.run.xml
Normal file
18
.run/main.ts.run.xml
Normal file
@@ -0,0 +1,18 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="main.ts" type="TypeScriptProgramRunner" factoryName="TypeScript">
|
||||
<module name="smsgwtester" />
|
||||
<envs>
|
||||
<env name="LOG_LEVEL" value="4" />
|
||||
</envs>
|
||||
<option name="interpreterRef" value="project" />
|
||||
<option name="enabledTsNodeEsmLoader" value="false" />
|
||||
<option name="interpreterOptions" value="" />
|
||||
<option name="workingDirectory" value="C:\Users\Administrator\WebstormProjects\smsgwtester\src" />
|
||||
<option name="tsconfigFile" value="" />
|
||||
<option name="extraTypeScriptOptions" value="" />
|
||||
<option name="scriptName" value="$PROJECT_DIR$/src/main.ts" />
|
||||
<option name="programParameters" value="" />
|
||||
<option name="tsnodePackage" value="C:\Program Files\nodejs\node_modules\ts-node" />
|
||||
<method v="2" />
|
||||
</configuration>
|
||||
</component>
|
30
WebsocketTest.ts
Normal file
30
WebsocketTest.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
// @ts-ignore
|
||||
const Websocket = require('ws');
|
||||
|
||||
const ws = new Websocket('ws://localhost:8191');
|
||||
ws.on('open', function open() {
|
||||
ws.send('something');
|
||||
});
|
||||
|
||||
interface Animal {
|
||||
doNoise(): void;
|
||||
}
|
||||
|
||||
class Dog implements Animal {
|
||||
doNoise(): void {
|
||||
console.log("woof");
|
||||
}
|
||||
}
|
||||
|
||||
class Cat implements Animal {
|
||||
doNoise(): void {
|
||||
console.log("meow");
|
||||
}
|
||||
}
|
||||
|
||||
const dog = new Dog();
|
||||
dog.doNoise();
|
||||
const cat = new Cat();
|
||||
cat.doNoise();
|
||||
let animals: Animal[] = [dog, cat];
|
||||
animals.forEach(animal => animal.doNoise());
|
192
src/Center/Center.ts
Normal file
192
src/Center/Center.ts
Normal file
@@ -0,0 +1,192 @@
|
||||
import {Job} from "../Job/Job";
|
||||
import Logger from "../Logger";
|
||||
import {DebugPduProcessor} from "../PDUProcessor/DebugPduProcessor";
|
||||
import {PduProcessor} from "../PDUProcessor/PduProcessor";
|
||||
import {SmppSession} from "../SmppSession";
|
||||
|
||||
const NanoTimer = require('nanotimer');
|
||||
const smpp = require("smpp");
|
||||
|
||||
export class Center extends SmppSession {
|
||||
readonly STATUS: string[] = [
|
||||
"WAITING CONNECTION",
|
||||
"CONNECTING",
|
||||
"CONNECTED",
|
||||
];
|
||||
|
||||
id: number;
|
||||
username: string;
|
||||
password: string;
|
||||
status: string = this.STATUS[0];
|
||||
port: number;
|
||||
|
||||
pduProcessors: PduProcessor[] = [];
|
||||
defaultSingleJob!: Job;
|
||||
defaultMultipleJob!: Job;
|
||||
readonly logger: Logger;
|
||||
private pendingSessions: any[] = [];
|
||||
private sessions: any[] = [];
|
||||
private nextSession: number = 0;
|
||||
private server: any;
|
||||
|
||||
constructor(id: number, port: number, username: string, password: string) {
|
||||
super();
|
||||
this.id = id;
|
||||
this.username = username;
|
||||
this.password = password;
|
||||
this.port = port;
|
||||
|
||||
this.setDefaultSingleJob(Job.createEmptySingle());
|
||||
this.setDefaultMultipleJob(Job.createEmptyMultiple());
|
||||
|
||||
this.logger = new Logger(`Center-${id}`);
|
||||
|
||||
this.initialize();
|
||||
}
|
||||
|
||||
sendMultiple(job: Job): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.validateSessions(reject);
|
||||
if (!job.count || !job.perSecond) {
|
||||
reject(`Center-${this.getId()} sendMultiple failed: invalid job, missing fields`);
|
||||
}
|
||||
this.logger.log1(`Center-${this.getId()} sending multiple messages: ${JSON.stringify(job)}`);
|
||||
|
||||
let counter = 0;
|
||||
let previousUpdateCounter = 0;
|
||||
|
||||
this.counterUpdateTimer.setInterval(() => {
|
||||
if (previousUpdateCounter !== counter) {
|
||||
this.eventEmitter.emit(this.EVENT.MESSAGE_SEND_COUNTER_UPDATE_EVENT, counter);
|
||||
previousUpdateCounter = counter;
|
||||
}
|
||||
}, '', `${this.MESSAGE_SEND_UPDATE_DELAY / 1000} s`);
|
||||
|
||||
let count = job.count || 1;
|
||||
let interval = 1 / (job.perSecond || 1);
|
||||
this.sendTimer.setInterval(() => {
|
||||
if (count > 0 && counter >= count) {
|
||||
this.cancelSendInterval();
|
||||
} else {
|
||||
this.sendPdu(job.pdu, true);
|
||||
counter++;
|
||||
}
|
||||
}, '', `${interval} s`);
|
||||
resolve();
|
||||
});
|
||||
}
|
||||
|
||||
sendPdu(pdu: object, force?: boolean): Promise<object> {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!force) {
|
||||
this.validateSessions(reject);
|
||||
}
|
||||
this.logger.log5(`Center-${this.getId()} sending PDU: ${JSON.stringify(pdu)}`);
|
||||
this.getNextSession().send(pdu, (replyPdu: any) => {
|
||||
resolve(replyPdu);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
initialize(): void {
|
||||
this.server = smpp.createServer({}, this.eventSessionConnected.bind(this));
|
||||
this.server.listen(this.port);
|
||||
PduProcessor.attachProcessor(this, PduProcessor.getProcessor(DebugPduProcessor.name));
|
||||
this.setStatus(0);
|
||||
}
|
||||
|
||||
close(): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.logger.log1(`Center-${this.getId()} closing active connections`);
|
||||
this.server.close();
|
||||
this.setStatus(0);
|
||||
resolve();
|
||||
});
|
||||
}
|
||||
|
||||
serialize(): object {
|
||||
return {
|
||||
id: this.id,
|
||||
port: this.port,
|
||||
username: this.username,
|
||||
password: this.password,
|
||||
status: this.status,
|
||||
defaultSingleJob: this.defaultSingleJob,
|
||||
defaultMultipleJob: this.defaultMultipleJob,
|
||||
processors: this.pduProcessors.map(p => p.serialize()),
|
||||
};
|
||||
}
|
||||
|
||||
getPort(): number {
|
||||
return this.port;
|
||||
}
|
||||
|
||||
private validateSessions(reject: (reason?: any) => void) {
|
||||
if (this.sessions.length === 0) {
|
||||
reject(`No clients connected`);
|
||||
}
|
||||
}
|
||||
|
||||
private getNextSession(): any {
|
||||
if (this.sessions.length === 0) {
|
||||
return null;
|
||||
}
|
||||
let session = this.sessions[this.nextSession];
|
||||
this.nextSession = (this.nextSession + 1) % this.sessions.length;
|
||||
return session;
|
||||
}
|
||||
|
||||
private eventBindTransceiver(session: any, pdu: any) {
|
||||
this.logger.log1(`Center-${this.getId()} got a bind_transciever 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(`Center-${this.getId()} client connection successful`);
|
||||
session.send(pdu.response());
|
||||
session.resume();
|
||||
this.pendingSessions = this.pendingSessions.filter((s) => s !== session);
|
||||
this.sessions.push(session);
|
||||
this.updateStatus();
|
||||
} else {
|
||||
this.logger.log1(`Center-${this.getId()} client connection failed, invalid credentials (expected: ${this.username}, ${this.password})`);
|
||||
session.send(pdu.response({
|
||||
command_status: smpp.ESME_RBINDFAIL
|
||||
}));
|
||||
this.pendingSessions = this.pendingSessions.filter((s) => s !== session);
|
||||
this.updateStatus();
|
||||
session.close();
|
||||
}
|
||||
}
|
||||
|
||||
private eventSessionConnected(session: any): void {
|
||||
this.logger.log1(`A client connected to center-${this.getId()}`);
|
||||
this.pendingSessions.push(session);
|
||||
session.on('close', this.eventSessionClose.bind(this, session));
|
||||
session.on('error', this.eventSessionError.bind(this, session));
|
||||
session.on('bind_transceiver', this.eventBindTransceiver.bind(this, session));
|
||||
session.on('pdu', this.eventAnyPdu.bind(this, session));
|
||||
this.updateStatus();
|
||||
this.eventEmitter.emit(this.EVENT.STATE_CHANGED, this.serialize());
|
||||
}
|
||||
|
||||
private eventSessionError(session: any): void {
|
||||
this.logger.log1(`A client encountered an error on center-${this.getId()}`);
|
||||
}
|
||||
|
||||
private eventSessionClose(session: any): void {
|
||||
this.logger.log1(`A client disconnected from center-${this.getId()}`);
|
||||
this.sessions = this.sessions.filter((s: any) => s !== session);
|
||||
this.nextSession = 0;
|
||||
this.pendingSessions = this.pendingSessions.filter((s: any) => s !== session);
|
||||
this.updateStatus();
|
||||
}
|
||||
|
||||
private updateStatus(): void {
|
||||
if (this.sessions.length > 0) {
|
||||
this.setStatus(2);
|
||||
} else if (this.pendingSessions.length > 0) {
|
||||
this.setStatus(1);
|
||||
} else {
|
||||
this.setStatus(0);
|
||||
}
|
||||
}
|
||||
}
|
23
src/Center/CenterSessionManager.ts
Normal file
23
src/Center/CenterSessionManager.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import Logger from "../Logger";
|
||||
import {SessionManager} from "../SessionManager";
|
||||
import {SmppSession} from "../SmppSession";
|
||||
import {Center} from "./Center";
|
||||
|
||||
const CENTER_SESSIONS_FILE: string = process.env.CENTER_SESSIONS_FILE || "center_sessions.json";
|
||||
|
||||
export class CenterSessionManager extends SessionManager {
|
||||
StorageFile: string = CENTER_SESSIONS_FILE
|
||||
ManagedSessionClass: any = Center;
|
||||
sessionId: number = 0;
|
||||
sessions: Center[] = [];
|
||||
identifier: string = "center";
|
||||
readonly logger: Logger = new Logger("CenterSessionManager");
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.setup();
|
||||
// super.eventEmitter.on(super.SESSION_ADDED_EVENT, (session: SmppSession) => this.eventEmitter.emit(this.SESSION_ADDED_EVENT, session));
|
||||
}
|
||||
|
||||
comparatorFn: (arg: any, session: SmppSession) => boolean = (arg: any, session: SmppSession) => (session as Center).getPort() === arg;
|
||||
}
|
256
src/Client/Client.ts
Normal file
256
src/Client/Client.ts
Normal file
@@ -0,0 +1,256 @@
|
||||
import {Job} from "../Job/Job";
|
||||
import Logger from "../Logger";
|
||||
import {PduProcessor} from "../PDUProcessor/PduProcessor";
|
||||
import PersistentPromise from "../PersistentPromise";
|
||||
import {SmppSession} from "../SmppSession";
|
||||
|
||||
const NanoTimer = require('nanotimer');
|
||||
const smpp = require("smpp");
|
||||
|
||||
const AUTO_ENQUIRE_LINK_PERIOD: number = Number(process.env.AUTO_ENQUIRE_LINK_PERIOD) || 30000;
|
||||
|
||||
export class Client extends SmppSession {
|
||||
readonly STATUS: string[] = [
|
||||
"NOT CONNECTED",
|
||||
"CONNECTING",
|
||||
"CONNECTED",
|
||||
"BINDING",
|
||||
"BOUND",
|
||||
"BUSY",
|
||||
]
|
||||
|
||||
id: number;
|
||||
username: string;
|
||||
password: string;
|
||||
status: string = this.STATUS[0];
|
||||
url: string;
|
||||
|
||||
pduProcessors: PduProcessor[] = [];
|
||||
defaultSingleJob!: Job;
|
||||
defaultMultipleJob!: Job;
|
||||
readonly logger: Logger;
|
||||
private session?: any;
|
||||
private connectPromise: PersistentPromise | null = null;
|
||||
private bindPromise: PersistentPromise | null = null;
|
||||
private closePromise: PersistentPromise | null = null;
|
||||
// TODO: Implement close promise
|
||||
// Apparently the sessions are not closed on a dime but instead a .close() call causes eventSessionClose
|
||||
|
||||
constructor(id: number, url: string, username: string, password: string) {
|
||||
super();
|
||||
this.id = id;
|
||||
this.username = username;
|
||||
this.password = password;
|
||||
this.url = url;
|
||||
|
||||
this.setDefaultSingleJob(Job.createEmptySingle());
|
||||
this.setDefaultMultipleJob(Job.createEmptyMultiple());
|
||||
|
||||
this.logger = new Logger(`Client-${id}`);
|
||||
}
|
||||
|
||||
doConnect(): PersistentPromise {
|
||||
this.connectPromise = new PersistentPromise((resolve, reject) => {
|
||||
if (this.status !== this.STATUS[0]) {
|
||||
let errorString = `Client-${this.getId()} already connected`;
|
||||
this.logger.log1(errorString);
|
||||
reject(errorString);
|
||||
return;
|
||||
}
|
||||
|
||||
this.logger.log1(`Client-${this.getId()} connecting to ${this.url}`);
|
||||
this.setStatus(1);
|
||||
this.connectSession().then(resolve, ((err: any) => {
|
||||
this.logger.log1(`Client-${this.getId()} connection failed: ${err}`);
|
||||
this.setStatus(0);
|
||||
this.session.close();
|
||||
reject(err);
|
||||
}));
|
||||
});
|
||||
return this.connectPromise;
|
||||
}
|
||||
|
||||
doBind(): PersistentPromise {
|
||||
this.bindPromise = new PersistentPromise((resolve, reject) => {
|
||||
this.validateFields(reject);
|
||||
|
||||
this.session.bind_transceiver({
|
||||
system_id: this.username,
|
||||
password: this.password,
|
||||
}, this.eventBindReply.bind(this));
|
||||
this.setStatus(3);
|
||||
});
|
||||
return this.bindPromise;
|
||||
}
|
||||
|
||||
connectAndBind(): Promise<void> {
|
||||
return this.doConnect().then(this.doBind.bind(this), (error) => {
|
||||
this.logger.log1(`Client-${this.getId()} connectAndBind failed: ${error}`);
|
||||
});
|
||||
}
|
||||
|
||||
serialize(): object {
|
||||
return {
|
||||
id: this.getId(),
|
||||
url: this.url,
|
||||
username: this.username,
|
||||
password: this.password,
|
||||
status: this.status,
|
||||
defaultSingleJob: this.defaultSingleJob,
|
||||
defaultMultipleJob: this.defaultMultipleJob,
|
||||
processors: this.pduProcessors.map(p => p.serialize()),
|
||||
};
|
||||
}
|
||||
|
||||
close(): Promise<void> {
|
||||
this.logger.log1(`Client-${this.getId()} closing connection`);
|
||||
return Promise.resolve(this.session.close());
|
||||
}
|
||||
|
||||
sendPdu(pdu: object, force?: boolean): Promise<object> {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!force) {
|
||||
this.validateSession(reject);
|
||||
this.validateBound(reject);
|
||||
}
|
||||
this.logger.log5(`Client-${this.getId()} sending PDU: ${JSON.stringify(pdu)}`);
|
||||
this.session.send(pdu, (replyPdu: object) => resolve(replyPdu));
|
||||
});
|
||||
}
|
||||
|
||||
sendMultiple(job: Job): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.validateSession(reject);
|
||||
this.validateBound(reject);
|
||||
if (!job.count || !job.perSecond) {
|
||||
reject(`Client-${this.getId()} sendMultiple failed: invalid job, missing fields`);
|
||||
}
|
||||
this.logger.log1(`Client-${this.getId()} sending multiple messages: ${JSON.stringify(job)}`);
|
||||
|
||||
this.setStatus(4);
|
||||
|
||||
let counter = 0;
|
||||
let previousUpdateCounter = 0;
|
||||
|
||||
this.counterUpdateTimer.setInterval(() => {
|
||||
if (previousUpdateCounter !== counter) {
|
||||
this.eventEmitter.emit(this.EVENT.MESSAGE_SEND_COUNTER_UPDATE_EVENT, counter);
|
||||
previousUpdateCounter = counter;
|
||||
}
|
||||
}, '', `${this.MESSAGE_SEND_UPDATE_DELAY / 1000} s`);
|
||||
|
||||
let count = job.count || 1;
|
||||
let interval = 1 / (job.perSecond || 1);
|
||||
this.sendTimer.setInterval(() => {
|
||||
if (count > 0 && counter >= count) {
|
||||
this.cancelSendInterval();
|
||||
} else {
|
||||
this.sendPdu(job.pdu, true)
|
||||
.catch(e => this.logger.log1(`Error sending message: ${e}`));
|
||||
counter++;
|
||||
}
|
||||
}, '', `${interval} s`);
|
||||
resolve();
|
||||
});
|
||||
}
|
||||
|
||||
getUrl(): string {
|
||||
return this.url;
|
||||
}
|
||||
|
||||
private connectSession(): Promise<void> {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
this.validateFields(reject);
|
||||
this.logger.log1(`Client-${this.getId()} connecting to ${this.url}`);
|
||||
|
||||
this.session = smpp.connect({
|
||||
url: this.url, auto_enquire_link_period: AUTO_ENQUIRE_LINK_PERIOD,
|
||||
}, this.eventSessionConnected.bind(this));
|
||||
this.session.on('error', this.eventSessionError.bind(this));
|
||||
this.session.on('close', this.eventSessionClose.bind(this));
|
||||
this.session.on('pdu', this.eventAnyPdu.bind(this));
|
||||
});
|
||||
}
|
||||
|
||||
private eventSessionConnected(): void {
|
||||
this.logger.log1(`Client-${this.getId()} connected to ${this.url}`);
|
||||
this.setStatus(2);
|
||||
if (this.connectPromise) {
|
||||
this.connectPromise.resolve();
|
||||
}
|
||||
}
|
||||
|
||||
private eventSessionError(pdu: any): void {
|
||||
this.logger.log1(`Client-${this.getId()} error on ${this.url}`);
|
||||
this.setStatus(0);
|
||||
this.rejectPromises();
|
||||
}
|
||||
|
||||
private eventSessionClose(): void {
|
||||
this.logger.log1(`Client-${this.getId()} closed on ${this.url}`);
|
||||
this.setStatus(0);
|
||||
this.rejectPromises();
|
||||
}
|
||||
|
||||
private eventBindReply(pdu: any): void {
|
||||
if (pdu.command_status === 0) {
|
||||
this.logger.log1(`Client-${this.getId()} bound to ${this.url}`);
|
||||
this.setStatus(4);
|
||||
if (this.bindPromise) {
|
||||
this.bindPromise.resolve();
|
||||
}
|
||||
} else {
|
||||
this.logger.log1(`Client-${this.getId()} bind failed to ${this.url}`);
|
||||
this.setStatus(2);
|
||||
if (this.bindPromise) {
|
||||
this.bindPromise.reject(pdu);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private rejectPromises(err?: any): void {
|
||||
if (this.connectPromise) {
|
||||
this.connectPromise.reject(err);
|
||||
}
|
||||
if (this.bindPromise) {
|
||||
this.bindPromise.reject(err);
|
||||
}
|
||||
if (this.closePromise) {
|
||||
this.closePromise.resolve();
|
||||
}
|
||||
}
|
||||
|
||||
private validateFields(reject: (reason?: any) => void) {
|
||||
if (!this.url) {
|
||||
let error = `Client-${this.getId()} has no url set`;
|
||||
this.logger.log1(error);
|
||||
reject(error);
|
||||
}
|
||||
if (!this.username) {
|
||||
let error = `Client-${this.getId()} has no username set`;
|
||||
this.logger.log1(error);
|
||||
reject(error);
|
||||
}
|
||||
if (!this.password) {
|
||||
let error = `Client-${this.getId()} has no password set`;
|
||||
this.logger.log1(error);
|
||||
reject(error);
|
||||
}
|
||||
}
|
||||
|
||||
private validateSession(reject: (reason?: any) => void) {
|
||||
if (!this.session) {
|
||||
let errorMessage = `Client-${this.getId()} session is not defined`;
|
||||
this.logger.log1(errorMessage);
|
||||
reject(errorMessage);
|
||||
}
|
||||
}
|
||||
|
||||
private validateBound(reject: (reason?: any) => void) {
|
||||
if (this.status !== this.STATUS[4]) {
|
||||
let errorMessage = `Client-${this.getId()} is not bound`;
|
||||
this.logger.log1(errorMessage);
|
||||
reject(errorMessage);
|
||||
}
|
||||
}
|
||||
}
|
24
src/Client/ClientSessionManager.ts
Normal file
24
src/Client/ClientSessionManager.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import Logger from "../Logger";
|
||||
import {SessionManager} from "../SessionManager";
|
||||
import {SmppSession} from "../SmppSession";
|
||||
import {Client} from "./Client";
|
||||
|
||||
const CLIENT_SESSIONS_FILE: string = process.env.CLIENT_SESSIONS_FILE || "client_sessions.json";
|
||||
|
||||
export default class ClientSessionManager extends SessionManager {
|
||||
StorageFile: string = CLIENT_SESSIONS_FILE;
|
||||
ManagedSessionClass: any = Client;
|
||||
sessionId: number = 0;
|
||||
sessions: Client[] = [];
|
||||
// Identifier is used in websockets to identify the type of session this manager manages
|
||||
identifier: string = "client";
|
||||
readonly logger: Logger = new Logger("ClientSessionManager");
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.setup();
|
||||
// super.eventEmitter.on(super.SESSION_ADDED_EVENT, (session: SmppSession) => this.eventEmitter.emit(this.SESSION_ADDED_EVENT, session));
|
||||
}
|
||||
|
||||
comparatorFn: (arg: any, session: SmppSession) => boolean = (arg: any, session: SmppSession) => (session as Client).getUrl() === arg;
|
||||
}
|
64
src/HttpServer/CenterRequestHandler.ts
Normal file
64
src/HttpServer/CenterRequestHandler.ts
Normal file
@@ -0,0 +1,64 @@
|
||||
import {Center} from "../Center/Center";
|
||||
import {CenterSessionManager} from "../Center/CenterSessionManager";
|
||||
import Logger from "../Logger";
|
||||
import {PduProcessor} from "../PDUProcessor/PduProcessor";
|
||||
import {SessionManager} from "../SessionManager";
|
||||
import {SmppSession} from "../SmppSession";
|
||||
import {RequestHandler} from "./RequestHandler";
|
||||
|
||||
export class CenterRequestHandler extends RequestHandler {
|
||||
sessionManager: CenterSessionManager;
|
||||
logger: Logger = new Logger(this.constructor.name);
|
||||
|
||||
constructor(sessionManager: SessionManager) {
|
||||
super();
|
||||
this.sessionManager = sessionManager as CenterSessionManager;
|
||||
}
|
||||
|
||||
doGetAvailableProcessors(req: any, res: any): void {
|
||||
this.logger.log1("Getting available processors");
|
||||
let processors: PduProcessor[] = PduProcessor.getProcessorsForType(Center.name);
|
||||
res.send(processors.map((processor: any) => processor.serialize()));
|
||||
}
|
||||
|
||||
doGetAppliedProcessors(req: any, res: any): void {
|
||||
this.sessionManager.getSession(req.params.id).then((session: SmppSession) => {
|
||||
let processors: PduProcessor[] = session.pduProcessors;
|
||||
res.send(processors.map((processor: any) => processor.serialize()));
|
||||
}, this.handleSessionNotFound.bind(this, req, res));
|
||||
}
|
||||
|
||||
doAddProcessor(req: any, res: any): void {
|
||||
this.sessionManager.getSession(req.params.id).then((session: SmppSession) => {
|
||||
let processor = PduProcessor.getProcessor(req.body.name);
|
||||
PduProcessor.attachProcessor(session, processor);
|
||||
res.send(session.serialize());
|
||||
}, this.handleSessionNotFound.bind(this, req, res));
|
||||
}
|
||||
|
||||
doRemoveProcessor(req: any, res: any): void {
|
||||
this.sessionManager.getSession(req.params.id).then((session: SmppSession) => {
|
||||
let processor = PduProcessor.getProcessor(req.body.name);
|
||||
PduProcessor.detachProcessor(session, processor);
|
||||
res.send(session.serialize());
|
||||
}, this.handleSessionNotFound.bind(this, req, res));
|
||||
}
|
||||
|
||||
doPost(req: any, res: any): void {
|
||||
this.logger.log1("Creating client session");
|
||||
this.sessionManager.createSession(req.body.port, req.body.username, req.body.password).then((session: SmppSession) => {
|
||||
res.send(session.serialize());
|
||||
}, (err: any) => {
|
||||
this.logger.log1(`Failed to create client session: ${err}`);
|
||||
res.status(500).send();
|
||||
});
|
||||
}
|
||||
|
||||
doConnect(req: any, res: any): void {
|
||||
throw new Error("Method not implemented.");
|
||||
}
|
||||
|
||||
doBind(req: any, res: any): void {
|
||||
throw new Error("Method not implemented.");
|
||||
}
|
||||
}
|
68
src/HttpServer/ClientRequestHandler.ts
Normal file
68
src/HttpServer/ClientRequestHandler.ts
Normal file
@@ -0,0 +1,68 @@
|
||||
import {Client} from "../Client/Client";
|
||||
import ClientSessionManager from "../Client/ClientSessionManager";
|
||||
import Logger from "../Logger";
|
||||
import {SessionManager} from "../SessionManager";
|
||||
import {SmppSession} from "../SmppSession";
|
||||
import {RequestHandler} from "./RequestHandler";
|
||||
|
||||
export default class ClientRequestHandler extends RequestHandler {
|
||||
sessionManager: ClientSessionManager;
|
||||
logger: Logger = new Logger(this.constructor.name);
|
||||
|
||||
constructor(sessionManager: SessionManager) {
|
||||
super();
|
||||
this.sessionManager = sessionManager as ClientSessionManager;
|
||||
}
|
||||
|
||||
doGetAvailableProcessors(req: any, res: any): void {
|
||||
throw new Error("Method not implemented.");
|
||||
}
|
||||
|
||||
doGetAppliedProcessors(req: any, res: any): void {
|
||||
throw new Error("Method not implemented.");
|
||||
}
|
||||
|
||||
doAddProcessor(req: any, res: any): void {
|
||||
throw new Error("Method not implemented.");
|
||||
}
|
||||
|
||||
doRemoveProcessor(req: any, res: any): void {
|
||||
throw new Error("Method not implemented.");
|
||||
}
|
||||
|
||||
doPost(req: any, res: any): void {
|
||||
this.logger.log1("Creating client session");
|
||||
this.sessionManager.createSession(req.body.url, req.body.username, req.body.password).then((session: SmppSession) => {
|
||||
res.send(session.serialize());
|
||||
}, (err: any) => {
|
||||
this.logger.log1(`Failed to create client session: ${err}`);
|
||||
res.status(500).send();
|
||||
});
|
||||
}
|
||||
|
||||
doBind(req: any, res: any): void {
|
||||
this.sessionManager.getSession(req.params.id).then((session: SmppSession) => {
|
||||
this.logger.log1(`Binding client session with ID ${req.params.id}`)
|
||||
let client = session as Client;
|
||||
client.doBind()
|
||||
.then(() => res.send(session.serialize()))
|
||||
.catch(err => res.status(400).send({
|
||||
err: true,
|
||||
msg: err
|
||||
}));
|
||||
}, this.handleSessionNotFound.bind(this, req, res));
|
||||
}
|
||||
|
||||
doConnect(req: any, res: any): void {
|
||||
this.sessionManager.getSession(req.params.id).then((session: SmppSession) => {
|
||||
this.logger.log1(`Connecting client session with ID ${req.params.id}`)
|
||||
let client = session as Client;
|
||||
client.doConnect()
|
||||
.then(() => res.send(session.serialize()))
|
||||
.catch(err => res.status(400).send({
|
||||
err: true,
|
||||
msg: err
|
||||
}));
|
||||
}, this.handleSessionNotFound.bind(this, req, res));
|
||||
}
|
||||
}
|
70
src/HttpServer/HttpServer.ts
Normal file
70
src/HttpServer/HttpServer.ts
Normal file
@@ -0,0 +1,70 @@
|
||||
import Logger from "../Logger";
|
||||
import {SessionManager} from "../SessionManager";
|
||||
import {CenterRequestHandler} from "./CenterRequestHandler";
|
||||
import ClientRequestHandler from "./ClientRequestHandler";
|
||||
import {RequestHandler} from "./RequestHandler";
|
||||
|
||||
const express = require("express");
|
||||
const bodyParser = require("body-parser");
|
||||
|
||||
const SERVER_PORT: number = Number(process.env.SERVER_PORT) || 8190;
|
||||
|
||||
export class HttpServer {
|
||||
private clientRequestHandler: RequestHandler;
|
||||
private centerRequestHandler: RequestHandler;
|
||||
|
||||
private app: any;
|
||||
private server: any;
|
||||
private readonly logger: Logger = new Logger(this.constructor.name);
|
||||
|
||||
constructor(clientManager: SessionManager, centerManager: SessionManager) {
|
||||
this.clientRequestHandler = new ClientRequestHandler(clientManager);
|
||||
this.centerRequestHandler = new CenterRequestHandler(centerManager);
|
||||
|
||||
this.app = express();
|
||||
this.app.use(bodyParser.json());
|
||||
|
||||
this.app.get('/api/client', this.clientRequestHandler.doGet.bind(this.clientRequestHandler));
|
||||
this.app.post('/api/client', this.clientRequestHandler.doPost.bind(this.clientRequestHandler));
|
||||
this.app.get('/api/client/:id', this.clientRequestHandler.doGetById.bind(this.clientRequestHandler));
|
||||
this.app.patch('/api/client/:id', this.clientRequestHandler.doPatch.bind(this.clientRequestHandler));
|
||||
this.app.put('/api/client/:id/send', this.clientRequestHandler.doConfigureSingleJob.bind(this.clientRequestHandler));
|
||||
this.app.post('/api/client/:id/send/default', this.clientRequestHandler.doSendSingleJob.bind(this.clientRequestHandler));
|
||||
this.app.post('/api/client/:id/send', this.clientRequestHandler.doSend.bind(this.clientRequestHandler));
|
||||
this.app.put('/api/client/:id/sendMany', this.clientRequestHandler.doConfigureManyJob.bind(this.clientRequestHandler));
|
||||
this.app.post('/api/client/:id/sendMany/default', this.clientRequestHandler.doSendManyJob.bind(this.clientRequestHandler));
|
||||
this.app.post('/api/client/:id/sendMany', this.clientRequestHandler.doSendMany.bind(this.clientRequestHandler));
|
||||
this.app.delete('/api/client/:id/sendMany', this.clientRequestHandler.doCancelSendMany.bind(this.clientRequestHandler));
|
||||
this.app.post('/api/client/:id/bind', this.clientRequestHandler.doBind.bind(this.clientRequestHandler));
|
||||
this.app.post('/api/client/:id/connect', this.clientRequestHandler.doConnect.bind(this.clientRequestHandler));
|
||||
this.app.delete('/api/client/:id/connect', this.clientRequestHandler.doDisconnect.bind(this.clientRequestHandler));
|
||||
this.app.delete('/api/client/:id', this.clientRequestHandler.doDelete.bind(this.clientRequestHandler));
|
||||
|
||||
this.app.get('/api/center', this.centerRequestHandler.doGet.bind(this.centerRequestHandler));
|
||||
this.app.post('/api/center', this.centerRequestHandler.doPost.bind(this.centerRequestHandler));
|
||||
this.app.get('/api/center/:id', this.centerRequestHandler.doGetById.bind(this.centerRequestHandler));
|
||||
this.app.patch('/api/center/:id', this.centerRequestHandler.doPatch.bind(this.centerRequestHandler));
|
||||
this.app.put('/api/center/:id/send', this.centerRequestHandler.doConfigureSingleJob.bind(this.centerRequestHandler));
|
||||
this.app.post('/api/center/:id/send/default', this.centerRequestHandler.doSendSingleJob.bind(this.centerRequestHandler));
|
||||
this.app.post('/api/center/:id/send', this.centerRequestHandler.doSend.bind(this.centerRequestHandler));
|
||||
this.app.put('/api/center/:id/sendMany', this.centerRequestHandler.doConfigureManyJob.bind(this.centerRequestHandler));
|
||||
this.app.post('/api/center/:id/sendMany/default', this.centerRequestHandler.doSendManyJob.bind(this.centerRequestHandler));
|
||||
this.app.post('/api/center/:id/sendMany', this.centerRequestHandler.doSendMany.bind(this.centerRequestHandler));
|
||||
this.app.delete('/api/center/:id/sendMany', this.centerRequestHandler.doCancelSendMany.bind(this.centerRequestHandler));
|
||||
this.app.delete('/api/center/:id/connect', this.centerRequestHandler.doDisconnect.bind(this.centerRequestHandler));
|
||||
this.app.delete('/api/center/:id', this.centerRequestHandler.doDelete.bind(this.centerRequestHandler));
|
||||
this.app.get('/api/center/processors', this.centerRequestHandler.doGetAppliedProcessors.bind(this.centerRequestHandler));
|
||||
this.app.get('/api/center/processors/all', this.centerRequestHandler.doGetAvailableProcessors.bind(this.centerRequestHandler));
|
||||
this.app.post('/api/center/processors', this.centerRequestHandler.doAddProcessor.bind(this.centerRequestHandler));
|
||||
this.app.delete('/api/center/processors', this.centerRequestHandler.doRemoveProcessor.bind(this.centerRequestHandler));
|
||||
|
||||
this.app.get('/api/ping', function (req: any, res: any) {
|
||||
res.send('pong');
|
||||
});
|
||||
|
||||
this.server = this.app.listen(SERVER_PORT, function () {
|
||||
// @ts-ignore
|
||||
this.logger.log1(`HTTPServer listening at http://localhost:${SERVER_PORT}`)
|
||||
}.bind(this));
|
||||
}
|
||||
}
|
177
src/HttpServer/RequestHandler.ts
Normal file
177
src/HttpServer/RequestHandler.ts
Normal file
@@ -0,0 +1,177 @@
|
||||
import {Job} from "../Job/Job";
|
||||
import Logger from "../Logger";
|
||||
import {SessionManager} from "../SessionManager";
|
||||
import {SmppSession} from "../SmppSession";
|
||||
|
||||
export abstract class RequestHandler {
|
||||
abstract sessionManager: SessionManager;
|
||||
logger: Logger = new Logger(this.constructor.name);
|
||||
|
||||
doGet(req: any, res: any): void {
|
||||
this.logger.log1("Getting client sessions");
|
||||
res.send(this.sessionManager.serialize());
|
||||
}
|
||||
|
||||
doGetById(req: any, res: any): void {
|
||||
this.sessionManager.getSession(req.params.id).then((session: SmppSession) => {
|
||||
this.logger.log1(`Client session found with ID ${req.params.id}`)
|
||||
res.send(session.serialize());
|
||||
}, this.handleSessionNotFound.bind(this, req, res));
|
||||
}
|
||||
|
||||
doPatch(req: any, res: any): void {
|
||||
this.sessionManager.getSession(req.params.id).then((session: SmppSession) => {
|
||||
this.logger.log1(`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());
|
||||
}, this.handleSessionNotFound.bind(this, req, res));
|
||||
}
|
||||
|
||||
doConfigureSingleJob(req: any, res: any): void {
|
||||
this.sessionManager.getSession(req.params.id).then((session: SmppSession) => {
|
||||
let job: Job = session.getDefaultSingleJob();
|
||||
if (job.pdu.source_addr && job.pdu.source_addr !== req.body.source) {
|
||||
job.pdu.source_addr = req.body.source;
|
||||
}
|
||||
if (job.pdu.destination_addr && job.pdu.destination_addr !== req.body.destination) {
|
||||
job.pdu.destination_addr = req.body.destination;
|
||||
}
|
||||
if (job.pdu.short_message && job.pdu.short_message !== req.body.message) {
|
||||
job.pdu.short_message = req.body.message;
|
||||
}
|
||||
this.logger.log1(`Updating default job on session with ID ${req.params.id}`)
|
||||
}, this.handleSessionNotFound.bind(this, req, res));
|
||||
}
|
||||
|
||||
doSendSingleJob(req: any, res: any): void {
|
||||
this.sessionManager.getSession(req.params.id).then((session: SmppSession) => {
|
||||
this.logger.log1(`Sending pre-configured message on session with ID ${req.params.id}`)
|
||||
session.sendSingleDefault()
|
||||
.then(pdu => res.send(pdu),
|
||||
err => res.status(400).send({
|
||||
err: true,
|
||||
message: err
|
||||
}));
|
||||
}, this.handleSessionNotFound.bind(this, req, res));
|
||||
}
|
||||
|
||||
doSend(req: any, res: any): void {
|
||||
this.sessionManager.getSession(req.params.id).then((session: SmppSession) => {
|
||||
this.logger.log1(`Sending message on session with ID ${req.params.id}`)
|
||||
let tempJob: Job = JSON.parse(JSON.stringify(session.defaultSingleJob));
|
||||
tempJob.pdu.source_addr = req.body.source;
|
||||
tempJob.pdu.destination_addr = req.body.destination;
|
||||
tempJob.pdu.short_message = req.body.message;
|
||||
session.sendSingle(tempJob)
|
||||
.then(pdu => res.send(pdu),
|
||||
err => res.status(400).send({
|
||||
err: true,
|
||||
message: err
|
||||
}));
|
||||
}, this.handleSessionNotFound.bind(this, req, res));
|
||||
}
|
||||
|
||||
doConfigureManyJob(req: any, res: any): void {
|
||||
this.sessionManager.getSession(req.params.id).then((session: SmppSession) => {
|
||||
let perSecond: number = 1 / (req.body.interval / 1000)
|
||||
let job: Job = session.getDefaultMultipleJob();
|
||||
if (!job.pdu.source_addr && job.pdu.source_addr !== req.body.source) {
|
||||
job.pdu.source_addr = req.body.source;
|
||||
}
|
||||
if (!job.pdu.destination_addr && job.pdu.destination_addr !== req.body.destination) {
|
||||
job.pdu.destination_addr = req.body.destination;
|
||||
}
|
||||
if (!job.pdu.short_message && job.pdu.short_message !== req.body.message) {
|
||||
job.pdu.short_message = req.body.message;
|
||||
}
|
||||
if (!job.perSecond && job.perSecond !== perSecond) {
|
||||
job.perSecond = perSecond;
|
||||
}
|
||||
if (!job.count && job.count !== req.body.count) {
|
||||
job.count = req.body.count;
|
||||
}
|
||||
this.logger.log1(`Updating default job on session with ID ${req.params.id}`)
|
||||
res.send(session.serialize());
|
||||
}, this.handleSessionNotFound.bind(this, req, res));
|
||||
}
|
||||
|
||||
doSendManyJob(req: any, res: any): void {
|
||||
this.sessionManager.getSession(req.params.id).then((session: SmppSession) => {
|
||||
this.logger.log1(`Sending pre-configured messages on session with ID ${req.params.id}`)
|
||||
session.sendMultipleDefault()
|
||||
.then(() => res.send({}),
|
||||
err => res.status(400).send({
|
||||
err: true,
|
||||
message: err
|
||||
}));
|
||||
}, this.handleSessionNotFound.bind(this, req, res));
|
||||
}
|
||||
|
||||
doSendMany(req: any, res: any) {
|
||||
this.sessionManager.getSession(req.params.id).then((session: SmppSession) => {
|
||||
this.logger.log1(`Sending message on session with ID ${req.params.id}`)
|
||||
let tempJob: Job = JSON.parse(JSON.stringify(session.defaultMultipleJob));
|
||||
tempJob.pdu.source_addr = req.body.source;
|
||||
tempJob.pdu.destination_addr = req.body.destination;
|
||||
tempJob.pdu.short_message = req.body.message;
|
||||
tempJob.perSecond = 1 / (req.body.interval / 1000);
|
||||
tempJob.count = req.body.count;
|
||||
session.sendMultiple(tempJob)
|
||||
.then(pdu => res.send(pdu),
|
||||
err => res.status(400).send({
|
||||
err: true,
|
||||
message: err
|
||||
}));
|
||||
}, this.handleSessionNotFound.bind(this, req, res));
|
||||
}
|
||||
|
||||
doCancelSendMany(req: any, res: any) {
|
||||
this.sessionManager.getSession(req.params.id).then((session: SmppSession) => {
|
||||
this.logger.log1(`Cancelling send timer for session with ID ${req.params.id}`);
|
||||
session.cancelSendInterval();
|
||||
}, this.handleSessionNotFound.bind(this, req, res));
|
||||
}
|
||||
|
||||
doDisconnect(req: any, res: any) {
|
||||
this.sessionManager.getSession(req.params.id).then((session: SmppSession) => {
|
||||
this.logger.log1(`Disconnecting session with ID ${req.params.id}`)
|
||||
session.close().then(() => res.send(session.serialize()), err => res.status(400).send({
|
||||
err: true,
|
||||
message: err
|
||||
}));
|
||||
}, this.handleSessionNotFound.bind(this, req, res));
|
||||
}
|
||||
|
||||
doDelete(req: any, res: any) {
|
||||
this.sessionManager.getSession(req.params.id).then((session: SmppSession) => {
|
||||
this.sessionManager.removeSession(session);
|
||||
}, this.handleSessionNotFound.bind(this, req, res));
|
||||
}
|
||||
|
||||
abstract doPost(req: any, res: any): void;
|
||||
|
||||
abstract doConnect(req: any, res: any): void;
|
||||
|
||||
abstract doBind(req: any, res: any): void;
|
||||
|
||||
abstract doGetAvailableProcessors(req: any, res: any): void;
|
||||
|
||||
abstract doGetAppliedProcessors(req: any, res: any): void;
|
||||
|
||||
abstract doAddProcessor(req: any, res: any): void;
|
||||
|
||||
abstract doRemoveProcessor(req: any, res: any): void;
|
||||
handleSessionNotFound(req: any, res: any): void {
|
||||
let error = `No session found with ID ${req.params.id}`;
|
||||
this.logger.log1(error);
|
||||
res.status(404).send({
|
||||
err: true,
|
||||
message: error
|
||||
});
|
||||
}
|
||||
}
|
71
src/Job/Job.ts
Normal file
71
src/Job/Job.ts
Normal file
@@ -0,0 +1,71 @@
|
||||
import EventEmitter from "events";
|
||||
|
||||
const smpp = require("smpp");
|
||||
|
||||
export class Job {
|
||||
static readonly STATE_CHANGED: string = "STATE_CHANGED";
|
||||
private eventEmitter: EventEmitter = new EventEmitter();
|
||||
|
||||
constructor(pdu: any, perSecond?: number, count?: number) {
|
||||
this._pdu = pdu;
|
||||
this._perSecond = perSecond;
|
||||
this._count = count;
|
||||
}
|
||||
|
||||
private _pdu: any;
|
||||
|
||||
get pdu(): any {
|
||||
return this._pdu;
|
||||
}
|
||||
|
||||
set pdu(value: any) {
|
||||
this._pdu = value;
|
||||
this.eventEmitter.emit(Job.STATE_CHANGED, {});
|
||||
}
|
||||
|
||||
private _perSecond?: number;
|
||||
|
||||
get perSecond(): number {
|
||||
return <number>this._perSecond;
|
||||
}
|
||||
|
||||
set perSecond(value: number) {
|
||||
this._perSecond = value;
|
||||
this.eventEmitter.emit(Job.STATE_CHANGED, {});
|
||||
}
|
||||
|
||||
private _count?: number;
|
||||
|
||||
get count(): number {
|
||||
return <number>this._count;
|
||||
}
|
||||
|
||||
set count(value: number) {
|
||||
this._count = value;
|
||||
this.eventEmitter.emit(Job.STATE_CHANGED, {});
|
||||
}
|
||||
|
||||
static deserialize(serialized: any): Job {
|
||||
if (!serialized._pdu) {
|
||||
return Job.createEmptyMultiple();
|
||||
}
|
||||
let pdu: any = new smpp.PDU(serialized._pdu.command, serialized._pdu);
|
||||
return new Job(pdu, serialized._perSecond, serialized._count);
|
||||
}
|
||||
|
||||
static createEmptySingle(): Job {
|
||||
return new Job({});
|
||||
}
|
||||
|
||||
static createEmptyMultiple(): Job {
|
||||
return new Job({}, 1, 1);
|
||||
}
|
||||
|
||||
serialize(): string {
|
||||
return JSON.stringify(this);
|
||||
}
|
||||
|
||||
on(event: string, callback: (...args: any[]) => void): void {
|
||||
this.eventEmitter.on(event, callback);
|
||||
}
|
||||
}
|
71
src/Logger.ts
Normal file
71
src/Logger.ts
Normal file
@@ -0,0 +1,71 @@
|
||||
// @ts-ignore
|
||||
import {WriteStream} from "fs";
|
||||
|
||||
const fs = require('fs');
|
||||
|
||||
// @ts-ignore
|
||||
const LOG_LEVEL: number = process.env.LOG_LEVEL || 6;
|
||||
// @ts-ignore
|
||||
const LOG_FILE: string = process.env.LOG_FILE || "";
|
||||
|
||||
export default class Logger {
|
||||
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);
|
||||
private clazz: string;
|
||||
private readonly logLevel: number;
|
||||
private readonly logFile: string;
|
||||
private readonly logFileWriteStream: WriteStream | null = null;
|
||||
|
||||
constructor(clazz: string) {
|
||||
this.clazz = clazz;
|
||||
this.logLevel = LOG_LEVEL;
|
||||
this.logFile = LOG_FILE;
|
||||
|
||||
if (!!this.logFile) {
|
||||
this.logFileWriteStream = fs.createWriteStream(this.logFile, {flags: 'a'});
|
||||
}
|
||||
}
|
||||
|
||||
leftPad(str: string, len: number, char: string): string {
|
||||
str = String(str);
|
||||
let i: number = -1;
|
||||
len = len - str.length;
|
||||
if (char === undefined) {
|
||||
char = " ";
|
||||
}
|
||||
while (++i < len) {
|
||||
str = char + str;
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
log(logLevel: number, data: any): void {
|
||||
if (typeof data === "object") {
|
||||
data = JSON.stringify(data);
|
||||
}
|
||||
let date = new Date();
|
||||
|
||||
let year: string = this.leftPad(String(date.getFullYear()), 4, '0');
|
||||
let month: string = this.leftPad(String(date.getMonth() + 1), 2, '0');
|
||||
let day: string = this.leftPad(String(date.getDate()), 2, '0');
|
||||
|
||||
let hours: string = this.leftPad(String(date.getHours()), 2, '0');
|
||||
let minutes: string = this.leftPad(String(date.getMinutes()), 2, '0');
|
||||
let seconds: string = this.leftPad(String(date.getSeconds()), 2, '0');
|
||||
let milliseconds: string = this.leftPad(String(date.getMilliseconds()), 3, '0');
|
||||
|
||||
let datePrefix: string = `[${day}/${month}/${year}-${hours}:${minutes}:${seconds}:${milliseconds}]`
|
||||
|
||||
let out: string = datePrefix.padEnd(30, ' ') + `[${this.clazz}]`.padEnd(28, ' ') + `(${logLevel})`.padEnd(8, ' ') + data;
|
||||
if (logLevel <= this.logLevel) {
|
||||
console.log(out);
|
||||
}
|
||||
if (!!this.logFileWriteStream) {
|
||||
this.logFileWriteStream.write(out + "\n");
|
||||
}
|
||||
}
|
||||
}
|
14
src/PDUProcessor/DebugPduProcessor.ts
Normal file
14
src/PDUProcessor/DebugPduProcessor.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import {Center} from "../Center/Center";
|
||||
import {PduProcessor} from "./PduProcessor";
|
||||
|
||||
export class DebugPduProcessor extends PduProcessor {
|
||||
servesSessionType: string = Center.name;
|
||||
|
||||
processPdu(session: any, pdu: any, ...args: any[]): Promise<any> {
|
||||
return new Promise<any>((resolve, reject) => {
|
||||
session.send(pdu.response(), (replyPdu: any) => {
|
||||
resolve(replyPdu);
|
||||
});
|
||||
})
|
||||
}
|
||||
}
|
25
src/PDUProcessor/EchoPduProcessor.ts
Normal file
25
src/PDUProcessor/EchoPduProcessor.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import {Center} from "../Center/Center";
|
||||
import {PduProcessor} from "./PduProcessor";
|
||||
const smpp = require("smpp");
|
||||
|
||||
export class EchoPduProcessor extends PduProcessor {
|
||||
servesSessionType: string = Center.name;
|
||||
processPdu(session: any, pdu: any, ...args: any[]): Promise<any> {
|
||||
return new Promise<any>((resolve, reject) => {
|
||||
let promises = [];
|
||||
let replyPromise = session.send(pdu.response());
|
||||
let sendPromise = session.send(new smpp.PDU('deliver_sm', {
|
||||
source_addr: pdu.destination_addr,
|
||||
destination_addr: pdu.source_addr,
|
||||
short_message: pdu.short_message
|
||||
}));
|
||||
promises.push(replyPromise);
|
||||
promises.push(sendPromise);
|
||||
Promise.all(promises).then((replyPdus: any) => {
|
||||
resolve(replyPdus);
|
||||
}).catch((error: any) => {
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
57
src/PDUProcessor/PduProcessor.ts
Normal file
57
src/PDUProcessor/PduProcessor.ts
Normal file
@@ -0,0 +1,57 @@
|
||||
import Logger from "../Logger";
|
||||
import {SmppSession} from "../SmppSession";
|
||||
import {DebugPduProcessor} from "./DebugPduProcessor";
|
||||
|
||||
export abstract class PduProcessor {
|
||||
static processors: PduProcessor[] = [];
|
||||
abstract readonly servesSessionType: string;
|
||||
readonly name: string = this.constructor.name;
|
||||
readonly logger: Logger = new Logger(`PduProcessor: ${this.name}`);
|
||||
private static logger: Logger = new Logger("PduProcessor");
|
||||
|
||||
static getProcessor(name: string): PduProcessor {
|
||||
this.logger.log1(`Looking for processor with name ${name}...`);
|
||||
let pduProcessor = this.processors.find((processor: any) => processor.name === name);
|
||||
if (pduProcessor) {
|
||||
this.logger.log1(`Found processor with name ${name}`);
|
||||
return pduProcessor;
|
||||
} else {
|
||||
this.logger.log1(`Processor with name ${name} not found`);
|
||||
return this.processors[0];
|
||||
}
|
||||
}
|
||||
|
||||
static attachProcessor(session: SmppSession, processor: PduProcessor): void {
|
||||
this.logger.log1(`Trying to attach processor ${processor.name} to session ${session.constructor.name}-${session.getId()}`);
|
||||
if (PduProcessor.areCompatible(session, processor)) {
|
||||
session.addPduProcessor(processor);
|
||||
}
|
||||
}
|
||||
|
||||
static detachProcessor(session: SmppSession, processor: PduProcessor): void {
|
||||
this.logger.log1(`Trying to detach processor ${processor.name} from session ${session.constructor.name}-${session.getId()}`);
|
||||
session.removePduProcessor(processor);
|
||||
}
|
||||
|
||||
static areCompatible(session: SmppSession, processor: PduProcessor): boolean {
|
||||
this.logger.log1(`Checking compatibility between session ${session.constructor.name}-${session.getId()} and processor ${processor.name}`);
|
||||
return session.constructor.name === processor.servesSessionType;
|
||||
}
|
||||
|
||||
static addProcessor(processor: any): void {
|
||||
PduProcessor.processors.push(new processor());
|
||||
}
|
||||
|
||||
static getProcessorsForType(type: string): PduProcessor[] {
|
||||
return this.processors.filter((processor: any) => processor.servesSessionType === type);
|
||||
}
|
||||
|
||||
abstract processPdu(session: any, pdu: any, ...args: any[]): Promise<any>;
|
||||
|
||||
serialize(): object {
|
||||
return {
|
||||
servesSessionType: this.servesSessionType,
|
||||
name: this.name
|
||||
};
|
||||
}
|
||||
}
|
33
src/PersistentPromise.ts
Normal file
33
src/PersistentPromise.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
export default class PersistentPromise {
|
||||
private readonly promise: Promise<any>;
|
||||
private promiseResolve: ((value?: any) => void) | undefined;
|
||||
private promiseReject: ((reason?: any) => void) | undefined;
|
||||
|
||||
constructor(callback: (resolve: (value?: any) => void, reject: (reason?: any) => void) => void) {
|
||||
this.promise = new Promise((resolve, reject) => {
|
||||
callback(resolve, reject);
|
||||
this.promiseResolve = resolve;
|
||||
this.promiseReject = reject;
|
||||
});
|
||||
}
|
||||
|
||||
getPromise(): Promise<any> {
|
||||
return this.promise;
|
||||
}
|
||||
|
||||
resolve(value?: any): void {
|
||||
if (this.promiseResolve) {
|
||||
this.promiseResolve(value);
|
||||
}
|
||||
}
|
||||
|
||||
reject(reason?: any): void {
|
||||
if (this.promiseReject) {
|
||||
this.promiseReject(reason);
|
||||
}
|
||||
}
|
||||
|
||||
then(onfulfilled?: ((value: any) => any) | undefined | null, onrejected?: ((reason: any) => any) | undefined | null): Promise<any> {
|
||||
return this.promise.then(onfulfilled, onrejected);
|
||||
}
|
||||
}
|
129
src/SessionManager.ts
Normal file
129
src/SessionManager.ts
Normal file
@@ -0,0 +1,129 @@
|
||||
import EventEmitter from "events";
|
||||
import fs from "fs";
|
||||
import {Job} from "./Job/Job";
|
||||
import Logger from "./Logger";
|
||||
import {SmppSession} from "./SmppSession";
|
||||
|
||||
export abstract class SessionManager {
|
||||
// I could've done this by passing these abstract properties to the constructor, but I wanted to have the possibility
|
||||
// of implementing additional methods
|
||||
abstract sessions: SmppSession[];
|
||||
abstract sessionId: number;
|
||||
abstract comparatorFn: (arg: any, session: SmppSession) => boolean;
|
||||
readonly abstract identifier: string;
|
||||
readonly abstract ManagedSessionClass: any;
|
||||
readonly abstract StorageFile: string;
|
||||
|
||||
readonly SESSION_ADDED_EVENT: string = "SESSION ADDED";
|
||||
readonly logger: Logger = new Logger("SessionManager");
|
||||
readonly eventEmitter: EventEmitter = new EventEmitter();
|
||||
|
||||
addSession(session: SmppSession): Promise<void> {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
this.logger.log1(`Adding session with id ${session.getId()}`);
|
||||
this.sessions.push(session);
|
||||
this.eventEmitter.emit(this.SESSION_ADDED_EVENT, session.getId());
|
||||
resolve();
|
||||
});
|
||||
}
|
||||
|
||||
on(event: string, listener: (...args: any[]) => void): void {
|
||||
this.eventEmitter.on(event, listener);
|
||||
}
|
||||
|
||||
getSessions(): Promise<SmppSession[]> {
|
||||
return Promise.resolve(this.sessions);
|
||||
}
|
||||
|
||||
removeSession(session: SmppSession): Promise<void> {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
this.logger.log1(`Removing session with id ${session.getId()}`);
|
||||
this.sessions = this.sessions.filter(s => s.getId() !== session.getId());
|
||||
resolve();
|
||||
});
|
||||
}
|
||||
|
||||
getSession(id: number): Promise<SmppSession> {
|
||||
return new Promise<SmppSession>((resolve, reject) => {
|
||||
this.logger.log1(`Looking for session with id ${id}...`);
|
||||
let session: SmppSession | undefined = this.sessions.find(s => s.getId() === id);
|
||||
if (session) {
|
||||
this.logger.log1(`Found session with id ${id}`);
|
||||
resolve(session);
|
||||
} else {
|
||||
this.logger.log1(`Session with id ${id} not found`);
|
||||
reject(`Session with id ${id} not found`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
serialize(): object {
|
||||
this.logger.log1(`Serializing ${this.sessions.length} clients`);
|
||||
return this.sessions.map((session: SmppSession) => {
|
||||
return session.serialize();
|
||||
});
|
||||
}
|
||||
|
||||
createSession(arg: any, username: string, password: string): Promise<SmppSession> {
|
||||
return new Promise<SmppSession>((resolve, reject) => {
|
||||
this.logger.log1(`Creating session of type ${this.ManagedSessionClass.name} with arg ${arg}`);
|
||||
this.getExisting(arg).then((s: SmppSession) => {
|
||||
resolve(s);
|
||||
}, err => {
|
||||
});
|
||||
this.verifyField(arg, reject);
|
||||
this.verifyField(username, reject);
|
||||
this.verifyField(password, reject);
|
||||
|
||||
let session = new this.ManagedSessionClass(this.sessionId++, arg, username, password);
|
||||
this.addSession(session).then(() => {
|
||||
resolve(session);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
verifyField(field: string, reject: (reason?: any) => void) {
|
||||
if (!field) {
|
||||
let error = `Request to make a new client failed because of missing ${field}.`;
|
||||
this.logger.log1(error);
|
||||
reject(error);
|
||||
}
|
||||
}
|
||||
|
||||
setup(): void {
|
||||
try {
|
||||
this.logger.log1(`Loading ${this.ManagedSessionClass.name} from ${this.StorageFile}`)
|
||||
let sessions: Buffer = fs.readFileSync(this.StorageFile);
|
||||
let loadedSessions: any[] = JSON.parse(String(sessions));
|
||||
this.logger.log1(`Loaded ${loadedSessions.length} clients from ${this.StorageFile}`);
|
||||
loadedSessions.forEach(session => {
|
||||
this.createSession(session.url, session.username, session.password).then((sessionObj: SmppSession) => {
|
||||
sessionObj.setDefaultSingleJob(Job.deserialize(session.defaultSingleJob));
|
||||
sessionObj.setDefaultMultipleJob(Job.deserialize(session.defaultMultipleJob));
|
||||
});
|
||||
});
|
||||
} catch (e) {
|
||||
this.logger.log1(`Error loading centers from ${this.StorageFile}: ${e}`);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
cleanup(): void {
|
||||
this.logger.log1(`Saving centers to ${this.StorageFile}...`);
|
||||
fs.writeFileSync(this.StorageFile, JSON.stringify(this.serialize(), null, 4));
|
||||
}
|
||||
|
||||
getExisting(arg: any): Promise<SmppSession> {
|
||||
return new Promise<SmppSession>((resolve, reject) => {
|
||||
this.logger.log1(`Looking for session with arg ${arg}...`);
|
||||
let session: SmppSession | undefined = this.sessions.find(this.comparatorFn.bind(this, arg));
|
||||
if (session) {
|
||||
this.logger.log1(`Found session with arg ${arg}`);
|
||||
resolve(session);
|
||||
} else {
|
||||
this.logger.log1(`Session with arg ${arg} not found`);
|
||||
reject(`Session with arg ${arg} not found`);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
180
src/SmppSession.ts
Normal file
180
src/SmppSession.ts
Normal file
@@ -0,0 +1,180 @@
|
||||
import EventEmitter from "events";
|
||||
import {Job} from "./Job/Job";
|
||||
import Logger from "./Logger";
|
||||
import {PduProcessor} from "./PDUProcessor/PduProcessor";
|
||||
|
||||
const NanoTimer = require("nanotimer");
|
||||
const smpp = require("smpp");
|
||||
|
||||
export abstract class SmppSession {
|
||||
readonly EVENT: any = {
|
||||
STATUS_CHANGED: "STATUS_CHANGED",
|
||||
STATE_CHANGED: "STATE_CHANGED",
|
||||
ANY_PDU: "ANY_PDU",
|
||||
MESSAGE_SEND_COUNTER_UPDATE_EVENT: "MESSAGE_SEND_COUNTER_UPDATE_EVENT",
|
||||
};
|
||||
abstract STATUS: string[];
|
||||
|
||||
abstract id: number;
|
||||
abstract username: string;
|
||||
abstract password: string;
|
||||
abstract status: string;
|
||||
abstract pduProcessors: PduProcessor[];
|
||||
|
||||
abstract defaultSingleJob: Job;
|
||||
abstract defaultMultipleJob: Job;
|
||||
|
||||
readonly UPDATE_WS: string = "UPDATE_WS";
|
||||
readonly eventEmitter: EventEmitter = new EventEmitter();
|
||||
readonly logger: Logger = new Logger(`SmppSession`);
|
||||
readonly sendTimer: any = new NanoTimer();
|
||||
readonly counterUpdateTimer: any = new NanoTimer();
|
||||
readonly MESSAGE_SEND_UPDATE_DELAY: number = Number(process.env.MESSAGE_SEND_UPDATE_DELAY) || 500;
|
||||
|
||||
constructor() {
|
||||
this.eventEmitter.on(this.EVENT.STATE_CHANGED, () => this.updateWs(this.EVENT.STATE_CHANGED));
|
||||
this.eventEmitter.on(this.EVENT.STATUS_CHANGED, () => this.updateWs(this.EVENT.STATUS_CHANGED));
|
||||
this.eventEmitter.on(this.EVENT.ANY_PDU, (pdu: any) => this.updateWs(this.EVENT.ANY_PDU, [pdu]));
|
||||
this.eventEmitter.on(this.EVENT.MESSAGE_SEND_COUNTER_UPDATE_EVENT, (count: number) => this.updateWs(this.EVENT.MESSAGE_SEND_COUNTER_UPDATE_EVENT, [count]));
|
||||
}
|
||||
|
||||
abstract sendPdu(pdu: object, force?: boolean): Promise<object>;
|
||||
|
||||
sendSingle(job: Job): Promise<object> {
|
||||
return this.sendPdu(job.pdu);
|
||||
}
|
||||
|
||||
sendSingleDefault(): Promise<object> {
|
||||
return this.sendSingle(this.defaultSingleJob);
|
||||
}
|
||||
|
||||
abstract sendMultiple(job: Job): Promise<void>;
|
||||
|
||||
sendMultipleDefault(): Promise<void> {
|
||||
return this.sendMultiple(this.defaultMultipleJob);
|
||||
}
|
||||
|
||||
cancelSendInterval(): void {
|
||||
this.sendTimer.clearInterval();
|
||||
this.counterUpdateTimer.clearInterval();
|
||||
}
|
||||
|
||||
abstract close(): Promise<void>;
|
||||
|
||||
abstract serialize(): object;
|
||||
|
||||
on(event: string, callback: (...args: any[]) => void): void {
|
||||
this.eventEmitter.on(event, callback);
|
||||
}
|
||||
|
||||
updateWs(event: string, args?: any[]): void {
|
||||
this.logger.log1(`Update WS: ${event}`);
|
||||
let message: {
|
||||
type: string,
|
||||
data?: string
|
||||
} = {
|
||||
type: event,
|
||||
};
|
||||
switch (event) {
|
||||
case this.EVENT.STATE_CHANGED:
|
||||
message.data = JSON.stringify(this.serialize());
|
||||
break;
|
||||
case this.EVENT.STATUS_CHANGED:
|
||||
message.data = JSON.stringify(this.status);
|
||||
break;
|
||||
case this.EVENT.ANY_PDU:
|
||||
message.data = JSON.stringify(args![0]);
|
||||
break;
|
||||
case this.EVENT.MESSAGE_SEND_COUNTER_UPDATE_EVENT:
|
||||
message.data = JSON.stringify(args![0]);
|
||||
break;
|
||||
}
|
||||
this.eventEmitter.emit(this.UPDATE_WS, message);
|
||||
}
|
||||
|
||||
getDefaultSingleJob(): Job {
|
||||
return this.defaultSingleJob;
|
||||
}
|
||||
|
||||
setDefaultSingleJob(job: Job): void {
|
||||
this.defaultSingleJob = job;
|
||||
job.on(Job.STATE_CHANGED, this.eventJobUpdated);
|
||||
this.eventEmitter.emit(this.EVENT.STATE_CHANGED, this.serialize());
|
||||
}
|
||||
|
||||
getDefaultMultipleJob(): Job {
|
||||
return this.defaultMultipleJob;
|
||||
}
|
||||
|
||||
setDefaultMultipleJob(job: Job): void {
|
||||
this.defaultMultipleJob = job;
|
||||
job.on(Job.STATE_CHANGED, this.eventJobUpdated);
|
||||
this.eventEmitter.emit(this.EVENT.STATE_CHANGED, this.serialize());
|
||||
}
|
||||
|
||||
getId(): number {
|
||||
return this.id;
|
||||
}
|
||||
|
||||
setStatus(statusIndex: number): void {
|
||||
this.status = this.STATUS[statusIndex];
|
||||
this.eventEmitter.emit(this.EVENT.STATUS_CHANGED, this.status);
|
||||
}
|
||||
|
||||
setUsername(username: string): void {
|
||||
this.username = username;
|
||||
this.eventEmitter.emit(this.EVENT.STATE_CHANGED, this.serialize());
|
||||
}
|
||||
|
||||
setPassword(password: string): void {
|
||||
this.password = password;
|
||||
this.eventEmitter.emit(this.EVENT.STATE_CHANGED, this.serialize());
|
||||
}
|
||||
|
||||
eventJobUpdated(): void {
|
||||
this.eventEmitter.emit(this.EVENT.STATE_CHANGED, this.serialize());
|
||||
}
|
||||
|
||||
addPduProcessor(pduProcessor: PduProcessor): void {
|
||||
if (this.pduProcessors.indexOf(pduProcessor) === -1) {
|
||||
this.pduProcessors.push(pduProcessor);
|
||||
this.logger.log1(`Adding PDU processor: ${pduProcessor.constructor.name}-${this.getId()}, now active: ${this.pduProcessors.length} processors`);
|
||||
this.eventEmitter.emit(this.EVENT.STATE_CHANGED, this.serialize());
|
||||
} else {
|
||||
this.logger.log1(`PDU processor: ${pduProcessor.constructor.name}-${this.getId()} already attached to session`);
|
||||
}
|
||||
}
|
||||
|
||||
removePduProcessor(pduProcessor: PduProcessor): void {
|
||||
this.pduProcessors = this.pduProcessors.splice(this.pduProcessors.indexOf(pduProcessor), 1);
|
||||
this.logger.log1(`Removing PDU processor: ${pduProcessor.constructor.name}-${this.getId()}, now active: ${this.pduProcessors.length} processors`);
|
||||
this.eventEmitter.emit(this.EVENT.STATE_CHANGED, this.serialize());
|
||||
}
|
||||
|
||||
getPduProcessors(): PduProcessor[] {
|
||||
return this.pduProcessors;
|
||||
}
|
||||
|
||||
serializePduProcessors(): object {
|
||||
this.logger.log1(`Serializing ${this.pduProcessors.length} clients`)
|
||||
return this.pduProcessors.map((processor: PduProcessor) => {
|
||||
return processor.serialize();
|
||||
});
|
||||
}
|
||||
|
||||
eventAnyPdu(session: any, pdu: any): Promise<any> {
|
||||
this.eventEmitter.emit(this.EVENT.ANY_PDU, pdu);
|
||||
let successful: number = 0;
|
||||
this.pduProcessors.forEach((pduProcessor: PduProcessor) => {
|
||||
pduProcessor.processPdu(session, pdu).then((result: any) => {
|
||||
successful++;
|
||||
}, (error: any) => {
|
||||
});
|
||||
});
|
||||
if (successful === 0) {
|
||||
return Promise.resolve("No PDU processor was able to process the PDU");
|
||||
} else {
|
||||
return Promise.resolve();
|
||||
}
|
||||
}
|
||||
}
|
66
src/WS/ClientSet.ts
Normal file
66
src/WS/ClientSet.ts
Normal file
@@ -0,0 +1,66 @@
|
||||
import Logger from "../Logger";
|
||||
import {SessionManager} from "../SessionManager";
|
||||
import {SmppSession} from "../SmppSession";
|
||||
|
||||
export class ClientSet {
|
||||
identifier: string;
|
||||
private clients: any[];
|
||||
private readonly type: string;
|
||||
private readonly sessionId: number;
|
||||
private readonly logger: Logger;
|
||||
private readonly relevantSessionManager: SessionManager | undefined;
|
||||
|
||||
constructor(identifier: string, sessionManagers: SessionManager[]) {
|
||||
this.clients = [];
|
||||
this.identifier = identifier;
|
||||
|
||||
let data: string[] = identifier.split(':');
|
||||
this.type = data[0];
|
||||
this.sessionId = parseInt(data[1]);
|
||||
|
||||
this.logger = new Logger(`ClientSet-${this.type}-${this.sessionId}`);
|
||||
|
||||
this.relevantSessionManager = sessionManagers.find(sm => sm.identifier === this.type);
|
||||
if (!this.relevantSessionManager) {
|
||||
this.logger.log1(`No session manager found for type ${this.type}`);
|
||||
return;
|
||||
}
|
||||
if (this.relevantSessionManager) {
|
||||
this.relevantSessionManager.getSessions().then((sessions) => {
|
||||
sessions.forEach((session) => {
|
||||
this.attachListener(session);
|
||||
});
|
||||
});
|
||||
}
|
||||
this.relevantSessionManager.on(this.relevantSessionManager.SESSION_ADDED_EVENT, this.eventOnSessionAdded.bind(this));
|
||||
}
|
||||
|
||||
eventOnSessionAdded(sessionId: number): void {
|
||||
this.logger.log2(`Session added: ${sessionId}`);
|
||||
this.relevantSessionManager?.getSession(sessionId).then((session) => {
|
||||
this.attachListener(session);
|
||||
})
|
||||
}
|
||||
|
||||
add(ws: any): void {
|
||||
this.logger.log2(`Added client`);
|
||||
this.clients.push(ws);
|
||||
ws.on('close', this.eventOnClose.bind(this));
|
||||
}
|
||||
|
||||
eventOnClose(ws: any): void {
|
||||
this.logger.log2(`Removed client`);
|
||||
this.clients.splice(this.clients.indexOf(ws), 1);
|
||||
}
|
||||
|
||||
notifyClients(message: string) {
|
||||
this.logger.log2(`Notifying clients: ${message}`);
|
||||
this.clients.forEach((ws) => {
|
||||
ws.send(message);
|
||||
});
|
||||
}
|
||||
|
||||
private attachListener(session: SmppSession) {
|
||||
session.on(session.UPDATE_WS, (message: object) => this.notifyClients(JSON.stringify(message)));
|
||||
}
|
||||
}
|
292
src/WS/WSServer.ts
Normal file
292
src/WS/WSServer.ts
Normal file
@@ -0,0 +1,292 @@
|
||||
import Logger from "../Logger";
|
||||
import {SessionManager} from "../SessionManager";
|
||||
import {ClientSet} from "./ClientSet";
|
||||
|
||||
const WebSocket = require("ws");
|
||||
|
||||
const WS_SERVER_PORT: number = Number(process.env.WS_SERVER_PORT) || 8191;
|
||||
|
||||
export class WSServer {
|
||||
private readonly clients: ClientSet[];
|
||||
private readonly unknownClients: any[];
|
||||
private readonly server: any;
|
||||
private readonly logger: Logger;
|
||||
private readonly sessionManagers: SessionManager[];
|
||||
|
||||
constructor(sessionManagers: SessionManager[]) {
|
||||
this.clients = [];
|
||||
this.unknownClients = [];
|
||||
this.server = new WebSocket.Server({port: WS_SERVER_PORT});
|
||||
this.sessionManagers = sessionManagers;
|
||||
this.logger = new Logger("WSServer");
|
||||
this.server.on('connection', this.eventOnConnection.bind(this));
|
||||
this.logger.log1(`WSServer listening atws://localhost:${WS_SERVER_PORT}`);
|
||||
}
|
||||
|
||||
private eventOnConnection(ws: WebSocket): void {
|
||||
this.logger.log1("New connection");
|
||||
this.unknownClients.push(ws);
|
||||
// @ts-ignore
|
||||
ws.on('message', this.eventOnMessage.bind(this, ws));
|
||||
// @ts-ignore
|
||||
ws.on('close', this.eventOnClose.bind(this, ws));
|
||||
}
|
||||
|
||||
private eventOnMessage(ws: any, message: string): void {
|
||||
this.logger.log1("New message");
|
||||
message = String(message);
|
||||
this.unknownClients.splice(this.unknownClients.indexOf(ws), 1);
|
||||
let clientSet: ClientSet | undefined = this.clients.find((clientSet: ClientSet) => clientSet.identifier === message);
|
||||
if (!clientSet) {
|
||||
clientSet = new ClientSet(message, this.sessionManagers);
|
||||
}
|
||||
clientSet.add(ws);
|
||||
}
|
||||
|
||||
private eventOnClose(ws: any): void {
|
||||
this.logger.log1("Connection closed");
|
||||
this.unknownClients.splice(this.unknownClients.indexOf(ws), 1);
|
||||
}
|
||||
|
||||
// constructor() {
|
||||
// // @ts-ignore
|
||||
// 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: WebSocket) {
|
||||
// this.logger.log1("New connection");
|
||||
// this.unknownClients.push(ws);
|
||||
// ws.on('message', this.onMessage.bind(this, ws));
|
||||
// ws.on('close', this.onClose.bind(this, ws));
|
||||
// }
|
||||
//
|
||||
// addClient(ws, type, sessionId) {
|
||||
// if (!this.clients[type]) {
|
||||
// this.clients[type] = {};
|
||||
// }
|
||||
// if (!this.clients[type][sessionId]) {
|
||||
// this.clients[type][sessionId] = [];
|
||||
// }
|
||||
// this.logger.log1(`Adding client ${ws.id} to ${type} session ${sessionId}`);
|
||||
//
|
||||
// if (type === "client") {
|
||||
// if (this.listenersAlreadySetup.indexOf(`client-${sessionId}`) === -1) {
|
||||
// let session = clientSessionManager.getSession(sessionId);
|
||||
// if (!!session) {
|
||||
// this.logger.log1(`Setting up listeners for client session ${sessionId}`);
|
||||
// session.on(ClientSession.STATUS_CHANGED_EVENT, this.onClientSessionStatusChange.bind(this, sessionId));
|
||||
// session.on(ClientSession.ANY_PDU_EVENT, this.onClientSessionPdu.bind(this, sessionId));
|
||||
// session.on(ClientSession.MESSAGE_SEND_COUNTER_UPDATE_EVENT, this.onClientMessageCounterUpdate.bind(this, sessionId));
|
||||
// }
|
||||
// this.listenersAlreadySetup.push(`client-${sessionId}`);
|
||||
// } else {
|
||||
// this.logger.log1(`Listeners for client session ${sessionId} already set up`);
|
||||
// }
|
||||
// } else if (type === "center") {
|
||||
// if (this.listenersAlreadySetup.indexOf(`center-${sessionId}`) === -1) {
|
||||
// let session = centerSessionManager.getSession(sessionId);
|
||||
// if (!!session) {
|
||||
// this.logger.log1(`Setting up listeners for center session ${sessionId}`);
|
||||
// session.on(CenterSession.STATUS_CHANGED_EVENT, this.onCenterStatusChange.bind(this, sessionId));
|
||||
// session.on(CenterSession.ANY_PDU_EVENT, this.onCenterServerPdu.bind(this, sessionId));
|
||||
// session.on(CenterSession.MODE_CHANGED_EVENT, this.onCenterModeChanged.bind(this, sessionId));
|
||||
// session.on(CenterSession.SESSION_CHANGED_EVENT, this.onCenterSessionsChanged.bind(this, sessionId));
|
||||
// session.on(ClientSession.MESSAGE_SEND_COUNTER_UPDATE_EVENT, this.onCenterMessageCounterUpdate.bind(this, sessionId));
|
||||
// }
|
||||
// this.listenersAlreadySetup.push(`center-${sessionId}`);
|
||||
// } else {
|
||||
// this.logger.log1(`Listeners for center session ${sessionId} already set up`);
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// this.clients[type][sessionId].push(ws);
|
||||
// this.logger.log1(`Now active ${this.clients[type][sessionId].length} clients in session ID: ${sessionId} of type ${type}`);
|
||||
// }
|
||||
//
|
||||
// onMessage(ws, message) {
|
||||
// this.logger.log1("New message");
|
||||
// message = String(message);
|
||||
// let data = message.split(":");
|
||||
// let type = data[0];
|
||||
// let sessionId = data[1];
|
||||
//
|
||||
// this.logger.log1(`Moving client to session ID: ${sessionId} of type ${type}`);
|
||||
// delete this.unknownClients[ws];
|
||||
// this.unknownClients = this.unknownClients.filter(Boolean);
|
||||
//
|
||||
// this.addClient(ws, type, sessionId);
|
||||
// this.logger.log1(`Now active ${this.clients[type][sessionId].length} clients in session ID: ${sessionId} of type ${type}`);
|
||||
// }
|
||||
//
|
||||
// onClose(ws) {
|
||||
// this.removeClient(ws);
|
||||
// // this.logger.log6(this.clients);
|
||||
// this.logger.log1("Connection closed");
|
||||
// }
|
||||
//
|
||||
// removeClient(ws) {
|
||||
// this.clients.client = this.removeFromArray(this.clients.client, ws);
|
||||
// this.clients.center = this.removeFromArray(this.clients.center, ws);
|
||||
// }
|
||||
//
|
||||
// removeFromArray(array, element) {
|
||||
// for (let sessionId in array) {
|
||||
// let index = array[sessionId].indexOf(element);
|
||||
// if (index > -1) {
|
||||
// delete array[sessionId][index];
|
||||
// }
|
||||
// array[sessionId] = array[sessionId].filter(Boolean);
|
||||
// if (array[sessionId].length === 0) {
|
||||
// delete array[sessionId];
|
||||
// }
|
||||
// }
|
||||
// return array;
|
||||
// }
|
||||
//
|
||||
// onClientSessionStatusChange(sessionId, newStatus) {
|
||||
// this.logger.log1(`Session with ID ${sessionId} changed`);
|
||||
// let payload = {
|
||||
// objectType: "client",
|
||||
// type: 'status',
|
||||
// sessionId: sessionId,
|
||||
// value: newStatus
|
||||
// }
|
||||
// let clients = this.clients["client"][sessionId];
|
||||
// if (!!clients) {
|
||||
// this.logger.log1(`Broadcasting session with ID ${sessionId} to ${clients.length} clients`);
|
||||
// clients.forEach(client => {
|
||||
// client.send(JSON.stringify(payload));
|
||||
// });
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// onClientSessionPdu(sessionId, pdu) {
|
||||
// // TODO: Maybe move this to an "ignored" array against who the pdu.command is compared
|
||||
// if (pdu.command === 'enquire_link_resp' || pdu.command === 'enquire_link') {
|
||||
// return;
|
||||
// }
|
||||
// let clients = this.clients["client"][sessionId];
|
||||
// if (!!clients) {
|
||||
// this.logger.log2(`Session with ID ${sessionId} fired PDU`);
|
||||
// let payload = {
|
||||
// objectType: "client",
|
||||
// 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));
|
||||
// });
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// onClientMessageCounterUpdate(sessionId, counter) {
|
||||
// this.logger.log2(`Session with ID ${sessionId} updating message send counter`);
|
||||
// let payload = {
|
||||
// objectType: "client",
|
||||
// type: 'counterUpdate',
|
||||
// sessionId: sessionId,
|
||||
// value: counter
|
||||
// }
|
||||
// let clients = this.clients["client"][sessionId];
|
||||
// if (!!clients) {
|
||||
// this.logger.log2(`Broadcasting session with ID ${sessionId} to ${clients.length} clients`);
|
||||
// clients.forEach(client => {
|
||||
// client.send(JSON.stringify(payload));
|
||||
// });
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// onCenterStatusChange(sessionId, newStatus) {
|
||||
// this.logger.log1(`Session with ID ${sessionId} changed`);
|
||||
// let payload = {
|
||||
// objectType: "center",
|
||||
// type: 'status',
|
||||
// sessionId: sessionId,
|
||||
// value: newStatus
|
||||
// }
|
||||
// let clients = this.clients["center"][sessionId];
|
||||
// if (!!clients) {
|
||||
// this.logger.log1(`Broadcasting session with ID ${sessionId} to ${clients.length} clients`);
|
||||
// clients.forEach(client => {
|
||||
// client.send(JSON.stringify(payload));
|
||||
// });
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// onCenterServerPdu(sessionId, pdu) {
|
||||
// if (pdu.command === 'enquire_link_resp' || pdu.command === 'enquire_link') {
|
||||
// return;
|
||||
// }
|
||||
// let clients = this.clients["center"][sessionId];
|
||||
// if (!!clients) {
|
||||
// this.logger.log2(`Session with ID ${sessionId} fired PDU`);
|
||||
// let payload = {
|
||||
// objectType: "center",
|
||||
// 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));
|
||||
// });
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// onCenterModeChanged(sessionId, newMode) {
|
||||
// this.logger.log1(`Session with ID ${sessionId} changed`);
|
||||
// let payload = {
|
||||
// objectType: "center",
|
||||
// type: 'mode',
|
||||
// sessionId: sessionId,
|
||||
// value: newMode,
|
||||
// text: CenterMode[newMode]
|
||||
// }
|
||||
// let clients = this.clients["center"][sessionId];
|
||||
// if (!!clients) {
|
||||
// this.logger.log1(`Broadcasting session with ID ${sessionId} to ${clients.length} clients`);
|
||||
// clients.forEach(client => {
|
||||
// client.send(JSON.stringify(payload));
|
||||
// });
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// onCenterSessionsChanged(sessionId, newSession) {
|
||||
// this.logger.log1(`Session with ID ${sessionId} changed`);
|
||||
// let payload = {
|
||||
// objectType: "center",
|
||||
// type: 'sessions',
|
||||
// sessionId: sessionId,
|
||||
// value: newSession
|
||||
// }
|
||||
// let clients = this.clients["center"][sessionId];
|
||||
// if (!!clients) {
|
||||
// this.logger.log1(`Broadcasting session with ID ${sessionId} to ${clients.length} clients`);
|
||||
// clients.forEach(client => {
|
||||
// client.send(JSON.stringify(payload));
|
||||
// });
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// onCenterMessageCounterUpdate(sessionId, counter) {
|
||||
// this.logger.log2(`Session with ID ${sessionId} updating message send counter`);
|
||||
// let payload = {
|
||||
// objectType: "center",
|
||||
// type: 'counterUpdate',
|
||||
// sessionId: sessionId,
|
||||
// value: counter
|
||||
// }
|
||||
// let clients = this.clients["center"][sessionId];
|
||||
// if (!!clients) {
|
||||
// this.logger.log2(`Broadcasting session with ID ${sessionId} to ${clients.length} clients`);
|
||||
// clients.forEach(client => {
|
||||
// client.send(JSON.stringify(payload));
|
||||
// });
|
||||
// }
|
||||
// }
|
||||
}
|
37
src/main.ts
Normal file
37
src/main.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import {Center} from "./Center/Center";
|
||||
import {CenterSessionManager} from "./Center/CenterSessionManager";
|
||||
import {Client} from "./Client/Client";
|
||||
import ClientSessionManager from "./Client/ClientSessionManager";
|
||||
import {HttpServer} from "./HttpServer/HttpServer";
|
||||
import {Job} from "./Job/Job";
|
||||
import Logger from "./Logger";
|
||||
import {DebugPduProcessor} from "./PDUProcessor/DebugPduProcessor";
|
||||
import {EchoPduProcessor} from "./PDUProcessor/EchoPduProcessor";
|
||||
import {PduProcessor} from "./PDUProcessor/PduProcessor";
|
||||
import {WSServer} from "./WS/WSServer";
|
||||
|
||||
const {PDU} = require("smpp");
|
||||
// TODO: Add support for encodings
|
||||
// TODO: Implement some sort of metrics on frontend by counting the pdus
|
||||
|
||||
let logger = new Logger("main");
|
||||
|
||||
PduProcessor.addProcessor(DebugPduProcessor);
|
||||
PduProcessor.addProcessor(EchoPduProcessor);
|
||||
|
||||
let clientManager: ClientSessionManager = new ClientSessionManager();
|
||||
let centerManager: CenterSessionManager = new CenterSessionManager();
|
||||
|
||||
let wss: WSServer = new WSServer([clientManager, centerManager]);
|
||||
let httpServer: HttpServer = new HttpServer(clientManager, centerManager);
|
||||
|
||||
function cleanup(): void {
|
||||
logger.log1("Cleaning up...");
|
||||
clientManager.cleanup();
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
process.on('exit', cleanup);
|
||||
process.on('SIGINT', cleanup);
|
||||
process.on('SIGUSR1', cleanup);
|
||||
process.on('SIGUSR2', cleanup);
|
14
tsconfig.json
Normal file
14
tsconfig.json
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es6",
|
||||
"module": "commonjs",
|
||||
"outDir": "./dist",
|
||||
"rootDir": "./src",
|
||||
"strict": true,
|
||||
"moduleResolution": "node",
|
||||
"esModuleInterop": true
|
||||
},
|
||||
"exclude": [
|
||||
"./node_modules"
|
||||
]
|
||||
}
|
Reference in New Issue
Block a user