badmfck-api-server 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
Binary file
@@ -0,0 +1,39 @@
1
+ import { Response } from 'express';
2
+ import { BaseService, IBaseService } from './BaseService';
3
+ import { IBaseEndpoint } from './BaseEndpoint';
4
+ import { SyncSignal } from '../signal/Signal';
5
+ import { TransferPacketVO } from './structures/Interfaces';
6
+ export interface APIServiceNetworkLogItem {
7
+ id: number;
8
+ created: number;
9
+ time: number;
10
+ request: {
11
+ [key: string]: any;
12
+ };
13
+ response: {
14
+ [key: string]: any;
15
+ };
16
+ error?: string | null;
17
+ }
18
+ export interface APIServiceOptions {
19
+ port: number;
20
+ baseEndPoint: string;
21
+ corsHostWhiteList: string[];
22
+ endpoints: IBaseEndpoint[];
23
+ jsonLimit: string;
24
+ onNetworkLog?: ((log: APIServiceNetworkLogItem) => void) | null;
25
+ onError?: ((...rest: any[]) => void) | null;
26
+ }
27
+ export declare function getDefaultOptions(): APIServiceOptions;
28
+ export declare const REQ_CREATE_NET_LOG: SyncSignal<void, APIServiceNetworkLogItem>;
29
+ export declare const REQ_HTTP_LOG: SyncSignal<void, APIServiceNetworkLogItem[]>;
30
+ export declare function Initializer(services: IBaseService[]): Promise<void>;
31
+ export declare class APIService extends BaseService {
32
+ private static nextLogID;
33
+ private version;
34
+ private options;
35
+ netLog: APIServiceNetworkLogItem[];
36
+ constructor(options?: APIServiceOptions | null);
37
+ init(): Promise<void>;
38
+ sendResponse(res: Response, data: TransferPacketVO<any>, requestTime: number, endpoint?: string, log?: APIServiceNetworkLogItem | null): void;
39
+ }
@@ -0,0 +1,169 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.APIService = exports.Initializer = exports.REQ_HTTP_LOG = exports.REQ_CREATE_NET_LOG = exports.getDefaultOptions = void 0;
7
+ const express_1 = __importDefault(require("express"));
8
+ const BaseService_1 = require("./BaseService");
9
+ const cors_1 = __importDefault(require("cors"));
10
+ const BaseEndpoint_1 = require("./BaseEndpoint");
11
+ const DefaultErrors_1 = __importDefault(require("./structures/DefaultErrors"));
12
+ const Signal_1 = require("../signal/Signal");
13
+ function getDefaultOptions() {
14
+ return {
15
+ port: 8091,
16
+ baseEndPoint: '/api/',
17
+ corsHostWhiteList: [
18
+ 'http://localhost:3000',
19
+ 'http://localhost:8091'
20
+ ],
21
+ endpoints: [],
22
+ jsonLimit: "10mb",
23
+ onNetworkLog: function (log) {
24
+ console.log(log);
25
+ },
26
+ onError: function (err) {
27
+ console.error.call(this, err);
28
+ }
29
+ };
30
+ }
31
+ exports.getDefaultOptions = getDefaultOptions;
32
+ exports.REQ_CREATE_NET_LOG = new Signal_1.SyncSignal();
33
+ exports.REQ_HTTP_LOG = new Signal_1.SyncSignal();
34
+ async function Initializer(services) {
35
+ for (let i of services) {
36
+ await i.init();
37
+ }
38
+ for (let i of services) {
39
+ i.applicationReady();
40
+ }
41
+ }
42
+ exports.Initializer = Initializer;
43
+ class APIService extends BaseService_1.BaseService {
44
+ static nextLogID = 0;
45
+ version = "1.2.0";
46
+ options;
47
+ netLog = [];
48
+ constructor(options) {
49
+ super('HTTP Service');
50
+ this.options = options ?? getDefaultOptions();
51
+ }
52
+ async init() {
53
+ exports.REQ_HTTP_LOG.listener = (ignore, cb) => { cb(this.netLog); };
54
+ exports.REQ_CREATE_NET_LOG.listener = (ignore, cb) => {
55
+ const log = {
56
+ created: +new Date(),
57
+ time: 0,
58
+ request: {},
59
+ response: {},
60
+ error: null,
61
+ id: APIService.nextLogID++
62
+ };
63
+ this.netLog.push(log);
64
+ if (this.netLog.length > 1000)
65
+ this.netLog.shift();
66
+ cb(log);
67
+ };
68
+ const app = (0, express_1.default)();
69
+ app.use(express_1.default.json({ limit: '10mb' }));
70
+ app.use(express_1.default.urlencoded({ limit: '10mb', extended: true }));
71
+ const corsOptions = {
72
+ origin: (origin, callback) => {
73
+ const originIsWhitelisted = this.options.corsHostWhiteList.includes(origin);
74
+ callback(null, originIsWhitelisted);
75
+ },
76
+ credentials: true
77
+ };
78
+ app.use((0, cors_1.default)(corsOptions));
79
+ BaseEndpoint_1.BaseEndpoint.setEntryPoint(this.options.baseEndPoint);
80
+ for (let i of this.options.endpoints) {
81
+ await i.init();
82
+ for (let j of i.endpoints) {
83
+ const ep = BaseEndpoint_1.BaseEndpoint.getEntryPoint() + j;
84
+ app.all(ep, async (req, res) => {
85
+ const tme = +new Date();
86
+ let log = null;
87
+ if (!i.ignoreHttpLogging) {
88
+ log = await exports.REQ_CREATE_NET_LOG.request();
89
+ log.request = {
90
+ method: req.method,
91
+ data: req.body,
92
+ params: req.params
93
+ };
94
+ }
95
+ if (this.options.onNetworkLog && log)
96
+ this.options.onNetworkLog(log);
97
+ const execute = async () => {
98
+ const httpRequest = {
99
+ raw: req,
100
+ method: req.method,
101
+ data: req.body,
102
+ params: req.params,
103
+ headers: req.headers,
104
+ endpoint: ep
105
+ };
106
+ const result = await i.execute(httpRequest);
107
+ this.sendResponse(res, result, tme, ep, log);
108
+ };
109
+ execute();
110
+ });
111
+ }
112
+ }
113
+ app.use((req, res, next) => {
114
+ const tme = +new Date();
115
+ this.sendResponse(res, {
116
+ error: DefaultErrors_1.default.UNKNOWN_REQUEST,
117
+ data: null,
118
+ httpStatus: 404
119
+ }, tme, req.path);
120
+ });
121
+ app.listen(this.options.port, () => {
122
+ console.log('API Service started at: ' + this.options.port + ", with base endpoint:" + this.options.baseEndPoint + ", ver.: " + this.version);
123
+ });
124
+ }
125
+ sendResponse(res, data, requestTime, endpoint, log) {
126
+ data.responseTime = (+new Date()) - requestTime;
127
+ data.version = this.version;
128
+ data.endpoint = endpoint ?? "no_endpoint";
129
+ if (log)
130
+ log.time = data.responseTime;
131
+ if (res.destroyed || res.closed) {
132
+ if (log)
133
+ log.error = "Connection already closed, can't send response";
134
+ if (this.options.onError)
135
+ this.options.onError("Connection already closed, can't send response", data);
136
+ }
137
+ else {
138
+ try {
139
+ if (data.redirect) {
140
+ res.redirect(data.redirect);
141
+ if (log)
142
+ log.response = { redirect: data.redirect };
143
+ }
144
+ else {
145
+ res.statusCode = data.httpStatus ?? 200;
146
+ if (data.rawResponse) {
147
+ res.send(data.data);
148
+ if (log)
149
+ log.response = data.data;
150
+ }
151
+ else {
152
+ res.send(data);
153
+ if (log)
154
+ log.response = data;
155
+ }
156
+ }
157
+ }
158
+ catch (e) {
159
+ if (this.options.onError)
160
+ this.options.onError("Can't send response", e);
161
+ if (log)
162
+ log.error = "Can't send response";
163
+ }
164
+ }
165
+ if (this.options.onNetworkLog && log)
166
+ this.options.onNetworkLog(log);
167
+ }
168
+ }
169
+ exports.APIService = APIService;
@@ -0,0 +1,17 @@
1
+ import { HTTPRequestVO, TransferPacketVO } from "./structures/Interfaces";
2
+ export interface IBaseEndpoint {
3
+ endpoints: string[];
4
+ execute: (req: HTTPRequestVO) => Promise<TransferPacketVO<any>>;
5
+ init: () => Promise<void>;
6
+ ignoreHttpLogging: boolean;
7
+ }
8
+ export declare class BaseEndpoint implements IBaseEndpoint {
9
+ ignoreHttpLogging: boolean;
10
+ private static entrypoint;
11
+ static setEntryPoint: (ep: string) => void;
12
+ static getEntryPoint: () => string;
13
+ endpoints: string[];
14
+ constructor(ep: string | string[]);
15
+ init(): Promise<void>;
16
+ execute(req: HTTPRequestVO): Promise<TransferPacketVO<any>>;
17
+ }
@@ -0,0 +1,46 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.BaseEndpoint = void 0;
7
+ const DefaultErrors_1 = __importDefault(require("./structures/DefaultErrors"));
8
+ class BaseEndpoint {
9
+ ignoreHttpLogging = false;
10
+ static entrypoint = "/";
11
+ static setEntryPoint = (ep) => {
12
+ this.entrypoint = ep;
13
+ if (!this.entrypoint.endsWith("/")) {
14
+ this.entrypoint += "/";
15
+ }
16
+ if (!ep.startsWith("/"))
17
+ this.entrypoint = "/" + ep;
18
+ };
19
+ static getEntryPoint = () => {
20
+ return this.entrypoint;
21
+ };
22
+ endpoints;
23
+ constructor(ep) {
24
+ if (typeof ep === "string")
25
+ ep = [ep];
26
+ let eps = [];
27
+ for (let i of ep) {
28
+ if (i.startsWith("/"))
29
+ i = i.substring(1);
30
+ eps.push(i);
31
+ }
32
+ this.endpoints = eps;
33
+ }
34
+ async init() {
35
+ for (let i of this.endpoints)
36
+ console.log("endpoint: " + BaseEndpoint.entrypoint + i + " initalized");
37
+ }
38
+ ;
39
+ async execute(req) {
40
+ return {
41
+ error: DefaultErrors_1.default.NOT_IMPLEMENTED,
42
+ data: null
43
+ };
44
+ }
45
+ }
46
+ exports.BaseEndpoint = BaseEndpoint;
@@ -0,0 +1,10 @@
1
+ export interface IBaseService {
2
+ init: () => Promise<void>;
3
+ applicationReady: () => void;
4
+ }
5
+ export declare class BaseService implements IBaseService {
6
+ protected name: string;
7
+ constructor(name: string);
8
+ init(): Promise<void>;
9
+ applicationReady(): void;
10
+ }
@@ -0,0 +1,14 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.BaseService = void 0;
4
+ class BaseService {
5
+ name;
6
+ constructor(name) {
7
+ this.name = name;
8
+ }
9
+ async init() {
10
+ console.log("Service: " + this.name + " initialized");
11
+ }
12
+ applicationReady() { }
13
+ }
14
+ exports.BaseService = BaseService;
@@ -0,0 +1,3 @@
1
+ export declare const LocalRequest: (ep: string, data?: any, method?: string, headers?: {
2
+ [key: string]: string;
3
+ } | undefined) => Promise<void>;
@@ -0,0 +1,64 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.LocalRequest = void 0;
7
+ const axios_1 = __importDefault(require("axios"));
8
+ axios_1.default.interceptors.request.use((x) => {
9
+ console.log(`AXIOS REQ:\n\turl:${x.url}\n\tmethod: ${x.method}\n\tdata: ${(() => {
10
+ if (typeof x.data === "string")
11
+ return x.data;
12
+ return JSON.stringify(x.data, null, "\t\t");
13
+ })()}\n\theaders:\n${(() => {
14
+ let h = "";
15
+ for (let i in x.headers) {
16
+ if (h !== "")
17
+ h += "\n";
18
+ h += "\t\t" + i + " = " + x.headers[i];
19
+ }
20
+ return h;
21
+ })()}`);
22
+ return x;
23
+ });
24
+ axios_1.default.interceptors.response.use((x) => {
25
+ console.log(`AXIOS RESP:\n\tdata: ${(() => {
26
+ if (typeof x.data === "string")
27
+ return x.data;
28
+ return JSON.stringify(x.data, null, "\t\t");
29
+ })()}\n\theaders:\n${(() => {
30
+ let h = "";
31
+ for (let i in x.headers) {
32
+ if (h !== "")
33
+ h += "\n";
34
+ h += "\t\t" + i + " = " + x.headers[i];
35
+ }
36
+ return h;
37
+ })()}`);
38
+ return x;
39
+ });
40
+ const LocalRequest = async (ep, data, method, headers) => {
41
+ let m = "get";
42
+ if (method)
43
+ m = method.toLowerCase();
44
+ if (data && !method)
45
+ m = "post";
46
+ if (ep.startsWith("/"))
47
+ ep = ep.substring(1);
48
+ let url = "http://localhost:8091/" + ep;
49
+ if (ep.toLowerCase().startsWith("http"))
50
+ url = ep;
51
+ let resp = null;
52
+ console.log("open url: ", url);
53
+ try {
54
+ if (m === "get")
55
+ resp = await axios_1.default.get(url, { headers: headers });
56
+ if (m === "post")
57
+ resp = await axios_1.default.post(url, data, { headers: headers });
58
+ }
59
+ catch (e) {
60
+ console.log("ERROR! cant open url", e);
61
+ }
62
+ console.log(resp);
63
+ };
64
+ exports.LocalRequest = LocalRequest;
@@ -0,0 +1,6 @@
1
+ import { ErrorVO } from "./Interfaces";
2
+ declare class DefaultErrors {
3
+ static NOT_IMPLEMENTED: ErrorVO;
4
+ static UNKNOWN_REQUEST: ErrorVO;
5
+ }
6
+ export default DefaultErrors;
@@ -0,0 +1,7 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ class DefaultErrors {
4
+ static NOT_IMPLEMENTED = { code: 1, message: "Not implemented" };
5
+ static UNKNOWN_REQUEST = { code: 2, message: "Unknown request" };
6
+ }
7
+ exports.default = DefaultErrors;
@@ -0,0 +1,7 @@
1
+ export declare enum HTTP_STATUS {
2
+ OK = 200,
3
+ NOT_FOUND = 404,
4
+ UNAUTHORIZED = 401,
5
+ BAD_REQUEST = 400,
6
+ SERVER_ERROR = 500
7
+ }
@@ -0,0 +1,11 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.HTTP_STATUS = void 0;
4
+ var HTTP_STATUS;
5
+ (function (HTTP_STATUS) {
6
+ HTTP_STATUS[HTTP_STATUS["OK"] = 200] = "OK";
7
+ HTTP_STATUS[HTTP_STATUS["NOT_FOUND"] = 404] = "NOT_FOUND";
8
+ HTTP_STATUS[HTTP_STATUS["UNAUTHORIZED"] = 401] = "UNAUTHORIZED";
9
+ HTTP_STATUS[HTTP_STATUS["BAD_REQUEST"] = 400] = "BAD_REQUEST";
10
+ HTTP_STATUS[HTTP_STATUS["SERVER_ERROR"] = 500] = "SERVER_ERROR";
11
+ })(HTTP_STATUS = exports.HTTP_STATUS || (exports.HTTP_STATUS = {}));
@@ -0,0 +1,25 @@
1
+ export interface TransferPacketVO<T> {
2
+ data?: T | null;
3
+ error?: ErrorVO | null;
4
+ responseTime?: number;
5
+ version?: string;
6
+ endpoint?: string;
7
+ httpStatus?: number;
8
+ redirect?: string;
9
+ rawResponse?: boolean;
10
+ }
11
+ export interface HTTPRequestVO {
12
+ raw: any;
13
+ method: string;
14
+ data: any;
15
+ params: {
16
+ [key: string]: string;
17
+ };
18
+ headers: any;
19
+ endpoint: string;
20
+ }
21
+ export interface ErrorVO {
22
+ code: number;
23
+ message: string;
24
+ details?: string;
25
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,3 @@
1
+ import { APIService, Initializer } from "./apiServer/APIService";
2
+ import { LocalRequest } from "./apiServer/LocalRequest";
3
+ export { APIService, Initializer, LocalRequest };
package/dist/index.js ADDED
@@ -0,0 +1,8 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.LocalRequest = exports.Initializer = exports.APIService = void 0;
4
+ const APIService_1 = require("./apiServer/APIService");
5
+ Object.defineProperty(exports, "APIService", { enumerable: true, get: function () { return APIService_1.APIService; } });
6
+ Object.defineProperty(exports, "Initializer", { enumerable: true, get: function () { return APIService_1.Initializer; } });
7
+ const LocalRequest_1 = require("./apiServer/LocalRequest");
8
+ Object.defineProperty(exports, "LocalRequest", { enumerable: true, get: function () { return LocalRequest_1.LocalRequest; } });
@@ -0,0 +1,28 @@
1
+ declare class Signal<T> {
2
+ static nextID: number;
3
+ private busy;
4
+ private tempAdd;
5
+ private tempRem;
6
+ private tempInvoke;
7
+ private callbacks;
8
+ private tempClear;
9
+ constructor(name?: string);
10
+ add(callback: (data: T) => void, id?: string): string;
11
+ clear(): void;
12
+ remove(callback?: (data: T) => void, id?: string): string | undefined;
13
+ invoke(data: T): void;
14
+ }
15
+ export declare class SignalHandler {
16
+ static nextID: number;
17
+ private id;
18
+ private signals;
19
+ constructor();
20
+ add<T>(signal: Signal<T>, cb: (data: T) => void): void;
21
+ clear(): void;
22
+ }
23
+ export declare class SyncSignal<T, K> {
24
+ private worker?;
25
+ request(data: T): Promise<K>;
26
+ set listener(_listener: (request: T, response: (data: K) => void) => void);
27
+ }
28
+ export default Signal;
@@ -0,0 +1,140 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SyncSignal = exports.SignalHandler = void 0;
4
+ class Signal {
5
+ static nextID = 0;
6
+ busy = false;
7
+ tempAdd = [];
8
+ tempRem = [];
9
+ tempInvoke = [];
10
+ callbacks = [];
11
+ tempClear = false;
12
+ constructor(name) { }
13
+ add(callback, id) {
14
+ if (!id)
15
+ id = "" + (Signal.nextID++);
16
+ if (this.busy) {
17
+ for (let i of this.tempAdd) {
18
+ if (i.cb === callback)
19
+ return i.id;
20
+ }
21
+ this.tempAdd.push({ cb: callback, id: id });
22
+ return id;
23
+ }
24
+ for (let i of this.callbacks) {
25
+ if (i.cb === callback)
26
+ return i.id;
27
+ }
28
+ this.callbacks.push({ cb: callback, id: id });
29
+ return id;
30
+ }
31
+ clear() {
32
+ if (this.busy) {
33
+ this.tempClear = true;
34
+ return;
35
+ }
36
+ this.callbacks = [];
37
+ }
38
+ remove(callback, id) {
39
+ if (!callback && !id)
40
+ return;
41
+ if (this.busy) {
42
+ for (let i of this.tempRem) {
43
+ if (callback) {
44
+ if (i.cb === callback)
45
+ return;
46
+ }
47
+ if (id) {
48
+ if (i.id === id)
49
+ return;
50
+ }
51
+ }
52
+ this.tempRem.push({ cb: callback, id: id });
53
+ return id;
54
+ }
55
+ for (let i = 0; i < this.callbacks.length; i++) {
56
+ const itm = this.callbacks[i];
57
+ let remove = false;
58
+ if (callback)
59
+ remove = itm.cb === callback;
60
+ if (id)
61
+ remove = remove || itm.id === id;
62
+ if (remove) {
63
+ this.callbacks.splice(i, 1);
64
+ i--;
65
+ }
66
+ }
67
+ }
68
+ invoke(data) {
69
+ if (this.busy) {
70
+ this.tempInvoke.push({ data: data });
71
+ return;
72
+ }
73
+ this.busy = true;
74
+ for (let i of this.callbacks) {
75
+ if (i && i.cb && typeof i.cb === "function") {
76
+ i.cb(data);
77
+ }
78
+ }
79
+ this.busy = false;
80
+ for (let i of this.tempAdd) {
81
+ this.add(i.cb, i.id);
82
+ }
83
+ this.tempAdd = [];
84
+ for (let i of this.tempRem) {
85
+ this.remove(i.cb, i.id);
86
+ }
87
+ this.tempRem = [];
88
+ for (let i of this.tempInvoke) {
89
+ this.invoke(i.data);
90
+ }
91
+ if (this.tempClear)
92
+ this.clear();
93
+ }
94
+ }
95
+ class SignalHandler {
96
+ static nextID = 0;
97
+ id;
98
+ signals = [];
99
+ constructor() {
100
+ this.id = SignalHandler.nextID++;
101
+ }
102
+ add(signal, cb) {
103
+ let added = false;
104
+ for (let i of this.signals) {
105
+ if (i === signal) {
106
+ added = true;
107
+ break;
108
+ }
109
+ }
110
+ if (!added)
111
+ this.signals.push(signal);
112
+ signal.add(cb, "signaller_" + this.id);
113
+ }
114
+ clear() {
115
+ for (let i of this.signals)
116
+ i.remove(undefined, "signaller_" + this.id);
117
+ this.signals = [];
118
+ }
119
+ }
120
+ exports.SignalHandler = SignalHandler;
121
+ class SyncSignal {
122
+ worker;
123
+ request(data) {
124
+ const executor = (resolve, reject) => {
125
+ if (!this.worker) {
126
+ console.error("No worker registered in SyncSignal");
127
+ reject("No worker registered in SyncSignal");
128
+ return;
129
+ }
130
+ this.worker(data, resolve);
131
+ };
132
+ const promise = new Promise(executor);
133
+ return promise;
134
+ }
135
+ set listener(_listener) {
136
+ this.worker = _listener;
137
+ }
138
+ }
139
+ exports.SyncSignal = SyncSignal;
140
+ exports.default = Signal;
package/package.json ADDED
@@ -0,0 +1,31 @@
1
+ {
2
+ "name": "badmfck-api-server",
3
+ "version": "1.0.1",
4
+ "description": "Simple API http server based on express",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "scripts": {
8
+ "prepublish": "tsc",
9
+ "build":"tsc",
10
+ "test": "echo \"Error: no test specified\" && exit 1"
11
+ },
12
+ "keywords": [
13
+ "api",
14
+ "rest",
15
+ "http",
16
+ "server",
17
+ "express"
18
+ ],
19
+ "dependencies": {
20
+ "axios": "^1.4.0",
21
+ "badmfck-signal": "^1.0.1",
22
+ "cors": "^2.8.5",
23
+ "express": "^4.18.2"
24
+ },
25
+ "devDependencies": {
26
+ "@types/cors": "^2.8.13",
27
+ "@types/express": "^4.17.17"
28
+ },
29
+ "author": "Igor Bloom",
30
+ "license": "MIT"
31
+ }