corebasic 1.0.230 → 1.0.232
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/dist/libs/auth.js +5 -4
- package/dist/libs/compression.js +1 -7
- package/dist/libs/dip/suffix/index.js +8 -6
- package/dist/libs/dip/suffix/policy.js +6 -4
- package/dist/libs/dip/suffix/query.js +290 -279
- package/dist/libs/dip/suffix/suffix.js +17 -13
- package/dist/libs/dip/suffix/tests/date.test.js +21 -34
- package/dist/libs/dip/suffix/tests/policy.test.js +41 -80
- package/dist/libs/dip.js +29 -26
- package/dist/libs/dipper.js +0 -1
- package/dist/libs/elabase.js +1 -1
- package/dist/libs/features.js +110 -60
- package/dist/libs/kafka.js +6 -5
- package/dist/libs/messaging.js +7 -4
- package/dist/libs/session.js +2 -2
- package/libs/auth.ts +48 -9
- package/libs/compression.ts +6 -1
- package/libs/cpp.ts +15 -13
- package/libs/dip/suffix/date.ts +6 -6
- package/libs/dip/suffix/index.ts +41 -26
- package/libs/dip/suffix/policy.ts +16 -13
- package/libs/dip/suffix/query.ts +289 -289
- package/libs/dip/suffix/suffix.ts +26 -20
- package/libs/dip/suffix/tests/date.test.ts +32 -64
- package/libs/dip/suffix/tests/policy.applySuffixPolicy.test.ts +20 -20
- package/libs/dip/suffix/tests/policy.test.ts +41 -109
- package/libs/dip/suffix/tests/suffix.test.ts +2 -2
- package/libs/dip.ts +61 -33
- package/libs/dipper.ts +25 -10
- package/libs/elabase.ts +1 -1
- package/libs/features.ts +232 -95
- package/libs/kafka.ts +75 -14
- package/libs/messaging.ts +80 -19
- package/libs/session.ts +21 -10
- package/package.json +1 -1
package/dist/libs/features.js
CHANGED
|
@@ -16,10 +16,13 @@ import axios from 'axios';
|
|
|
16
16
|
// @ts-ignore
|
|
17
17
|
import jwt from 'jsonwebtoken';
|
|
18
18
|
let features = {};
|
|
19
|
-
let apis = {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
const
|
|
19
|
+
let apis = {
|
|
20
|
+
// <api>: {<feature>: FeatureEntry}
|
|
21
|
+
};
|
|
22
|
+
const getFeatureMethod = (api) => api.split(' ')[0].toLowerCase();
|
|
23
|
+
const getFeatureUrl = (api) => { const url = api.split(' ')[1]; if (!url)
|
|
24
|
+
throw new Error(`Invalid Feature url in api: ${api}`); return url; };
|
|
25
|
+
const getFeature = (name) => features[name];
|
|
23
26
|
const DEPLOY_TOKEN_SECRET = process.env.DEPLOY_TOKEN_SECRET || "MY_SECRET_DEPLOY_TOKEN";
|
|
24
27
|
const SERVICE_ACCESS_TOKEN = jwt.sign({ app: process.env.APP_DEPLOYMENT_NAME }, DEPLOY_TOKEN_SECRET, { expiresIn: '365d' });
|
|
25
28
|
const NDCURVE_DEVELOPER_LICENSE_ACCESS_TOKEN = await (async () => { if (process.env.NDCURVE_DEVELOPER_SERVICE)
|
|
@@ -50,7 +53,7 @@ async function loadLocalFeatures() {
|
|
|
50
53
|
let SLYP_FEATURES_LIST = await loadLocalFeatures();
|
|
51
54
|
let appId = Utils.uid();
|
|
52
55
|
let SERVICE_ADDRESS = process.env.APP_ENDPOINT || 'http://127.0.0.1:3000';
|
|
53
|
-
export const get = feature => {
|
|
56
|
+
export const get = (feature) => {
|
|
54
57
|
const throwError = () => { throw new Error(`Feature ${feature} not found in internal or external list during Features.subscribe call`); };
|
|
55
58
|
const baseFeature = getFeature(feature);
|
|
56
59
|
let { api, service = SERVICE_ADDRESS, topic } = baseFeature ? baseFeature : (SLYP_FEATURES_LIST[feature] ?? throwError());
|
|
@@ -68,16 +71,27 @@ export const send = async (meta, feature, data, params) => {
|
|
|
68
71
|
if (url.includes(':'))
|
|
69
72
|
throw new Error(`Error: Internal feature call send on feature: ${feature} when api/params/substitution`);
|
|
70
73
|
}
|
|
71
|
-
|
|
74
|
+
const payload = { ...meta, data, feature, txn: Utils.uid() };
|
|
72
75
|
service = service.replace('http://slyp.app', 'https://slyp.app');
|
|
73
76
|
// return (await axios[method](`${service}${url}`, { data: payload, headers: {jwt: headers.jwt}, timeout: 1000 })).data
|
|
74
77
|
// Used to work. But request to 127.0.0.1 fails to send the specified headers along with the request.
|
|
75
78
|
// return (await axios[method](`${service}${url}`, payload, {headers: {jwt: SERVICE_ACCESS_TOKEN, service: true}, timeout: 1000 })).data // Worked Earlier, but issue spotted
|
|
76
|
-
if (
|
|
79
|
+
if (baseFeature) { // Local call
|
|
77
80
|
let response;
|
|
78
|
-
let req = {
|
|
81
|
+
let req = { body: payload, params: params ?? {}, method, path: url, url: '' };
|
|
79
82
|
req = JSON.parse(JSON.stringify(req));
|
|
80
|
-
|
|
83
|
+
const callback = (payload) => { response = payload; };
|
|
84
|
+
const res = {
|
|
85
|
+
json: callback,
|
|
86
|
+
send: callback,
|
|
87
|
+
end: () => { },
|
|
88
|
+
status: (_status) => { return { send: callback, json: callback }; },
|
|
89
|
+
sendStatus: (_status) => { },
|
|
90
|
+
setHeader: (_header, _value) => { },
|
|
91
|
+
flushHeaders: () => { },
|
|
92
|
+
headersSent: false,
|
|
93
|
+
writableEnded: false,
|
|
94
|
+
};
|
|
81
95
|
await apiHandler(req, res);
|
|
82
96
|
return response;
|
|
83
97
|
}
|
|
@@ -109,7 +123,8 @@ async function announce() {
|
|
|
109
123
|
function getFeaturelessFeature(req) {
|
|
110
124
|
if (Session.ALLOWED_URLS.includes(req.path)) {
|
|
111
125
|
for (let key in features) {
|
|
112
|
-
|
|
126
|
+
const feature = features[key];
|
|
127
|
+
if (feature.featureless && feature.api === req.method.toUpperCase() + ' ' + req.path) {
|
|
113
128
|
return getFeature(key);
|
|
114
129
|
}
|
|
115
130
|
}
|
|
@@ -129,44 +144,77 @@ const apiHandler = async (req, res) => {
|
|
|
129
144
|
console.warn(`Feature: ${req.body.feature} not available`);
|
|
130
145
|
throw { status: 404, message: `Resource not found. Feature ${req.body.feature} not available.` };
|
|
131
146
|
}
|
|
147
|
+
feature = feature; // typescript validation with !
|
|
132
148
|
let topic = feature.topic;
|
|
133
|
-
let params = getFeatureUrl(feature.api).split("/").filter(item => item.startsWith(":")).map(item => item.replace(":", ""));
|
|
149
|
+
let params = getFeatureUrl(feature.api).split("/").filter((item) => item.startsWith(":")).map((item) => item.replace(":", ""));
|
|
134
150
|
for (let param of params)
|
|
135
151
|
if (!req.params[param])
|
|
136
152
|
throw { status: 404, message: "Resource not found. One or more url parameter not specified." };
|
|
137
|
-
let meta = { ...req.body, data: undefined };
|
|
138
|
-
req.meta = meta;
|
|
153
|
+
let meta = { ...req.body, data: undefined }; // TODO: Must also include invoiceTxn
|
|
139
154
|
if (process.env.USE_DEFAULT_COMPANY) {
|
|
140
|
-
|
|
141
|
-
|
|
155
|
+
meta.company = 'DEFAULT_COMPANY';
|
|
156
|
+
meta.outlet = 'DEFAULT_OUTLET';
|
|
142
157
|
}
|
|
158
|
+
meta.txn = meta.txn ?? Utils.uid();
|
|
159
|
+
const message = {
|
|
160
|
+
meta,
|
|
161
|
+
data: req.body.data,
|
|
162
|
+
params: req.params,
|
|
163
|
+
// To be removed 1
|
|
164
|
+
date: new Date().getTime(),
|
|
165
|
+
// To be removed 2
|
|
166
|
+
topic,
|
|
167
|
+
feature: req.body.feature,
|
|
168
|
+
user: req.body.user,
|
|
169
|
+
txn: meta.txn,
|
|
170
|
+
};
|
|
171
|
+
const appReq = {
|
|
172
|
+
meta: message.meta,
|
|
173
|
+
body: message,
|
|
174
|
+
params: message.params,
|
|
175
|
+
method: method,
|
|
176
|
+
path: req.path,
|
|
177
|
+
url: req.url,
|
|
178
|
+
headers: req.headers,
|
|
179
|
+
query: req.query,
|
|
180
|
+
on: req.on,
|
|
181
|
+
};
|
|
182
|
+
const appRes = res;
|
|
143
183
|
if (method === "get") {
|
|
144
184
|
try {
|
|
145
|
-
req.body
|
|
146
|
-
|
|
185
|
+
// await feature.handler({...req, headers: req.headers, body: {...req.body, topic} }, res)
|
|
186
|
+
// req.body = {...req.body, topic}
|
|
187
|
+
await feature.handler(appReq, appRes);
|
|
147
188
|
}
|
|
148
189
|
catch (err) {
|
|
149
190
|
if (process.env.DEBUG_MODE)
|
|
150
|
-
console.
|
|
191
|
+
console.error('Error: Feature: ', req.body?.feature ?? (featureless ? `Featureless Api:${featureless.api}` : undefined), err);
|
|
151
192
|
throw { status: 500, message: "Failed to GET feature", ...err };
|
|
152
193
|
}
|
|
153
194
|
}
|
|
154
195
|
else if (method !== "get" && feature.bypass) {
|
|
155
196
|
try {
|
|
156
|
-
await feature.handler(topic,
|
|
197
|
+
await feature.handler(topic, message, appReq, appRes);
|
|
157
198
|
}
|
|
158
199
|
catch (err) {
|
|
159
200
|
if (process.env.DEBUG_MODE)
|
|
160
|
-
console.
|
|
201
|
+
console.error('Error: Feature: ', req.body?.feature ?? (featureless ? `Featureless Api:${featureless.api}` : undefined), err);
|
|
161
202
|
throw { status: 500, message: "Failed to POST feature", ...err };
|
|
162
203
|
}
|
|
163
204
|
}
|
|
164
|
-
else
|
|
165
|
-
|
|
205
|
+
else {
|
|
206
|
+
try {
|
|
207
|
+
await Kafka.send(`Features.${topic}`, message, message.user, { compression: Kafka.CompressionTypes.GZIP });
|
|
208
|
+
}
|
|
209
|
+
catch {
|
|
210
|
+
throw { status: 500, message: "Failed to queue the transaction" };
|
|
211
|
+
}
|
|
212
|
+
appRes.json({ data: { txn: message.txn, success: true, status: "Queued", featureQueued: true } });
|
|
213
|
+
}
|
|
166
214
|
}
|
|
167
215
|
catch (err) {
|
|
168
216
|
if (process.env.DEBUG_MODE)
|
|
169
|
-
console.
|
|
217
|
+
console.error('Error: Feature: ', req.body?.feature ?? (featureless ? `Featureless Api:${featureless.api}` : undefined), err);
|
|
170
218
|
try { // Sometimes error occurs when disconnecting stream from front end
|
|
171
219
|
res.status(err.status ?? 500).json(err);
|
|
172
220
|
}
|
|
@@ -203,6 +251,8 @@ function registerApi() {
|
|
|
203
251
|
for (let api in apis) {
|
|
204
252
|
let method = getFeatureMethod(api);
|
|
205
253
|
let url = api.split(' ')[1];
|
|
254
|
+
if (!url)
|
|
255
|
+
throw new Error(`No url path found in api ${api} during Features.registerApi()`);
|
|
206
256
|
if (api === 'GET /transactions/:id')
|
|
207
257
|
continue;
|
|
208
258
|
ExpressApp[method](url, apiHandler);
|
|
@@ -219,10 +269,10 @@ export const start = async (app, url, file) => {
|
|
|
219
269
|
app.get('/features', async (req, res) => {
|
|
220
270
|
let exp_features = {};
|
|
221
271
|
for (let [key, { api }] of Object.entries(features))
|
|
222
|
-
exp_features[key] = { api, service: `${SERVICE_ADDRESS}
|
|
272
|
+
exp_features[key] = { api, service: `${SERVICE_ADDRESS}`, subscribe: '', topic: '' };
|
|
223
273
|
if (process.env.LOAD_LOCAL_FEATURES) {
|
|
224
274
|
for (let key in SLYP_FEATURES_LIST)
|
|
225
|
-
SLYP_FEATURES_LIST[key].headers = { JWT: SERVICE_ACCESS_TOKEN, service: true, NDCURVE_DEVELOPER_LICENSE_ACCESS_TOKEN };
|
|
275
|
+
SLYP_FEATURES_LIST[key].headers = { JWT: SERVICE_ACCESS_TOKEN, service: true, NDCURVE_DEVELOPER_LICENSE_ACCESS_TOKEN }; // TODO: CRITICAL: Security issue
|
|
226
276
|
}
|
|
227
277
|
res.json({ data: { ...SLYP_FEATURES_LIST, ...exp_features } });
|
|
228
278
|
});
|
|
@@ -230,20 +280,21 @@ export const start = async (app, url, file) => {
|
|
|
230
280
|
await registerFeatures(features);
|
|
231
281
|
// Get unique kafka topics list from features
|
|
232
282
|
let kafkaTopics = [];
|
|
233
|
-
for (
|
|
283
|
+
for (const name in features) {
|
|
234
284
|
let feature = features[name];
|
|
235
|
-
if (feature.api.split(' ')[0]
|
|
285
|
+
if (feature.api.split(' ')[0]?.toLowerCase() === "get")
|
|
236
286
|
continue;
|
|
237
287
|
kafkaTopics = [...new Set(kafkaTopics.concat([feature.topic]))];
|
|
238
288
|
}
|
|
239
289
|
// Subscribe to each topic
|
|
240
290
|
for (let topic of kafkaTopics) {
|
|
241
|
-
|
|
291
|
+
const groupId = '';
|
|
292
|
+
Kafka.receive(`Features.${topic}`, groupId, async (topic, message) => {
|
|
242
293
|
if (Utils.isEmpty(message.meta?.company))
|
|
243
294
|
message.meta = { ...message.meta, company: "GLOBAL", outlet: "GLOBAL" };
|
|
244
295
|
if (!message?.feature)
|
|
245
296
|
return;
|
|
246
|
-
const timer = ms => new Promise(res => setTimeout(res, ms)); // A promise that resolves after "ms" Milliseconds
|
|
297
|
+
const timer = (ms) => new Promise(res => setTimeout(res, ms)); // A promise that resolves after "ms" Milliseconds
|
|
247
298
|
topic = topic.replace(/^.*Features./, '');
|
|
248
299
|
while (true) {
|
|
249
300
|
try {
|
|
@@ -251,7 +302,12 @@ export const start = async (app, url, file) => {
|
|
|
251
302
|
// // TODO: use $useChunks: [] once support is added in dip insert
|
|
252
303
|
// await Dip.insert(globalMeta, `users.txns`, { _id: `${message.user}_${iso_date}_${message.txn}`, user: message.user, feature: message.feature, date: message.date, created: message.date, updated: message.date, status: "Queued" }, {idempotent: true}) // Can always update the status later
|
|
253
304
|
// TODO: Avoid duplicate processing
|
|
254
|
-
|
|
305
|
+
const feature = features[message.feature];
|
|
306
|
+
if (!feature) {
|
|
307
|
+
console.warn(`Feature ${message.feature} missing, in executing handler during Kafka.receive(topic: ${topic})`);
|
|
308
|
+
throw new Error(`Feature ${message.feature} missing, in executing handler during Kafka.receive(topic: ${topic})`);
|
|
309
|
+
}
|
|
310
|
+
await feature.handler(topic, message);
|
|
255
311
|
await Dip.insert(globalMeta, "Features.txns", { _id: message.txn, status: "Processed" }, { idempotent: true });
|
|
256
312
|
break;
|
|
257
313
|
}
|
|
@@ -296,31 +352,13 @@ export const start = async (app, url, file) => {
|
|
|
296
352
|
console.log('Messaging failed to start. Maybe missing redis');
|
|
297
353
|
}
|
|
298
354
|
};
|
|
299
|
-
function prepareMessage(req) {
|
|
300
|
-
let txn = req.body.txn ?? Utils.uid();
|
|
301
|
-
let _id = req.body.data?._id ?? txn;
|
|
302
|
-
let { data, feature, app, user, outlet, company, client, version } = req.body;
|
|
303
|
-
let meta = req.meta;
|
|
304
|
-
let params = req.params;
|
|
305
|
-
return { data, params, meta, feature, app, user, outlet, company, client, version, txn, id: _id, date: new Date().getTime() };
|
|
306
|
-
}
|
|
307
|
-
const commandAction = async (req, res, topic) => {
|
|
308
|
-
let message = prepareMessage(req);
|
|
309
|
-
try {
|
|
310
|
-
await Kafka.send(`Features.${topic}`, message, message.user, { compression: Kafka.CompressionTypes.GZIP });
|
|
311
|
-
}
|
|
312
|
-
catch {
|
|
313
|
-
throw { status: 500, message: "Failed to queue the transaction" };
|
|
314
|
-
}
|
|
315
|
-
res.json({ data: { txn: message.txn, success: true, status: "Queued", featureQueued: true } });
|
|
316
|
-
};
|
|
317
355
|
let subscriptions = {
|
|
318
356
|
// "coins.query.hello": "invoices.command.add", // Consumer : Feature of Topic
|
|
319
357
|
};
|
|
320
358
|
let subscribed_consumers = {};
|
|
321
359
|
async function subscribe() {
|
|
322
360
|
let Features = { get, send };
|
|
323
|
-
for (let [key, { subscribe }] of Object.entries(features))
|
|
361
|
+
for (let [key, { subscribe }] of Object.entries(features)) {
|
|
324
362
|
if (!Utils.isEmpty(subscribe) && subscriptions[key] !== subscribe && subscribed_consumers[key]) {
|
|
325
363
|
try {
|
|
326
364
|
await subscribed_consumers[key].disconnect();
|
|
@@ -328,30 +366,37 @@ async function subscribe() {
|
|
|
328
366
|
catch (_) { }
|
|
329
367
|
delete subscribed_consumers[key];
|
|
330
368
|
}
|
|
331
|
-
|
|
332
|
-
|
|
369
|
+
}
|
|
370
|
+
for (let key in SLYP_FEATURES_LIST) {
|
|
371
|
+
const feature = SLYP_FEATURES_LIST[key];
|
|
372
|
+
if (!Utils.isEmpty(feature?.subscribe) && subscriptions[key] !== feature?.subscribe && subscribed_consumers[key]) {
|
|
333
373
|
try {
|
|
334
374
|
await subscribed_consumers[key].disconnect();
|
|
335
375
|
}
|
|
336
376
|
catch (_) { }
|
|
337
377
|
delete subscribed_consumers[key];
|
|
338
378
|
}
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
379
|
+
}
|
|
380
|
+
for (let key in SLYP_FEATURES_LIST) {
|
|
381
|
+
const feature = SLYP_FEATURES_LIST[key];
|
|
382
|
+
if (!Utils.isEmpty(feature?.subscribe)) {
|
|
383
|
+
subscriptions[key] = feature.subscribe;
|
|
342
384
|
}
|
|
343
|
-
|
|
385
|
+
}
|
|
386
|
+
for (let [key, { subscribe }] of Object.entries(features)) {
|
|
344
387
|
if (!Utils.isEmpty(subscribe)) {
|
|
345
388
|
subscriptions[key] = subscribe;
|
|
346
389
|
}
|
|
390
|
+
}
|
|
347
391
|
for (let consumer in subscriptions) {
|
|
348
392
|
let publisher = subscriptions[consumer];
|
|
349
|
-
|
|
350
|
-
if (subscribed_consumers[consumer] || !
|
|
393
|
+
const publisherFeature = getFeature(publisher);
|
|
394
|
+
if (subscribed_consumers[consumer] || !publisherFeature)
|
|
351
395
|
continue;
|
|
352
|
-
let
|
|
396
|
+
let { topic } = publisherFeature;
|
|
397
|
+
let kafka_consumer_promise = Kafka.receive(`Features.${topic}`, consumer, async (topic, message) => {
|
|
353
398
|
topic = topic.replace(/^.*Features./, '');
|
|
354
|
-
const timer = ms => new Promise(res => setTimeout(res, ms)); // A promise that resolves after "ms" Milliseconds
|
|
399
|
+
const timer = (ms) => new Promise(res => setTimeout(res, ms)); // A promise that resolves after "ms" Milliseconds
|
|
355
400
|
while (true) {
|
|
356
401
|
try {
|
|
357
402
|
await Features.send(message.meta, consumer, message.data, message.params);
|
|
@@ -363,6 +408,11 @@ async function subscribe() {
|
|
|
363
408
|
await timer(10000);
|
|
364
409
|
}
|
|
365
410
|
});
|
|
366
|
-
|
|
411
|
+
kafka_consumer_promise
|
|
412
|
+
.then(kafka_consumer => {
|
|
413
|
+
subscribed_consumers[consumer] = kafka_consumer;
|
|
414
|
+
}).catch(err => {
|
|
415
|
+
throw new Error("Error: Remote service to service kafka Feature subscription.");
|
|
416
|
+
});
|
|
367
417
|
}
|
|
368
418
|
}
|
package/dist/libs/kafka.js
CHANGED
|
@@ -2,7 +2,9 @@
|
|
|
2
2
|
import { Kafka, logLevel as KafkaLogLevel, CompressionTypes as KafkaCompressionTypes } from 'kafkajs';
|
|
3
3
|
export const logLevel = KafkaLogLevel;
|
|
4
4
|
export const CompressionTypes = KafkaCompressionTypes;
|
|
5
|
-
let kafka
|
|
5
|
+
let kafka;
|
|
6
|
+
let producer;
|
|
7
|
+
let admin;
|
|
6
8
|
let appName;
|
|
7
9
|
let topicPrefix = '';
|
|
8
10
|
export const start = (arg) => {
|
|
@@ -31,8 +33,7 @@ export const createTopic = async (topic, partition, replicas) => {
|
|
|
31
33
|
// ===========
|
|
32
34
|
let consumers = [];
|
|
33
35
|
const start_consumer = async function (topic, groupId, callback) {
|
|
34
|
-
|
|
35
|
-
groupId = typeof groupId === "string" ? `${appName}.${groupId}` : `${appName}.${topic}`;
|
|
36
|
+
groupId = groupId ? `${appName}.${groupId}` : `${appName}.${topic}`;
|
|
36
37
|
const consumer = kafka.consumer({ groupId: groupId });
|
|
37
38
|
consumers.push(consumer);
|
|
38
39
|
await consumer.connect();
|
|
@@ -46,7 +47,7 @@ const start_consumer = async function (topic, groupId, callback) {
|
|
|
46
47
|
success = await callback(topic, JSON.parse(message.value), partition);
|
|
47
48
|
}
|
|
48
49
|
catch (ex) {
|
|
49
|
-
success = await callback(topic, message.value, partition);
|
|
50
|
+
success = await callback(topic, message.value, partition); // NOTE: Beware: Ensure Kafka.receive<T> can handle raw message type that is not json parseable
|
|
50
51
|
}
|
|
51
52
|
await consumer.commitOffsets([{ topic, partition, offset: (Number(message.offset) + 1).toString() }]);
|
|
52
53
|
},
|
|
@@ -79,7 +80,7 @@ const start_producer = async function (topic, message, key, options) {
|
|
|
79
80
|
// start_producer('quickstart-events', 'Hello KafkaJS user! Little')
|
|
80
81
|
export const receive = async function (topic, groupId, callback) {
|
|
81
82
|
topic = topicPrefix + topic;
|
|
82
|
-
return
|
|
83
|
+
return await start_consumer(topic, groupId, callback);
|
|
83
84
|
};
|
|
84
85
|
export const send = async function (topic, message, key, options) {
|
|
85
86
|
topic = topicPrefix + topic;
|
package/dist/libs/messaging.js
CHANGED
|
@@ -4,14 +4,17 @@ import axios from 'axios';
|
|
|
4
4
|
// @ts-ignore
|
|
5
5
|
import { createClient } from 'redis';
|
|
6
6
|
const url = process.env.REDIS_URL || "redis://localhost:6380";
|
|
7
|
-
|
|
8
|
-
let
|
|
7
|
+
let consumer;
|
|
8
|
+
let publisher;
|
|
9
9
|
let users_pool = {};
|
|
10
10
|
async function disconnect(user, uid) {
|
|
11
11
|
if (!users_pool[user])
|
|
12
12
|
return;
|
|
13
|
-
let
|
|
14
|
-
|
|
13
|
+
let res_items = users_pool[user].reqres.filter(item => item.uid === uid);
|
|
14
|
+
if (res_items) {
|
|
15
|
+
const res = res_items[0].res;
|
|
16
|
+
res.end();
|
|
17
|
+
}
|
|
15
18
|
if (users_pool[user].listeners[uid]) {
|
|
16
19
|
if (users_pool[user].listeners.count == 1)
|
|
17
20
|
await consumer.unsubscribe(user, users_pool[user].listeners[uid]);
|
package/dist/libs/session.js
CHANGED
|
@@ -8,7 +8,7 @@ const REFRESH_TOKEN_SECRET = process.env.JWT_REFRESH_TOKEN_SECRET || "MY_SECRET_
|
|
|
8
8
|
const DEPLOY_TOKEN_SECRET = process.env.DEPLOY_TOKEN_SECRET || "MY_SECRET_DEPLOY_TOKEN";
|
|
9
9
|
const NDCURVE_DEVELOPER_LICENSE_ACCESS_TOKEN_SECRET = process.env.NDCURVE_DEVELOPER_LICENSE_ACCESS_TOKEN_PUBLIC_KEY;
|
|
10
10
|
let urlsAllowed = [];
|
|
11
|
-
export
|
|
11
|
+
export let ALLOWED_URLS = [];
|
|
12
12
|
export const start = (expressApp, allowedUrls) => {
|
|
13
13
|
urlsAllowed = ["/refreshToken", "/login"].concat(allowedUrls ?? []);
|
|
14
14
|
ALLOWED_URLS = urlsAllowed;
|
|
@@ -54,7 +54,7 @@ export const start = (expressApp, allowedUrls) => {
|
|
|
54
54
|
throw null;
|
|
55
55
|
}
|
|
56
56
|
catch (error) {
|
|
57
|
-
|
|
57
|
+
res.status(401).json({ message: error?.message === "Access Denied" ? "Access Denied" : 'Unauthorized' });
|
|
58
58
|
}
|
|
59
59
|
});
|
|
60
60
|
app.post("/refreshToken", (req, res) => {
|
package/libs/auth.ts
CHANGED
|
@@ -7,12 +7,51 @@ import * as Dip from './dip.ts'
|
|
|
7
7
|
import * as Utils from './utils.ts'
|
|
8
8
|
import * as Session from './session.ts'
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
|
|
11
|
+
type AuthRequest = {
|
|
12
|
+
body: {
|
|
13
|
+
mob?: string
|
|
14
|
+
phone?: string
|
|
15
|
+
code?: string
|
|
16
|
+
otp?: string
|
|
17
|
+
clientId?: string
|
|
18
|
+
app?: string
|
|
19
|
+
hash?: string
|
|
20
|
+
userId?: string
|
|
21
|
+
data?: Record<string, unknown>
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
type AuthResponse = {
|
|
26
|
+
status: (code: number) => AuthResponse
|
|
27
|
+
json: (body: unknown) => void
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
type AuthApp = {
|
|
31
|
+
post: (
|
|
32
|
+
path: string,
|
|
33
|
+
handler: (req: AuthRequest, res: AuthResponse) => Promise<void>
|
|
34
|
+
) => void
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
type ValidateFunction = (req: AuthRequest) => boolean
|
|
38
|
+
|
|
39
|
+
type SuccessCallback = (
|
|
40
|
+
req: AuthRequest,
|
|
41
|
+
res: AuthResponse,
|
|
42
|
+
data: Record<string, unknown>
|
|
43
|
+
) => void | Promise<void>
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
let validateFn: ValidateFunction | undefined
|
|
48
|
+
let validateErrMessage: string | undefined
|
|
49
|
+
|
|
50
|
+
export const validate = (callback: ValidateFunction, errMessage?: string) => {
|
|
12
51
|
validateFn = callback
|
|
13
52
|
}
|
|
14
53
|
|
|
15
|
-
export const start = (app, successCallback) => {
|
|
54
|
+
export const start = (app: AuthApp, successCallback?: SuccessCallback) => {
|
|
16
55
|
app.post("/login", async (req, res) => {
|
|
17
56
|
if (validateFn && !validateFn(req)) {
|
|
18
57
|
res.status(401).json({ mode: 'login', success: false, info: validateErrMessage ?? 'Validation Failed', message: 'Login Server Error' })
|
|
@@ -21,7 +60,7 @@ export const start = (app, successCallback) => {
|
|
|
21
60
|
try {
|
|
22
61
|
let login = await attemptLogin(req, res)
|
|
23
62
|
if (login.mode === 'verify' && login.success) {
|
|
24
|
-
let tokens = Session.generateAccessToken(login.userId, req.body.clientId)
|
|
63
|
+
let tokens = Session.generateAccessToken(login.userId, req.body.clientId ?? '')
|
|
25
64
|
let response = {...login, tokens}
|
|
26
65
|
if (successCallback)
|
|
27
66
|
await successCallback(req, res, response)
|
|
@@ -35,12 +74,12 @@ export const start = (app, successCallback) => {
|
|
|
35
74
|
})
|
|
36
75
|
}
|
|
37
76
|
|
|
38
|
-
async function attemptLogin(req, res) {
|
|
77
|
+
async function attemptLogin(req: AuthRequest, res: AuthResponse) {
|
|
39
78
|
let meta = {company: "GLOBAL", outlet: "GLOBAL"}
|
|
40
79
|
|
|
41
80
|
let expiry = 300000
|
|
42
|
-
let userMob = req.body.mob ?? req.body.phone
|
|
43
|
-
const code = req.body.code
|
|
81
|
+
let userMob = req.body.mob ?? req.body.phone ?? ''
|
|
82
|
+
const code = req.body.code ?? ''
|
|
44
83
|
let mob = userMob.endsWith('123456789') ? '0123456789' : Utils.parseMob(userMob, code)
|
|
45
84
|
let time = new Date().getTime()
|
|
46
85
|
let collection = (req.body.app ? req.body.app + '.' : '') + "auth.login"
|
|
@@ -73,7 +112,7 @@ async function attemptLogin(req, res) {
|
|
|
73
112
|
}
|
|
74
113
|
|
|
75
114
|
|
|
76
|
-
async function sendOtp(mob, otp, app, hash) {
|
|
115
|
+
async function sendOtp(mob: string, otp: string, app?: string, hash?: string) {
|
|
77
116
|
if (process.env.LOGIN_OTP_DISABLE) {
|
|
78
117
|
console.log(otp);
|
|
79
118
|
return true
|
|
@@ -81,7 +120,7 @@ async function sendOtp(mob, otp, app, hash) {
|
|
|
81
120
|
try {
|
|
82
121
|
if (app && hash)
|
|
83
122
|
app = encodeURIComponent(`${app} (#${hash})`) // Of the form '<app> (#<hash>)' for better readability. Note: Axios works with SlypBusiness%20(%23<HASH>) but Curl requires SlypBusiness%20%28%23<HASH>%29
|
|
84
|
-
let api = process.env.SMS_API
|
|
123
|
+
let api = process.env.SMS_API!.replace(':phone', mob).replace(':otp', otp).replace(':app', app ?? 'app')
|
|
85
124
|
await axios.get(api)
|
|
86
125
|
return true
|
|
87
126
|
} catch {
|
package/libs/compression.ts
CHANGED
|
@@ -9,6 +9,11 @@ import compression from 'compression'
|
|
|
9
9
|
// Regarding Client compressing data send to server, I think if server has `Content-Encoding: gzip` set, then client might compress before sending. But not sure. Need to check.
|
|
10
10
|
// Http Stream or ServerSideEvents require res.flush() after res.write(). res.flush function is added by the express compression middleware
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
|
|
13
|
+
type ExpressApp = {
|
|
14
|
+
use: (fn: unknown) => unknown
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export const start = (app: ExpressApp) => {
|
|
13
18
|
app.use(Utils.excludeMiddleware(compression({threshold: 1024}))) // Response above 1024 bytes should be compressed. Default is also 1KB
|
|
14
19
|
}
|
package/libs/cpp.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
|
|
2
2
|
import crypto from 'node:crypto';
|
|
3
3
|
|
|
4
|
+
type ValueRef = { value: bigint }
|
|
5
|
+
type BytesRef = { value: number }
|
|
4
6
|
|
|
5
|
-
function readContent(buffer: Uint8Array, valueRef, bytesRef) { // Buffer is assignable to Uint8Array so no need for Buffer | Uint8Array
|
|
7
|
+
function readContent(buffer: Uint8Array, valueRef: ValueRef, bytesRef: BytesRef) { // Buffer is assignable to Uint8Array so no need for Buffer | Uint8Array
|
|
6
8
|
const MASK = 0x7F;
|
|
7
9
|
const MSB = 0x80;
|
|
8
10
|
const MAX_VLQ_BYTES = 10;
|
|
@@ -21,7 +23,7 @@ function readContent(buffer: Uint8Array, valueRef, bytesRef) { // Buffer is assi
|
|
|
21
23
|
return false;
|
|
22
24
|
}
|
|
23
25
|
|
|
24
|
-
const byte = view[lenBytes]
|
|
26
|
+
const byte = view[lenBytes]!;
|
|
25
27
|
lenBytes++;
|
|
26
28
|
|
|
27
29
|
length |= BigInt(byte & MASK) << shift;
|
|
@@ -41,7 +43,7 @@ function readContent(buffer: Uint8Array, valueRef, bytesRef) { // Buffer is assi
|
|
|
41
43
|
|
|
42
44
|
|
|
43
45
|
|
|
44
|
-
export function vlqToUint64(arrayBuffer) {
|
|
46
|
+
export function vlqToUint64(arrayBuffer: Uint8Array) {
|
|
45
47
|
// Objects act as containers to simulate C++ pointers/references
|
|
46
48
|
const valueRef = { value: 0n };
|
|
47
49
|
const bytesRef = { value: 0 };
|
|
@@ -63,7 +65,7 @@ export function vlqToUint64(arrayBuffer) {
|
|
|
63
65
|
|
|
64
66
|
|
|
65
67
|
|
|
66
|
-
function writeVlqcontent(buffer, value) {
|
|
68
|
+
function writeVlqcontent(buffer: Buffer, value: number | bigint) {
|
|
67
69
|
const MASK = 0x7Fn;
|
|
68
70
|
const MSB = 0x80;
|
|
69
71
|
let bufferPtr = 0;
|
|
@@ -81,7 +83,7 @@ function writeVlqcontent(buffer, value) {
|
|
|
81
83
|
return bufferPtr;
|
|
82
84
|
}
|
|
83
85
|
|
|
84
|
-
export function uint64ToVlq(value) {
|
|
86
|
+
export function uint64ToVlq(value: number | bigint) {
|
|
85
87
|
// 10 bytes is the maximum VLQ size for a uint64
|
|
86
88
|
const out = Buffer.allocUnsafe(10);
|
|
87
89
|
const size = writeVlqcontent(out, value);
|
|
@@ -93,7 +95,7 @@ export function uint64ToVlq(value) {
|
|
|
93
95
|
|
|
94
96
|
|
|
95
97
|
|
|
96
|
-
export function sliceArrayBuffer(buffer, offset, length) {
|
|
98
|
+
export function sliceArrayBuffer(buffer: Buffer | Uint8Array, offset: number, length: number) {
|
|
97
99
|
// Ensure we are working with a Node.js Buffer
|
|
98
100
|
const buf = Buffer.isBuffer(buffer) ? buffer : Buffer.from(buffer);
|
|
99
101
|
const totalSize = buf.length;
|
|
@@ -113,13 +115,13 @@ export function sliceArrayBuffer(buffer, offset, length) {
|
|
|
113
115
|
return buf.subarray(offset, offset + length); // COW/Shallow copy I hope or else what is the use of this function
|
|
114
116
|
}
|
|
115
117
|
|
|
116
|
-
export function stringToUtf8ArrayBuffer(arg) {
|
|
118
|
+
export function stringToUtf8ArrayBuffer(arg: string) {
|
|
117
119
|
// Converts the string into a Node.js Buffer encoded in UTF-8
|
|
118
120
|
return Buffer.from(arg, 'utf8');
|
|
119
121
|
}
|
|
120
122
|
|
|
121
123
|
|
|
122
|
-
export function numberToSortableBytes(value) {
|
|
124
|
+
export function numberToSortableBytes(value: number) {
|
|
123
125
|
// 1. Create an 8-byte buffer and a data view to read/write raw bits
|
|
124
126
|
const buffer = Buffer.alloc(8);
|
|
125
127
|
const view = new DataView(buffer.buffer, buffer.byteOffset, buffer.byteLength);
|
|
@@ -148,7 +150,7 @@ export function numberToSortableBytes(value) {
|
|
|
148
150
|
|
|
149
151
|
|
|
150
152
|
|
|
151
|
-
export function defaultHash(buffer) {
|
|
153
|
+
export function defaultHash(buffer: Buffer | Uint8Array) {
|
|
152
154
|
const buf = Buffer.isBuffer(buffer) ? buffer : Buffer.from(buffer);
|
|
153
155
|
|
|
154
156
|
// Uses the high-speed, non-cryptographic xxhash64 algorithm
|
|
@@ -160,7 +162,7 @@ export function defaultHash(buffer) {
|
|
|
160
162
|
}
|
|
161
163
|
|
|
162
164
|
|
|
163
|
-
export function memcmpEqual(buffer1, buffer2) {
|
|
165
|
+
export function memcmpEqual(buffer1: Buffer | Uint8Array, buffer2: Buffer | Uint8Array) {
|
|
164
166
|
const b1 = Buffer.isBuffer(buffer1) ? buffer1 : Buffer.from(buffer1);
|
|
165
167
|
const b2 = Buffer.isBuffer(buffer2) ? buffer2 : Buffer.from(buffer2);
|
|
166
168
|
|
|
@@ -168,7 +170,7 @@ export function memcmpEqual(buffer1, buffer2) {
|
|
|
168
170
|
}
|
|
169
171
|
|
|
170
172
|
|
|
171
|
-
export function arrayBufferToString(buffer) {
|
|
173
|
+
export function arrayBufferToString(buffer: Buffer | Uint8Array) {
|
|
172
174
|
const buf = Buffer.isBuffer(buffer) ? buffer : Buffer.from(buffer);
|
|
173
175
|
|
|
174
176
|
// Decodes the buffer bytes as a UTF-8 JavaScript string
|
|
@@ -180,7 +182,7 @@ export function arrayBufferToString(buffer) {
|
|
|
180
182
|
|
|
181
183
|
|
|
182
184
|
// Matches QString Response::getSliceAsText
|
|
183
|
-
export function getSliceAsText(rawTextBuffer, offset: number, length: number): string {
|
|
185
|
+
export function getSliceAsText(rawTextBuffer: Buffer, offset: number, length: number): string {
|
|
184
186
|
// Reuses the identical slice boundary logic from above
|
|
185
187
|
try {
|
|
186
188
|
const slice = getSliceAsArrayBuffer(rawTextBuffer, offset, length);
|
|
@@ -191,7 +193,7 @@ export function getSliceAsText(rawTextBuffer, offset: number, length: number): s
|
|
|
191
193
|
}
|
|
192
194
|
|
|
193
195
|
// Matches QByteArray Response::getSliceAsArrayBuffer
|
|
194
|
-
export function getSliceAsArrayBuffer(rawTextBuffer, offset: number, length: number): Buffer {
|
|
196
|
+
export function getSliceAsArrayBuffer(rawTextBuffer: Buffer, offset: number, length: number): Buffer {
|
|
195
197
|
const totalSize = rawTextBuffer.length;
|
|
196
198
|
|
|
197
199
|
if (offset < 0 || offset >= totalSize) {
|
package/libs/dip/suffix/date.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {getNormalizedBounds, type
|
|
1
|
+
import {getNormalizedBounds, type AbsoluteBounds} from './index.ts'
|
|
2
2
|
import {addUTCYears, addUTCMonths, addUTCDays} from '../../utils.ts'
|
|
3
3
|
|
|
4
4
|
|
|
@@ -6,7 +6,7 @@ const MONTH_SHORT = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Se
|
|
|
6
6
|
const MONTH_LONG = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"]
|
|
7
7
|
|
|
8
8
|
|
|
9
|
-
export function formatDate(value, format) {
|
|
9
|
+
export function formatDate(value: string | number | Date, format: string) {
|
|
10
10
|
|
|
11
11
|
const dateObj = value instanceof Date ? value : new Date(value)
|
|
12
12
|
|
|
@@ -59,7 +59,7 @@ export function formatDate(value, format) {
|
|
|
59
59
|
D: () => dates.join(separator),
|
|
60
60
|
};
|
|
61
61
|
|
|
62
|
-
return format.replace(/YYYY|MMMM|MMM|YY|MM|DD|D/g, token => replacements[token]());
|
|
62
|
+
return format.replace(/YYYY|MMMM|MMM|YY|MM|DD|D/g, token => replacements[token as keyof typeof replacements]());
|
|
63
63
|
}
|
|
64
64
|
|
|
65
65
|
|
|
@@ -83,7 +83,7 @@ export function formatDate(value, format) {
|
|
|
83
83
|
|
|
84
84
|
|
|
85
85
|
|
|
86
|
-
export function fillDates(bounds_t, format) {
|
|
86
|
+
export function fillDates(bounds_t: AbsoluteBounds, format: string) {
|
|
87
87
|
const result = [];
|
|
88
88
|
|
|
89
89
|
let step;
|
|
@@ -94,11 +94,11 @@ export function fillDates(bounds_t, format) {
|
|
|
94
94
|
else
|
|
95
95
|
step = "year";
|
|
96
96
|
|
|
97
|
-
let bounds:
|
|
97
|
+
let bounds: AbsoluteBounds = { ...bounds_t, from: new Date(bounds_t.from), to: new Date(bounds_t.to) }
|
|
98
98
|
|
|
99
99
|
bounds = getNormalizedBounds(bounds, "date")
|
|
100
100
|
|
|
101
|
-
while (bounds.from <= bounds.to) {
|
|
101
|
+
while (bounds.from! <= bounds.to!) {
|
|
102
102
|
result.push(formatDate(bounds.from, format));
|
|
103
103
|
|
|
104
104
|
switch (step) {
|