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.
- package/LICENSE +1 -0
- package/README.md +8 -0
- package/db/TSMongo.d.ts +103 -0
- package/db/TSMongo.js +483 -0
- package/db/TSRQW.d.ts +271 -0
- package/db/TSRQW.js +699 -0
- package/db/TSRedis.d.ts +174 -0
- package/db/TSRedis.js +602 -0
- package/package.json +85 -0
- package/ussd/TSUssdMenu.d.ts +139 -0
- package/ussd/TSUssdMenu.js +364 -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/TSFile.d.ts +36 -0
- package/utils/TSFile.js +244 -0
- package/utils/TSHash.d.ts +20 -0
- package/utils/TSHash.js +75 -0
- package/utils/TSRequest.d.ts +39 -0
- package/utils/TSRequest.js +256 -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
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.providersMap = exports.TSUssdProviders = void 0;
|
|
4
|
+
exports.body = body;
|
|
5
|
+
exports.mapArgs = mapArgs;
|
|
6
|
+
exports.purify = purify;
|
|
7
|
+
exports.provider = provider;
|
|
8
|
+
var TSUssdProviders;
|
|
9
|
+
(function (TSUssdProviders) {
|
|
10
|
+
TSUssdProviders["vodacomdrc"] = "vodacom";
|
|
11
|
+
TSUssdProviders["orangedrc"] = "orange";
|
|
12
|
+
TSUssdProviders["airteldrc"] = "airtel";
|
|
13
|
+
TSUssdProviders["africastalking"] = "africastalking";
|
|
14
|
+
})(TSUssdProviders || (exports.TSUssdProviders = TSUssdProviders = {}));
|
|
15
|
+
function body(request) {
|
|
16
|
+
const { headers = {}, body = {}, query = {}, params = {} } = request;
|
|
17
|
+
return { ...headers, ...body, ...query, ...params };
|
|
18
|
+
}
|
|
19
|
+
function mapArgs(args = {}, map = {}) {
|
|
20
|
+
const results = { provider: map.provider };
|
|
21
|
+
for (const p in args) {
|
|
22
|
+
if (Object.hasOwn(args, p) && Object.hasOwn(map, p)) {
|
|
23
|
+
results[String(map[p])] = args[p];
|
|
24
|
+
}
|
|
25
|
+
else if (Object.hasOwn(args, p)) {
|
|
26
|
+
results[p] = args[p];
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return results;
|
|
30
|
+
}
|
|
31
|
+
function purify(text = '') {
|
|
32
|
+
return text ? text.replace(/[^\w\s]/g, '') : '';
|
|
33
|
+
}
|
|
34
|
+
function provider({ headers = {}, body, query }) {
|
|
35
|
+
const h = headers;
|
|
36
|
+
if (body && typeof body === 'object' && 'methodcall' in body) {
|
|
37
|
+
return TSUssdProviders.vodacomdrc;
|
|
38
|
+
}
|
|
39
|
+
else if (h['user-session'] && h['user-msisdn']) {
|
|
40
|
+
return TSUssdProviders.orangedrc;
|
|
41
|
+
}
|
|
42
|
+
else if (query && typeof query === 'object' && 'MBILE_NUMBER' in query) {
|
|
43
|
+
return TSUssdProviders.airteldrc;
|
|
44
|
+
}
|
|
45
|
+
else if (body && typeof body === 'object' && 'text' in body) {
|
|
46
|
+
return TSUssdProviders.africastalking;
|
|
47
|
+
}
|
|
48
|
+
return TSUssdProviders.africastalking;
|
|
49
|
+
}
|
|
50
|
+
exports.providersMap = {
|
|
51
|
+
[TSUssdProviders.africastalking]: {
|
|
52
|
+
africastalking: 'provider',
|
|
53
|
+
sessionId: 'sessionId',
|
|
54
|
+
serviceCode: 'serviceCode',
|
|
55
|
+
phoneNumber: 'phoneNumber',
|
|
56
|
+
networkCode: 'networkCode',
|
|
57
|
+
text: 'message'
|
|
58
|
+
},
|
|
59
|
+
[TSUssdProviders.vodacomdrc]: {
|
|
60
|
+
vodacom: 'provider',
|
|
61
|
+
TransactionId: 'sessionId',
|
|
62
|
+
USSDServiceCode: 'serviceCode',
|
|
63
|
+
MSISDN: 'phoneNumber',
|
|
64
|
+
networkCode: 'networkCode',
|
|
65
|
+
USSDRequestString: 'message'
|
|
66
|
+
},
|
|
67
|
+
[TSUssdProviders.orangedrc]: {
|
|
68
|
+
orange: 'provider',
|
|
69
|
+
'user-session': 'sessionId',
|
|
70
|
+
'user-imsi': 'serviceCode',
|
|
71
|
+
'user-msisdn': 'phoneNumber',
|
|
72
|
+
networkCode: 'networkCode',
|
|
73
|
+
'user-identity': 'message'
|
|
74
|
+
},
|
|
75
|
+
[TSUssdProviders.airteldrc]: {
|
|
76
|
+
airtel: 'provider',
|
|
77
|
+
MBILE_NUMBER: 'phoneNumber',
|
|
78
|
+
USSDServiceCode: 'serviceCode',
|
|
79
|
+
SESSION_ID: 'sessionId',
|
|
80
|
+
INPUT: 'message',
|
|
81
|
+
IMEI: 'networkCode'
|
|
82
|
+
}
|
|
83
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import * as africastalking from './AfricasTalking';
|
|
2
|
+
import * as airteldrc from './AirtelDRC';
|
|
3
|
+
import * as orangedrc from './OrangeDRC';
|
|
4
|
+
import * as vodacomdrc from './VodacomDRC';
|
|
5
|
+
import { TSUssdProviders } from './_';
|
|
6
|
+
export * from './_';
|
|
7
|
+
export declare const providers: {
|
|
8
|
+
vodacom: typeof vodacomdrc;
|
|
9
|
+
orange: typeof orangedrc;
|
|
10
|
+
airtel: typeof airteldrc;
|
|
11
|
+
africastalking: typeof africastalking;
|
|
12
|
+
};
|
|
13
|
+
export declare const providersAvailable: TSUssdProviders[];
|
|
@@ -0,0 +1,56 @@
|
|
|
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 __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
36
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.providersAvailable = exports.providers = void 0;
|
|
40
|
+
const africastalking = __importStar(require("./AfricasTalking"));
|
|
41
|
+
const airteldrc = __importStar(require("./AirtelDRC"));
|
|
42
|
+
const orangedrc = __importStar(require("./OrangeDRC"));
|
|
43
|
+
const vodacomdrc = __importStar(require("./VodacomDRC"));
|
|
44
|
+
const _1 = require("./_");
|
|
45
|
+
__exportStar(require("./_"), exports);
|
|
46
|
+
exports.providers = {
|
|
47
|
+
[_1.TSUssdProviders.vodacomdrc]: vodacomdrc,
|
|
48
|
+
[_1.TSUssdProviders.orangedrc]: orangedrc,
|
|
49
|
+
[_1.TSUssdProviders.airteldrc]: airteldrc,
|
|
50
|
+
[_1.TSUssdProviders.africastalking]: africastalking
|
|
51
|
+
};
|
|
52
|
+
exports.providersAvailable = [
|
|
53
|
+
_1.TSUssdProviders.vodacomdrc,
|
|
54
|
+
_1.TSUssdProviders.orangedrc,
|
|
55
|
+
_1.TSUssdProviders.airteldrc
|
|
56
|
+
];
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
type FileType = {
|
|
2
|
+
filename: string;
|
|
3
|
+
type: string;
|
|
4
|
+
ext?: string;
|
|
5
|
+
mime?: string;
|
|
6
|
+
charset?: string;
|
|
7
|
+
};
|
|
8
|
+
export declare class TSFile {
|
|
9
|
+
static list(path?: string): FileType[];
|
|
10
|
+
static info(filename: string): FileType;
|
|
11
|
+
private static readFileHead;
|
|
12
|
+
/** Prefer POSIX `file(1)` when present; otherwise extension + optional magic-byte sniff (Windows-friendly). */
|
|
13
|
+
static mime(filename: string): {
|
|
14
|
+
mime: string;
|
|
15
|
+
charset?: string;
|
|
16
|
+
ext?: string;
|
|
17
|
+
};
|
|
18
|
+
private static mimeFromFileCommand;
|
|
19
|
+
private static mimeFallback;
|
|
20
|
+
private static charsetForMime;
|
|
21
|
+
static extension(mime: string): string | undefined;
|
|
22
|
+
static mimeFromExtension(ext: string): string;
|
|
23
|
+
static mimeFromFilename(filename: string): string | undefined;
|
|
24
|
+
static isImageMime(mime: string | undefined | null): boolean;
|
|
25
|
+
static isVideoMime(mime: string | undefined | null): boolean;
|
|
26
|
+
static isImageFilename(filename: string): boolean;
|
|
27
|
+
static isVideoFilename(filename: string): boolean;
|
|
28
|
+
private static bytesMatch;
|
|
29
|
+
static sniffMimeFromMagicBytes(buffer: ArrayBuffer | Uint8Array): "video/mp4" | "video/webm" | "image/jpeg" | "image/png" | "image/gif" | undefined;
|
|
30
|
+
private static headerSuggestsBinaryVideo;
|
|
31
|
+
private static normalizeVideoExtMime;
|
|
32
|
+
static resolveMediaContentType(filename: string, headerContentType: string | undefined | null, buffer: ArrayBuffer | Uint8Array): string | undefined;
|
|
33
|
+
static readJson<T = unknown>(filename: string): Promise<T>;
|
|
34
|
+
static writeJson(filename: string, data: unknown, space?: number): Promise<void>;
|
|
35
|
+
}
|
|
36
|
+
export {};
|
package/utils/TSFile.js
ADDED
|
@@ -0,0 +1,244 @@
|
|
|
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 __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.TSFile = void 0;
|
|
37
|
+
const child_process_1 = require("child_process");
|
|
38
|
+
const fs_1 = require("fs");
|
|
39
|
+
const promises_1 = require("fs/promises");
|
|
40
|
+
const path_1 = require("path");
|
|
41
|
+
const mimeDB = __importStar(require("./mime.json"));
|
|
42
|
+
const STAT_METHODS = ['isFile', 'isDirectory', 'isSymbolicLink', 'isFIFO', 'isSocket', 'isCharacterDevice', 'isBlockDevice'];
|
|
43
|
+
const OCTET_STREAM = 'application/octet-stream';
|
|
44
|
+
const MIME_MAP = (mimeDB.default ??
|
|
45
|
+
mimeDB);
|
|
46
|
+
const EXT_TO_MIME = new Map();
|
|
47
|
+
const MEDIA_MIME_OVERRIDES = {
|
|
48
|
+
mp4: 'video/mp4',
|
|
49
|
+
m4v: 'video/mp4',
|
|
50
|
+
mov: 'video/quicktime',
|
|
51
|
+
webm: 'video/webm',
|
|
52
|
+
mkv: 'video/x-matroska',
|
|
53
|
+
avi: 'video/x-msvideo',
|
|
54
|
+
jpg: 'image/jpeg',
|
|
55
|
+
};
|
|
56
|
+
for (const [mime, data] of Object.entries(MIME_MAP)) {
|
|
57
|
+
for (const ext of data.extensions ?? []) {
|
|
58
|
+
const key = ext.toLowerCase();
|
|
59
|
+
if (!EXT_TO_MIME.has(key)) {
|
|
60
|
+
EXT_TO_MIME.set(key, mime);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
class TSFile {
|
|
65
|
+
static list(path = './') {
|
|
66
|
+
const l = (0, fs_1.readdirSync)(path);
|
|
67
|
+
return l.map(f => TSFile.info((0, path_1.join)(path, f)));
|
|
68
|
+
}
|
|
69
|
+
static info(filename) {
|
|
70
|
+
let type = 'Unknown';
|
|
71
|
+
const s = (0, fs_1.existsSync)(filename) ? (0, fs_1.lstatSync)(filename) : null;
|
|
72
|
+
for (const method of STAT_METHODS) {
|
|
73
|
+
const fn = s ? s[method] : null;
|
|
74
|
+
if (fn && typeof fn === 'function' && fn.call(s)) {
|
|
75
|
+
type = method.replace(/^is/, '');
|
|
76
|
+
break;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return { filename, type, ...TSFile.mime(filename) };
|
|
80
|
+
}
|
|
81
|
+
static readFileHead(filename, maxLen) {
|
|
82
|
+
let fd;
|
|
83
|
+
try {
|
|
84
|
+
fd = (0, fs_1.openSync)(filename, 'r');
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
return undefined;
|
|
88
|
+
}
|
|
89
|
+
try {
|
|
90
|
+
const buf = Buffer.allocUnsafe(maxLen);
|
|
91
|
+
const n = (0, fs_1.readSync)(fd, buf, 0, maxLen, 0);
|
|
92
|
+
return new Uint8Array(buf.buffer, buf.byteOffset, n);
|
|
93
|
+
}
|
|
94
|
+
finally {
|
|
95
|
+
(0, fs_1.closeSync)(fd);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
/** Prefer POSIX `file(1)` when present; otherwise extension + optional magic-byte sniff (Windows-friendly). */
|
|
99
|
+
static mime(filename) {
|
|
100
|
+
const fromFile = TSFile.mimeFromFileCommand(filename);
|
|
101
|
+
if (fromFile)
|
|
102
|
+
return fromFile;
|
|
103
|
+
return TSFile.mimeFallback(filename);
|
|
104
|
+
}
|
|
105
|
+
static mimeFromFileCommand(filename) {
|
|
106
|
+
if (process.platform === 'win32')
|
|
107
|
+
return null;
|
|
108
|
+
try {
|
|
109
|
+
const result = (0, child_process_1.execFileSync)('file', ['--mime-type', '-i', filename], { encoding: 'utf-8', maxBuffer: 1024 * 1024 });
|
|
110
|
+
const parts = result.split(/\s+/).filter(Boolean);
|
|
111
|
+
parts.shift();
|
|
112
|
+
const [mime, charset] = parts.map(p => p.replace(/;$/, ''));
|
|
113
|
+
return { mime, charset, ext: TSFile.extension(mime) };
|
|
114
|
+
}
|
|
115
|
+
catch {
|
|
116
|
+
return null;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
static mimeFallback(filename) {
|
|
120
|
+
const extFromPath = (0, path_1.extname)(filename).replace(/^\./, '').toLowerCase();
|
|
121
|
+
const fromExt = TSFile.mimeFromExtension(extFromPath);
|
|
122
|
+
if (!(0, fs_1.existsSync)(filename)) {
|
|
123
|
+
const mime = fromExt ?? OCTET_STREAM;
|
|
124
|
+
return {
|
|
125
|
+
mime,
|
|
126
|
+
charset: TSFile.charsetForMime(mime),
|
|
127
|
+
ext: TSFile.extension(mime),
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
const st = (0, fs_1.lstatSync)(filename);
|
|
131
|
+
if (st.isDirectory()) {
|
|
132
|
+
return { mime: 'inode/directory', charset: 'binary', ext: undefined };
|
|
133
|
+
}
|
|
134
|
+
if (!st.isFile()) {
|
|
135
|
+
return { mime: fromExt ?? OCTET_STREAM, charset: 'binary', ext: TSFile.extension(fromExt ?? OCTET_STREAM) };
|
|
136
|
+
}
|
|
137
|
+
const head = TSFile.readFileHead(filename, 512);
|
|
138
|
+
const sniff = head ? TSFile.sniffMimeFromMagicBytes(head) : undefined;
|
|
139
|
+
const mime = sniff ?? fromExt ?? OCTET_STREAM;
|
|
140
|
+
return {
|
|
141
|
+
mime,
|
|
142
|
+
charset: TSFile.charsetForMime(mime),
|
|
143
|
+
ext: TSFile.extension(mime),
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
static charsetForMime(mime) {
|
|
147
|
+
const m = mime.toLowerCase();
|
|
148
|
+
if (m.startsWith('text/'))
|
|
149
|
+
return 'utf-8';
|
|
150
|
+
if (m.endsWith('+json') || m.endsWith('+xml'))
|
|
151
|
+
return 'utf-8';
|
|
152
|
+
if (m === 'application/javascript' || m === 'application/json')
|
|
153
|
+
return 'utf-8';
|
|
154
|
+
return 'binary';
|
|
155
|
+
}
|
|
156
|
+
static extension(mime) {
|
|
157
|
+
const mimeData = MIME_MAP[mime];
|
|
158
|
+
const { extensions } = mimeData || {};
|
|
159
|
+
return extensions?.[0];
|
|
160
|
+
}
|
|
161
|
+
static mimeFromExtension(ext) {
|
|
162
|
+
const normalized = ext.replace(/^\./, '').toLowerCase();
|
|
163
|
+
return MEDIA_MIME_OVERRIDES[normalized] ?? EXT_TO_MIME.get(normalized);
|
|
164
|
+
}
|
|
165
|
+
static mimeFromFilename(filename) {
|
|
166
|
+
const ext = (0, path_1.extname)(filename).replace(/^\./, '');
|
|
167
|
+
if (!ext)
|
|
168
|
+
return undefined;
|
|
169
|
+
return TSFile.mimeFromExtension(ext);
|
|
170
|
+
}
|
|
171
|
+
static isImageMime(mime) {
|
|
172
|
+
return typeof mime === 'string' && mime.toLowerCase().startsWith('image/');
|
|
173
|
+
}
|
|
174
|
+
static isVideoMime(mime) {
|
|
175
|
+
return typeof mime === 'string' && mime.toLowerCase().startsWith('video/');
|
|
176
|
+
}
|
|
177
|
+
static isImageFilename(filename) {
|
|
178
|
+
return TSFile.isImageMime(TSFile.mimeFromFilename(filename));
|
|
179
|
+
}
|
|
180
|
+
static isVideoFilename(filename) {
|
|
181
|
+
return TSFile.isVideoMime(TSFile.mimeFromFilename(filename));
|
|
182
|
+
}
|
|
183
|
+
static bytesMatch(u, offset, seq) {
|
|
184
|
+
if (u.byteLength < offset + seq.length)
|
|
185
|
+
return false;
|
|
186
|
+
for (let i = 0; i < seq.length; i++) {
|
|
187
|
+
if (u[offset + i] !== seq[i])
|
|
188
|
+
return false;
|
|
189
|
+
}
|
|
190
|
+
return true;
|
|
191
|
+
}
|
|
192
|
+
static sniffMimeFromMagicBytes(buffer) {
|
|
193
|
+
const u = buffer instanceof Uint8Array ? buffer : new Uint8Array(buffer);
|
|
194
|
+
if (u.byteLength < 12)
|
|
195
|
+
return undefined;
|
|
196
|
+
if (TSFile.bytesMatch(u, 0, [0x1a, 0x45, 0xdf, 0xa3]))
|
|
197
|
+
return 'video/webm';
|
|
198
|
+
if (TSFile.bytesMatch(u, 4, [0x66, 0x74, 0x79, 0x70]))
|
|
199
|
+
return 'video/mp4';
|
|
200
|
+
if (TSFile.bytesMatch(u, 0, [0xff, 0xd8, 0xff]))
|
|
201
|
+
return 'image/jpeg';
|
|
202
|
+
if (TSFile.bytesMatch(u, 0, [0x89, 0x50, 0x4e, 0x47]))
|
|
203
|
+
return 'image/png';
|
|
204
|
+
if (TSFile.bytesMatch(u, 0, [0x47, 0x49, 0x46]))
|
|
205
|
+
return 'image/gif';
|
|
206
|
+
return undefined;
|
|
207
|
+
}
|
|
208
|
+
static headerSuggestsBinaryVideo(header) {
|
|
209
|
+
return TSFile.isImageMime(header) || header === OCTET_STREAM || !header;
|
|
210
|
+
}
|
|
211
|
+
static normalizeVideoExtMime(extMime) {
|
|
212
|
+
if (extMime === 'video/webm')
|
|
213
|
+
return 'video/webm';
|
|
214
|
+
if (extMime === 'video/quicktime')
|
|
215
|
+
return 'video/quicktime';
|
|
216
|
+
return 'video/mp4';
|
|
217
|
+
}
|
|
218
|
+
static resolveMediaContentType(filename, headerContentType, buffer) {
|
|
219
|
+
const header = (headerContentType?.split(';')[0] ?? '').trim().toLowerCase();
|
|
220
|
+
const extMime = TSFile.mimeFromFilename(filename)?.toLowerCase();
|
|
221
|
+
const sniff = TSFile.sniffMimeFromMagicBytes(buffer);
|
|
222
|
+
if (sniff && TSFile.isVideoMime(sniff) && (TSFile.isVideoMime(extMime) || TSFile.headerSuggestsBinaryVideo(header))) {
|
|
223
|
+
return sniff;
|
|
224
|
+
}
|
|
225
|
+
if (sniff && TSFile.isImageMime(sniff) && TSFile.isImageMime(extMime)) {
|
|
226
|
+
return sniff;
|
|
227
|
+
}
|
|
228
|
+
if (TSFile.isVideoMime(extMime) && TSFile.headerSuggestsBinaryVideo(header)) {
|
|
229
|
+
return TSFile.normalizeVideoExtMime(extMime);
|
|
230
|
+
}
|
|
231
|
+
if (TSFile.isImageMime(extMime) && TSFile.isVideoMime(header)) {
|
|
232
|
+
return extMime;
|
|
233
|
+
}
|
|
234
|
+
return header || extMime || OCTET_STREAM;
|
|
235
|
+
}
|
|
236
|
+
static async readJson(filename) {
|
|
237
|
+
const content = await (0, promises_1.readFile)(filename, 'utf-8');
|
|
238
|
+
return JSON.parse(content);
|
|
239
|
+
}
|
|
240
|
+
static async writeJson(filename, data, space = 2) {
|
|
241
|
+
await (0, promises_1.writeFile)(filename, `${JSON.stringify(data, null, space)}\n`, 'utf-8');
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
exports.TSFile = TSFile;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export type HmacEncoding = 'hex' | 'base64url' | 'base64';
|
|
2
|
+
export declare class TSHash {
|
|
3
|
+
static id: (length?: number) => string;
|
|
4
|
+
static md5: (text: string) => string;
|
|
5
|
+
static sha256: (text: string, encoding?: "hex" | "base64" | "base64url") => string;
|
|
6
|
+
static hmacSha256: (data: string, secret: string | Buffer, encoding?: HmacEncoding) => string;
|
|
7
|
+
static verifyHmacSha256: (data: string, signature: string, secret: string | Buffer, encoding?: HmacEncoding) => boolean;
|
|
8
|
+
static encryptAesGcm: (plaintext: string, secret: string) => string;
|
|
9
|
+
static decryptAesGcm: (token: string, secret: string) => string | null;
|
|
10
|
+
static hashToken: (token: string) => Promise<string>;
|
|
11
|
+
/**
|
|
12
|
+
* Random bytes encoded as the requested format.
|
|
13
|
+
* - `'base64url'` (default) — opaque tokens, refresh-token-size secrets
|
|
14
|
+
* - `'hex'` — webhook signatures, short IDs, stub identifiers
|
|
15
|
+
* - `'base64'` — legacy base64 encoding
|
|
16
|
+
*/
|
|
17
|
+
static randomId: (byteLength?: number, encoding?: "base64url" | "hex" | "base64") => string;
|
|
18
|
+
static generateBackupCodes: (count?: number) => string[];
|
|
19
|
+
static generateDeviceId: (userAgent: string | undefined, ipAddress: string | undefined) => string;
|
|
20
|
+
}
|
package/utils/TSHash.js
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.TSHash = void 0;
|
|
4
|
+
const node_crypto_1 = require("node:crypto");
|
|
5
|
+
function idChar(e) {
|
|
6
|
+
const x = e & 63;
|
|
7
|
+
if (x < 36)
|
|
8
|
+
return x.toString(36);
|
|
9
|
+
if (x < 62)
|
|
10
|
+
return (x - 26).toString(36).toUpperCase();
|
|
11
|
+
return x > 62 ? '-' : '_';
|
|
12
|
+
}
|
|
13
|
+
const AES_ALGORITHM = 'aes-256-gcm';
|
|
14
|
+
const IV_LENGTH = 12;
|
|
15
|
+
const AUTH_TAG_LENGTH = 16;
|
|
16
|
+
class TSHash {
|
|
17
|
+
static id = (length = 9) => (0, node_crypto_1.getRandomValues)(new Uint8Array(length)).reduce((t, e) => t + idChar(e), '');
|
|
18
|
+
static md5 = (text) => (0, node_crypto_1.createHash)('md5').update(text).digest('hex');
|
|
19
|
+
static sha256 = (text, encoding = 'hex') => (0, node_crypto_1.createHash)('sha256').update(text).digest(encoding);
|
|
20
|
+
static hmacSha256 = (data, secret, encoding = 'hex') => (0, node_crypto_1.createHmac)('sha256', secret).update(data).digest(encoding);
|
|
21
|
+
static verifyHmacSha256 = (data, signature, secret, encoding = 'hex') => {
|
|
22
|
+
const expected = TSHash.hmacSha256(data, secret, encoding);
|
|
23
|
+
const sigBuf = Buffer.from(signature, encoding);
|
|
24
|
+
const expBuf = Buffer.from(expected, encoding);
|
|
25
|
+
if (sigBuf.length !== expBuf.length)
|
|
26
|
+
return false;
|
|
27
|
+
return (0, node_crypto_1.timingSafeEqual)(sigBuf, expBuf);
|
|
28
|
+
};
|
|
29
|
+
static encryptAesGcm = (plaintext, secret) => {
|
|
30
|
+
const key = (0, node_crypto_1.createHash)('sha256').update(secret).digest();
|
|
31
|
+
const iv = (0, node_crypto_1.randomBytes)(IV_LENGTH);
|
|
32
|
+
const cipher = (0, node_crypto_1.createCipheriv)(AES_ALGORITHM, key, iv, { authTagLength: AUTH_TAG_LENGTH });
|
|
33
|
+
const encrypted = Buffer.concat([cipher.update(plaintext, 'utf8'), cipher.final()]);
|
|
34
|
+
const authTag = cipher.getAuthTag();
|
|
35
|
+
return Buffer.concat([iv, encrypted, authTag]).toString('base64url');
|
|
36
|
+
};
|
|
37
|
+
static decryptAesGcm = (token, secret) => {
|
|
38
|
+
try {
|
|
39
|
+
const buf = Buffer.from(token, 'base64url');
|
|
40
|
+
if (buf.length < IV_LENGTH + AUTH_TAG_LENGTH + 1)
|
|
41
|
+
return null;
|
|
42
|
+
const iv = buf.subarray(0, IV_LENGTH);
|
|
43
|
+
const authTag = buf.subarray(buf.length - AUTH_TAG_LENGTH);
|
|
44
|
+
const encrypted = buf.subarray(IV_LENGTH, buf.length - AUTH_TAG_LENGTH);
|
|
45
|
+
const key = (0, node_crypto_1.createHash)('sha256').update(secret).digest();
|
|
46
|
+
const decipher = (0, node_crypto_1.createDecipheriv)(AES_ALGORITHM, key, iv, { authTagLength: AUTH_TAG_LENGTH });
|
|
47
|
+
decipher.setAuthTag(authTag);
|
|
48
|
+
return Buffer.concat([decipher.update(encrypted), decipher.final()]).toString('utf8');
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
static hashToken = (token) => Promise.resolve(TSHash.sha256(token, 'hex'));
|
|
55
|
+
/**
|
|
56
|
+
* Random bytes encoded as the requested format.
|
|
57
|
+
* - `'base64url'` (default) — opaque tokens, refresh-token-size secrets
|
|
58
|
+
* - `'hex'` — webhook signatures, short IDs, stub identifiers
|
|
59
|
+
* - `'base64'` — legacy base64 encoding
|
|
60
|
+
*/
|
|
61
|
+
static randomId = (byteLength = 32, encoding = 'base64url') => (0, node_crypto_1.randomBytes)(byteLength).toString(encoding);
|
|
62
|
+
static generateBackupCodes = (count = 10) => {
|
|
63
|
+
const codes = [];
|
|
64
|
+
for (let i = 0; i < count; i++) {
|
|
65
|
+
const code = (0, node_crypto_1.randomBytes)(4).toString('hex').toUpperCase();
|
|
66
|
+
codes.push(`${code.slice(0, 4)}-${code.slice(4)}`);
|
|
67
|
+
}
|
|
68
|
+
return codes;
|
|
69
|
+
};
|
|
70
|
+
static generateDeviceId = (userAgent, ipAddress) => {
|
|
71
|
+
const data = `${userAgent || 'unknown'}:${ipAddress || 'unknown'}`;
|
|
72
|
+
return TSHash.md5(data);
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
exports.TSHash = TSHash;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
export type ITSResponse = {
|
|
2
|
+
data?: any;
|
|
3
|
+
kind: string;
|
|
4
|
+
status: number;
|
|
5
|
+
message: string;
|
|
6
|
+
} | void;
|
|
7
|
+
/** Options for {@link TSRequest.raw} / form / json / xml — `signal` aborts the in-flight HTTP request. */
|
|
8
|
+
export type TSRequestHttpOptions = Record<string, unknown> & {
|
|
9
|
+
signal?: AbortSignal;
|
|
10
|
+
};
|
|
11
|
+
export declare class TSRequest {
|
|
12
|
+
static form(url: string, options?: TSRequestHttpOptions, debug?: boolean): Promise<any>;
|
|
13
|
+
static xml(url: string, options?: TSRequestHttpOptions, debug?: boolean): Promise<any>;
|
|
14
|
+
static json(url: string, options?: TSRequestHttpOptions, debug?: boolean): Promise<any>;
|
|
15
|
+
static raw(url: string, options?: TSRequestHttpOptions): Promise<ITSResponse>;
|
|
16
|
+
static error(kind: string, status: number, message: any): Error;
|
|
17
|
+
static url(url: string): {
|
|
18
|
+
href: string;
|
|
19
|
+
protocol: string;
|
|
20
|
+
host: string;
|
|
21
|
+
hostname: string;
|
|
22
|
+
port: string;
|
|
23
|
+
path: string;
|
|
24
|
+
pathname: string;
|
|
25
|
+
search: string;
|
|
26
|
+
hash: string;
|
|
27
|
+
} | {
|
|
28
|
+
href?: undefined;
|
|
29
|
+
protocol?: undefined;
|
|
30
|
+
host?: undefined;
|
|
31
|
+
hostname?: undefined;
|
|
32
|
+
port?: undefined;
|
|
33
|
+
path?: undefined;
|
|
34
|
+
pathname?: undefined;
|
|
35
|
+
search?: undefined;
|
|
36
|
+
hash?: undefined;
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
export declare function request(url: string, { body, qs, debug, ...options }?: Record<string, unknown>, kind?: 'raw' | 'xml' | 'json' | 'form'): Promise<any>;
|