tiny-essentials 1.28.0 → 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/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 -1
- 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 -1
- 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 -1
- 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/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 -7
- package/dist/v1/basics/objFilter.d.mts +6 -6
- package/dist/v1/basics/objFilter.mjs +32 -7
- 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 -0
- package/dist/v1/index.d.mts +4 -1
- package/dist/v1/index.mjs +4 -3
- package/dist/v1/libs/TinyAdvancedRaffle.cjs +64 -50
- package/dist/v1/libs/TinyAdvancedRaffle.d.mts +4 -13
- package/dist/v1/libs/TinyAdvancedRaffle.mjs +62 -49
- 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 +18 -1
- package/dist/v1/libs/TinyIframeEvents.mjs +18 -1
- package/dist/v1/libs/TinyLocalStorage.cjs +48 -0
- package/dist/v1/libs/TinyLocalStorage.d.mts +6 -0
- package/dist/v1/libs/TinyLocalStorage.mjs +45 -0
- package/dist/v1/libs/TinyMediaPlayer.cjs +72 -0
- package/dist/v1/libs/TinyMediaPlayer.d.mts +10 -0
- package/dist/v1/libs/TinyMediaPlayer.mjs +66 -0
- package/dist/v1/libs/TinyNewWinEvents.cjs +29 -0
- package/dist/v1/libs/TinyNewWinEvents.d.mts +6 -0
- package/dist/v1/libs/TinyNewWinEvents.mjs +26 -0
- 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 +38 -0
- package/dist/v1/libs/TinyRadioFm.d.mts +1 -0
- package/dist/v1/libs/TinyRadioFm.mjs +35 -0
- 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 +52 -3
- package/dist/v1/libs/TinySmartScroller.d.mts +17 -15
- package/dist/v1/libs/TinySmartScroller.mjs +51 -5
- 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 +19 -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/README.md +1 -0
- 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/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 +2 -2
- package/docs/v1/libs/TinyLevelUp.md +1 -1
- package/docs/v1/libs/TinyLoadingScreen.md +1 -1
- package/docs/v1/libs/TinyNewWinEvents.md +2 -2
- 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 +10 -1
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { EventEmitter } from 'events';
|
|
2
2
|
import { blobUrlToBase64, convertToBlobUrl, parseMediaMetadata, revokeContentUrls, } from '../basics/mediaContent.mjs';
|
|
3
|
+
import { createCheckDestroyed } from './utils.mjs';
|
|
4
|
+
const checkDestroy = createCheckDestroyed('TinyRadioFm');
|
|
3
5
|
/**
|
|
4
6
|
* @typedef {import('../basics/mediaContent.mjs').MediaContentBase} MediaContentBase
|
|
5
7
|
* @typedef {import('../basics/mediaContent.mjs').MediaContentMetadata} MediaContentMetadata
|
|
@@ -147,11 +149,17 @@ class TinyRadioFm extends EventEmitter {
|
|
|
147
149
|
throw new TypeError('unknownArtist must have an string or function.');
|
|
148
150
|
TinyRadioFm.#unknownArtist = value;
|
|
149
151
|
}
|
|
152
|
+
/** @type {boolean} */
|
|
153
|
+
#destroyed = false;
|
|
154
|
+
get destroyed() {
|
|
155
|
+
return this.#destroyed;
|
|
156
|
+
}
|
|
150
157
|
/**
|
|
151
158
|
* Gets the total count of all content items (music and voice) in the system.
|
|
152
159
|
* @returns {number}
|
|
153
160
|
*/
|
|
154
161
|
get size() {
|
|
162
|
+
checkDestroy(this.#destroyed);
|
|
155
163
|
return this.#musicList.length + this.#voiceList.length;
|
|
156
164
|
}
|
|
157
165
|
/**
|
|
@@ -159,6 +167,7 @@ class TinyRadioFm extends EventEmitter {
|
|
|
159
167
|
* @returns {number}
|
|
160
168
|
*/
|
|
161
169
|
get musicSize() {
|
|
170
|
+
checkDestroy(this.#destroyed);
|
|
162
171
|
return this.#musicList.length;
|
|
163
172
|
}
|
|
164
173
|
/**
|
|
@@ -166,6 +175,7 @@ class TinyRadioFm extends EventEmitter {
|
|
|
166
175
|
* @returns {number}
|
|
167
176
|
*/
|
|
168
177
|
get voiceSize() {
|
|
178
|
+
checkDestroy(this.#destroyed);
|
|
169
179
|
return this.#voiceList.length;
|
|
170
180
|
}
|
|
171
181
|
/**
|
|
@@ -173,6 +183,7 @@ class TinyRadioFm extends EventEmitter {
|
|
|
173
183
|
* @returns {number}
|
|
174
184
|
*/
|
|
175
185
|
get customPosSize() {
|
|
186
|
+
checkDestroy(this.#destroyed);
|
|
176
187
|
return this.#customPositions.length;
|
|
177
188
|
}
|
|
178
189
|
/**
|
|
@@ -180,6 +191,7 @@ class TinyRadioFm extends EventEmitter {
|
|
|
180
191
|
* @returns {number}
|
|
181
192
|
*/
|
|
182
193
|
get tasksSize() {
|
|
194
|
+
checkDestroy(this.#destroyed);
|
|
183
195
|
return this.#scheduledTasks.length;
|
|
184
196
|
}
|
|
185
197
|
/**
|
|
@@ -187,6 +199,7 @@ class TinyRadioFm extends EventEmitter {
|
|
|
187
199
|
* @returns {number}
|
|
188
200
|
*/
|
|
189
201
|
get cycleCacheSize() {
|
|
202
|
+
checkDestroy(this.#destroyed);
|
|
190
203
|
return this.#cycleCache.size;
|
|
191
204
|
}
|
|
192
205
|
/** @type {MediaContent[]} */
|
|
@@ -196,6 +209,7 @@ class TinyRadioFm extends EventEmitter {
|
|
|
196
209
|
* @returns {MediaContent[]}
|
|
197
210
|
*/
|
|
198
211
|
get musicList() {
|
|
212
|
+
checkDestroy(this.#destroyed);
|
|
199
213
|
return structuredClone(this.#musicList);
|
|
200
214
|
}
|
|
201
215
|
/** @type {MediaContent[]} */
|
|
@@ -205,6 +219,7 @@ class TinyRadioFm extends EventEmitter {
|
|
|
205
219
|
* @returns {MediaContent[]}
|
|
206
220
|
*/
|
|
207
221
|
get voiceList() {
|
|
222
|
+
checkDestroy(this.#destroyed);
|
|
208
223
|
return structuredClone(this.#voiceList);
|
|
209
224
|
}
|
|
210
225
|
/** @type {CustomPosition[]} */
|
|
@@ -214,6 +229,7 @@ class TinyRadioFm extends EventEmitter {
|
|
|
214
229
|
* @returns {CustomPosition[]}
|
|
215
230
|
*/
|
|
216
231
|
get customPositions() {
|
|
232
|
+
checkDestroy(this.#destroyed);
|
|
217
233
|
return structuredClone(this.#customPositions);
|
|
218
234
|
}
|
|
219
235
|
/** @type {ScheduledTask[]} */
|
|
@@ -223,6 +239,7 @@ class TinyRadioFm extends EventEmitter {
|
|
|
223
239
|
* @returns {ScheduledTask[]}
|
|
224
240
|
*/
|
|
225
241
|
get scheduledTasks() {
|
|
242
|
+
checkDestroy(this.#destroyed);
|
|
226
243
|
return structuredClone(this.#scheduledTasks);
|
|
227
244
|
}
|
|
228
245
|
/**
|
|
@@ -230,6 +247,7 @@ class TinyRadioFm extends EventEmitter {
|
|
|
230
247
|
* @returns {MediaContent[]} A cloned object of the cache.
|
|
231
248
|
*/
|
|
232
249
|
get allList() {
|
|
250
|
+
checkDestroy(this.#destroyed);
|
|
233
251
|
return [...this.musicList, ...this.voiceList];
|
|
234
252
|
}
|
|
235
253
|
/** @type {number} */
|
|
@@ -239,6 +257,7 @@ class TinyRadioFm extends EventEmitter {
|
|
|
239
257
|
* @returns {number}
|
|
240
258
|
*/
|
|
241
259
|
get seed() {
|
|
260
|
+
checkDestroy(this.#destroyed);
|
|
242
261
|
return this.#seed;
|
|
243
262
|
}
|
|
244
263
|
/**
|
|
@@ -246,6 +265,7 @@ class TinyRadioFm extends EventEmitter {
|
|
|
246
265
|
* @param {number} seed - The new seed.
|
|
247
266
|
*/
|
|
248
267
|
set seed(seed) {
|
|
268
|
+
checkDestroy(this.#destroyed);
|
|
249
269
|
if (typeof seed !== 'number')
|
|
250
270
|
throw new TypeError('Seed must be a number.');
|
|
251
271
|
this.#seed = seed;
|
|
@@ -259,6 +279,7 @@ class TinyRadioFm extends EventEmitter {
|
|
|
259
279
|
* @returns {number}
|
|
260
280
|
*/
|
|
261
281
|
get anchorDate() {
|
|
282
|
+
checkDestroy(this.#destroyed);
|
|
262
283
|
return this.#anchorDate;
|
|
263
284
|
}
|
|
264
285
|
/** @type {Map<number, CycleBlock>} */
|
|
@@ -280,6 +301,7 @@ class TinyRadioFm extends EventEmitter {
|
|
|
280
301
|
* @returns {RadioConfig}
|
|
281
302
|
*/
|
|
282
303
|
get config() {
|
|
304
|
+
checkDestroy(this.#destroyed);
|
|
283
305
|
return structuredClone(this.#config);
|
|
284
306
|
}
|
|
285
307
|
/**
|
|
@@ -288,6 +310,7 @@ class TinyRadioFm extends EventEmitter {
|
|
|
288
310
|
* @throws {TypeError|RangeError} If the new configuration is invalid.
|
|
289
311
|
*/
|
|
290
312
|
set config(config) {
|
|
313
|
+
checkDestroy(this.#destroyed);
|
|
291
314
|
// Validate the entire object before applying it
|
|
292
315
|
this.#validateConfig(config);
|
|
293
316
|
this.#config = structuredClone(config);
|
|
@@ -377,6 +400,7 @@ class TinyRadioFm extends EventEmitter {
|
|
|
377
400
|
* @throws {TypeError} If the type is invalid or the data lacks a valid ID and numerical duration.
|
|
378
401
|
*/
|
|
379
402
|
add(type, data, smartQueue = true) {
|
|
403
|
+
checkDestroy(this.#destroyed);
|
|
380
404
|
if (!['music', 'voice', 'custom'].includes(type)) {
|
|
381
405
|
throw new TypeError('Type must be "music", "voice", or "custom".');
|
|
382
406
|
}
|
|
@@ -410,6 +434,7 @@ class TinyRadioFm extends EventEmitter {
|
|
|
410
434
|
* @throws {TypeError} If arguments do not match the required types or action/type constraints.
|
|
411
435
|
*/
|
|
412
436
|
scheduleTask(timestamp, action, type, payload, smartQueue = true) {
|
|
437
|
+
checkDestroy(this.#destroyed);
|
|
413
438
|
if (typeof timestamp !== 'number' || isNaN(timestamp))
|
|
414
439
|
throw new TypeError('timestamp must be a valid number.');
|
|
415
440
|
if (!['add', 'remove', 'move'].includes(action))
|
|
@@ -467,6 +492,7 @@ class TinyRadioFm extends EventEmitter {
|
|
|
467
492
|
* @throws {TypeError} If the id is not a string.
|
|
468
493
|
*/
|
|
469
494
|
remove(id) {
|
|
495
|
+
checkDestroy(this.#destroyed);
|
|
470
496
|
if (typeof id !== 'string')
|
|
471
497
|
throw new TypeError('id must be a string.');
|
|
472
498
|
// Revoke Blob URLs of items being removed to free memory
|
|
@@ -505,6 +531,7 @@ class TinyRadioFm extends EventEmitter {
|
|
|
505
531
|
* @throws {TypeError|RangeError} If the provided values or the resulting state is invalid.
|
|
506
532
|
*/
|
|
507
533
|
setConfig(config) {
|
|
534
|
+
checkDestroy(this.#destroyed);
|
|
508
535
|
// First, validate the incoming partial object for basic type correctness
|
|
509
536
|
this.#validateConfig(config);
|
|
510
537
|
// Create the potential new state
|
|
@@ -520,6 +547,7 @@ class TinyRadioFm extends EventEmitter {
|
|
|
520
547
|
* @returns {RadioEvent|null} The current active event, or null if empty.
|
|
521
548
|
*/
|
|
522
549
|
getCurrentEvent() {
|
|
550
|
+
checkDestroy(this.#destroyed);
|
|
523
551
|
const now = Date.now();
|
|
524
552
|
this.#syncRealTimeState(now);
|
|
525
553
|
return this.#getEventAtTime(now, now);
|
|
@@ -534,6 +562,7 @@ class TinyRadioFm extends EventEmitter {
|
|
|
534
562
|
* @throws {RangeError} If the limit exceeds the configured queryLimit or is <= 0.
|
|
535
563
|
*/
|
|
536
564
|
queryTimeline(targetDate, limit = 10) {
|
|
565
|
+
checkDestroy(this.#destroyed);
|
|
537
566
|
if (typeof limit !== 'number' || isNaN(limit)) {
|
|
538
567
|
throw new TypeError(`Invalid query limit value. Ensure it is a number.`);
|
|
539
568
|
}
|
|
@@ -565,6 +594,7 @@ class TinyRadioFm extends EventEmitter {
|
|
|
565
594
|
* @returns {CustomPosition[]} Shallow copy of custom positions array.
|
|
566
595
|
*/
|
|
567
596
|
searchCustomPositions() {
|
|
597
|
+
checkDestroy(this.#destroyed);
|
|
568
598
|
this.#syncRealTimeState(Date.now());
|
|
569
599
|
return [...this.#customPositions];
|
|
570
600
|
}
|
|
@@ -607,6 +637,7 @@ class TinyRadioFm extends EventEmitter {
|
|
|
607
637
|
* @returns {Promise<string>} Stringified JSON state.
|
|
608
638
|
*/
|
|
609
639
|
async exportState() {
|
|
640
|
+
checkDestroy(this.#destroyed);
|
|
610
641
|
const processedMusic = await this._processListForExport(this.#musicList);
|
|
611
642
|
const processedVoice = await this._processListForExport(this.#voiceList);
|
|
612
643
|
const processedCustom = await Promise.all(this.#customPositions.map(async (cp) => {
|
|
@@ -641,6 +672,7 @@ class TinyRadioFm extends EventEmitter {
|
|
|
641
672
|
* @throws {TypeError} If json is not a valid string or object.
|
|
642
673
|
*/
|
|
643
674
|
importState(json) {
|
|
675
|
+
checkDestroy(this.#destroyed);
|
|
644
676
|
/** @type {TinyRadioFmImport} */
|
|
645
677
|
let data;
|
|
646
678
|
if (typeof json === 'string') {
|
|
@@ -1111,6 +1143,8 @@ class TinyRadioFm extends EventEmitter {
|
|
|
1111
1143
|
* @param {boolean} [destroyThumbs=true]
|
|
1112
1144
|
*/
|
|
1113
1145
|
destroy(destroyThumbs = true) {
|
|
1146
|
+
if (this.#destroyed)
|
|
1147
|
+
return;
|
|
1114
1148
|
if (destroyThumbs) {
|
|
1115
1149
|
this.#musicList.forEach((item) => revokeContentUrls(item));
|
|
1116
1150
|
this.#voiceList.forEach((item) => revokeContentUrls(item));
|
|
@@ -1131,6 +1165,7 @@ class TinyRadioFm extends EventEmitter {
|
|
|
1131
1165
|
this.#cycleCache.clear();
|
|
1132
1166
|
this.emit('destroyed', { timestamp: Date.now() });
|
|
1133
1167
|
this.removeAllListeners();
|
|
1168
|
+
this.#destroyed = true;
|
|
1134
1169
|
}
|
|
1135
1170
|
}
|
|
1136
1171
|
export default TinyRadioFm;
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
var utils = require('./utils.cjs');
|
|
4
|
+
|
|
5
|
+
const checkDestroy = utils.createCheckDestroyed('TinyRateLimiter');
|
|
6
|
+
|
|
3
7
|
/**
|
|
4
8
|
* Callback triggered when a group's stored hit history exceeds
|
|
5
9
|
* the configured memory limit.
|
|
@@ -70,6 +74,21 @@
|
|
|
70
74
|
* and safe to use in long-running Node.js processes.
|
|
71
75
|
*/
|
|
72
76
|
class TinyRateLimiter {
|
|
77
|
+
/**
|
|
78
|
+
* Tracks whether the instance has been destroyed.
|
|
79
|
+
* @type {boolean}
|
|
80
|
+
*/
|
|
81
|
+
#destroyed = false;
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Gets the current destruction status of the instance.
|
|
85
|
+
*
|
|
86
|
+
* @returns {boolean} True if the instance is destroyed, false otherwise.
|
|
87
|
+
*/
|
|
88
|
+
get destroyed() {
|
|
89
|
+
return this.#destroyed;
|
|
90
|
+
}
|
|
91
|
+
|
|
73
92
|
/**
|
|
74
93
|
* Maximum number of timestamps stored per group.
|
|
75
94
|
* When exceeded, older entries are discarded.
|
|
@@ -186,6 +205,7 @@ class TinyRateLimiter {
|
|
|
186
205
|
* @param {OnMemoryExceeded} callback
|
|
187
206
|
*/
|
|
188
207
|
setOnMemoryExceeded(callback) {
|
|
208
|
+
checkDestroy(this.#destroyed);
|
|
189
209
|
if (typeof callback !== 'function') throw new Error('onMemoryExceeded must be a function');
|
|
190
210
|
this.#onMemoryExceeded = callback;
|
|
191
211
|
}
|
|
@@ -194,6 +214,7 @@ class TinyRateLimiter {
|
|
|
194
214
|
* Removes the memory-exceeded callback.
|
|
195
215
|
*/
|
|
196
216
|
clearOnMemoryExceeded() {
|
|
217
|
+
checkDestroy(this.#destroyed);
|
|
197
218
|
this.#onMemoryExceeded = null;
|
|
198
219
|
}
|
|
199
220
|
|
|
@@ -213,6 +234,7 @@ class TinyRateLimiter {
|
|
|
213
234
|
* @param {OnGroupExpired} callback - A function that receives the expired groupId.
|
|
214
235
|
*/
|
|
215
236
|
setOnGroupExpired(callback) {
|
|
237
|
+
checkDestroy(this.#destroyed);
|
|
216
238
|
if (typeof callback !== 'function') throw new Error('onGroupExpired must be a function');
|
|
217
239
|
this.#onGroupExpired = callback;
|
|
218
240
|
}
|
|
@@ -221,6 +243,7 @@ class TinyRateLimiter {
|
|
|
221
243
|
* Removes the group-expiration callback.
|
|
222
244
|
*/
|
|
223
245
|
clearOnGroupExpired() {
|
|
246
|
+
checkDestroy(this.#destroyed);
|
|
224
247
|
this.#onGroupExpired = null;
|
|
225
248
|
}
|
|
226
249
|
|
|
@@ -285,6 +308,7 @@ class TinyRateLimiter {
|
|
|
285
308
|
* @returns {boolean}
|
|
286
309
|
*/
|
|
287
310
|
isGroupId(id) {
|
|
311
|
+
checkDestroy(this.#destroyed);
|
|
288
312
|
const result = this.groupFlags.get(id);
|
|
289
313
|
return typeof result === 'boolean' ? result : false;
|
|
290
314
|
}
|
|
@@ -295,6 +319,7 @@ class TinyRateLimiter {
|
|
|
295
319
|
* @returns {string[]}
|
|
296
320
|
*/
|
|
297
321
|
getUsersInGroup(groupId) {
|
|
322
|
+
checkDestroy(this.#destroyed);
|
|
298
323
|
const users = [];
|
|
299
324
|
for (const [userId, assignedGroup] of this.userToGroup.entries()) {
|
|
300
325
|
if (assignedGroup === groupId) {
|
|
@@ -311,6 +336,7 @@ class TinyRateLimiter {
|
|
|
311
336
|
* @param {number} ttl
|
|
312
337
|
*/
|
|
313
338
|
setGroupTTL(groupId, ttl) {
|
|
339
|
+
checkDestroy(this.#destroyed);
|
|
314
340
|
if (typeof ttl !== 'number' || !Number.isFinite(ttl) || ttl <= 0)
|
|
315
341
|
throw new Error('TTL must be a positive number in milliseconds');
|
|
316
342
|
this.groupTTL.set(groupId, ttl);
|
|
@@ -322,6 +348,7 @@ class TinyRateLimiter {
|
|
|
322
348
|
* @returns {number|null}
|
|
323
349
|
*/
|
|
324
350
|
getGroupTTL(groupId) {
|
|
351
|
+
checkDestroy(this.#destroyed);
|
|
325
352
|
return this.groupTTL.get(groupId) ?? null;
|
|
326
353
|
}
|
|
327
354
|
|
|
@@ -330,6 +357,7 @@ class TinyRateLimiter {
|
|
|
330
357
|
* @param {string} groupId
|
|
331
358
|
*/
|
|
332
359
|
deleteGroupTTL(groupId) {
|
|
360
|
+
checkDestroy(this.#destroyed);
|
|
333
361
|
this.groupTTL.delete(groupId);
|
|
334
362
|
}
|
|
335
363
|
|
|
@@ -344,6 +372,7 @@ class TinyRateLimiter {
|
|
|
344
372
|
* @throws {Error} If the user belongs to another group
|
|
345
373
|
*/
|
|
346
374
|
assignToGroup(userId, groupId) {
|
|
375
|
+
checkDestroy(this.#destroyed);
|
|
347
376
|
const existingGroup = this.userToGroup.get(userId);
|
|
348
377
|
if (existingGroup && existingGroup !== groupId)
|
|
349
378
|
throw new Error(`User ${userId} is already assigned to group ${existingGroup}`);
|
|
@@ -391,6 +420,7 @@ class TinyRateLimiter {
|
|
|
391
420
|
* @returns {string}
|
|
392
421
|
*/
|
|
393
422
|
getGroupId(userId) {
|
|
423
|
+
checkDestroy(this.#destroyed);
|
|
394
424
|
return this.userToGroup.get(userId) || userId; // fallback: use userId as own group
|
|
395
425
|
}
|
|
396
426
|
|
|
@@ -417,6 +447,7 @@ class TinyRateLimiter {
|
|
|
417
447
|
* @param {string} userId
|
|
418
448
|
*/
|
|
419
449
|
hit(userId) {
|
|
450
|
+
checkDestroy(this.#destroyed);
|
|
420
451
|
const groupId = this.getGroupId(userId);
|
|
421
452
|
const now = Date.now();
|
|
422
453
|
|
|
@@ -470,6 +501,7 @@ class TinyRateLimiter {
|
|
|
470
501
|
* @returns {boolean}
|
|
471
502
|
*/
|
|
472
503
|
isRateLimited(userId) {
|
|
504
|
+
checkDestroy(this.#destroyed);
|
|
473
505
|
const groupId = this.getGroupId(userId);
|
|
474
506
|
if (!this.groupData.has(groupId)) return false;
|
|
475
507
|
|
|
@@ -501,6 +533,7 @@ class TinyRateLimiter {
|
|
|
501
533
|
* @param {string} groupId
|
|
502
534
|
*/
|
|
503
535
|
resetGroup(groupId) {
|
|
536
|
+
checkDestroy(this.#destroyed);
|
|
504
537
|
this.groupFlags.delete(groupId);
|
|
505
538
|
this.groupData.delete(groupId);
|
|
506
539
|
this.lastSeen.delete(groupId);
|
|
@@ -512,6 +545,7 @@ class TinyRateLimiter {
|
|
|
512
545
|
* @param {string} userId
|
|
513
546
|
*/
|
|
514
547
|
resetUserGroup(userId) {
|
|
548
|
+
checkDestroy(this.#destroyed);
|
|
515
549
|
this.userToGroup.delete(userId);
|
|
516
550
|
}
|
|
517
551
|
|
|
@@ -521,6 +555,7 @@ class TinyRateLimiter {
|
|
|
521
555
|
* @param {number[]} timestamps
|
|
522
556
|
*/
|
|
523
557
|
setData(groupId, timestamps) {
|
|
558
|
+
checkDestroy(this.#destroyed);
|
|
524
559
|
if (!Array.isArray(timestamps)) throw new Error('timestamps must be an array of numbers.');
|
|
525
560
|
for (const t of timestamps) {
|
|
526
561
|
if (typeof t !== 'number' || !Number.isFinite(t)) {
|
|
@@ -538,6 +573,7 @@ class TinyRateLimiter {
|
|
|
538
573
|
* @returns {boolean}
|
|
539
574
|
*/
|
|
540
575
|
hasData(groupId) {
|
|
576
|
+
checkDestroy(this.#destroyed);
|
|
541
577
|
return this.groupData.has(groupId);
|
|
542
578
|
}
|
|
543
579
|
|
|
@@ -547,6 +583,7 @@ class TinyRateLimiter {
|
|
|
547
583
|
* @returns {number[]}
|
|
548
584
|
*/
|
|
549
585
|
getData(groupId) {
|
|
586
|
+
checkDestroy(this.#destroyed);
|
|
550
587
|
return this.groupData.get(groupId) || [];
|
|
551
588
|
}
|
|
552
589
|
|
|
@@ -555,6 +592,7 @@ class TinyRateLimiter {
|
|
|
555
592
|
* @returns {number}
|
|
556
593
|
*/
|
|
557
594
|
getMaxIdle() {
|
|
595
|
+
checkDestroy(this.#destroyed);
|
|
558
596
|
if (typeof this.#maxIdle !== 'number' || !Number.isFinite(this.#maxIdle) || this.#maxIdle < 0) {
|
|
559
597
|
throw new Error("'maxIdle' must be a non-negative finite number.");
|
|
560
598
|
}
|
|
@@ -566,6 +604,7 @@ class TinyRateLimiter {
|
|
|
566
604
|
* @param {number} ms
|
|
567
605
|
*/
|
|
568
606
|
setMaxIdle(ms) {
|
|
607
|
+
checkDestroy(this.#destroyed);
|
|
569
608
|
if (typeof ms !== 'number' || !Number.isFinite(ms) || ms < 0) {
|
|
570
609
|
throw new Error("'maxIdle' must be a non-negative finite number.");
|
|
571
610
|
}
|
|
@@ -599,6 +638,7 @@ class TinyRateLimiter {
|
|
|
599
638
|
* @returns {string[]}
|
|
600
639
|
*/
|
|
601
640
|
getActiveGroups() {
|
|
641
|
+
checkDestroy(this.#destroyed);
|
|
602
642
|
return Array.from(this.groupData.keys());
|
|
603
643
|
}
|
|
604
644
|
|
|
@@ -607,6 +647,7 @@ class TinyRateLimiter {
|
|
|
607
647
|
* @returns {Record<string, string>}
|
|
608
648
|
*/
|
|
609
649
|
getAllUserMappings() {
|
|
650
|
+
checkDestroy(this.#destroyed);
|
|
610
651
|
return Object.fromEntries(this.userToGroup);
|
|
611
652
|
}
|
|
612
653
|
|
|
@@ -619,6 +660,7 @@ class TinyRateLimiter {
|
|
|
619
660
|
* @returns {number}
|
|
620
661
|
*/
|
|
621
662
|
getInterval() {
|
|
663
|
+
checkDestroy(this.#destroyed);
|
|
622
664
|
if (typeof this.#interval !== 'number' || !Number.isFinite(this.#interval)) {
|
|
623
665
|
throw new Error("'interval' is not a valid finite number.");
|
|
624
666
|
}
|
|
@@ -630,6 +672,7 @@ class TinyRateLimiter {
|
|
|
630
672
|
* @returns {number}
|
|
631
673
|
*/
|
|
632
674
|
getMaxHits() {
|
|
675
|
+
checkDestroy(this.#destroyed);
|
|
633
676
|
if (typeof this.#maxHits !== 'number' || !Number.isFinite(this.#maxHits)) {
|
|
634
677
|
throw new Error("'maxHits' is not a valid finite number.");
|
|
635
678
|
}
|
|
@@ -642,6 +685,7 @@ class TinyRateLimiter {
|
|
|
642
685
|
* @returns {number}
|
|
643
686
|
*/
|
|
644
687
|
getTotalHits(groupId) {
|
|
688
|
+
checkDestroy(this.#destroyed);
|
|
645
689
|
const history = this.groupData.get(groupId);
|
|
646
690
|
return Array.isArray(history) ? history.length : 0;
|
|
647
691
|
}
|
|
@@ -652,6 +696,7 @@ class TinyRateLimiter {
|
|
|
652
696
|
* @returns {number|null}
|
|
653
697
|
*/
|
|
654
698
|
getLastHit(groupId) {
|
|
699
|
+
checkDestroy(this.#destroyed);
|
|
655
700
|
const history = this.groupData.get(groupId);
|
|
656
701
|
return history?.length ? history[history.length - 1] : null;
|
|
657
702
|
}
|
|
@@ -662,6 +707,7 @@ class TinyRateLimiter {
|
|
|
662
707
|
* @returns {number|null}
|
|
663
708
|
*/
|
|
664
709
|
getTimeSinceLastHit(groupId) {
|
|
710
|
+
checkDestroy(this.#destroyed);
|
|
665
711
|
const last = this.getLastHit(groupId);
|
|
666
712
|
return last !== null ? Date.now() - last : null;
|
|
667
713
|
}
|
|
@@ -687,6 +733,7 @@ class TinyRateLimiter {
|
|
|
687
733
|
* @returns {number|null}
|
|
688
734
|
*/
|
|
689
735
|
getAverageHitSpacing(groupId) {
|
|
736
|
+
checkDestroy(this.#destroyed);
|
|
690
737
|
return this._calculateAverageSpacing(this.groupData.get(groupId));
|
|
691
738
|
}
|
|
692
739
|
|
|
@@ -701,6 +748,7 @@ class TinyRateLimiter {
|
|
|
701
748
|
* }}
|
|
702
749
|
*/
|
|
703
750
|
getMetrics(groupId) {
|
|
751
|
+
checkDestroy(this.#destroyed);
|
|
704
752
|
const history = this.groupData.get(groupId);
|
|
705
753
|
|
|
706
754
|
if (!Array.isArray(history) || history.length === 0) {
|
|
@@ -729,6 +777,7 @@ class TinyRateLimiter {
|
|
|
729
777
|
* Destroy the rate limiter, stopping cleanup and clearing data
|
|
730
778
|
*/
|
|
731
779
|
destroy() {
|
|
780
|
+
if (this.destroyed) return;
|
|
732
781
|
if (this.#cleanupTimer) clearInterval(this.#cleanupTimer);
|
|
733
782
|
this._cleanup();
|
|
734
783
|
this.groupData.clear();
|
|
@@ -736,6 +785,9 @@ class TinyRateLimiter {
|
|
|
736
785
|
this.userToGroup.clear();
|
|
737
786
|
this.groupTTL.clear();
|
|
738
787
|
this.groupFlags.clear();
|
|
788
|
+
|
|
789
|
+
// Lock the instance
|
|
790
|
+
this.#destroyed = true;
|
|
739
791
|
}
|
|
740
792
|
}
|
|
741
793
|
|
|
@@ -106,6 +106,12 @@ declare class TinyRateLimiter {
|
|
|
106
106
|
cleanupInterval?: number | undefined;
|
|
107
107
|
maxIdle?: number | undefined;
|
|
108
108
|
});
|
|
109
|
+
/**
|
|
110
|
+
* Gets the current destruction status of the instance.
|
|
111
|
+
*
|
|
112
|
+
* @returns {boolean} True if the instance is destroyed, false otherwise.
|
|
113
|
+
*/
|
|
114
|
+
get destroyed(): boolean;
|
|
109
115
|
/**
|
|
110
116
|
* Stores hit timestamps per group.
|
|
111
117
|
*
|