ts-server-lib 0.0.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,139 @@
1
+ /**
2
+ * USSD Menu with state
3
+ *
4
+ * - General rules
5
+ * - Routing
6
+ * - ussd text in form 1*2*7
7
+ * - * bring back to start menu
8
+ *
9
+ * - Development Section
10
+ * - To publish local ussd receiver to remote through ssh
11
+ * # ssh -R 9093:localhost:9090 root@185.41.154.247 -N
12
+ *
13
+ */
14
+ import { EventEmitter } from 'events';
15
+ export declare class TSUssdState {
16
+ menu: TSUssdMenu;
17
+ name: string | null;
18
+ run: ((state: TSUssdState) => void) | null;
19
+ defaultNext: string | null;
20
+ val: string | null;
21
+ next: Record<string, unknown> | null;
22
+ constructor(menu: TSUssdMenu);
23
+ }
24
+ export declare class TSUssdMenu extends EventEmitter {
25
+ map: Record<string, unknown>;
26
+ static START_STATE: string;
27
+ static config: any;
28
+ session: any;
29
+ args: any;
30
+ states: any;
31
+ result: string;
32
+ onResult: any;
33
+ current: any;
34
+ val: string;
35
+ static forOwn(items: unknown[] | Record<string, unknown>, cb: (v: unknown, key?: number | string) => void): void;
36
+ static mapArgs(args?: Record<string, unknown>, map?: Record<string, unknown>): Record<string, unknown>;
37
+ static cleanRoute(route: string): string;
38
+ constructor(args: Record<string, unknown> | undefined, map: Record<string, unknown>);
39
+ /**
40
+ * A callback after result is set
41
+ */
42
+ callOnResult(): void;
43
+ /**
44
+ * This method send message to the client and take the session opened
45
+ *
46
+ * @param text string Message to sent
47
+ */
48
+ con(text: string): string;
49
+ /**
50
+ * This method send message to the client and close the session
51
+ *
52
+ * @param text string Message to sent
53
+ */
54
+ end(text: string): string;
55
+ /**
56
+ * This method format the message and if debug calcualte the length
57
+ *
58
+ * @param text string Message to sent
59
+ */
60
+ print(text: string): string;
61
+ testLinkRule(rule: string | RegExp | unknown, val: string): boolean;
62
+ /**
63
+ * Find state based on route — async/await, no external dependencies.
64
+ *
65
+ * next values may be: a string, a callback-based function (cb) => void,
66
+ * a function returning a string synchronously, or a function returning a Promise.
67
+ */
68
+ resolveRoute(route: string): Promise<TSUssdState | undefined>;
69
+ runState(state: TSUssdState): boolean | undefined;
70
+ go(stateName: string): void;
71
+ goStart(): void;
72
+ /**
73
+ * Configure custom session handler cross-compatible between callbacks and promises
74
+ *
75
+ * @example Memory configuration example with callback
76
+ *
77
+ * let sessions = {};
78
+ * let menu = new UssdMenu();
79
+ * menu.sessionConfig({
80
+ * start: (sessionId, callback) {
81
+ * // initialize current session if it doesn't exist this is called by menu.run()
82
+ * if(!(sessionId in sessions)) sessions[sessionId] = {};
83
+ * callback();
84
+ * },
85
+ * end: (sessionId, callback) {
86
+ * // clear current session this is called by menu.end()
87
+ * delete sessions[sessionId];
88
+ * callback();
89
+ * },
90
+ * set: (sessionId, key, value, callback) {
91
+ * // store key-value pair in current session
92
+ * sessions[sessionId][key] = value;
93
+ * callback();
94
+ * },
95
+ * get: (sessionId, key, callback) {
96
+ * // retrieve value by key in current session
97
+ * callback(null, sessions[sessionId][key]);
98
+ * },
99
+ * del: (sessionId, key, callback) {
100
+ * delete sessions[sessionId][key];
101
+ * callback();
102
+ * }
103
+ * });
104
+ *
105
+ * @param {Object} c object with implementation for get, set, start and end methods
106
+ * @param {String} a attribute of args
107
+ */
108
+ sessionConfig(c: Record<string, (...args: unknown[]) => unknown>, a?: string): void;
109
+ /**
110
+ * Create a state on the ussd chain
111
+ *
112
+ * @param string name name of the state
113
+ * @param object options
114
+ * @param object options.next object mapping of route val to state names
115
+ * @param string options.defaultNext name of state to run when the given route from this state can't be resolved
116
+ * @param function options.run the method to run when this state is resolved
117
+ * @return TSUssdMenu the same instance of Ussd
118
+ */
119
+ state(name: string, options: {
120
+ next?: Record<string, unknown>;
121
+ defaultNext?: string;
122
+ run?: (state: TSUssdState) => void;
123
+ }): this;
124
+ /**
125
+ * Create the start state of the ussd chain
126
+ */
127
+ startState(options: {
128
+ next?: Record<string, unknown>;
129
+ defaultNext?: string;
130
+ run?: (state: TSUssdState) => void;
131
+ }): this;
132
+ /**
133
+ * Run the ussd menu
134
+ *
135
+ * @param function onResult a callback to handle on result
136
+ * @param TSUssdArgs options request args from the gateway api
137
+ */
138
+ run(onResult: (result: string) => void, map: Record<string, unknown>): void;
139
+ }
@@ -0,0 +1,364 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.TSUssdMenu = exports.TSUssdState = void 0;
4
+ /**
5
+ * USSD Menu with state
6
+ *
7
+ * - General rules
8
+ * - Routing
9
+ * - ussd text in form 1*2*7
10
+ * - * bring back to start menu
11
+ *
12
+ * - Development Section
13
+ * - To publish local ussd receiver to remote through ssh
14
+ * # ssh -R 9093:localhost:9090 root@185.41.154.247 -N
15
+ *
16
+ */
17
+ const events_1 = require("events");
18
+ class TSUssdState {
19
+ menu;
20
+ name;
21
+ run;
22
+ defaultNext;
23
+ val;
24
+ next = null;
25
+ constructor(menu) {
26
+ this.menu = menu;
27
+ this.name = null;
28
+ this.run = null;
29
+ this.defaultNext = null;
30
+ this.val = null;
31
+ }
32
+ }
33
+ exports.TSUssdState = TSUssdState;
34
+ class TSUssdMenu extends events_1.EventEmitter {
35
+ map;
36
+ static START_STATE = '__start__';
37
+ static config = {
38
+ debug: true,
39
+ general: { separator: ')' },
40
+ navigation: { back: '00', main: '01' },
41
+ pager: {
42
+ default: 3, maxLength: 90, mapping: {},
43
+ strategies: [{ term: ' - ', enabled: true, type: 'truncate' }]
44
+ }
45
+ };
46
+ session;
47
+ args;
48
+ states;
49
+ result;
50
+ onResult;
51
+ current;
52
+ val;
53
+ static forOwn(items, cb) {
54
+ const obj = items;
55
+ for (const i in obj) {
56
+ if (Object.hasOwn(obj, i)) {
57
+ cb(obj[i], i);
58
+ }
59
+ }
60
+ }
61
+ static mapArgs(args = {}, map = {}) {
62
+ const results = { provider: map.provider };
63
+ for (const p in args) {
64
+ if (Object.hasOwn(args, p) && Object.hasOwn(map, p)) {
65
+ results[String(map[p])] = args[p];
66
+ }
67
+ else if (Object.hasOwn(args, p)) {
68
+ results[p] = args[p];
69
+ }
70
+ }
71
+ return results;
72
+ }
73
+ static cleanRoute(route) {
74
+ if (route && route.includes('*000')) {
75
+ const reset = route.substring(route.lastIndexOf('*000') + 4);
76
+ route = '*' + (reset === '0' ? '' : reset);
77
+ }
78
+ return route;
79
+ }
80
+ constructor(args = {}, map) {
81
+ super();
82
+ this.map = map;
83
+ this.session = null;
84
+ this.args = TSUssdMenu.mapArgs(args, map);
85
+ this.states = {};
86
+ this.result = '';
87
+ this.onResult = null;
88
+ this.val = '';
89
+ }
90
+ /**
91
+ * A callback after result is set
92
+ */
93
+ callOnResult() {
94
+ if (this.onResult) {
95
+ this.onResult(this.result);
96
+ }
97
+ }
98
+ /**
99
+ * This method send message to the client and take the session opened
100
+ *
101
+ * @param text string Message to sent
102
+ */
103
+ con(text) {
104
+ this.result = 'CON ' + this.print(text);
105
+ this.callOnResult();
106
+ return this.result;
107
+ }
108
+ /**
109
+ * This method send message to the client and close the session
110
+ *
111
+ * @param text string Message to sent
112
+ */
113
+ end(text) {
114
+ this.result = 'END ' + this.print(text);
115
+ this.callOnResult();
116
+ if (this.session) {
117
+ this.session.end();
118
+ }
119
+ return this.result;
120
+ }
121
+ /**
122
+ * This method format the message and if debug calcualte the length
123
+ *
124
+ * @param text string Message to sent
125
+ */
126
+ print(text) {
127
+ return TSUssdMenu.config.debug ? '[' + (this.current ? this.current.name : 'N/A') + ':' + text.length + '] ' + text : text;
128
+ }
129
+ testLinkRule(rule, val) {
130
+ // if rule starts with *, treat as regex
131
+ if (typeof rule === 'string' && rule.startsWith('*')) {
132
+ return new RegExp(rule.substring(1)).test(val);
133
+ }
134
+ return rule === val;
135
+ }
136
+ /**
137
+ * Find state based on route — async/await, no external dependencies.
138
+ *
139
+ * next values may be: a string, a callback-based function (cb) => void,
140
+ * a function returning a string synchronously, or a function returning a Promise.
141
+ */
142
+ async resolveRoute(route) {
143
+ route = TSUssdMenu.cleanRoute(route);
144
+ const parts = !route ? [] : route.split('*');
145
+ let state = this.states[TSUssdMenu.START_STATE];
146
+ if (!state.next || Object.keys(state.next).length === 0) {
147
+ return this.states[state.defaultNext];
148
+ }
149
+ if ('' in state.next) {
150
+ parts.unshift('');
151
+ }
152
+ while (parts.length > 0) {
153
+ const part = parts.shift();
154
+ let nextFound = false;
155
+ this.val = part ?? '';
156
+ for (const [link, next] of Object.entries(state.next)) {
157
+ if (!this.testLinkRule(link, part ?? ''))
158
+ continue;
159
+ let nextPath;
160
+ if (typeof next === 'string') {
161
+ nextPath = next;
162
+ }
163
+ else if (typeof next === 'function') {
164
+ // supports: callback-based fn(cb), sync-returning fn, and promise-returning fn
165
+ nextPath = await new Promise((resolve, reject) => {
166
+ const result = next(resolve);
167
+ if (typeof result === 'string')
168
+ resolve(result);
169
+ else if (result && typeof result.then === 'function')
170
+ result.then(resolve).catch(reject);
171
+ });
172
+ }
173
+ if (!nextPath)
174
+ continue;
175
+ state = this.states[nextPath];
176
+ this.current = state;
177
+ if (!state)
178
+ throw new Error(`[TSUssdMenu]: declared state does not exist: ${nextPath}`);
179
+ state.val = part ?? '';
180
+ nextFound = true;
181
+ break;
182
+ }
183
+ if (!nextFound && state.defaultNext) {
184
+ state = this.states[state.defaultNext];
185
+ this.current = state;
186
+ state.val = part;
187
+ }
188
+ }
189
+ return state;
190
+ }
191
+ runState(state) {
192
+ if (!state.run) {
193
+ return this.emit('error', new Error(`run function not defined for state: ${state.name}`));
194
+ }
195
+ state.run(state);
196
+ }
197
+ go(stateName) {
198
+ const state = this.states[stateName];
199
+ state.val = this.val;
200
+ this.current = state;
201
+ this.runState(state);
202
+ }
203
+ goStart() {
204
+ this.go(TSUssdMenu.START_STATE);
205
+ }
206
+ /**
207
+ * Configure custom session handler cross-compatible between callbacks and promises
208
+ *
209
+ * @example Memory configuration example with callback
210
+ *
211
+ * let sessions = {};
212
+ * let menu = new UssdMenu();
213
+ * menu.sessionConfig({
214
+ * start: (sessionId, callback) {
215
+ * // initialize current session if it doesn't exist this is called by menu.run()
216
+ * if(!(sessionId in sessions)) sessions[sessionId] = {};
217
+ * callback();
218
+ * },
219
+ * end: (sessionId, callback) {
220
+ * // clear current session this is called by menu.end()
221
+ * delete sessions[sessionId];
222
+ * callback();
223
+ * },
224
+ * set: (sessionId, key, value, callback) {
225
+ * // store key-value pair in current session
226
+ * sessions[sessionId][key] = value;
227
+ * callback();
228
+ * },
229
+ * get: (sessionId, key, callback) {
230
+ * // retrieve value by key in current session
231
+ * callback(null, sessions[sessionId][key]);
232
+ * },
233
+ * del: (sessionId, key, callback) {
234
+ * delete sessions[sessionId][key];
235
+ * callback();
236
+ * }
237
+ * });
238
+ *
239
+ * @param {Object} c object with implementation for get, set, start and end methods
240
+ * @param {String} a attribute of args
241
+ */
242
+ sessionConfig(c, a = 'sessionId') {
243
+ // creates a callback that calls the promise resolve and reject functions as well as the provided callback
244
+ const makeCb = (resolve, reject, cb) => (err, res) => {
245
+ if (err) {
246
+ if (cb) {
247
+ cb(err);
248
+ }
249
+ reject(err);
250
+ this.emit('error', err);
251
+ }
252
+ else {
253
+ if (cb) {
254
+ cb(null, res);
255
+ }
256
+ resolve(res);
257
+ }
258
+ };
259
+ // if p is a promise, handle its resolve and reject chains and invoke the provided callback
260
+ const resolveIfPromise = (p, resolve, reject, cb) => {
261
+ if (p && typeof p.then === 'function') {
262
+ p.then((res) => {
263
+ if (cb) {
264
+ cb(null, res);
265
+ }
266
+ resolve(res);
267
+ }).catch((err) => {
268
+ if (cb) {
269
+ cb(err);
270
+ }
271
+ reject(err);
272
+ this.emit('error', err);
273
+ });
274
+ }
275
+ };
276
+ // implement session methods based on user-defined handlers
277
+ this.session = {
278
+ start: async (cb) => new Promise((resolve, reject) => {
279
+ const res = c.start(this.args[a], makeCb(resolve, reject, cb));
280
+ resolveIfPromise(res, resolve, reject, cb);
281
+ }),
282
+ get: async (key, cb) => new Promise((resolve, reject) => {
283
+ const res = c.get(this.args[a], key, makeCb(resolve, reject, cb));
284
+ resolveIfPromise(res, resolve, reject, cb);
285
+ }),
286
+ del: async (key, cb) => new Promise((resolve, reject) => {
287
+ const res = c.del(this.args[a], key, makeCb(resolve, reject, cb));
288
+ resolveIfPromise(res, resolve, reject, cb);
289
+ }),
290
+ set: async (key, val, cb) => new Promise((resolve, reject) => {
291
+ const res = c.set(this.args[a], key, val, makeCb(resolve, reject, cb));
292
+ resolveIfPromise(res, resolve, reject, cb);
293
+ }),
294
+ end: async (cb) => new Promise((resolve, reject) => {
295
+ const res = c.end(this.args[a], makeCb(resolve, reject, cb));
296
+ resolveIfPromise(res, resolve, reject, cb);
297
+ })
298
+ };
299
+ }
300
+ /**
301
+ * Create a state on the ussd chain
302
+ *
303
+ * @param string name name of the state
304
+ * @param object options
305
+ * @param object options.next object mapping of route val to state names
306
+ * @param string options.defaultNext name of state to run when the given route from this state can't be resolved
307
+ * @param function options.run the method to run when this state is resolved
308
+ * @return TSUssdMenu the same instance of Ussd
309
+ */
310
+ state(name, options) {
311
+ const state = new TSUssdState(this);
312
+ this.states[name] = state;
313
+ state.name = name;
314
+ state.next = options.next ?? null;
315
+ state.run = options.run ?? null;
316
+ // default defaultNext to same state
317
+ state.defaultNext = options.defaultNext || name;
318
+ return this;
319
+ }
320
+ /**
321
+ * Create the start state of the ussd chain
322
+ */
323
+ startState(options) {
324
+ return this.state(TSUssdMenu.START_STATE, options);
325
+ }
326
+ /**
327
+ * Run the ussd menu
328
+ *
329
+ * @param function onResult a callback to handle on result
330
+ * @param TSUssdArgs options request args from the gateway api
331
+ */
332
+ run(onResult, map) {
333
+ if (map.provider !== this.args.provider) {
334
+ this.args = TSUssdMenu.mapArgs(this.args, map);
335
+ }
336
+ const mapAny = map;
337
+ const mapUnderscore = mapAny._;
338
+ if (mapUnderscore?.[String(this.args.phoneNumber)] && /\d/.test(String(mapUnderscore[String(this.args.phoneNumber)]))) {
339
+ this.args.phoneNumber = mapUnderscore[String(this.args.phoneNumber)];
340
+ }
341
+ else if (mapUnderscore) {
342
+ this.args._ = mapUnderscore;
343
+ }
344
+ this.onResult = onResult;
345
+ const run = async () => {
346
+ try {
347
+ const state = await this.resolveRoute(this.args.message);
348
+ this.current = state;
349
+ if (state)
350
+ this.runState(state);
351
+ }
352
+ catch (err) {
353
+ this.emit('error', err);
354
+ }
355
+ };
356
+ if (this.session) {
357
+ void this.session.start().then(run);
358
+ }
359
+ else {
360
+ void run();
361
+ }
362
+ }
363
+ }
364
+ exports.TSUssdMenu = TSUssdMenu;
@@ -0,0 +1,58 @@
1
+ import { TSUssdMenu } from './TSUssdMenu';
2
+ type PagerStrategy = {
3
+ term?: string;
4
+ type?: string;
5
+ };
6
+ export declare class TSUssdScreen {
7
+ menu: TSUssdMenu;
8
+ storage?: {
9
+ hget: (k: string, f: string) => Promise<unknown>;
10
+ hset: (k: string, f: string, d: unknown) => Promise<unknown>;
11
+ } | undefined;
12
+ strategies: {
13
+ truncate: (data: {
14
+ name?: string;
15
+ }[], c: PagerStrategy & {
16
+ term: string;
17
+ }, factor: number) => {
18
+ name?: string;
19
+ }[];
20
+ };
21
+ static nav(command: number | string): unknown;
22
+ static pagerChoice(max?: number, min?: number): string;
23
+ constructor(menu: TSUssdMenu, storage?: {
24
+ hget: (k: string, f: string) => Promise<unknown>;
25
+ hset: (k: string, f: string, d: unknown) => Promise<unknown>;
26
+ } | undefined);
27
+ render(items: unknown[]): void;
28
+ bind(m: number | string, s: unknown): void;
29
+ screen(title: number | string, items: Record<string, unknown>): any;
30
+ list(items: unknown[], title?: string, key?: string, meta?: Record<string, boolean>): string;
31
+ getAndSet(key: string, field: string): Promise<Record<string, unknown> | null>;
32
+ pager(items: {
33
+ name?: string;
34
+ length?: number;
35
+ }[] | undefined, key: string, pagerSize: number): Promise<{
36
+ meta: {
37
+ command: number;
38
+ total: number;
39
+ length: number;
40
+ offset: number;
41
+ limit: number;
42
+ };
43
+ data: unknown[];
44
+ }>;
45
+ _pager(items: unknown[] | undefined, key: string, pagerSize: number): Promise<{
46
+ meta: {
47
+ command: number;
48
+ total: number;
49
+ length: number;
50
+ offset: number;
51
+ limit: number;
52
+ };
53
+ data: unknown[];
54
+ }>;
55
+ pagerPickAndSet(dataSet: unknown[] | undefined, field: string, size?: number): Promise<any>;
56
+ checkPick(entity: unknown, state?: string): boolean;
57
+ }
58
+ export {};