From a9ee51061de7de60f0d3aae68794fea155e54952 Mon Sep 17 00:00:00 2001 From: oweitman Date: Thu, 28 Nov 2024 21:17:42 +0100 Subject: [PATCH] chore: release v1.5.0 - Umstellung auf iobroker/eslint - Neues Widget playlist --- .vscode/settings.json | 36 +- README.md | 23 +- .eslintignore => backup/.eslintignore | 6 +- .eslintrc.json => backup/.eslintrc.json | 76 +- .prettierignore => backup/.prettierignore | 2 +- .prettierrc.js => backup/.prettierrc.js | 34 +- bin/translate.js | 273 ++ eslint.config.mjs | 34 + io-package.json | 292 +- lib/ioUtil.js | 310 ++ lib/iosbplayer.js | 2139 ++++++---- lib/iosbserver.js | 2187 ++++++---- lib/squeezenode/squeezeplayer.js | 113 +- lib/squeezenode/squeezerequest.js | 138 +- lib/squeezenode/squeezeserver.js | 148 +- lib/tools.js | 74 +- package-lock.json | 3764 ++++++++++++++--- package.json | 11 +- prettier.config.mjs | 8 + squeezeboxrpc.js | 46 +- widgets/.npmignore | 2 + widgets/package.json | 13 + widgets/squeezeboxrpc.html | 6 +- widgets/squeezeboxrpc/i18n/en.json | 43 + widgets/squeezeboxrpc/i18n/translations.json | 366 ++ widgets/squeezeboxrpc/img/playlist.png | Bin 0 -> 2727 bytes widgets/squeezeboxrpc/js/bundle.js | 3 + .../squeezeboxrpc/js/squeezeboxrpc-dist.js | 2658 ++++++++++++ .../js/squeezeboxrpc-dist.js.map | 7 + widgets/squeezeboxrpc/js/squeezeboxrpc.js | 2784 ++++++------ widgets/squeezeboxrpc/js/textImage.js | 289 +- 31 files changed, 11808 insertions(+), 4077 deletions(-) rename .eslintignore => backup/.eslintignore (95%) rename .eslintrc.json => backup/.eslintrc.json (95%) mode change 100755 => 100644 rename .prettierignore => backup/.prettierignore (96%) rename .prettierrc.js => backup/.prettierrc.js (94%) create mode 100644 bin/translate.js create mode 100644 eslint.config.mjs create mode 100644 lib/ioUtil.js create mode 100644 prettier.config.mjs create mode 100644 widgets/.npmignore create mode 100644 widgets/package.json create mode 100644 widgets/squeezeboxrpc/i18n/en.json create mode 100644 widgets/squeezeboxrpc/i18n/translations.json create mode 100644 widgets/squeezeboxrpc/img/playlist.png create mode 100644 widgets/squeezeboxrpc/js/bundle.js create mode 100644 widgets/squeezeboxrpc/js/squeezeboxrpc-dist.js create mode 100644 widgets/squeezeboxrpc/js/squeezeboxrpc-dist.js.map diff --git a/.vscode/settings.json b/.vscode/settings.json index 1315e00..dc9e339 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,18 +1,18 @@ -{ - "eslint.enable": true, - "editor.formatOnSave": true, - "editor.defaultFormatter": "esbenp.prettier-vscode", - "json.schemas": [ - { - "fileMatch": ["io-package.json"], - "url": "https://raw.githubusercontent.com/ioBroker/ioBroker.js-controller/master/schemas/io-package.json" - }, - { - "fileMatch": ["admin/jsonConfig.json", "admin/jsonCustom.json", "admin/jsonTab.json"], - "url": "https://raw.githubusercontent.com/ioBroker/adapter-react-v5/main/schemas/jsonConfig.json" - } - ], - "[markdown]": { - "editor.wordWrap": "off" - } -} +{ + "eslint.enable": true, + "editor.formatOnSave": false, + "editor.defaultFormatter": "esbenp.prettier-vscode", + "json.schemas": [ + { + "fileMatch": ["io-package.json"], + "url": "https://raw.githubusercontent.com/ioBroker/ioBroker.js-controller/master/schemas/io-package.json" + }, + { + "fileMatch": ["admin/jsonConfig.json", "admin/jsonCustom.json", "admin/jsonTab.json"], + "url": "https://raw.githubusercontent.com/ioBroker/adapter-react-v5/main/schemas/jsonConfig.json" + } + ], + "[markdown]": { + "editor.wordWrap": "off" + } +} diff --git a/README.md b/README.md index 512baa1..3401699 100644 --- a/README.md +++ b/README.md @@ -438,6 +438,20 @@ you must connect the button to the player widget. | Comma as separator | Advanced settings | A comma is used to separate the decimal places. | | Thousands separator | Advanced settings | For large numbers, a separator is inserted every 3 places. | +### Playlist + +![Number](/widgets/squeezeboxrpc/img/playlist.png) + +Display the playlist from the server. If you click on an entry the playlist +is loaded and the player starts. +The widged dosent refresh automaticly, you have to press the refreshh button. + +#### Attributes for Playlist + +| Group | Attribute | Description | +| --------------------- | ----------------- | ----------------------------------------------------------------- | +| Player widget | General group | Selection of the player widget. | + ## SendTo-Befehle ### cmdGeneral @@ -451,7 +465,7 @@ _Alle Playlists:_ ```js async function main() { - let data = await sendToAsync("squeezeboxrpc.0", "cdmGeneral", { + let data = await sendToAsync("squeezeboxrpc.0", "cmdGeneral", { playerid: "", cmdArray: ["playlists", "0", "999", "tags:us"], }); @@ -466,7 +480,7 @@ Dieser Befehl wird vom Adapter intern verwendet, um die Favoriten zu laden. ```js async function main() { - let data = await sendToAsync("squeezeboxrpc.0", "cdmGeneral", { + let data = await sendToAsync("squeezeboxrpc.0", "cmdGeneral", { playerid: "", cmdArray: ["favorites", "items", "0", "999", "want_url:1", "item_id:"], }); @@ -508,6 +522,11 @@ der folgenden CLI-Dokumentation enthalten: ### **WORK IN PROGRESS** --> +### 1.5.0 (2024-11-28) + +- Umstellung auf iobroker/eslint +- Neues Widget playlist + ### 1.4.0 (2024-11-27) - fix some missing objects errors diff --git a/.eslintignore b/backup/.eslintignore similarity index 95% rename from .eslintignore rename to backup/.eslintignore index 7e8d9fe..a87ed24 100644 --- a/.eslintignore +++ b/backup/.eslintignore @@ -1,4 +1,4 @@ -.prettierrc.js -**/.eslintrc.js -admin/words.js +.prettierrc.js +**/.eslintrc.js +admin/words.js liB/squeezenode/* \ No newline at end of file diff --git a/.eslintrc.json b/backup/.eslintrc.json old mode 100755 new mode 100644 similarity index 95% rename from .eslintrc.json rename to backup/.eslintrc.json index cec049e..988bcff --- a/.eslintrc.json +++ b/backup/.eslintrc.json @@ -1,38 +1,38 @@ -{ - "root": true, - "env": { - "es6": true, - "node": true, - "mocha": true - }, - "extends": ["eslint:recommended"], - "plugins": [], - "rules": { - "no-console": "off", - "no-unused-vars": [ - "error", - { - "ignoreRestSiblings": true, - "argsIgnorePattern": "^_" - } - ], - "no-var": "error", - "no-trailing-spaces": "error", - "prefer-const": "error", - "quotes": [ - "error", - "double", - { - "avoidEscape": true, - "allowTemplateLiterals": true - } - ], - "semi": ["error", "always"] - }, - "parserOptions": { - "ecmaVersion": "latest" - }, - "globals": { - "systemDictionary": true - } -} +{ + "root": true, + "env": { + "es6": true, + "node": true, + "mocha": true + }, + "extends": ["eslint:recommended"], + "plugins": [], + "rules": { + "no-console": "off", + "no-unused-vars": [ + "error", + { + "ignoreRestSiblings": true, + "argsIgnorePattern": "^_" + } + ], + "no-var": "error", + "no-trailing-spaces": "error", + "prefer-const": "error", + "quotes": [ + "error", + "double", + { + "avoidEscape": true, + "allowTemplateLiterals": true + } + ], + "semi": ["error", "always"] + }, + "parserOptions": { + "ecmaVersion": "latest" + }, + "globals": { + "systemDictionary": true + } +} diff --git a/.prettierignore b/backup/.prettierignore similarity index 96% rename from .prettierignore rename to backup/.prettierignore index 515bcd4..6bfe96c 100644 --- a/.prettierignore +++ b/backup/.prettierignore @@ -1,2 +1,2 @@ -package.json +package.json package-lock.json \ No newline at end of file diff --git a/.prettierrc.js b/backup/.prettierrc.js similarity index 94% rename from .prettierrc.js rename to backup/.prettierrc.js index 1648bb4..9c5c9cf 100644 --- a/.prettierrc.js +++ b/backup/.prettierrc.js @@ -1,17 +1,17 @@ -module.exports = { - semi: true, - trailingComma: "all", - singleQuote: false, - printWidth: 120, - useTabs: false, - tabWidth: 4, - endOfLine: "lf", - "overrides": [ - { - "files": "*.md", - "options": { - "tabWidth": 2 - } - } - ] -}; +module.exports = { + semi: true, + trailingComma: "all", + singleQuote: false, + printWidth: 120, + useTabs: false, + tabWidth: 4, + endOfLine: "lf", + "overrides": [ + { + "files": "*.md", + "options": { + "tabWidth": 2 + } + } + ] +}; diff --git a/bin/translate.js b/bin/translate.js new file mode 100644 index 0000000..fa2c7db --- /dev/null +++ b/bin/translate.js @@ -0,0 +1,273 @@ +const fs = require('node:fs'); +const path = require('path'); + +const langTemplate = { + en: {}, + de: {}, + ru: {}, + pt: {}, + nl: {}, + fr: {}, + it: {}, + es: {}, + pl: {}, + uk: {}, + 'zh-cn': {}, +}; +let i18npath = '../src/i18n'; +let format = 'multi'; +function importi18nKeys() { + // importiere alle json dateien, die in einem bestimmten verzeichnis liegen + const i18n = {}; + const dir = path.resolve(__dirname, '../', i18npath); + + if (format === 'multi') { + const files = fs.readdirSync(dir); + for (const file of files) { + const filePath = `${dir}/${file}`; + if (fs.statSync(filePath).isFile() && filePath.endsWith('.json')) { + i18n[file.replace('.json', '')] = require(filePath); + } + } + } + if (format === 'single') { + const filePath = path.resolve(__dirname, '../', i18npath); + i18n['en'] = require(filePath); + } + return i18n; +} +function exporti18nKeysMultiFile(i18n) { + const dir = path.resolve(__dirname, i18npath); + for (const lang in i18n) { + const json = JSON.stringify(i18n[lang], null, 4); + const filePath = `${dir}/${lang}.json`; + fs.writeFileSync(filePath, json); + } +} +function exporti18nKeysSingleFile(i18n) { + const source = i18n.en; + const target = {}; + // schleife über source und erstelle ein neues objekt miteiner liste von objekten, die nach dem key benannt werden, welches wiederum als property jede einzelne sprache mit der jeweiligen übersetzung enthäl + for (const key in source) { + target[key] = {}; + for (const lang in i18n) { + target[key][lang] = i18n[lang][key]; + } + } + const pathObject = path.parse(i18npath); + pathObject.base = 'translations.json'; + const newPath = path.format(pathObject); + const filePath = path.resolve(__dirname, '../', newPath); + const json = JSON.stringify(target, null, 4); + fs.writeFileSync(filePath, json); +} +function exporti18nKeys(i18n) { + let tempI18n; + if (!format) { + format = 'multi'; + } + if (format === 'multi') { + tempI18n = exporti18nKeysMultiFile(i18n); + } + if (format === 'single') { + tempI18n = exporti18nKeysSingleFile(i18n); + } + return tempI18n; +} +function extendLanguages(i18n) { + return { ...langTemplate, ...i18n }; +} +function createObjectFromKeys(keyNames) { + const obj = {}; + keyNames.forEach(key => { + if (key !== '') { + obj[key] = ''; + } + }); + return obj; +} +function extendLanguageKeysFromLang(i18n, lang) { + const obj = createObjectFromKeys(Object.keys(i18n[lang])); + for (const key in i18n) { + i18n[key] = { ...obj, ...i18n[key] }; + } + return i18n; +} +function isKeyEmptyInAnyLanguage(i18n, key) { + if (key === '') { + return false; + } + for (const lang in i18n) { + if (i18n[lang][key] === '') { + console.log(`Key ${key} is empty in ${lang}`); + return true; + } + } + return false; +} +async function fetchTranslations(word) { + console.log(`translate ${word}`); + const response = await fetch('https://oz7q7o4tl3.execute-api.eu-west-1.amazonaws.com/', { + headers: { + Referer: 'https://translator-ui.iobroker.in/', + }, + body: JSON.stringify({ text: word, service: 'deepl', together: false }), + method: 'POST', + }); + const data = await response.json(); + return data; +} +async function updateEmptyKeysWithTranslation(i18n, lang) { + for (const key in i18n[lang]) { + if (isKeyEmptyInAnyLanguage(i18n, key)) { + const translatedKey = await fetchTranslations(i18n[lang][key]); + for (const k in translatedKey) { + if (i18n[k][key] === '') { + i18n[k][key] = translatedKey[k]; + } + } + } + } + return i18n; +} +function deleteKeys(i18n, keys) { + keys.forEach(key => { + for (const lang in i18n) { + if (lang === 'en') { + continue; + } + if (i18n[lang][key] !== undefined) { + delete i18n[lang][key]; + } + } + }); + return i18n; +} +function doDeleteKeys(args) { + console.log('start delete keys'); + if (args.length > 1) { + args[0] = args.join(','); + } + if (args.length > 0) { + let keys = args[0].split(','); + keys = keys.map(k => k.trim()); + let i18n = importi18nKeys(); + i18n = deleteKeys(i18n, keys); + exporti18nKeys(i18n); + } + console.log('end delete keys'); +} +function emptyKeys(i18n, keys) { + keys.forEach(key => { + for (const lang in i18n) { + if (lang === 'en') { + continue; + } + if (i18n[lang][key] !== undefined) { + i18n[lang][key] = ''; + } + } + }); + return i18n; +} +function doEmptyKeys(args) { + console.log('start empty keys'); + if (args.length > 1) { + args[0] = args.join(','); + } + if (args.length > 0) { + let keys = args[0].split(','); + keys = keys.map(k => k.trim()); + let i18n = importi18nKeys(); + i18n = emptyKeys(i18n, keys); + exporti18nKeys(i18n); + } + console.log('end empty keys'); +} +function emptyLang(i18n, lang) { + for (const key in i18n[lang]) { + if (i18n[lang][key] !== undefined) { + i18n[lang][key] = ''; + } + } + return i18n; +} +function doEmptyLang(args) { + console.log('start empty lang'); + if (args.length !== 1) { + console.log('Only one language is supported'); + return; + } + if (args.length > 0) { + const lang = args[0].trim(); + if (lang === 'en') { + console.log('empty of en not allowed'); + return; + } + let i18n = importi18nKeys(); + i18n = emptyLang(i18n, lang); + exporti18nKeys(i18n); + } + console.log('end empty lang'); +} +function doCleanKeys() { + console.log('start clean keys'); + const i18n = importi18nKeys(); + let languages = Object.keys(i18n); + languages = languages.filter(lang => lang !== 'en'); + for (const lang of languages) { + for (const key in i18n[lang]) { + if (i18n['en'][key] === undefined) { + delete i18n[lang][key]; + } + } + } + exporti18nKeys(i18n); + console.log('end clean keys'); +} +async function doTranslate() { + console.log('start translate'); + let i18n = importi18nKeys(); + i18n = extendLanguages(i18n); + i18n = extendLanguageKeysFromLang(i18n, 'en'); + i18n = await updateEmptyKeysWithTranslation(i18n, 'en'); + exporti18nKeys(i18n); + console.log('end translate'); +} +async function main() { + let pos; + const args = process.argv.slice(2); + pos = args.indexOf('--source'); + if (pos >= 0) { + i18npath = args[pos + 1]; + args.splice(pos, 2); + } + pos = args.indexOf('--format'); + if (pos >= 0) { + format = args[pos + 1]; + args.splice(pos, 2); + } + + if (args.length === 0) { + doTranslate(); + return; + } + if (args[0] === 'deletekey') { + args.shift(); + doDeleteKeys(args); + } + if (args[0] === 'emptykey') { + args.shift(); + doEmptyKeys(args); + } + if (args[0] === 'emptylang') { + args.shift(); + doEmptyLang(args); + } + if (args[0] === 'cleanKeys') { + args.shift(); + doCleanKeys(args); + } +} + +main(); diff --git a/eslint.config.mjs b/eslint.config.mjs new file mode 100644 index 0000000..4127ddf --- /dev/null +++ b/eslint.config.mjs @@ -0,0 +1,34 @@ +// ioBroker eslint template configuration file for js and ts files +// Please note that esm or react based modules need additional modules loaded. +import config from '@iobroker/eslint-config'; + +export default [ + ...config, + + { + // specify files to exclude from linting here + ignores: [ + 'widgets/squeezeboxrpc/js/squeezeboxrpc-dist.js', + 'widgets/squeezeboxrpc/js/date.format.js', + '.dev-server/**', + 'backup/**', + '*.test.js', + 'test/**/*.js', + '*.config.mjs', + 'build', + 'admin/build', + 'admin/words.js', + 'admin/admin.d.ts', + '**/adapter-config.d.ts' + ] + }, + + { + // you may disable some 'jsdoc' warnings - but using jsdoc is highly recommended + // as this improves maintainability. jsdoc warnings will not block buiuld process. + rules: { + // 'jsdoc/require-jsdoc': 'off', + }, + }, + +]; \ No newline at end of file diff --git a/io-package.json b/io-package.json index 6071fbe..b97c15b 100644 --- a/io-package.json +++ b/io-package.json @@ -1,138 +1,164 @@ { - "common": { - "name": "squeezeboxrpc", - "version": "1.4.0", - "news": { - "1.4.0": { - "en": "fix some missing objects errors\nsanitize more playernames in syncgroups\nadd sendTo Command \"cmdGeneral\"\nsanitize more the playername\nimprove translation\nif trackartist is avail then write to artist if empty\nimprove handling for artwork_url\nmove widget documentation from html to markdown\nadjust responsive tab style\nimprove attribute widgets\nchange TPE2 handling once more\njsonConfig add sizing options for differenz screen sizes\ntest implementation of TPE2 handling. switch in settings\nadd datapoints album_artist, track_artist, artistOriginal", - "de": "einige fehlende objektfehler beheben\nsanitize mehr spielernamen in syncgroups\nsendTo Command \"cmdGeneral\" hinzufügen\nsanitize mehr der spielername\nverbesserung der übersetzung\nwenn trackartist in anspruch genommen wird, dann schreiben sie an künstler, wenn leer\nverbesserung der handhabung für artwork_url\nwidget-dokumentation von html zu markdown verschieben\npassive tab-stil anpassen\nwidgets des attributs verbessern\ntPE2 Handling erneut ändern\njsonConfig Hinzufügen von Größenoptionen für unterschiedliche Bildschirmgrößen\ntestdurchführung des TPE2-Handlings. Schalter in Einstellungen\ndatapoints album_artist, track_artist, artistOriginal", - "ru": "исправить ошибки отсутствующих объектов\nсанитизировать больше имен игроков в синхгруппах\nдобавить команду sendTo \"cmd General\"\nобезвредить больше имя игрока\nулучшить перевод\nесли трекартист воспользуется, тогда напишите художнику, если пустой\nулучшить обработку для art_url\nпереместить документацию виджета из html в отметку\nнастроить адаптивный стиль вкладки\nулучшить виджеты атрибутов\nизменение TPE2 снова\njsonConfig добавляет варианты калибровки для различных размеров экрана\nтестовая реализация обработки TPE2. выключение в настройках\nдобавить datapoints album_artist, track_artist, ArtistOriginal", - "pt": "corrigir alguns erros de objetos ausentes\nhigienizar mais nomes de jogadores em grupos de sincronização\nadicionar sendTo Comando \"cmdGeneral\"\nhigienizar mais o nome do jogador\nmelhorar a tradução\nse trackartist é aproveitado então escreva para artista se vazio\nmelhorar o manuseio para artifício_url\nmover documentação do widget de html para marcação\najustar o estilo de guia responsivo\nmelhorar os widgets de atributos\nmudar o manuseio TPE2 mais uma vez\njsonConfig adicionar opções de dimensionamento para diferentes tamanhos de tela\nimplementação de teste de manuseio TPE2. alternar em configurações\nadd datapoints album_artist, track_artist, artistOriginal", - "nl": "enkele ontbrekende objecten fouten herstellen\nmeer spelernamen in syncgroepen verwijderen\nsendTo Command \"cmdGeneral\" toevoegen\nde naam van de speler deactiveren\nverbeteren van de vertaling\nals track artist beschikbaar is schrijf dan naar artiest indien leeg\nverbeteren van de behandeling voor artwork_url\nwidget-documentatie verplaatsen van html naar markdown\nresponsieve tabstijl aanpassen\nattribuutwidgets verbeteren\ntPE2-behandeling opnieuw wijzigen\njsonConfig voeg grootte opties toe voor verschillende schermgroottes\ntestimplementatie van TPE2-behandeling. schakelaar in instellingen\ndatapoints album_artist, track_artist, artiestOriginal toevoegen", - "fr": "correction d'erreurs d'objets manquants\ndésinfecter plus de noms de lecteur dans les groupes de synchronisation\najouter sendTo Command \"cmdGeneral\"\ndésinfecter plus le nom du joueur\naméliorer la traduction\nsi trackartist est disponible alors écrire à l'artiste si vide\naméliorer la manipulation pour artwork_url\ndéplacer la documentation de widget de html à balisage\najuster le style de l'onglet responsive\naméliorer les widgets d'attribut\nchanger la manipulation TPE2 une fois de plus\njsonConfig ajouter des options de dimensionnement pour différentes tailles d'écran\ntest de mise en œuvre de la manipulation TPE2. commutateur dans les paramètres\najouter datapoints album_artiste, track_artiste, artisteOriginal", - "it": "correggere alcuni errori di oggetti mancanti\nsanitizzare più nomi dei giocatori in gruppi di sincronizzazione\naggiungere sendTo Command \"cmdGeneral\"\nsanitizzare più il nome del giocatore\nmigliorare la traduzione\nse trackartist è avail poi scrivere all'artista se vuoto\nmigliorare la gestione per art_url\nspostare la documentazione del widget da html a markdown\nregolare stile di scheda reattiva\nmigliorare widget attributo\ncambiare la gestione di TPE2 ancora una volta\njsonConfig aggiungere opzioni di dimensionamento per diverseenz dimensioni dello schermo\nimplementazione del test di gestione TPE2. interruttore nelle impostazioni\naggiungere datapoints album_artist, track_artist, artistaOriginal", - "es": "solucionar algunos errores de objetos perdidos\nsanitize more playernames in syncgroups\nañadir sendTo Command \"cmdGeneral\"\nsanitize más el nombre de jugador\nmejorar la traducción\nsi el trackartista está disponible entonces escriba al artista si está vacío\nmejorar el manejo de artwork_url\nmover la documentación del widget de html a marcadown\najuste estilo de pestaña sensible\nmejorar widgets de atributo\ncambio de manejo TPE2 una vez más\njsonConfig añadir opciones de tamaño para diferentes tamaños de pantalla\naplicación de pruebas de manipulación TPE2. conmutar la configuración\nañadir datapoints album_artist, track_artist, artistOriginal", - "pl": "naprawić kilka brakujących błędów obiektów\nsanitize więcej nazw graczy w zespołach\ndodaj polecenie sendTo \"cmdGeneral\"\nsanitize more the playername\npoprawić tłumaczenie\njeśli trackartist jest użyteczny, napisz do artysty jeśli pusty\npoprawa obsługi grafiki _ url\nprzenieść dokumentację widget z html do markdown\ndopasowanie odpowiadającego stylu karty\npoprawić widżety atrybutów\nzmienić obsługę TPE2 jeszcze raz\njsonConfig dodać opcje rozmiaru dla różnej wielkości ekranu\ntestowa realizacja obsługi TPE2. przełączanie w ustawieniach\nadd datapoints album _ artist, track _ artist, artistOriginal", - "uk": "виправити деякі помилки відсутніх об'єктів\nдезінфікувати більше назв гравців у синкгрупах\nдодати команду \"Команда\"\nсатизувати більше ім'я гравця\nполіпшення перекладу\nякщо трактист - аваїл, то напишіть художника, якщо порожня\nпокращити роботу для арт-роботи_url\nперемістити документацію віджету з html для розмітки\nрегулюйте чуйний стиль вкладки\nполіпшення віджетів атрибутів\nзміна обробки TPE2 ще раз\njsonConfig add sizing options для різних розмірів екрана\nтестування впровадження TPE2 обробки. Перемикач в налаштуваннях\nдодавати альбоми/артист, трек_artist, художникOriginal", - "zh-cn": "修正一些缺失的对象错误\n在同步组中消毒更多玩家名\n添加发送到命令“ cmdGeneral ”\n更多消毒玩家名称\n改进翻译\n如果 trackartist 有用, 如果空则写给艺人\n改进对艺术品的处理_ url\n将部件文档从 html 移动到下标记\n调整响应标签样式\n改进属性部件\n再次更改TPE2处理\njsonConfig 添加不同屏幕大小的大小选项\n试验实施TPE2处理。 在设置中切换\n添加数据点相册_艺人,音轨_艺人,艺人" - }, - "1.3.17": { - "en": "add edit button to the vie index field of favorites widget", - "de": "fügen sie edit-taste in das vie-index-feld der favoriten widget", - "ru": "добавить редактирующую кнопку в поле индекса vie", - "pt": "adicionar botão de edição para o campo de índice vie de widget favorito", - "nl": "bewerken knop toevoegen aan het vie index veld van favorieten widget", - "fr": "ajouter le bouton d'édition au champ d'index de vie des favoris widget", - "it": "aggiungere il pulsante di modifica al campo indice vie di widget preferiti", - "es": "añadir botón de edición al campo índice vie de favoritos widget", - "pl": "dodaj przycisk edycji do pola indeksu vie ulubionych widget", - "uk": "додати кнопку редагування в vie index поле улюблених віджетів", - "zh-cn": "在最爱部件的vie索引字段中添加编辑按钮" - } - }, - "titleLang": { - "en": "SqueezeboxRPC: Controller for Logitech media Server over RPC-protokol", - "de": "SqueezeboxRPC: Controller für Logitech Media Server über RPC-Protokoll", - "ru": "SqueezeboxRPC: контроллер для медиасервера Logitech по протоколу RPC", - "pt": "SqueezeboxRPC: Controlador para Logitech Media Server sobre protocolo RPC", - "nl": "SqueezeboxRPC: Controller voor Logitech-mediaserver via RPC-protocol", - "fr": "SqueezeboxRPC : contrôleur pour serveur multimédia Logitech via protocole RPC", - "it": "SqueezeboxRPC: controller per Logitech media Server su protocollo RPC", - "es": "SqueezeboxRPC: Controlador para servidor de medios Logitech sobre protocolo RPC", - "pl": "SqueezeboxRPC: Kontroler serwera multimediów Logitech poprzez protokół RPC", - "uk": "SqueezeboxRPC: контролер для медіасервера Logitech через RPC-протокол", - "zh-cn": "SqueezeboxRPC:基于 RPC 协议的 Logitech 媒体服务器控制器" - }, - "desc": { - "en": "SqueezeboxRPC: Controller for Logitech media Server over RPC-protokol", - "de": "SqueezeboxRPC: Controller for Logitech media Server over RPC-protokol", - "ru": "SqueezeboxRPC: Controller for Logitech media Server over RPC-protokol", - "pt": "SqueezeboxRPC: Controller for Logitech media Server over RPC-protokol", - "nl": "SqueezeboxRPC: Controller for Logitech media Server over RPC-protokol", - "fr": "SqueezeboxRPC: Controller for Logitech media Server over RPC-protokol", - "it": "SqueezeboxRPC: Controller for Logitech media Server over RPC-protokol", - "es": "SqueezeboxRPC: Controller for Logitech media Server over RPC-protokol", - "pl": "SqueezeboxRPC: Controller for Logitech media Server over RPC-protokol", - "zh-cn": "SqueezeboxRPC: Controller for Logitech media Server over RPC-protokol", - "uk": "SqueezeboxRPC: контролер для медіасервера Logitech через RPC-протокол" - }, - "authors": ["oweitman "], - "keywords": ["squeezebox", "LMS", "Logitech Media Server", "music server"], - "licenseInformation": { - "type": "free", - "license": "MIT" - }, - "platform": "Javascript/Node.js", - "icon": "squeezeboxrpc.png", - "enabled": true, - "extIcon": "https://raw.githubusercontent.com/oweitman/ioBroker.squeezeboxrpc/main/admin/squeezeboxrpc.png", - "readme": "https://github.com/oweitman/ioBroker.squeezeboxrpc/blob/main/README.md", - "loglevel": "info", - "tier": 3, - "restartAdapters": ["vis"], - "mode": "daemon", - "type": "multimedia", - "compact": true, - "connectionType": "local", - "dataSource": "poll", - "messagebox": true, - "adminUI": { - "config": "json" - }, - "dependencies": [ - { - "js-controller": ">=5.0.19" - } - ], - "globalDependencies": [ - { - "admin": ">=5.1.13" - } - ] + "common": { + "name": "squeezeboxrpc", + "version": "1.5.0", + "news": { + "1.5.0": { + "en": "Umstellung auf iobroker/eslint\nNeues Widget playlist", + "de": "Anpassung an iobroker/eslint\nNeues Widget Playlist", + "ru": "Umstellung auf iobroker/eslint\nNeues Widget playlist", + "pt": "Umstellung auf iobroker/eslint\nLista de reprodução de Neues Widget", + "nl": "Umstellung auf iobroker/eslint\nNeues widget-afspeellijst", + "fr": "Umstellung auf iobroker/ester\nListe de lecture Neues Widget", + "it": "Impiegazione\nPlaylist di Neues Widget", + "es": "Umstellung auf iobroker/eslint\nNeues Widget lista de reproducción", + "pl": "Umstellung auf iobroker / eslint\nNeues Widget playlist", + "uk": "Umstellung auf iobroker/eslint\nNeues Widget список відтворення", + "zh-cn": "Umstellung auf io 经纪人/雇员\nNeues 部件播放列表" + }, + "1.4.0": { + "en": "fix some missing objects errors\nsanitize more playernames in syncgroups\nadd sendTo Command \"cmdGeneral\"\nsanitize more the playername\nimprove translation\nif trackartist is avail then write to artist if empty\nimprove handling for artwork_url\nmove widget documentation from html to markdown\nadjust responsive tab style\nimprove attribute widgets\nchange TPE2 handling once more\njsonConfig add sizing options for differenz screen sizes\ntest implementation of TPE2 handling. switch in settings\nadd datapoints album_artist, track_artist, artistOriginal", + "de": "einige fehlende objektfehler beheben\nsanitize mehr spielernamen in syncgroups\nsendTo Command \"cmdGeneral\" hinzufügen\nsanitize mehr der spielername\nverbesserung der übersetzung\nwenn trackartist in anspruch genommen wird, dann schreiben sie an künstler, wenn leer\nverbesserung der handhabung für artwork_url\nwidget-dokumentation von html zu markdown verschieben\npassive tab-stil anpassen\nwidgets des attributs verbessern\ntPE2 Handling erneut ändern\njsonConfig Hinzufügen von Größenoptionen für unterschiedliche Bildschirmgrößen\ntestdurchführung des TPE2-Handlings. Schalter in Einstellungen\ndatapoints album_artist, track_artist, artistOriginal", + "ru": "исправить ошибки отсутствующих объектов\nсанитизировать больше имен игроков в синхгруппах\nдобавить команду sendTo \"cmd General\"\nобезвредить больше имя игрока\nулучшить перевод\nесли трекартист воспользуется, тогда напишите художнику, если пустой\nулучшить обработку для art_url\nпереместить документацию виджета из html в отметку\nнастроить адаптивный стиль вкладки\nулучшить виджеты атрибутов\nизменение TPE2 снова\njsonConfig добавляет варианты калибровки для различных размеров экрана\nтестовая реализация обработки TPE2. выключение в настройках\nдобавить datapoints album_artist, track_artist, ArtistOriginal", + "pt": "corrigir alguns erros de objetos ausentes\nhigienizar mais nomes de jogadores em grupos de sincronização\nadicionar sendTo Comando \"cmdGeneral\"\nhigienizar mais o nome do jogador\nmelhorar a tradução\nse trackartist é aproveitado então escreva para artista se vazio\nmelhorar o manuseio para artifício_url\nmover documentação do widget de html para marcação\najustar o estilo de guia responsivo\nmelhorar os widgets de atributos\nmudar o manuseio TPE2 mais uma vez\njsonConfig adicionar opções de dimensionamento para diferentes tamanhos de tela\nimplementação de teste de manuseio TPE2. alternar em configurações\nadd datapoints album_artist, track_artist, artistOriginal", + "nl": "enkele ontbrekende objecten fouten herstellen\nmeer spelernamen in syncgroepen verwijderen\nsendTo Command \"cmdGeneral\" toevoegen\nde naam van de speler deactiveren\nverbeteren van de vertaling\nals track artist beschikbaar is schrijf dan naar artiest indien leeg\nverbeteren van de behandeling voor artwork_url\nwidget-documentatie verplaatsen van html naar markdown\nresponsieve tabstijl aanpassen\nattribuutwidgets verbeteren\ntPE2-behandeling opnieuw wijzigen\njsonConfig voeg grootte opties toe voor verschillende schermgroottes\ntestimplementatie van TPE2-behandeling. schakelaar in instellingen\ndatapoints album_artist, track_artist, artiestOriginal toevoegen", + "fr": "correction d'erreurs d'objets manquants\ndésinfecter plus de noms de lecteur dans les groupes de synchronisation\najouter sendTo Command \"cmdGeneral\"\ndésinfecter plus le nom du joueur\naméliorer la traduction\nsi trackartist est disponible alors écrire à l'artiste si vide\naméliorer la manipulation pour artwork_url\ndéplacer la documentation de widget de html à balisage\najuster le style de l'onglet responsive\naméliorer les widgets d'attribut\nchanger la manipulation TPE2 une fois de plus\njsonConfig ajouter des options de dimensionnement pour différentes tailles d'écran\ntest de mise en œuvre de la manipulation TPE2. commutateur dans les paramètres\najouter datapoints album_artiste, track_artiste, artisteOriginal", + "it": "correggere alcuni errori di oggetti mancanti\nsanitizzare più nomi dei giocatori in gruppi di sincronizzazione\naggiungere sendTo Command \"cmdGeneral\"\nsanitizzare più il nome del giocatore\nmigliorare la traduzione\nse trackartist è avail poi scrivere all'artista se vuoto\nmigliorare la gestione per art_url\nspostare la documentazione del widget da html a markdown\nregolare stile di scheda reattiva\nmigliorare widget attributo\ncambiare la gestione di TPE2 ancora una volta\njsonConfig aggiungere opzioni di dimensionamento per diverseenz dimensioni dello schermo\nimplementazione del test di gestione TPE2. interruttore nelle impostazioni\naggiungere datapoints album_artist, track_artist, artistaOriginal", + "es": "solucionar algunos errores de objetos perdidos\nsanitize more playernames in syncgroups\nañadir sendTo Command \"cmdGeneral\"\nsanitize más el nombre de jugador\nmejorar la traducción\nsi el trackartista está disponible entonces escriba al artista si está vacío\nmejorar el manejo de artwork_url\nmover la documentación del widget de html a marcadown\najuste estilo de pestaña sensible\nmejorar widgets de atributo\ncambio de manejo TPE2 una vez más\njsonConfig añadir opciones de tamaño para diferentes tamaños de pantalla\naplicación de pruebas de manipulación TPE2. conmutar la configuración\nañadir datapoints album_artist, track_artist, artistOriginal", + "pl": "naprawić kilka brakujących błędów obiektów\nsanitize więcej nazw graczy w zespołach\ndodaj polecenie sendTo \"cmdGeneral\"\nsanitize more the playername\npoprawić tłumaczenie\njeśli trackartist jest użyteczny, napisz do artysty jeśli pusty\npoprawa obsługi grafiki _ url\nprzenieść dokumentację widget z html do markdown\ndopasowanie odpowiadającego stylu karty\npoprawić widżety atrybutów\nzmienić obsługę TPE2 jeszcze raz\njsonConfig dodać opcje rozmiaru dla różnej wielkości ekranu\ntestowa realizacja obsługi TPE2. przełączanie w ustawieniach\nadd datapoints album _ artist, track _ artist, artistOriginal", + "uk": "виправити деякі помилки відсутніх об'єктів\nдезінфікувати більше назв гравців у синкгрупах\nдодати команду \"Команда\"\nсатизувати більше ім'я гравця\nполіпшення перекладу\nякщо трактист - аваїл, то напишіть художника, якщо порожня\nпокращити роботу для арт-роботи_url\nперемістити документацію віджету з html для розмітки\nрегулюйте чуйний стиль вкладки\nполіпшення віджетів атрибутів\nзміна обробки TPE2 ще раз\njsonConfig add sizing options для різних розмірів екрана\nтестування впровадження TPE2 обробки. Перемикач в налаштуваннях\nдодавати альбоми/артист, трек_artist, художникOriginal", + "zh-cn": "修正一些缺失的对象错误\n在同步组中消毒更多玩家名\n添加发送到命令“ cmdGeneral ”\n更多消毒玩家名称\n改进翻译\n如果 trackartist 有用, 如果空则写给艺人\n改进对艺术品的处理_ url\n将部件文档从 html 移动到下标记\n调整响应标签样式\n改进属性部件\n再次更改TPE2处理\njsonConfig 添加不同屏幕大小的大小选项\n试验实施TPE2处理。 在设置中切换\n添加数据点相册_艺人,音轨_艺人,艺人" + }, + "1.3.17": { + "en": "add edit button to the vie index field of favorites widget", + "de": "fügen sie edit-taste in das vie-index-feld der favoriten widget", + "ru": "добавить редактирующую кнопку в поле индекса vie", + "pt": "adicionar botão de edição para o campo de índice vie de widget favorito", + "nl": "bewerken knop toevoegen aan het vie index veld van favorieten widget", + "fr": "ajouter le bouton d'édition au champ d'index de vie des favoris widget", + "it": "aggiungere il pulsante di modifica al campo indice vie di widget preferiti", + "es": "añadir botón de edición al campo índice vie de favoritos widget", + "pl": "dodaj przycisk edycji do pola indeksu vie ulubionych widget", + "uk": "додати кнопку редагування в vie index поле улюблених віджетів", + "zh-cn": "在最爱部件的vie索引字段中添加编辑按钮" + } }, - "encryptedNative": ["password"], - "protectedNative": ["password"], - "native": { - "server": "0.0.0.0", - "port": 9000, - "telnetport": 9090, - "username": "", - "password": "", - "serverrefresh": 30, - "playerrefresh": 950, - "favoriterefresh": 720, - "discoveryrefresh": 30, - "useplaylist": true, - "usediscovery": true, - "usetelnet": false, - "usefavorites": true, - "outputserverdebug": false, - "outputserversilly": false, - "outputplayerdebug": false, - "outputplayersilly": false + "titleLang": { + "en": "SqueezeboxRPC: Controller for Logitech media Server over RPC-protokol", + "de": "SqueezeboxRPC: Controller für Logitech Media Server über RPC-Protokoll", + "ru": "SqueezeboxRPC: контроллер для медиасервера Logitech по протоколу RPC", + "pt": "SqueezeboxRPC: Controlador para Logitech Media Server sobre protocolo RPC", + "nl": "SqueezeboxRPC: Controller voor Logitech-mediaserver via RPC-protocol", + "fr": "SqueezeboxRPC : contrôleur pour serveur multimédia Logitech via protocole RPC", + "it": "SqueezeboxRPC: controller per Logitech media Server su protocollo RPC", + "es": "SqueezeboxRPC: Controlador para servidor de medios Logitech sobre protocolo RPC", + "pl": "SqueezeboxRPC: Kontroler serwera multimediów Logitech poprzez protokół RPC", + "uk": "SqueezeboxRPC: контролер для медіасервера Logitech через RPC-протокол", + "zh-cn": "SqueezeboxRPC:基于 RPC 协议的 Logitech 媒体服务器控制器" }, - "objects": [], - "instanceObjects": [ - { - "_id": "info", - "type": "channel", - "common": { - "name": "Information" - }, - "native": {} - }, - { - "_id": "info.connection", - "type": "state", - "common": { - "role": "indicator.connected", - "name": "Device or service connected", - "type": "boolean", - "read": true, - "write": false, - "def": false - }, - "native": {} - } + "desc": { + "en": "SqueezeboxRPC: Controller for Logitech media Server over RPC-protokol", + "de": "SqueezeboxRPC: Controller for Logitech media Server over RPC-protokol", + "ru": "SqueezeboxRPC: Controller for Logitech media Server over RPC-protokol", + "pt": "SqueezeboxRPC: Controller for Logitech media Server over RPC-protokol", + "nl": "SqueezeboxRPC: Controller for Logitech media Server over RPC-protokol", + "fr": "SqueezeboxRPC: Controller for Logitech media Server over RPC-protokol", + "it": "SqueezeboxRPC: Controller for Logitech media Server over RPC-protokol", + "es": "SqueezeboxRPC: Controller for Logitech media Server over RPC-protokol", + "pl": "SqueezeboxRPC: Controller for Logitech media Server over RPC-protokol", + "zh-cn": "SqueezeboxRPC: Controller for Logitech media Server over RPC-protokol", + "uk": "SqueezeboxRPC: контролер для медіасервера Logitech через RPC-протокол" + }, + "authors": [ + "oweitman " + ], + "keywords": [ + "squeezebox", + "LMS", + "Logitech Media Server", + "music server" + ], + "licenseInformation": { + "type": "free", + "license": "MIT" + }, + "platform": "Javascript/Node.js", + "icon": "squeezeboxrpc.png", + "enabled": true, + "extIcon": "https://raw.githubusercontent.com/oweitman/ioBroker.squeezeboxrpc/main/admin/squeezeboxrpc.png", + "readme": "https://github.com/oweitman/ioBroker.squeezeboxrpc/blob/main/README.md", + "loglevel": "info", + "tier": 3, + "restartAdapters": [ + "vis" + ], + "mode": "daemon", + "type": "multimedia", + "compact": true, + "connectionType": "local", + "dataSource": "poll", + "messagebox": true, + "adminUI": { + "config": "json" + }, + "dependencies": [ + { + "js-controller": ">=5.0.19" + } + ], + "globalDependencies": [ + { + "admin": ">=5.1.13" + } ] + }, + "encryptedNative": [ + "password" + ], + "protectedNative": [ + "password" + ], + "native": { + "server": "0.0.0.0", + "port": 9000, + "telnetport": 9090, + "username": "", + "password": "", + "serverrefresh": 30, + "playerrefresh": 950, + "favoriterefresh": 720, + "discoveryrefresh": 30, + "useplaylist": true, + "usediscovery": true, + "usetelnet": false, + "usefavorites": true, + "outputserverdebug": false, + "outputserversilly": false, + "outputplayerdebug": false, + "outputplayersilly": false + }, + "objects": [], + "instanceObjects": [ + { + "_id": "info", + "type": "channel", + "common": { + "name": "Information" + }, + "native": {} + }, + { + "_id": "info.connection", + "type": "state", + "common": { + "role": "indicator.connected", + "name": "Device or service connected", + "type": "boolean", + "read": true, + "write": false, + "def": false + }, + "native": {} + } + ] } diff --git a/lib/ioUtil.js b/lib/ioUtil.js new file mode 100644 index 0000000..6486435 --- /dev/null +++ b/lib/ioUtil.js @@ -0,0 +1,310 @@ +/** + * class with util function for object, state and log access + */ +class ioUtil { + /** + * The constructor for the ioUtil class + * + * @param adapter - the iobroker adapter + * @param islogdebug - flag for debug logging + * @param islogsilly - flag for verbose logging + */ + constructor(adapter, islogdebug, islogsilly) { + this.adapter = adapter; + this.islogsilly = islogsilly; + this.islogdebug = islogdebug; + this.observers = []; + this.doClose = false; + } + /** + * Asynchronously creates a channel object in the ioBroker system. + * + * @param stateTemplate - The template containing the common properties of the channel. + * @param level1path - The first level path to prepend to the channel name, can be null or empty. + * @param level2path - The second level path to prepend to the channel name, can be null or empty. + * @returns A promise that resolves when the channel object is set in the adapter. + */ + createObjectChannelAsync(stateTemplate, level1path, level2path) { + this.logdebug(`createObject ${stateTemplate.name}`); + const name = (level1path ? `${level1path}.` : '') + (level2path ? `${level2path}.` : '') + stateTemplate.name; + const newobj = { + type: 'channel', + common: stateTemplate, + native: {}, + }; + return this.adapter.setObjectAsync(name, newobj); + } + /** + * Asynchronously creates a state object in the ioBroker system. + * + * @param stateTemplate - The template containing the common properties of the state. + * @param level1path - The first level path to prepend to the state name, can be null or empty. + * @param level2path - The second level path to prepend to the state name, can be null or empty. + * @returns A promise that resolves when the state object is set in the adapter. + */ + createObjectAsync(stateTemplate, level1path, level2path) { + this.logdebug(`createObject ${stateTemplate.name}`); + const name = (level1path ? `${level1path}.` : '') + (level2path ? `${level2path}.` : '') + stateTemplate.name; + const newobj = { + type: 'state', + common: stateTemplate, + native: {}, + }; + return this.adapter.setObjectAsync(name, newobj); + } + /** + * Creates a state object in the ioBroker system if it does not already exist. + * + * @param stateTemplate - The template containing the common properties of the state. + * @param level1path - The first level path to prepend to the state name, can be null or empty. + * @param level2path - The second level path to prepend to the state name, can be null or empty. + * @param callback - Optional callback function, called when the object is created. + */ + createObjectState(stateTemplate, level1path, level2path, callback) { + this.logdebug(`createObject ${stateTemplate.name}`); + const name = (level1path ? `${level1path}.` : '') + (level2path ? `${level2path}.` : '') + stateTemplate.name; + this.adapter.getObject(name, (err, obj) => { + const newobj = { + type: 'state', + common: stateTemplate, + native: {}, + }; + if (!obj) { + callback ? this.adapter.setObject(name, newobj, callback) : this.adapter.setObject(name, newobj); + } else { + if (callback) { + callback(); + } + } + }); + } + /** + * Asynchronously deletes an object in the ioBroker system. + * + * @param id - The name of the object to delete. + * @param level1path - The first level path to prepend to the object name, can be null or empty. + * @param level2path - The second level path to prepend to the object name, can be null or empty. + * @returns A promise that resolves when the object is deleted. + */ + deleteObjectAsync(id, level1path, level2path) { + this.logdebug(`deleteObject ${id}`); + const name = (level1path ? `${level1path}.` : '') + (level2path ? `${level2path}.` : '') + id; + return this.adapter.delObject(name, { recursive: true }); + } + /** + * Deletes an object in the ioBroker system. + * + * @param id - The name of the object to delete. + * @param level1path - The first level path to prepend to the object name, can be null or empty. + * @param level2path - The second level path to prepend to the object name, can be null or empty. + * @param callback - Optional callback function, called when the object is deleted. + */ + deleteObject(id, level1path, level2path, callback) { + this.logdebug(`deleteObject ${id}`); + const name = (level1path ? `${level1path}.` : '') + (level2path ? `${level2path}.` : '') + id; + this.adapter.delObject(name, callback); + } + /** + * Asynchronously sets the state of an object in the ioBroker system. + * + * @param name - The name of the state to set. + * @param value - The value to set the state to. + * @param level1path - The first level path to prepend to the state name, can be null or empty. + * @param level2path - The second level path to prepend to the state name, can be null or empty. + * @returns A promise that resolves when the state is set in the adapter. + */ + setStateAsync(name, value, level1path, level2path) { + this.logdebug(`setState ${name}`); + name = (level1path ? `${level1path}.` : '') + (level2path ? `${level2path}.` : '') + name; + return this.adapter.setState(name, value, true); // jshint ignore:line + } + /** + * Sets the state of an object in the ioBroker system, with ack=false. + * + * @param name - The name of the state to set. + * @param value - The value to set the state to. + * @param level1path - The first level path to prepend to the state name, can be null or empty. + * @param level2path - The second level path to prepend to the state name, can be null or empty. + * @param callback - Optional callback function, called when the state is set in the adapter, but without ack. + */ + setStateNack(name, value, level1path, level2path, callback) { + this.logdebug(`setState ${name} ${JSON.stringify(value)}`); + name = (level1path ? `${level1path}.` : '') + (level2path ? `${level2path}.` : '') + name; + callback ? this.adapter.setState(name, value, false, callback) : this.adapter.setState(name, value, true); + } + /** + * Gets all objects in the given path. + * + * @param path - The path to retrieve objects from. + * @returns A promise that resolves with an object containing all retrieved objects. + */ + async getObjects(path) { + const key = `${this.adapter.namespace}.${path}`; + return convertObjects(await this.adapter.getObjectListAsync({ startkey: key, endkey: `${key}\u9999` })); + } + + /** + * Gets all states matching the given pattern in the given path. + * + * @param pattern - The pattern to match the state names with. + * @param level1path - The first level path to prepend to the state name, can be null or empty. + * @param level2path - The second level path to prepend to the state name, can be null or empty. + * @returns A promise that resolves with an object containing all retrieved states. + */ + async getStates(pattern, level1path, level2path) { + this.logdebug(`getStates ${pattern} ${level1path || ''} ${level2path || ''}`); + const name = (level1path ? `${level1path}.` : '') + (level2path ? `${level2path}.` : '') + pattern; + return await this.adapter.getStatesAsync(name); + } + /** + * Retrieves the state of an object in the ioBroker system. + * + * @param id - The name of the state to retrieve. + * @param level1path - The first level path to prepend to the state name, can be null or empty. + * @param level2path - The second level path to prepend to the state name, can be null or empty. + * @param callback - The callback function to call when the state is retrieved. + */ + getState(id, level1path = false, level2path = false, callback) { + this.logdebug(`getState ${id}`); + const name = (level1path ? `${level1path}.` : '') + (level2path ? `${level2path}.` : '') + id; + this.adapter.getState(name, callback); + } + /** + * Creates a state object in the ioBroker system. + * + * @param stateTemplate - The template containing the common properties of the state. + * @param level1path - The first level path to prepend to the state name, can be null or empty. + * @param level2path - The second level path to prepend to the state name, can be null or empty. + * @param callback - Optional callback function, called when the state is created. + */ + createState(stateTemplate, level1path = false, level2path = false, callback) { + this.logdebug(`createState ${stateTemplate.name}`); + const name = (level1path ? `${level1path}.` : '') + (level2path ? `${level2path}.` : '') + stateTemplate.name; + this.logdebug(`Create Key ${name}`); + this.adapter.createState(level1path, level2path, stateTemplate.name, stateTemplate, callback); + } + /** + * Sets the state of an object in the ioBroker system. + * + * @param name - The name of the state to set. + * @param value - The value to set the state to. + * @param level1path - The first level path to prepend to the state name, can be null or empty. + * @param level2path - The second level path to prepend to the state name, can be null or empty. + * @param [callback] - Optional callback function, called when the state is set in the adapter. + */ + setState(name, value, level1path, level2path, callback) { + this.logdebug(`setState ${name}: ${value}`); + name = (level1path ? `${level1path}.` : '') + (level2path ? `${level2path}.` : '') + name; + callback ? this.adapter.setState(name, value, true, callback) : this.adapter.setState(name, value, true); // jshint ignore:line + } + /** + * Sets a timeout for a specified callback function and stores it in the observers object. + * + * @param id - Unique identifier for the timeout, used to manage and clear the timeout. + * @param callback - The function to execute after the specified time delay. + * @param time - The delay in milliseconds before executing the callback. + * @param arg1 - Optional first argument to pass to the callback function. + * @param arg2 - Optional second argument to pass to the callback function. + */ + setTimeout(id, callback, time, arg1 = null, arg2 = null) { + this.logsilly(`setTimeout ${id}`); + if (this.doClose) { + return; + } + this.clearTimeout(id); + this.observers[id] = setTimeout(callback.bind(this), time, arg1, arg2); + } + /** + * Clears a timeout that was previously set using setTimeout. + * + * @param id - Unique identifier for the timeout to clear. + */ + clearTimeout(id) { + this.logsilly(`clearTimeout ${id}`); + if (this.observers[id]) { + clearTimeout(this.observers[id]); + } + delete this.observers[id]; + } + /** + * Clears an interval that was previously set using setInterval. + * + * @param id - Unique identifier for the interval to clear. + */ + clearInterval(id) { + this.logsilly(`clearInterval ${id}`); + if (this.observers[id]) { + clearInterval(this.observers[id]); + } + delete this.observers[id]; + } + /** + * Clears all timeouts and intervals that were previously set using setTimeout and setInterval. + * + * This is a convenience method to clear all observers at once. + */ + deleteObservers() { + this.logdebug('deleteObservers'); + Object.keys(this.observers).map(i => this.clearTimeout(i)); + } + /** + * Closes all active connections by logging the action, deleting observers, + * and setting the doClose flag to true. + */ + closeConnections() { + this.logdebug('closeConnections'); + this.deleteObservers(); + this.doClose = true; + } + /** + * Logs a message at the silly log level if the adapter is configured for it. + * + * @param s - The message to log. + */ + logsilly(s) { + if (this.islogsilly) { + this.adapter.log.silly(s); + } + } + /** + * Logs a message at the debug log level if the adapter is configured for it. + * + * @param s - The message to log. + */ + logdebug(s) { + if (this.islogdebug) { + this.adapter.log.debug(s); + } + } + /** + * Logs a message at the error log level. + * + * @param s - The message to log. + */ + logerror(s) { + this.adapter.log.error(s); + } + /** + * Logs a message at the info log level. + * + * @param s - The message to log. + */ + loginfo(s) { + this.adapter.log.info(s); + } +} +/** + * Converts an object list returned by getObjects or getObjectListAsync into a normal object with id as key. + * + * @param objects - The object list to convert. + * @returns The converted object. + */ +function convertObjects(objects) { + return objects.rows.reduce((acc, obj) => { + acc[obj.id] = obj; + return acc; + }, {}); +} +module.exports = { + ioUtil, +}; diff --git a/lib/iosbplayer.js b/lib/iosbplayer.js index 2a8a26f..160b89e 100755 --- a/lib/iosbplayer.js +++ b/lib/iosbplayer.js @@ -1,922 +1,1217 @@ -"use strict"; - -function ioSBPlayer(server, playerdata) { - - this.server = server; - this.adapter = server.adapter; - - this.fullStatus = "tags:cgABbehldiqtyrSuoKLNJ"; - this.smallStatus = "tags:uB"; - this.statePath = this.server.PlayersStatePath; - this.btnStatePath = "Buttons"; - this.currentStates = {}; - this.observers = []; - this.playername = ""; - this.playerid = ""; - this.playerindex = 0; - this.remote = 0; - this.statuscounter = 0; - this.connected = 0; - - this.log = {}; - - this.islogsilly = this.adapter.config.outputplayersilly; - this.islogdebug = this.adapter.config.outputplayerdebug; - this.TPE2Handling = 1; - - this.FORBIDDEN_CHARS = /[^\d\w_]+/gm; - //test the regex: https://regex101.com/r/Ed0WhH/1 - - this.sbPlayerStatusMain = { - "player_name": { - name: "Playername", - read: true, - write: false, - type: "string", - role: "info.name", - exist: false - }, - "playerid": { - name: "PlayerID", - read: true, - write: false, - type: "string", - role: "info.name", - exist: false - }, - "player_connected": { - name: "Connected", - read: true, - write: false, - type: "number", - role: "value", - exist: false - }, - "player_ip": { - name: "IP", - read: true, - write: false, - type: "string", - role: "info.ip", - exist: false - }, - "power": { - name: "Power", - read: true, - write: true, - type: "number", - role: "switch", - exist: false, - min: 0, - max: 1 - }, - "mode": { - name: "Mode", - read: true, - write: false, - type: "string", - role: "media.state", - exist: false - }, - "time": { - name: "Time", - read: true, - write: false, - type: "number", - role: "media.elapsed", - exist: false - }, - "rate": { - name: "Rate", - read: true, - write: false, - type: "number", - role: "value", - exist: false - }, - "sync_slaves": { - name: "SyncSlaves", - read: true, - write: false, - type: "string", - role: "value", - exist: false - }, - "sync_master": { - name: "SyncMaster", - read: true, - write: false, - type: "string", - role: "value", - exist: false - }, - "mixer volume": { - name: "Volume", - read: true, - write: true, - type: "number", - role: "level.volume", - exist: false, - min: 0, - max: 100 - }, - "playlist repeat": { - name: "PlaylistRepeat", - read: true, - write: true, - type: "number", - role: "media.mode.repeat", - exist: false - }, - "playlist shuffle": { - name: "PlaylistShuffle", - read: true, - write: true, - type: "number", - role: "media.mode.shuffle", - exist: false - }, - "remote": { - name: "Remote", - read: true, - write: false, - type: "number", - role: "value", - exist: false - }, - "playlist": { - name: "Playlist", - read: true, - write: false, - type: "string", - role: "value", - exist: false - }, - "playlist_cur_index": { - name: "PlaylistCurrentIndex", - read: true, - write: true, - type: "string", - role: "value", - exist: false - }, - "alarms": { - name: "Alarms", - read: true, - write: false, - type: "string", - role: "value", - exist: false - }, - "state": { - name: "state", - read: true, - write: true, - type: "number", - role: "media.state", - exist: false, - min: 0, - max: 2 - } - }; - - this.sbPlayerStatusLoop = { - "duration": { - name: "Duration", - read: true, - write: false, - type: "number", - role: "media.duration", - exist: false - }, - "artwork_url": { - name: "ArtworkUrl", - read: true, - write: false, - type: "string", - role: "media.cover", - exist: false - }, - "bitrate": { - name: "Bitrate", - read: true, - write: false, - type: "string", - role: "media.bitrate", - exist: false - }, - "album": { - name: "Album", - read: true, - write: false, - type: "string", - role: "media.album", - exist: false - }, - "coverid": { - name: "ArtworkUrl", - read: true, - write: false, - type: "string", - role: "value", - exist: true - }, - "genre": { - name: "Genre", - read: true, - write: false, - type: "string", - role: "media.genre", - exist: false - }, - "type": { - name: "Type", - read: true, - write: false, - type: "string", - role: "value", - exist: false - }, - "title": { - name: "Title", - read: true, - write: false, - type: "string", - role: "media.title", - exist: false - }, - "artist": { - name: "Artist", - read: true, - write: false, - type: "string", - role: "media.artist", - exist: false - }, - "albumartist": { - name: "Albumartist", - read: true, - write: false, - type: "string", - role: "media.artist", - exist: false - }, - "trackartist": { - name: "Trackartist", - read: true, - write: false, - type: "string", - role: "media.artist", - exist: false - }, - "band": { - name: "Band", - read: true, - write: false, - type: "string", - role: "media.artist", - exist: false - }, "url": { - name: "Url", - read: true, - write: false, - type: "string", - role: "media.url", - exist: false - }, - "remote_title": { - name: "RadioName", - read: true, - write: false, - type: "string", - role: "value", - exist: false - } - }; - this.sbPlayerButtons = { - "cmdPlayFavorite": { - name: "cmdPlayFavorite", - read: true, - write: true, - type: "string", - role: "state", - exist: false, - def: " " - }, - "cmdPlayUrl": { - name: "cmdPlayUrl", - read: true, - write: true, - type: "string", - role: "state", - exist: false, - def: " " - }, - "cmdGeneral": { - name: "cmdGeneral", - read: true, - write: true, - type: "string", - role: "state", - exist: false, - def: " " - }, - "cmdGoTime": { - name: "cmdGoTime", - read: true, - write: true, - type: "string", - role: "state", - exist: false, - def: " " - }, - "forward": { - name: "btnForward", - read: true, - write: true, - type: "boolean", - role: "button.forward", - exist: false, - def: false - }, - "preset_1": { - name: "btnPreset_1", - read: true, - write: true, - type: "boolean", - role: "button", - exist: false, - def: false - }, - "preset_2": { - name: "btnPreset_2", - read: true, - write: true, - type: "boolean", - role: "button", - exist: false, - def: false - }, - "preset_3": { - name: "btnPreset_3", - read: true, - write: true, - type: "boolean", - role: "button", - exist: false, - def: false - }, - "preset_4": { - name: "btnPreset_4", - read: true, - write: true, - type: "boolean", - role: "button", - exist: false, - def: false - }, - "preset_5": { - name: "btnPreset_5", - read: true, - write: true, - type: "boolean", - role: "button", - exist: false, - def: false - }, - "preset_6": { - name: "btnPreset_6", - read: true, - write: true, - type: "boolean", - role: "button", - exist: false, - def: false - }, - "rewind": { - name: "btnRewind", - read: true, - write: true, - type: "boolean", - role: "button.reverse", - exist: false, - def: false - } - }; - this.sbPlayerStatusPlaylist = { - "playlist index": { - name: "index", - read: true, - write: false, - type: "number", - role: "value", - exist: false - }, - "id": { - name: "id", - read: true, - write: false, - type: "number", - role: "value", - exist: false - }, - "url": { - name: "url", - read: true, - write: false, - type: "string", - role: "value", - exist: false - }, - "title": { - name: "title", - read: true, - write: false, - type: "string", - role: "value", - exist: false, - }, - "artwork_url": { - name: "ArtworkUrl", - read: true, - write: false, - type: "string", - role: "media.cover", - exist: false - }, - "coverid": { - name: "ArtworkUrl", - read: true, - write: false, - type: "string", - role: "value", - exist: true - }, - "type": { - name: "Type", - read: true, - write: false, - type: "string", - role: "value", - exist: false - }, - "bitrate": { - name: "Bitrate", - read: true, - write: false, - type: "string", - role: "media.bitrate", - exist: false - }, - "duration": { - name: "Duration", - read: true, - write: false, - type: "number", - role: "media.duration", - exist: false - }, - "artist": { - name: "Artist", - read: true, - write: false, - type: "string", - role: "media.artist", - exist: false - }, - "album": { - name: "Album", - read: true, - write: false, - type: "string", - role: "media.album", - exist: false - }, - "remote_title": { - name: "RadioName", - read: true, - write: false, - type: "string", - role: "value", - exist: false - - } - }; - this.init = async function (playerdata) { - this.doPlayerStatus(playerdata); - this.logdebug("New Player found: " + this.playername + " with id " + this.playerid); - await this.createFolder(this.statePath); - await this.createDevice(this.playername, this.statePath); - const delay = this.playerindex || 0; - setTimeout(this.doObserverPlayer.bind(this), delay * 190); - }; - this.sanitizePlayername = (playername) => playername.replace(this.FORBIDDEN_CHARS, "_"); - this.doObserverPlayer = function () { - this.logsilly("doObserverPlayer"); - this.getPlayerUpdateStatus(); - this.observers["player"] = setTimeout(this.doObserverPlayer.bind(this), this.adapter.config.playerrefresh); - }; - this.getPlayerUpdateStatus = () => { - this.logsilly("getPlayerUpdate"); - if (this.statuscounter == 0) { - this.request(this.playerid, ["status", "0", "999", this.fullStatus], this.doPlayerUpdateStatus.bind(this)); - this.request(this.playerid, ["alarms", "0", "999", "filter:all"], this.doAlarmsUpdateStatus.bind(this)); - } else { - this.request(this.playerid, ["status", "-", "1", this.smallStatus], this.doPlayerUpdateStatus.bind(this)); - } - this.statuscounter += 1; - if (this.statuscounter > 9) this.statuscounter = 0; - }; - this.doAlarmsUpdateStatus = function (result) { - this.logsilly("doAlarmsUpdateStatus"); - const alarmdata = JSON.stringify(result.result); - this.setState(this.sbPlayerStatusMain["alarms"].name, this.convertState(this.sbPlayerStatusMain["alarms"], alarmdata), this.statePath, this.playername); - }; - this.doPlayerUpdateStatus = (result) => { - this.logsilly("doPlayerUpdateStatus"); - const fullStatus = result.params[1][3] == this.fullStatus; - let playerdata = result.result; - - for (const key in this.sbPlayerStatusMain) { - let value = ""; - if (key == "state") continue; - if (key == "playlist") continue; - if (key == "playerid") continue; - if (key == "alarms") continue; - if (Object.prototype.hasOwnProperty.call(playerdata, key)) { - value = playerdata[key]; - if (key == "name") this.playername = this.sanitizePlayername(value); - if (key == "playerindex") this.playerindex = parseInt(value); - if (key == "remote") this.remote = parseInt(value); - if (key == "mode") { - if (value == "play") { - this.setState("state", 1, this.statePath, this.playername); - } - if (value == "pause") { - this.setState("state", 0, this.statePath, this.playername); - } - if (value == "stop") { - this.setState("state", 2, this.statePath, this.playername); - } - } - } else { - //if (key == "remote") value = 0; - if (key == "remote") this.remote = 0; - } - this.setState(this.sbPlayerStatusMain[key].name, this.convertState(this.sbPlayerStatusMain[key], value), this.statePath, this.playername); - } - this.setState(this.sbPlayerStatusMain["playerid"].name, this.convertState(this.sbPlayerStatusMain["playerid"], this.playerid), this.statePath, this.playername); - if (!result.result.playlist_loop) return; - //todo - playerdata = result.result.playlist_loop.find(function (result, el) { return el["playlist index"] == result.playlist_cur_index; }.bind(this, result.result)); - if (playerdata) { - for (const key in this.sbPlayerStatusLoop) { - let value = ""; - if (Object.prototype.hasOwnProperty.call(playerdata, key)) { - value = playerdata[key]; - if (key == "coverid") { - if (value[0] != "-") { - value = this.createArtworkUrl(value); - } else { - continue; - } - } - if (key == "artwork_url" && playerdata["coverid"] && playerdata["coverid"][0] != "-") { - continue; - } - if (key == "artwork_url") { - const regex = /imageproxy\/(.*)\/[^/]/gm; - const m = regex.exec(value); - if (m && m.index > 0) { - value = decodeURIComponent(m[1]); - } - else if (value === "html/images/radio.png") { - value = "http://" + this.adapter.config.server + ":" + this.adapter.config.port + "/html/images/radio.png"; - } - else if (value === "html/images/cover.png") { - value = "http://" + this.adapter.config.server + ":" + this.adapter.config.port + "/html/images/cover.png"; - } - else if (value === "html/images/favorites.png") { - value = "http://" + this.adapter.config.server + ":" + this.adapter.config.port + "/html/images/favorites.png"; - } - } - this.setState(this.sbPlayerStatusLoop[key].name, this.convertState(this.sbPlayerStatusLoop[key], value), this.statePath, this.playername); - } else { - if (key == "remote_title" && this.remote == 0) { - value = ""; - } - if (fullStatus) this.setState(this.sbPlayerStatusLoop[key].name, this.convertState(this.sbPlayerStatusLoop[key], value), this.statePath, this.playername); - } - } - if (playerdata["trackartist"] !== undefined && playerdata["trackartist"] !== null && playerdata["trackartist"] !== "" && (playerdata["artist"] === undefined || playerdata["artist"] === null || playerdata["artist"] === "")) { - this.setState(this.sbPlayerStatusLoop["artist"].name, this.convertState(this.sbPlayerStatusLoop["artist"], playerdata["trackartist"]), this.statePath, this.playername); - } - } - if (fullStatus) { - const playlist = result.result.playlist_loop; - const pla = []; - for (const playlistkey in playlist) { - const playlistitem = playlist[playlistkey]; - const pli = {}; - for (const key in this.sbPlayerStatusPlaylist) { - if (Object.prototype.hasOwnProperty.call(playlistitem, key)) { - - let value = playlistitem[key]; - if (key == "coverid") { - if (value[0] != "-") { - value = this.createArtworkUrl(value); - } else { - continue; - } - } - if (key == "artwork_url" && playlistitem["coverid"] && playlistitem["coverid"][0] != "-") { - continue; - } - if (key == "artwork_url") { - const regex = /imageproxy\/(.*)\/[^/]/gm; - const m = regex.exec(value); - if (m && m.index > 0) { - value = decodeURIComponent(m[1]); - } - } - pli[this.sbPlayerStatusPlaylist[key].name] = value; - } - } - pla.push(pli); - } - if (this.adapter.config.useplaylist) { - this.setState(this.sbPlayerStatusMain["playlist"].name, this.convertState(this.sbPlayerStatusMain["playlist"], JSON.stringify(pla)), this.statePath, this.playername); - } else { - this.setState(this.sbPlayerStatusMain["playlist"].name, this.convertState(this.sbPlayerStatusMain["playlist"], ""), this.statePath, this.playername); - } - } - return; - }; - this.doPlayerStatus = (playerdata) => { - this.logsilly("doPlayerStatus"); - if (playerdata.playerid) this.playerid = playerdata.playerid; - if (playerdata.name) this.playername = this.sanitizePlayername(playerdata.name); - if (playerdata.playerindex) this.playerindex = playerdata.playerindex; - if (playerdata.connected) this.connected = playerdata.connected; - this.checkPlayerdataStates(playerdata); - }; - this.checkPlayerdataStates = (playerdata) => { - this.logsilly("checkPlayerdataStates"); - if (!playerdata) return; - if (playerdata.name) this.playername = this.sanitizePlayername(playerdata.name); - for (const key in this.sbPlayerStatusMain) { - const stateTemplate = this.sbPlayerStatusMain[key]; - if (key == "playlist" && !this.adapter.config.useplaylist) continue; - if (!stateTemplate.exist) { - this.sbPlayerStatusMain[key] = this.createObject(stateTemplate, this.statePath, this.playername); - } - } - for (const key in this.sbPlayerStatusLoop) { - //if (key == "albumartist") key = "artist"; - const stateTemplate = this.sbPlayerStatusLoop[key]; - if (!stateTemplate.exist) { - this.sbPlayerStatusLoop[key] = this.createObject(stateTemplate, this.statePath, this.playername); - } - } - for (const key in this.sbPlayerButtons) { - const stateTemplate = this.sbPlayerButtons[key]; - if (!stateTemplate.exist) { - stateTemplate.create = true; - this.sbPlayerButtons[key] = this.createObject(stateTemplate, this.statePath, this.playername); - } - } - }; - this.doStateChange = function (idParts, state) { - this.logsilly("doPlayerStateChange"); - idParts.shift(); - if (idParts[0] == "Volume") { - if (state.val != null) { - this.request(this.playerid, ["mixer", "volume", Math.round(state.val)]); - this.setState(idParts[0], Math.round(state.val), this.statePath, this.playername, false); - } - } - if (idParts[0] == "Power") { - if (state.val == 0) this.request(this.playerid, ["power", 0]); - if (state.val == 1) this.request(this.playerid, ["power", 1]); - } - if (idParts[0] == "state") { - if (state.val == 0) { - this.request(this.playerid, ["pause", "1"]); - } - if (state.val == 1) { - this.request(this.playerid, ["play", "2"]); - } - if (state.val == 2) { - this.request(this.playerid, ["stop"]); - } - } - if (idParts[0] == "PlaylistRepeat") { - if (state.val == 0) { - this.request(this.playerid, ["playlist", "repeat", "0"]); - } - if (state.val == 1) { - this.request(this.playerid, ["playlist", "repeat", "1"]); - } - if (state.val == 2) { - this.request(this.playerid, ["playlist", "repeat", "2"]); - } - } - if (idParts[0] == "PlaylistShuffle") { - if (state.val == 0) { - this.request(this.playerid, ["playlist", "shuffle", "0"]); - } - if (state.val == 1) { - this.request(this.playerid, ["playlist", "shuffle", "1"]); - } - if (state.val == 2) { - this.request(this.playerid, ["playlist", "shuffle", "2"]); - } - } - if (idParts[0] == "cmdGoTime") { - // state.val needs to be a string, although the time is set in seconds - // when settings cmdGoTime via ioBroker.simple-api its not possible to submit a number as string - // if we receive a number, we need to cast this to a string - if (typeof state.val === "number") { - state.val = state.val.toString(); - } - if (state.val.trim() !== "" && !isNaN(state.val.trim())) { - this.request(this.playerid, ["time", state.val.trim()]); - this.setState(idParts[0], " ", this.statePath, this.playername, false); - } - } - if (idParts[0] == "PlaylistCurrentIndex") { - if (state.val.trim() !== "" && !isNaN(state.val.trim())) { - this.request(this.playerid, ["playlist", "index", state.val.trim()]); - } - } - if (idParts[0] == "cmdPlayFavorite") { - if (state.val !== " ") { - this.request(this.playerid, ["favorites", "playlist", "play", "item_id:" + state.val]); - this.setState(idParts[0], " ", this.statePath, this.playername, false); - } - } - if (idParts[0] == "cmdPlayUrl") { - if (state.val !== " ") { - this.request(this.playerid, ["playlist", "play", state.val]); - this.setState(idParts[0], " ", this.statePath, this.playername, false); - } - } - if (idParts[0] == "cmdGeneral") { - if (state.val !== " ") { - try { - const cmd = JSON.parse("[" + state.val + "]"); - this.request(this.playerid, cmd); - } catch (error) { - this.logerror("cmdGeneral, Parameter error: " + state.val); - } - this.setState(idParts[0], " ", this.statePath, this.playername, false); - } - } - if (idParts[0] == "btnForward") { - if (state.val) { - this.request(this.playerid, ["button", "jump_fwd"]); - this.setState(idParts[0], false, this.statePath, this.playername, false); - } - } - if (idParts[0] == "btnRewind") { - if (state.val) { - this.request(this.playerid, ["button", "jump_rew"]); - this.setState(idParts[0], false, this.statePath, this.playername, false); - } - } - if (idParts[0].startsWith("btnPreset_")) { - const name = "preset_" + idParts[0].split("_")[1] + ".single"; - if (state.val) { - this.request(this.playerid, ["button", name]); - this.setState(idParts[0], false, this.statePath, this.playername, false); - } - } - }; - this.createArtworkUrl = function (artworkid) { - return "http://" + this.adapter.config.server + ":" + this.adapter.config.port + "/music/" + artworkid + "/cover.jpg"; - }; - this.disconnect = () => { - this.logdebug("Player " + this.playername + " disconnected with id " + this.playerid); - clearInterval(this.observers["player"]); - this.setState("Power", 0, this.statePath, this.playername, false); - delete this.observers["player"]; - this.connected = 0; - }; - this.connect = () => { - this.logdebug("Player " + this.playername + " connected with id " + this.playerid); - this.doObserverPlayer(); - this.connected = 1; - }; - this.createState = function (stateTemplate, level1path = false, level2path = false) { - const name = (level1path ? level1path + "." : "") + (level2path ? level2path + "." : "") + stateTemplate.name; - this.logsilly("Create state " + name); - if (!this.currentStates[name]) this.adapter.createState(level1path, level2path, stateTemplate.name, stateTemplate); - stateTemplate.exist = true; - return stateTemplate; - }; - this.createObject = function (stateTemplate, level1path, level2path, callback) { - this.logdebug("createObject " + stateTemplate.name); - const name = (level1path ? level1path + "." : "") + (level2path ? level2path + "." : "") + stateTemplate.name; - this.adapter.getObject(name, (err, obj) => { - const newobj = { - type: "state", - common: stateTemplate, - native: {} - }; - if (!obj) { - (callback) ? this.adapter.setObject(name, newobj, callback) : this.adapter.setObject(name, newobj); - } else { - if (callback) callback(); - } - }); - stateTemplate.exist = true; - return stateTemplate; - }; - this.request = function (playerid, params, callback) { - if (this.connected) this.server.request(playerid, params, callback); - }; - this.setTPE2Handling = function (value) { - this.TPE2Handling = value; - }; - this.setState = function (name, value, level1path, level2path, check = true) { - name = (level1path ? level1path + "." : "") + (level2path ? level2path + "." : "") + name; - if (this.currentStates[name] !== value && check) { - this.currentStates[name] = value; - if (!name.includes("Time")) this.logsilly("setState name: " + name + " value: " + value); - this.adapter.setState(name, value, true); - } - if (!check) { - this.currentStates[name] = value; - this.logsilly("setState name: " + name + " value: " + value); - this.adapter.setState(name, value, true); - } - }; - this.createFolder = async function (foldername, level1path, level2path) { - const id = (level1path ? level1path + "." : "") + (level2path ? level2path + "." : "") + foldername; - this.logsilly("createFolder " + id); - if (await this.existsObjectAsync(id)) { - this.logsilly("Folder exists: " + id); - } else { - const obj = { - type: "folder", - common: { - name: foldername - }, - native: {} - }; - this.adapter.setObject(id, obj); - } - }; - this.createDevice = async function (devicename, level1path, level2path) { - const id = (level1path ? level1path + "." : "") + (level2path ? level2path + "." : "") + devicename; - this.logsilly("createDevice " + id); - if (await this.existsObjectAsync(id)) { - this.logsilly("Device exists: " + id); - } else { - const obj = { - type: "device", - common: { - name: devicename - }, - native: {} - }; - this.adapter.setObject(id, obj); - } - }; - this.existsObjectAsync = function (id) { - return new Promise((resolve, reject) => { - id = this.adapter.namespace + "." + id; - - this.adapter.getForeignObject(id, (err, obj) => { - if (err) { - reject(err); - } else { - resolve(!!obj); - } - - }); - }); - }; - this.convertState = function (stateTemplate, value) { - if (stateTemplate.type == "string") return String(value); - if (stateTemplate.type == "number") return Number(value); - this.logdebug("Missing conversion function for type " + stateTemplate.type + " Please report."); - return value; - }; - this.logsilly = (s) => { - if (this.islogsilly) this.adapter.log.silly(s); - }; - this.logdebug = (s) => { - if (this.islogdebug) this.adapter.log.debug(s); - }; - this.logerror = function (s) { - // @ts-ignore - this.adapter.log.error(s); - }; - this.loginfo = (s) => { - this.adapter.log.info(s); - }; - - this.init(playerdata); -} -module.exports = ioSBPlayer; \ No newline at end of file +'use strict'; +const { ioUtil } = require('./ioUtil'); + +/** + * Represents a Squeezebox player within a server environment. + * + * @param server - The server instance that manages the player. + * @param playerdata - Initial data related to the player. + * + * Properties include: + * - server: The server instance. + * - adapter: Adapter for server communication. + * - ioUtil: Utility for IO operations. + * - fullStatus: String representing full status tags. + * - smallStatus: String representing small status tags. + * - statePath: Path to the player's state within the server. + * - btnStatePath: Path to the player's button states. + * - currentStates: Object holding the current states of the player. + * - observers: List of observer functions for state changes. + * - playername: Name of the player. + * - playerid: Unique identifier for the player. + * - playerindex: Index of the player. + * - remote: Indicator for remote control capability. + * - statuscounter: Counter for status updates. + * - connected: Connection status of the player. + * - log: Object for logging information. + * - islogsilly: Flag for verbose logging. + * - islogdebug: Flag for debug logging. + * - TPE2Handling: Handling mode for TPE2 frames. + * - FORBIDDEN_CHARS: Regular expression for forbidden characters in player names. + * - sbPlayerStatusMain: Main status attributes of the player. + * - sbPlayerStatusLoop: Loop status attributes of the player. + * - sbPlayerButtons: Button commands available for the player. + * - sbPlayerStatusPlaylist: Playlist status attributes of the player. + * + * Methods include: + * - init: Initializes player settings and starts observation. + * - sanitizePlayername: Sanitizes the player name. + * - doObserverPlayer: Manages player status observation. + * - getPlayerUpdateStatus: Updates player status. + * - doAlarmsUpdateStatus: Updates alarm status. + * - doPlayerUpdateStatus: Updates player data based on server responses. + * - doPlayerStatus: Sets player status from initial data. + * - checkPlayerdataStates: Ensures all player data states are initialized. + * - doStateChange: Handles state changes based on updates. + * - createArtworkUrl: Generates a URL for player artwork. + * - disconnect: Handles player disconnection. + * - connect: Handles player connection. + * - createState: Creates a state object for the player. + * - createObject: Creates a device or state object. + * - request: Sends a request to the server. + * - setTPE2Handling: Sets the TPE2 handling mode. + * - setState: Updates the state of the player. + * - createFolder: Creates a folder object. + * - createDevice: Creates a device object. + * - existsObjectAsync: Checks if an object exists asynchronously. + * - convertState: Converts state values based on type. + * - logsilly: Logs messages at a verbose level. + * - logdebug: Logs debug messages. + * - logerror: Logs error messages. + * - loginfo: Logs informational messages. + */ +function ioSBPlayer(server, playerdata) { + this.server = server; + this.adapter = server.adapter; + this.ioUtil = new ioUtil( + server.adapter, + this.adapter.config.outputplayerdebug, + this.adapter.config.outputplayersilly, + ); + this.fullStatus = 'tags:cgABbehldiqtyrSuoKLNJ'; + this.smallStatus = 'tags:uB'; + this.statePath = this.server.PlayersStatePath; + this.btnStatePath = 'Buttons'; + this.currentStates = {}; + this.observers = []; + this.playername = ''; + this.playerid = ''; + this.playerindex = 0; + this.remote = 0; + this.statuscounter = 0; + this.connected = 0; + + this.log = {}; + + this.islogsilly = this.adapter.config.outputplayersilly; + this.islogdebug = this.adapter.config.outputplayerdebug; + this.TPE2Handling = 1; + + this.FORBIDDEN_CHARS = /[^\d\w_]+/gm; + //test the regex: https://regex101.com/r/Ed0WhH/1 + + this.sbPlayerStatusMain = { + player_name: { + name: 'Playername', + read: true, + write: false, + type: 'string', + role: 'info.name', + exist: false, + }, + playerid: { + name: 'PlayerID', + read: true, + write: false, + type: 'string', + role: 'info.name', + exist: false, + }, + player_connected: { + name: 'Connected', + read: true, + write: false, + type: 'number', + role: 'value', + exist: false, + }, + player_ip: { + name: 'IP', + read: true, + write: false, + type: 'string', + role: 'info.ip', + exist: false, + }, + power: { + name: 'Power', + read: true, + write: true, + type: 'number', + role: 'switch', + exist: false, + min: 0, + max: 1, + }, + mode: { + name: 'Mode', + read: true, + write: false, + type: 'string', + role: 'media.state', + exist: false, + }, + time: { + name: 'Time', + read: true, + write: false, + type: 'number', + role: 'media.elapsed', + exist: false, + }, + rate: { + name: 'Rate', + read: true, + write: false, + type: 'number', + role: 'value', + exist: false, + }, + sync_slaves: { + name: 'SyncSlaves', + read: true, + write: false, + type: 'string', + role: 'value', + exist: false, + }, + sync_master: { + name: 'SyncMaster', + read: true, + write: false, + type: 'string', + role: 'value', + exist: false, + }, + 'mixer volume': { + name: 'Volume', + read: true, + write: true, + type: 'number', + role: 'level.volume', + exist: false, + min: 0, + max: 100, + }, + 'playlist repeat': { + name: 'PlaylistRepeat', + read: true, + write: true, + type: 'number', + role: 'media.mode.repeat', + exist: false, + }, + 'playlist shuffle': { + name: 'PlaylistShuffle', + read: true, + write: true, + type: 'number', + role: 'media.mode.shuffle', + exist: false, + }, + remote: { + name: 'Remote', + read: true, + write: false, + type: 'number', + role: 'value', + exist: false, + }, + playlist: { + name: 'Playlist', + read: true, + write: false, + type: 'string', + role: 'value', + exist: false, + }, + playlist_cur_index: { + name: 'PlaylistCurrentIndex', + read: true, + write: true, + type: 'string', + role: 'value', + exist: false, + }, + alarms: { + name: 'Alarms', + read: true, + write: false, + type: 'string', + role: 'value', + exist: false, + }, + state: { + name: 'state', + read: true, + write: true, + type: 'number', + role: 'media.state', + exist: false, + min: 0, + max: 2, + }, + }; + + this.sbPlayerStatusLoop = { + duration: { + name: 'Duration', + read: true, + write: false, + type: 'number', + role: 'media.duration', + exist: false, + }, + artwork_url: { + name: 'ArtworkUrl', + read: true, + write: false, + type: 'string', + role: 'media.cover', + exist: false, + }, + bitrate: { + name: 'Bitrate', + read: true, + write: false, + type: 'string', + role: 'media.bitrate', + exist: false, + }, + album: { + name: 'Album', + read: true, + write: false, + type: 'string', + role: 'media.album', + exist: false, + }, + coverid: { + name: 'ArtworkUrl', + read: true, + write: false, + type: 'string', + role: 'value', + exist: true, + }, + genre: { + name: 'Genre', + read: true, + write: false, + type: 'string', + role: 'media.genre', + exist: false, + }, + type: { + name: 'Type', + read: true, + write: false, + type: 'string', + role: 'value', + exist: false, + }, + title: { + name: 'Title', + read: true, + write: false, + type: 'string', + role: 'media.title', + exist: false, + }, + artist: { + name: 'Artist', + read: true, + write: false, + type: 'string', + role: 'media.artist', + exist: false, + }, + albumartist: { + name: 'Albumartist', + read: true, + write: false, + type: 'string', + role: 'media.artist', + exist: false, + }, + trackartist: { + name: 'Trackartist', + read: true, + write: false, + type: 'string', + role: 'media.artist', + exist: false, + }, + band: { + name: 'Band', + read: true, + write: false, + type: 'string', + role: 'media.artist', + exist: false, + }, + url: { + name: 'Url', + read: true, + write: false, + type: 'string', + role: 'media.url', + exist: false, + }, + remote_title: { + name: 'RadioName', + read: true, + write: false, + type: 'string', + role: 'value', + exist: false, + }, + }; + this.sbPlayerButtons = { + cmdPlayFavorite: { + name: 'cmdPlayFavorite', + read: true, + write: true, + type: 'string', + role: 'state', + exist: false, + def: ' ', + }, + cmdPlayUrl: { + name: 'cmdPlayUrl', + read: true, + write: true, + type: 'string', + role: 'state', + exist: false, + def: ' ', + }, + cmdGeneral: { + name: 'cmdGeneral', + read: true, + write: true, + type: 'string', + role: 'state', + exist: false, + def: ' ', + }, + cmdGoTime: { + name: 'cmdGoTime', + read: true, + write: true, + type: 'string', + role: 'state', + exist: false, + def: ' ', + }, + forward: { + name: 'btnForward', + read: true, + write: true, + type: 'boolean', + role: 'button.forward', + exist: false, + def: false, + }, + preset_1: { + name: 'btnPreset_1', + read: true, + write: true, + type: 'boolean', + role: 'button', + exist: false, + def: false, + }, + preset_2: { + name: 'btnPreset_2', + read: true, + write: true, + type: 'boolean', + role: 'button', + exist: false, + def: false, + }, + preset_3: { + name: 'btnPreset_3', + read: true, + write: true, + type: 'boolean', + role: 'button', + exist: false, + def: false, + }, + preset_4: { + name: 'btnPreset_4', + read: true, + write: true, + type: 'boolean', + role: 'button', + exist: false, + def: false, + }, + preset_5: { + name: 'btnPreset_5', + read: true, + write: true, + type: 'boolean', + role: 'button', + exist: false, + def: false, + }, + preset_6: { + name: 'btnPreset_6', + read: true, + write: true, + type: 'boolean', + role: 'button', + exist: false, + def: false, + }, + rewind: { + name: 'btnRewind', + read: true, + write: true, + type: 'boolean', + role: 'button.reverse', + exist: false, + def: false, + }, + }; + this.sbPlayerStatusPlaylist = { + 'playlist index': { + name: 'index', + read: true, + write: false, + type: 'number', + role: 'value', + exist: false, + }, + id: { + name: 'id', + read: true, + write: false, + type: 'number', + role: 'value', + exist: false, + }, + url: { + name: 'url', + read: true, + write: false, + type: 'string', + role: 'value', + exist: false, + }, + title: { + name: 'title', + read: true, + write: false, + type: 'string', + role: 'value', + exist: false, + }, + artwork_url: { + name: 'ArtworkUrl', + read: true, + write: false, + type: 'string', + role: 'media.cover', + exist: false, + }, + coverid: { + name: 'ArtworkUrl', + read: true, + write: false, + type: 'string', + role: 'value', + exist: true, + }, + type: { + name: 'Type', + read: true, + write: false, + type: 'string', + role: 'value', + exist: false, + }, + bitrate: { + name: 'Bitrate', + read: true, + write: false, + type: 'string', + role: 'media.bitrate', + exist: false, + }, + duration: { + name: 'Duration', + read: true, + write: false, + type: 'number', + role: 'media.duration', + exist: false, + }, + artist: { + name: 'Artist', + read: true, + write: false, + type: 'string', + role: 'media.artist', + exist: false, + }, + album: { + name: 'Album', + read: true, + write: false, + type: 'string', + role: 'media.album', + exist: false, + }, + remote_title: { + name: 'RadioName', + read: true, + write: false, + type: 'string', + role: 'value', + exist: false, + }, + }; + /** + * Initializes the player with the provided player data. + * + * @param playerdata - Initial data related to the player. + * @async + * @returns - A promise that resolves after initializing the player settings and starting observation. + * + * This function performs the following tasks: + * - Sets the initial player status using the provided player data. + * - Logs the discovery of a new player using its name and ID. + * - Creates a folder and device object for the player in the server's state path. + * - Starts observing player status updates with a delay based on the player's index. + */ + this.init = async function (playerdata) { + this.doPlayerStatus(playerdata); + this.ioUtil.logdebug(`New Player found: ${this.playername} with id ${this.playerid}`); + await this.createFolder(this.statePath); + await this.createDevice(this.playername, this.statePath); + const delay = this.playerindex || 0; + setTimeout(this.doObserverPlayer.bind(this), delay * 190); + }; + this.sanitizePlayername = playername => playername.replace(this.FORBIDDEN_CHARS, '_'); + /** + * Starts observing player status updates. + * + * Logs the start of observing player updates, calls the function to request the player status, + * and schedules the next call to itself with a delay based on the configured player refresh interval. + */ + this.doObserverPlayer = function () { + this.ioUtil.logsilly('doObserverPlayer'); + this.getPlayerUpdateStatus(); + this.observers['player'] = setTimeout(this.doObserverPlayer.bind(this), this.adapter.config.playerrefresh); + }; + /** + * Requests the player status, choosing between full and small status updates + * in an alternating pattern. + * + * @description + * Requests the player status using the "status" command, with a counter + * cycling between 0 and 9. When the counter is 0, the full player status + * is requested using the fullStatus parameter. Otherwise, the last player + * status is requested using the smallStatus parameter. The + * doPlayerUpdateStatus function is called with the result. + * Additionally, the alarms are requested using the "alarms" command with + * the filter "all". The doAlarmsUpdateStatus function is called with the + * result. + */ + this.getPlayerUpdateStatus = () => { + this.ioUtil.logsilly('getPlayerUpdate'); + if (this.statuscounter == 0) { + this.request(this.playerid, ['status', '0', '999', this.fullStatus], this.doPlayerUpdateStatus.bind(this)); + this.request(this.playerid, ['alarms', '0', '999', 'filter:all'], this.doAlarmsUpdateStatus.bind(this)); + } else { + this.request(this.playerid, ['status', '-', '1', this.smallStatus], this.doPlayerUpdateStatus.bind(this)); + } + this.statuscounter += 1; + if (this.statuscounter > 9) { + this.statuscounter = 0; + } + }; + /** + * Updates the alarm status for the player. + * + * @description + * This function is responsible for updating the alarm status of the player + * by logging the operation, serializing the result of the alarms request + * into a JSON string, and setting the state of the alarms in the player's + * status using the converted state value. It utilizes the player's main + * status attributes and the corresponding state path. + * @param result - The result object from the alarms request, + * containing the alarm data to be processed and updated. + */ + this.doAlarmsUpdateStatus = function (result) { + this.ioUtil.logsilly('doAlarmsUpdateStatus'); + const alarmdata = JSON.stringify(result.result); + this.setState( + this.sbPlayerStatusMain['alarms'].name, + this.convertState(this.sbPlayerStatusMain['alarms'], alarmdata), + this.statePath, + this.playername, + ); + }; + + this.doPlayerUpdateStatus = result => { + this.ioUtil.logsilly('doPlayerUpdateStatus'); + const fullStatus = result.params[1][3] == this.fullStatus; + let playerdata = result.result; + + for (const key in this.sbPlayerStatusMain) { + let value = ''; + if (key == 'state') { + continue; + } + if (key == 'playlist') { + continue; + } + if (key == 'playerid') { + continue; + } + if (key == 'alarms') { + continue; + } + if (Object.prototype.hasOwnProperty.call(playerdata, key)) { + value = playerdata[key]; + if (key == 'name') { + this.playername = this.sanitizePlayername(value); + } + if (key == 'playerindex') { + this.playerindex = parseInt(value); + } + if (key == 'remote') { + this.remote = parseInt(value); + } + if (key == 'mode') { + if (value == 'play') { + this.setState('state', 1, this.statePath, this.playername); + } + if (value == 'pause') { + this.setState('state', 0, this.statePath, this.playername); + } + if (value == 'stop') { + this.setState('state', 2, this.statePath, this.playername); + } + } + } else { + //if (key == "remote") value = 0; + if (key == 'remote') { + this.remote = 0; + } + } + this.setState( + this.sbPlayerStatusMain[key].name, + this.convertState(this.sbPlayerStatusMain[key], value), + this.statePath, + this.playername, + ); + } + this.setState( + this.sbPlayerStatusMain['playerid'].name, + this.convertState(this.sbPlayerStatusMain['playerid'], this.playerid), + this.statePath, + this.playername, + ); + if (!result.result.playlist_loop) { + return; + } + //todo + playerdata = result.result.playlist_loop.find( + function (result, el) { + return el['playlist index'] == result.playlist_cur_index; + }.bind(this, result.result), + ); + if (playerdata) { + for (const key in this.sbPlayerStatusLoop) { + let value = ''; + if (Object.prototype.hasOwnProperty.call(playerdata, key)) { + value = playerdata[key]; + if (key == 'coverid') { + if (value[0] != '-') { + value = this.createArtworkUrl(value); + } else { + continue; + } + } + if (key == 'artwork_url' && playerdata['coverid'] && playerdata['coverid'][0] != '-') { + continue; + } + if (key == 'artwork_url') { + const regex = /imageproxy\/(.*)\/[^/]/gm; + const m = regex.exec(value); + if (m && m.index > 0) { + value = decodeURIComponent(m[1]); + } else if (value === 'html/images/radio.png') { + value = `http://${this.adapter.config.server}:${ + this.adapter.config.port + }/html/images/radio.png`; + } else if (value === 'html/images/cover.png') { + value = `http://${this.adapter.config.server}:${ + this.adapter.config.port + }/html/images/cover.png`; + } else if (value === 'html/images/favorites.png') { + value = `http://${this.adapter.config.server}:${ + this.adapter.config.port + }/html/images/favorites.png`; + } + } + this.setState( + this.sbPlayerStatusLoop[key].name, + this.convertState(this.sbPlayerStatusLoop[key], value), + this.statePath, + this.playername, + ); + } else { + if (key == 'remote_title' && this.remote == 0) { + value = ''; + } + if (fullStatus) { + this.setState( + this.sbPlayerStatusLoop[key].name, + this.convertState(this.sbPlayerStatusLoop[key], value), + this.statePath, + this.playername, + ); + } + } + } + if ( + playerdata['trackartist'] !== undefined && + playerdata['trackartist'] !== null && + playerdata['trackartist'] !== '' && + (playerdata['artist'] === undefined || playerdata['artist'] === null || playerdata['artist'] === '') + ) { + this.setState( + this.sbPlayerStatusLoop['artist'].name, + this.convertState(this.sbPlayerStatusLoop['artist'], playerdata['trackartist']), + this.statePath, + this.playername, + ); + } + } + if (fullStatus) { + const playlist = result.result.playlist_loop; + const pla = []; + for (const playlistkey in playlist) { + const playlistitem = playlist[playlistkey]; + const pli = {}; + for (const key in this.sbPlayerStatusPlaylist) { + if (Object.prototype.hasOwnProperty.call(playlistitem, key)) { + let value = playlistitem[key]; + if (key == 'coverid') { + if (value[0] != '-') { + value = this.createArtworkUrl(value); + } else { + continue; + } + } + if (key == 'artwork_url' && playlistitem['coverid'] && playlistitem['coverid'][0] != '-') { + continue; + } + if (key == 'artwork_url') { + const regex = /imageproxy\/(.*)\/[^/]/gm; + const m = regex.exec(value); + if (m && m.index > 0) { + value = decodeURIComponent(m[1]); + } + } + pli[this.sbPlayerStatusPlaylist[key].name] = value; + } + } + pla.push(pli); + } + if (this.adapter.config.useplaylist) { + this.setState( + this.sbPlayerStatusMain['playlist'].name, + this.convertState(this.sbPlayerStatusMain['playlist'], JSON.stringify(pla)), + this.statePath, + this.playername, + ); + } else { + this.setState( + this.sbPlayerStatusMain['playlist'].name, + this.convertState(this.sbPlayerStatusMain['playlist'], ''), + this.statePath, + this.playername, + ); + } + } + return; + }; + this.doPlayerStatus = playerdata => { + this.ioUtil.logsilly('doPlayerStatus'); + if (playerdata.playerid) { + this.playerid = playerdata.playerid; + } + if (playerdata.name) { + this.playername = this.sanitizePlayername(playerdata.name); + } + if (playerdata.playerindex) { + this.playerindex = playerdata.playerindex; + } + if (playerdata.connected) { + this.connected = playerdata.connected; + } + this.checkPlayerdataStates(playerdata); + }; + this.checkPlayerdataStates = playerdata => { + this.ioUtil.logsilly('checkPlayerdataStates'); + if (!playerdata) { + return; + } + if (playerdata.name) { + this.playername = this.sanitizePlayername(playerdata.name); + } + for (const key in this.sbPlayerStatusMain) { + const stateTemplate = this.sbPlayerStatusMain[key]; + if (key == 'playlist' && !this.adapter.config.useplaylist) { + continue; + } + if (!stateTemplate.exist) { + this.sbPlayerStatusMain[key] = this.createObject(stateTemplate, this.statePath, this.playername); + } + } + for (const key in this.sbPlayerStatusLoop) { + //if (key == "albumartist") key = "artist"; + const stateTemplate = this.sbPlayerStatusLoop[key]; + if (!stateTemplate.exist) { + this.sbPlayerStatusLoop[key] = this.createObject(stateTemplate, this.statePath, this.playername); + } + } + for (const key in this.sbPlayerButtons) { + const stateTemplate = this.sbPlayerButtons[key]; + if (!stateTemplate.exist) { + stateTemplate.create = true; + this.sbPlayerButtons[key] = this.createObject(stateTemplate, this.statePath, this.playername); + } + } + }; + /************* ✨ Codeium Command ⭐ *************/ + /** + * Handles state changes for player. + * + * @param idParts - The state identifier split into parts. + * @param state - The new state object; can be null if the state was deleted. + * + * The function logs the state change, shifts the idParts array and invokes the + * appropriate state change handler based on the first part of the id. + * It returns early if id, state, or state.ack is falsy. + * + * Supported state changes: + * - Volume: Sets the volume of the player. + * - Power: Sets the power state of the player. + * - state: Sets the state of the player (0 = off, 1 = play, 2 = stop) + * - PlaylistRepeat: Sets the repeat state of the playlist (0 = off, 1 = repeat all, 2 = repeat one) + * - PlaylistShuffle: Sets the shuffle state of the playlist (0 = off, 1 = shuffle, 2 = shuffle all) + * - cmdGoTime: Sets the current time of the player in seconds. + * - PlaylistCurrentIndex: Sets the current index of the playlist. + * - cmdPlayFavorite: Plays a favorite with the given item_id. + * - cmdPlayUrl: Plays a URL. + * - cmdGeneral: Executes a general command (e.g. a button press). + * - btnForward: Jump forward. + * - btnRewind: Jump backward. + * - btnPreset_X: Executes the preset X (e.g. preset_1.single). + */ + /****** 4364f116-3e7c-49b0-821a-4798e59f5e85 *******/ + this.doStateChange = function (idParts, state) { + this.ioUtil.logsilly('doPlayerStateChange'); + idParts.shift(); + if (idParts[0] == 'Volume') { + if (state.val != null) { + this.request(this.playerid, ['mixer', 'volume', Math.round(state.val)]); + this.setState(idParts[0], Math.round(state.val), this.statePath, this.playername, false); + } + } + if (idParts[0] == 'Power') { + if (state.val == 0) { + this.request(this.playerid, ['power', 0]); + } + if (state.val == 1) { + this.request(this.playerid, ['power', 1]); + } + } + if (idParts[0] == 'state') { + if (state.val == 0) { + this.request(this.playerid, ['pause', '1']); + } + if (state.val == 1) { + this.request(this.playerid, ['play', '2']); + } + if (state.val == 2) { + this.request(this.playerid, ['stop']); + } + } + if (idParts[0] == 'PlaylistRepeat') { + if (state.val == 0) { + this.request(this.playerid, ['playlist', 'repeat', '0']); + } + if (state.val == 1) { + this.request(this.playerid, ['playlist', 'repeat', '1']); + } + if (state.val == 2) { + this.request(this.playerid, ['playlist', 'repeat', '2']); + } + } + if (idParts[0] == 'PlaylistShuffle') { + if (state.val == 0) { + this.request(this.playerid, ['playlist', 'shuffle', '0']); + } + if (state.val == 1) { + this.request(this.playerid, ['playlist', 'shuffle', '1']); + } + if (state.val == 2) { + this.request(this.playerid, ['playlist', 'shuffle', '2']); + } + } + if (idParts[0] == 'cmdGoTime') { + // state.val needs to be a string, although the time is set in seconds + // when settings cmdGoTime via ioBroker.simple-api its not possible to submit a number as string + // if we receive a number, we need to cast this to a string + if (typeof state.val === 'number') { + state.val = state.val.toString(); + } + if (state.val.trim() !== '' && !isNaN(state.val.trim())) { + this.request(this.playerid, ['time', state.val.trim()]); + this.setState(idParts[0], ' ', this.statePath, this.playername, false); + } + } + if (idParts[0] == 'PlaylistCurrentIndex') { + if (state.val.trim() !== '' && !isNaN(state.val.trim())) { + this.request(this.playerid, ['playlist', 'index', state.val.trim()]); + } + } + if (idParts[0] == 'cmdPlayFavorite') { + if (state.val !== ' ') { + this.request(this.playerid, ['favorites', 'playlist', 'play', `item_id:${state.val}`]); + this.setState(idParts[0], ' ', this.statePath, this.playername, false); + } + } + if (idParts[0] == 'cmdPlayUrl') { + if (state.val !== ' ') { + this.request(this.playerid, ['playlist', 'play', state.val]); + this.setState(idParts[0], ' ', this.statePath, this.playername, false); + } + } + if (idParts[0] == 'cmdGeneral') { + if (state.val !== ' ') { + try { + const cmd = JSON.parse(`[${state.val}]`); + this.request(this.playerid, cmd); + } catch { + this.ioUtil.logerror(`cmdGeneral, Parameter error: ${state.val}`); + } + this.setState(idParts[0], ' ', this.statePath, this.playername, false); + } + } + if (idParts[0] == 'btnForward') { + if (state.val) { + this.request(this.playerid, ['button', 'jump_fwd']); + this.setState(idParts[0], false, this.statePath, this.playername, false); + } + } + if (idParts[0] == 'btnRewind') { + if (state.val) { + this.request(this.playerid, ['button', 'jump_rew']); + this.setState(idParts[0], false, this.statePath, this.playername, false); + } + } + if (idParts[0].startsWith('btnPreset_')) { + const name = `preset_${idParts[0].split('_')[1]}.single`; + if (state.val) { + this.request(this.playerid, ['button', name]); + this.setState(idParts[0], false, this.statePath, this.playername, false); + } + } + }; + /** + * Returns a URL for a given artworkid. + * + * @param artworkid The artworkid + * @returns The URL for the artwork + */ + this.createArtworkUrl = function (artworkid) { + return `http://${this.adapter.config.server}:${this.adapter.config.port}/music/${artworkid}/cover.jpg`; + }; + this.disconnect = () => { + /** + * Disconnects the player from the server. + * + * Logs the disconnection event, removes the player observer and clears the + * power state, and sets the connected flag to 0. + */ + this.ioUtil.logdebug(`Player ${this.playername} disconnected with id ${this.playerid}`); + clearInterval(this.observers['player']); + this.setState('Power', 0, this.statePath, this.playername, false); + delete this.observers['player']; + this.connected = 0; + }; + /** + * Connects the player to the server. + * + * Logs the connection event, starts the player observer and sets the + * connected flag to 1. + */ + this.connect = () => { + this.ioUtil.logdebug(`Player ${this.playername} connected with id ${this.playerid}`); + this.doObserverPlayer(); + this.connected = 1; + }; + /** + * Creates a state object for the player if it does not already exist. + * + * @param stateTemplate - The template containing the common properties of the state. + * @param [level1path] - The first level path to prepend to the state name, can be false or empty. + * @param [level2path] - The second level path to prepend to the state name, can be false or empty. + * @returns The state template with an 'exist' property set to true. + */ + this.createState = function (stateTemplate, level1path = false, level2path = false) { + const name = (level1path ? `${level1path}.` : '') + (level2path ? `${level2path}.` : '') + stateTemplate.name; + this.ioUtil.logsilly(`Create state ${name}`); + if (!this.currentStates[name]) { + this.adapter.createState(level1path, level2path, stateTemplate.name, stateTemplate); + } + stateTemplate.exist = true; + return stateTemplate; + }; + /** + * Creates a state object in the ioBroker system if it does not already exist. + * + * @param stateTemplate - The template containing the common properties of the state. + * @param level1path - The first level path to prepend to the state name, can be null or empty. + * @param level2path - The second level path to prepend to the state name, can be null or empty. + * @param [callback] - Optional callback function, called when the object is created. + * @returns The state template with an 'exist' property set to true. + */ + this.createObject = function (stateTemplate, level1path, level2path, callback) { + this.ioUtil.logdebug(`createObject ${stateTemplate.name}`); + const name = (level1path ? `${level1path}.` : '') + (level2path ? `${level2path}.` : '') + stateTemplate.name; + this.adapter.getObject(name, (err, obj) => { + const newobj = { + type: 'state', + common: stateTemplate, + native: {}, + }; + if (!obj) { + callback ? this.adapter.setObject(name, newobj, callback) : this.adapter.setObject(name, newobj); + } else { + if (callback) { + callback(); + } + } + }); + stateTemplate.exist = true; + return stateTemplate; + }; + /** + * Makes a request to the Logitech Media Server. + * + * @param playerid - The player ID to request from, or an empty string for the server. + * @param params - The parameters to pass to the request. + * @param [callback] - The function to call with the result of the request. + * + * If the request is successful, calls the callback function with the result. + * If the request fails, logs an error and does not call the callback function. + */ + this.request = function (playerid, params, callback) { + if (this.connected) { + this.server.request(playerid, params, callback); + } + }; + /** + * Sets the TPE2 handling value for the player. + * + * @param value - The value to set for TPE2 handling. + * + * This function updates the TPE2Handling property of the player to + * the provided value, which determines how the TPE2 tag is used + * in handling album artist metadata. + */ + this.setTPE2Handling = function (value) { + this.TPE2Handling = value; + }; + this.setState = function (name, value, level1path, level2path, check = true) { + name = (level1path ? `${level1path}.` : '') + (level2path ? `${level2path}.` : '') + name; + if (this.currentStates[name] !== value && check) { + this.currentStates[name] = value; + if (!name.includes('Time')) { + this.ioUtil.logsilly(`setState name: ${name} value: ${value}`); + } + this.adapter.setState(name, value, true); + } + if (!check) { + this.currentStates[name] = value; + this.ioUtil.logsilly(`setState name: ${name} value: ${value}`); + this.adapter.setState(name, value, true); + } + }; + this.createFolder = async function (foldername, level1path, level2path) { + const id = (level1path ? `${level1path}.` : '') + (level2path ? `${level2path}.` : '') + foldername; + this.ioUtil.logsilly(`createFolder ${id}`); + if (await this.existsObjectAsync(id)) { + this.ioUtil.logsilly(`Folder exists: ${id}`); + } else { + const obj = { + type: 'folder', + common: { + name: foldername, + }, + native: {}, + }; + this.adapter.setObject(id, obj); + } + }; + this.createDevice = async function (devicename, level1path, level2path) { + const id = (level1path ? `${level1path}.` : '') + (level2path ? `${level2path}.` : '') + devicename; + this.ioUtil.logsilly(`createDevice ${id}`); + if (await this.existsObjectAsync(id)) { + this.ioUtil.logsilly(`Device exists: ${id}`); + } else { + const obj = { + type: 'device', + common: { + name: devicename, + }, + native: {}, + }; + this.adapter.setObject(id, obj); + } + }; + this.existsObjectAsync = function (id) { + return new Promise((resolve, reject) => { + id = `${this.adapter.namespace}.${id}`; + + this.adapter.getForeignObject(id, (err, obj) => { + if (err) { + reject(err); + } else { + resolve(!!obj); + } + }); + }); + }; + this.convertState = function (stateTemplate, value) { + if (stateTemplate.type == 'string') { + return String(value); + } + if (stateTemplate.type == 'number') { + return Number(value); + } + this.ioUtil.logdebug(`Missing conversion function for type ${stateTemplate.type} Please report.`); + return value; + }; + this.logsilly = s => { + if (this.islogsilly) { + this.adapter.log.silly(s); + } + }; + this.logdebug = s => { + if (this.islogdebug) { + this.adapter.log.debug(s); + } + }; + this.logerror = function (s) { + this.adapter.log.error(s); + }; + this.loginfo = s => { + this.adapter.log.info(s); + }; + + this.init(playerdata); +} +module.exports = ioSBPlayer; diff --git a/lib/iosbserver.js b/lib/iosbserver.js index b13bba8..2515df9 100755 --- a/lib/iosbserver.js +++ b/lib/iosbserver.js @@ -1,877 +1,1310 @@ -"use strict"; - -//const utils = require('@iobroker/adapter-core'); -const dgram = require("dgram"); -const SqueezeServerNew = require(__dirname + "/squeezenode/squeezeserver"); -const ioSBPlayer = require(__dirname + "/iosbplayer"); - -function IoSbServer(adapter) { - - - this.sbServerStatus = { - "lastscan": { - name: "LastScan", - read: true, - write: false, - type: "string", - role: "value", - exist: false - }, - "version": { - name: "Version", - read: true, - write: false, - type: "string", - role: "value", - exist: false - }, - "uuid": { - name: "uuid", - read: true, - write: false, - type: "string", - role: "value", - exist: false - }, - "name": { - name: "Name", - read: true, - write: false, - type: "string", - role: "value", - exist: false - }, - "mac": { - name: "mac", - read: true, - write: false, - type: "string", - role: "info.mac", - exist: false - }, - "info total albums": { - name: "TotalAlbums", - read: true, - write: false, - type: "number", - role: "value", - exist: false - }, - "info total artists": { - name: "TotalArtists", - read: true, - write: false, - type: "number", - role: "value", - exist: false - }, - "info total genres": { - name: "TotalGenres", - read: true, - write: false, - type: "number", - role: "value", - exist: false - }, - "info total songs": { - name: "TotalSongs", - read: true, - write: false, - type: "number", - role: "value", - exist: false - }, - "info total duration": { - name: "TotalDuration", - read: true, - write: false, - type: "number", - role: "media.duration", - exist: false - }, - "player count": { - name: "PlayerCount", - read: true, - write: false, - type: "number", - role: "value", - exist: false - }, - "sn player count": { - name: "PlayerCountSN", - read: true, - write: false, - type: "number", - role: "value", - exist: false - }, - "other player count": { - name: "PlayerCountOther", - read: true, - write: false, - type: "number", - role: "value", - exist: false - }, - "syncgroups": { - name: "SyncGroups", - read: true, - write: false, - type: "string", - role: "value", - exist: false - }, - "otherServers": { - name: "otherServers", - read: true, - write: true, - type: "string", - role: "value", - exist: false - }, - "getFavorites": { - name: "getFavorites", - read: true, - write: true, - type: "boolean", - role: "button", - def: false, - exist: false - } - }; - this.sbFavoritesState = { - "name": { - name: "Name", - read: true, - write: false, - type: "string", - role: "value", - exist: false - }, - "type": { - name: "type", - read: true, - write: false, - type: "string", - role: "value", - exist: false - }, - "id": { - name: "id", - read: true, - write: false, - type: "string", - role: "value", - exist: false - }, - "hasitems": { - name: "hasitems", - read: true, - write: false, - type: "number", - role: "value", - exist: false - }, - "url": { - name: "url", - read: true, - write: false, - type: "string", - role: "media.url", - exist: false - }, - "image": { - name: "image", - read: true, - write: false, - type: "string", - role: "url.icon", - exist: false - }, - "isaudio": { - name: "isaudio", - read: true, - write: false, - type: "number", - role: "value", - exist: false - } - }; - - this.ServerStatePath = "Server"; - this.FavoritesStatePath = "Favorites"; - this.PlayersStatePath = "Players"; - - this.FORBIDDEN_CHARS = /[^\d\w_]+/gm; - //test the regex: https://regex101.com/r/Ed0WhH/1 - - this.currentStates = {}; - this.players = []; - this.observers = []; - - this.adapter = adapter; - - this.adapter.setState("info.connection", false, true); - this.adapter.subscribeStates("*"); - - this.sbServer = (adapter.config.username != "") ? new SqueezeServerNew("http://" + this.adapter.config.server, Number.parseInt(this.adapter.config.port), adapter.config.username, adapter.config.password) : new SqueezeServerNew("http://" + this.adapter.config.server, Number.parseInt(this.adapter.config.port)); - - this.log = {}; - this.islogsilly = this.adapter.config.outputserversilly; - this.islogdebug = this.adapter.config.outputserverdebug; - this.errmax = 5; - this.errcnt = -1; - this.connected = 0; - this.firstStart = true; - this.server = null; - this.telnet = null; - this.otherserver = []; - - this.init = async function () { - this.logdebug("server init"); - this.setState("connection", true, "info"); - this.doObserverServer(); - this.doObserverFavorites(); - this.doTelnet(); - }; - this.doObserverServer = function () { - this.logdebug("doObserverServer"); - this.getServerstatus(); - this.setTimeout("serverstatus", this.doObserverServer.bind(this), this.adapter.config.serverrefresh * 1000); - }; - this.processMessages = function (msg) { - this.logdebug("processMessages " + JSON.stringify(msg)); - if (msg.command === "discoverlms") { - this.logdebug("send discoverlms"); - this.discoverLMS(msg); - } - if (msg.command === "cdmGeneral") { - this.logdebug("send cdmGeneral"); - this.sendCmdGeneral(msg); - } - }; - this.sendCmdGeneral = async (msg) => { - this.logdebug("sendCmdGeneral " + JSON.stringify(msg)); - let error = ""; - if (typeof msg.message === "object") { - let data; - const playerid = msg.message.playerid || ""; - const command = msg.message.cmdArray; - if (!Array.isArray(command)) { - error += "cmdArray is not an array"; - } - if (error == "") { - try { - data = await this.requestAsync(playerid, command); - } catch { - error = "Problem with Server request"; - } - } - if (msg.callback && error == "") { - this.adapter.sendTo(msg.from, msg.command, data, msg.callback); - } else { - this.adapter.sendTo(msg.from, msg.command, `error: ${error}`, msg.callback); - } - } - }; - this.sanitizePlayername = (playername) => playername.replace(this.FORBIDDEN_CHARS, "_"); - this.discoverLMS = function (msg) { - this.logdebug("discoverLMS " + JSON.stringify(msg)); - const data = []; - for (const [, srv] of Object.entries(this.otherserver)) { - data.push({ label: `${srv.NAME}/${srv.ADDRESS}`, value: srv.ADDRESS }); - } - this.adapter.sendTo(msg.from, msg.command, [{ value: "", label: "---" }, ...data], msg.callback); - }; - this.setTimeout = function (id, callback, time) { - this.clearTimeout(id); - this.observers[id] = setTimeout(callback.bind(this), time); - }; - this.setInterval = function (id, callback, time) { - this.clearInterval(id); - this.observers[id] = setInterval(callback.bind(this), time); - }; - this.clearInterval = function (id) { - if (this.observers[id]) clearInterval(this.observers[id]); - delete this.observers[id]; - }; - this.clearTimeout = function (id) { - if (this.observers[id]) clearTimeout(this.observers[id]); - delete this.observers[id]; - }; - this.doObserverFavorites = function () { - this.logdebug("doObserverFavorites"); - this.delFavorites(); - if (this.adapter.config.usefavorites) { - this.setFavorites(); - return; - } - this.setTimeout("favorites", this.doObserverFavorites.bind(this), this.adapter.config.favoriterefresh * 60 * 1000); - }; - this.getDiscoverServers = () => { - this.logdebug("getDiscoverServers"); - const that = this; - if (!this.adapter.config.usediscovery) { - this.delState(this.ServerStatePath, this.sbServerStatus["otherServers"].name, function () { - that.delObject(that.ServerStatePath, that.sbServerStatus["otherServers"].name); - }); - return; - } - this.server = dgram.createSocket({ type: "udp4", reuseAddr: true }); - const broadcastPort = 3483; - this.server.bind(broadcastPort, "0.0.0.0", function () { - that.server && that.server.setBroadcast(true); - }.bind(this)); - this.server.on("message", async function (data, rinfo) { - that.logsilly("getDiscoverServers: Message resceived " + escape(data)); - if (data.toString().charAt() == "E") { - let msg = data.toString(); - msg = msg.substr(1); - const srv = {}; - let len = msg.length; - let tag, len2, val; - while (len > 0) { - tag = msg.substr(0, 4); - len2 = msg.charCodeAt(4); - val = msg.substr(5, len2); - msg = msg.substr(len2 + 5); - len = len - len2 - 5; - srv[tag] = val; - } - srv["ADDRESS"] = rinfo.address; - srv["TIMESTAMP"] = Date.now(); - that.otherserver[srv["ADDRESS"]] = srv; - const state = {}; - Object.assign(state, that.sbServerStatus["otherServers"]); - state.name = srv["ADDRESS"].replace(/\./g, "-"); - state.def = JSON.stringify(srv); - await that.createFolder(that.sbServerStatus["otherServers"].name, that.ServerStatePath); - that.createObject(state, that.ServerStatePath, that.sbServerStatus["otherServers"].name, function () { - that.setState(srv["ADDRESS"].replace(/\./g, "-"), JSON.stringify(srv), that.ServerStatePath, that.sbServerStatus["otherServers"].name, false); - }); - that.logdebug("Autodiscover: Server found " + srv["NAME"] + " IP: " + srv["ADDRESS"] + " Port " + srv["JSON"] + " UUID " + srv["UUID"]); - } - }.bind(this)); - this.server.on("error", function (err) { - that.logerror("Error with Server discovery " + err.message); - if (err.message.includes("EADDRINUSE")) { - that.loginfo("use 'lsof -i -P' to check for ports used."); - } - try { - that.server && that.server.close(function () { - that.loginfo("Server discovery deactivated."); - that.server = null; - }); - } catch (err) { - that.logerror("server.close() " + err.message); - } - }); - setTimeout(this.doDiscoverServerSearch, 1000); - }; - this.doDiscoverServerSearch = () => { - this.logdebug("doDiscoverServerSearch"); - const that = this; - const msg = Buffer.from("eIPAD\0NAME\0JSON\0UUID\0VERS"); - const broadcastAddress = "255.255.255.255"; - const broadcastPort = 3483; - this.getStates("*", this.ServerStatePath, this.sbServerStatus["otherServers"].name, function (err, states) { - for (const id in states) { - const srv = JSON.parse(states[id]?.val || "{}"); - if (((Date.now() - srv.TIMESTAMP) / 1000) > 60) { - that.delObject(id); - that.logdebug("Autodiscover: Server removed " + srv["NAME"] + " IP: " + srv["ADDRESS"] + " Port " + srv["JSON"] + " UUID " + srv["UUID"]); - } - } - }.bind(this)); - if (!this.server) return; - this.server.send(msg, - 0, - msg.length, - broadcastPort, - broadcastAddress, - function (err) { - if (err) that.log.error(err); - } - ); - this.setTimeout("discover", this.doDiscoverServerSearch.bind(this), this.adapter.config.discoveryrefresh * 1000); - }; - this.doDiscoverServerClose = function () { - this.logdebug("doDiscoverServerClose"); - this.server && this.server.close(); - }; - this.doTelnet = () => { - this.logdebug("doTelnet"); - if (!this.adapter.config.usetelnet) return; - const net = require("net"); - this.telnet = net.createConnection({ host: this.adapter.config.server, port: this.adapter.config.telnetport }, () => { - this.logdebug("doTelnet connected to server!"); - this.telnet.write("login " + this.adapter.config.username + " " + this.adapter.config.password + "\r\n"); - this.telnet.write("listen 1\r\n"); - }); - this.telnet.on("data", (data) => { - this.logdebug("doTelnet received Data: " + data.toString()); - //console.log(decodeURIComponent(data.toString())); - const regex = /^((?:[0-9A-Fa-f]{2}[:-]){5}(?:[0-9A-Fa-f]{2}))\s*(.*)/gm; - let m; - const cmdQueue = decodeURIComponent(data.toString()).split("\r\n"); - for (const cmd of cmdQueue) { - - if ((m = regex.exec(cmd)) !== null) { - if (m[2] == "client reconnect") { - this.logdebug("doTelnet received reconnect for : " + m[1]); - this.getServerstatus(); - } - if (m[2] == "client disconnect") { - this.logdebug("doTelnet received disconnect for : " + m[1]); - this.getServerstatus(); - } - if (m[2] == "client new") { - this.logdebug("doTelnet received disconnect for : " + m[1]); - this.getServerstatus(); - } - if (m[2] == "power 0") { - this.logdebug("doTelnet received power off : " + m[1]); - this.getServerstatus(); - } - if (m[2] == "power 1") { - this.logdebug("doTelnet received power on : " + m[1]); - this.getServerstatus(); - } - } - } - }); - this.telnet.on("end", () => { - this.logdebug("doTelnet disconnected from server"); - }); - this.telnet.on("timeout", () => { - this.logdebug("doTelnet Socket timeout, closing"); - this.telnet.close(); - }); - this.telnet.on("error", (err) => { - this.logdebug("doTelnet Socket error: " + err.message); - }); - this.telnet.on("close", (err) => { - this.logdebug("doTelnet Socket closed " + err.message); - }); - }; - this.doTelnetClose = function () { - this.logdebug("doTelnetClose"); - this.telnet.end(); - }; - this.closeConnections = function () { - this.logdebug("closeConnections"); - if (this.adapter.config.usetelnet) this.doTelnetClose(); - if (this.adapter.config.usediscovery) this.doDiscoverServerClose(); - }; - this.getServerstatus = function () { - this.logdebug("getServerstatus"); - this.request("", ["serverstatus", "0", "888"], this.doServerstatus.bind(this)); - this.request("", ["syncgroups", "?"], this.doSyncgroups.bind(this)); - }; - this.doSyncgroups = function (result) { - this.logdebug("doSyncgroups"); - if (result.result && result.result.syncgroups_loop) { - const key = "syncgroups"; - const stateTemplate = this.sbServerStatus[key]; - if (result.result?.syncgroups_loop?.[0]?.sync_member_names) { - result.result.syncgroups_loop[0].sync_member_names = result.result?.syncgroups_loop?.[0]?.sync_member_names.split(",").map(el => this.sanitizePlayername(el)); - } - this.createObject(stateTemplate, this.ServerStatePath, null, - () => this.setState(this.sbServerStatus[key].name, JSON.stringify(result.result.syncgroups_loop), this.ServerStatePath) - ); - } - }; - this.doServerstatus = function (result) { - this.logdebug("doServerstatus"); - this.checkServerstatusStates(result); - for (const key in this.sbServerStatus) { - if (Object.prototype.hasOwnProperty.call(result.result, key)) { - const stateTemplate = this.sbServerStatus[key]; - this.createObject(stateTemplate, this.ServerStatePath, null, - () => this.setState(this.sbServerStatus[key].name, this.convertState(this.sbServerStatus[key], result.result[key]), this.ServerStatePath) - ); - } - } - this.checkNewPlayer(result.result.players_loop); - this.checkPlayer(result.result.players_loop); - }; - this.setFavorites = async function () { - this.logdebug("setFavorites"); - this.getFavoritesLMS("", - (result) => this.setFavoritesDP(result) - ); - }; - this.delFavorites = async function () { - this.logdebug("delFavorites"); - let favobj = await this.getObjects(this.FavoritesStatePath); - favobj = favobj.rows.filter(obj => obj.value.type === "folder"); - for (let i = 0; i < favobj.length; i++) { - await this.adapter.delForeignObjectAsync(favobj[i].id); - } - }; - this.getFavoritesLMS = function (id = "", callback) { - this.logdebug("getFavoritesLMS"); - this.request("", ["favorites", "items", "0", "888", "want_url:1", "item_id:" + id], - (result) => callback(result.result.loop_loop)//.bind(this); - ); - }; - this.setFavoritesDP = async function (favorites) { - this.logdebug("setFavoritesDP"); - await this.createFolder(this.FavoritesStatePath); - for (const favkey in favorites) { - const favorite = favorites[favkey]; - const oid = this.getFavId(favorite["id"], false); - const id = this.getFavId(favorite["id"]); - for (const key in this.sbFavoritesState) { - if (Object.prototype.hasOwnProperty.call(favorite, key)) { - if (key == "id") favorite["id"] = oid; - if (key == "image") { - let result = "http://" + this.adapter.config.server + ":" + this.adapter.config.port + "/html/images/favorites.png"; - if (/music\/(.*)\/cover.png/gm.test(favorite[key])) { - result = "http://" + this.adapter.config.server + ":" + this.adapter.config.port + "/" + favorite[key]; - } - else if (/imageproxy\/(.*)\/[^/]/gm.test(favorite[key])) { - const m = /imageproxy\/(.*)\/[^/]/gm.exec(favorite[key]); - if (m && m[1]) { - result = decodeURIComponent(m[1] || ""); - } - } - else if (favorite[key] === "html/images/radio.png") { - result = "http://" + this.adapter.config.server + ":" + this.adapter.config.port + "/html/images/radio.png"; - } - else if (favorite[key] === "html/images/cover.png") { - result = "http://" + this.adapter.config.server + ":" + this.adapter.config.port + "/html/images/cover.png"; - } - else if (favorite[key] === "html/images/favorites.png") { - result = "http://" + this.adapter.config.server + ":" + this.adapter.config.port + "/html/images/favorites.png"; - } - favorite[key] = result; - } - const stateTemplate = this.sbFavoritesState[key]; - await this.createFolder(id, this.FavoritesStatePath); - this.createObject(stateTemplate, this.FavoritesStatePath, id, - () => this.setState(this.sbFavoritesState[key].name, this.convertState(this.sbFavoritesState[key], favorite[key]), this.FavoritesStatePath, id, false) - ); - } - } - if (favorite["hasitems"] > 0) this.getFavoritesLMS(oid, - (result) => this.setFavoritesDP(result) - ); - } - }; - this.getFavId = function (id, replace = true) { - let ret; - if (id.indexOf(".") == 8) { - ret = id.substr(9); - } else { - ret = id; - } - if (replace) ret = ret.replace(/\./g, "-"); - return ret; - }; - this.checkServerstatusStates = async function (result) { - this.logdebug("checkServerstatusStates"); - await this.createDevice(this.ServerStatePath); - for (const key in this.sbServerStatus) { - if (Object.prototype.hasOwnProperty.call(result.result, key)) { - const stateTemplate = this.sbServerStatus[key]; - if (!this.currentStates[stateTemplate.name]) { - if (!stateTemplate.exist) { - this.sbServerStatus[key] = this.createObject(stateTemplate, this.ServerStatePath); - } - } - } - } - let key = "getFavorites"; - let stateTemplate = this.sbServerStatus[key]; - if (!stateTemplate.exist) { - this.sbServerStatus[key] = this.createObject(stateTemplate, this.ServerStatePath); - } - key = "syncgroups"; - stateTemplate = this.sbServerStatus[key]; - if (!stateTemplate.exist) { - this.sbServerStatus[key] = this.createObject(stateTemplate, this.ServerStatePath); - } - }; - this.checkNewPlayer = function (playersdata) { - this.logdebug("checkNewPlayer"); - for (const key in playersdata) { - if (!Object.prototype.hasOwnProperty.call(this.players, playersdata[key].playerid)) { - this.players[playersdata[key].playerid] = new ioSBPlayer(this, playersdata[key]); - } - } - }; - this.checkPlayer = function (playersdata) { - this.logdebug("checkPlayer"); - this.checkTPE2(); - for (const key in playersdata) { - if (this.players[playersdata[key].playerid].connected != playersdata[key].connected) { - if (playersdata[key].connected == 0) { - this.players[playersdata[key].playerid].disconnect(); - } - if (playersdata[key].connected == 1) { - this.players[playersdata[key].playerid].connect(); - } - } - } - }; - this.checkTPE2 = function () { - const that = this; - this.request("", ["pref", "useTPE2AsAlbumArtist", "?"], function (result) { - if (result.ok) { - if (Object.keys(result.result).length > 0) { - const value = result.result[Object.keys(result.result)[0]]; - for (const key in that.players) { - if (Object.prototype.hasOwnProperty.call(that.players, key)) { - // console.log("setTPE2Handling", key, value); - that.players[key].setTPE2Handling(parseInt(value)); - } - } - } - } - }.bind(this)); - }; - this.stateChange = function (id, state) { - this.logsilly("stateChange"); - // Warning, state can be null if it was deleted - if (!id || !state || state.ack) { - return; - } - const idParts = id.split("."); - idParts.shift(); - idParts.shift(); - if (idParts[0] == this.ServerStatePath) this.doServerStateChange(idParts, state); - if (idParts[0] == this.FavoritesStatePath) this.doFavoritesStateChange(idParts, state); - if (idParts[0] == this.PlayersStatePath) this.doPlayersStateChange(idParts, state); - }; - this.doServerStateChange = function (idParts, state) { - this.logdebug("doServerStateChange"); - idParts.shift(); - if (idParts[0] == "getFavorites") { - if (state.val == true) { - this.delFavorites(); - this.setFavorites(); - } - } - }; - this.doFavoritesStateChange = function (idParts, state) { - this.logdebug("doFavoritesStateChange"); - idParts.shift(); - if (state) return; - }; - this.doPlayersStateChange = function (idParts, state) { - this.logsilly("doPlayersStateChange"); - idParts.shift(); - const player = this.findPlayerByName(idParts[0]); - if (player) player.doStateChange(idParts, state); - }; - this.findPlayerByName = function (name) { - for (const key in this.players) { - if (this.players[key].playername == name) return this.players[key]; - } - }; - this.request = function (playerid, params, callback) { - this.logsilly("request"); - const that = this; - this.sbServer.request(playerid, params, function (callback, result) { - if (result.ok) { - this.connected = 1; - this.errcnt = -1; - this.firstStart = false; - if (callback) callback(result); - } else { - if (this.firstStart) that.disconnect(); - if (this.errcnt == -1) this.errcnt = that.errmax; - that.errcnt--; - if (this.errcnt == 0) { - this.errcnt = -1; - that.disconnect(); - } - } - }.bind(this, callback)); - }; - this.requestAsync = async (playerid, params) => { - this.logsilly("requestAsync"); - return new Promise((resolve, reject) => { - this.sbServer.request(playerid, params, (result) => { - if (result.ok) { - this.connected = 1; - this.errcnt = -1; - this.firstStart = false; - resolve(result); - } else { - if (this.firstStart) this.disconnect(); - if (this.errcnt == -1) this.errcnt = this.errmax; - this.errcnt--; - if (this.errcnt == 0) { - this.errcnt = -1; - this.disconnect(); - } - reject(); - } - }); - }); - }; - this.disconnect = () => { - this.logdebug("Server disconnect"); - this.firstStart = false; - this.connected = 0; - this.clearTimeout("serverstatus"); - this.clearTimeout("favorites"); - if (this.connected == 0 && this.observers["checkserver"]) return; - this.setState("connection", false, "info"); - for (const key in this.players) { - this.players[key].disconnect(); - } - this.setInterval("checkserver", this.doCheckServer.bind(this), 10 * 1000); - }; - this.connect = () => { - this.logdebug("Server connect"); - this.clearInterval("checkserver"); - this.setState("connection", true, "info"); - this.connected = 1; - this.init(); - }; - this.doCheckServer = () => { - const that = this; - this.logdebug("doCheckServer"); - this.request("", ["serverstatus", "0", "888"], function (result) { - if (result.ok) { - that.connect(); - } - }.bind(this)); - }; - this.setState = function (name, value, level1path, level2path, check = true, callback) { - name = (level1path ? level1path + "." : "") + (level2path ? level2path + "." : "") + name; - if (this.currentStates[name] !== value && check) { - this.currentStates[name] = value; - this.logsilly("setState name: " + name + " value: " + value); - this.adapter.setState(name, value, true, callback); - } else { - this.currentStates[name] = value; - this.logsilly("setState name: " + name + " value: " + value); - this.adapter.setState(name, value, true, callback); - } - }; - this.getState = function (name, level1path = false, level2path = false, callback) { - name = (level1path ? level1path + "." : "") + (level2path ? level2path + "." : "") + name; - this.logsilly("getState " + name); - this.adapter.getState(name, callback); - }; - this.delObject = function (name, level1path, level2path, callback) { - name = (level1path ? level1path + "." : "") + (level2path ? level2path + "." : "") + name; - this.logsilly("delObject " + name); - delete this.currentStates[name]; - this.adapter.delObject(name, callback); - }; - this.delState = function (name, level1path, level2path, callback) { - name = (level1path ? level1path + "." : "") + (level2path ? level2path + "." : "") + name; - this.logsilly("delObject " + name); - delete this.currentStates[name]; - this.adapter.delState(name, callback); - }; - this.getStates = function (pattern, level1path, level2path, callback) { - const name = (level1path ? level1path + "." : "") + (level2path ? level2path + "." : "") + pattern; - this.adapter.getStates(name, callback); - }; - this.getObjects = async function (level1path, level2path) { - const name = this.adapter.namespace + "." + (level1path ? level1path + "." : "") + (level2path ? level2path : ""); - return await this.adapter.getObjectListAsync({ - startkey: name, - endkey: name + "\u9999" - }); - }; - this.createObject = function (stateTemplate, level1path, level2path, callback) { - const name = (level1path ? level1path + "." : "") + (level2path ? level2path + "." : "") + stateTemplate.name; - this.logsilly("createObject " + name); - this.adapter.getObject(name, (err, obj) => { - const newobj = { - type: "state", - common: stateTemplate, - native: {} - }; - if (!obj) { - (callback) ? this.adapter.setObject(name, newobj, callback) : this.adapter.setObject(name, newobj); - } else { - if (callback) callback(); - } - }); - stateTemplate.exist = true; - return stateTemplate; - }; - this.createState = function (stateTemplate, level1path = false, level2path = false, callback) { - const name = (level1path ? level1path + "." : "") + (level2path ? level2path + "." : "") + stateTemplate.name; - this.logsilly("Create state " + name); - this.adapter.createState(level1path, level2path, stateTemplate.name, stateTemplate, callback); - stateTemplate.exist = true; - return stateTemplate; - }; - this.createFolder = async function (foldername, level1path, level2path) { - const id = (level1path ? level1path + "." : "") + (level2path ? level2path + "." : "") + foldername; - this.logsilly("createFolder " + id); - if (await this.existsObjectAsync(id)) { - this.logsilly("Folder exists: " + id); - } else { - const obj = { - type: "folder", - common: { - name: foldername - }, - native: {} - }; - this.adapter.setObject(id, obj); - } - }; - this.createDevice = async function (devicename, level1path, level2path) { - const id = (level1path ? level1path + "." : "") + (level2path ? level2path + "." : "") + devicename; - this.logsilly("createDevice " + id); - if (await this.existsObjectAsync(id)) { - this.logsilly("Device exists: " + id); - } else { - const obj = { - type: "device", - common: { - name: devicename - }, - native: {} - }; - this.adapter.setObject(id, obj); - } - }; - this.existsObjectAsync = function (id) { - return new Promise((resolve, reject) => { - id = this.adapter.namespace + "." + id; - - this.adapter.getForeignObject(id, (err, obj) => { - if (err) { - reject(err); - } else { - resolve(!!obj); - } - - }); - }); - }; - this.convertState = function (stateTemplate, value) { - if (stateTemplate.type == "string") return String(value); - if (stateTemplate.type == "number") return Number(value); - this.logdebug("Missing conversion function for type " + stateTemplate.type + " Please report."); - return value; - }; - this.logsilly = (s) => { - if (this.islogsilly) this.adapter.log.silly(s); - }; - this.logdebug = (s) => { - if (this.islogdebug) this.adapter.log.debug(s); - }; - this.logerror = function (s) { - // @ts-ignore - this.adapter.log.error(s); - }; - this.loginfo = (s) => { - this.adapter.log.info(s); - }; - this.getDiscoverServers(); - this.sbServer.on("register", this.init.bind(this)); -} -module.exports = IoSbServer; +'use strict'; + +const dgram = require('dgram'); +const { ioUtil } = require('./ioUtil'); +const SqueezeServerNew = require(`${__dirname}/squeezenode/squeezeserver`); +const ioSBPlayer = require(`${__dirname}/iosbplayer`); + +/** + * IoSbServer is a constructor function that initializes and manages a server + * for interacting with SqueezeBox devices. It sets up various server states, + * handles server and player discovery, manages favorites, and communicates + * with the SqueezeBox server via telnet and HTTP requests. + * + * @param adapter - An instance of the adapter to manage states and configurations. + */ +function IoSbServer(adapter) { + this.ioUtil = new ioUtil(adapter, adapter.config.outputserverdebug, adapter.config.outputserversilly); + + this.sbServerStatus = { + lastscan: { + name: 'LastScan', + read: true, + write: false, + type: 'string', + role: 'value', + exist: false, + }, + version: { + name: 'Version', + read: true, + write: false, + type: 'string', + role: 'value', + exist: false, + }, + uuid: { + name: 'uuid', + read: true, + write: false, + type: 'string', + role: 'value', + exist: false, + }, + name: { + name: 'Name', + read: true, + write: false, + type: 'string', + role: 'value', + exist: false, + }, + mac: { + name: 'mac', + read: true, + write: false, + type: 'string', + role: 'info.mac', + exist: false, + }, + 'info total albums': { + name: 'TotalAlbums', + read: true, + write: false, + type: 'number', + role: 'value', + exist: false, + }, + 'info total artists': { + name: 'TotalArtists', + read: true, + write: false, + type: 'number', + role: 'value', + exist: false, + }, + 'info total genres': { + name: 'TotalGenres', + read: true, + write: false, + type: 'number', + role: 'value', + exist: false, + }, + 'info total songs': { + name: 'TotalSongs', + read: true, + write: false, + type: 'number', + role: 'value', + exist: false, + }, + 'info total duration': { + name: 'TotalDuration', + read: true, + write: false, + type: 'number', + role: 'media.duration', + exist: false, + }, + 'player count': { + name: 'PlayerCount', + read: true, + write: false, + type: 'number', + role: 'value', + exist: false, + }, + 'sn player count': { + name: 'PlayerCountSN', + read: true, + write: false, + type: 'number', + role: 'value', + exist: false, + }, + 'other player count': { + name: 'PlayerCountOther', + read: true, + write: false, + type: 'number', + role: 'value', + exist: false, + }, + syncgroups: { + name: 'SyncGroups', + read: true, + write: false, + type: 'string', + role: 'value', + exist: false, + }, + otherServers: { + name: 'otherServers', + read: true, + write: true, + type: 'string', + role: 'value', + exist: false, + }, + getFavorites: { + name: 'getFavorites', + read: true, + write: true, + type: 'boolean', + role: 'button', + def: false, + exist: false, + }, + }; + this.sbFavoritesState = { + name: { + name: 'Name', + read: true, + write: false, + type: 'string', + role: 'value', + exist: false, + }, + type: { + name: 'type', + read: true, + write: false, + type: 'string', + role: 'value', + exist: false, + }, + id: { + name: 'id', + read: true, + write: false, + type: 'string', + role: 'value', + exist: false, + }, + hasitems: { + name: 'hasitems', + read: true, + write: false, + type: 'number', + role: 'value', + exist: false, + }, + url: { + name: 'url', + read: true, + write: false, + type: 'string', + role: 'media.url', + exist: false, + }, + image: { + name: 'image', + read: true, + write: false, + type: 'string', + role: 'url.icon', + exist: false, + }, + isaudio: { + name: 'isaudio', + read: true, + write: false, + type: 'number', + role: 'value', + exist: false, + }, + }; + + this.ServerStatePath = 'Server'; + this.FavoritesStatePath = 'Favorites'; + this.PlayersStatePath = 'Players'; + + this.FORBIDDEN_CHARS = /[^\d\w_]+/gm; + //test the regex: https://regex101.com/r/Ed0WhH/1 + + this.currentStates = {}; + this.players = []; + this.observers = []; + + this.adapter = adapter; + + this.adapter.setState('info.connection', false, true); + this.adapter.subscribeStates('*'); + + this.sbServer = + adapter.config.username != '' + ? new SqueezeServerNew( + `http://${this.adapter.config.server}`, + Number.parseInt(this.adapter.config.port), + adapter.config.username, + adapter.config.password, + ) + : new SqueezeServerNew(`http://${this.adapter.config.server}`, Number.parseInt(this.adapter.config.port)); + + this.log = {}; + + this.errmax = 5; + this.errcnt = -1; + this.connected = 0; + this.firstStart = true; + this.server = null; + this.telnet = null; + this.otherserver = []; + + /** + * Initialization function of the Server object. + * + * @async + * @description + * This function is called when the adapter object is created and + * initializes the IoSbServer object. It sets up the connection state + * and starts the server state and favorites observers. + */ + this.init = async function () { + this.ioUtil.logdebug('server init'); + this.setState('connection', true, 'info'); + this.doObserverServer(); + this.doObserverFavorites(); + this.doTelnet(); + }; + /** + * Observes the server status at regular intervals. + * + * @description + * Logs the execution of the observer, retrieves the current server status, + * and schedules the next execution of the observer based on the configured + * server refresh interval in seconds. + */ + this.doObserverServer = function () { + this.ioUtil.logdebug('doObserverServer'); + this.getServerstatus(); + this.setTimeout('serverstatus', this.doObserverServer.bind(this), this.adapter.config.serverrefresh * 1000); + }; + /** + * Processes incoming messages and executes corresponding commands. + * + * @param msg - The message object containing command and optional data. + * @description Logs the received message and executes specific functions based on the command. + * For 'discoverlms', it initiates server discovery. For 'cmdGeneral', it sends a general command + * to the LMS server. + */ + this.processMessages = function (msg) { + this.ioUtil.logdebug(`processMessages ${JSON.stringify(msg)}`); + if (msg.command === 'discoverlms') { + this.ioUtil.logdebug('send discoverlms'); + this.discoverLMS(msg.command, msg.message, msg.from, msg.callback); + } + if (msg.command === 'cmdGeneral') { + this.ioUtil.logdebug('send cmdGeneral'); + this.sendCmdGeneral(msg.command, msg.message, msg.from, msg.callback); + } + }; + + /** + * Processes a general command message to the Logitech Media Server. + * + * @param command - The command identifier, should be 'cmdGeneral'. + * @param message - The message object containing a playerid and a cmdArray. + * @param from - The sender of the message. + * @param callback - The callback function to be called with the result. + * @description + * Logs the received message and executes a request to the LMS server + * with the provided playerid and cmdArray. If the request is successful, + * it sends the result to the callback function. If an error occurs, it + * sends an error message to the callback function. + */ + this.sendCmdGeneral = async (command, message, from, callback) => { + this.ioUtil.logdebug(`sendCmdGeneral ${JSON.stringify(message)}`); + let error = ''; + if (typeof message === 'object') { + let data; + const playerid = message.playerid || ''; + const cmd = message.cmdArray; + if (!Array.isArray(cmd)) { + error += 'cmdArray is not an array'; + } + if (error == '') { + try { + data = await this.requestAsync(playerid, cmd); + } catch { + error = 'Problem with Server request'; + } + } + if (callback && error == '') { + this.adapter.sendTo(from, command, data, callback); + } else { + this.adapter.sendTo(from, command, `error: ${error}`, callback); + } + } + }; + + this.sanitizePlayername = playername => playername.replace(this.FORBIDDEN_CHARS, '_'); + + /** + * Returns a list of other Logitech Media Servers discovered by the current + * instance of the adapter. This function is called from the Admin interface + * + * @param command - The command identifier, should be 'discoverlms'. + * @param message - The message object containing no data. + * @param from - The adapter instance ID that sent the message. + * @param callback - The callback function to send the response to. + * @description + * Logs the received message, processes the discovered servers and sends a + * response back to the adapter instance that sent the message. + */ + this.discoverLMS = function (command, message, from, callback) { + this.ioUtil.logdebug(`discoverLMS ${JSON.stringify(message)}`); + const data = []; + for (const [, srv] of Object.entries(this.otherserver)) { + data.push({ label: `${srv.NAME}/${srv.ADDRESS}`, value: srv.ADDRESS }); + } + this.adapter.sendTo(from, command, [{ value: '', label: '---' }, ...data], callback); + }; + /** + * Sets a timeout for a specified callback function and stores it in the observers object. + * + * @param id - Unique identifier for the timeout, used to manage and clear the timeout. + * @param callback - The function to execute after the specified time delay. + * @param time - The delay in milliseconds before executing the callback. + */ + this.setTimeout = function (id, callback, time) { + this.clearTimeout(id); + this.observers[id] = setTimeout(callback.bind(this), time); + }; + + /** + * Sets an interval for a specified callback function and stores it in the observers object. + * + * @param id - Unique identifier for the interval, used to manage and clear the interval. + * @param callback - The function to execute after the specified time delay. + * @param time - The delay in milliseconds before executing the callback. + */ + this.setInterval = function (id, callback, time) { + this.clearInterval(id); + this.observers[id] = setInterval(callback.bind(this), time); + }; + /** + * Clears an interval that was previously set using setInterval. + * + * @param id - Unique identifier for the interval to clear. + */ + this.clearInterval = function (id) { + if (this.observers[id]) { + clearInterval(this.observers[id]); + } + delete this.observers[id]; + }; + this.clearTimeout = function (id) { + if (this.observers[id]) { + clearTimeout(this.observers[id]); + } + delete this.observers[id]; + }; + /** + * Observes the favorites of the player and updates the favorites datapoints. + * + * If the user has set the "Use Favorites" option in the adapter settings, the + * favorites are queried from the Logitech Media Server and the datapoints are + * updated using the setFavorites method. + * + * If the user has not set the "Use Favorites" option, the favorites are deleted + * and the function is called again after a certain time using the setTimeout + * method. + */ + this.doObserverFavorites = function () { + this.ioUtil.logdebug('doObserverFavorites'); + this.delFavorites(); + if (this.adapter.config.usefavorites) { + this.setFavorites(); + return; + } + this.setTimeout( + 'favorites', + this.doObserverFavorites.bind(this), + this.adapter.config.favoriterefresh * 60 * 1000, + ); + }; + /** + * Gets a list of other Logitech Media Servers discovered by the current + * instance. + * + * If the user has set the "Use Discovery" option in the adapter settings, + * a UDP socket is created and bound to a port. The socket listens for + * incoming messages and processes them to detect other Logitech Media + * Servers. For each detected server, a datapoint is created with the + * server's IP address as the name and the server's configuration as the + * value. + * + * If the user has not set the "Use Discovery" option, the datapoints for + * other servers are deleted and the function is exited. + * + */ + this.getDiscoverServers = () => { + this.ioUtil.logdebug('getDiscoverServers'); + if (!this.adapter.config.usediscovery) { + this.delState(this.ServerStatePath, this.sbServerStatus['otherServers'].name, () => { + this.delObject(this.ServerStatePath, this.sbServerStatus['otherServers'].name); + }); + return; + } + this.server = dgram.createSocket({ type: 'udp4', reuseAddr: true }); + const broadcastPort = 3483; + this.server.bind(broadcastPort, '0.0.0.0', () => { + this.server && this.server.setBroadcast(true); + }); + this.server.on('message', async (data, rinfo) => { + this.ioUtil.logsilly(`getDiscoverServers: Message resceived ${escape(data.toString())}`); + if (data.toString().charAt(0) == 'E') { + let msg = data.toString(); + msg = msg.substr(1); + const srv = {}; + let len = msg.length; + let tag, len2, val; + while (len > 0) { + tag = msg.substr(0, 4); + len2 = msg.charCodeAt(4); + val = msg.substr(5, len2); + msg = msg.substr(len2 + 5); + len = len - len2 - 5; + srv[tag] = val; + } + srv['ADDRESS'] = rinfo.address; + srv['TIMESTAMP'] = Date.now(); + this.otherserver[srv['ADDRESS']] = srv; + const state = {}; + Object.assign(state, this.sbServerStatus['otherServers']); + state.name = srv['ADDRESS'].replace(/\./g, '-'); + state.def = JSON.stringify(srv); + await this.createFolder(this.sbServerStatus['otherServers'].name, this.ServerStatePath); + this.createObject(state, this.ServerStatePath, this.sbServerStatus['otherServers'].name, () => { + this.setState( + srv['ADDRESS'].replace(/\./g, '-'), + JSON.stringify(srv), + this.ServerStatePath, + this.sbServerStatus['otherServers'].name, + false, + ); + }); + this.ioUtil.logdebug( + `Autodiscover: Server found ${srv['NAME']} IP: ${srv['ADDRESS']} Port ${srv['JSON']} UUID ${ + srv['UUID'] + }`, + ); + } + }); + this.server.on('error', err => { + this.ioUtil.logerror(`Error with Server discovery ${err.message}`); + if (err.message.includes('EADDRINUSE')) { + this.ioUtil.loginfo("use 'lsof -i -P' to check for ports used."); + } + try { + this.server && + this.server.close(() => { + this.ioUtil.loginfo('Server discovery deactivated.'); + this.server = null; + }); + } catch (err) { + this.ioUtil.logerror(`server.close() ${err.message}`); + } + }); + setTimeout(this.doDiscoverServerSearch, 1000); + }; + /** + * Called periodically to send a broadcast message to find other servers, + * and remove old entries from the list. + * + */ + this.doDiscoverServerSearch = () => { + this.ioUtil.logdebug('doDiscoverServerSearch'); + const msg = Buffer.from('eIPAD\0NAME\0JSON\0UUID\0VERS'); + const broadcastAddress = '255.255.255.255'; + const broadcastPort = 3483; + this.getStates('*', this.ServerStatePath, this.sbServerStatus['otherServers'].name, (err, states) => { + for (const id in states) { + const srv = JSON.parse(states[id]?.val || '{}'); + if ((Date.now() - srv.TIMESTAMP) / 1000 > 60) { + this.delObject(id); + this.ioUtil.logdebug( + `Autodiscover: Server removed ${srv['NAME']} IP: ${srv['ADDRESS']} Port ${ + srv['JSON'] + } UUID ${srv['UUID']}`, + ); + } + } + }); + if (!this.server) { + return; + } + this.server.send(msg, 0, msg.length, broadcastPort, broadcastAddress, err => { + if (err) { + this.ioUtil.logerror(err); + } + }); + this.setTimeout( + 'discover', + this.doDiscoverServerSearch.bind(this), + this.adapter.config.discoveryrefresh * 1000, + ); + }; + /** + * Close the UDP server for discovery mode. + * + */ + this.doDiscoverServerClose = function () { + this.ioUtil.logdebug('doDiscoverServerClose'); + this.server && this.server.close(); + }; + /** + * Connect to the LMS server via Telnet, login and listen for player events. + * + */ + this.doTelnet = () => { + this.ioUtil.logdebug('doTelnet'); + if (!this.adapter.config.usetelnet) { + return; + } + const net = require('net'); + this.telnet = net.createConnection( + { host: this.adapter.config.server, port: this.adapter.config.telnetport }, + () => { + this.ioUtil.logdebug('doTelnet connected to server!'); + this.telnet.write(`login ${this.adapter.config.username} ${this.adapter.config.password}\r\n`); + this.telnet.write('listen 1\r\n'); + }, + ); + this.telnet.on('data', data => { + this.ioUtil.logdebug(`doTelnet received Data: ${data.toString()}`); + //console.log(decodeURIComponent(data.toString())); + const regex = /^((?:[0-9A-Fa-f]{2}[:-]){5}(?:[0-9A-Fa-f]{2}))\s*(.*)/gm; + let m; + const cmdQueue = decodeURIComponent(data.toString()).split('\r\n'); + for (const cmd of cmdQueue) { + if ((m = regex.exec(cmd)) !== null) { + if (m[2] == 'client reconnect') { + this.ioUtil.logdebug(`doTelnet received reconnect for : ${m[1]}`); + this.getServerstatus(); + } + if (m[2] == 'client disconnect') { + this.ioUtil.logdebug(`doTelnet received disconnect for : ${m[1]}`); + this.getServerstatus(); + } + if (m[2] == 'client new') { + this.ioUtil.logdebug(`doTelnet received disconnect for : ${m[1]}`); + this.getServerstatus(); + } + if (m[2] == 'power 0') { + this.ioUtil.logdebug(`doTelnet received power off : ${m[1]}`); + this.getServerstatus(); + } + if (m[2] == 'power 1') { + this.ioUtil.logdebug(`doTelnet received power on : ${m[1]}`); + this.getServerstatus(); + } + } + } + }); + this.telnet.on('end', () => { + this.ioUtil.logdebug('doTelnet disconnected from server'); + }); + this.telnet.on('timeout', () => { + this.ioUtil.logdebug('doTelnet Socket timeout, closing'); + this.telnet.close(); + }); + this.telnet.on('error', err => { + this.ioUtil.logdebug(`doTelnet Socket error: ${err.message}`); + }); + this.telnet.on('close', err => { + this.ioUtil.logdebug(`doTelnet Socket closed ${err.message}`); + }); + }; + /** + * Closes the Telnet connection by ending the socket connection with the server. + * Logs the action for debugging purposes. + */ + this.doTelnetClose = function () { + this.ioUtil.logdebug('doTelnetClose'); + this.telnet.end(); + }; + + /** + * Closes all active connections by invoking the appropriate closing + * methods for Telnet and Discovery servers based on the adapter configuration. + * Logs the action for debugging purposes. + */ + this.closeConnections = function () { + this.ioUtil.logdebug('closeConnections'); + if (this.adapter.config.usetelnet) { + this.doTelnetClose(); + } + if (this.adapter.config.usediscovery) { + this.doDiscoverServerClose(); + } + }; + /** + * Retrieves the current server status and sync group information. + * + * @description + * Logs the initiation of the server status retrieval process. Sends a request + * for the server status with a specified range and another request for the + * synchronization groups. The results are processed by respective callback + * functions. + */ + this.getServerstatus = function () { + this.ioUtil.logdebug('getServerstatus'); + this.request('', ['serverstatus', '0', '888'], this.doServerstatus.bind(this)); + this.request('', ['syncgroups', '?'], this.doSyncgroups.bind(this)); + }; + /** + * Processes the result of a syncgroups request. + * + * @description + * Takes the result of a syncgroups request, sanitizes the player names + * and creates or updates the respective state object. The state object + * value is set to the JSON string of the syncgroups_loop array. + * Logs the action for debugging purposes. + * @param result The result of the syncgroups request. + */ + this.doSyncgroups = function (result) { + this.ioUtil.logdebug('doSyncgroups'); + if (result.result && result.result.syncgroups_loop) { + const key = 'syncgroups'; + const stateTemplate = this.sbServerStatus[key]; + if (result.result?.syncgroups_loop?.[0]?.sync_member_names) { + result.result.syncgroups_loop[0].sync_member_names = + result.result?.syncgroups_loop?.[0]?.sync_member_names + .split(',') + .map(el => this.sanitizePlayername(el)); + } + this.createObject(stateTemplate, this.ServerStatePath, null, () => + this.setState( + this.sbServerStatus[key].name, + JSON.stringify(result.result.syncgroups_loop), + this.ServerStatePath, + ), + ); + } + }; + /** + * Processes the result of a serverstatus request. + * + * @description + * Takes the result of a serverstatus request, checks if the server status + * states are up to date, creates or updates the respective state objects + * and sets their values to the respective values in the result. If the + * result contains a players_loop array, it checks if new players have been + * added and if so, creates or updates the respective state objects and + * sets their values. It also checks if any players have been removed and + * if so, removes the respective state objects. + * Logs the action for debugging purposes. + * @param result The result of the serverstatus request. + */ + this.doServerstatus = function (result) { + this.ioUtil.logdebug('doServerstatus'); + this.checkServerstatusStates(result); + for (const key in this.sbServerStatus) { + if (Object.prototype.hasOwnProperty.call(result.result, key)) { + const stateTemplate = this.sbServerStatus[key]; + this.createObject(stateTemplate, this.ServerStatePath, null, () => + this.setState( + this.sbServerStatus[key].name, + this.convertState(this.sbServerStatus[key], result.result[key]), + this.ServerStatePath, + ), + ); + } + } + this.checkNewPlayer(result.result.players_loop); + this.checkPlayer(result.result.players_loop); + }; + /** + * Retrieves and sets the favorites from the Logitech Media Server (LMS). + * + * @description + * Logs the initiation of the favorites setting process. Sends a request to + * retrieve the list of favorites from the LMS and processes the result to + * update the favorites data points. + */ + this.setFavorites = async function () { + this.ioUtil.logdebug('setFavorites'); + this.getFavoritesLMS('', result => this.setFavoritesDP(result)); + }; + this.delFavorites = async function () { + this.ioUtil.logdebug('delFavorites'); + let favobj = await this.getObjects(this.FavoritesStatePath); + favobj = favobj.rows.filter(obj => obj.value.type === 'folder'); + for (let i = 0; i < favobj.length; i++) { + await this.adapter.delForeignObjectAsync(favobj[i].id); + } + }; + /** + * Retrieves the list of favorites from the Logitech Media Server (LMS). + * + * @description + * Logs the initiation of the favorites retrieval process. Sends a request to + * retrieve the list of favorites from the LMS and processes the result by + * calling the callback function with the result. + * @param id The ID of the favorite to retrieve. If not provided, + * retrieves all favorites. + * @param callback The callback function to be called with the + * result of the request. + */ + this.getFavoritesLMS = function (id, callback) { + this.ioUtil.logdebug('getFavoritesLMS'); + this.request( + '', + ['favorites', 'items', '0', '888', 'want_url:1', `item_id:${id}`], + result => callback(result.result.loop_loop), //.bind(this); + ); + }; + /** + * Processes the list of favorites retrieved from the LMS. + * + * @description + * Logs the initiation of the favorites setting process. Creates the + * necessary state paths and creates the objects for each favorite. + * @param favorites The list of favorites retrieved from the LMS. + */ + this.setFavoritesDP = async function (favorites) { + this.ioUtil.logdebug('setFavoritesDP'); + await this.createFolder(this.FavoritesStatePath); + for (const favkey in favorites) { + const favorite = favorites[favkey]; + const oid = this.getFavId(favorite['id'], false); + const id = this.getFavId(favorite['id']); + for (const key in this.sbFavoritesState) { + if (Object.prototype.hasOwnProperty.call(favorite, key)) { + if (key == 'id') { + favorite['id'] = oid; + } + if (key == 'image') { + let result = `http://${this.adapter.config.server}:${ + this.adapter.config.port + }/html/images/favorites.png`; + if (/music\/(.*)\/cover.png/gm.test(favorite[key])) { + result = `http://${this.adapter.config.server}:${this.adapter.config.port}/${ + favorite[key] + }`; + } else if (/imageproxy\/(.*)\/[^/]/gm.test(favorite[key])) { + const m = /imageproxy\/(.*)\/[^/]/gm.exec(favorite[key]); + if (m && m[1]) { + result = decodeURIComponent(m[1] || ''); + } + } else if (favorite[key] === 'html/images/radio.png') { + result = `http://${this.adapter.config.server}:${ + this.adapter.config.port + }/html/images/radio.png`; + } else if (favorite[key] === 'html/images/cover.png') { + result = `http://${this.adapter.config.server}:${ + this.adapter.config.port + }/html/images/cover.png`; + } else if (favorite[key] === 'html/images/favorites.png') { + result = `http://${this.adapter.config.server}:${ + this.adapter.config.port + }/html/images/favorites.png`; + } + favorite[key] = result; + } + const stateTemplate = this.sbFavoritesState[key]; + await this.createFolder(id, this.FavoritesStatePath); + this.createObject(stateTemplate, this.FavoritesStatePath, id, () => + this.setState( + this.sbFavoritesState[key].name, + this.convertState(this.sbFavoritesState[key], favorite[key]), + this.FavoritesStatePath, + id, + false, + ), + ); + } + } + if (favorite['hasitems'] > 0) { + this.getFavoritesLMS(oid, result => this.setFavoritesDP(result)); + } + } + }; + /** + * Extracts and optionally replaces periods in the favorite ID. + * + * @param id - The original favorite ID. + * @param [replace] - Whether to replace periods with hyphens. + * @returns - The processed favorite ID with optional replacements. + */ + this.getFavId = function (id, replace = true) { + let ret; + if (id.indexOf('.') == 8) { + ret = id.substr(9); + } else { + ret = id; + } + if (replace) { + ret = ret.replace(/\./g, '-'); + } + return ret; + }; + /** + * Checks if the server status datapoints exist and creates them if not. + * + * Iterates over the sbServerStatus object and checks if the datapoint exists. + * If not, it creates the datapoint using createObject. Additionally, it + * checks and creates the datapoints for getFavorites and syncgroups. + * + * @param result - The result of the serverstatus request. + */ + this.checkServerstatusStates = async function (result) { + this.ioUtil.logdebug('checkServerstatusStates'); + await this.createDevice(this.ServerStatePath); + for (const key in this.sbServerStatus) { + if (Object.prototype.hasOwnProperty.call(result.result, key)) { + const stateTemplate = this.sbServerStatus[key]; + if (!this.currentStates[stateTemplate.name]) { + if (!stateTemplate.exist) { + this.sbServerStatus[key] = this.createObject(stateTemplate, this.ServerStatePath); + } + } + } + } + let key = 'getFavorites'; + let stateTemplate = this.sbServerStatus[key]; + if (!stateTemplate.exist) { + this.sbServerStatus[key] = this.createObject(stateTemplate, this.ServerStatePath); + } + key = 'syncgroups'; + stateTemplate = this.sbServerStatus[key]; + if (!stateTemplate.exist) { + this.sbServerStatus[key] = this.createObject(stateTemplate, this.ServerStatePath); + } + }; + /** + * Checks for new players in the playersdata array. + * + * @description + * Iterates over the playersdata array and checks if each player is already + * present in the current list of players. If a player is not found, it + * creates a new ioSBPlayer object for the player and adds it to the list. + * Logs the action for debugging purposes. + * @param playersdata The array containing player data to be checked. + */ + this.checkNewPlayer = function (playersdata) { + this.ioUtil.logdebug('checkNewPlayer'); + for (const key in playersdata) { + if (!Object.prototype.hasOwnProperty.call(this.players, playersdata[key].playerid)) { + this.players[playersdata[key].playerid] = new ioSBPlayer(this, playersdata[key]); + } + } + }; + /** + * Checks if any player connections have changed. + * + * @description + * Iterates over the playersdata array and checks if each player's connection + * state has changed. If a player's connection state has changed, it either + * connects or disconnects the ioSBPlayer object for the player. Logs the action + * for debugging purposes. + * @param playersdata The array containing player data to be checked. + */ + this.checkPlayer = function (playersdata) { + this.ioUtil.logdebug('checkPlayer'); + this.checkTPE2(); + for (const key in playersdata) { + if (this.players[playersdata[key].playerid].connected != playersdata[key].connected) { + if (playersdata[key].connected == 0) { + this.players[playersdata[key].playerid].disconnect(); + } + if (playersdata[key].connected == 1) { + this.players[playersdata[key].playerid].connect(); + } + } + } + }; + /** + * Checks the value of the LMS preference "useTPE2AsAlbumArtist" + * and sets the TPE2Handling of all players accordingly. + * + * @description + * This function requests the value of the LMS preference "useTPE2AsAlbumArtist" + * and sets the TPE2Handling of all ioSBPlayer objects to the value of the + * preference. This preference determines if the album artist should be taken + * from the TPE2 tag of a song instead of the TPE1 tag. + */ + this.checkTPE2 = function () { + // eslint-disable-next-line prettier/prettier + this.request( + '', + ['pref', 'useTPE2AsAlbumArtist', '?'], + (result) => { + if (result.ok) { + if (Object.keys(result.result).length > 0) { + const value = result.result[Object.keys(result.result)[0]]; + for (const key in this.players) { + if (Object.prototype.hasOwnProperty.call(this.players, key)) { + // console.log("setTPE2Handling", key, value); + this.players[key].setTPE2Handling(parseInt(value)); + } + } + } + } + }); + }; + /** + * Handles state changes for server, favorites, and players. + * + * @param id - The state identifier, which is expected to be in dot-separated format. + * @param state - The new state object; can be null if the state was deleted. + * + * The function logs the state change, splits the id into parts, and invokes the + * appropriate state change handler based on the first part of the id. + * It returns early if id, state, or state.ack is falsy. + */ + this.stateChange = function (id, state) { + this.ioUtil.logsilly('stateChange'); + // Warning, state can be null if it was deleted + if (!id || !state || state.ack) { + return; + } + const idParts = id.split('.'); + idParts.shift(); + idParts.shift(); + if (idParts[0] == this.ServerStatePath) { + this.doServerStateChange(idParts, state); + } + if (idParts[0] == this.FavoritesStatePath) { + this.doFavoritesStateChange(idParts, state); + } + if (idParts[0] == this.PlayersStatePath) { + this.doPlayersStateChange(idParts, state); + } + }; + /** + * Handles state changes for the server, such as changes to the favorites list. + * + * @param idParts - The state identifier split into parts. + * @param state - The new state object; can be null if the state was deleted. + * + * The function logs the state change, splits the id into parts, and invokes the + * appropriate state change handler based on the first part of the id. + * It returns early if id, state, or state.ack is falsy. + */ + this.doServerStateChange = function (idParts, state) { + this.ioUtil.logdebug('doServerStateChange'); + idParts.shift(); + if (idParts[0] == 'getFavorites') { + if (state.val == true) { + this.delFavorites(); + this.setFavorites(); + } + } + }; + /** + * Handles state changes for favorites. + * + * @param idParts - The state identifier split into parts. + * @param state - The new state object; can be null if the state was deleted. + * + * Logs the state change and shifts the idParts array. Returns early if the state is truthy. + */ + this.doFavoritesStateChange = function (idParts, state) { + this.ioUtil.logdebug('doFavoritesStateChange'); + idParts.shift(); + if (state) { + return; + } + }; + /** + * Handles state changes for a player. + * + * @param idParts - The state identifier split into parts. + * @param state - The new state object; can be null if the state was deleted. + * + * Shifts the idParts array and finds the player with the name in the first + * part of the idParts array. If a player is found, it calls the player's + * doStateChange method with the remaining idParts and state. + */ + this.doPlayersStateChange = function (idParts, state) { + this.ioUtil.logsilly('doPlayersStateChange'); + idParts.shift(); + const player = this.findPlayerByName(idParts[0]); + if (player) { + player.doStateChange(idParts, state); + } + }; + /** + * Finds a player with a given name. + * + * @param name - The name of the player. + * @returns The player object if found; otherwise, undefined. + */ + this.findPlayerByName = function (name) { + for (const key in this.players) { + if (this.players[key].playername == name) { + return this.players[key]; + } + } + }; + /** + * Makes a request to the Logitech Media Server. + * + * @param playerid - The playerid of the player to request from, or '' for the server. + * @param params - The parameters to pass to the request. + * @param callback - The function to call with the result of the request. + * + * If the request is successful, sets the connected flag and clears the error counter. + * If the request fails, decrements the error counter and calls disconnect if the counter reaches 0. + * If the request fails and the firstStart flag is set, calls disconnect. + */ + this.request = (playerid, params, callback) => { + this.ioUtil.logsilly('request'); + this.sbServer.request(playerid, params, result => { + if (result.ok) { + this.connected = 1; + this.errcnt = -1; + this.firstStart = false; + if (callback) { + callback(result); + } + } else { + if (this.firstStart) { + this.disconnect(); + } + if (this.errcnt == -1) { + this.errcnt = this.errmax; + } + this.errcnt--; + if (this.errcnt == 0) { + this.errcnt = -1; + this.disconnect(); + } + } + }); + }; + /** + * Asynchronous method to make a request to the Logitech Media Server. + * + * @param playerid - The player ID to request from, or an empty string for the server. + * @param params - The parameters to pass to the request. + * @returns A promise that resolves with the result of the request. + * + * Logs the request attempt and uses a promise to handle the asynchronous operation. + * If the request is successful, sets the connected flag, clears the error counter, + * and resolves the promise with the result. + * If the request fails, manages error counters, may call disconnect, + * and rejects the promise if the error threshold is reached. + */ + this.requestAsync = async (playerid, params) => { + this.ioUtil.logsilly('requestAsync'); + return new Promise((resolve, reject) => { + this.sbServer.request(playerid, params, result => { + if (result.ok) { + this.connected = 1; + this.errcnt = -1; + this.firstStart = false; + resolve(result); + } else { + if (this.firstStart) { + this.disconnect(); + } + if (this.errcnt == -1) { + this.errcnt = this.errmax; + } + this.errcnt--; + if (this.errcnt == 0) { + this.errcnt = -1; + this.disconnect(); + } + reject(); + } + }); + }); + }; + /** + * Disconnects the server and its players. + * + * @description + * Logs the disconnection event, updates the server's state to disconnected, + * and clears related timeouts. It checks for server observers and performs + * necessary state updates. Iterates over all players and disconnects them. + * Sets an interval to periodically check the server status. + */ + this.disconnect = () => { + this.ioUtil.logdebug('Server disconnect'); + this.firstStart = false; + this.connected = 0; + this.clearTimeout('serverstatus'); + this.clearTimeout('favorites'); + if (this.connected == 0 && this.observers['checkserver']) { + return; + } + this.setState('connection', false, 'info'); + for (const key in this.players) { + this.players[key].disconnect(); + } + this.setInterval('checkserver', this.doCheckServer.bind(this), 10 * 1000); + }; + /** + * Connects the server and its players. + * + * @description + * Logs the connection event, clears the check server interval, + * and sets the server connection state to true. + * Calls init() to initialize players and other data. + */ + this.connect = () => { + this.ioUtil.logdebug('Server connect'); + this.clearInterval('checkserver'); + this.setState('connection', true, 'info'); + this.connected = 1; + this.init(); + }; + /** + * Checks the server status and attempts to reconnect if necessary. + * + * @description + * Logs the server check action for debugging purposes. Sends a serverstatus + * request to the Logitech Media Server. If the request is successful, it + * initiates a server connection. + */ + this.doCheckServer = () => { + this.ioUtil.logdebug('doCheckServer'); + this.request('', ['serverstatus', '0', '888'], result => { + if (result.ok) { + this.connect(); + } + }); + }; + /** + * Sets the state of an object in the ioBroker system. + * + * @param name - The name of the state to set. + * @param value - The value to set the state to. + * @param [level1path] - The first level path to prepend to the state name, can be null or empty. + * @param [level2path] - The second level path to prepend to the state name, can be null or empty. + * @param [check] - If true, only sets the state if the current value differs from the new value. + * @param [callback] - Optional callback function, called when the state is set in the adapter. + */ + this.setState = function (name, value, level1path, level2path, check, callback) { + name = (level1path ? `${level1path}.` : '') + (level2path ? `${level2path}.` : '') + name; + if (this.currentStates[name] !== value && check) { + this.currentStates[name] = value; + this.ioUtil.logsilly(`setState name: ${name} value: ${value}`); + this.adapter.setState(name, value, true, callback); + } else { + this.currentStates[name] = value; + this.ioUtil.logsilly(`setState name: ${name} value: ${value}`); + this.adapter.setState(name, value, true, callback); + } + }; + this.getState = function (name, level1path = false, level2path = false, callback) { + name = (level1path ? `${level1path}.` : '') + (level2path ? `${level2path}.` : '') + name; + this.ioUtil.logsilly(`getState ${name}`); + this.adapter.getState(name, callback); + }; + this.delObject = function (name, level1path, level2path, callback) { + name = (level1path ? `${level1path}.` : '') + (level2path ? `${level2path}.` : '') + name; + this.ioUtil.logsilly(`delObject ${name}`); + delete this.currentStates[name]; + this.adapter.delObject(name, callback); + }; + this.delState = function (name, level1path, level2path, callback) { + name = (level1path ? `${level1path}.` : '') + (level2path ? `${level2path}.` : '') + name; + this.ioUtil.logsilly(`delObject ${name}`); + delete this.currentStates[name]; + this.adapter.delState(name, callback); + }; + this.getStates = function (pattern, level1path, level2path, callback) { + const name = (level1path ? `${level1path}.` : '') + (level2path ? `${level2path}.` : '') + pattern; + this.adapter.getStates(name, callback); + }; + this.getObjects = async function (level1path, level2path) { + const name = `${this.adapter.namespace}.${level1path ? `${level1path}.` : ''}${level2path ? level2path : ''}`; + return await this.adapter.getObjectListAsync({ + startkey: name, + endkey: `${name}\u9999`, + }); + }; + this.createObject = function (stateTemplate, level1path, level2path, callback) { + const name = (level1path ? `${level1path}.` : '') + (level2path ? `${level2path}.` : '') + stateTemplate.name; + this.ioUtil.logsilly(`createObject ${name}`); + this.adapter.getObject(name, (err, obj) => { + const newobj = { + type: 'state', + common: stateTemplate, + native: {}, + }; + if (!obj) { + callback ? this.adapter.setObject(name, newobj, callback) : this.adapter.setObject(name, newobj); + } else { + if (callback) { + callback(); + } + } + }); + stateTemplate.exist = true; + return stateTemplate; + }; + this.createState = function (stateTemplate, level1path = false, level2path = false, callback) { + const name = (level1path ? `${level1path}.` : '') + (level2path ? `${level2path}.` : '') + stateTemplate.name; + this.ioUtil.logsilly(`Create state ${name}`); + this.adapter.createState(level1path, level2path, stateTemplate.name, stateTemplate, callback); + stateTemplate.exist = true; + return stateTemplate; + }; + this.createFolder = async function (foldername, level1path, level2path) { + const id = (level1path ? `${level1path}.` : '') + (level2path ? `${level2path}.` : '') + foldername; + this.ioUtil.logsilly(`createFolder ${id}`); + if (await this.existsObjectAsync(id)) { + this.ioUtil.logsilly(`Folder exists: ${id}`); + } else { + const obj = { + type: 'folder', + common: { + name: foldername, + }, + native: {}, + }; + this.adapter.setObject(id, obj); + } + }; + this.createDevice = async function (devicename, level1path, level2path) { + const id = (level1path ? `${level1path}.` : '') + (level2path ? `${level2path}.` : '') + devicename; + this.ioUtil.logsilly(`createDevice ${id}`); + if (await this.existsObjectAsync(id)) { + this.ioUtil.logsilly(`Device exists: ${id}`); + } else { + const obj = { + type: 'device', + common: { + name: devicename, + }, + native: {}, + }; + this.adapter.setObject(id, obj); + } + }; + this.existsObjectAsync = function (id) { + return new Promise((resolve, reject) => { + id = `${this.adapter.namespace}.${id}`; + + this.adapter.getForeignObject(id, (err, obj) => { + if (err) { + reject(err); + } else { + resolve(!!obj); + } + }); + }); + }; + this.convertState = function (stateTemplate, value) { + if (stateTemplate.type == 'string') { + return String(value); + } + if (stateTemplate.type == 'number') { + return Number(value); + } + this.ioUtil.logdebug(`Missing conversion function for type ${stateTemplate.type} Please report.`); + return value; + }; + /* this.xlogsilly = s => { + if (this.islogsilly) { + this.adapter.log.silly(s); + } + }; + this.xlogerror = function (s) { + this.adapter.log.error(s); + }; + this.xloginfo = s => { + this.adapter.log.info(s); + }; */ + this.getDiscoverServers(); + this.sbServer.on('register', this.init.bind(this)); +} +module.exports = IoSbServer; diff --git a/lib/squeezenode/squeezeplayer.js b/lib/squeezenode/squeezeplayer.js index fe86a52..5347bfa 100644 --- a/lib/squeezenode/squeezeplayer.js +++ b/lib/squeezenode/squeezeplayer.js @@ -32,16 +32,14 @@ var SqueezeRequest = require('./squeezerequest'); * @param username The username for authentication * @param password The password for authentication */ - function SqueezePlayer(playerId, name, address, port, username, password) { - this.playerId = playerId; this.name = name; SqueezePlayer.super_.apply(this, [address, port, username, password]); /** - * @method callMethod + * @function callMethod * * You can use this function to call any available method in the Logitech * Media Server API. The documentation for the API can be found here - @@ -66,7 +64,6 @@ function SqueezePlayer(playerId, name, address, port, username, password) { * to remember. Use whichever you prefer. `callMethod` is designed to provide flexibility * for calling methods that have not been explicitly defined on the SqueezePlayer * object. Plus, it supports promises! 🙀 - * * @param Object opts - The options object for the request. * @param string opts.method - The method name. Required. * @param array opts.params - The additional parameters for the request. Required. @@ -74,38 +71,31 @@ function SqueezePlayer(playerId, name, address, port, username, password) { * provide a callback, a promise will be returned. * @throws Throws an error is opts.method is empty. * @returns Promise|Undefined - * */ this.callMethod = function (opts) { - if (_.isUndefined(opts.method)) { throw Error('Method name missing.'); } - var params = _.flatten([ - _.get(opts, 'method'), - _.get(opts, 'params'), - ]); + var params = _.flatten([_.get(opts, 'method'), _.get(opts, 'params')]); var cb = _.get(opts, 'callback'); if (cb) { this.request(this.playerId, params, cb); } else { - return new Promise(_.bind(function (resolve, reject) { - this.request( - this.playerId, - params, - function (result) { + return new Promise( + _.bind(function (resolve, reject) { + this.request(this.playerId, params, function (result) { if (!result.ok) { reject(result); } else { resolve(result); } - } - ); - }, this)); + }); + }, this), + ); } }; @@ -116,7 +106,7 @@ function SqueezePlayer(playerId, name, address, port, username, password) { */ this.clearPlayList = function (callback) { - this.request(playerId, ["playlist", "clear"], callback); + this.request(playerId, ['playlist', 'clear'], callback); }; /** @@ -126,7 +116,7 @@ function SqueezePlayer(playerId, name, address, port, username, password) { */ this.getMode = function (callback) { - this.request(playerId, ["mode", "?"], callback); + this.request(playerId, ['mode', '?'], callback); }; /** @@ -137,7 +127,7 @@ function SqueezePlayer(playerId, name, address, port, username, password) { */ this.setName = function (name, callback) { - this.request(playerId, ["name", name], callback); + this.request(playerId, ['name', name], callback); }; /** @@ -147,7 +137,7 @@ function SqueezePlayer(playerId, name, address, port, username, password) { */ this.getName = function (callback) { - this.request(playerId, ["name", "?"], callback); + this.request(playerId, ['name', '?'], callback); }; /** @@ -157,9 +147,10 @@ function SqueezePlayer(playerId, name, address, port, username, password) { */ this.getCurrentTitle = function (callback) { - this.request(playerId, ["current_title", "?"], function (reply) { - if (reply.ok) + this.request(playerId, ['current_title', '?'], function (reply) { + if (reply.ok) { reply.result = reply.result._current_title; + } callback(reply); }); }; @@ -171,9 +162,10 @@ function SqueezePlayer(playerId, name, address, port, username, password) { */ this.getArtist = function (callback) { - this.request(playerId, ["artist", "?"], function (reply) { - if (reply.ok) + this.request(playerId, ['artist', '?'], function (reply) { + if (reply.ok) { reply.result = reply.result._artist; + } callback(reply); }); }; @@ -185,9 +177,10 @@ function SqueezePlayer(playerId, name, address, port, username, password) { */ this.getAlbum = function (callback) { - this.request(playerId, ["album", "?"], function (reply) { - if (reply.ok) + this.request(playerId, ['album', '?'], function (reply) { + if (reply.ok) { reply.result = reply.result._album; + } callback(reply); }); }; @@ -199,9 +192,10 @@ function SqueezePlayer(playerId, name, address, port, username, password) { */ this.getCurrentRemoteMeta = function (callback) { - this.request(playerId, ["status"], function (reply) { - if (reply.ok) + this.request(playerId, ['status'], function (reply) { + if (reply.ok) { reply.result = reply.result.remoteMeta; + } callback(reply); }); }; @@ -213,7 +207,7 @@ function SqueezePlayer(playerId, name, address, port, username, password) { */ this.getStatus = function (callback) { - this.request(playerId, ["status"], callback); + this.request(playerId, ['status'], callback); }; /** @@ -225,9 +219,10 @@ function SqueezePlayer(playerId, name, address, port, username, password) { */ this.getStatusWithPlaylist = function (from, to, callback) { - this.request(playerId, ["status", from, to], function (reply) { - if (reply.ok) + this.request(playerId, ['status', from, to], function (reply) { + /* if (reply.ok) { reply.result = reply.result; + } */ callback(reply); }); }; @@ -241,9 +236,10 @@ function SqueezePlayer(playerId, name, address, port, username, password) { */ this.getPlaylist = function (from, to, callback) { - this.request(playerId, ["status", from, to], function (reply) { - if (reply.ok) + this.request(playerId, ['status', from, to], function (reply) { + if (reply.ok) { reply.result = reply.result.playlist_loop; + } callback(reply); }); }; @@ -255,7 +251,7 @@ function SqueezePlayer(playerId, name, address, port, username, password) { */ this.play = function (callback) { - this.request(playerId, ["play"], callback); + this.request(playerId, ['play'], callback); }; /** @@ -266,7 +262,7 @@ function SqueezePlayer(playerId, name, address, port, username, password) { */ this.playIndex = function (index, callback) { - this.request(playerId, ["playlist", "index", index], callback); + this.request(playerId, ['playlist', 'index', index], callback); }; /** @@ -277,7 +273,7 @@ function SqueezePlayer(playerId, name, address, port, username, password) { */ this.pause = function (state, callback) { - this.request(playerId, ["pause", state ? "1" : "0"], callback); + this.request(playerId, ['pause', state ? '1' : '0'], callback); }; /** @@ -287,7 +283,7 @@ function SqueezePlayer(playerId, name, address, port, username, password) { */ this.togglepause = function (callback) { - this.request(playerId, ["pause"], callback); + this.request(playerId, ['pause'], callback); }; /** @@ -297,7 +293,7 @@ function SqueezePlayer(playerId, name, address, port, username, password) { */ this.previous = function (callback) { - this.request(playerId, ["button", "jump_rew"], callback); + this.request(playerId, ['button', 'jump_rew'], callback); }; /** @@ -307,7 +303,7 @@ function SqueezePlayer(playerId, name, address, port, username, password) { */ this.next = function (callback) { - this.request(playerId, ["button", "jump_fwd"], callback); + this.request(playerId, ['button', 'jump_fwd'], callback); }; /** @@ -318,7 +314,7 @@ function SqueezePlayer(playerId, name, address, port, username, password) { */ this.playlistDelete = function (index, callback) { - this.request(playerId, ["playlist", "delete", index], callback); + this.request(playerId, ['playlist', 'delete', index], callback); }; /** @@ -330,7 +326,7 @@ function SqueezePlayer(playerId, name, address, port, username, password) { */ this.playlistMove = function (fromIndex, toIndex, callback) { - this.request(playerId, ["playlist", "move", fromIndex, toIndex], callback); + this.request(playerId, ['playlist', 'move', fromIndex, toIndex], callback); }; /** @@ -341,7 +337,7 @@ function SqueezePlayer(playerId, name, address, port, username, password) { */ this.playlistSave = function (playlistName, callback) { - this.request(playerId, ["playlist", "save", playlistName], callback); + this.request(playerId, ['playlist', 'save', playlistName], callback); }; /** @@ -352,7 +348,7 @@ function SqueezePlayer(playerId, name, address, port, username, password) { */ this.sync = function (syncTo, callback) { - this.request(playerId, ["sync", syncTo], callback); + this.request(playerId, ['sync', syncTo], callback); }; /** @@ -362,7 +358,7 @@ function SqueezePlayer(playerId, name, address, port, username, password) { */ this.unSync = function (callback) { - this.request(playerId, ["sync", "-"], callback); + this.request(playerId, ['sync', '-'], callback); }; /** @@ -373,7 +369,7 @@ function SqueezePlayer(playerId, name, address, port, username, password) { */ this.seek = function (seconds, callback) { - this.request(playerId, ["time", seconds], callback); + this.request(playerId, ['time', seconds], callback); }; /** @@ -384,7 +380,7 @@ function SqueezePlayer(playerId, name, address, port, username, password) { */ this.setVolume = function (volume, callback) { - this.request(playerId, ["mixer", "volume", volume], callback); + this.request(playerId, ['mixer', 'volume', volume], callback); }; /** @@ -394,9 +390,10 @@ function SqueezePlayer(playerId, name, address, port, username, password) { */ this.getVolume = function (callback) { - this.request(playerId, ["mixer", "volume", "?"], function (reply) { - if (reply.ok) + this.request(playerId, ['mixer', 'volume', '?'], function (reply) { + if (reply.ok) { reply.result = reply.result._volume; + } callback(reply); }); }; @@ -409,7 +406,7 @@ function SqueezePlayer(playerId, name, address, port, username, password) { */ this.randomPlay = function (target, callback) { - this.request(playerId, ["randomplay", target], callback); + this.request(playerId, ['randomplay', target], callback); }; /** @@ -420,7 +417,7 @@ function SqueezePlayer(playerId, name, address, port, username, password) { */ this.power = function (state, callback) { - this.request(playerId, ["power", state ? "1" : "0"], callback); + this.request(playerId, ['power', state ? '1' : '0'], callback); }; /** @@ -431,7 +428,7 @@ function SqueezePlayer(playerId, name, address, port, username, password) { */ this.playFavorite = function (favorite, callback) { - this.request(playerId, ["favorites", "playlist", "play", "item_id:" + favorite], callback); + this.request(playerId, ['favorites', 'playlist', 'play', `item_id:${favorite}`], callback); }; /** @@ -442,7 +439,7 @@ function SqueezePlayer(playerId, name, address, port, username, password) { */ this.addToPlaylist = function (item, callback) { - this.request(playerId, ["playlist", "add", item], callback); + this.request(playerId, ['playlist', 'add', item], callback); }; /** @@ -453,7 +450,7 @@ function SqueezePlayer(playerId, name, address, port, username, password) { */ this.insertToPlaylist = function (item, callback) { - this.request(playerId, ["playlist", "insert", item], callback); + this.request(playerId, ['playlist', 'insert', item], callback); }; /** @@ -464,7 +461,7 @@ function SqueezePlayer(playerId, name, address, port, username, password) { */ this.shuffle = function (state, callback) { - this.request(playerId, ["playlist", "shuffle", state ? "1" : "0"], callback); + this.request(playerId, ['playlist', 'shuffle', state ? '1' : '0'], callback); }; /** @@ -474,10 +471,10 @@ function SqueezePlayer(playerId, name, address, port, username, password) { */ this.setLinein = function (callback) { - this.request(playerId, ["setlinein", "linein"], callback); + this.request(playerId, ['setlinein', 'linein'], callback); }; } inherits(SqueezePlayer, SqueezeRequest); -module.exports = SqueezePlayer; \ No newline at end of file +module.exports = SqueezePlayer; diff --git a/lib/squeezenode/squeezerequest.js b/lib/squeezenode/squeezerequest.js index 7238f2b..4e3c61b 100644 --- a/lib/squeezenode/squeezerequest.js +++ b/lib/squeezenode/squeezerequest.js @@ -22,25 +22,31 @@ SOFTWARE. */ -const jayson = require("jayson"); -const inherits = require("super"); +const jayson = require('jayson'); +// const inherits = require('super'); let AWS; - // address "://|hostbame[:port]" // transport "<[0-9]*|sqs[://region]|ssh://password:username@hostname:port" // sqs password = {send: "Queue URL, recv "Queue URL" } +/** + * Request function for Squeezebox server + * + * @param address The address of the Squeezebox server + * @param port The port number, or a string of the form "ssh://password:username@hostname:port" for an SSH tunnel + * @param username The username for basic authentication + * @param password The password for basic authentication + */ function SqueezeRequest(address, port, username, password) { // FIXME URI - this.address = (typeof address !== "string") ? "localhost" : address; - this.port = (port !== undefined) ? port : 9000; + this.address = typeof address !== 'string' ? 'localhost' : address; + this.port = port !== undefined ? port : 9000; this.username = username; this.password = password; - this.id = "squeezenode." + process.pid;// FIXME lamba seems to be 1 always add instance? + this.id = `squeezenode.${process.pid}`; // FIXME lamba seems to be 1 always add instance? this.queue = []; - const that = this; - + let client; /** * Function to handle responses * @@ -58,24 +64,25 @@ function SqueezeRequest(address, port, username, password) { result.ok = true; } - if (callback) + if (callback) { callback(result); + } } // Set up to use an AWS SQS queue URL to communicate - if (typeof port === "string" && port.substr(0, 3) === "ssh") { - const { URL } = require("url"); + if (typeof port === 'string' && port.substr(0, 3) === 'ssh') { + const { URL } = require('url'); const turl = new URL(this.port); const surl = new URL(this.address); const fp = 9192; - const Tunnel = require("tunnel-ssh"); + const Tunnel = require('tunnel-ssh'); const tunnel_config = { // transport url host: turl.hostname, - port: (turl.port) ? turl.port : 22, + port: turl.port ? turl.port : 22, username: turl.username, // server url - dstPort: (surl.port) ? surl.port : 9000, + dstPort: surl.port ? surl.port : 9000, dstHost: surl.hostname, // forward url? localPort: fp, @@ -84,47 +91,48 @@ function SqueezeRequest(address, port, username, password) { }; const tp = decodeURIComponent(turl.password); console.log(tp); - if (tp.substr(0, 7) === "file://") { - tunnel_config["privateKey"] = require("fs").readFileSync(tp.substr(7)); + if (tp.substr(0, 7) === 'file://') { + tunnel_config['privateKey'] = require('fs').readFileSync(tp.substr(7)); } else { - tunnel_config["password"] = turl.password; + tunnel_config['password'] = turl.password; } console.log(tunnel_config); - this.ssh_tunnel = Tunnel(tunnel_config, function (error, server) { + this.ssh_tunnel = Tunnel(tunnel_config, function (error) { if (error) { console.log(error); } }); // Use a listener to handle errors outside the callback - this.ssh_tunnel.on("error", function (err) { - console.error("SSH Tunnel:", err); - console.log("Tunnel error"); + this.ssh_tunnel.on('error', function (err) { + console.error('SSH Tunnel:', err); + console.log('Tunnel error'); }); - this.address = surl.protocol + "//localhost"; + this.address = `${surl.protocol}//localhost`; this.port = fp; console.log(surl); } - if (typeof this.port === "string" && this.port.substr(0, 6) === "awssqs") { - AWS = require("aws-sdk"); + if (typeof this.port === 'string' && this.port.substr(0, 6) === 'awssqs') { + AWS = require('aws-sdk'); this.request = request_sqs; // FIXME decode region from url - this.sqs = new AWS.SQS({ region: "us-west-2" }); + this.sqs = new AWS.SQS({ region: 'us-west-2' }); this.sendq = { QueueUrl: password.send, MessageGroupId: this.id }; this.recvq = { QueueUrl: password.recv, WaitTimeSeconds: 0, MaxNumberOfMessages: 10 }; this.reciving = true; - this.sqs.receiveMessage(that.recvq, function (err, reply) { - that.recvq.MaxNumberOfMessages = 1; - that.recvq.WaitTimeSeconds = 20; + + this.sqs.receiveMessage(this.recvq, function (err, reply) { + this.recvq.MaxNumberOfMessages = 1; + this.recvq.WaitTimeSeconds = 20; if (reply.Messages) { let msg = reply.Messages.pop(); while (msg) { const deleteParams = { - QueueUrl: that.recvq.QueueUrl, - ReceiptHandle: msg.ReceiptHandle + QueueUrl: this.recvq.QueueUrl, + ReceiptHandle: msg.ReceiptHandle, }; - that.sqs.deleteMessage(deleteParams, function (err, data) { + this.sqs.deleteMessage(deleteParams, function (/* err, data */) { // }); msg = reply.Messages.pop(); @@ -132,21 +140,25 @@ function SqueezeRequest(address, port, username, password) { } request_sqs_dispatch_queue(); }); //Drain Q. - } else { // Use JSON RPC to communicate this.request = request_rpc; - const jsonrpc = (typeof this.port === "number") ? this.address + ":" + this.port + "/jsonrpc.js" : this.address + "/jsonrpc.js"; - client = (this.address.substr(0, 5) === "https") ? jayson.client.https(jsonrpc) : client = jayson.client.http(jsonrpc); + const jsonrpc = + typeof this.port === 'number' ? `${this.address}:${this.port}/jsonrpc.js` : `${this.address}/jsonrpc.js`; + client = + this.address.substr(0, 5) === 'https' + ? jayson.client.https(jsonrpc) + : (client = jayson.client.http(jsonrpc)); client.options.version = 1; // FIXME URL // Add a header for basic authentication if a username and password are given if (username && password) { - if (!client.options.headers) + if (!client.options.headers) { client.options.headers = {}; - client.options.headers["Authorization"] = formatBasicHeader(username, password); + } + client.options.headers['Authorization'] = formatBasicHeader(username, password); } } @@ -159,8 +171,7 @@ function SqueezeRequest(address, port, username, password) { */ function request_rpc(player, params, callback) { - - client.request("slim.request", [player, params], that.id, function (err, reply) { + client.request('slim.request', [player, params], this.id, function (err, reply) { handle(err, reply, callback); }); } @@ -174,27 +185,25 @@ function SqueezeRequest(address, port, username, password) { */ function request_sqs_dispatch(player, params, callback) { - - that.sendq["MessageBody"] = JSON.stringify({ - "params": [player, params], - "id": that.id + process.hrtime(), - "version": "1.0", - "method": "slim.request" + this.sendq['MessageBody'] = JSON.stringify({ + params: [player, params], + id: this.id + process.hrtime(), + version: '1.0', + method: 'slim.request', }); // FIXME id not on pid but an uuid? for instrance from env? - that.sendq["MessageDeduplicationId"] = that.sendq.MessageGroupId + process.hrtime(); + this.sendq['MessageDeduplicationId'] = this.sendq.MessageGroupId + process.hrtime(); //console.log("send ",that.sendq['MessageBody']); - that.sqs.sendMessage(that.sendq, function (err, data) { + this.sqs.sendMessage(this.sendq, function (err, data) { if (err) { handle(err, data, callback); - console.error("Send Error ", err); + console.error('Send Error ', err); } else { // successful response - callback for reply - that.reciving = true; - that.sqs.receiveMessage(that.recvq, function (err, reply) { + this.reciving = true; + this.sqs.receiveMessage(this.recvq, function (err, reply) { //console.log("reply data ",reply); let message_data = {}; if (reply.Messages) { - //id check? message_data = reply.Messages.pop(); @@ -206,23 +215,22 @@ function SqueezeRequest(address, port, username, password) { //console.log("reply ",message_data); while (msg) { const deleteParams = { - QueueUrl: that.recvq.QueueUrl, - ReceiptHandle: msg.ReceiptHandle + QueueUrl: this.recvq.QueueUrl, + ReceiptHandle: msg.ReceiptHandle, }; // FIXME delete error - that.sqs.deleteMessage(deleteParams, function (err, data) { + this.sqs.deleteMessage(deleteParams, function (err /* , data */) { if (err) { - console.error("Delete Error", err); + console.error('Delete Error', err); } else { //console.log("Message Deleted", data); } }); msg = reply.Messages.pop(); } - } else { - console.warn("no msgs ", err); - err = "No reply message recvied"; + console.warn('no msgs ', err); + err = 'No reply message recvied'; } handle(err, message_data, callback); @@ -235,19 +243,19 @@ function SqueezeRequest(address, port, username, password) { // We need to serialise requests and responces for queue use function request_sqs(player, params, callback) { - if (that.reciving) { - that.queue.push([player, params, callback]); + if (this.reciving) { + this.queue.push([player, params, callback]); } else { request_sqs_dispatch(player, params, callback); } } function request_sqs_dispatch_queue() { - const args = that.queue.pop(); + const args = this.queue.pop(); if (args) { request_sqs_dispatch.apply(null, args); } else { - that.reciving = false; + this.reciving = false; } } } @@ -257,9 +265,9 @@ function SqueezeRequest(address, port, username, password) { */ function formatBasicHeader(username, password) { - const tok = username + ":" + password; - const hash = new Buffer.from(tok).toString("base64"); - return "Basic " + hash; + const tok = `${username}:${password}`; + const hash = new Buffer.from(tok).toString('base64'); + return `Basic ${hash}`; } -module.exports = SqueezeRequest; \ No newline at end of file +module.exports = SqueezeRequest; diff --git a/lib/squeezenode/squeezeserver.js b/lib/squeezenode/squeezeserver.js index 0d6fc15..78150b9 100644 --- a/lib/squeezenode/squeezeserver.js +++ b/lib/squeezenode/squeezeserver.js @@ -22,10 +22,10 @@ SOFTWARE. */ -const inherits = require("super"); -const fs = require("fs"); -const SqueezeRequest = require(__dirname + "/squeezerequest"); -const SqueezePlayer = require(__dirname + "/squeezeplayer"); +const inherits = require('super'); +const fs = require('fs'); +const SqueezeRequest = require(`${__dirname}/squeezerequest`); +const SqueezePlayer = require(`${__dirname}/squeezeplayer`); /** * Create a SqueezeServer object @@ -35,13 +35,11 @@ const SqueezePlayer = require(__dirname + "/squeezeplayer"); * @param username The username for authentication * @param password The password for authentication * @param sa A flag to skip apps + * @param sp A flag to skip players */ - function SqueezeServer(address, port, username, password, sa, sp) { - SqueezeServer.super_.apply(this, arguments); - const defaultPlayer = "00:00:00:00:00:00"; - const self = this; + const defaultPlayer = '00:00:00:00:00:00'; this.players = []; this.apps = []; const subs = {}; @@ -54,7 +52,6 @@ function SqueezeServer(address, port, username, password, sa, sp) { */ this.on = function (channel, sub) { - subs[channel] = subs[channel] || []; subs[channel].push(sub); }; @@ -66,7 +63,6 @@ function SqueezeServer(address, port, username, password, sa, sp) { */ this.emit = function (channel) { - const args = [].slice.call(arguments, 1); for (const sub in subs[channel]) { subs[channel][sub].apply(void 0, args); @@ -80,7 +76,7 @@ function SqueezeServer(address, port, username, password, sa, sp) { */ this.getPlayerCount = function (callback) { - this.request(defaultPlayer, ["player", "count", "?"], callback); + this.request(defaultPlayer, ['player', 'count', '?'], callback); }; /** @@ -91,7 +87,7 @@ function SqueezeServer(address, port, username, password, sa, sp) { */ this.getPlayerId = function (index, callback) { - this.request(defaultPlayer, ["player", "id", index, "?"], callback); + this.request(defaultPlayer, ['player', 'id', index, '?'], callback); }; /** @@ -102,7 +98,7 @@ function SqueezeServer(address, port, username, password, sa, sp) { */ this.getPlayerIp = function (playerId, callback) { - this.request(defaultPlayer, ["player", "ip", playerId, "?"], callback); + this.request(defaultPlayer, ['player', 'ip', playerId, '?'], callback); }; /** @@ -113,7 +109,7 @@ function SqueezeServer(address, port, username, password, sa, sp) { */ this.getPlayerName = function (playerId, callback) { - this.request(defaultPlayer, ["player", "name", playerId, "?"], callback); + this.request(defaultPlayer, ['player', 'name', playerId, '?'], callback); }; /** @@ -123,7 +119,7 @@ function SqueezeServer(address, port, username, password, sa, sp) { */ this.getSyncGroups = function (callback) { - this.request(defaultPlayer, ["syncgroups", "?"], callback); + this.request(defaultPlayer, ['syncgroups', '?'], callback); }; /** @@ -133,7 +129,7 @@ function SqueezeServer(address, port, username, password, sa, sp) { */ this.getApps = function (callback) { - this.request(defaultPlayer, ["apps", 0, 100], callback); + this.request(defaultPlayer, ['apps', 0, 100], callback); }; // @@ -146,7 +142,7 @@ function SqueezeServer(address, port, username, password, sa, sp) { */ this.musicfolder = function (folderId, callback) { - this.request(defaultPlayer, ["musicfolder", 0, 100, "folder_id:" + folderId], callback); + this.request(defaultPlayer, ['musicfolder', 0, 100, `folder_id:${folderId}`], callback); }; /** @@ -155,11 +151,11 @@ function SqueezeServer(address, port, username, password, sa, sp) { * @param callback The function to call with the result. */ - this.getPlayers = function (callback) { - - self.request(defaultPlayer, ["players", 0, 100], function (reply) { - if (reply.ok) + this.getPlayers = callback => { + this.request(defaultPlayer, ['players', 0, 100], function (reply) { + if (reply.ok) { reply.result = reply.result.players_loop; + } callback(reply); }); }; @@ -171,11 +167,11 @@ function SqueezeServer(address, port, username, password, sa, sp) { * @param limit The maximum number of results */ - this.getArtists = function (callback, limit) { - - self.request(defaultPlayer, ["artists", 0, limit], function (reply) { - if (reply.ok) + this.getArtists = (callback, limit) => { + this.request(defaultPlayer, ['artists', 0, limit], function (reply) { + if (reply.ok) { reply.result = reply.result.artists_loop; + } callback(reply); }); }; @@ -187,11 +183,11 @@ function SqueezeServer(address, port, username, password, sa, sp) { * @param limit The maximum number of results */ - this.getAlbums = function (callback, limit) { - - self.request(defaultPlayer, ["albums", 0, limit], function (reply) { - if (reply.ok) + this.getAlbums = (callback, limit) => { + this.request(defaultPlayer, ['albums', 0, limit], function (reply) { + if (reply.ok) { reply.result = reply.result.albums_loop; + } callback(reply); }); }; @@ -203,11 +199,11 @@ function SqueezeServer(address, port, username, password, sa, sp) { * @param limit The maximum number of results */ - this.getGenres = function (callback, limit) { - - self.request(defaultPlayer, ["genres", 0, limit], function (reply) { - if (reply.ok) + this.getGenres = (callback, limit) => { + this.request(defaultPlayer, ['genres', 0, limit], function (reply) { + if (reply.ok) { reply.result = reply.result.genres_loop; + } callback(reply); }); }; @@ -220,19 +216,27 @@ function SqueezeServer(address, port, username, password, sa, sp) { * @param slot The query to make to the server [genres, albums, artists, playlists ] */ - this.getInfo = function (callback, limit, slot) { - - self.request(defaultPlayer, [slot, 0, limit], function (reply) { - if (reply.ok) - reply.result = reply.result[slot + "_loop"]; + this.getInfo = (callback, limit, slot) => { + this.request(defaultPlayer, [slot, 0, limit], function (reply) { + if (reply.ok) { + reply.result = reply.result[`${slot}_loop`]; + } callback(reply); }); }; - this.registerPlayers = function (players) { + this.registerPlayers = players => { for (const pl in players) { - if (!self.players[players[pl].playerid]) { // player not on the list - self.players[players[pl].playerid] = new SqueezePlayer(players[pl].playerid, players[pl].name, self.address, self.port, self.username, self.password); + if (!this.players[players[pl].playerid]) { + // player not on the list + this.players[players[pl].playerid] = new SqueezePlayer( + players[pl].playerid, + players[pl].name, + this.address, + this.port, + this.username, + this.password, + ); } } }; @@ -252,7 +256,7 @@ function SqueezeServer(address, port, username, password, sa, sp) { for (const id in this.players) { if ( this.normalizePlayer(this.players[id].name) === name || // name matches the requested player - (name === "" && (only && this.players.length === 1)) // name is undefined and there's only one player, + (name === '' && only && this.players.length === 1) // name is undefined and there's only one player, // so assume that's the one we want. ) { return this.players[id]; @@ -270,11 +274,10 @@ function SqueezeServer(address, port, username, password, sa, sp) { */ this.normalizePlayer = function (playerName) { - - playerName || (playerName = ""); // protect against `playerName` being undefined - playerName = playerName.replace("-", " "); - playerName = playerName.replace(" ", " "); - playerName = playerName.replace(" ", " "); + playerName || (playerName = ''); // protect against `playerName` being undefined + playerName = playerName.replace('-', ' '); + playerName = playerName.replace(' ', ' '); + playerName = playerName.replace(' ', ' '); playerName = playerName.toLowerCase(playerName); return playerName; @@ -286,61 +289,64 @@ function SqueezeServer(address, port, username, password, sa, sp) { * @param skipApps A flag to skip getting information about the apps */ - function register(skipApps, skipPlayers) { - + this.register = function (skipApps, skipPlayers) { if (!skipPlayers) { // Get the list of players from the server - self.getPlayers(function (reply) { + this.getPlayers(reply => { // Process the player information and create Player objects for each one - if (reply.ok) - self.registerPlayers(reply.result); + if (reply.ok) { + this.registerPlayers(reply.result); + } // Send a signal that we are done if (skipApps) { - self.emit("register", reply, undefined); + this.emit('register', reply, undefined); } else { - self.emit("registerPlayers", reply); + this.emit('registerPlayers', reply); } }); } if (!skipApps) { // Once the players have been obtained, request a list of the apps - self.on("registerPlayers", function (reply) { - - self.getApps(function (areply) { - + this.on('registerPlayers', reply => { + this.getApps(areply => { if (areply.ok) { - const apps = areply.result.appss_loop; - const dir = __dirname + "/"; - fs.readdir(dir, function (err, files) { - files.forEach(function (file) { - - const fil = file.substr(0, file.lastIndexOf(".")); + const dir = `${__dirname}/`; + fs.readdir(dir, (err, files) => { + files.forEach(file => { + const fil = file.substr(0, file.lastIndexOf('.')); for (const pl in apps) { if (fil === apps[pl].cmd) { const app = require(dir + file); - self.apps[apps[pl].cmd] = new app(defaultPlayer, apps[pl].name, apps[pl].cmd, self.address, self.port, self.username, self.password); + this.apps[apps[pl].cmd] = new app( + defaultPlayer, + apps[pl].name, + apps[pl].cmd, + this.address, + this.port, + this.username, + this.password, + ); /* workaround, app needs existing player id so first is used here */ } } }); - self.emit("register", reply, areply); + this.emit('register', reply, areply); }); - } else { - self.emit("register", reply, areply); + this.emit('register', reply, areply); } }); }); } - } + }; - register(sa, sp); + this.register(sa, sp); } inherits(SqueezeServer, SqueezeRequest); -module.exports = SqueezeServer; \ No newline at end of file +module.exports = SqueezeServer; diff --git a/lib/tools.js b/lib/tools.js index ad52be1..280a650 100755 --- a/lib/tools.js +++ b/lib/tools.js @@ -1,84 +1,64 @@ -const axios = require("axios").default; +/* eslint-disable jsdoc/require-returns-description */ +const axios = require('axios').defaults; /** * Tests whether the given variable is a real object and not an Array - * @param {any} it The variable to test - * @returns {it is Record} + * + * @param it The variable to test + * @returns */ function isObject(it) { // This is necessary because: // typeof null === 'object' // typeof [] === 'object' // [] instanceof Object === true - return Object.prototype.toString.call(it) === "[object Object]"; + return Object.prototype.toString.call(it) === '[object Object]'; } /** * Tests whether the given variable is really an Array - * @param {any} it The variable to test - * @returns {it is any[]} + * + * @param it The variable to test + * @returns */ function isArray(it) { - if (typeof Array.isArray === "function") return Array.isArray(it); - return Object.prototype.toString.call(it) === "[object Array]"; + if (typeof Array.isArray === 'function') { + return Array.isArray(it); + } + return Object.prototype.toString.call(it) === '[object Array]'; } /** * Translates text to the target language. Automatically chooses the right translation API. - * @param {string} text The text to translate - * @param {string} targetLang The target languate - * @param {string} [yandexApiKey] The yandex API key. You can create one for free at https://translate.yandex.com/developers - * @returns {Promise} + * + * @param text The text to translate + * @param targetLang The target languate + * @returns */ -async function translateText(text, targetLang, yandexApiKey) { - if (targetLang === "en") { +async function translateText(text, targetLang) { + if (targetLang === 'en') { return text; } - if (yandexApiKey) { - return await translateYandex(text, targetLang, yandexApiKey); - } else { - return await translateGoogle(text, targetLang); - } -} - -/** - * Translates text with Yandex API - * @param {string} text The text to translate - * @param {string} targetLang The target languate - * @param {string} [apiKey] The yandex API key. You can create one for free at https://translate.yandex.com/developers - * @returns {Promise} - */ -async function translateYandex(text, targetLang, apiKey) { - if (targetLang === "zh-cn") { - targetLang = "zh"; - } - try { - const url = `https://translate.yandex.net/api/v1.5/tr.json/translate?key=${apiKey}&text=${encodeURIComponent(text)}&lang=en-${targetLang}`; - const response = await axios({ url, timeout: 15000 }); - if (response.data && response.data["text"]) { - return response.data["text"][0]; - } - throw new Error("Invalid response for translate request"); - } catch (e) { - throw new Error(`Could not translate to "${targetLang}": ${e}`); - } + return await translateGoogle(text, targetLang); } /** * Translates text with Google API - * @param {string} text The text to translate - * @param {string} targetLang The target languate - * @returns {Promise} + * + * @param text The text to translate + * @param targetLang The target languate + * @returns */ async function translateGoogle(text, targetLang) { try { const url = `http://translate.googleapis.com/translate_a/single?client=gtx&sl=en&tl=${targetLang}&dt=t&q=${encodeURIComponent(text)}&ie=UTF-8&oe=UTF-8`; + // @ts-expect-error eslint error const response = await axios({ url, timeout: 15000 }); if (isArray(response.data)) { // we got a valid response return response.data[0][0][0]; } - throw new Error("Invalid response for translate request"); + throw new Error('Invalid response for translate request'); } catch (e) { throw new Error(`Could not translate to "${targetLang}": ${e}`); } @@ -87,5 +67,5 @@ async function translateGoogle(text, targetLang) { module.exports = { isArray, isObject, - translateText + translateText, }; diff --git a/package-lock.json b/package-lock.json index 43ec706..5bd3e65 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "iobroker.squeezeboxrpc", - "version": "1.4.0", + "version": "1.5.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "iobroker.squeezeboxrpc", - "version": "1.4.0", + "version": "1.5.0", "license": "MIT", "dependencies": { "@iobroker/adapter-core": "^3.2.2", @@ -21,6 +21,7 @@ "@alcalzone/release-script-plugin-license": "^3.7.0", "@alcalzone/release-script-plugin-manual-review": "^3.7.0", "@iobroker/adapter-dev": "^1.3.0", + "@iobroker/eslint-config": "^1.0.0", "@iobroker/testing": "^5.0.0", "@tsconfig/node18": "^18.2.4", "@types/chai": "^4.3.16", @@ -33,11 +34,7 @@ "axios": "^1.7.7", "chai": "^4.4.1", "chai-as-promised": "^8.0.0", - "eslint": "^8.57.0", - "eslint-config-prettier": "^9.1.0", - "eslint-plugin-prettier": "^5.2.1", "mocha": "^10.8.2", - "prettier": "^3.3.3", "proxyquire": "^2.1.3", "sinon": "^19.0.2", "sinon-chai": "^3.7.0", @@ -261,6 +258,21 @@ "node": ">=12.20" } }, + "node_modules/@babel/code-frame": { + "version": "7.26.2", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz", + "integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.25.9", + "js-tokens": "^4.0.0", + "picocolors": "^1.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/helper-string-parser": { "version": "7.24.8", "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.8.tgz", @@ -308,6 +320,21 @@ "node": ">=6.9.0" } }, + "node_modules/@es-joy/jsdoccomment": { + "version": "0.49.0", + "resolved": "https://registry.npmjs.org/@es-joy/jsdoccomment/-/jsdoccomment-0.49.0.tgz", + "integrity": "sha512-xjZTSFgECpb9Ohuk5yMX5RhUEbfeQcuOp8IF60e+wyzWEF0M5xeSgqsfLtvPEX8BIyOX9saZqzuGPmZ8oWc+5Q==", + "dev": true, + "peer": true, + "dependencies": { + "comment-parser": "1.4.1", + "esquery": "^1.6.0", + "jsdoc-type-pratt-parser": "~4.1.0" + }, + "engines": { + "node": ">=16" + } + }, "node_modules/@esbuild/aix-ppc64": { "version": "0.20.2", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.20.2.tgz", @@ -677,29 +704,197 @@ } }, "node_modules/@eslint-community/eslint-utils": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", - "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.1.tgz", + "integrity": "sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA==", "dev": true, + "peer": true, "dependencies": { - "eslint-visitor-keys": "^3.3.0" + "eslint-visitor-keys": "^3.4.3" }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, + "funding": { + "url": "https://opencollective.com/eslint" + }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, "node_modules/@eslint-community/regexpp": { - "version": "4.11.0", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.11.0.tgz", - "integrity": "sha512-G/M/tIiMrTAxEWRfLfQJMmGNX28IxBg4PBz8XqQhqUHLFI6TL2htpIB1iQCj144V5ee/JaKyT9/WZ0MGZWfA7A==", + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", + "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", "dev": true, + "peer": true, "engines": { "node": "^12.0.0 || ^14.0.0 || >=16.0.0" } }, + "node_modules/@eslint/config-array": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.19.0.tgz", + "integrity": "sha512-zdHg2FPIFNKPdcHWtiNT+jEFCHYVplAXRDlQDyqy0zGx/q2parwh7brGJSiTxRk/TSMkbM//zt/f5CHgyTyaSQ==", + "dev": true, + "peer": true, + "dependencies": { + "@eslint/object-schema": "^2.1.4", + "debug": "^4.3.1", + "minimatch": "^3.1.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.9.0.tgz", + "integrity": "sha512-7ATR9F0e4W85D/0w7cU0SNj7qkAexMG+bAHEZOjo9akvGuhHE2m7umzWzfnpa0XAg5Kxc1BWmtPMV67jJ+9VUg==", + "dev": true, + "peer": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.2.0.tgz", + "integrity": "sha512-grOjVNN8P3hjJn/eIETF1wwd12DdnwFDoyceUJLYYdkpbwq3nLi+4fqrTAONx7XDALqlL220wC/RHSC/QTI/0w==", + "dev": true, + "peer": true, + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/eslint-visitor-keys": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", + "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", + "dev": true, + "peer": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/espree": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz", + "integrity": "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==", + "dev": true, + "peer": true, + "dependencies": { + "acorn": "^8.14.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "peer": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/js": { + "version": "9.15.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.15.0.tgz", + "integrity": "sha512-tMTqrY+EzbXmKJR5ToI8lxu7jaN5EdmrBFJpQk5JmSlyLsx6o4t27r883K5xsLuCYCpfKBCGswMSWXsM+jB7lg==", + "dev": true, + "peer": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.4.tgz", + "integrity": "sha512-BsWiH1yFGjXXS2yvrf5LyuoSIIbPrGUWob917o+BTKuZ7qJdxX8aJLRxs1fS9n6r7vESrq1OUqb68dANcFXuQQ==", + "dev": true, + "peer": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.3.tgz", + "integrity": "sha512-2b/g5hRmpbb1o4GnTZax9N9m0FXzz9OV42ZzI4rDDMDuHUqigAiQCEWChBWCY4ztAGVRjoWT19v0yMmc5/L5kA==", + "dev": true, + "peer": true, + "dependencies": { + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit/node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "peer": true, + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/@eslint/plugin-kit/node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "peer": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/@eslint/plugin-kit/node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "peer": true, + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/@esm2cjs/execa": { "version": "6.1.1-cjs.1", "resolved": "https://registry.npmjs.org/@esm2cjs/execa/-/execa-6.1.1-cjs.1.tgz", @@ -947,49 +1142,50 @@ "node": ">=12.0.0" } }, - "node_modules/@humanwhocodes/config-array": { - "version": "0.13.0", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", - "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==", - "deprecated": "Use @eslint/config-array instead", + "node_modules/@humanfs/core": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", "dev": true, - "dependencies": { - "@humanwhocodes/object-schema": "^2.0.3", - "debug": "^4.3.1", - "minimatch": "^3.0.5" - }, + "peer": true, "engines": { - "node": ">=10.10.0" + "node": ">=18.18.0" } }, - "node_modules/@humanwhocodes/config-array/node_modules/debug": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", - "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "node_modules/@humanfs/node": { + "version": "0.16.6", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.6.tgz", + "integrity": "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==", "dev": true, + "peer": true, "dependencies": { - "ms": "^2.1.3" + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.3.0" }, "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } + "node": ">=18.18.0" } }, - "node_modules/@humanwhocodes/config-array/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true + "node_modules/@humanfs/node/node_modules/@humanwhocodes/retry": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz", + "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==", + "dev": true, + "peer": true, + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } }, "node_modules/@humanwhocodes/module-importer": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", "dev": true, + "peer": true, "engines": { "node": ">=12.22" }, @@ -998,12 +1194,19 @@ "url": "https://github.com/sponsors/nzakas" } }, - "node_modules/@humanwhocodes/object-schema": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", - "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", - "deprecated": "Use @eslint/object-schema instead", - "dev": true + "node_modules/@humanwhocodes/retry": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.1.tgz", + "integrity": "sha512-c7hNEllBlenFTHBky65mhq8WD2kbN9Q6gk0bTk8lSBvc554jpXSkST1iePudpt7+A/AQvuHs9EMqjHDXMY1lrA==", + "dev": true, + "peer": true, + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } }, "node_modules/@iobroker/adapter-core": { "version": "3.2.2", @@ -1053,6 +1256,28 @@ "node": ">=14.14" } }, + "node_modules/@iobroker/eslint-config": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@iobroker/eslint-config/-/eslint-config-1.0.0.tgz", + "integrity": "sha512-84gqXXsmAFKjpb2iXOvhMRAR0qgLb5xaWATnuwD7GW029J7ofqweFpgdDb7JxJ6Jdtwk4J0kwz1jmp4FsqDETA==", + "dev": true, + "peerDependencies": { + "@eslint/eslintrc": "^3.1.0", + "@eslint/js": "^9.10.0", + "@typescript-eslint/eslint-plugin": "^8.4.0", + "@typescript-eslint/parser": "^8.4.0", + "eslint": ">=9.10.0", + "eslint-config-prettier": "^9.1.0", + "eslint-plugin-jsdoc": "^50.2.2", + "eslint-plugin-prettier": "^5.2.1", + "eslint-plugin-react": "^7.36.0", + "eslint-plugin-react-hooks": "^5.1.0-rc.0", + "eslint-plugin-unicorn": "^55.0.0", + "globals": "^15.9.0", + "prettier": "^3.3.3", + "typescript-eslint": "^8.5.0" + } + }, "node_modules/@iobroker/testing": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/@iobroker/testing/-/testing-5.0.0.tgz", @@ -1081,23 +1306,6 @@ "chai": ">= 2.1.2 < 6" } }, - "node_modules/@iobroker/testing/node_modules/debug": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", - "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", - "dev": true, - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, "node_modules/@iobroker/testing/node_modules/fs-extra": { "version": "11.2.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.2.0.tgz", @@ -1112,12 +1320,6 @@ "node": ">=14.14" } }, - "node_modules/@iobroker/testing/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true - }, "node_modules/@iobroker/types": { "version": "6.0.11", "resolved": "https://registry.npmjs.org/@iobroker/types/-/types-6.0.11.tgz", @@ -1144,6 +1346,7 @@ "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", "dev": true, + "peer": true, "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" @@ -1157,6 +1360,7 @@ "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", "dev": true, + "peer": true, "engines": { "node": ">= 8" } @@ -1166,6 +1370,7 @@ "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", "dev": true, + "peer": true, "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" @@ -1179,6 +1384,7 @@ "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.1.1.tgz", "integrity": "sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA==", "dev": true, + "peer": true, "engines": { "node": "^12.20.0 || ^14.18.0 || >=16.0.0" }, @@ -1332,6 +1538,13 @@ "@types/node": "*" } }, + "node_modules/@types/estree": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", + "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", + "dev": true, + "peer": true + }, "node_modules/@types/events": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/@types/events/-/events-3.0.0.tgz", @@ -1349,6 +1562,13 @@ "@types/node": "*" } }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "peer": true + }, "node_modules/@types/linkify-it": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-5.0.0.tgz", @@ -1397,6 +1617,13 @@ "undici-types": "~6.19.8" } }, + "node_modules/@types/normalize-package-data": { + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.4.tgz", + "integrity": "sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==", + "dev": true, + "peer": true + }, "node_modules/@types/proxyquire": { "version": "1.3.31", "resolved": "https://registry.npmjs.org/@types/proxyquire/-/proxyquire-1.3.31.tgz", @@ -1446,79 +1673,287 @@ "@types/node": "*" } }, - "node_modules/@ungap/structured-clone": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", - "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", - "dev": true - }, - "node_modules/abort-controller": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", - "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.16.0.tgz", + "integrity": "sha512-5YTHKV8MYlyMI6BaEG7crQ9BhSc8RxzshOReKwZwRWN0+XvvTOm+L/UYLCYxFpfwYuAAqhxiq4yae0CMFwbL7Q==", "dev": true, + "peer": true, "dependencies": { - "event-target-shim": "^5.0.0" + "@eslint-community/regexpp": "^4.10.0", + "@typescript-eslint/scope-manager": "8.16.0", + "@typescript-eslint/type-utils": "8.16.0", + "@typescript-eslint/utils": "8.16.0", + "@typescript-eslint/visitor-keys": "8.16.0", + "graphemer": "^1.4.0", + "ignore": "^5.3.1", + "natural-compare": "^1.4.0", + "ts-api-utils": "^1.3.0" }, "engines": { - "node": ">=6.5" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.0.0 || ^8.0.0-alpha.0", + "eslint": "^8.57.0 || ^9.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, - "node_modules/acorn": { - "version": "8.12.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz", - "integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==", + "node_modules/@typescript-eslint/parser": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.16.0.tgz", + "integrity": "sha512-D7DbgGFtsqIPIFMPJwCad9Gfi/hC0PWErRRHFnaCWoEDYi5tQUDiJCTmGUbBiLzjqAck4KcXt9Ayj0CNlIrF+w==", "dev": true, - "bin": { - "acorn": "bin/acorn" + "peer": true, + "dependencies": { + "@typescript-eslint/scope-manager": "8.16.0", + "@typescript-eslint/types": "8.16.0", + "@typescript-eslint/typescript-estree": "8.16.0", + "@typescript-eslint/visitor-keys": "8.16.0", + "debug": "^4.3.4" }, "engines": { - "node": ">=0.4.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, - "node_modules/acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.16.0.tgz", + "integrity": "sha512-mwsZWubQvBki2t5565uxF0EYvG+FwdFb8bMtDuGQLdCCnGPrDEDvm1gtfynuKlnpzeBRqdFCkMf9jg1fnAK8sg==", + "dev": true, + "peer": true, + "dependencies": { + "@typescript-eslint/types": "8.16.0", + "@typescript-eslint/visitor-keys": "8.16.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.16.0.tgz", + "integrity": "sha512-IqZHGG+g1XCWX9NyqnI/0CX5LL8/18awQqmkZSl2ynn8F76j579dByc0jhfVSnSnhf7zv76mKBQv9HQFKvDCgg==", "dev": true, + "peer": true, + "dependencies": { + "@typescript-eslint/typescript-estree": "8.16.0", + "@typescript-eslint/utils": "8.16.0", + "debug": "^4.3.4", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, "peerDependencies": { - "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + "eslint": "^8.57.0 || ^9.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, - "node_modules/agent-base": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "node_modules/@typescript-eslint/types": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.16.0.tgz", + "integrity": "sha512-NzrHj6thBAOSE4d9bsuRNMvk+BvaQvmY4dDglgkgGC0EW/tB3Kelnp3tAKH87GEwzoxgeQn9fNGRyFJM/xd+GQ==", + "dev": true, + "peer": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.16.0.tgz", + "integrity": "sha512-E2+9IzzXMc1iaBy9zmo+UYvluE3TW7bCGWSF41hVWUE01o8nzr1rvOQYSxelxr6StUvRcTMe633eY8mXASMaNw==", "dev": true, + "peer": true, "dependencies": { - "debug": "4" + "@typescript-eslint/types": "8.16.0", + "@typescript-eslint/visitor-keys": "8.16.0", + "debug": "^4.3.4", + "fast-glob": "^3.3.2", + "is-glob": "^4.0.3", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^1.3.0" }, "engines": { - "node": ">= 6.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "peer": true, + "dependencies": { + "balanced-match": "^1.0.0" } }, - "node_modules/agent-base/node_modules/debug": { - "version": "4.3.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz", - "integrity": "sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==", + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", "dev": true, + "peer": true, "dependencies": { - "ms": "2.1.2" + "brace-expansion": "^2.0.1" }, "engines": { - "node": ">=6.0" + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.16.0.tgz", + "integrity": "sha512-C1zRy/mOL8Pj157GiX4kaw7iyRLKfJXBR3L82hk5kS/GyHcOFmy4YUq/zfZti72I9wnuQtA/+xzft4wCC8PJdA==", + "dev": true, + "peer": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "@typescript-eslint/scope-manager": "8.16.0", + "@typescript-eslint/types": "8.16.0", + "@typescript-eslint/typescript-estree": "8.16.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0" }, "peerDependenciesMeta": { - "supports-color": { + "typescript": { "optional": true } } }, - "node_modules/agent-base/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.16.0.tgz", + "integrity": "sha512-pq19gbaMOmFE3CbL0ZB8J8BFCo2ckfHBfaIsaOZgBIF4EoISJIdLX5xRhd0FGB0LlHReNRuzoJoMGpTjq8F2CQ==", + "dev": true, + "peer": true, + "dependencies": { + "@typescript-eslint/types": "8.16.0", + "eslint-visitor-keys": "^4.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", + "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", + "dev": true, + "peer": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "dev": true, + "dependencies": { + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" + } + }, + "node_modules/acorn": { + "version": "8.14.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", + "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dev": true, + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } }, "node_modules/ajv": { "version": "6.12.6", @@ -1547,29 +1982,6 @@ "node": ">=12" } }, - "node_modules/alcalzone-shared/node_modules/debug": { - "version": "4.3.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz", - "integrity": "sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==", - "dev": true, - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/alcalzone-shared/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, "node_modules/ansi-colors": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", @@ -1616,12 +2028,159 @@ "node": ">= 8" } }, + "node_modules/are-docs-informative": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/are-docs-informative/-/are-docs-informative-0.0.2.tgz", + "integrity": "sha512-ixiS0nLNNG5jNQzgZJNoUpBKdo9yTYZMGJ+QgT2jmjR7G7+QHRCc4v6LQ3NgE7EBJq+o0ams3waJwkrlBom8Ig==", + "dev": true, + "peer": true, + "engines": { + "node": ">=14" + } + }, "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "dev": true }, + "node_modules/array-buffer-byte-length": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.1.tgz", + "integrity": "sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.5", + "is-array-buffer": "^3.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-includes": { + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.8.tgz", + "integrity": "sha512-itaWrbYbqpGXkGhZPGUulwnhVf5Hpy1xiCFsGqyIGglbBxmG5vSjxQen3/WGOjPpNEv1RtBLKxbmVXm8HpJStQ==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.4", + "is-string": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.findlast": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/array.prototype.findlast/-/array.prototype.findlast-1.2.5.tgz", + "integrity": "sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flat": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.2.tgz", + "integrity": "sha512-djYB+Zx2vLewY8RWlNCUdHjDXs2XOgm602S9E7P/UpHgfeHL00cRiIF+IN/G/aUJ7kGPb6yO/ErDI5V2s8iycA==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "es-shim-unscopables": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flatmap": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.2.tgz", + "integrity": "sha512-Ewyx0c9PmpcsByhSW4r+9zDU7sGjFc86qf/kKtuSCRdhfbk0SNLLkaT5qvcHnRGgc5NP/ly/y+qkXkqONX54CQ==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "es-shim-unscopables": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.tosorted": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.4.tgz", + "integrity": "sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.3", + "es-errors": "^1.3.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/arraybuffer.prototype.slice": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.3.tgz", + "integrity": "sha512-bMxMKAjg13EBSVscxTaYA4mRc5t1UAXa2kXiGTNfZ079HIWXEkKmkgFrh/nJqamaLSrXO5H4WFFkPEaLJWbs3A==", + "dev": true, + "peer": true, + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", + "es-abstract": "^1.22.3", + "es-errors": "^1.2.1", + "get-intrinsic": "^1.2.3", + "is-array-buffer": "^3.0.4", + "is-shared-array-buffer": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/arrify": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz", @@ -1661,6 +2220,22 @@ "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" }, + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "dev": true, + "peer": true, + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/aws-sign2": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", @@ -1765,6 +2340,7 @@ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, + "peer": true, "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -1788,23 +2364,90 @@ "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", "dev": true }, - "node_modules/buffer-equal-constant-time": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", - "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", - "dev": true - }, - "node_modules/buffer-from": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", + "node_modules/browserslist": { + "version": "4.24.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.2.tgz", + "integrity": "sha512-ZIc+Q62revdMcqC6aChtW4jz3My3klmCO1fEmINZY/8J3EpBg5/A/D0AKmBveUh6pgoeycoMkVMko84tuYS+Gg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "peer": true, + "dependencies": { + "caniuse-lite": "^1.0.30001669", + "electron-to-chromium": "^1.5.41", + "node-releases": "^2.0.18", + "update-browserslist-db": "^1.1.1" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", + "dev": true + }, + "node_modules/buffer-from": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", "dev": true }, + "node_modules/builtin-modules": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.3.0.tgz", + "integrity": "sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==", + "dev": true, + "peer": true, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/call-bind": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", + "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", + "dev": true, + "peer": true, + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", "dev": true, + "peer": true, "engines": { "node": ">=6" } @@ -1821,6 +2464,27 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/caniuse-lite": { + "version": "1.0.30001684", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001684.tgz", + "integrity": "sha512-G1LRwLIQjBQoyq0ZJGqGIJUXzJ8irpbjHLpVRXDvBEScFJ9b17sgK6vlx0GAJFE21okD7zXl08rRRUfq6HdoEQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "peer": true + }, "node_modules/caseless": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", @@ -1938,6 +2602,45 @@ "fsevents": "~2.3.2" } }, + "node_modules/ci-info": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.1.0.tgz", + "integrity": "sha512-HutrvTNsF48wnxkzERIXOe5/mlcfFcbfCmwcg6CJnizbSue78AbDt+1cgl26zwn61WFxhcPykPfZrbqjGmBb4A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/clean-regexp": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/clean-regexp/-/clean-regexp-1.0.0.tgz", + "integrity": "sha512-GfisEZEJvzKrmGWkvfhgzcz/BllN1USeqD2V6tg14OAOgaCD2Z/PUEuxnAZ/nPvmaHRG7a8y77p1T/IRQ4D1Hw==", + "dev": true, + "peer": true, + "dependencies": { + "escape-string-regexp": "^1.0.5" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/clean-regexp/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "peer": true, + "engines": { + "node": ">=0.8.0" + } + }, "node_modules/cliui": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", @@ -1986,11 +2689,36 @@ "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" }, + "node_modules/comment-parser": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/comment-parser/-/comment-parser-1.4.1.tgz", + "integrity": "sha512-buhp5kePrmda3vhc5B9t7pUQXAb2Tnd0qgpkIhPhkHXxJpiPJ11H0ZEU0oBpJ2QztSbzG/ZxMj/CHsYJqRHmyg==", + "dev": true, + "peer": true, + "engines": { + "node": ">= 12.0.0" + } + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "dev": true + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "peer": true + }, + "node_modules/core-js-compat": { + "version": "3.39.0", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.39.0.tgz", + "integrity": "sha512-VgEUx3VwlExr5no0tXlBt+silBvhTryPwCXRI2Id1PN8WTKu7MreethvddqOubrYxkFdv/RnYrqlv1sFNAUelw==", + "dev": true, + "peer": true, + "dependencies": { + "browserslist": "^4.24.2" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } }, "node_modules/core-util-is": { "version": "1.0.2", @@ -1998,9 +2726,9 @@ "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" }, "node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "dev": true, "dependencies": { "path-key": "^3.1.0", @@ -2037,6 +2765,77 @@ "node": ">=0.10" } }, + "node_modules/data-view-buffer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.1.tgz", + "integrity": "sha512-0lht7OugA5x3iJLOWFhWK/5ehONdprk0ISXqVFn/NFrDu+cuc8iADFrGQz5BnRK7LLU3JmkbXSxaqX+/mXYtUA==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/data-view-byte-length": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.1.tgz", + "integrity": "sha512-4J7wRJD3ABAzr8wP+OcIcqq2dlUKp4DVflx++hs5h5ZKydWMI6/D/fAot+yh6g2tHh8fLFTvNOaVN357NvSrOQ==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/data-view-byte-offset": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.0.tgz", + "integrity": "sha512-t/Ygsytq+R995EJ5PZlD4Cu56sWa8InXySaViRzw9apusqsOO2bQP+SbYzAhR0pFKoB+43lYy8rWban9JSuXnA==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, "node_modules/deep-eql": { "version": "4.1.4", "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.4.tgz", @@ -2055,6 +2854,42 @@ "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", "dev": true }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dev": true, + "peer": true, + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "dev": true, + "peer": true, + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/delay": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/delay/-/delay-5.0.0.tgz", @@ -2083,6 +2918,19 @@ "node": ">=0.3.1" } }, + "node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "peer": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/ecc-jsbn": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", @@ -2101,6 +2949,13 @@ "safe-buffer": "^5.0.1" } }, + "node_modules/electron-to-chromium": { + "version": "1.5.66", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.66.tgz", + "integrity": "sha512-pI2QF6+i+zjPbqRzJwkMvtvkdI7MjVbSh2g8dlMguDJIXEPw+kwasS1Jl+YGPEBfGVxsVgGUratAKymPdPo2vQ==", + "dev": true, + "peer": true + }, "node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", @@ -2147,70 +3002,254 @@ "integrity": "sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==", "dev": true }, - "node_modules/es6-promise": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", - "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==" - }, - "node_modules/es6-promisify": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz", - "integrity": "sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM=", + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "peer": true, "dependencies": { - "es6-promise": "^4.0.3" + "is-arrayish": "^0.2.1" } }, - "node_modules/esbuild": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.20.2.tgz", - "integrity": "sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g==", + "node_modules/es-abstract": { + "version": "1.23.5", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.5.tgz", + "integrity": "sha512-vlmniQ0WNPwXqA0BnmwV3Ng7HxiGlh6r5U6JcTMNx8OilcAGqVJBHJcPjqOMaczU9fRuRK5Px2BdVyPRnKMMVQ==", "dev": true, - "hasInstallScript": true, - "bin": { - "esbuild": "bin/esbuild" + "peer": true, + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "arraybuffer.prototype.slice": "^1.0.3", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", + "data-view-buffer": "^1.0.1", + "data-view-byte-length": "^1.0.1", + "data-view-byte-offset": "^1.0.0", + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-set-tostringtag": "^2.0.3", + "es-to-primitive": "^1.2.1", + "function.prototype.name": "^1.1.6", + "get-intrinsic": "^1.2.4", + "get-symbol-description": "^1.0.2", + "globalthis": "^1.0.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.0.3", + "has-symbols": "^1.0.3", + "hasown": "^2.0.2", + "internal-slot": "^1.0.7", + "is-array-buffer": "^3.0.4", + "is-callable": "^1.2.7", + "is-data-view": "^1.0.1", + "is-negative-zero": "^2.0.3", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.3", + "is-string": "^1.0.7", + "is-typed-array": "^1.1.13", + "is-weakref": "^1.0.2", + "object-inspect": "^1.13.3", + "object-keys": "^1.1.1", + "object.assign": "^4.1.5", + "regexp.prototype.flags": "^1.5.3", + "safe-array-concat": "^1.1.2", + "safe-regex-test": "^1.0.3", + "string.prototype.trim": "^1.2.9", + "string.prototype.trimend": "^1.0.8", + "string.prototype.trimstart": "^1.0.8", + "typed-array-buffer": "^1.0.2", + "typed-array-byte-length": "^1.0.1", + "typed-array-byte-offset": "^1.0.2", + "typed-array-length": "^1.0.6", + "unbox-primitive": "^1.0.2", + "which-typed-array": "^1.1.15" }, "engines": { - "node": ">=12" + "node": ">= 0.4" }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.20.2", - "@esbuild/android-arm": "0.20.2", - "@esbuild/android-arm64": "0.20.2", - "@esbuild/android-x64": "0.20.2", - "@esbuild/darwin-arm64": "0.20.2", - "@esbuild/darwin-x64": "0.20.2", - "@esbuild/freebsd-arm64": "0.20.2", - "@esbuild/freebsd-x64": "0.20.2", - "@esbuild/linux-arm": "0.20.2", - "@esbuild/linux-arm64": "0.20.2", - "@esbuild/linux-ia32": "0.20.2", - "@esbuild/linux-loong64": "0.20.2", - "@esbuild/linux-mips64el": "0.20.2", - "@esbuild/linux-ppc64": "0.20.2", - "@esbuild/linux-riscv64": "0.20.2", - "@esbuild/linux-s390x": "0.20.2", - "@esbuild/linux-x64": "0.20.2", - "@esbuild/netbsd-x64": "0.20.2", - "@esbuild/openbsd-x64": "0.20.2", - "@esbuild/sunos-x64": "0.20.2", - "@esbuild/win32-arm64": "0.20.2", - "@esbuild/win32-ia32": "0.20.2", - "@esbuild/win32-x64": "0.20.2" + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/escalade": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "node_modules/es-define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", + "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", "dev": true, + "peer": true, + "dependencies": { + "get-intrinsic": "^1.2.4" + }, "engines": { - "node": ">=6" + "node": ">= 0.4" } }, - "node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "peer": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-iterator-helpers": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.2.0.tgz", + "integrity": "sha512-tpxqxncxnpw3c93u8n3VOzACmRFoVmWJqbWXvX/JfKbkhBw1oslgPrUfeSt2psuqyEJFD6N/9lg5i7bsKpoq+Q==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.3", + "es-errors": "^1.3.0", + "es-set-tostringtag": "^2.0.3", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "globalthis": "^1.0.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.0.3", + "has-symbols": "^1.0.3", + "internal-slot": "^1.0.7", + "iterator.prototype": "^1.1.3", + "safe-array-concat": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-module-lexer": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.5.4.tgz", + "integrity": "sha512-MVNK56NiMrOwitFB7cqDwq0CQutbw+0BvLshJSse0MUNU+y1FC3bUS/AQg7oUng+/wKrrki7JfmwtVHkVfPLlw==", + "dev": true, + "peer": true + }, + "node_modules/es-object-atoms": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.0.0.tgz", + "integrity": "sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==", + "dev": true, + "peer": true, + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.3.tgz", + "integrity": "sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ==", + "dev": true, + "peer": true, + "dependencies": { + "get-intrinsic": "^1.2.4", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-shim-unscopables": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.2.tgz", + "integrity": "sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw==", + "dev": true, + "peer": true, + "dependencies": { + "hasown": "^2.0.0" + } + }, + "node_modules/es-to-primitive": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.3.0.tgz", + "integrity": "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==", + "dev": true, + "peer": true, + "dependencies": { + "is-callable": "^1.2.7", + "is-date-object": "^1.0.5", + "is-symbol": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es6-promise": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", + "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==" + }, + "node_modules/es6-promisify": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz", + "integrity": "sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM=", + "dependencies": { + "es6-promise": "^4.0.3" + } + }, + "node_modules/esbuild": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.20.2.tgz", + "integrity": "sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.20.2", + "@esbuild/android-arm": "0.20.2", + "@esbuild/android-arm64": "0.20.2", + "@esbuild/android-x64": "0.20.2", + "@esbuild/darwin-arm64": "0.20.2", + "@esbuild/darwin-x64": "0.20.2", + "@esbuild/freebsd-arm64": "0.20.2", + "@esbuild/freebsd-x64": "0.20.2", + "@esbuild/linux-arm": "0.20.2", + "@esbuild/linux-arm64": "0.20.2", + "@esbuild/linux-ia32": "0.20.2", + "@esbuild/linux-loong64": "0.20.2", + "@esbuild/linux-mips64el": "0.20.2", + "@esbuild/linux-ppc64": "0.20.2", + "@esbuild/linux-riscv64": "0.20.2", + "@esbuild/linux-s390x": "0.20.2", + "@esbuild/linux-x64": "0.20.2", + "@esbuild/netbsd-x64": "0.20.2", + "@esbuild/openbsd-x64": "0.20.2", + "@esbuild/sunos-x64": "0.20.2", + "@esbuild/win32-arm64": "0.20.2", + "@esbuild/win32-ia32": "0.20.2", + "@esbuild/win32-x64": "0.20.2" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", "dev": true, "engines": { "node": ">=10" @@ -2252,59 +3291,63 @@ } }, "node_modules/eslint": { - "version": "8.57.1", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz", - "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==", - "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", + "version": "9.15.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.15.0.tgz", + "integrity": "sha512-7CrWySmIibCgT1Os28lUU6upBshZ+GxybLOrmRzi08kS8MBuO8QA7pXEgYgY5W8vK3e74xv0lpjo9DbaGU9Rkw==", "dev": true, + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", - "@eslint-community/regexpp": "^4.6.1", - "@eslint/eslintrc": "^2.1.4", - "@eslint/js": "8.57.1", - "@humanwhocodes/config-array": "^0.13.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.19.0", + "@eslint/core": "^0.9.0", + "@eslint/eslintrc": "^3.2.0", + "@eslint/js": "9.15.0", + "@eslint/plugin-kit": "^0.2.3", + "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", - "@nodelib/fs.walk": "^1.2.8", - "@ungap/structured-clone": "^1.2.0", + "@humanwhocodes/retry": "^0.4.1", + "@types/estree": "^1.0.6", + "@types/json-schema": "^7.0.15", "ajv": "^6.12.4", "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", + "cross-spawn": "^7.0.5", "debug": "^4.3.2", - "doctrine": "^3.0.0", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.2.2", - "eslint-visitor-keys": "^3.4.3", - "espree": "^9.6.1", - "esquery": "^1.4.2", + "eslint-scope": "^8.2.0", + "eslint-visitor-keys": "^4.2.0", + "espree": "^10.3.0", + "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^6.0.1", + "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", - "globals": "^13.19.0", - "graphemer": "^1.4.0", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", - "is-path-inside": "^3.0.3", - "js-yaml": "^4.1.0", "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.4.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", - "optionator": "^0.9.3", - "strip-ansi": "^6.0.1", - "text-table": "^0.2.0" + "optionator": "^0.9.3" }, "bin": { "eslint": "bin/eslint.js" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { - "url": "https://opencollective.com/eslint" + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } } }, "node_modules/eslint-config-prettier": { @@ -2312,6 +3355,7 @@ "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-9.1.0.tgz", "integrity": "sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw==", "dev": true, + "peer": true, "bin": { "eslint-config-prettier": "bin/cli.js" }, @@ -2319,11 +3363,69 @@ "eslint": ">=7.0.0" } }, + "node_modules/eslint-plugin-jsdoc": { + "version": "50.6.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-50.6.0.tgz", + "integrity": "sha512-tCNp4fR79Le3dYTPB0dKEv7yFyvGkUCa+Z3yuTrrNGGOxBlXo9Pn0PEgroOZikUQOGjxoGMVKNjrOHcYEdfszg==", + "dev": true, + "peer": true, + "dependencies": { + "@es-joy/jsdoccomment": "~0.49.0", + "are-docs-informative": "^0.0.2", + "comment-parser": "1.4.1", + "debug": "^4.3.6", + "escape-string-regexp": "^4.0.0", + "espree": "^10.1.0", + "esquery": "^1.6.0", + "parse-imports": "^2.1.1", + "semver": "^7.6.3", + "spdx-expression-parse": "^4.0.0", + "synckit": "^0.9.1" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0 || ^9.0.0" + } + }, + "node_modules/eslint-plugin-jsdoc/node_modules/eslint-visitor-keys": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", + "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", + "dev": true, + "peer": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-plugin-jsdoc/node_modules/espree": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz", + "integrity": "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==", + "dev": true, + "peer": true, + "dependencies": { + "acorn": "^8.14.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, "node_modules/eslint-plugin-prettier": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.2.1.tgz", "integrity": "sha512-gH3iR3g4JfF+yYPaJYkN7jEl9QbweL/YfkoRlNnuIEHEz1vHVlCmWOS+eGGiRuzHQXdJFCOTxRgvju9b8VUmrw==", "dev": true, + "peer": true, "dependencies": { "prettier-linter-helpers": "^1.0.0", "synckit": "^0.9.1" @@ -2349,17 +3451,136 @@ } } }, + "node_modules/eslint-plugin-react": { + "version": "7.37.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.37.2.tgz", + "integrity": "sha512-EsTAnj9fLVr/GZleBLFbj/sSuXeWmp1eXIN60ceYnZveqEaUCyW4X+Vh4WTdUhCkW4xutXYqTXCUSyqD4rB75w==", + "dev": true, + "peer": true, + "dependencies": { + "array-includes": "^3.1.8", + "array.prototype.findlast": "^1.2.5", + "array.prototype.flatmap": "^1.3.2", + "array.prototype.tosorted": "^1.1.4", + "doctrine": "^2.1.0", + "es-iterator-helpers": "^1.1.0", + "estraverse": "^5.3.0", + "hasown": "^2.0.2", + "jsx-ast-utils": "^2.4.1 || ^3.0.0", + "minimatch": "^3.1.2", + "object.entries": "^1.1.8", + "object.fromentries": "^2.0.8", + "object.values": "^1.2.0", + "prop-types": "^15.8.1", + "resolve": "^2.0.0-next.5", + "semver": "^6.3.1", + "string.prototype.matchall": "^4.0.11", + "string.prototype.repeat": "^1.0.0" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7" + } + }, + "node_modules/eslint-plugin-react-hooks": { + "version": "5.1.0-rc-fb9a90fa48-20240614", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-5.1.0-rc-fb9a90fa48-20240614.tgz", + "integrity": "sha512-xsiRwaDNF5wWNC4ZHLut+x/YcAxksUd9Rizt7LaEn3bV8VyYRpXnRJQlLOfYaVy9esk4DFP4zPPnoNVjq5Gc0w==", + "dev": true, + "peer": true, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0" + } + }, + "node_modules/eslint-plugin-react/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "peer": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/eslint-plugin-react/node_modules/resolve": { + "version": "2.0.0-next.5", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.5.tgz", + "integrity": "sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==", + "dev": true, + "peer": true, + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/eslint-plugin-react/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "peer": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/eslint-plugin-unicorn": { + "version": "55.0.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-unicorn/-/eslint-plugin-unicorn-55.0.0.tgz", + "integrity": "sha512-n3AKiVpY2/uDcGrS3+QsYDkjPfaOrNrsfQxU9nt5nitd9KuvVXrfAvgCO9DYPSfap+Gqjw9EOrXIsBp5tlHZjA==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.24.5", + "@eslint-community/eslint-utils": "^4.4.0", + "ci-info": "^4.0.0", + "clean-regexp": "^1.0.0", + "core-js-compat": "^3.37.0", + "esquery": "^1.5.0", + "globals": "^15.7.0", + "indent-string": "^4.0.0", + "is-builtin-module": "^3.2.1", + "jsesc": "^3.0.2", + "pluralize": "^8.0.0", + "read-pkg-up": "^7.0.1", + "regexp-tree": "^0.1.27", + "regjsparser": "^0.10.0", + "semver": "^7.6.1", + "strip-indent": "^3.0.0" + }, + "engines": { + "node": ">=18.18" + }, + "funding": { + "url": "https://github.com/sindresorhus/eslint-plugin-unicorn?sponsor=1" + }, + "peerDependencies": { + "eslint": ">=8.56.0" + } + }, "node_modules/eslint-scope": { - "version": "7.2.2", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", - "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.2.0.tgz", + "integrity": "sha512-PHlWUfG6lvPc3yvP5A4PNyBL1W8fkDUccmI21JUu/+GKZBoH/W5u6usENXUrWFRsyoW5ACUjFGgAFQp5gUlb/A==", "dev": true, + "peer": true, "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "url": "https://opencollective.com/eslint" @@ -2370,6 +3591,7 @@ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", "dev": true, + "peer": true, "engines": { "node": ">=4.0" } @@ -2386,72 +3608,43 @@ "url": "https://opencollective.com/eslint" } }, - "node_modules/eslint/node_modules/@eslint/eslintrc": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", - "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", + "node_modules/eslint/node_modules/eslint-visitor-keys": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", + "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", "dev": true, - "dependencies": { - "ajv": "^6.12.4", - "debug": "^4.3.2", - "espree": "^9.6.0", - "globals": "^13.19.0", - "ignore": "^5.2.0", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", - "minimatch": "^3.1.2", - "strip-json-comments": "^3.1.1" - }, + "peer": true, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "url": "https://opencollective.com/eslint" } }, - "node_modules/eslint/node_modules/@eslint/js": { - "version": "8.57.1", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz", - "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==", - "dev": true, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, - "node_modules/eslint/node_modules/debug": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", - "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "node_modules/eslint/node_modules/espree": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz", + "integrity": "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==", "dev": true, + "peer": true, "dependencies": { - "ms": "^2.1.3" + "acorn": "^8.14.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.0" }, "engines": { - "node": ">=6.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } + "funding": { + "url": "https://opencollective.com/eslint" } }, - "node_modules/eslint/node_modules/doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", - "dev": true, - "dependencies": { - "esutils": "^2.0.2" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/eslint/node_modules/glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "node_modules/eslint/node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", "dev": true, + "peer": true, "dependencies": { "is-glob": "^4.0.3" }, @@ -2459,26 +3652,12 @@ "node": ">=10.13.0" } }, - "node_modules/eslint/node_modules/globals": { - "version": "13.24.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", - "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", - "dev": true, - "dependencies": { - "type-fest": "^0.20.2" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/eslint/node_modules/levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", "dev": true, + "peer": true, "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" @@ -2487,17 +3666,12 @@ "node": ">= 0.8.0" } }, - "node_modules/eslint/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true - }, "node_modules/eslint/node_modules/optionator": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", "dev": true, + "peer": true, "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", @@ -2515,6 +3689,7 @@ "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", "dev": true, + "peer": true, "engines": { "node": ">= 0.8.0" } @@ -2524,6 +3699,7 @@ "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", "dev": true, + "peer": true, "dependencies": { "prelude-ls": "^1.2.1" }, @@ -2566,6 +3742,7 @@ "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", "dev": true, + "peer": true, "dependencies": { "estraverse": "^5.1.0" }, @@ -2578,6 +3755,7 @@ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", "dev": true, + "peer": true, "engines": { "node": ">=4.0" } @@ -2587,6 +3765,7 @@ "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", "dev": true, + "peer": true, "dependencies": { "estraverse": "^5.2.0" }, @@ -2599,6 +3778,7 @@ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", "dev": true, + "peer": true, "engines": { "node": ">=4.0" } @@ -2707,7 +3887,25 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz", "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==", - "dev": true + "dev": true, + "peer": true + }, + "node_modules/fast-glob": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", + "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", + "dev": true, + "peer": true, + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } }, "node_modules/fast-json-stable-stringify": { "version": "2.1.0", @@ -2731,20 +3929,22 @@ "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", "dev": true, + "peer": true, "dependencies": { "reusify": "^1.0.4" } }, "node_modules/file-entry-cache": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", - "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", "dev": true, + "peer": true, "dependencies": { - "flat-cache": "^3.0.4" + "flat-cache": "^4.0.0" }, "engines": { - "node": "^10.12.0 || >=12.0.0" + "node": ">=16.0.0" } }, "node_modules/fill-keys": { @@ -2798,24 +3998,25 @@ } }, "node_modules/flat-cache": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", - "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", "dev": true, + "peer": true, "dependencies": { "flatted": "^3.2.9", - "keyv": "^4.5.3", - "rimraf": "^3.0.2" + "keyv": "^4.5.4" }, "engines": { - "node": "^10.12.0 || >=12.0.0" + "node": ">=16" } }, "node_modules/flatted": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz", - "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", - "dev": true + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.2.tgz", + "integrity": "sha512-AiwGJM8YcNOaobumgtng+6NHuOqC3A7MixFeDafM3X9cIUM+xUXoS5Vfgf+OihAYe20fxqNM9yPBXJzRtZ/4eA==", + "dev": true, + "peer": true }, "node_modules/follow-redirects": { "version": "1.15.6", @@ -2837,6 +4038,16 @@ } } }, + "node_modules/for-each": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", + "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", + "dev": true, + "peer": true, + "dependencies": { + "is-callable": "^1.1.3" + } + }, "node_modules/forever-agent": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", @@ -2892,6 +4103,45 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "peer": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/function.prototype.name": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.6.tgz", + "integrity": "sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "functions-have-names": "^1.2.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "dev": true, + "peer": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/gaxios": { "version": "5.1.3", "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-5.1.3.tgz", @@ -2950,33 +4200,50 @@ "node": "*" } }, - "node_modules/getpass": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", - "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", + "node_modules/get-intrinsic": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", + "dev": true, + "peer": true, "dependencies": { - "assert-plus": "^1.0.0" + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Glob versions prior to v9 are no longer supported", + "node_modules/get-symbol-description": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.2.tgz", + "integrity": "sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg==", "dev": true, + "peer": true, "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "call-bind": "^1.0.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4" }, "engines": { - "node": "*" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/sponsors/isaacs" + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", + "dependencies": { + "assert-plus": "^1.0.0" } }, "node_modules/glob-parent": { @@ -2991,6 +4258,36 @@ "node": ">= 6" } }, + "node_modules/globals": { + "version": "15.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-15.12.0.tgz", + "integrity": "sha512-1+gLErljJFhbOVyaetcwJiJ4+eLe45S2E7P5UiZ9xGfeq3ATQf5DOv9G7MH3gGbKQLkzmNh2DxfZwLdw+j6oTQ==", + "dev": true, + "peer": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globalthis": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", + "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", + "dev": true, + "peer": true, + "dependencies": { + "define-properties": "^1.2.1", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/globalyzer": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/globalyzer/-/globalyzer-0.1.0.tgz", @@ -3099,6 +4396,19 @@ "node": ">=12.0.0" } }, + "node_modules/gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "dev": true, + "peer": true, + "dependencies": { + "get-intrinsic": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/graceful-fs": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.3.tgz", @@ -3109,7 +4419,8 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", - "dev": true + "dev": true, + "peer": true }, "node_modules/gtoken": { "version": "6.1.2", @@ -3146,6 +4457,84 @@ "node": ">=6" } }, + "node_modules/has-bigints": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", + "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", + "dev": true, + "peer": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dev": true, + "peer": true, + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", + "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", + "dev": true, + "peer": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "dev": true, + "peer": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dev": true, + "peer": true, + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "peer": true, + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/he": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", @@ -3155,6 +4544,13 @@ "he": "bin/he" } }, + "node_modules/hosted-git-info": { + "version": "2.8.9", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", + "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", + "dev": true, + "peer": true + }, "node_modules/html-tags": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/html-tags/-/html-tags-3.3.1.tgz", @@ -3181,29 +4577,6 @@ "node": ">= 6" } }, - "node_modules/http-proxy-agent/node_modules/debug": { - "version": "4.3.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz", - "integrity": "sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==", - "dev": true, - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/http-proxy-agent/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, "node_modules/http-signature": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", @@ -3231,29 +4604,6 @@ "node": ">= 6" } }, - "node_modules/https-proxy-agent/node_modules/debug": { - "version": "4.3.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz", - "integrity": "sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==", - "dev": true, - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/https-proxy-agent/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, "node_modules/human-signals": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", @@ -3264,10 +4614,11 @@ } }, "node_modules/ignore": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz", - "integrity": "sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==", + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", "dev": true, + "peer": true, "engines": { "node": ">= 4" } @@ -3277,6 +4628,7 @@ "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", "dev": true, + "peer": true, "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" @@ -3291,12 +4643,23 @@ "node_modules/imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", "dev": true, + "peer": true, "engines": { "node": ">=0.8.19" } }, + "node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true, + "peer": true, + "engines": { + "node": ">=8" + } + }, "node_modules/inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", @@ -3314,6 +4677,74 @@ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "dev": true }, + "node_modules/internal-slot": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.7.tgz", + "integrity": "sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g==", + "dev": true, + "peer": true, + "dependencies": { + "es-errors": "^1.3.0", + "hasown": "^2.0.0", + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/is-array-buffer": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.4.tgz", + "integrity": "sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true, + "peer": true + }, + "node_modules/is-async-function": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.0.0.tgz", + "integrity": "sha512-Y1JXKrfykRJGdlDwdKlLpLyMIiWqWvuSd17TvZk68PLAOGOoF4Xyav1z0Xhoi+gCYjZVeC5SI+hYFOfvXmGRCA==", + "dev": true, + "peer": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-bigint": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", + "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", + "dev": true, + "peer": true, + "dependencies": { + "has-bigints": "^1.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-binary-path": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", @@ -3326,6 +4757,100 @@ "node": ">=8" } }, + "node_modules/is-boolean-object": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", + "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-builtin-module": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-3.2.1.tgz", + "integrity": "sha512-BSLE3HnV2syZ0FK0iMA/yUGplUeMmNz4AW5fnTunbCIqZi4vG3WjJT9FHMy5D69xmAYBHXQhJdALdpwVxV501A==", + "dev": true, + "peer": true, + "dependencies": { + "builtin-modules": "^3.3.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "dev": true, + "peer": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-core-module": { + "version": "2.15.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.1.tgz", + "integrity": "sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==", + "dev": true, + "peer": true, + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-data-view": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.1.tgz", + "integrity": "sha512-AHkaJrsUVW6wq6JS8y3JnM/GJF/9cf+k20+iDzlSaJrinEo5+7vRiteOSwBhHRiAyQATN1AmY4hwzxJKPmYf+w==", + "dev": true, + "peer": true, + "dependencies": { + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-date-object": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", + "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", + "dev": true, + "peer": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -3335,6 +4860,22 @@ "node": ">=0.10.0" } }, + "node_modules/is-finalizationregistry": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.1.0.tgz", + "integrity": "sha512-qfMdqbAQEwBw78ZyReKnlA8ezmPdb9BemzIIip/JkjaZUhitfXDkkr+3QTboW0JrSXT1QWyYShpvnNHGZ4c4yA==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", @@ -3344,6 +4885,22 @@ "node": ">=8" } }, + "node_modules/is-generator-function": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.10.tgz", + "integrity": "sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==", + "dev": true, + "peer": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-glob": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", @@ -3356,48 +4913,175 @@ "node": ">=0.10.0" } }, - "node_modules/is-html": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-html/-/is-html-2.0.0.tgz", - "integrity": "sha512-S+OpgB5i7wzIue/YSE5hg0e5ZYfG3hhpNh9KGl6ayJ38p7ED6wxQLd1TV91xHpcTvw90KMJ9EwN3F/iNflHBVg==", + "node_modules/is-html": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-html/-/is-html-2.0.0.tgz", + "integrity": "sha512-S+OpgB5i7wzIue/YSE5hg0e5ZYfG3hhpNh9KGl6ayJ38p7ED6wxQLd1TV91xHpcTvw90KMJ9EwN3F/iNflHBVg==", + "dev": true, + "dependencies": { + "html-tags": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-map": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", + "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", + "dev": true, + "peer": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-negative-zero": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", + "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", + "dev": true, + "peer": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-number-object": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", + "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", + "dev": true, + "peer": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-object": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-object/-/is-object-1.0.1.tgz", + "integrity": "sha1-iVJojF7C/9awPsyF52ngKQMINHA=", + "dev": true + }, + "node_modules/is-regex": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", + "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-set": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", + "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", + "dev": true, + "peer": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-shared-array-buffer": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.3.tgz", + "integrity": "sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-stream-ended": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-stream-ended/-/is-stream-ended-0.1.4.tgz", + "integrity": "sha512-xj0XPvmr7bQFTvirqnFr50o0hQIh6ZItDqloxt5aJrR4NQsYeSsyFQERYGCAzfindAcnKjINnwEEgLx4IqVzQw==", + "dev": true + }, + "node_modules/is-string": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", + "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", "dev": true, + "peer": true, "dependencies": { - "html-tags": "^3.0.0" + "has-tostringtag": "^1.0.0" }, "engines": { - "node": ">=8" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "node_modules/is-symbol": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", + "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", "dev": true, + "peer": true, + "dependencies": { + "has-symbols": "^1.0.2" + }, "engines": { - "node": ">=0.12.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-object": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-object/-/is-object-1.0.1.tgz", - "integrity": "sha1-iVJojF7C/9awPsyF52ngKQMINHA=", - "dev": true - }, - "node_modules/is-path-inside": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", - "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "node_modules/is-typed-array": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.13.tgz", + "integrity": "sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw==", "dev": true, + "peer": true, + "dependencies": { + "which-typed-array": "^1.1.14" + }, "engines": { - "node": ">=8" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-stream-ended": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-stream-ended/-/is-stream-ended-0.1.4.tgz", - "integrity": "sha512-xj0XPvmr7bQFTvirqnFr50o0hQIh6ZItDqloxt5aJrR4NQsYeSsyFQERYGCAzfindAcnKjINnwEEgLx4IqVzQw==", - "dev": true - }, "node_modules/is-typedarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", @@ -3415,6 +5099,56 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-weakmap": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", + "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", + "dev": true, + "peer": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakref": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", + "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakset": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.3.tgz", + "integrity": "sha512-LvIm3/KWzS9oRFHugab7d+M/GcBXuXX5xZkzPmN+NxihdQlZUQ4dWuSV1xR/sq6upL1TJEDrfBgRepHFdBtSNQ==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.7", + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true, + "peer": true + }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -3434,6 +5168,23 @@ "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" }, + "node_modules/iterator.prototype": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.3.tgz", + "integrity": "sha512-FW5iMbeQ6rBGm/oKgzq2aW4KvAGpxPzYES8N4g4xNXUKpL1mclMvOe+76AcLDTvD+Ze+sOpVhgdAQEKF4L9iGQ==", + "dev": true, + "peer": true, + "dependencies": { + "define-properties": "^1.2.1", + "get-intrinsic": "^1.2.1", + "has-symbols": "^1.0.3", + "reflect.getprototypeof": "^1.0.4", + "set-function-name": "^2.0.1" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/jayson": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/jayson/-/jayson-4.1.2.tgz", @@ -3492,6 +5243,13 @@ } } }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "peer": true + }, "node_modules/js-yaml": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", @@ -3547,6 +5305,16 @@ "node": ">=12.0.0" } }, + "node_modules/jsdoc-type-pratt-parser": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/jsdoc-type-pratt-parser/-/jsdoc-type-pratt-parser-4.1.0.tgz", + "integrity": "sha512-Hicd6JK5Njt2QB6XYFS7ok9e37O8AYk3jTcppG4YVQnYjOemymvTcmc7OWsmq/Qqj5TdRFO5/x/tIPmBeRtGHg==", + "dev": true, + "peer": true, + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/jsdoc/node_modules/escape-string-regexp": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", @@ -3556,6 +5324,19 @@ "node": ">=8" } }, + "node_modules/jsesc": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz", + "integrity": "sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==", + "dev": true, + "peer": true, + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/json-bigint": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", @@ -3569,7 +5350,15 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", - "dev": true + "dev": true, + "peer": true + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true, + "peer": true }, "node_modules/json-schema": { "version": "0.4.0", @@ -3584,8 +5373,9 @@ "node_modules/json-stable-stringify-without-jsonify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", - "dev": true + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "peer": true }, "node_modules/json-stringify-safe": { "version": "5.0.1", @@ -3641,6 +5431,22 @@ "node": ">=0.6.0" } }, + "node_modules/jsx-ast-utils": { + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", + "integrity": "sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==", + "dev": true, + "peer": true, + "dependencies": { + "array-includes": "^3.1.6", + "array.prototype.flat": "^1.3.1", + "object.assign": "^4.1.4", + "object.values": "^1.1.6" + }, + "engines": { + "node": ">=4.0" + } + }, "node_modules/just-extend": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-6.2.0.tgz", @@ -3673,6 +5479,7 @@ "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", "dev": true, + "peer": true, "dependencies": { "json-buffer": "3.0.1" } @@ -3699,6 +5506,13 @@ "node": ">= 0.8.0" } }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true, + "peer": true + }, "node_modules/linkify-it": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz", @@ -3744,7 +5558,8 @@ "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true + "dev": true, + "peer": true }, "node_modules/log-symbols": { "version": "4.1.0", @@ -3768,6 +5583,19 @@ "integrity": "sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==", "dev": true }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dev": true, + "peer": true, + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, "node_modules/loupe": { "version": "2.3.7", "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.7.tgz", @@ -3849,6 +5677,30 @@ "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", "dev": true }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "peer": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "peer": true, + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, "node_modules/mime-db": { "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", @@ -3877,11 +5729,22 @@ "node": ">=6" } }, + "node_modules/min-indent": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", + "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", + "dev": true, + "peer": true, + "engines": { + "node": ">=4" + } + }, "node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, + "peer": true, "dependencies": { "brace-expansion": "^1.1.7" }, @@ -3965,29 +5828,6 @@ "wrap-ansi": "^7.0.0" } }, - "node_modules/mocha/node_modules/debug": { - "version": "4.3.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz", - "integrity": "sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==", - "dev": true, - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/mocha/node_modules/debug/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, "node_modules/mocha/node_modules/glob": { "version": "8.1.0", "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", @@ -4029,12 +5869,6 @@ "node": ">=10" } }, - "node_modules/mocha/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true - }, "node_modules/mocha/node_modules/supports-color": { "version": "8.1.1", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", @@ -4074,11 +5908,18 @@ "integrity": "sha1-z4tP9PKWQGdNbN0CsOO8UjwrvcA=", "dev": true }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, "node_modules/natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", - "dev": true + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "peer": true }, "node_modules/nise": { "version": "6.1.1", @@ -4131,6 +5972,36 @@ "node": ">= 6.13.0" } }, + "node_modules/node-releases": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz", + "integrity": "sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==", + "dev": true, + "peer": true + }, + "node_modules/normalize-package-data": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "dev": true, + "peer": true, + "dependencies": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } + }, + "node_modules/normalize-package-data/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true, + "peer": true, + "bin": { + "semver": "bin/semver" + } + }, "node_modules/normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", @@ -4160,13 +6031,117 @@ "node": "*" } }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/object-hash": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", "dev": true, "engines": { - "node": ">= 6" + "node": ">= 6" + } + }, + "node_modules/object-inspect": { + "version": "1.13.3", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.3.tgz", + "integrity": "sha512-kDCGIbxkDSXE3euJZZXzc6to7fCrKHNI/hSRQnRuQ+BWjFNzZwiFF8fj/6o2t2G9/jTj8PSIYTfCLelLZEeRpA==", + "dev": true, + "peer": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "peer": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.5.tgz", + "integrity": "sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", + "has-symbols": "^1.0.3", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.entries": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.8.tgz", + "integrity": "sha512-cmopxi8VwRIAw/fkijJohSfpef5PdN0pMQJN6VC/ZKvn0LIknWD8KtgY6KlQdEc4tIjcQ3HxSMmnvtzIscdaYQ==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.fromentries": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.8.tgz", + "integrity": "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.values": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.0.tgz", + "integrity": "sha512-yBYjY9QX2hnRmZHAjG/f13MzmBzxzYgQhFrke06TTyKY5zSTEqkOeukBzIdVA3j3ulu8Qa3MbVFShV7T2RmGtQ==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/once": { @@ -4240,11 +6215,22 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "peer": true, + "engines": { + "node": ">=6" + } + }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", "dev": true, + "peer": true, "dependencies": { "callsites": "^3.0.0" }, @@ -4252,22 +6238,46 @@ "node": ">=6" } }, - "node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "node_modules/parse-imports": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/parse-imports/-/parse-imports-2.2.1.tgz", + "integrity": "sha512-OL/zLggRp8mFhKL0rNORUTR4yBYujK/uU+xZL+/0Rgm2QE4nLO9v8PzEweSJEbMGKmDRjJE4R3IMJlL2di4JeQ==", + "dev": true, + "peer": true, + "dependencies": { + "es-module-lexer": "^1.5.3", + "slashes": "^3.0.12" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", "dev": true, + "peer": true, + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, "engines": { "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", "dev": true, "engines": { - "node": ">=0.10.0" + "node": ">=8" } }, "node_modules/path-key": { @@ -4317,6 +6327,26 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/pluralize": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-8.0.0.tgz", + "integrity": "sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==", + "dev": true, + "peer": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/possible-typed-array-names": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz", + "integrity": "sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==", + "dev": true, + "peer": true, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/prelude-ls": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", @@ -4327,10 +6357,11 @@ } }, "node_modules/prettier": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.3.tgz", - "integrity": "sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==", + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.4.1.tgz", + "integrity": "sha512-G+YdqtITVZmOJje6QkXQWzl3fSfMxFwm1tjTyo9exhkmWSqC4Yhd1+lug++IlR2mvRVAxEDDWYkQdeSztajqgg==", "dev": true, + "peer": true, "bin": { "prettier": "bin/prettier.cjs" }, @@ -4346,6 +6377,7 @@ "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", "dev": true, + "peer": true, "dependencies": { "fast-diff": "^1.1.2" }, @@ -4353,6 +6385,18 @@ "node": ">=6.0.0" } }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "dev": true, + "peer": true, + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, "node_modules/proto3-json-serializer": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/proto3-json-serializer/-/proto3-json-serializer-1.1.1.tgz", @@ -4532,7 +6576,8 @@ "type": "consulting", "url": "https://feross.org/support" } - ] + ], + "peer": true }, "node_modules/randombytes": { "version": "2.1.0", @@ -4543,6 +6588,113 @@ "safe-buffer": "^5.1.0" } }, + "node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "dev": true, + "peer": true + }, + "node_modules/read-pkg": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", + "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==", + "dev": true, + "peer": true, + "dependencies": { + "@types/normalize-package-data": "^2.4.0", + "normalize-package-data": "^2.5.0", + "parse-json": "^5.0.0", + "type-fest": "^0.6.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/read-pkg-up": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz", + "integrity": "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==", + "dev": true, + "peer": true, + "dependencies": { + "find-up": "^4.1.0", + "read-pkg": "^5.2.0", + "type-fest": "^0.8.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/read-pkg-up/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "peer": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/read-pkg-up/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "peer": true, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/read-pkg-up/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "peer": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/read-pkg-up/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "peer": true, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/read-pkg/node_modules/type-fest": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz", + "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==", + "dev": true, + "peer": true, + "engines": { + "node": ">=8" + } + }, "node_modules/readable-stream": { "version": "3.6.2", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", @@ -4569,6 +6721,80 @@ "node": ">=8.10.0" } }, + "node_modules/reflect.getprototypeof": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.7.tgz", + "integrity": "sha512-bMvFGIUKlc/eSfXNX+aZ+EL95/EgZzuwA0OBPTbZZDEJw/0AkentjMuM1oiRfwHrshqk4RzdgiTg5CcDalXN5g==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "which-builtin-type": "^1.1.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/regexp-tree": { + "version": "0.1.27", + "resolved": "https://registry.npmjs.org/regexp-tree/-/regexp-tree-0.1.27.tgz", + "integrity": "sha512-iETxpjK6YoRWJG5o6hXLwvjYAoW+FEZn9os0PD/b6AP6xQwsa/Y7lCVgIixBbUPMfhu+i2LtdeAqVTgGlQarfA==", + "dev": true, + "peer": true, + "bin": { + "regexp-tree": "bin/regexp-tree" + } + }, + "node_modules/regexp.prototype.flags": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.3.tgz", + "integrity": "sha512-vqlC04+RQoFalODCbCumG2xIOvapzVMHwsyIGM/SIE8fRhFFsXeH8/QQ+s0T0kDAhKc4k30s73/0ydkHQz6HlQ==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "set-function-name": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/regjsparser": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.10.0.tgz", + "integrity": "sha512-qx+xQGZVsy55CH0a1hiVwHmqjLryfh7wQyF5HO07XJ9f7dQMY/gPQHhlyDkIzJKC+x2fUCpCcUODUUUFrm7SHA==", + "dev": true, + "peer": true, + "dependencies": { + "jsesc": "~0.5.0" + }, + "bin": { + "regjsparser": "bin/parser" + } + }, + "node_modules/regjsparser/node_modules/jsesc": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", + "integrity": "sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==", + "dev": true, + "peer": true, + "bin": { + "jsesc": "bin/jsesc" + } + }, "node_modules/request": { "version": "2.88.2", "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", @@ -4635,6 +6861,7 @@ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", "dev": true, + "peer": true, "engines": { "node": ">=4" } @@ -4652,55 +6879,17 @@ "node": ">=12" } }, - "node_modules/retry-request/node_modules/debug": { - "version": "4.3.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz", - "integrity": "sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==", - "dev": true, - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/retry-request/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, "node_modules/reusify": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", "dev": true, + "peer": true, "engines": { "iojs": ">=1.0.0", "node": ">=0.10.0" } }, - "node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "deprecated": "Rimraf versions prior to v4 are no longer supported", - "dev": true, - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -4720,10 +6909,30 @@ "url": "https://feross.org/support" } ], + "peer": true, "dependencies": { "queue-microtask": "^1.2.2" } }, + "node_modules/safe-array-concat": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.2.tgz", + "integrity": "sha512-vj6RsCsWBCf19jIeHEfkRMw8DPiBb+DMXklQ/1SGDHOMlHdPUkZXFQ2YdplS23zESTijAcurb1aSgJA3AgMu1Q==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.7", + "get-intrinsic": "^1.2.4", + "has-symbols": "^1.0.3", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">=0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -4743,6 +6952,24 @@ } ] }, + "node_modules/safe-regex-test": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.3.tgz", + "integrity": "sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "is-regex": "^1.1.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", @@ -4769,6 +6996,40 @@ "randombytes": "^2.1.0" } }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dev": true, + "peer": true, + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-function-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", + "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", + "dev": true, + "peer": true, + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -4790,6 +7051,25 @@ "node": ">=8" } }, + "node_modules/side-channel": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", + "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4", + "object-inspect": "^1.13.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/signal-exit": { "version": "3.0.7", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", @@ -4833,6 +7113,13 @@ "node": ">=0.3.1" } }, + "node_modules/slashes": { + "version": "3.0.12", + "resolved": "https://registry.npmjs.org/slashes/-/slashes-3.0.12.tgz", + "integrity": "sha512-Q9VME8WyGkc7pJf6QEkj3wE+2CnvZMI+XJhwdTPR8Z/kWQRXi7boAWLDibRPyHRTUTPx5FaU7MsyrjI3yLB4HA==", + "dev": true, + "peer": true + }, "node_modules/source-map-support": { "version": "0.5.21", "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", @@ -4852,6 +7139,53 @@ "node": ">=0.10.0" } }, + "node_modules/spdx-correct": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", + "integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==", + "dev": true, + "peer": true, + "dependencies": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-correct/node_modules/spdx-expression-parse": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", + "dev": true, + "peer": true, + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-exceptions": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz", + "integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==", + "dev": true, + "peer": true + }, + "node_modules/spdx-expression-parse": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-4.0.0.tgz", + "integrity": "sha512-Clya5JIij/7C6bRR22+tnGXbc4VKlibKSVj2iHvVeX5iMW7s1SIQlqu699JkODJJIhh/pUu8L0/VLh8xflD+LQ==", + "dev": true, + "peer": true, + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-license-ids": { + "version": "3.0.20", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.20.tgz", + "integrity": "sha512-jg25NiDV/1fLtSgEgyvVyDunvaNHbuwF9lfNV17gSmPFAlYzdfNBlLtLzXTevwkPj7DhGbmN9VnmJIgLnhvaBw==", + "dev": true, + "peer": true + }, "node_modules/sshpk": { "version": "1.16.1", "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", @@ -4914,6 +7248,96 @@ "node": ">=8" } }, + "node_modules/string.prototype.matchall": { + "version": "4.0.11", + "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.11.tgz", + "integrity": "sha512-NUdh0aDavY2og7IbBPenWqR9exH+E26Sv8e0/eTe1tltDGZL+GtBkDAnnyBtmekfK6/Dq3MkcGtzXFEd1LQrtg==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-symbols": "^1.0.3", + "internal-slot": "^1.0.7", + "regexp.prototype.flags": "^1.5.2", + "set-function-name": "^2.0.2", + "side-channel": "^1.0.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.repeat": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/string.prototype.repeat/-/string.prototype.repeat-1.0.0.tgz", + "integrity": "sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==", + "dev": true, + "peer": true, + "dependencies": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" + } + }, + "node_modules/string.prototype.trim": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.9.tgz", + "integrity": "sha512-klHuCNxiMZ8MlsOihJhJEBJAiMVqU3Z2nEXWfWnIqjN0gEFS9J9+IxKozWWtQGcgoa1WUZzLjKPTr4ZHNFTFxw==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.0", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.8.tgz", + "integrity": "sha512-p73uL5VCHCO2BZZ6krwwQE3kCzM7NKmis8S//xEC6fQonchbum4eP6kR4DLEjQFO3Wnj3Fuo8NM0kOSjVdHjZQ==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", + "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", @@ -4935,6 +7359,19 @@ "node": ">=6" } }, + "node_modules/strip-indent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", + "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", + "dev": true, + "peer": true, + "dependencies": { + "min-indent": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/strip-json-comments": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", @@ -4982,11 +7419,25 @@ "node": ">=8" } }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "peer": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/synckit": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.9.1.tgz", - "integrity": "sha512-7gr8p9TQP6RAHusBOSLs46F4564ZrjV8xFmw5zCmgmhGUcw2hxsShhJ6CEiHQMgPDwAQ1fWHPM0ypc4RMAig4A==", + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.9.2.tgz", + "integrity": "sha512-vrozgXDQwYO72vHjUb/HnFbQx1exDjoKzqx23aXEg2a9VIg2TSFZ8FmeZpTjUCFMYw7mpX4BE2SFu8wI7asYsw==", "dev": true, + "peer": true, "dependencies": { "@pkgr/core": "^0.1.0", "tslib": "^2.6.2" @@ -5027,12 +7478,6 @@ "uuid": "dist/bin/uuid" } }, - "node_modules/text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", - "dev": true - }, "node_modules/through": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", @@ -5096,11 +7541,25 @@ "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", "dev": true }, + "node_modules/ts-api-utils": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.4.2.tgz", + "integrity": "sha512-ZF5gQIQa/UmzfvxbHZI3JXN0/Jt+vnAfAviNRAMc491laiK6YCLpCW9ft8oaCRFOTxCZtUTE6XB0ZQAe3olntw==", + "dev": true, + "peer": true, + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "typescript": ">=4.2.0" + } + }, "node_modules/tslib": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", - "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==", - "dev": true + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "peer": true }, "node_modules/tunnel-agent": { "version": "0.6.0", @@ -5140,15 +7599,91 @@ } }, "node_modules/type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", "dev": true, + "peer": true, "engines": { - "node": ">=10" + "node": ">=8" + } + }, + "node_modules/typed-array-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.2.tgz", + "integrity": "sha512-gEymJYKZtKXzzBzM4jqa9w6Q1Jjm7x2d+sh19AdsD4wqnMPDYyvwpsIc2Q/835kHuo3BEQ7CjelGhfTsoBb2MQ==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/typed-array-byte-length": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.1.tgz", + "integrity": "sha512-3iMJ9q0ao7WE9tWcaYKIptkNBuOIcZCCT0d4MRvuuH88fEoEH62IuQe0OtraD3ebQEoTRk8XCBoknUNc1Y67pw==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-byte-offset": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.3.tgz", + "integrity": "sha512-GsvTyUHTriq6o/bHcTd0vM7OQ9JEdlvluu9YISaA7+KzDzPaIzEeDFNkTfhdE3MYcNhNi0vq/LlegYgIs5yPAw==", + "dev": true, + "peer": true, + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13", + "reflect.getprototypeof": "^1.0.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-length": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.7.tgz", + "integrity": "sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "is-typed-array": "^1.1.13", + "possible-typed-array-names": "^1.0.0", + "reflect.getprototypeof": "^1.0.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/typescript": { @@ -5164,6 +7699,33 @@ "node": ">=14.17" } }, + "node_modules/typescript-eslint": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.16.0.tgz", + "integrity": "sha512-wDkVmlY6O2do4V+lZd0GtRfbtXbeD0q9WygwXXSJnC1xorE8eqyC2L1tJimqpSeFrOzRlYtWnUp/uzgHQOgfBQ==", + "dev": true, + "peer": true, + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.16.0", + "@typescript-eslint/parser": "8.16.0", + "@typescript-eslint/utils": "8.16.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, "node_modules/uc.micro": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz", @@ -5182,6 +7744,22 @@ "node": ">=0.8.0" } }, + "node_modules/unbox-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", + "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-bigints": "^1.0.2", + "has-symbols": "^1.0.3", + "which-boxed-primitive": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/underscore": { "version": "1.13.7", "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.7.tgz", @@ -5202,6 +7780,44 @@ "node": ">= 10.0.0" } }, + "node_modules/update-browserslist-db": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.1.tgz", + "integrity": "sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "peer": true, + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.0" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/update-browserslist-db/node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "peer": true + }, "node_modules/uri-js": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", @@ -5225,6 +7841,28 @@ "uuid": "bin/uuid" } }, + "node_modules/validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "dev": true, + "peer": true, + "dependencies": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "node_modules/validate-npm-package-license/node_modules/spdx-expression-parse": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", + "dev": true, + "peer": true, + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, "node_modules/verror": { "version": "1.10.0", "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", @@ -5254,6 +7892,90 @@ "webidl-conversions": "^3.0.0" } }, + "node_modules/which-boxed-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", + "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", + "dev": true, + "peer": true, + "dependencies": { + "is-bigint": "^1.0.1", + "is-boolean-object": "^1.1.0", + "is-number-object": "^1.0.4", + "is-string": "^1.0.5", + "is-symbol": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-builtin-type": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.2.0.tgz", + "integrity": "sha512-I+qLGQ/vucCby4tf5HsLmGueEla4ZhwTBSqaooS+Y0BuxN4Cp+okmGuV+8mXZ84KDI9BA+oklo+RzKg0ONdSUA==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.7", + "function.prototype.name": "^1.1.6", + "has-tostringtag": "^1.0.2", + "is-async-function": "^2.0.0", + "is-date-object": "^1.0.5", + "is-finalizationregistry": "^1.1.0", + "is-generator-function": "^1.0.10", + "is-regex": "^1.1.4", + "is-weakref": "^1.0.2", + "isarray": "^2.0.5", + "which-boxed-primitive": "^1.0.2", + "which-collection": "^1.0.2", + "which-typed-array": "^1.1.15" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-collection": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz", + "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==", + "dev": true, + "peer": true, + "dependencies": { + "is-map": "^2.0.3", + "is-set": "^2.0.3", + "is-weakmap": "^2.0.2", + "is-weakset": "^2.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.16", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.16.tgz", + "integrity": "sha512-g+N+GAWiRj66DngFwHvISJd+ITsyphZvD1vChfVg6cEdnzy53GzB3oy0fUNlvhz7H7+MiqhYr26qxQShCpKTTQ==", + "dev": true, + "peer": true, + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/word-wrap": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", diff --git a/package.json b/package.json index 6c8f6fd..d878aeb 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "iobroker.squeezeboxrpc", - "version": "1.4.0", + "version": "1.5.0", "description": "ioBroker Logitech Squeezebox Adapter over JSON/RPC-Protocol", "author": { "name": "oweitman", @@ -35,6 +35,7 @@ "@alcalzone/release-script-plugin-license": "^3.7.0", "@alcalzone/release-script-plugin-manual-review": "^3.7.0", "@iobroker/adapter-dev": "^1.3.0", + "@iobroker/eslint-config": "^1.0.0", "@iobroker/testing": "^5.0.0", "@tsconfig/node18": "^18.2.4", "@types/chai": "^4.3.16", @@ -47,11 +48,7 @@ "axios": "^1.7.7", "chai": "^4.4.1", "chai-as-promised": "^8.0.0", - "eslint": "^8.57.0", - "eslint-config-prettier": "^9.1.0", - "eslint-plugin-prettier": "^5.2.1", "mocha": "^10.8.2", - "prettier": "^3.3.3", "proxyquire": "^2.1.3", "sinon": "^19.0.2", "sinon-chai": "^3.7.0", @@ -75,9 +72,11 @@ "test:integration": "mocha test/integration --exit", "test": "npm run test:js && npm run test:package", "check": "tsc --noEmit -p tsconfig.check.json", - "lint": "eslint .", + "lint": "eslint -c eslint.config.mjs .", + "build": "cd widgets/squeezeboxrpc && npm run build && cd ../..", "repochecker": "npx github:oweitman/ioBroker.repochecker https://github.com/oweitman/ioBroker.squeezeboxrpc --local", "translate": "translate-adapter", + "translate-widgets": "node ./bin/translate.js --source widgets/squeezeboxrpc/i18n/en.json --format single", "release": "release-script --all" }, "bugs": { diff --git a/prettier.config.mjs b/prettier.config.mjs new file mode 100644 index 0000000..7ef21d8 --- /dev/null +++ b/prettier.config.mjs @@ -0,0 +1,8 @@ +// iobroker prettier configuration file +import prettierConfig from '@iobroker/eslint-config/prettier.config.mjs'; + +export default { + ...prettierConfig, + // uncomment next line if you prefer double quotes + // singleQuote: false, +} \ No newline at end of file diff --git a/squeezeboxrpc.js b/squeezeboxrpc.js index cfbacfb..98028b9 100755 --- a/squeezeboxrpc.js +++ b/squeezeboxrpc.js @@ -1,4 +1,4 @@ -"use strict"; +'use strict'; /* * Created with @iobroker/create-adapter v2.6.3 @@ -6,24 +6,24 @@ // The adapter-core module gives you access to the core ioBroker functions // you need to create an adapter -const utils = require("@iobroker/adapter-core"); +const utils = require('@iobroker/adapter-core'); let squeezeboxServer; -const IoSbServerRequire = require(__dirname + "/lib/iosbserver"); +const IoSbServerRequire = require(`${__dirname}/lib/iosbserver`); class Squeezeboxrpc extends utils.Adapter { /** - * @param {Partial} [options={}] + * @param [options] - object with options */ constructor(options) { super({ ...options, - name: "squeezeboxrpc", + name: 'squeezeboxrpc', }); - this.on("ready", this.onReady.bind(this)); - this.on("stateChange", this.onStateChange.bind(this)); - this.on("unload", this.onUnload.bind(this)); - this.on("message", this.onMessage.bind(this)); + this.on('ready', this.onReady.bind(this)); + this.on('stateChange', this.onStateChange.bind(this)); + this.on('unload', this.onUnload.bind(this)); + this.on('message', this.onMessage.bind(this)); } /** @@ -33,44 +33,48 @@ class Squeezeboxrpc extends utils.Adapter { // Initialize your adapter here // Reset the connection indicator during startup - this.setState("info.connection", false, true); + this.setState('info.connection', false, true); // In order to get state updates, you need to subscribe to them. The following line adds a subscription for our variable we have created above. - this.subscribeStates("*"); + this.subscribeStates('*'); // Initialize your adapter here if (!squeezeboxServer) { - this.log.debug("main onReady open squeezeboxrpc"); + this.log.debug('main onReady open squeezeboxrpc'); squeezeboxServer = new IoSbServerRequire(this); - this.subscribeStates("*"); + this.subscribeStates('*'); } } /** * Is called when adapter shuts down - callback has to be called under any circumstances! - * @param {() => void} callback + * + * @param callback - call back function */ onUnload(callback) { try { squeezeboxServer.closeConnections(); callback(); - } catch (e) { + } catch { callback(); } } onMessage(obj) { - if (typeof obj === "object" && obj.message) { + if (typeof obj === 'object' && obj.message) { squeezeboxServer.processMessages(obj); } } /** * Is called if a subscribed state changes - * @param {string} id - * @param {ioBroker.State | null | undefined} state + * + * @param id - state id + * @param state - new state */ onStateChange(id, state) { if (state) { // The state was changed - if (squeezeboxServer) squeezeboxServer.stateChange(id, state); + if (squeezeboxServer) { + squeezeboxServer.stateChange(id, state); + } } } } @@ -78,9 +82,9 @@ class Squeezeboxrpc extends utils.Adapter { if (require.main !== module) { // Export the constructor in compact mode /** - * @param {Partial} [options={}] + * @param [options] - object with options */ - module.exports = (options) => new Squeezeboxrpc(options); + module.exports = options => new Squeezeboxrpc(options); } else { // otherwise start the instance directly new Squeezeboxrpc(); diff --git a/widgets/.npmignore b/widgets/.npmignore new file mode 100644 index 0000000..da1def8 --- /dev/null +++ b/widgets/.npmignore @@ -0,0 +1,2 @@ +eslint.config.mjs +prettier.config.mjs \ No newline at end of file diff --git a/widgets/package.json b/widgets/package.json new file mode 100644 index 0000000..02fabb6 --- /dev/null +++ b/widgets/package.json @@ -0,0 +1,13 @@ +{ + "name": "squeezeboxrpc-vis1-widget", + "version": "1.0.0", + "main": "squeezeboxrpc/js/squeezeboxrpc.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1", + "build": "esbuild squeezeboxrpc/js/bundle.js --bundle --outfile=squeezeboxrpc/js/squeezeboxrpc-dist.js --target=es6 --sourcemap", + "buildxx": "esbuild squeezeboxrpc/js/bundle.js --minify --bundle --outfile=squeezeboxrpc/js/squeezeboxrpc-dist.js --target=es6 --sourcemap" + }, + "author": "", + "license": "MIT", + "description": "" +} diff --git a/widgets/squeezeboxrpc.html b/widgets/squeezeboxrpc.html index 81beea0..43cf0e9 100644 --- a/widgets/squeezeboxrpc.html +++ b/widgets/squeezeboxrpc.html @@ -8,9 +8,9 @@ - - - + +