@etsoo/shared 1.2.40 → 1.2.42
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/README.md +2 -0
- package/__tests__/DomUtils.ts +89 -0
- package/__tests__/Utils.ts +11 -0
- package/lib/cjs/DomUtils.d.ts +43 -0
- package/lib/cjs/DomUtils.js +99 -0
- package/lib/cjs/Utils.d.ts +2 -1
- package/lib/cjs/Utils.js +22 -13
- package/lib/mjs/DomUtils.d.ts +43 -0
- package/lib/mjs/DomUtils.js +99 -0
- package/lib/mjs/Utils.d.ts +2 -1
- package/lib/mjs/Utils.js +22 -13
- package/package.json +2 -2
- package/src/DomUtils.ts +156 -0
- package/src/Utils.ts +26 -15
package/README.md
CHANGED
|
@@ -224,8 +224,10 @@ DOM/window related utilities
|
|
|
224
224
|
|headersToObject|Convert headers to object|
|
|
225
225
|
|isFormData|Is IFormData type guard|
|
|
226
226
|
|isJSONContentType|Is JSON content type|
|
|
227
|
+
|isWechatClient|Is Wechat client|
|
|
227
228
|
|mergeFormData|Merge form data to primary one|
|
|
228
229
|
|mergeURLSearchParams|Merge URL search parameters|
|
|
230
|
+
|parseUserAgent|parseUserAgent|
|
|
229
231
|
|setFocus|Set HTML element focus by name|
|
|
230
232
|
|setupLogging|Setup frontend logging|
|
|
231
233
|
|verifyPermission|Verify file system permission|
|
package/__tests__/DomUtils.ts
CHANGED
|
@@ -328,6 +328,95 @@ test('Tests for getInputValue', () => {
|
|
|
328
328
|
}
|
|
329
329
|
});
|
|
330
330
|
|
|
331
|
+
test('Tests for getUserAgentData 1', () => {
|
|
332
|
+
const data = DomUtils.parseUserAgent(
|
|
333
|
+
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36'
|
|
334
|
+
);
|
|
335
|
+
expect(data?.device).toBe('Desktop');
|
|
336
|
+
expect(data?.platform).toBe('Windows NT');
|
|
337
|
+
expect(data?.platformVersion).toBe('10.0');
|
|
338
|
+
expect(data?.brands.find((b) => b.brand === 'Chrome')?.version).toBe('124');
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
test('Tests for getUserAgentData 2', () => {
|
|
342
|
+
const data = DomUtils.parseUserAgent(
|
|
343
|
+
'Mozilla/5.0 (Linux; U; Android 2.3.6; zh-cn; GT-S5660 Build/GINGERBREAD) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1 MicroMessenger/4.5.255'
|
|
344
|
+
);
|
|
345
|
+
expect(data?.device).toBe('GT-S5660');
|
|
346
|
+
expect(data?.platform).toBe('Android');
|
|
347
|
+
expect(data?.platformVersion).toBe('2.3.6');
|
|
348
|
+
expect(data?.mobile).toBeTruthy();
|
|
349
|
+
expect(DomUtils.isWechatClient(data)).toBeTruthy();
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
test('Tests for getUserAgentData 3', () => {
|
|
353
|
+
const data = DomUtils.parseUserAgent(
|
|
354
|
+
'Mozilla/5.0 (Linux; Android 7.1.1;MEIZU E3 Build/NGI77B; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/66.0.3359.126 MQQBrowser/9.6 TBS/044428 Mobile Safari/537.36 MicroMessenger/6.6.7.1321(0x26060739) NetType/WIFI Language/zh_CN'
|
|
355
|
+
);
|
|
356
|
+
|
|
357
|
+
expect(data?.device).toBe('MEIZU E3');
|
|
358
|
+
expect(data?.platform).toBe('Android');
|
|
359
|
+
expect(data?.platformVersion).toBe('7.1.1');
|
|
360
|
+
expect(data?.mobile).toBeTruthy();
|
|
361
|
+
expect(DomUtils.isWechatClient(data)).toBeTruthy();
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
test('Tests for getUserAgentData 4', () => {
|
|
365
|
+
const data = DomUtils.parseUserAgent(
|
|
366
|
+
'Mozilla/5.0 (iPhone; CPU iPhone OS 17_5_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.4.1 Mobile/15E148 Safari/604.1'
|
|
367
|
+
);
|
|
368
|
+
|
|
369
|
+
expect(data?.device).toBe('iPhone');
|
|
370
|
+
expect(data?.platform).toBe('iPhone OS');
|
|
371
|
+
expect(data?.platformVersion).toBe('17.5.1');
|
|
372
|
+
expect(data?.mobile).toBeTruthy();
|
|
373
|
+
expect(DomUtils.isWechatClient(data)).toBeFalsy();
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
test('Tests for getUserAgentData 5', () => {
|
|
377
|
+
const data = DomUtils.parseUserAgent(
|
|
378
|
+
'Mozilla/5.0 (SMART-TV; Linux; Tizen 2.3) AppleWebkit/538.1 (KHTML, like Gecko) SamsungBrowser/1.0 TV Safari/538.1'
|
|
379
|
+
);
|
|
380
|
+
|
|
381
|
+
expect(data?.device).toBe('SMART-TV');
|
|
382
|
+
expect(data?.platform).toBe('Tizen');
|
|
383
|
+
expect(data?.platformVersion).toBe('2.3');
|
|
384
|
+
expect(data?.mobile).toBeFalsy();
|
|
385
|
+
});
|
|
386
|
+
|
|
387
|
+
test('Tests for getUserAgentData 6', () => {
|
|
388
|
+
const data = DomUtils.parseUserAgent(
|
|
389
|
+
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0 Safari/605.1.15'
|
|
390
|
+
);
|
|
391
|
+
|
|
392
|
+
expect(data?.device).toBe('Macintosh');
|
|
393
|
+
expect(data?.platform).toBe('Mac OS X');
|
|
394
|
+
expect(data?.platformVersion).toBe('10.15');
|
|
395
|
+
expect(data?.mobile).toBeFalsy();
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
test('Tests for getUserAgentData 7', () => {
|
|
399
|
+
const data = DomUtils.parseUserAgent(
|
|
400
|
+
'Mozilla/5.0 (Linux; Android 8.1; LEO-DLXXE Build/HONORLRA-AL00) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.111 HuaweiBrowser/9.1.1.308 Mobile Safari/537.36'
|
|
401
|
+
);
|
|
402
|
+
|
|
403
|
+
expect(data?.device).toBe('LEO-DLXXE');
|
|
404
|
+
expect(data?.platform).toBe('Android');
|
|
405
|
+
expect(data?.platformVersion).toBe('8.1');
|
|
406
|
+
expect(data?.mobile).toBeTruthy();
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
test('Tests for getUserAgentData 8', () => {
|
|
410
|
+
const data = DomUtils.parseUserAgent(
|
|
411
|
+
'Mozilla/5.0 (Linux; Android 9; SM-R825F Build/QP1A.190711.020; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/80.0.3987.119 Mobile Safari/537.36'
|
|
412
|
+
);
|
|
413
|
+
|
|
414
|
+
expect(data?.device).toBe('SM-R825F');
|
|
415
|
+
expect(data?.platform).toBe('Android');
|
|
416
|
+
expect(data?.platformVersion).toBe('9');
|
|
417
|
+
expect(data?.mobile).toBeTruthy();
|
|
418
|
+
});
|
|
419
|
+
|
|
331
420
|
test('Tests for setupLogging', async () => {
|
|
332
421
|
// Arrange
|
|
333
422
|
const action = jest.fn((data: ErrorData) => {
|
package/__tests__/Utils.ts
CHANGED
|
@@ -306,6 +306,16 @@ test('Tests for setNestedValue', () => {
|
|
|
306
306
|
expect(Reflect.get((obj.jsonData as any).newProperty, 'value')).toBe(125);
|
|
307
307
|
});
|
|
308
308
|
|
|
309
|
+
test('Tests for setNestedValue removal', () => {
|
|
310
|
+
const obj = { jsonData: { photoSize: [200, 100], supportResizing: true } };
|
|
311
|
+
|
|
312
|
+
Utils.setNestedValue(obj, 'jsonData.photoSize', undefined);
|
|
313
|
+
expect(obj.jsonData.photoSize).toBeUndefined();
|
|
314
|
+
|
|
315
|
+
Utils.setNestedValue(obj, 'jsonData.supportResizing', undefined);
|
|
316
|
+
expect(obj.jsonData.supportResizing).toBeUndefined();
|
|
317
|
+
});
|
|
318
|
+
|
|
309
319
|
test('Tests for sortByFavor', () => {
|
|
310
320
|
const items = [1, 2, 3, 4, 5, 6, 7];
|
|
311
321
|
expect(Utils.sortByFavor(items, [5, 1, 3])).toStrictEqual([
|
|
@@ -345,4 +355,5 @@ test('Tests for trimEnd', () => {
|
|
|
345
355
|
expect(Utils.trimEnd('//a/', '/')).toBe('//a');
|
|
346
356
|
expect(Utils.trimEnd('/*/a*/', ...['/', '*'])).toBe('/*/a');
|
|
347
357
|
expect(Utils.trimEnd('abc', ...['/', '*'])).toBe('abc');
|
|
358
|
+
expect(Utils.trimEnd('12.0.0.0', '.0')).toBe('12');
|
|
348
359
|
});
|
package/lib/cjs/DomUtils.d.ts
CHANGED
|
@@ -2,6 +2,35 @@
|
|
|
2
2
|
import { DataTypes } from './DataTypes';
|
|
3
3
|
import { ErrorData, ErrorType } from './types/ErrorData';
|
|
4
4
|
import { FormDataFieldValue, IFormData } from './types/FormData';
|
|
5
|
+
/**
|
|
6
|
+
* User agent data, maybe replaced by navigator.userAgentData in future
|
|
7
|
+
* @see https://developer.mozilla.org/en-US/docs/Web/API/Navigator/userAgentData
|
|
8
|
+
*/
|
|
9
|
+
export type UserAgentData = {
|
|
10
|
+
/**
|
|
11
|
+
* Browser brands
|
|
12
|
+
*/
|
|
13
|
+
brands: {
|
|
14
|
+
brand: string;
|
|
15
|
+
version: string;
|
|
16
|
+
}[];
|
|
17
|
+
/**
|
|
18
|
+
* Is mobile device
|
|
19
|
+
*/
|
|
20
|
+
mobile: boolean;
|
|
21
|
+
/**
|
|
22
|
+
* Device brand (name)
|
|
23
|
+
*/
|
|
24
|
+
device: string;
|
|
25
|
+
/**
|
|
26
|
+
* Platform (OS)
|
|
27
|
+
*/
|
|
28
|
+
platform: string;
|
|
29
|
+
/**
|
|
30
|
+
* Platform version
|
|
31
|
+
*/
|
|
32
|
+
platformVersion?: string;
|
|
33
|
+
};
|
|
5
34
|
/**
|
|
6
35
|
* Dom Utilities
|
|
7
36
|
* Not all methods support Node
|
|
@@ -71,6 +100,12 @@ export declare namespace DomUtils {
|
|
|
71
100
|
* @returns Object
|
|
72
101
|
*/
|
|
73
102
|
function formDataToObject(form: IFormData): Record<string, FormDataFieldValue | FormDataFieldValue[]>;
|
|
103
|
+
/**
|
|
104
|
+
* Is wechat client
|
|
105
|
+
* @param data User agent data
|
|
106
|
+
* @returns Result
|
|
107
|
+
*/
|
|
108
|
+
function isWechatClient(data?: UserAgentData | null): boolean;
|
|
74
109
|
/**
|
|
75
110
|
* Culture match case Enum
|
|
76
111
|
*/
|
|
@@ -144,6 +179,14 @@ export declare namespace DomUtils {
|
|
|
144
179
|
* @param data New simple object data to merge
|
|
145
180
|
*/
|
|
146
181
|
function mergeURLSearchParams(base: URLSearchParams, data: DataTypes.SimpleObject): URLSearchParams;
|
|
182
|
+
/**
|
|
183
|
+
* Parse navigator's user agent string
|
|
184
|
+
* Lightweight User-Agent string parser
|
|
185
|
+
* https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/User-Agent
|
|
186
|
+
* @param ua User agent string
|
|
187
|
+
* @returns User agent data
|
|
188
|
+
*/
|
|
189
|
+
function parseUserAgent(ua?: string): UserAgentData | null;
|
|
147
190
|
/**
|
|
148
191
|
* Set HTML element focus by name
|
|
149
192
|
* @param name Element name or first collection item
|
package/lib/cjs/DomUtils.js
CHANGED
|
@@ -307,6 +307,18 @@ var DomUtils;
|
|
|
307
307
|
return dic;
|
|
308
308
|
}
|
|
309
309
|
DomUtils.formDataToObject = formDataToObject;
|
|
310
|
+
/**
|
|
311
|
+
* Is wechat client
|
|
312
|
+
* @param data User agent data
|
|
313
|
+
* @returns Result
|
|
314
|
+
*/
|
|
315
|
+
function isWechatClient(data) {
|
|
316
|
+
data ?? (data = parseUserAgent());
|
|
317
|
+
if (!data)
|
|
318
|
+
return false;
|
|
319
|
+
return data.brands.some((item) => item.brand.toLowerCase() === 'micromessenger');
|
|
320
|
+
}
|
|
321
|
+
DomUtils.isWechatClient = isWechatClient;
|
|
310
322
|
/**
|
|
311
323
|
* Culture match case Enum
|
|
312
324
|
*/
|
|
@@ -483,6 +495,93 @@ var DomUtils;
|
|
|
483
495
|
return base;
|
|
484
496
|
}
|
|
485
497
|
DomUtils.mergeURLSearchParams = mergeURLSearchParams;
|
|
498
|
+
/**
|
|
499
|
+
* Parse navigator's user agent string
|
|
500
|
+
* Lightweight User-Agent string parser
|
|
501
|
+
* https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/User-Agent
|
|
502
|
+
* @param ua User agent string
|
|
503
|
+
* @returns User agent data
|
|
504
|
+
*/
|
|
505
|
+
function parseUserAgent(ua) {
|
|
506
|
+
ua ?? (ua = globalThis.navigator.userAgent);
|
|
507
|
+
if (!ua) {
|
|
508
|
+
return null;
|
|
509
|
+
}
|
|
510
|
+
const parts = ua.split(/(?!\(.*)\s+(?!\()(?![^(]*?\))/g);
|
|
511
|
+
let mobile = false;
|
|
512
|
+
let platform = '';
|
|
513
|
+
let platformVersion;
|
|
514
|
+
let device = 'Desktop';
|
|
515
|
+
const brands = [];
|
|
516
|
+
// with the 'g' will causing failures for multiple calls
|
|
517
|
+
const platformVersionReg = /^[a-zA-Z0-9-\s]+\s+(0|\d+)(\.(0|\d+)){0,3}(\(|$)/;
|
|
518
|
+
const versionReg = /^[a-zA-Z0-9]+\/(0|\d+)(\.(0|\d+)){0,3}(\(|$)/;
|
|
519
|
+
parts.forEach((part) => {
|
|
520
|
+
const pl = part.toLowerCase();
|
|
521
|
+
if (pl.startsWith('mozilla/')) {
|
|
522
|
+
const data = /\((.*)\)$/.exec(part);
|
|
523
|
+
if (data && data.length > 1) {
|
|
524
|
+
const pfItems = data[1].split(/;\s*/);
|
|
525
|
+
// Platform + Version
|
|
526
|
+
const pfIndex = pfItems.findIndex((item) => platformVersionReg.test(item));
|
|
527
|
+
if (pfIndex !== -1) {
|
|
528
|
+
const pfParts = pfItems[pfIndex].split(/\s+/);
|
|
529
|
+
platformVersion = pfParts.pop();
|
|
530
|
+
platform = pfParts.join(' ');
|
|
531
|
+
}
|
|
532
|
+
else {
|
|
533
|
+
const appleVersionReg = /((iPhone|Mac)\s+OS(\s+\w+)?)\s+((0|\d+)(_(0|\d+)){0,3})/i;
|
|
534
|
+
for (let i = 0; i < pfItems.length; i++) {
|
|
535
|
+
const match = appleVersionReg.exec(pfItems[i]);
|
|
536
|
+
if (match && match.length > 4) {
|
|
537
|
+
platform = match[1];
|
|
538
|
+
platformVersion = match[4].replace(/_/g, '.');
|
|
539
|
+
pfItems.splice(i, 1);
|
|
540
|
+
break;
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
// Device
|
|
545
|
+
const deviceIndex = pfItems.findIndex((item) => item.includes(' Build/'));
|
|
546
|
+
if (deviceIndex === -1) {
|
|
547
|
+
const firstItem = pfItems[0];
|
|
548
|
+
if (firstItem.toLowerCase() !== 'linux' &&
|
|
549
|
+
!firstItem.startsWith(platform)) {
|
|
550
|
+
device = firstItem;
|
|
551
|
+
pfItems.shift();
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
else {
|
|
555
|
+
device = pfItems[deviceIndex].split(' Build/')[0];
|
|
556
|
+
pfItems.splice(deviceIndex, 1);
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
return;
|
|
560
|
+
}
|
|
561
|
+
if (pl === 'mobile' || pl.startsWith('mobile/')) {
|
|
562
|
+
mobile = true;
|
|
563
|
+
return;
|
|
564
|
+
}
|
|
565
|
+
if (pl === 'version' || pl.startsWith('version/')) {
|
|
566
|
+
// No process
|
|
567
|
+
return;
|
|
568
|
+
}
|
|
569
|
+
if (versionReg.test(part)) {
|
|
570
|
+
let [brand, version] = part.split('/');
|
|
571
|
+
const pindex = version.indexOf('(');
|
|
572
|
+
if (pindex > 0) {
|
|
573
|
+
version = version.substring(0, pindex);
|
|
574
|
+
}
|
|
575
|
+
brands.push({
|
|
576
|
+
brand,
|
|
577
|
+
version: Utils_1.Utils.trimEnd(version, '.0')
|
|
578
|
+
});
|
|
579
|
+
return;
|
|
580
|
+
}
|
|
581
|
+
});
|
|
582
|
+
return { mobile, platform, platformVersion, brands, device };
|
|
583
|
+
}
|
|
584
|
+
DomUtils.parseUserAgent = parseUserAgent;
|
|
486
585
|
/**
|
|
487
586
|
* Set HTML element focus by name
|
|
488
587
|
* @param name Element name or first collection item
|
package/lib/cjs/Utils.d.ts
CHANGED
|
@@ -267,8 +267,9 @@ export declare namespace Utils {
|
|
|
267
267
|
* @param data Data
|
|
268
268
|
* @param name Field name, support property chain like 'jsonData.logSize'
|
|
269
269
|
* @param value Value
|
|
270
|
+
* @param keepNull Keep null value or not
|
|
270
271
|
*/
|
|
271
|
-
function setNestedValue(data: object, name: string, value: unknown): void;
|
|
272
|
+
function setNestedValue(data: object, name: string, value: unknown, keepNull?: boolean): void;
|
|
272
273
|
/**
|
|
273
274
|
* Parse path similar with node.js path.parse
|
|
274
275
|
* @param path Input path
|
package/lib/cjs/Utils.js
CHANGED
|
@@ -541,18 +541,26 @@ var Utils;
|
|
|
541
541
|
* @param data Data
|
|
542
542
|
* @param name Field name, support property chain like 'jsonData.logSize'
|
|
543
543
|
* @param value Value
|
|
544
|
+
* @param keepNull Keep null value or not
|
|
544
545
|
*/
|
|
545
|
-
function setNestedValue(data, name, value) {
|
|
546
|
+
function setNestedValue(data, name, value, keepNull) {
|
|
546
547
|
const properties = name.split('.');
|
|
547
548
|
const len = properties.length;
|
|
548
|
-
if (len === 1)
|
|
549
|
-
|
|
549
|
+
if (len === 1) {
|
|
550
|
+
if (value == null && keepNull !== true) {
|
|
551
|
+
Reflect.deleteProperty(data, name);
|
|
552
|
+
}
|
|
553
|
+
else {
|
|
554
|
+
Reflect.set(data, name, value);
|
|
555
|
+
}
|
|
556
|
+
}
|
|
550
557
|
else {
|
|
551
558
|
let curr = data;
|
|
552
559
|
for (let i = 0; i < len; i++) {
|
|
553
560
|
const property = properties[i];
|
|
554
561
|
if (i + 1 === len) {
|
|
555
|
-
|
|
562
|
+
setNestedValue(curr, property, value, keepNull);
|
|
563
|
+
// Reflect.set(curr, property, value);
|
|
556
564
|
}
|
|
557
565
|
else {
|
|
558
566
|
let p = Reflect.get(curr, property);
|
|
@@ -647,10 +655,11 @@ var Utils;
|
|
|
647
655
|
* @returns Result
|
|
648
656
|
*/
|
|
649
657
|
Utils.trimEnd = (input, ...chars) => {
|
|
650
|
-
let
|
|
651
|
-
while (
|
|
652
|
-
|
|
653
|
-
|
|
658
|
+
let char;
|
|
659
|
+
while ((char = chars.find((char) => input.endsWith(char))) != null) {
|
|
660
|
+
input = input.substring(0, input.length - char.length);
|
|
661
|
+
}
|
|
662
|
+
return input;
|
|
654
663
|
};
|
|
655
664
|
/**
|
|
656
665
|
* Trim start chars
|
|
@@ -659,10 +668,10 @@ var Utils;
|
|
|
659
668
|
* @returns Result
|
|
660
669
|
*/
|
|
661
670
|
Utils.trimStart = (input, ...chars) => {
|
|
662
|
-
let
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
return input
|
|
671
|
+
let char;
|
|
672
|
+
while ((char = chars.find((char) => input.startsWith(char))) != null) {
|
|
673
|
+
input = input.substring(char.length);
|
|
674
|
+
}
|
|
675
|
+
return input;
|
|
667
676
|
};
|
|
668
677
|
})(Utils || (exports.Utils = Utils = {}));
|
package/lib/mjs/DomUtils.d.ts
CHANGED
|
@@ -2,6 +2,35 @@
|
|
|
2
2
|
import { DataTypes } from './DataTypes';
|
|
3
3
|
import { ErrorData, ErrorType } from './types/ErrorData';
|
|
4
4
|
import { FormDataFieldValue, IFormData } from './types/FormData';
|
|
5
|
+
/**
|
|
6
|
+
* User agent data, maybe replaced by navigator.userAgentData in future
|
|
7
|
+
* @see https://developer.mozilla.org/en-US/docs/Web/API/Navigator/userAgentData
|
|
8
|
+
*/
|
|
9
|
+
export type UserAgentData = {
|
|
10
|
+
/**
|
|
11
|
+
* Browser brands
|
|
12
|
+
*/
|
|
13
|
+
brands: {
|
|
14
|
+
brand: string;
|
|
15
|
+
version: string;
|
|
16
|
+
}[];
|
|
17
|
+
/**
|
|
18
|
+
* Is mobile device
|
|
19
|
+
*/
|
|
20
|
+
mobile: boolean;
|
|
21
|
+
/**
|
|
22
|
+
* Device brand (name)
|
|
23
|
+
*/
|
|
24
|
+
device: string;
|
|
25
|
+
/**
|
|
26
|
+
* Platform (OS)
|
|
27
|
+
*/
|
|
28
|
+
platform: string;
|
|
29
|
+
/**
|
|
30
|
+
* Platform version
|
|
31
|
+
*/
|
|
32
|
+
platformVersion?: string;
|
|
33
|
+
};
|
|
5
34
|
/**
|
|
6
35
|
* Dom Utilities
|
|
7
36
|
* Not all methods support Node
|
|
@@ -71,6 +100,12 @@ export declare namespace DomUtils {
|
|
|
71
100
|
* @returns Object
|
|
72
101
|
*/
|
|
73
102
|
function formDataToObject(form: IFormData): Record<string, FormDataFieldValue | FormDataFieldValue[]>;
|
|
103
|
+
/**
|
|
104
|
+
* Is wechat client
|
|
105
|
+
* @param data User agent data
|
|
106
|
+
* @returns Result
|
|
107
|
+
*/
|
|
108
|
+
function isWechatClient(data?: UserAgentData | null): boolean;
|
|
74
109
|
/**
|
|
75
110
|
* Culture match case Enum
|
|
76
111
|
*/
|
|
@@ -144,6 +179,14 @@ export declare namespace DomUtils {
|
|
|
144
179
|
* @param data New simple object data to merge
|
|
145
180
|
*/
|
|
146
181
|
function mergeURLSearchParams(base: URLSearchParams, data: DataTypes.SimpleObject): URLSearchParams;
|
|
182
|
+
/**
|
|
183
|
+
* Parse navigator's user agent string
|
|
184
|
+
* Lightweight User-Agent string parser
|
|
185
|
+
* https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/User-Agent
|
|
186
|
+
* @param ua User agent string
|
|
187
|
+
* @returns User agent data
|
|
188
|
+
*/
|
|
189
|
+
function parseUserAgent(ua?: string): UserAgentData | null;
|
|
147
190
|
/**
|
|
148
191
|
* Set HTML element focus by name
|
|
149
192
|
* @param name Element name or first collection item
|
package/lib/mjs/DomUtils.js
CHANGED
|
@@ -304,6 +304,18 @@ export var DomUtils;
|
|
|
304
304
|
return dic;
|
|
305
305
|
}
|
|
306
306
|
DomUtils.formDataToObject = formDataToObject;
|
|
307
|
+
/**
|
|
308
|
+
* Is wechat client
|
|
309
|
+
* @param data User agent data
|
|
310
|
+
* @returns Result
|
|
311
|
+
*/
|
|
312
|
+
function isWechatClient(data) {
|
|
313
|
+
data ?? (data = parseUserAgent());
|
|
314
|
+
if (!data)
|
|
315
|
+
return false;
|
|
316
|
+
return data.brands.some((item) => item.brand.toLowerCase() === 'micromessenger');
|
|
317
|
+
}
|
|
318
|
+
DomUtils.isWechatClient = isWechatClient;
|
|
307
319
|
/**
|
|
308
320
|
* Culture match case Enum
|
|
309
321
|
*/
|
|
@@ -480,6 +492,93 @@ export var DomUtils;
|
|
|
480
492
|
return base;
|
|
481
493
|
}
|
|
482
494
|
DomUtils.mergeURLSearchParams = mergeURLSearchParams;
|
|
495
|
+
/**
|
|
496
|
+
* Parse navigator's user agent string
|
|
497
|
+
* Lightweight User-Agent string parser
|
|
498
|
+
* https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/User-Agent
|
|
499
|
+
* @param ua User agent string
|
|
500
|
+
* @returns User agent data
|
|
501
|
+
*/
|
|
502
|
+
function parseUserAgent(ua) {
|
|
503
|
+
ua ?? (ua = globalThis.navigator.userAgent);
|
|
504
|
+
if (!ua) {
|
|
505
|
+
return null;
|
|
506
|
+
}
|
|
507
|
+
const parts = ua.split(/(?!\(.*)\s+(?!\()(?![^(]*?\))/g);
|
|
508
|
+
let mobile = false;
|
|
509
|
+
let platform = '';
|
|
510
|
+
let platformVersion;
|
|
511
|
+
let device = 'Desktop';
|
|
512
|
+
const brands = [];
|
|
513
|
+
// with the 'g' will causing failures for multiple calls
|
|
514
|
+
const platformVersionReg = /^[a-zA-Z0-9-\s]+\s+(0|\d+)(\.(0|\d+)){0,3}(\(|$)/;
|
|
515
|
+
const versionReg = /^[a-zA-Z0-9]+\/(0|\d+)(\.(0|\d+)){0,3}(\(|$)/;
|
|
516
|
+
parts.forEach((part) => {
|
|
517
|
+
const pl = part.toLowerCase();
|
|
518
|
+
if (pl.startsWith('mozilla/')) {
|
|
519
|
+
const data = /\((.*)\)$/.exec(part);
|
|
520
|
+
if (data && data.length > 1) {
|
|
521
|
+
const pfItems = data[1].split(/;\s*/);
|
|
522
|
+
// Platform + Version
|
|
523
|
+
const pfIndex = pfItems.findIndex((item) => platformVersionReg.test(item));
|
|
524
|
+
if (pfIndex !== -1) {
|
|
525
|
+
const pfParts = pfItems[pfIndex].split(/\s+/);
|
|
526
|
+
platformVersion = pfParts.pop();
|
|
527
|
+
platform = pfParts.join(' ');
|
|
528
|
+
}
|
|
529
|
+
else {
|
|
530
|
+
const appleVersionReg = /((iPhone|Mac)\s+OS(\s+\w+)?)\s+((0|\d+)(_(0|\d+)){0,3})/i;
|
|
531
|
+
for (let i = 0; i < pfItems.length; i++) {
|
|
532
|
+
const match = appleVersionReg.exec(pfItems[i]);
|
|
533
|
+
if (match && match.length > 4) {
|
|
534
|
+
platform = match[1];
|
|
535
|
+
platformVersion = match[4].replace(/_/g, '.');
|
|
536
|
+
pfItems.splice(i, 1);
|
|
537
|
+
break;
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
// Device
|
|
542
|
+
const deviceIndex = pfItems.findIndex((item) => item.includes(' Build/'));
|
|
543
|
+
if (deviceIndex === -1) {
|
|
544
|
+
const firstItem = pfItems[0];
|
|
545
|
+
if (firstItem.toLowerCase() !== 'linux' &&
|
|
546
|
+
!firstItem.startsWith(platform)) {
|
|
547
|
+
device = firstItem;
|
|
548
|
+
pfItems.shift();
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
else {
|
|
552
|
+
device = pfItems[deviceIndex].split(' Build/')[0];
|
|
553
|
+
pfItems.splice(deviceIndex, 1);
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
return;
|
|
557
|
+
}
|
|
558
|
+
if (pl === 'mobile' || pl.startsWith('mobile/')) {
|
|
559
|
+
mobile = true;
|
|
560
|
+
return;
|
|
561
|
+
}
|
|
562
|
+
if (pl === 'version' || pl.startsWith('version/')) {
|
|
563
|
+
// No process
|
|
564
|
+
return;
|
|
565
|
+
}
|
|
566
|
+
if (versionReg.test(part)) {
|
|
567
|
+
let [brand, version] = part.split('/');
|
|
568
|
+
const pindex = version.indexOf('(');
|
|
569
|
+
if (pindex > 0) {
|
|
570
|
+
version = version.substring(0, pindex);
|
|
571
|
+
}
|
|
572
|
+
brands.push({
|
|
573
|
+
brand,
|
|
574
|
+
version: Utils.trimEnd(version, '.0')
|
|
575
|
+
});
|
|
576
|
+
return;
|
|
577
|
+
}
|
|
578
|
+
});
|
|
579
|
+
return { mobile, platform, platformVersion, brands, device };
|
|
580
|
+
}
|
|
581
|
+
DomUtils.parseUserAgent = parseUserAgent;
|
|
483
582
|
/**
|
|
484
583
|
* Set HTML element focus by name
|
|
485
584
|
* @param name Element name or first collection item
|
package/lib/mjs/Utils.d.ts
CHANGED
|
@@ -267,8 +267,9 @@ export declare namespace Utils {
|
|
|
267
267
|
* @param data Data
|
|
268
268
|
* @param name Field name, support property chain like 'jsonData.logSize'
|
|
269
269
|
* @param value Value
|
|
270
|
+
* @param keepNull Keep null value or not
|
|
270
271
|
*/
|
|
271
|
-
function setNestedValue(data: object, name: string, value: unknown): void;
|
|
272
|
+
function setNestedValue(data: object, name: string, value: unknown, keepNull?: boolean): void;
|
|
272
273
|
/**
|
|
273
274
|
* Parse path similar with node.js path.parse
|
|
274
275
|
* @param path Input path
|
package/lib/mjs/Utils.js
CHANGED
|
@@ -535,18 +535,26 @@ export var Utils;
|
|
|
535
535
|
* @param data Data
|
|
536
536
|
* @param name Field name, support property chain like 'jsonData.logSize'
|
|
537
537
|
* @param value Value
|
|
538
|
+
* @param keepNull Keep null value or not
|
|
538
539
|
*/
|
|
539
|
-
function setNestedValue(data, name, value) {
|
|
540
|
+
function setNestedValue(data, name, value, keepNull) {
|
|
540
541
|
const properties = name.split('.');
|
|
541
542
|
const len = properties.length;
|
|
542
|
-
if (len === 1)
|
|
543
|
-
|
|
543
|
+
if (len === 1) {
|
|
544
|
+
if (value == null && keepNull !== true) {
|
|
545
|
+
Reflect.deleteProperty(data, name);
|
|
546
|
+
}
|
|
547
|
+
else {
|
|
548
|
+
Reflect.set(data, name, value);
|
|
549
|
+
}
|
|
550
|
+
}
|
|
544
551
|
else {
|
|
545
552
|
let curr = data;
|
|
546
553
|
for (let i = 0; i < len; i++) {
|
|
547
554
|
const property = properties[i];
|
|
548
555
|
if (i + 1 === len) {
|
|
549
|
-
|
|
556
|
+
setNestedValue(curr, property, value, keepNull);
|
|
557
|
+
// Reflect.set(curr, property, value);
|
|
550
558
|
}
|
|
551
559
|
else {
|
|
552
560
|
let p = Reflect.get(curr, property);
|
|
@@ -641,10 +649,11 @@ export var Utils;
|
|
|
641
649
|
* @returns Result
|
|
642
650
|
*/
|
|
643
651
|
Utils.trimEnd = (input, ...chars) => {
|
|
644
|
-
let
|
|
645
|
-
while (
|
|
646
|
-
|
|
647
|
-
|
|
652
|
+
let char;
|
|
653
|
+
while ((char = chars.find((char) => input.endsWith(char))) != null) {
|
|
654
|
+
input = input.substring(0, input.length - char.length);
|
|
655
|
+
}
|
|
656
|
+
return input;
|
|
648
657
|
};
|
|
649
658
|
/**
|
|
650
659
|
* Trim start chars
|
|
@@ -653,10 +662,10 @@ export var Utils;
|
|
|
653
662
|
* @returns Result
|
|
654
663
|
*/
|
|
655
664
|
Utils.trimStart = (input, ...chars) => {
|
|
656
|
-
let
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
return input
|
|
665
|
+
let char;
|
|
666
|
+
while ((char = chars.find((char) => input.startsWith(char))) != null) {
|
|
667
|
+
input = input.substring(char.length);
|
|
668
|
+
}
|
|
669
|
+
return input;
|
|
661
670
|
};
|
|
662
671
|
})(Utils || (Utils = {}));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@etsoo/shared",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.42",
|
|
4
4
|
"description": "TypeScript shared utilities and functions",
|
|
5
5
|
"main": "lib/cjs/index.js",
|
|
6
6
|
"module": "lib/mjs/index.js",
|
|
@@ -58,7 +58,7 @@
|
|
|
58
58
|
"@types/lodash.isequal": "^4.5.8",
|
|
59
59
|
"jest": "^29.7.0",
|
|
60
60
|
"jest-environment-jsdom": "^29.7.0",
|
|
61
|
-
"ts-jest": "^29.1.
|
|
61
|
+
"ts-jest": "^29.1.4",
|
|
62
62
|
"typescript": "^5.4.5"
|
|
63
63
|
},
|
|
64
64
|
"dependencies": {
|
package/src/DomUtils.ts
CHANGED
|
@@ -11,6 +11,40 @@ if (typeof navigator === 'undefined') {
|
|
|
11
11
|
globalThis.location = { href: 'http://localhost/' } as any;
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
+
/**
|
|
15
|
+
* User agent data, maybe replaced by navigator.userAgentData in future
|
|
16
|
+
* @see https://developer.mozilla.org/en-US/docs/Web/API/Navigator/userAgentData
|
|
17
|
+
*/
|
|
18
|
+
export type UserAgentData = {
|
|
19
|
+
/**
|
|
20
|
+
* Browser brands
|
|
21
|
+
*/
|
|
22
|
+
brands: {
|
|
23
|
+
brand: string;
|
|
24
|
+
version: string;
|
|
25
|
+
}[];
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Is mobile device
|
|
29
|
+
*/
|
|
30
|
+
mobile: boolean;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Device brand (name)
|
|
34
|
+
*/
|
|
35
|
+
device: string;
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Platform (OS)
|
|
39
|
+
*/
|
|
40
|
+
platform: string;
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Platform version
|
|
44
|
+
*/
|
|
45
|
+
platformVersion?: string;
|
|
46
|
+
};
|
|
47
|
+
|
|
14
48
|
/**
|
|
15
49
|
* Dom Utilities
|
|
16
50
|
* Not all methods support Node
|
|
@@ -367,6 +401,20 @@ export namespace DomUtils {
|
|
|
367
401
|
return dic;
|
|
368
402
|
}
|
|
369
403
|
|
|
404
|
+
/**
|
|
405
|
+
* Is wechat client
|
|
406
|
+
* @param data User agent data
|
|
407
|
+
* @returns Result
|
|
408
|
+
*/
|
|
409
|
+
export function isWechatClient(data?: UserAgentData | null) {
|
|
410
|
+
data ??= parseUserAgent();
|
|
411
|
+
if (!data) return false;
|
|
412
|
+
|
|
413
|
+
return data.brands.some(
|
|
414
|
+
(item) => item.brand.toLowerCase() === 'micromessenger'
|
|
415
|
+
);
|
|
416
|
+
}
|
|
417
|
+
|
|
370
418
|
/**
|
|
371
419
|
* Culture match case Enum
|
|
372
420
|
*/
|
|
@@ -583,6 +631,114 @@ export namespace DomUtils {
|
|
|
583
631
|
return base;
|
|
584
632
|
}
|
|
585
633
|
|
|
634
|
+
/**
|
|
635
|
+
* Parse navigator's user agent string
|
|
636
|
+
* Lightweight User-Agent string parser
|
|
637
|
+
* https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/User-Agent
|
|
638
|
+
* @param ua User agent string
|
|
639
|
+
* @returns User agent data
|
|
640
|
+
*/
|
|
641
|
+
export function parseUserAgent(ua?: string): UserAgentData | null {
|
|
642
|
+
ua ??= globalThis.navigator.userAgent;
|
|
643
|
+
|
|
644
|
+
if (!ua) {
|
|
645
|
+
return null;
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
const parts = ua.split(/(?!\(.*)\s+(?!\()(?![^(]*?\))/g);
|
|
649
|
+
|
|
650
|
+
let mobile = false;
|
|
651
|
+
let platform = '';
|
|
652
|
+
let platformVersion: string | undefined;
|
|
653
|
+
let device = 'Desktop';
|
|
654
|
+
const brands: UserAgentData['brands'] = [];
|
|
655
|
+
|
|
656
|
+
// with the 'g' will causing failures for multiple calls
|
|
657
|
+
const platformVersionReg =
|
|
658
|
+
/^[a-zA-Z0-9-\s]+\s+(0|\d+)(\.(0|\d+)){0,3}(\(|$)/;
|
|
659
|
+
const versionReg = /^[a-zA-Z0-9]+\/(0|\d+)(\.(0|\d+)){0,3}(\(|$)/;
|
|
660
|
+
|
|
661
|
+
parts.forEach((part) => {
|
|
662
|
+
const pl = part.toLowerCase();
|
|
663
|
+
|
|
664
|
+
if (pl.startsWith('mozilla/')) {
|
|
665
|
+
const data = /\((.*)\)$/.exec(part);
|
|
666
|
+
if (data && data.length > 1) {
|
|
667
|
+
const pfItems = data[1].split(/;\s*/);
|
|
668
|
+
|
|
669
|
+
// Platform + Version
|
|
670
|
+
const pfIndex = pfItems.findIndex((item) =>
|
|
671
|
+
platformVersionReg.test(item)
|
|
672
|
+
);
|
|
673
|
+
|
|
674
|
+
if (pfIndex !== -1) {
|
|
675
|
+
const pfParts = pfItems[pfIndex].split(/\s+/);
|
|
676
|
+
platformVersion = pfParts.pop();
|
|
677
|
+
platform = pfParts.join(' ');
|
|
678
|
+
} else {
|
|
679
|
+
const appleVersionReg =
|
|
680
|
+
/((iPhone|Mac)\s+OS(\s+\w+)?)\s+((0|\d+)(_(0|\d+)){0,3})/i;
|
|
681
|
+
|
|
682
|
+
for (let i = 0; i < pfItems.length; i++) {
|
|
683
|
+
const match = appleVersionReg.exec(pfItems[i]);
|
|
684
|
+
if (match && match.length > 4) {
|
|
685
|
+
platform = match[1];
|
|
686
|
+
platformVersion = match[4].replace(/_/g, '.');
|
|
687
|
+
|
|
688
|
+
pfItems.splice(i, 1);
|
|
689
|
+
break;
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
// Device
|
|
695
|
+
const deviceIndex = pfItems.findIndex((item) =>
|
|
696
|
+
item.includes(' Build/')
|
|
697
|
+
);
|
|
698
|
+
if (deviceIndex === -1) {
|
|
699
|
+
const firstItem = pfItems[0];
|
|
700
|
+
if (
|
|
701
|
+
firstItem.toLowerCase() !== 'linux' &&
|
|
702
|
+
!firstItem.startsWith(platform)
|
|
703
|
+
) {
|
|
704
|
+
device = firstItem;
|
|
705
|
+
pfItems.shift();
|
|
706
|
+
}
|
|
707
|
+
} else {
|
|
708
|
+
device = pfItems[deviceIndex].split(' Build/')[0];
|
|
709
|
+
pfItems.splice(deviceIndex, 1);
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
return;
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
if (pl === 'mobile' || pl.startsWith('mobile/')) {
|
|
716
|
+
mobile = true;
|
|
717
|
+
return;
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
if (pl === 'version' || pl.startsWith('version/')) {
|
|
721
|
+
// No process
|
|
722
|
+
return;
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
if (versionReg.test(part)) {
|
|
726
|
+
let [brand, version] = part.split('/');
|
|
727
|
+
const pindex = version.indexOf('(');
|
|
728
|
+
if (pindex > 0) {
|
|
729
|
+
version = version.substring(0, pindex);
|
|
730
|
+
}
|
|
731
|
+
brands.push({
|
|
732
|
+
brand,
|
|
733
|
+
version: Utils.trimEnd(version, '.0')
|
|
734
|
+
});
|
|
735
|
+
return;
|
|
736
|
+
}
|
|
737
|
+
});
|
|
738
|
+
|
|
739
|
+
return { mobile, platform, platformVersion, brands, device };
|
|
740
|
+
}
|
|
741
|
+
|
|
586
742
|
/**
|
|
587
743
|
* Set HTML element focus by name
|
|
588
744
|
* @param name Element name or first collection item
|
package/src/Utils.ts
CHANGED
|
@@ -735,18 +735,30 @@ export namespace Utils {
|
|
|
735
735
|
* @param data Data
|
|
736
736
|
* @param name Field name, support property chain like 'jsonData.logSize'
|
|
737
737
|
* @param value Value
|
|
738
|
+
* @param keepNull Keep null value or not
|
|
738
739
|
*/
|
|
739
|
-
export function setNestedValue(
|
|
740
|
+
export function setNestedValue(
|
|
741
|
+
data: object,
|
|
742
|
+
name: string,
|
|
743
|
+
value: unknown,
|
|
744
|
+
keepNull?: boolean
|
|
745
|
+
) {
|
|
740
746
|
const properties = name.split('.');
|
|
741
747
|
const len = properties.length;
|
|
742
|
-
if (len === 1)
|
|
743
|
-
|
|
748
|
+
if (len === 1) {
|
|
749
|
+
if (value == null && keepNull !== true) {
|
|
750
|
+
Reflect.deleteProperty(data, name);
|
|
751
|
+
} else {
|
|
752
|
+
Reflect.set(data, name, value);
|
|
753
|
+
}
|
|
754
|
+
} else {
|
|
744
755
|
let curr = data;
|
|
745
756
|
for (let i = 0; i < len; i++) {
|
|
746
757
|
const property = properties[i];
|
|
747
758
|
|
|
748
759
|
if (i + 1 === len) {
|
|
749
|
-
|
|
760
|
+
setNestedValue(curr, property, value, keepNull);
|
|
761
|
+
// Reflect.set(curr, property, value);
|
|
750
762
|
} else {
|
|
751
763
|
let p = Reflect.get(curr, property);
|
|
752
764
|
if (p == null) {
|
|
@@ -858,11 +870,11 @@ export namespace Utils {
|
|
|
858
870
|
* @returns Result
|
|
859
871
|
*/
|
|
860
872
|
export const trimEnd = (input: string, ...chars: string[]) => {
|
|
861
|
-
let
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
return input
|
|
873
|
+
let char: string | undefined;
|
|
874
|
+
while ((char = chars.find((char) => input.endsWith(char))) != null) {
|
|
875
|
+
input = input.substring(0, input.length - char.length);
|
|
876
|
+
}
|
|
877
|
+
return input;
|
|
866
878
|
};
|
|
867
879
|
|
|
868
880
|
/**
|
|
@@ -872,11 +884,10 @@ export namespace Utils {
|
|
|
872
884
|
* @returns Result
|
|
873
885
|
*/
|
|
874
886
|
export const trimStart = (input: string, ...chars: string[]) => {
|
|
875
|
-
let
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
return input.substring(start);
|
|
887
|
+
let char: string | undefined;
|
|
888
|
+
while ((char = chars.find((char) => input.startsWith(char))) != null) {
|
|
889
|
+
input = input.substring(char.length);
|
|
890
|
+
}
|
|
891
|
+
return input;
|
|
881
892
|
};
|
|
882
893
|
}
|