ts-server-lib 0.0.48
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.
- package/LICENSE +1 -0
- package/README.md +8 -0
- package/db/TSJournal.d.ts +108 -0
- package/db/TSJournal.js +229 -0
- package/db/TSMongo.d.ts +103 -0
- package/db/TSMongo.js +516 -0
- package/db/TSRQW.d.ts +625 -0
- package/db/TSRQW.js +1204 -0
- package/db/TSRedis.d.ts +530 -0
- package/db/TSRedis.js +1368 -0
- package/db/TSRedisTB.d.ts +80 -0
- package/db/TSRedisTB.js +178 -0
- package/package.json +85 -0
- package/ussd/TSUssdMenu.d.ts +139 -0
- package/ussd/TSUssdMenu.js +368 -0
- package/ussd/TSUssdScreen.d.ts +58 -0
- package/ussd/TSUssdScreen.js +218 -0
- package/ussd/index.d.ts +3 -0
- package/ussd/index.js +19 -0
- package/ussd/providers/AfricasTalking.d.ts +3 -0
- package/ussd/providers/AfricasTalking.js +17 -0
- package/ussd/providers/AirtelDRC.d.ts +9 -0
- package/ussd/providers/AirtelDRC.js +31 -0
- package/ussd/providers/OrangeDRC.d.ts +5 -0
- package/ussd/providers/OrangeDRC.js +213 -0
- package/ussd/providers/VodacomDRC.d.ts +9 -0
- package/ussd/providers/VodacomDRC.js +48 -0
- package/ussd/providers/_.d.ts +55 -0
- package/ussd/providers/_.js +83 -0
- package/ussd/providers/index.d.ts +13 -0
- package/ussd/providers/index.js +56 -0
- package/utils/TSFifo.d.ts +109 -0
- package/utils/TSFifo.js +145 -0
- package/utils/TSFile.d.ts +36 -0
- package/utils/TSFile.js +244 -0
- package/utils/TSHash.d.ts +19 -0
- package/utils/TSHash.js +71 -0
- package/utils/TSRequest.d.ts +248 -0
- package/utils/TSRequest.js +689 -0
- package/utils/TSStub.d.ts +159 -0
- package/utils/TSStub.js +296 -0
- package/utils/abort.d.ts +18 -0
- package/utils/abort.js +97 -0
- package/utils/mime.json +11358 -0
- package/utils/object-keys.d.ts +39 -0
- package/utils/object-keys.js +52 -0
|
@@ -0,0 +1,368 @@
|
|
|
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
|
+
const object_keys_1 = require("../utils/object-keys");
|
|
19
|
+
class TSUssdState {
|
|
20
|
+
menu;
|
|
21
|
+
name;
|
|
22
|
+
run;
|
|
23
|
+
defaultNext;
|
|
24
|
+
val;
|
|
25
|
+
next = null;
|
|
26
|
+
constructor(menu) {
|
|
27
|
+
this.menu = menu;
|
|
28
|
+
this.name = null;
|
|
29
|
+
this.run = null;
|
|
30
|
+
this.defaultNext = null;
|
|
31
|
+
this.val = null;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
exports.TSUssdState = TSUssdState;
|
|
35
|
+
class TSUssdMenu extends events_1.EventEmitter {
|
|
36
|
+
map;
|
|
37
|
+
static START_STATE = '__start__';
|
|
38
|
+
static config = {
|
|
39
|
+
debug: true,
|
|
40
|
+
general: { separator: ')' },
|
|
41
|
+
navigation: { back: '00', main: '01' },
|
|
42
|
+
pager: {
|
|
43
|
+
default: 3, maxLength: 90, mapping: {},
|
|
44
|
+
strategies: [{ term: ' - ', enabled: true, type: 'truncate' }]
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
session;
|
|
48
|
+
args;
|
|
49
|
+
states;
|
|
50
|
+
result;
|
|
51
|
+
onResult;
|
|
52
|
+
current;
|
|
53
|
+
val;
|
|
54
|
+
static forOwn(items, cb) {
|
|
55
|
+
const obj = items;
|
|
56
|
+
for (const i in obj) {
|
|
57
|
+
if (Object.hasOwn(obj, i)) {
|
|
58
|
+
cb(obj[i], i);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
static mapArgs(args = {}, map = {}) {
|
|
63
|
+
const results = { provider: map.provider };
|
|
64
|
+
for (const p in args) {
|
|
65
|
+
if (Object.hasOwn(args, p) && Object.hasOwn(map, p)) {
|
|
66
|
+
results[String(map[p])] = args[p];
|
|
67
|
+
}
|
|
68
|
+
else if (Object.hasOwn(args, p)) {
|
|
69
|
+
results[p] = args[p];
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return results;
|
|
73
|
+
}
|
|
74
|
+
static cleanRoute(route) {
|
|
75
|
+
if (route && route.includes('*000')) {
|
|
76
|
+
const reset = route.substring(route.lastIndexOf('*000') + 4);
|
|
77
|
+
route = '*' + (reset === '0' ? '' : reset);
|
|
78
|
+
}
|
|
79
|
+
return route;
|
|
80
|
+
}
|
|
81
|
+
constructor(args = {}, map) {
|
|
82
|
+
super();
|
|
83
|
+
this.map = map;
|
|
84
|
+
this.session = null;
|
|
85
|
+
this.args = TSUssdMenu.mapArgs(args, map);
|
|
86
|
+
this.states = {};
|
|
87
|
+
this.result = '';
|
|
88
|
+
this.onResult = null;
|
|
89
|
+
this.val = '';
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* A callback after result is set
|
|
93
|
+
*/
|
|
94
|
+
callOnResult() {
|
|
95
|
+
if (this.onResult) {
|
|
96
|
+
this.onResult(this.result);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* This method send message to the client and take the session opened
|
|
101
|
+
*
|
|
102
|
+
* @param text string Message to sent
|
|
103
|
+
*/
|
|
104
|
+
con(text) {
|
|
105
|
+
this.result = 'CON ' + this.print(text);
|
|
106
|
+
this.callOnResult();
|
|
107
|
+
return this.result;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* This method send message to the client and close the session
|
|
111
|
+
*
|
|
112
|
+
* @param text string Message to sent
|
|
113
|
+
*/
|
|
114
|
+
end(text) {
|
|
115
|
+
this.result = 'END ' + this.print(text);
|
|
116
|
+
this.callOnResult();
|
|
117
|
+
if (this.session) {
|
|
118
|
+
this.session.end();
|
|
119
|
+
}
|
|
120
|
+
return this.result;
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* This method format the message and if debug calcualte the length
|
|
124
|
+
*
|
|
125
|
+
* @param text string Message to sent
|
|
126
|
+
*/
|
|
127
|
+
print(text) {
|
|
128
|
+
return TSUssdMenu.config.debug ? '[' + (this.current ? this.current.name : 'N/A') + ':' + text.length + '] ' + text : text;
|
|
129
|
+
}
|
|
130
|
+
testLinkRule(rule, val) {
|
|
131
|
+
// if rule starts with *, treat as regex
|
|
132
|
+
if (typeof rule === 'string' && rule.startsWith('*')) {
|
|
133
|
+
return new RegExp(rule.substring(1)).test(val);
|
|
134
|
+
}
|
|
135
|
+
return rule === val;
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Find state based on route — async/await, no external dependencies.
|
|
139
|
+
*
|
|
140
|
+
* next values may be: a string, a callback-based function (cb) => void,
|
|
141
|
+
* a function returning a string synchronously, or a function returning a Promise.
|
|
142
|
+
*/
|
|
143
|
+
async resolveRoute(route) {
|
|
144
|
+
route = TSUssdMenu.cleanRoute(route);
|
|
145
|
+
const parts = !route ? [] : route.split('*');
|
|
146
|
+
let state = this.states[TSUssdMenu.START_STATE];
|
|
147
|
+
// CLY-3: emptiness, not a count. Cold path compared with the feed, so this is consistency rather than
|
|
148
|
+
// measured cost — but the pattern is the one being removed everywhere, and leaving exceptions is how a
|
|
149
|
+
// rule stops being applied.
|
|
150
|
+
if (!state.next || !(0, object_keys_1.hasOwnKey)(state.next)) {
|
|
151
|
+
return this.states[state.defaultNext];
|
|
152
|
+
}
|
|
153
|
+
if ('' in state.next) {
|
|
154
|
+
parts.unshift('');
|
|
155
|
+
}
|
|
156
|
+
while (parts.length > 0) {
|
|
157
|
+
const part = parts.shift();
|
|
158
|
+
let nextFound = false;
|
|
159
|
+
this.val = part ?? '';
|
|
160
|
+
for (const [link, next] of Object.entries(state.next)) {
|
|
161
|
+
if (!this.testLinkRule(link, part ?? ''))
|
|
162
|
+
continue;
|
|
163
|
+
let nextPath;
|
|
164
|
+
if (typeof next === 'string') {
|
|
165
|
+
nextPath = next;
|
|
166
|
+
}
|
|
167
|
+
else if (typeof next === 'function') {
|
|
168
|
+
// supports: callback-based fn(cb), sync-returning fn, and promise-returning fn
|
|
169
|
+
nextPath = await new Promise((resolve, reject) => {
|
|
170
|
+
const result = next(resolve);
|
|
171
|
+
if (typeof result === 'string')
|
|
172
|
+
resolve(result);
|
|
173
|
+
else if (result && typeof result.then === 'function')
|
|
174
|
+
result.then(resolve).catch(reject);
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
if (!nextPath)
|
|
178
|
+
continue;
|
|
179
|
+
state = this.states[nextPath];
|
|
180
|
+
this.current = state;
|
|
181
|
+
if (!state)
|
|
182
|
+
throw new Error(`[TSUssdMenu]: declared state does not exist: ${nextPath}`);
|
|
183
|
+
state.val = part ?? '';
|
|
184
|
+
nextFound = true;
|
|
185
|
+
break;
|
|
186
|
+
}
|
|
187
|
+
if (!nextFound && state.defaultNext) {
|
|
188
|
+
state = this.states[state.defaultNext];
|
|
189
|
+
this.current = state;
|
|
190
|
+
state.val = part;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
return state;
|
|
194
|
+
}
|
|
195
|
+
runState(state) {
|
|
196
|
+
if (!state.run) {
|
|
197
|
+
return this.emit('error', new Error(`run function not defined for state: ${state.name}`));
|
|
198
|
+
}
|
|
199
|
+
state.run(state);
|
|
200
|
+
}
|
|
201
|
+
go(stateName) {
|
|
202
|
+
const state = this.states[stateName];
|
|
203
|
+
state.val = this.val;
|
|
204
|
+
this.current = state;
|
|
205
|
+
this.runState(state);
|
|
206
|
+
}
|
|
207
|
+
goStart() {
|
|
208
|
+
this.go(TSUssdMenu.START_STATE);
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Configure custom session handler cross-compatible between callbacks and promises
|
|
212
|
+
*
|
|
213
|
+
* @example Memory configuration example with callback
|
|
214
|
+
*
|
|
215
|
+
* let sessions = {};
|
|
216
|
+
* let menu = new UssdMenu();
|
|
217
|
+
* menu.sessionConfig({
|
|
218
|
+
* start: (sessionId, callback) {
|
|
219
|
+
* // initialize current session if it doesn't exist this is called by menu.run()
|
|
220
|
+
* if(!(sessionId in sessions)) sessions[sessionId] = {};
|
|
221
|
+
* callback();
|
|
222
|
+
* },
|
|
223
|
+
* end: (sessionId, callback) {
|
|
224
|
+
* // clear current session this is called by menu.end()
|
|
225
|
+
* delete sessions[sessionId];
|
|
226
|
+
* callback();
|
|
227
|
+
* },
|
|
228
|
+
* set: (sessionId, key, value, callback) {
|
|
229
|
+
* // store key-value pair in current session
|
|
230
|
+
* sessions[sessionId][key] = value;
|
|
231
|
+
* callback();
|
|
232
|
+
* },
|
|
233
|
+
* get: (sessionId, key, callback) {
|
|
234
|
+
* // retrieve value by key in current session
|
|
235
|
+
* callback(null, sessions[sessionId][key]);
|
|
236
|
+
* },
|
|
237
|
+
* del: (sessionId, key, callback) {
|
|
238
|
+
* delete sessions[sessionId][key];
|
|
239
|
+
* callback();
|
|
240
|
+
* }
|
|
241
|
+
* });
|
|
242
|
+
*
|
|
243
|
+
* @param {Object} c object with implementation for get, set, start and end methods
|
|
244
|
+
* @param {String} a attribute of args
|
|
245
|
+
*/
|
|
246
|
+
sessionConfig(c, a = 'sessionId') {
|
|
247
|
+
// creates a callback that calls the promise resolve and reject functions as well as the provided callback
|
|
248
|
+
const makeCb = (resolve, reject, cb) => (err, res) => {
|
|
249
|
+
if (err) {
|
|
250
|
+
if (cb) {
|
|
251
|
+
cb(err);
|
|
252
|
+
}
|
|
253
|
+
reject(err);
|
|
254
|
+
this.emit('error', err);
|
|
255
|
+
}
|
|
256
|
+
else {
|
|
257
|
+
if (cb) {
|
|
258
|
+
cb(null, res);
|
|
259
|
+
}
|
|
260
|
+
resolve(res);
|
|
261
|
+
}
|
|
262
|
+
};
|
|
263
|
+
// if p is a promise, handle its resolve and reject chains and invoke the provided callback
|
|
264
|
+
const resolveIfPromise = (p, resolve, reject, cb) => {
|
|
265
|
+
if (p && typeof p.then === 'function') {
|
|
266
|
+
p.then((res) => {
|
|
267
|
+
if (cb) {
|
|
268
|
+
cb(null, res);
|
|
269
|
+
}
|
|
270
|
+
resolve(res);
|
|
271
|
+
}).catch((err) => {
|
|
272
|
+
if (cb) {
|
|
273
|
+
cb(err);
|
|
274
|
+
}
|
|
275
|
+
reject(err);
|
|
276
|
+
this.emit('error', err);
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
};
|
|
280
|
+
// implement session methods based on user-defined handlers
|
|
281
|
+
this.session = {
|
|
282
|
+
start: async (cb) => new Promise((resolve, reject) => {
|
|
283
|
+
const res = c.start(this.args[a], makeCb(resolve, reject, cb));
|
|
284
|
+
resolveIfPromise(res, resolve, reject, cb);
|
|
285
|
+
}),
|
|
286
|
+
get: async (key, cb) => new Promise((resolve, reject) => {
|
|
287
|
+
const res = c.get(this.args[a], key, makeCb(resolve, reject, cb));
|
|
288
|
+
resolveIfPromise(res, resolve, reject, cb);
|
|
289
|
+
}),
|
|
290
|
+
del: async (key, cb) => new Promise((resolve, reject) => {
|
|
291
|
+
const res = c.del(this.args[a], key, makeCb(resolve, reject, cb));
|
|
292
|
+
resolveIfPromise(res, resolve, reject, cb);
|
|
293
|
+
}),
|
|
294
|
+
set: async (key, val, cb) => new Promise((resolve, reject) => {
|
|
295
|
+
const res = c.set(this.args[a], key, val, makeCb(resolve, reject, cb));
|
|
296
|
+
resolveIfPromise(res, resolve, reject, cb);
|
|
297
|
+
}),
|
|
298
|
+
end: async (cb) => new Promise((resolve, reject) => {
|
|
299
|
+
const res = c.end(this.args[a], makeCb(resolve, reject, cb));
|
|
300
|
+
resolveIfPromise(res, resolve, reject, cb);
|
|
301
|
+
})
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
/**
|
|
305
|
+
* Create a state on the ussd chain
|
|
306
|
+
*
|
|
307
|
+
* @param string name name of the state
|
|
308
|
+
* @param object options
|
|
309
|
+
* @param object options.next object mapping of route val to state names
|
|
310
|
+
* @param string options.defaultNext name of state to run when the given route from this state can't be resolved
|
|
311
|
+
* @param function options.run the method to run when this state is resolved
|
|
312
|
+
* @return TSUssdMenu the same instance of Ussd
|
|
313
|
+
*/
|
|
314
|
+
state(name, options) {
|
|
315
|
+
const state = new TSUssdState(this);
|
|
316
|
+
this.states[name] = state;
|
|
317
|
+
state.name = name;
|
|
318
|
+
state.next = options.next ?? null;
|
|
319
|
+
state.run = options.run ?? null;
|
|
320
|
+
// default defaultNext to same state
|
|
321
|
+
state.defaultNext = options.defaultNext || name;
|
|
322
|
+
return this;
|
|
323
|
+
}
|
|
324
|
+
/**
|
|
325
|
+
* Create the start state of the ussd chain
|
|
326
|
+
*/
|
|
327
|
+
startState(options) {
|
|
328
|
+
return this.state(TSUssdMenu.START_STATE, options);
|
|
329
|
+
}
|
|
330
|
+
/**
|
|
331
|
+
* Run the ussd menu
|
|
332
|
+
*
|
|
333
|
+
* @param function onResult a callback to handle on result
|
|
334
|
+
* @param TSUssdArgs options request args from the gateway api
|
|
335
|
+
*/
|
|
336
|
+
run(onResult, map) {
|
|
337
|
+
if (map.provider !== this.args.provider) {
|
|
338
|
+
this.args = TSUssdMenu.mapArgs(this.args, map);
|
|
339
|
+
}
|
|
340
|
+
const mapAny = map;
|
|
341
|
+
const mapUnderscore = mapAny._;
|
|
342
|
+
if (mapUnderscore?.[String(this.args.phoneNumber)] && /\d/.test(String(mapUnderscore[String(this.args.phoneNumber)]))) {
|
|
343
|
+
this.args.phoneNumber = mapUnderscore[String(this.args.phoneNumber)];
|
|
344
|
+
}
|
|
345
|
+
else if (mapUnderscore) {
|
|
346
|
+
this.args._ = mapUnderscore;
|
|
347
|
+
}
|
|
348
|
+
this.onResult = onResult;
|
|
349
|
+
const run = async () => {
|
|
350
|
+
try {
|
|
351
|
+
const state = await this.resolveRoute(this.args.message);
|
|
352
|
+
this.current = state;
|
|
353
|
+
if (state)
|
|
354
|
+
this.runState(state);
|
|
355
|
+
}
|
|
356
|
+
catch (err) {
|
|
357
|
+
this.emit('error', err);
|
|
358
|
+
}
|
|
359
|
+
};
|
|
360
|
+
if (this.session) {
|
|
361
|
+
void this.session.start().then(run);
|
|
362
|
+
}
|
|
363
|
+
else {
|
|
364
|
+
void run();
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
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 {};
|
|
@@ -0,0 +1,218 @@
|
|
|
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.TSUssdScreen = void 0;
|
|
7
|
+
const i18n_1 = __importDefault(require("i18n"));
|
|
8
|
+
const TSUssdMenu_1 = require("./TSUssdMenu");
|
|
9
|
+
class TSUssdScreen {
|
|
10
|
+
menu;
|
|
11
|
+
storage;
|
|
12
|
+
strategies = {
|
|
13
|
+
truncate: (data, c, factor) => {
|
|
14
|
+
let parts = [];
|
|
15
|
+
let factors = {};
|
|
16
|
+
let f = 0;
|
|
17
|
+
TSUssdMenu_1.TSUssdMenu.forOwn(data, (i) => {
|
|
18
|
+
const item = i;
|
|
19
|
+
factors = {};
|
|
20
|
+
if (item.name && item.name.length > factor) {
|
|
21
|
+
parts = item.name.split(c.term);
|
|
22
|
+
if (parts.length > 1) {
|
|
23
|
+
f = factor / parts.length;
|
|
24
|
+
if (parts[0].length < f) {
|
|
25
|
+
factors[1] = factor - parts[0].length;
|
|
26
|
+
factors[0] = parts[0].length;
|
|
27
|
+
}
|
|
28
|
+
else if (parts[1].length < f) {
|
|
29
|
+
factors[0] = factor - parts[1].length;
|
|
30
|
+
factors[1] = parts[1].length;
|
|
31
|
+
}
|
|
32
|
+
parts.forEach((v, k) => parts[k] = v.substring(0, factors[k] ?? f));
|
|
33
|
+
item.name = parts.join(c.term);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
return data;
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
static nav(command) {
|
|
41
|
+
return TSUssdMenu_1.TSUssdMenu.config.navigation[String(command)];
|
|
42
|
+
}
|
|
43
|
+
static pagerChoice(max = 3, min = 1) {
|
|
44
|
+
return '*\\^\\[' + min + '-' + max + '\\]\\{1\\}\\$';
|
|
45
|
+
}
|
|
46
|
+
constructor(menu, storage) {
|
|
47
|
+
this.menu = menu;
|
|
48
|
+
this.storage = storage;
|
|
49
|
+
}
|
|
50
|
+
render(items) {
|
|
51
|
+
TSUssdMenu_1.TSUssdMenu.forOwn(items, (m) => { const mr = m; if (mr)
|
|
52
|
+
TSUssdMenu_1.TSUssdMenu.forOwn(mr, (v, k) => { if (v !== undefined && k !== undefined)
|
|
53
|
+
this.bind(k, v); }); });
|
|
54
|
+
}
|
|
55
|
+
bind(m, s) {
|
|
56
|
+
const sObj = s;
|
|
57
|
+
if (m === 'start') {
|
|
58
|
+
this.menu.startState({
|
|
59
|
+
run: () => this.menu.con(sObj.next ? i18n_1.default.__(String(m) + '.' + sObj.key, TSUssdMenu_1.TSUssdMenu.config.general || {}) : this.screen(m, sObj)), next: sObj.next ? sObj.next : sObj
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
let options = {};
|
|
64
|
+
if (typeof s === 'function') {
|
|
65
|
+
options.run = s;
|
|
66
|
+
}
|
|
67
|
+
else if (typeof s === 'object' && s !== null && Object.hasOwn(s, 'next')) {
|
|
68
|
+
const navMain = TSUssdScreen.nav('main');
|
|
69
|
+
sObj.next = { [navMain]: TSUssdMenu_1.TSUssdMenu.START_STATE, ...sObj.next };
|
|
70
|
+
options = sObj;
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
options.run = () => this.menu.con(this.screen(m, sObj));
|
|
74
|
+
const navMain = TSUssdScreen.nav('main');
|
|
75
|
+
options.next = { [navMain]: TSUssdMenu_1.TSUssdMenu.START_STATE, ...sObj };
|
|
76
|
+
}
|
|
77
|
+
this.menu.state(String(m), options);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
screen(title, items) {
|
|
81
|
+
let context = i18n_1.default.__(String(title), TSUssdMenu_1.TSUssdMenu.config.general || {});
|
|
82
|
+
const data = TSUssdMenu_1.TSUssdMenu.config.general || {};
|
|
83
|
+
TSUssdMenu_1.TSUssdMenu.forOwn(items, (v, k) => {
|
|
84
|
+
if (v != null && !isNaN(Number(k))) {
|
|
85
|
+
context += isNaN(Number(k)) ? '\n' : '\n' + k + data.separator + ' ';
|
|
86
|
+
context += i18n_1.default.__(String(title) + '.' + v, data || {});
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
if (this.menu.current.name !== TSUssdMenu_1.TSUssdMenu.START_STATE) {
|
|
90
|
+
context += '\n' + TSUssdScreen.nav('main');
|
|
91
|
+
context += data.separator + ' ' + i18n_1.default.__('main', data);
|
|
92
|
+
}
|
|
93
|
+
return context;
|
|
94
|
+
}
|
|
95
|
+
list(items, title = '', key = 'name', meta = { back: true, main: true }) {
|
|
96
|
+
let context = title;
|
|
97
|
+
TSUssdMenu_1.TSUssdMenu.forOwn(items, (v, k) => {
|
|
98
|
+
if (typeof v === 'object' && v !== null && key in v) {
|
|
99
|
+
context += isNaN(Number(k)) ? '\n' : '\n' + (parseInt(String(k), 10) + 1) + TSUssdMenu_1.TSUssdMenu.config.general.separator + ' ';
|
|
100
|
+
context += v[key];
|
|
101
|
+
}
|
|
102
|
+
else if (['number', 'string', 'boolean'].includes(typeof v)) {
|
|
103
|
+
context += String(v).indexOf('\n') === -1 ? '\n' + v : String(v);
|
|
104
|
+
}
|
|
105
|
+
else {
|
|
106
|
+
console.error(`TSUssdScreen:list[${key} -> ${k}] not mapped with ->`, JSON.stringify(v, null, 2));
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
TSUssdMenu_1.TSUssdMenu.forOwn(meta, (v, k) => { if (k !== undefined)
|
|
110
|
+
context += '\n' + TSUssdScreen.nav(String(k)) + TSUssdMenu_1.TSUssdMenu.config.general.separator + ' ' + i18n_1.default.__(String(k)); });
|
|
111
|
+
return context;
|
|
112
|
+
}
|
|
113
|
+
async getAndSet(key, field) {
|
|
114
|
+
if (!this.storage)
|
|
115
|
+
throw new Error('TSUssdScreen: storage not configured');
|
|
116
|
+
let data = await this.storage.hget(String(this.menu.args.phoneNumber), field);
|
|
117
|
+
if (this.menu.val && this.menu.val.toString().length > 0) {
|
|
118
|
+
if (!data) {
|
|
119
|
+
data = { [key]: this.menu.val };
|
|
120
|
+
}
|
|
121
|
+
else {
|
|
122
|
+
data[key] = this.menu.val;
|
|
123
|
+
}
|
|
124
|
+
await this.storage.hset(String(this.menu.args.phoneNumber), field, data);
|
|
125
|
+
}
|
|
126
|
+
return data;
|
|
127
|
+
}
|
|
128
|
+
async pager(items = [], key, pagerSize) {
|
|
129
|
+
let length = 0;
|
|
130
|
+
const result = await this._pager(items, key, pagerSize);
|
|
131
|
+
TSUssdMenu_1.TSUssdMenu.forOwn(result.data, (i) => { const it = i; length += (it.name?.length ?? 0) || (it.length ?? 0); });
|
|
132
|
+
if (TSUssdMenu_1.TSUssdMenu.config.pager.maxLength < length) {
|
|
133
|
+
TSUssdMenu_1.TSUssdMenu.forOwn(TSUssdMenu_1.TSUssdMenu.config.pager.strategies, (s) => {
|
|
134
|
+
const strat = s;
|
|
135
|
+
if (strat.enabled && strat.type && this.strategies[strat.type]) {
|
|
136
|
+
this.strategies[strat.type](result.data, strat, parseInt((TSUssdMenu_1.TSUssdMenu.config.pager.maxLength / pagerSize).toString(), 10));
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
return result;
|
|
141
|
+
}
|
|
142
|
+
async _pager(items = [], key, pagerSize) {
|
|
143
|
+
if (!this.storage)
|
|
144
|
+
throw new Error('TSUssdScreen: storage not configured');
|
|
145
|
+
const pagerOptions = (await this.storage.hget(String(this.menu.args.phoneNumber), key + 'Pager') || {}), command = parseInt(this.menu.val, 10), calculatePaging = () => {
|
|
146
|
+
if (pagerSize >= items.length) {
|
|
147
|
+
offset = 0;
|
|
148
|
+
limit = pagerSize;
|
|
149
|
+
}
|
|
150
|
+
else if (command === 7) {
|
|
151
|
+
limit = offset;
|
|
152
|
+
offset -= pagerSize;
|
|
153
|
+
if (offset < 0) {
|
|
154
|
+
offset = 0;
|
|
155
|
+
limit = pagerSize;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
else if (command === 9) {
|
|
159
|
+
offset = limit;
|
|
160
|
+
limit = offset + pagerSize;
|
|
161
|
+
if (limit > items.length) {
|
|
162
|
+
limit = items.length;
|
|
163
|
+
offset = limit - pagerSize;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
let offset = pagerOptions.offset || 0, limit = pagerOptions.limit || (offset + pagerSize);
|
|
168
|
+
calculatePaging();
|
|
169
|
+
let data = items.slice(offset, limit), { length } = data, dataLength = 0;
|
|
170
|
+
TSUssdMenu_1.TSUssdMenu.forOwn(data, (i) => { const it = i; dataLength += it.name ? it.name.length : (it.length ?? 0); });
|
|
171
|
+
if (TSUssdMenu_1.TSUssdMenu.config.pager.maxLength < dataLength) {
|
|
172
|
+
pagerSize = pagerSize - 1;
|
|
173
|
+
offset = pagerOptions.offset || 0;
|
|
174
|
+
limit = pagerOptions.limit || (offset + pagerSize);
|
|
175
|
+
calculatePaging();
|
|
176
|
+
data = items.slice(offset, limit);
|
|
177
|
+
length = data.length;
|
|
178
|
+
}
|
|
179
|
+
await this.storage.hset(String(this.menu.args.phoneNumber), key + 'Pager', { offset: offset, limit: limit });
|
|
180
|
+
if (offset < 1 && items.length > limit) {
|
|
181
|
+
data.push(i18n_1.default.__('\n9 [Next]', TSUssdMenu_1.TSUssdMenu.config.general));
|
|
182
|
+
}
|
|
183
|
+
else if (items.length > limit && offset > 0) {
|
|
184
|
+
data.push(i18n_1.default.__('\n7 [Prev] | 9 [Next]', TSUssdMenu_1.TSUssdMenu.config.general));
|
|
185
|
+
}
|
|
186
|
+
else if (offset > 0) {
|
|
187
|
+
data.push(i18n_1.default.__('\n7 [Prev]', TSUssdMenu_1.TSUssdMenu.config.general));
|
|
188
|
+
}
|
|
189
|
+
return { meta: { command: command, total: items.length, length: length, offset: offset, limit: limit }, data: data };
|
|
190
|
+
}
|
|
191
|
+
async pagerPickAndSet(dataSet = [], field, size = 0) {
|
|
192
|
+
if (!this.storage)
|
|
193
|
+
throw new Error('TSUssdScreen: storage not configured');
|
|
194
|
+
const pagerOptions = (await this.storage.hget(String(this.menu.args.phoneNumber), field + 'Pager') || {}), offset = pagerOptions.offset || 0, limit = pagerOptions.limit || (offset + size), cursor = parseInt(this.menu.val, 10) - 1;
|
|
195
|
+
let data;
|
|
196
|
+
dataSet = size ? dataSet.slice(offset, limit) : dataSet;
|
|
197
|
+
if (size && dataSet.length >= cursor) {
|
|
198
|
+
data = dataSet[cursor];
|
|
199
|
+
}
|
|
200
|
+
if (!size) {
|
|
201
|
+
data = dataSet[cursor + offset];
|
|
202
|
+
}
|
|
203
|
+
await this.storage.hset(String(this.menu.args.phoneNumber), field, data);
|
|
204
|
+
return data;
|
|
205
|
+
}
|
|
206
|
+
checkPick(entity, state) {
|
|
207
|
+
if (!entity && state) {
|
|
208
|
+
this.menu.go(state);
|
|
209
|
+
return false;
|
|
210
|
+
}
|
|
211
|
+
else if (!entity) {
|
|
212
|
+
this.menu.end(i18n_1.default.__('invalid.input', { input: this.menu.val }));
|
|
213
|
+
return false;
|
|
214
|
+
}
|
|
215
|
+
return true;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
exports.TSUssdScreen = TSUssdScreen;
|
package/ussd/index.d.ts
ADDED
package/ussd/index.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./TSUssdMenu"), exports);
|
|
18
|
+
__exportStar(require("./TSUssdScreen"), exports);
|
|
19
|
+
__exportStar(require("./providers/index"), exports);
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.response = response;
|
|
4
|
+
exports.request = request;
|
|
5
|
+
exports.text = text;
|
|
6
|
+
const _1 = require("./_");
|
|
7
|
+
function response(text, _rr) {
|
|
8
|
+
return text;
|
|
9
|
+
}
|
|
10
|
+
function request(request, _response) {
|
|
11
|
+
const body = (0, _1.body)(request);
|
|
12
|
+
request.body = (0, _1.mapArgs)(body, _1.providersMap[_1.TSUssdProviders.africastalking]);
|
|
13
|
+
return request.body;
|
|
14
|
+
}
|
|
15
|
+
function text(_prev = '', body) {
|
|
16
|
+
return body.message;
|
|
17
|
+
}
|