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/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
+ };