skapi-js 0.0.1
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 +201 -0
- package/README.md +53 -0
- package/dist/skapi.js +3 -0
- package/dist/skapi.js.LICENSE.txt +21 -0
- package/dist/skapi.js.map +1 -0
- package/package.json +35 -0
- package/src/Api.ts +3 -0
- package/src/Types.ts +385 -0
- package/src/decorators.ts +94 -0
- package/src/skapi.ts +3609 -0
- package/src/skapi_error.ts +43 -0
- package/src/utils.ts +735 -0
- package/tsconfig.json +116 -0
- package/webpack.config.js +21 -0
package/src/utils.ts
ADDED
|
@@ -0,0 +1,735 @@
|
|
|
1
|
+
import SkapiError from "./skapi_error";
|
|
2
|
+
import { RecordData, Form } from "./Types";
|
|
3
|
+
|
|
4
|
+
const sha256: any = function (ascii) {
|
|
5
|
+
// author: https://geraintluff.github.io/sha256/
|
|
6
|
+
|
|
7
|
+
function rightRotate(value, amount) {
|
|
8
|
+
return (value >>> amount) | (value << (32 - amount));
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
var mathPow = Math.pow;
|
|
12
|
+
var maxWord = mathPow(2, 32);
|
|
13
|
+
var lengthProperty = 'length';
|
|
14
|
+
var i, j; // Used as a counter across the whole file
|
|
15
|
+
var result = '';
|
|
16
|
+
|
|
17
|
+
var words = [];
|
|
18
|
+
var asciiBitLength = ascii[lengthProperty] * 8;
|
|
19
|
+
|
|
20
|
+
//* caching results is optional - remove/add slash from front of this line to toggle
|
|
21
|
+
// Initial hash value: first 32 bits of the fractional parts of the square roots of the first 8 primes
|
|
22
|
+
// (we actually calculate the first 64, but extra values are just ignored)
|
|
23
|
+
var hash = sha256.h = sha256.h || [];
|
|
24
|
+
// Round constants: first 32 bits of the fractional parts of the cube roots of the first 64 primes
|
|
25
|
+
var k = sha256.k = sha256.k || [];
|
|
26
|
+
var primeCounter = k[lengthProperty];
|
|
27
|
+
/*/
|
|
28
|
+
var hash = [], k = [];
|
|
29
|
+
var primeCounter = 0;
|
|
30
|
+
//*/
|
|
31
|
+
|
|
32
|
+
var isComposite = {};
|
|
33
|
+
for (var candidate = 2; primeCounter < 64; candidate++) {
|
|
34
|
+
if (!isComposite[candidate]) {
|
|
35
|
+
for (i = 0; i < 313; i += candidate) {
|
|
36
|
+
isComposite[i] = candidate;
|
|
37
|
+
}
|
|
38
|
+
hash[primeCounter] = (mathPow(candidate, .5) * maxWord) | 0;
|
|
39
|
+
k[primeCounter++] = (mathPow(candidate, 1 / 3) * maxWord) | 0;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
ascii += '\x80'; // Append Ƈ' bit (plus zero padding)
|
|
44
|
+
while (ascii[lengthProperty] % 64 - 56) ascii += '\x00'; // More zero padding
|
|
45
|
+
for (i = 0; i < ascii[lengthProperty]; i++) {
|
|
46
|
+
j = ascii.charCodeAt(i);
|
|
47
|
+
if (j >> 8) return; // ASCII check: only accept characters in range 0-255
|
|
48
|
+
words[i >> 2] |= j << ((3 - i) % 4) * 8;
|
|
49
|
+
}
|
|
50
|
+
words[words[lengthProperty]] = ((asciiBitLength / maxWord) | 0);
|
|
51
|
+
words[words[lengthProperty]] = (asciiBitLength);
|
|
52
|
+
|
|
53
|
+
// process each chunk
|
|
54
|
+
for (j = 0; j < words[lengthProperty];) {
|
|
55
|
+
var w = words.slice(j, j += 16); // The message is expanded into 64 words as part of the iteration
|
|
56
|
+
var oldHash = hash;
|
|
57
|
+
// This is now the undefinedworking hash", often labelled as variables a...g
|
|
58
|
+
// (we have to truncate as well, otherwise extra entries at the end accumulate
|
|
59
|
+
hash = hash.slice(0, 8);
|
|
60
|
+
|
|
61
|
+
for (i = 0; i < 64; i++) {
|
|
62
|
+
var i2 = i + j;
|
|
63
|
+
// Expand the message into 64 words
|
|
64
|
+
// Used below if
|
|
65
|
+
var w15 = w[i - 15], w2 = w[i - 2];
|
|
66
|
+
|
|
67
|
+
// Iterate
|
|
68
|
+
var a = hash[0], e = hash[4];
|
|
69
|
+
var temp1 = hash[7]
|
|
70
|
+
+ (rightRotate(e, 6) ^ rightRotate(e, 11) ^ rightRotate(e, 25)) // S1
|
|
71
|
+
+ ((e & hash[5]) ^ ((~e) & hash[6])) // ch
|
|
72
|
+
+ k[i]
|
|
73
|
+
// Expand the message schedule if needed
|
|
74
|
+
+ (w[i] = (i < 16) ? w[i] : (
|
|
75
|
+
w[i - 16]
|
|
76
|
+
+ (rightRotate(w15, 7) ^ rightRotate(w15, 18) ^ (w15 >>> 3)) // s0
|
|
77
|
+
+ w[i - 7]
|
|
78
|
+
+ (rightRotate(w2, 17) ^ rightRotate(w2, 19) ^ (w2 >>> 10)) // s1
|
|
79
|
+
) | 0
|
|
80
|
+
);
|
|
81
|
+
// This is only used once, so *could* be moved below, but it only saves 4 bytes and makes things unreadble
|
|
82
|
+
var temp2 = (rightRotate(a, 2) ^ rightRotate(a, 13) ^ rightRotate(a, 22)) // S0
|
|
83
|
+
+ ((a & hash[1]) ^ (a & hash[2]) ^ (hash[1] & hash[2])); // maj
|
|
84
|
+
|
|
85
|
+
hash = [(temp1 + temp2) | 0].concat(hash); // We don't bother trimming off the extra ones, they're harmless as long as we're truncating when we do the slice()
|
|
86
|
+
hash[4] = (hash[4] + temp1) | 0;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
for (i = 0; i < 8; i++) {
|
|
90
|
+
hash[i] = (hash[i] + oldHash[i]) | 0;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
for (i = 0; i < 8; i++) {
|
|
95
|
+
for (j = 3; j + 1; j--) {
|
|
96
|
+
var b = (hash[i] >> (j * 8)) & 255;
|
|
97
|
+
result += ((b < 16) ? 0 : '') + b.toString(16);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return result;
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
function checkParams(
|
|
104
|
+
params: any,
|
|
105
|
+
struct: Record<string, any>,
|
|
106
|
+
required: string[] | null = null,
|
|
107
|
+
bypassCheck: string[] | null = [],
|
|
108
|
+
_parentKey: string | null = null
|
|
109
|
+
): any {
|
|
110
|
+
// struct = {
|
|
111
|
+
// a: 'boolean',
|
|
112
|
+
// b: ['number', 'boolean', 'string', 'array', 'function', 'custom value', () => 'default value'],
|
|
113
|
+
// c: (v: any) => { return 'value to assign'; }
|
|
114
|
+
// }
|
|
115
|
+
|
|
116
|
+
if (Array.isArray(bypassCheck)) {
|
|
117
|
+
bypassCheck = bypassCheck.concat([
|
|
118
|
+
// list of default key names to bypass
|
|
119
|
+
'service',
|
|
120
|
+
'service_owner',
|
|
121
|
+
// 'alertError',
|
|
122
|
+
// 'response',
|
|
123
|
+
// 'startKey'
|
|
124
|
+
]);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function isObjectWithKeys(obj: any) {
|
|
128
|
+
if (obj instanceof Promise) {
|
|
129
|
+
throw new SkapiError('Parameter should not be a promise', { code: 'INVALID_PARAMETER' });
|
|
130
|
+
}
|
|
131
|
+
return obj && typeof obj === 'object' && !Array.isArray(obj) && Object.keys(obj).length;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function isEmptyObject(obj: any) {
|
|
135
|
+
return obj && typeof obj === 'object' && !Array.isArray(obj) && !Object.keys(obj).length;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
let _params = params; // processed params
|
|
139
|
+
let val: any; // value to return
|
|
140
|
+
let errToThrow: any = null; // error msg to output
|
|
141
|
+
let isInvalid = _parentKey ? ` in "${_parentKey}" is invalid.` : '. Parameter should be type <object>.';
|
|
142
|
+
|
|
143
|
+
if (_parentKey === null) {
|
|
144
|
+
// parent level. executes on first run
|
|
145
|
+
if (isObjectWithKeys(_params)) {
|
|
146
|
+
if (_params instanceof HTMLFormElement || _params instanceof FormData) {
|
|
147
|
+
// first execution, it's an object or form element
|
|
148
|
+
_params = extractFormMetaData(params)?.meta;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
else {
|
|
152
|
+
_params = JSON.parse(JSON.stringify(params));
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
for (let k in _params) {
|
|
156
|
+
// check if there is invalid key names
|
|
157
|
+
if (!struct.hasOwnProperty(k) && Array.isArray(bypassCheck) && !bypassCheck.includes(k)) {
|
|
158
|
+
throw new SkapiError(`Key name "${k}" is invalid in parameter.`, { code: 'INVALID_PARAMETER' });
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (Array.isArray(required) && required.length) {
|
|
163
|
+
// check if any required key names are missing
|
|
164
|
+
for (let k of required) {
|
|
165
|
+
if (!Object.keys(_params).includes(k)) {
|
|
166
|
+
throw new SkapiError(`Key "${k}" is required in parameter.`, { code: 'INVALID_PARAMETER' });
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
else if (isEmptyObject(_params) || typeof _params === 'undefined') {
|
|
173
|
+
// parameter is empty or undefined
|
|
174
|
+
let defaults: Record<string, any> = {};
|
|
175
|
+
|
|
176
|
+
// sets default for all keys
|
|
177
|
+
// key: [()=>'default']
|
|
178
|
+
for (let s in struct) {
|
|
179
|
+
// iterate whole structure object
|
|
180
|
+
let structValue = struct[s];
|
|
181
|
+
if (Array.isArray(structValue) && typeof structValue[structValue.length - 1] === 'function')
|
|
182
|
+
// set all default values
|
|
183
|
+
defaults[s] = structValue[structValue.length - 1]();
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// return if there is any default value
|
|
187
|
+
return Object.keys(defaults).length ? defaults : _params;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (_params === null) {
|
|
191
|
+
// if null ignore defaults
|
|
192
|
+
return null;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
if (isObjectWithKeys(struct) && isObjectWithKeys(_params)) {
|
|
197
|
+
for (let s in struct) {
|
|
198
|
+
// loop through structure keys
|
|
199
|
+
let structValue = struct[s];
|
|
200
|
+
|
|
201
|
+
if (_params.hasOwnProperty(s) && typeof _params[s] != 'undefined') {
|
|
202
|
+
// recurse to check data type
|
|
203
|
+
_params[s] = checkParams(_params[s], structValue, null, null, s);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
else {
|
|
207
|
+
// if current _params does not have the corresponding key name
|
|
208
|
+
let defaultSetter =
|
|
209
|
+
Array.isArray(structValue) &&
|
|
210
|
+
typeof structValue[structValue.length - 1] === 'function' ? structValue[structValue.length - 1] : null;
|
|
211
|
+
|
|
212
|
+
if (defaultSetter) {
|
|
213
|
+
// set default
|
|
214
|
+
let def = defaultSetter();
|
|
215
|
+
if (def !== undefined) {
|
|
216
|
+
_params[s] = def;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
val = _params;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// recursive level
|
|
226
|
+
else if (Array.isArray(struct)) {
|
|
227
|
+
// loop through value types
|
|
228
|
+
for (let s of struct) {
|
|
229
|
+
try {
|
|
230
|
+
if (typeof _params !== undefined && typeof s !== 'function') {
|
|
231
|
+
// dive in, check value types
|
|
232
|
+
val = checkParams(_params, s, null, null, _parentKey);
|
|
233
|
+
}
|
|
234
|
+
// if error, loop to next, otherwise break
|
|
235
|
+
break;
|
|
236
|
+
} catch (err) {
|
|
237
|
+
if (typeof err === 'string' && err.substring(0, 6) === 'BREAK:') {
|
|
238
|
+
// break on BREAK message
|
|
239
|
+
err = err.substring(5);
|
|
240
|
+
let errMsg = (err as string).split(':');
|
|
241
|
+
errToThrow = new SkapiError(errMsg[1], { code: errMsg[0] });
|
|
242
|
+
break;
|
|
243
|
+
}
|
|
244
|
+
else {
|
|
245
|
+
errToThrow = err;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// returned values will be applied to params
|
|
252
|
+
else if (typeof struct === 'function') {
|
|
253
|
+
return struct(_params);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
else if (typeof struct === 'string') {
|
|
257
|
+
// setup value type range
|
|
258
|
+
if (Array.isArray(_params)) {
|
|
259
|
+
// check for array
|
|
260
|
+
if (struct !== 'array') {
|
|
261
|
+
throw new SkapiError(`Invalid type "${typeof _params}"${isInvalid}`, { code: 'INVALID_PARAMETER' });
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// array only accepts number, string, boolean, null.
|
|
265
|
+
// object is not allowed to be nested in array.
|
|
266
|
+
for (let p of _params) {
|
|
267
|
+
if (!['number', 'string', 'boolean'].includes(typeof p) && p !== null) {
|
|
268
|
+
throw new SkapiError(`Invalid type "${typeof p}" in "${_parentKey}" array value.`, { code: 'INVALID_PARAMETER' });
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
val = _params;
|
|
273
|
+
}
|
|
274
|
+
else if (!['number', 'string', 'boolean', 'array', 'function'].includes(struct)) {
|
|
275
|
+
// match custom string values
|
|
276
|
+
if (_params === struct) {
|
|
277
|
+
val = _params;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
else {
|
|
281
|
+
throw new SkapiError(`Value: ${_params}${isInvalid}`, { code: 'INVALID_PARAMETER' });
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
else if (typeof _params === struct) {
|
|
285
|
+
if (struct === 'number') {
|
|
286
|
+
// throws error if number range is invalid
|
|
287
|
+
if (Math.abs(_params) > 4503599627370496) {
|
|
288
|
+
throw `BREAK:INVALID_PARAMETER:"${_parentKey}" integer value should be within -4503599627370496 ~ +4503599627370546.`;
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
val = _params;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
else if (struct === null) {
|
|
297
|
+
// bypass value on null
|
|
298
|
+
val = _params;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
if (val === undefined) {
|
|
302
|
+
throw errToThrow || new SkapiError(`Type "undefined"${isInvalid}`, { code: 'INVALID_PARAMETER' });
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
return val;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
function extractFormMetaData(form: Form) {
|
|
309
|
+
// creates meta object to post
|
|
310
|
+
|
|
311
|
+
function appendData(meta, key, val, append = true) {
|
|
312
|
+
if (meta[key] && append) {
|
|
313
|
+
if (Array.isArray(meta)) {
|
|
314
|
+
meta[key].push(val);
|
|
315
|
+
}
|
|
316
|
+
else {
|
|
317
|
+
meta[key] = [meta[key], val];
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
else {
|
|
321
|
+
meta[key] = val;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
if (form instanceof FormData) {
|
|
326
|
+
let meta = {};
|
|
327
|
+
let totalFileSize = 0;
|
|
328
|
+
let files = [];
|
|
329
|
+
|
|
330
|
+
for (let pair of form.entries()) {
|
|
331
|
+
let name = pair[0];
|
|
332
|
+
let v: any = pair[1];
|
|
333
|
+
|
|
334
|
+
if (v instanceof File) {
|
|
335
|
+
if (!files.includes(name)) {
|
|
336
|
+
files.push(name);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
totalFileSize += Math.round((v.size / 1024));
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
else if (v instanceof FileList) {
|
|
343
|
+
if (!files.includes(name)) {
|
|
344
|
+
files.push(name);
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
if (v && v.length > 0) {
|
|
348
|
+
for (let idx = 0; idx <= v.length - 1; idx++) {
|
|
349
|
+
totalFileSize += Math.round((v.item(idx).size / 1024));
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
else {
|
|
355
|
+
appendData(meta, name, v);
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
if (totalFileSize > 5120) {
|
|
360
|
+
throw new SkapiError('Files cannot exceed 5MB. Use skapi.uploadFiles(...) instead.', { code: 'INVALID_REQUEST' });
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
return { meta, files };
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
else if (form instanceof HTMLFormElement) {
|
|
367
|
+
let meta = {};
|
|
368
|
+
let files = [];
|
|
369
|
+
let totalFileSize = 0;
|
|
370
|
+
let inputs = form.querySelectorAll('input');
|
|
371
|
+
let textarea = form.querySelectorAll('textarea');
|
|
372
|
+
|
|
373
|
+
for (let i of textarea) {
|
|
374
|
+
if (i.name) {
|
|
375
|
+
appendData(meta, i.name, i.value);
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
for (let i of inputs) {
|
|
380
|
+
if (i.name) {
|
|
381
|
+
if (i.type === 'number' && i.value) {
|
|
382
|
+
appendData(meta, i.name, Number(i.value));
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
else if (i.type === 'checkbox' || i.type === 'radio') {
|
|
386
|
+
if (i.value === 'on' || i.value === 'true') {
|
|
387
|
+
appendData(meta, i.name, i.checked, false);
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
else if (i.value === 'false') {
|
|
391
|
+
appendData(meta, i.name, !i.checked, false);
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
else if (i.checked) {
|
|
395
|
+
appendData(meta, i.name, i.value, false);
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
else if (i.type === 'file') {
|
|
400
|
+
if (!files.includes(i.name)) {
|
|
401
|
+
files.push(i.name);
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
if (i.files && i.files.length > 0) {
|
|
405
|
+
for (let idx = 0; idx <= i.files.length - 1; idx++) {
|
|
406
|
+
totalFileSize += Math.round((i.files.item(idx).size / 1024));
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
else {
|
|
412
|
+
appendData(meta, i.name, i.value);
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
if (totalFileSize > 5120) {
|
|
418
|
+
throw new SkapiError('Files cannot exceed 5MB. Use skapi.uploadFiles(...) instead.', { code: 'INVALID_REQUEST' });
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
return { meta, files };
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
return null;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
// validation checks
|
|
428
|
+
|
|
429
|
+
function validateUserId(id: string, param = 'User ID') {
|
|
430
|
+
let uuid_regex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-5][0-9a-f]{3}-[089ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
431
|
+
|
|
432
|
+
if (!id) {
|
|
433
|
+
throw new SkapiError(`${param} is empty.`, { code: 'INVALID_PARAMETER' });
|
|
434
|
+
}
|
|
435
|
+
else if (typeof id !== 'string') {
|
|
436
|
+
throw new SkapiError(`${param} should be type: string.`, { code: 'INVALID_PARAMETER' });
|
|
437
|
+
}
|
|
438
|
+
else if (!id.match(uuid_regex)) {
|
|
439
|
+
throw new SkapiError(`${param} is invalid.`, { code: 'INVALID_PARAMETER' });
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
return id;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
function validatePhoneNumber(value: string) {
|
|
446
|
+
if (typeof value !== 'string' || value.charAt(0) !== '+' || isNaN(Number(value.substring(1)))) {
|
|
447
|
+
throw new SkapiError('"phone_number" is invalid. The format should be "+00123456789". Type: string.', { code: 'INVALID_PARAMETER' });
|
|
448
|
+
}
|
|
449
|
+
return value;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
function validateBirthdate(birthdate: string) {
|
|
453
|
+
// yyyy-mm-dd
|
|
454
|
+
if (typeof birthdate !== 'string') {
|
|
455
|
+
throw new SkapiError('"birthdate" is invalid. The format should be "yyyy-mm-dd". Type: string.', { code: 'INVALID_PARAMETER' });
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
else {
|
|
459
|
+
let date_regex = new RegExp(/([12]\d{3}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01]))/);
|
|
460
|
+
if (birthdate.length !== 10 || birthdate.split('-').length !== 3 || !date_regex.test(birthdate)) {
|
|
461
|
+
throw new SkapiError('"birthdate" is invalid. The format should be "yyyy-mm-dd". Type: string.', { code: 'INVALID_PARAMETER' });
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
return birthdate;
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
function validatePassword(password: string) {
|
|
468
|
+
if (!password) {
|
|
469
|
+
throw new SkapiError('"password" is empty.', { code: 'PASSWORD_REQUIRED' });
|
|
470
|
+
}
|
|
471
|
+
else if (typeof password !== 'string') {
|
|
472
|
+
throw new SkapiError('"password" should be type: string.', { code: 'INVALID_PASSWORD' });
|
|
473
|
+
}
|
|
474
|
+
else if (password.length < 6) {
|
|
475
|
+
throw new SkapiError('"password" should be at least 6 characters.', { code: 'INVALID_PASSWORD' });
|
|
476
|
+
}
|
|
477
|
+
else if (password.length > 60) {
|
|
478
|
+
throw new SkapiError('"password" can be up to 60 characters max.', { code: 'INVALID_PASSWORD' });
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
return password;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
function validateEmail(email: string, paramName: string = 'email') {
|
|
485
|
+
if (!email) {
|
|
486
|
+
throw new SkapiError(`"${paramName}" is empty.`, { code: 'EMAIL_REQUIRED' });
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
else if (typeof email !== 'string') {
|
|
490
|
+
throw new SkapiError(`"${paramName}"should be type: string.`, { code: 'INVALID_EMAIL' });
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
else if (email.length < 5 || email.length > 64) {
|
|
494
|
+
throw new SkapiError(`"${paramName}" should be at least 5 characters and max 64 characters.`, { code: 'INVALID_EMAIL' });
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
else if (/^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$/.test(email)) {
|
|
498
|
+
email = email.trim();
|
|
499
|
+
let splitAt = email.split('@');
|
|
500
|
+
let tld = splitAt[1].split('.');
|
|
501
|
+
|
|
502
|
+
if (tld.length >= 2) {
|
|
503
|
+
return email.toLowerCase();
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
throw new SkapiError(`"${email}" is an invalid email.`, { code: 'INVALID_EMAIL' });
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
function validateUrl(url: string | string[]) {
|
|
511
|
+
const baseUrl = (() => {
|
|
512
|
+
let baseUrl = window?.location?.origin || null;
|
|
513
|
+
|
|
514
|
+
if (baseUrl === 'file://') {
|
|
515
|
+
baseUrl += window.location.pathname;
|
|
516
|
+
let _baseUrl = baseUrl.split('/');
|
|
517
|
+
_baseUrl.pop();
|
|
518
|
+
baseUrl = _baseUrl.join('/');
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
return baseUrl;
|
|
522
|
+
})();
|
|
523
|
+
let check = (c: string) => {
|
|
524
|
+
if (typeof c === 'string') {
|
|
525
|
+
if (c === '*') {
|
|
526
|
+
return '*';
|
|
527
|
+
}
|
|
528
|
+
else {
|
|
529
|
+
let cu = c.trim();
|
|
530
|
+
if (!cu.includes(' ') && !cu.includes(',')) {
|
|
531
|
+
if (cu.substring(0, 1) === '/' && baseUrl) {
|
|
532
|
+
cu = baseUrl + cu;
|
|
533
|
+
}
|
|
534
|
+
let _url = null;
|
|
535
|
+
|
|
536
|
+
try {
|
|
537
|
+
_url = new URL(cu);
|
|
538
|
+
} catch (err) {
|
|
539
|
+
throw new SkapiError(`"${c}" is an invalid url.`, { code: 'INVALID_PARAMETER' });
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
if (_url.protocol) {
|
|
543
|
+
let url = _url.href;
|
|
544
|
+
if (url.charAt(url.length - 1) === '/')
|
|
545
|
+
url = url.substring(0, url.length - 1);
|
|
546
|
+
|
|
547
|
+
return url;
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
throw new SkapiError(`"${c}" is an invalid url.`, { code: 'INVALID_PARAMETER' });
|
|
554
|
+
};
|
|
555
|
+
|
|
556
|
+
if (Array.isArray(url)) {
|
|
557
|
+
return url.map(u => check(u));
|
|
558
|
+
}
|
|
559
|
+
else {
|
|
560
|
+
return check(url);
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
function normalize_record_data<T extends RecordData>(record: T): RecordData {
|
|
565
|
+
const output: Record<any, any> = {};
|
|
566
|
+
|
|
567
|
+
const keys = {
|
|
568
|
+
'rec': (r) => {
|
|
569
|
+
if (!r) return;
|
|
570
|
+
output.record_id = r;
|
|
571
|
+
let base36timestamp = r.substring(0, 8);
|
|
572
|
+
output.uploaded = parseInt(base36timestamp, 36);
|
|
573
|
+
},
|
|
574
|
+
'usr': (r) => {
|
|
575
|
+
if (!r) return;
|
|
576
|
+
output.user_id = r;
|
|
577
|
+
},
|
|
578
|
+
'tbl': (r) => {
|
|
579
|
+
if (!r) return;
|
|
580
|
+
let rSplit = r.split('/');
|
|
581
|
+
output.table = rSplit[0];
|
|
582
|
+
output.access_group = rSplit[2] == '@@' ? 'private' : parseInt(rSplit[2]);
|
|
583
|
+
if (rSplit?.[3]) {
|
|
584
|
+
output.subscription = {
|
|
585
|
+
user_id: rSplit[3],
|
|
586
|
+
group: parseInt(rSplit[4])
|
|
587
|
+
};
|
|
588
|
+
}
|
|
589
|
+
},
|
|
590
|
+
'idx': (r) => {
|
|
591
|
+
if (!r) return;
|
|
592
|
+
let rSplit = r.split('!');
|
|
593
|
+
let name = rSplit.splice(0, 1)[0];
|
|
594
|
+
let value = normalize_typed_string(rSplit.join('!'));
|
|
595
|
+
output.index = {
|
|
596
|
+
name,
|
|
597
|
+
value
|
|
598
|
+
};
|
|
599
|
+
},
|
|
600
|
+
'ref': (r) => {
|
|
601
|
+
if (!r) return;
|
|
602
|
+
output.reference = r.split('/')[0];
|
|
603
|
+
},
|
|
604
|
+
'tags': (r) => {
|
|
605
|
+
if (!r) return;
|
|
606
|
+
output.tags = r;
|
|
607
|
+
},
|
|
608
|
+
'upd': (r) => {
|
|
609
|
+
if (!r) return;
|
|
610
|
+
output.updated = r;
|
|
611
|
+
},
|
|
612
|
+
'acpt_mrf': (r) => {
|
|
613
|
+
if (!r) return;
|
|
614
|
+
if (!output?.config)
|
|
615
|
+
output.config = {};
|
|
616
|
+
|
|
617
|
+
output.config.allow_multiple_reference = r;
|
|
618
|
+
},
|
|
619
|
+
'ref_limt': (r) => {
|
|
620
|
+
if (!r) return;
|
|
621
|
+
if (!output?.config)
|
|
622
|
+
output.config = {};
|
|
623
|
+
|
|
624
|
+
output.config.reference_limit = r;
|
|
625
|
+
},
|
|
626
|
+
'rfd': (r) => {
|
|
627
|
+
output.referenced_count = r;
|
|
628
|
+
},
|
|
629
|
+
'prv_acs': (r) => {
|
|
630
|
+
if (!r) return;
|
|
631
|
+
if (!output?.config)
|
|
632
|
+
output.config = {};
|
|
633
|
+
|
|
634
|
+
output.config.private_access = r;
|
|
635
|
+
},
|
|
636
|
+
'data': (r) => {
|
|
637
|
+
let data = r;
|
|
638
|
+
if (r === '!D%{}') {
|
|
639
|
+
data = {};
|
|
640
|
+
}
|
|
641
|
+
else if (r === '!L%[]') {
|
|
642
|
+
data = [];
|
|
643
|
+
}
|
|
644
|
+
output.data = data;
|
|
645
|
+
}
|
|
646
|
+
};
|
|
647
|
+
|
|
648
|
+
if (record.record_id) {
|
|
649
|
+
// bypass already normalized records
|
|
650
|
+
return record;
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
for (let k in keys) {
|
|
654
|
+
keys[k](record[k]);
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
return output as RecordData;
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
function normalize_typed_string(v: string) {
|
|
661
|
+
let value = v.substring(2);
|
|
662
|
+
let type = v.substring(0, 2);
|
|
663
|
+
|
|
664
|
+
switch (type) {
|
|
665
|
+
case "S%":
|
|
666
|
+
// S%string
|
|
667
|
+
return value;
|
|
668
|
+
case "N%":
|
|
669
|
+
// N%0
|
|
670
|
+
return Number(value) - 4503599627370496;
|
|
671
|
+
case "B%":
|
|
672
|
+
// B%1
|
|
673
|
+
return value === '1';
|
|
674
|
+
case "L%":
|
|
675
|
+
// L%[0, "hello"]
|
|
676
|
+
try {
|
|
677
|
+
return JSON.parse(value);
|
|
678
|
+
} catch (err) {
|
|
679
|
+
throw new SkapiError('Value parse error.', { code: 'PARSE_ERROR' });
|
|
680
|
+
}
|
|
681
|
+
default:
|
|
682
|
+
return v;
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
function checkWhiteSpaceAndSpecialChars(
|
|
687
|
+
string: string | string[],
|
|
688
|
+
p = 'parameter',
|
|
689
|
+
allowPeriods = false,
|
|
690
|
+
allowWhiteSpace = false
|
|
691
|
+
) {
|
|
692
|
+
let checkStr = (s: string, array = false) => {
|
|
693
|
+
if (typeof s !== 'string') {
|
|
694
|
+
throw new SkapiError(`${p} should be type: <string | string[]>.`, { code: 'INVALID_PARAMETER' });
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
if (!allowWhiteSpace && string.includes(' ')) {
|
|
698
|
+
throw new SkapiError(`${p} should not have whitespace.`, { code: 'INVALID_PARAMETER' });
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
if (!allowPeriods && string.includes('.')) {
|
|
702
|
+
throw new SkapiError(`${p} should not have periods.`, { code: 'INVALID_PARAMETER' });
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
if (/[`!@#$%^&*()_+\-=\[\]{};':"\\|,<>\/?~]/.test(s)) {
|
|
706
|
+
throw new SkapiError(`${p} should not have special characters.`, { code: 'INVALID_PARAMETER' });
|
|
707
|
+
}
|
|
708
|
+
};
|
|
709
|
+
|
|
710
|
+
if (Array.isArray(string)) {
|
|
711
|
+
for (let s of string) {
|
|
712
|
+
checkStr(s, true);
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
else {
|
|
717
|
+
checkStr(string, false);
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
return string;
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
export {
|
|
724
|
+
checkWhiteSpaceAndSpecialChars,
|
|
725
|
+
normalize_record_data,
|
|
726
|
+
checkParams,
|
|
727
|
+
extractFormMetaData,
|
|
728
|
+
validateUserId,
|
|
729
|
+
validateBirthdate,
|
|
730
|
+
validateEmail,
|
|
731
|
+
validatePassword,
|
|
732
|
+
validatePhoneNumber,
|
|
733
|
+
validateUrl,
|
|
734
|
+
sha256
|
|
735
|
+
};
|