tiny-essentials 1.27.1 → 1.28.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/README.md +1 -0
- package/changelog/1/28/0.md +16 -0
- package/changelog/1/28/1.md +23 -0
- package/dist/v1/TinyAdvancedRaffle.min.js +1 -1
- package/dist/v1/TinyAfterScrollWatcher.min.js +1 -1
- package/dist/v1/TinyAnalogClock.min.js +1 -1
- package/dist/v1/TinyBasicsEs.min.js +1 -2
- package/dist/v1/TinyDayNightCycle.min.js +1 -1
- package/dist/v1/TinyDragDropDetector.min.js +1 -1
- package/dist/v1/TinyDragger.min.js +1 -1
- package/dist/v1/TinyElementObserver.min.js +1 -1
- package/dist/v1/TinyEssentials.min.js +1 -2
- package/dist/v1/TinyGamepad.min.js +1 -1
- package/dist/v1/TinyHtml.min.js +1 -1
- package/dist/v1/TinyI18.min.js +1 -1
- package/dist/v1/TinyIframeEvents.min.js +1 -1
- package/dist/v1/TinyLocalStorage.min.js +1 -1
- package/dist/v1/TinyMediaPlayer.min.js +1 -1
- package/dist/v1/TinyNewWinEvents.min.js +1 -1
- package/dist/v1/TinyNotifyCenter.min.js +1 -1
- package/dist/v1/TinyRadioFm.min.js +1 -1
- package/dist/v1/TinyRateLimiter.min.js +1 -1
- package/dist/v1/TinySmartScroller.min.js +1 -1
- package/dist/v1/TinyTextDiffer.min.js +1 -1
- package/dist/v1/TinyTextarea.min.js +1 -1
- package/dist/v1/TinyTimeout.min.js +1 -1
- package/dist/v1/TinyToastNotify.min.js +1 -1
- package/dist/v1/TinyUploadClicker.min.js +1 -2
- package/dist/v1/UltraRandomMsgGen.min.js +1 -1
- package/dist/v1/basics/array.cjs +57 -9
- package/dist/v1/basics/array.d.mts +27 -11
- package/dist/v1/basics/array.mjs +51 -9
- package/dist/v1/basics/clock.cjs +94 -42
- package/dist/v1/basics/clock.d.mts +10 -10
- package/dist/v1/basics/clock.mjs +91 -54
- package/dist/v1/basics/deprecated/html.cjs +246 -0
- package/dist/v1/basics/deprecated/html.d.mts +63 -0
- package/dist/v1/basics/deprecated/html.mjs +190 -0
- package/dist/v1/basics/extendObjType/Buffer.cjs +13 -0
- package/dist/v1/basics/extendObjType/Buffer.d.mts +4 -0
- package/dist/v1/basics/extendObjType/Buffer.mjs +8 -0
- package/dist/v1/basics/html.cjs +149 -130
- package/dist/v1/basics/html.d.mts +26 -61
- package/dist/v1/basics/html.mjs +113 -105
- package/dist/v1/basics/index.cjs +2 -0
- package/dist/v1/basics/index.d.mts +3 -1
- package/dist/v1/basics/index.mjs +3 -3
- package/dist/v1/basics/mediaContent.cjs +51 -9
- package/dist/v1/basics/mediaContent.d.mts +0 -87
- package/dist/v1/basics/mediaContent.mjs +40 -10
- package/dist/v1/basics/objChecker.cjs +7 -5
- package/dist/v1/basics/objChecker.d.mts +3 -2
- package/dist/v1/basics/objChecker.mjs +6 -6
- package/dist/v1/basics/objFilter.cjs +40 -18
- package/dist/v1/basics/objFilter.d.mts +6 -6
- package/dist/v1/basics/objFilter.mjs +32 -17
- package/dist/v1/basics/simpleMath.cjs +81 -25
- package/dist/v1/basics/simpleMath.d.mts +2 -2
- package/dist/v1/basics/simpleMath.mjs +70 -27
- package/dist/v1/basics/text.cjs +76 -31
- package/dist/v1/basics/text.d.mts +69 -7
- package/dist/v1/basics/text.mjs +62 -31
- package/dist/v1/index.cjs +4 -2
- package/dist/v1/index.d.mts +4 -2
- package/dist/v1/index.mjs +4 -4
- package/dist/v1/libs/TinyAdvancedRaffle.cjs +71 -255
- package/dist/v1/libs/TinyAdvancedRaffle.d.mts +6 -134
- package/dist/v1/libs/TinyAdvancedRaffle.mjs +69 -228
- package/dist/v1/libs/TinyAfterScrollWatcher.cjs +17 -1
- package/dist/v1/libs/TinyAfterScrollWatcher.d.mts +4 -1
- package/dist/v1/libs/TinyAfterScrollWatcher.mjs +15 -1
- package/dist/v1/libs/TinyAnalogClock.cjs +60 -0
- package/dist/v1/libs/TinyAnalogClock.d.mts +6 -0
- package/dist/v1/libs/TinyAnalogClock.mjs +57 -0
- package/dist/v1/libs/TinyClipboard.cjs +2 -0
- package/dist/v1/libs/TinyClipboard.d.mts +4 -2
- package/dist/v1/libs/TinyClipboard.mjs +2 -0
- package/dist/v1/libs/TinyColorConverter.cjs +1 -0
- package/dist/v1/libs/TinyColorConverter.d.mts +2 -1
- package/dist/v1/libs/TinyColorConverter.mjs +1 -0
- package/dist/v1/libs/TinyDayNightCycle.cjs +67 -42
- package/dist/v1/libs/TinyDayNightCycle.d.mts +2 -7
- package/dist/v1/libs/TinyDayNightCycle.mjs +65 -41
- package/dist/v1/libs/TinyDragDropDetector.cjs +25 -0
- package/dist/v1/libs/TinyDragDropDetector.d.mts +6 -0
- package/dist/v1/libs/TinyDragDropDetector.mjs +22 -0
- package/dist/v1/libs/TinyDragger.cjs +46 -19
- package/dist/v1/libs/TinyDragger.mjs +45 -18
- package/dist/v1/libs/TinyElementObserver.cjs +39 -0
- package/dist/v1/libs/TinyElementObserver.d.mts +6 -0
- package/dist/v1/libs/TinyElementObserver.mjs +36 -0
- package/dist/v1/libs/TinyGamepad.cjs +131 -1
- package/dist/v1/libs/TinyGamepad.mjs +129 -1
- package/dist/v1/libs/TinyHtml.cjs +1 -1
- package/dist/v1/libs/TinyHtml.mjs +2 -2
- package/dist/v1/libs/TinyI18.cjs +61 -0
- package/dist/v1/libs/TinyI18.d.mts +14 -0
- package/dist/v1/libs/TinyI18.mjs +55 -0
- package/dist/v1/libs/TinyIframeEvents.cjs +26 -196
- package/dist/v1/libs/TinyIframeEvents.d.mts +3 -122
- package/dist/v1/libs/TinyIframeEvents.mjs +26 -171
- package/dist/v1/libs/TinyLocalStorage.cjs +53 -205
- package/dist/v1/libs/TinyLocalStorage.d.mts +6 -127
- package/dist/v1/libs/TinyLocalStorage.mjs +50 -178
- package/dist/v1/libs/TinyMediaPlayer.cjs +74 -2
- package/dist/v1/libs/TinyMediaPlayer.d.mts +12 -2
- package/dist/v1/libs/TinyMediaPlayer.mjs +68 -2
- package/dist/v1/libs/TinyNewWinEvents.cjs +39 -197
- package/dist/v1/libs/TinyNewWinEvents.d.mts +6 -119
- package/dist/v1/libs/TinyNewWinEvents.mjs +36 -172
- package/dist/v1/libs/TinyNotifyCenter.cjs +36 -0
- package/dist/v1/libs/TinyNotifyCenter.d.mts +6 -0
- package/dist/v1/libs/TinyNotifyCenter.mjs +32 -0
- package/dist/v1/libs/TinyRadioFm.cjs +40 -3
- package/dist/v1/libs/TinyRadioFm.d.mts +3 -3
- package/dist/v1/libs/TinyRadioFm.mjs +37 -3
- package/dist/v1/libs/TinyRateLimiter.cjs +52 -0
- package/dist/v1/libs/TinyRateLimiter.d.mts +6 -0
- package/dist/v1/libs/TinyRateLimiter.mjs +48 -0
- package/dist/v1/libs/TinySmartScroller.cjs +56 -205
- package/dist/v1/libs/TinySmartScroller.d.mts +19 -152
- package/dist/v1/libs/TinySmartScroller.mjs +55 -181
- package/dist/v1/libs/TinyTextDiffer.cjs +19 -18
- package/dist/v1/libs/TinyTextDiffer.d.mts +1 -0
- package/dist/v1/libs/TinyTextDiffer.mjs +16 -17
- package/dist/v1/libs/TinyTextarea.cjs +19 -0
- package/dist/v1/libs/TinyTextarea.d.mts +6 -0
- package/dist/v1/libs/TinyTextarea.mjs +17 -0
- package/dist/v1/libs/TinyTimeout.cjs +11 -1
- package/dist/v1/libs/TinyTimeout.mjs +9 -1
- package/dist/v1/libs/TinyToastNotify.cjs +35 -0
- package/dist/v1/libs/TinyToastNotify.d.mts +6 -0
- package/dist/v1/libs/TinyToastNotify.mjs +31 -0
- package/dist/v1/libs/TinyUploadClicker.cjs +20 -0
- package/dist/v1/libs/TinyUploadClicker.d.mts +6 -0
- package/dist/v1/libs/TinyUploadClicker.mjs +17 -0
- package/dist/v1/libs/UltraRandomMsgGen.cjs +148 -35
- package/dist/v1/libs/UltraRandomMsgGen.d.mts +17 -3
- package/dist/v1/libs/UltraRandomMsgGen.mjs +133 -34
- package/dist/v1/libs/utils.cjs +21 -0
- package/dist/v1/libs/utils.d.mts +2 -0
- package/dist/v1/libs/utils.mjs +16 -0
- package/docs/v1/Exports.md +0 -1
- package/docs/v1/README.md +1 -1
- package/docs/v1/basics/array.md +2 -2
- package/docs/v1/basics/deprecated/html.md +17 -0
- package/docs/v1/basics/fullScreen.md +1 -1
- package/docs/v1/basics/html.md +12 -26
- package/docs/v1/basics/mediaContent.md +1 -1
- package/docs/v1/basics/objChecker.md +1 -1
- package/docs/v1/basics/objFilter.md +1 -1
- package/docs/v1/basics/text.md +2 -2
- package/docs/v1/fileManager/main.md +1 -1
- package/docs/v1/libs/ColorSafeStringify.md +1 -1
- package/docs/v1/libs/TinyAfterScrollWatcher.md +1 -1
- package/docs/v1/libs/TinyAnalogClock.md +1 -1
- package/docs/v1/libs/TinyArrayComparator.md +1 -1
- package/docs/v1/libs/TinyArrayPaginator.md +1 -1
- package/docs/v1/libs/TinyClassManager/TinyPluginInliner.md +2 -2
- package/docs/v1/libs/TinyClassManager.md +1 -1
- package/docs/v1/libs/TinyClipboard.md +1 -1
- package/docs/v1/libs/TinyCookieConsent.md +1 -1
- package/docs/v1/libs/TinyDomReadyManager.md +1 -1
- package/docs/v1/libs/TinyDragDropDetector.md +1 -1
- package/docs/v1/libs/TinyDragger.md +1 -1
- package/docs/v1/libs/TinyElementObserver.md +1 -1
- package/docs/v1/libs/TinyHtml.md +1 -1
- package/docs/v1/libs/TinyIframeEvents.md +10 -10
- package/docs/v1/libs/TinyLevelUp.md +1 -1
- package/docs/v1/libs/TinyLoadingScreen.md +1 -1
- package/docs/v1/libs/TinyLocalStorage.md +3 -3
- package/docs/v1/libs/TinyNewWinEvents.md +10 -11
- package/docs/v1/libs/TinyNotifications.md +1 -1
- package/docs/v1/libs/TinyPromiseQueue.md +2 -2
- package/docs/v1/libs/TinyTextDiffer.md +1 -1
- package/docs/v1/libs/TinyTextarea.md +1 -1
- package/package.json +15 -5
- package/dist/v1/TinyBasicsEs.min.js.LICENSE.txt +0 -8
- package/dist/v1/TinyEssentials.min.js.LICENSE.txt +0 -8
- package/dist/v1/TinyEvents.min.js +0 -1
- package/dist/v1/TinyUploadClicker.min.js.LICENSE.txt +0 -8
- package/dist/v1/build/TinyEvents.cjs +0 -7
- package/dist/v1/build/TinyEvents.d.mts +0 -3
- package/dist/v1/build/TinyEvents.mjs +0 -2
- package/dist/v1/libs/TinyEvents.cjs +0 -395
- package/dist/v1/libs/TinyEvents.d.mts +0 -164
- package/dist/v1/libs/TinyEvents.mjs +0 -360
- package/docs/v1/libs/TinyEvents.md +0 -187
|
@@ -104,6 +104,22 @@
|
|
|
104
104
|
|
|
105
105
|
//////////////////////////////////////////////////////////////////
|
|
106
106
|
|
|
107
|
+
const checkString = (/** @type {string} */ val, /** @type {string} */ name) => {
|
|
108
|
+
if (typeof val !== 'string')
|
|
109
|
+
throw new TypeError(`Expected "${name}" to be a string, but received ${typeof val}.`);
|
|
110
|
+
};
|
|
111
|
+
const checkFunction = (/** @type {Function} */ val, /** @type {string} */ name) => {
|
|
112
|
+
if (typeof val !== 'function')
|
|
113
|
+
throw new TypeError(`Expected "${name}" to be a function, but received ${typeof val}.`);
|
|
114
|
+
};
|
|
115
|
+
const checkObject = (/** @type {Object<string, any> | null} */ val, /** @type {string} */ name) => {
|
|
116
|
+
if (typeof val !== 'object' || val === null || Array.isArray(val)) {
|
|
117
|
+
throw new TypeError(
|
|
118
|
+
`Expected "${name}" to be a plain object, but received ${val === null ? 'null' : Array.isArray(val) ? 'array' : typeof val}.`,
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
|
|
107
123
|
/** @type {Map<string, number>} */
|
|
108
124
|
const _blobCounter = new Map();
|
|
109
125
|
|
|
@@ -145,6 +161,11 @@ class MediaLoadingError extends Error {
|
|
|
145
161
|
* @param {LoadingErrorStage} stage - The stage where the error occurred.
|
|
146
162
|
*/
|
|
147
163
|
constructor(message, url, stage) {
|
|
164
|
+
checkString(message, 'message');
|
|
165
|
+
checkString(url, 'url');
|
|
166
|
+
if (typeof stage !== 'string')
|
|
167
|
+
throw new TypeError(`Expected "stage" to be a string, but received ${typeof stage}.`);
|
|
168
|
+
|
|
148
169
|
super(message);
|
|
149
170
|
this.name = 'MediaLoadingError';
|
|
150
171
|
this.url = url;
|
|
@@ -159,6 +180,8 @@ class MediaLoadingError extends Error {
|
|
|
159
180
|
* @private
|
|
160
181
|
*/
|
|
161
182
|
const generateSimpleHash = async (str) => {
|
|
183
|
+
checkString(str, 'str');
|
|
184
|
+
|
|
162
185
|
const msgUint8 = new TextEncoder().encode(str);
|
|
163
186
|
const hashBuffer = await crypto.subtle.digest('SHA-1', msgUint8);
|
|
164
187
|
const hashArray = Array.from(new Uint8Array(hashBuffer));
|
|
@@ -174,9 +197,15 @@ const generateSimpleHash = async (str) => {
|
|
|
174
197
|
* @param {Uint8Array|string} data
|
|
175
198
|
* @param {string} format
|
|
176
199
|
* @returns {string} The generated Blob URL or original string if already valid.
|
|
177
|
-
* @private
|
|
178
200
|
*/
|
|
179
201
|
const convertToBlobUrl = (data, format = 'image/jpeg') => {
|
|
202
|
+
if (!(data instanceof Uint8Array) && typeof data !== 'string') {
|
|
203
|
+
throw new TypeError(
|
|
204
|
+
`Expected "data" to be an instance of Uint8Array or a string, but received ${typeof data}.`,
|
|
205
|
+
);
|
|
206
|
+
}
|
|
207
|
+
checkString(format, 'format');
|
|
208
|
+
|
|
180
209
|
const createBlobCounter = (/** @type {Blob} */ blob) => {
|
|
181
210
|
const url = URL.createObjectURL(blob);
|
|
182
211
|
const blobUrlUsage = _blobCounter.get(url);
|
|
@@ -205,10 +234,11 @@ const convertToBlobUrl = (data, format = 'image/jpeg') => {
|
|
|
205
234
|
* Asynchronous helper to convert a Blob URL back to Base64 (Date URL) at export time.
|
|
206
235
|
* @param {string} url
|
|
207
236
|
* @returns {Promise<string>}
|
|
208
|
-
* @private
|
|
209
237
|
*/
|
|
210
238
|
const blobUrlToBase64 = async (url) => {
|
|
211
|
-
|
|
239
|
+
checkString(url, 'url');
|
|
240
|
+
|
|
241
|
+
if (!url.startsWith('blob:')) return url;
|
|
212
242
|
try {
|
|
213
243
|
const response = await fetch(url);
|
|
214
244
|
const blob = await response.blob();
|
|
@@ -227,9 +257,10 @@ const blobUrlToBase64 = async (url) => {
|
|
|
227
257
|
/**
|
|
228
258
|
* Safely revokes Blob URLs to prevent memory leaks from createObjectURL.
|
|
229
259
|
* @param {MediaContent} content
|
|
230
|
-
* @private
|
|
231
260
|
*/
|
|
232
261
|
const revokeContentUrls = (content) => {
|
|
262
|
+
checkObject(content, 'content');
|
|
263
|
+
|
|
233
264
|
if (content && Array.isArray(content.picture)) {
|
|
234
265
|
content.picture.forEach((pic) => {
|
|
235
266
|
if (typeof pic.data === 'string' && pic.data.startsWith('blob:')) {
|
|
@@ -248,7 +279,9 @@ const revokeContentUrls = (content) => {
|
|
|
248
279
|
* @param {boolean} isPartial - If true, properties can be undefined.
|
|
249
280
|
*/
|
|
250
281
|
const validateMediaContent = (common, isPartial) => {
|
|
251
|
-
|
|
282
|
+
checkObject(common, 'common');
|
|
283
|
+
if (typeof isPartial !== 'boolean') throw new TypeError('Expected "isPartial" to be a boolean.');
|
|
284
|
+
|
|
252
285
|
const isUndefinedAllowed = (/** @type {any} */ v) => isPartial && typeof v === 'undefined';
|
|
253
286
|
|
|
254
287
|
const isString = (/** @type {string | null | undefined} */ v) =>
|
|
@@ -293,6 +326,8 @@ const validateMediaContent = (common, isPartial) => {
|
|
|
293
326
|
!isArray(
|
|
294
327
|
common.picture,
|
|
295
328
|
(value) =>
|
|
329
|
+
value &&
|
|
330
|
+
typeof value === 'object' &&
|
|
296
331
|
(typeof value.description === 'undefined' || typeof value.description === 'string') &&
|
|
297
332
|
(typeof value.name === 'undefined' || typeof value.name === 'string') &&
|
|
298
333
|
(typeof value.type === 'undefined' || typeof value.type === 'string') &&
|
|
@@ -351,10 +386,8 @@ const valMediaContentMetadataPartial = (common) => validateMediaContent(common,
|
|
|
351
386
|
*/
|
|
352
387
|
const extractMediaId3Tags = async (url, parseFile) => {
|
|
353
388
|
// Argument Validation
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
if (typeof parseFile !== 'function')
|
|
357
|
-
throw new TypeError(`Expected parseFile to be a function, but received ${typeof parseFile}.`);
|
|
389
|
+
checkString(url, 'url');
|
|
390
|
+
checkFunction(parseFile, 'parseFile');
|
|
358
391
|
|
|
359
392
|
try {
|
|
360
393
|
// 1. Download the file from the provided URL
|
|
@@ -459,11 +492,20 @@ const parseMediaMetadata = async (
|
|
|
459
492
|
if (typeof source !== 'string' && !(source instanceof HTMLMediaElement))
|
|
460
493
|
throw new TypeError('Source must be a string or an HTMLMediaElement.');
|
|
461
494
|
|
|
495
|
+
checkObject(defaultMetadata, 'defaultMetadata');
|
|
496
|
+
checkObject(metadata, 'metadata');
|
|
497
|
+
checkFunction(parseFile, 'parseFile');
|
|
498
|
+
checkObject(callbacks, 'callbacks');
|
|
499
|
+
|
|
462
500
|
if (callbacks.onProgress && typeof callbacks.onProgress !== 'function')
|
|
463
501
|
throw new TypeError('callbacks.onProgress must be a function.');
|
|
464
502
|
if (callbacks.onError && typeof callbacks.onError !== 'function')
|
|
465
503
|
throw new TypeError('callbacks.onError must be a function.');
|
|
466
504
|
|
|
505
|
+
if (typeof unknownArtist !== 'string' && typeof unknownArtist !== 'function') {
|
|
506
|
+
throw new TypeError('unknownArtist must be a string or a function returning a string.');
|
|
507
|
+
}
|
|
508
|
+
|
|
467
509
|
/** @type {HTMLMediaElement} */
|
|
468
510
|
let audio;
|
|
469
511
|
/** @type {string} */
|
|
@@ -1,90 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @typedef {string|(() => string)} UnknownArtistGetter
|
|
3
|
-
*/
|
|
4
|
-
/**
|
|
5
|
-
* Represents an image attachment template.
|
|
6
|
-
* @template {Uint8Array|string} PictureData
|
|
7
|
-
* @typedef {Object} IPictureTemplate
|
|
8
|
-
* @property {string} format - The MIME type of the image (e.g., 'image/jpeg').
|
|
9
|
-
* @property {PictureData} data - The raw binary data of the image.
|
|
10
|
-
* @property {string} [description] - An optional textual description of the image.
|
|
11
|
-
* @property {string} [type] - The specific type of picture (e.g., 'cover', 'front', 'back').
|
|
12
|
-
* @property {string} [name] - The filename associated with the image.
|
|
13
|
-
*/
|
|
14
|
-
/**
|
|
15
|
-
* Represents an image attachment, such as album art.
|
|
16
|
-
* @typedef {IPictureTemplate<string>} IPicture
|
|
17
|
-
*/
|
|
18
|
-
/**
|
|
19
|
-
* A numeric structure representing track or disk indexing.
|
|
20
|
-
* @typedef {{no: number|null, of: number|null}} MediaNumber
|
|
21
|
-
*/
|
|
22
|
-
/**
|
|
23
|
-
* This metadata structure is modeled template.
|
|
24
|
-
*
|
|
25
|
-
* @template {IPictureTemplate<Uint8Array|string>} IPictureContent
|
|
26
|
-
* @typedef {Object} ContentMetadataTemplate
|
|
27
|
-
* @property {string|null} title - The title of the track.
|
|
28
|
-
* @property {string|null} album - The name of the album.
|
|
29
|
-
* @property {string|null} albumartist - The primary artist of the album.
|
|
30
|
-
* @property {string[]} albumartists - An array of artists associated with the album.
|
|
31
|
-
* @property {string[]} genre - An array of genres associated with the track.
|
|
32
|
-
* @property {string[]} label - The record label.
|
|
33
|
-
* @property {string[]} composer - The composer of the track.
|
|
34
|
-
* @property {number|null} year - The release year.
|
|
35
|
-
* @property {string|null} artist - The primary artist of the track.
|
|
36
|
-
* @property {string[]} artists - An array of artists associated with the track.
|
|
37
|
-
* @property {MediaNumber} disk - Disk information containing the current disk number and total disks.
|
|
38
|
-
* @property {MediaNumber} track - Track information containing the current track number and total tracks.
|
|
39
|
-
* @property {IPictureContent[]} [picture] - An array of picture objects containing album art.
|
|
40
|
-
*/
|
|
41
|
-
/**
|
|
42
|
-
* This metadata structure is modeled after the standard output of the
|
|
43
|
-
* `music-metadata@11.13.0` npm package.
|
|
44
|
-
*
|
|
45
|
-
* @typedef {ContentMetadataTemplate<IPicture>} MediaContentMetadata
|
|
46
|
-
*/
|
|
47
|
-
/**
|
|
48
|
-
* The core properties required for any content item within the media system.
|
|
49
|
-
* @typedef {Object} MediaContentBase
|
|
50
|
-
* @property {string} id - Unique identifier.
|
|
51
|
-
* @property {string} title - Name of the track/message.
|
|
52
|
-
* @property {string} artist - Artist or speaker name.
|
|
53
|
-
* @property {number} duration - Duration in milliseconds.
|
|
54
|
-
* @property {string} url - Source URL/Path.
|
|
55
|
-
* @property {number} [weight=1] - Probability multiplier for random selection mode.
|
|
56
|
-
*/
|
|
57
|
-
/**
|
|
58
|
-
* The final content object used within the media, combining
|
|
59
|
-
* core playback properties with rich metadata.
|
|
60
|
-
*
|
|
61
|
-
* @typedef {MediaContentBase & MediaContentMetadata} MediaContent
|
|
62
|
-
*/
|
|
63
|
-
/**
|
|
64
|
-
* A promise that resolves to an object containing the extracted metadata.
|
|
65
|
-
* @callback ParseMediaContentMetadata
|
|
66
|
-
* @param {Blob} data - The raw file blob.
|
|
67
|
-
* @returns {Promise<{ common: Partial<ContentMetadataTemplate<IPictureTemplate<Uint8Array|string>>> }>} A promise resolving to the common metadata properties.
|
|
68
|
-
*/
|
|
69
|
-
/**
|
|
70
|
-
* @typedef {Object} LoadingMediaProgress
|
|
71
|
-
* @property {'loading'|'success'} status - The current status of the operation.
|
|
72
|
-
* @property {LoadingProgressStage} stage - The current execution stage.
|
|
73
|
-
* @property {ProgressEvent<EventTarget>} [event] - The current loading event.
|
|
74
|
-
* @property {string} url - The URL being processed.
|
|
75
|
-
*/
|
|
76
|
-
/**
|
|
77
|
-
* @typedef {'INITIALIZING'|'DOWNLOADING'|'METADATA_LOADED'|'EXTRACTING_ID3'|'COMPLETE'} LoadingProgressStage
|
|
78
|
-
*/
|
|
79
|
-
/**
|
|
80
|
-
* @typedef {'INITIALIZING'|'DOWNLOADING'|'METADATA'|'ID3'|'UNKNOWN'} LoadingErrorStage
|
|
81
|
-
*/
|
|
82
|
-
/**
|
|
83
|
-
* @typedef {Object} MediaLoadingErrorData
|
|
84
|
-
* @property {Error} error - The original error object.
|
|
85
|
-
* @property {string} url - The URL that failed.
|
|
86
|
-
* @property {LoadingErrorStage} stage - The stage where it failed.
|
|
87
|
-
*/
|
|
88
1
|
/** @type {Map<string, number>} */
|
|
89
2
|
export const _blobCounter: Map<string, number>;
|
|
90
3
|
export function getMediaContentBase(): MediaContentBase;
|
|
@@ -87,6 +87,19 @@
|
|
|
87
87
|
* @property {LoadingErrorStage} stage - The stage where it failed.
|
|
88
88
|
*/
|
|
89
89
|
//////////////////////////////////////////////////////////////////
|
|
90
|
+
const checkString = (/** @type {string} */ val, /** @type {string} */ name) => {
|
|
91
|
+
if (typeof val !== 'string')
|
|
92
|
+
throw new TypeError(`Expected "${name}" to be a string, but received ${typeof val}.`);
|
|
93
|
+
};
|
|
94
|
+
const checkFunction = (/** @type {Function} */ val, /** @type {string} */ name) => {
|
|
95
|
+
if (typeof val !== 'function')
|
|
96
|
+
throw new TypeError(`Expected "${name}" to be a function, but received ${typeof val}.`);
|
|
97
|
+
};
|
|
98
|
+
const checkObject = (/** @type {Object<string, any> | null} */ val, /** @type {string} */ name) => {
|
|
99
|
+
if (typeof val !== 'object' || val === null || Array.isArray(val)) {
|
|
100
|
+
throw new TypeError(`Expected "${name}" to be a plain object, but received ${val === null ? 'null' : Array.isArray(val) ? 'array' : typeof val}.`);
|
|
101
|
+
}
|
|
102
|
+
};
|
|
90
103
|
/** @type {Map<string, number>} */
|
|
91
104
|
export const _blobCounter = new Map();
|
|
92
105
|
/** @returns {MediaContentBase} */
|
|
@@ -125,6 +138,10 @@ export class MediaLoadingError extends Error {
|
|
|
125
138
|
* @param {LoadingErrorStage} stage - The stage where the error occurred.
|
|
126
139
|
*/
|
|
127
140
|
constructor(message, url, stage) {
|
|
141
|
+
checkString(message, 'message');
|
|
142
|
+
checkString(url, 'url');
|
|
143
|
+
if (typeof stage !== 'string')
|
|
144
|
+
throw new TypeError(`Expected "stage" to be a string, but received ${typeof stage}.`);
|
|
128
145
|
super(message);
|
|
129
146
|
this.name = 'MediaLoadingError';
|
|
130
147
|
this.url = url;
|
|
@@ -138,6 +155,7 @@ export class MediaLoadingError extends Error {
|
|
|
138
155
|
* @private
|
|
139
156
|
*/
|
|
140
157
|
export const generateSimpleHash = async (str) => {
|
|
158
|
+
checkString(str, 'str');
|
|
141
159
|
const msgUint8 = new TextEncoder().encode(str);
|
|
142
160
|
const hashBuffer = await crypto.subtle.digest('SHA-1', msgUint8);
|
|
143
161
|
const hashArray = Array.from(new Uint8Array(hashBuffer));
|
|
@@ -152,9 +170,12 @@ export const generateSimpleHash = async (str) => {
|
|
|
152
170
|
* @param {Uint8Array|string} data
|
|
153
171
|
* @param {string} format
|
|
154
172
|
* @returns {string} The generated Blob URL or original string if already valid.
|
|
155
|
-
* @private
|
|
156
173
|
*/
|
|
157
174
|
export const convertToBlobUrl = (data, format = 'image/jpeg') => {
|
|
175
|
+
if (!(data instanceof Uint8Array) && typeof data !== 'string') {
|
|
176
|
+
throw new TypeError(`Expected "data" to be an instance of Uint8Array or a string, but received ${typeof data}.`);
|
|
177
|
+
}
|
|
178
|
+
checkString(format, 'format');
|
|
158
179
|
const createBlobCounter = (/** @type {Blob} */ blob) => {
|
|
159
180
|
const url = URL.createObjectURL(blob);
|
|
160
181
|
const blobUrlUsage = _blobCounter.get(url);
|
|
@@ -182,10 +203,10 @@ export const convertToBlobUrl = (data, format = 'image/jpeg') => {
|
|
|
182
203
|
* Asynchronous helper to convert a Blob URL back to Base64 (Date URL) at export time.
|
|
183
204
|
* @param {string} url
|
|
184
205
|
* @returns {Promise<string>}
|
|
185
|
-
* @private
|
|
186
206
|
*/
|
|
187
207
|
export const blobUrlToBase64 = async (url) => {
|
|
188
|
-
|
|
208
|
+
checkString(url, 'url');
|
|
209
|
+
if (!url.startsWith('blob:'))
|
|
189
210
|
return url;
|
|
190
211
|
try {
|
|
191
212
|
const response = await fetch(url);
|
|
@@ -205,9 +226,9 @@ export const blobUrlToBase64 = async (url) => {
|
|
|
205
226
|
/**
|
|
206
227
|
* Safely revokes Blob URLs to prevent memory leaks from createObjectURL.
|
|
207
228
|
* @param {MediaContent} content
|
|
208
|
-
* @private
|
|
209
229
|
*/
|
|
210
230
|
export const revokeContentUrls = (content) => {
|
|
231
|
+
checkObject(content, 'content');
|
|
211
232
|
if (content && Array.isArray(content.picture)) {
|
|
212
233
|
content.picture.forEach((pic) => {
|
|
213
234
|
if (typeof pic.data === 'string' && pic.data.startsWith('blob:')) {
|
|
@@ -228,7 +249,9 @@ export const revokeContentUrls = (content) => {
|
|
|
228
249
|
* @param {boolean} isPartial - If true, properties can be undefined.
|
|
229
250
|
*/
|
|
230
251
|
const validateMediaContent = (common, isPartial) => {
|
|
231
|
-
|
|
252
|
+
checkObject(common, 'common');
|
|
253
|
+
if (typeof isPartial !== 'boolean')
|
|
254
|
+
throw new TypeError('Expected "isPartial" to be a boolean.');
|
|
232
255
|
const isUndefinedAllowed = (/** @type {any} */ v) => isPartial && typeof v === 'undefined';
|
|
233
256
|
const isString = (/** @type {string | null | undefined} */ v) => isUndefinedAllowed(v) || typeof v === 'string' || v === null;
|
|
234
257
|
const isNumber = (/** @type {number | null | undefined}} */ v) => isUndefinedAllowed(v) || typeof v === 'number' || v === null;
|
|
@@ -263,7 +286,9 @@ const validateMediaContent = (common, isPartial) => {
|
|
|
263
286
|
throw new TypeError('Invalid metadata: "composer" must be an array of string.');
|
|
264
287
|
if (!isArray(common.artists, (value) => typeof value === 'string'))
|
|
265
288
|
throw new TypeError('Invalid metadata: "artists" must be an array of string.');
|
|
266
|
-
if (!isArray(common.picture, (value) =>
|
|
289
|
+
if (!isArray(common.picture, (value) => value &&
|
|
290
|
+
typeof value === 'object' &&
|
|
291
|
+
(typeof value.description === 'undefined' || typeof value.description === 'string') &&
|
|
267
292
|
(typeof value.name === 'undefined' || typeof value.name === 'string') &&
|
|
268
293
|
(typeof value.type === 'undefined' || typeof value.type === 'string') &&
|
|
269
294
|
typeof value.format === 'string' &&
|
|
@@ -313,10 +338,8 @@ export const valMediaContentMetadataPartial = (common) => validateMediaContent(c
|
|
|
313
338
|
*/
|
|
314
339
|
export const extractMediaId3Tags = async (url, parseFile) => {
|
|
315
340
|
// Argument Validation
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
if (typeof parseFile !== 'function')
|
|
319
|
-
throw new TypeError(`Expected parseFile to be a function, but received ${typeof parseFile}.`);
|
|
341
|
+
checkString(url, 'url');
|
|
342
|
+
checkFunction(parseFile, 'parseFile');
|
|
320
343
|
try {
|
|
321
344
|
// 1. Download the file from the provided URL
|
|
322
345
|
const response = await fetch(url);
|
|
@@ -405,10 +428,17 @@ export const parseMediaMetadata = async (source, defaultMetadata = {}, metadata
|
|
|
405
428
|
// Argument Validation
|
|
406
429
|
if (typeof source !== 'string' && !(source instanceof HTMLMediaElement))
|
|
407
430
|
throw new TypeError('Source must be a string or an HTMLMediaElement.');
|
|
431
|
+
checkObject(defaultMetadata, 'defaultMetadata');
|
|
432
|
+
checkObject(metadata, 'metadata');
|
|
433
|
+
checkFunction(parseFile, 'parseFile');
|
|
434
|
+
checkObject(callbacks, 'callbacks');
|
|
408
435
|
if (callbacks.onProgress && typeof callbacks.onProgress !== 'function')
|
|
409
436
|
throw new TypeError('callbacks.onProgress must be a function.');
|
|
410
437
|
if (callbacks.onError && typeof callbacks.onError !== 'function')
|
|
411
438
|
throw new TypeError('callbacks.onError must be a function.');
|
|
439
|
+
if (typeof unknownArtist !== 'string' && typeof unknownArtist !== 'function') {
|
|
440
|
+
throw new TypeError('unknownArtist must be a string or a function returning a string.');
|
|
441
|
+
}
|
|
412
442
|
/** @type {HTMLMediaElement} */
|
|
413
443
|
let audio;
|
|
414
444
|
/** @type {string} */
|
|
@@ -4,20 +4,22 @@
|
|
|
4
4
|
* Counts the number of elements in an array or the number of properties in an object.
|
|
5
5
|
*
|
|
6
6
|
* @param {Array<*>|Record<string | number | symbol, any>} obj - The array or object to count.
|
|
7
|
-
* @returns {number} - The count of items (array elements or object keys)
|
|
7
|
+
* @returns {number} - The count of items (array elements or object keys).
|
|
8
|
+
* @throws {TypeError} If the input is neither an array nor a pure object.
|
|
8
9
|
*
|
|
9
10
|
* @example
|
|
10
11
|
* countObj([1, 2, 3]); // 3
|
|
11
12
|
* countObj({ a: 1, b: 2 }); // 2
|
|
12
|
-
* countObj('not an object'); //
|
|
13
|
+
* countObj('not an object'); // Throws TypeError
|
|
13
14
|
*/
|
|
14
15
|
function countObj(obj) {
|
|
16
|
+
if (!Array.isArray(obj) && !isJsonObject(obj))
|
|
17
|
+
throw new TypeError('Argument must be either an Array or a pure JSON Object.');
|
|
18
|
+
|
|
15
19
|
// Is Array
|
|
16
20
|
if (Array.isArray(obj)) return obj.length;
|
|
17
21
|
// Object
|
|
18
|
-
|
|
19
|
-
// Nothing
|
|
20
|
-
return 0;
|
|
22
|
+
return Object.keys(obj).length;
|
|
21
23
|
}
|
|
22
24
|
|
|
23
25
|
/**
|
|
@@ -2,12 +2,13 @@
|
|
|
2
2
|
* Counts the number of elements in an array or the number of properties in an object.
|
|
3
3
|
*
|
|
4
4
|
* @param {Array<*>|Record<string | number | symbol, any>} obj - The array or object to count.
|
|
5
|
-
* @returns {number} - The count of items (array elements or object keys)
|
|
5
|
+
* @returns {number} - The count of items (array elements or object keys).
|
|
6
|
+
* @throws {TypeError} If the input is neither an array nor a pure object.
|
|
6
7
|
*
|
|
7
8
|
* @example
|
|
8
9
|
* countObj([1, 2, 3]); // 3
|
|
9
10
|
* countObj({ a: 1, b: 2 }); // 2
|
|
10
|
-
* countObj('not an object'); //
|
|
11
|
+
* countObj('not an object'); // Throws TypeError
|
|
11
12
|
*/
|
|
12
13
|
export function countObj(obj: Array<any> | Record<string | number | symbol, any>): number;
|
|
13
14
|
/**
|
|
@@ -2,22 +2,22 @@
|
|
|
2
2
|
* Counts the number of elements in an array or the number of properties in an object.
|
|
3
3
|
*
|
|
4
4
|
* @param {Array<*>|Record<string | number | symbol, any>} obj - The array or object to count.
|
|
5
|
-
* @returns {number} - The count of items (array elements or object keys)
|
|
5
|
+
* @returns {number} - The count of items (array elements or object keys).
|
|
6
|
+
* @throws {TypeError} If the input is neither an array nor a pure object.
|
|
6
7
|
*
|
|
7
8
|
* @example
|
|
8
9
|
* countObj([1, 2, 3]); // 3
|
|
9
10
|
* countObj({ a: 1, b: 2 }); // 2
|
|
10
|
-
* countObj('not an object'); //
|
|
11
|
+
* countObj('not an object'); // Throws TypeError
|
|
11
12
|
*/
|
|
12
13
|
export function countObj(obj) {
|
|
14
|
+
if (!Array.isArray(obj) && !isJsonObject(obj))
|
|
15
|
+
throw new TypeError('Argument must be either an Array or a pure JSON Object.');
|
|
13
16
|
// Is Array
|
|
14
17
|
if (Array.isArray(obj))
|
|
15
18
|
return obj.length;
|
|
16
19
|
// Object
|
|
17
|
-
|
|
18
|
-
return Object.keys(obj).length;
|
|
19
|
-
// Nothing
|
|
20
|
-
return 0;
|
|
20
|
+
return Object.keys(obj).length;
|
|
21
21
|
}
|
|
22
22
|
/**
|
|
23
23
|
* Determines whether a given value is a pure JSON object (plain object).
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var buffer = require('buffer');
|
|
4
3
|
var objChecker = require('./objChecker.cjs');
|
|
5
4
|
|
|
6
5
|
const isBrowser = typeof window !== 'undefined' && typeof window.document !== 'undefined';
|
|
@@ -34,26 +33,48 @@ const typeValidator = {
|
|
|
34
33
|
* If no index is provided, the type is inserted just before 'object' (if it exists), or at the end.
|
|
35
34
|
*
|
|
36
35
|
* @param {ExtendObjType|ExtendObjTypeArray} newItems
|
|
37
|
-
*
|
|
36
|
+
* - New type validators to be added.
|
|
38
37
|
* @param {number} [index] - Optional. Position at which to insert each new type. Ignored if the type already exists.
|
|
39
38
|
* @returns {string[]} - A list of successfully added type names.
|
|
40
39
|
*
|
|
41
40
|
* @example
|
|
42
41
|
* extendObjType({
|
|
43
|
-
*
|
|
42
|
+
* htmlElement2: val => typeof HTMLElement !== 'undefined' && val instanceof HTMLElement
|
|
44
43
|
* });
|
|
45
44
|
*
|
|
46
45
|
* @example
|
|
47
46
|
* extendObjType([
|
|
48
|
-
*
|
|
49
|
-
*
|
|
47
|
+
* ['alpha', val => typeof val === 'string'],
|
|
48
|
+
* ['beta', val => Array.isArray(val)]
|
|
50
49
|
* ]);
|
|
51
50
|
*/
|
|
52
51
|
function extendObjType(newItems, index) {
|
|
52
|
+
if (typeof newItems !== 'object' || newItems === null) {
|
|
53
|
+
throw new TypeError("Argument 'newItems' must be an object or an array.");
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (typeof index !== 'undefined' && typeof index !== 'number') {
|
|
57
|
+
throw new TypeError("Argument 'index' must be a number.");
|
|
58
|
+
}
|
|
59
|
+
|
|
53
60
|
const added = [];
|
|
54
61
|
|
|
55
62
|
const entries = Array.isArray(newItems) ? newItems : Object.entries(newItems);
|
|
56
|
-
|
|
63
|
+
|
|
64
|
+
for (const entry of entries) {
|
|
65
|
+
if (!Array.isArray(entry) || entry.length < 2) {
|
|
66
|
+
throw new TypeError("Each item in 'newItems' array must be a [key, function] pair.");
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const [key, fn] = entry;
|
|
70
|
+
|
|
71
|
+
if (typeof key !== 'string') {
|
|
72
|
+
throw new TypeError('Validator key must be a string.');
|
|
73
|
+
}
|
|
74
|
+
if (typeof fn !== 'function') {
|
|
75
|
+
throw new TypeError(`Validator for key '${key}' must be a function.`);
|
|
76
|
+
}
|
|
77
|
+
|
|
57
78
|
if (!typeValidator.items.hasOwnProperty(key)) {
|
|
58
79
|
// @ts-ignore
|
|
59
80
|
typeValidator.items[key] = fn;
|
|
@@ -87,10 +108,17 @@ function extendObjType(newItems, index) {
|
|
|
87
108
|
*
|
|
88
109
|
* @example
|
|
89
110
|
* reorderObjTypeOrder([
|
|
90
|
-
*
|
|
111
|
+
* 'string', 'number', 'array', 'object'
|
|
91
112
|
* ]);
|
|
92
113
|
*/
|
|
93
114
|
function reorderObjTypeOrder(newOrder) {
|
|
115
|
+
if (!Array.isArray(newOrder)) {
|
|
116
|
+
throw new TypeError("Argument 'newOrder' must be an array of strings.");
|
|
117
|
+
}
|
|
118
|
+
if (!newOrder.every((item) => typeof item === 'string')) {
|
|
119
|
+
throw new TypeError("All elements in 'newOrder' must be strings.");
|
|
120
|
+
}
|
|
121
|
+
|
|
94
122
|
const currentOrder = [...typeValidator.order]; // shallow clone
|
|
95
123
|
|
|
96
124
|
// All keys in newOrder must exist in currentOrder
|
|
@@ -148,7 +176,7 @@ const getType = (val) => {
|
|
|
148
176
|
* @param {*} obj - The object to check or identify.
|
|
149
177
|
* @param {string} [type] - Optional. If provided, checks whether the object matches this type (e.g., "object", "array", "string").
|
|
150
178
|
* @returns {boolean|string|null} - Returns `true` if the type matches, `false` if not,
|
|
151
|
-
*
|
|
179
|
+
* the type string if no type is provided, or `null` if the object is `undefined`.
|
|
152
180
|
*
|
|
153
181
|
* @example
|
|
154
182
|
* objType([], 'array'); // true
|
|
@@ -158,6 +186,10 @@ const getType = (val) => {
|
|
|
158
186
|
*/
|
|
159
187
|
function objType(obj, type) {
|
|
160
188
|
if (typeof obj === 'undefined') return null;
|
|
189
|
+
if (typeof type !== 'undefined' && typeof type !== 'string') {
|
|
190
|
+
throw new TypeError("Argument 'type' must be a string.");
|
|
191
|
+
}
|
|
192
|
+
|
|
161
193
|
const result = getType(obj);
|
|
162
194
|
if (typeof type === 'string') return result === type.toLowerCase();
|
|
163
195
|
return result;
|
|
@@ -245,16 +277,6 @@ extendObjType([
|
|
|
245
277
|
],
|
|
246
278
|
]);
|
|
247
279
|
|
|
248
|
-
if (!isBrowser) {
|
|
249
|
-
extendObjType([
|
|
250
|
-
[
|
|
251
|
-
'buffer',
|
|
252
|
-
/** @param {*} val @returns {val is Buffer} */
|
|
253
|
-
(val) => typeof buffer.Buffer !== 'undefined' && buffer.Buffer.isBuffer(val),
|
|
254
|
-
],
|
|
255
|
-
]);
|
|
256
|
-
}
|
|
257
|
-
|
|
258
280
|
if (isBrowser) {
|
|
259
281
|
extendObjType([
|
|
260
282
|
[
|
|
@@ -7,19 +7,19 @@
|
|
|
7
7
|
* If no index is provided, the type is inserted just before 'object' (if it exists), or at the end.
|
|
8
8
|
*
|
|
9
9
|
* @param {ExtendObjType|ExtendObjTypeArray} newItems
|
|
10
|
-
*
|
|
10
|
+
* - New type validators to be added.
|
|
11
11
|
* @param {number} [index] - Optional. Position at which to insert each new type. Ignored if the type already exists.
|
|
12
12
|
* @returns {string[]} - A list of successfully added type names.
|
|
13
13
|
*
|
|
14
14
|
* @example
|
|
15
15
|
* extendObjType({
|
|
16
|
-
*
|
|
16
|
+
* htmlElement2: val => typeof HTMLElement !== 'undefined' && val instanceof HTMLElement
|
|
17
17
|
* });
|
|
18
18
|
*
|
|
19
19
|
* @example
|
|
20
20
|
* extendObjType([
|
|
21
|
-
*
|
|
22
|
-
*
|
|
21
|
+
* ['alpha', val => typeof val === 'string'],
|
|
22
|
+
* ['beta', val => Array.isArray(val)]
|
|
23
23
|
* ]);
|
|
24
24
|
*/
|
|
25
25
|
export function extendObjType(newItems: ExtendObjType | ExtendObjTypeArray, index?: number): string[];
|
|
@@ -33,7 +33,7 @@ export function extendObjType(newItems: ExtendObjType | ExtendObjTypeArray, inde
|
|
|
33
33
|
*
|
|
34
34
|
* @example
|
|
35
35
|
* reorderObjTypeOrder([
|
|
36
|
-
*
|
|
36
|
+
* 'string', 'number', 'array', 'object'
|
|
37
37
|
* ]);
|
|
38
38
|
*/
|
|
39
39
|
export function reorderObjTypeOrder(newOrder: string[]): boolean;
|
|
@@ -50,7 +50,7 @@ export function cloneObjTypeOrder(): string[];
|
|
|
50
50
|
* @param {*} obj - The object to check or identify.
|
|
51
51
|
* @param {string} [type] - Optional. If provided, checks whether the object matches this type (e.g., "object", "array", "string").
|
|
52
52
|
* @returns {boolean|string|null} - Returns `true` if the type matches, `false` if not,
|
|
53
|
-
*
|
|
53
|
+
* the type string if no type is provided, or `null` if the object is `undefined`.
|
|
54
54
|
*
|
|
55
55
|
* @example
|
|
56
56
|
* objType([], 'array'); // true
|