Compare commits

..
Author SHA1 Message Date
FoxxMD f61988597a fix: Initialize plays in correct scope 2026-04-02 12:33:48 +00:00
31 changed files with 8545 additions and 2170 deletions
+1 -1
View File
@@ -11,7 +11,7 @@ services:
# context: .
# dockerfile: .devcontainer/Dockerfile
image: mcr.microsoft.com/devcontainers/javascript-node:24-bookworm
image: mcr.microsoft.com/devcontainers/javascript-node:1-20-bookworm
volumes:
# Update this to wherever you want VS Code to mount the folder of your project
+1 -1
View File
@@ -20,7 +20,7 @@
"forwardPorts": [9078,3000],
// Use 'postCreateCommand' to run commands after the container is created.
"postCreateCommand": ".devcontainer/postCreate.sh",
"postCreateCommand": "npm install",
// Configure tool-specific properties.
"customizations": {
-12
View File
@@ -1,12 +0,0 @@
#!/usr/bin/env bash
echo 'Adding node_modules to path'
echo 'export PATH=/workspaces/node_modules/.bin:/workspaces/docsite/node_modules/.bin:$PATH' >> /home/node/.bashrc
echo 'Updating npm...'
npm install -g npm@11.12.1
echo 'Installing concurrently...'
npm install -g concurrently
echo 'If you have freshly cloned this project or node_modules folder does not exist then you should run "npm run install:parallel" now'
+1 -3
View File
@@ -30,6 +30,4 @@ tmp-*
.env
*.env
.storybook
.stories
*.db
*.db-*
.stories
-19
View File
@@ -1,19 +0,0 @@
# To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for all configuration options:
# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file
version: 2
updates:
- package-ecosystem: "npm" # See documentation for possible values
directories:
- "/"
- "/docsite"
schedule:
interval: "weekly"
- package-ecosystem: "docker"
# Look for a `Dockerfile` in the `root` directory
directory: "/"
# Check for updates once a week
schedule:
interval: "weekly"
-3
View File
@@ -151,6 +151,3 @@ tmp-*
*storybook.log
storybook-static
*.db
*.db-*
+1 -1
View File
@@ -1 +1 @@
v24.14.0
v18.19.1
+24
View File
@@ -0,0 +1,24 @@
{
"extends": "@istanbuljs/nyc-config-typescript",
"exclude": [
"node_modules/",
"**/src/backend/common/schema/**",
"**/src/backend/tests/**",
"register.js",
"**/src/server/**/*.d.ts",
"**/src/client/**"
],
"include": [
"**/src/backend/**/*.ts",
"**/src/**/backend/*.js",
"**/src/**/backend/*.js.map"
],
"extension": [
".ts"
],
"reporter": [
"text-summary",
"html"
],
"report-dir": "./coverage"
}
+2 -2
View File
@@ -9,10 +9,10 @@ export default defineMain({
],
"addons": [
"@chromatic-com/storybook",
// "@storybook/addon-vitest",
"@storybook/addon-vitest",
"@storybook/addon-a11y",
"@storybook/addon-docs",
// "@storybook/addon-onboarding"
"@storybook/addon-onboarding"
],
features: {
// https://github.com/storybookjs/storybook/discussions/33279
+1 -3
View File
@@ -1,7 +1,7 @@
FROM ghcr.io/linuxserver/baseimage-debian:bookworm AS base
ENV TZ=Etc/GMT
ENV NODE_VERSION=24.14.0
ENV NODE_VERSION=20.18.1
# borrowing openssl header removal trick from offical docker-node
# https://github.com/nodejs/docker-node/blob/main/18/bookworm-slim/Dockerfile#L8
@@ -49,8 +49,6 @@ RUN \
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
RUN echo "Node: $(node -v)\nNPM: $(npm -v)"
RUN npm install -g concurrently
ARG data_dir=/config
+247 -264
View File
@@ -40,7 +40,7 @@
"typescript": "~5.6.2"
},
"engines": {
"node": ">=20.18.1"
"node": ">=18.0"
}
},
"node_modules/@algolia/autocomplete-core": {
@@ -6191,6 +6191,15 @@
"node": ">=14.16"
}
},
"node_modules/@trysound/sax": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz",
"integrity": "sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==",
"license": "ISC",
"engines": {
"node": ">=10.13.0"
}
},
"node_modules/@tybys/wasm-util": {
"version": "0.10.0",
"resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.0.tgz",
@@ -6274,10 +6283,9 @@
}
},
"node_modules/@types/estree": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
"integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
"license": "MIT"
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz",
"integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw=="
},
"node_modules/@types/estree-jsx": {
"version": "1.0.5",
@@ -6785,10 +6793,9 @@
}
},
"node_modules/acorn": {
"version": "8.16.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz",
"integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==",
"license": "MIT",
"version": "8.14.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz",
"integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==",
"bin": {
"acorn": "bin/acorn"
},
@@ -6796,18 +6803,6 @@
"node": ">=0.4.0"
}
},
"node_modules/acorn-import-phases": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/acorn-import-phases/-/acorn-import-phases-1.0.4.tgz",
"integrity": "sha512-wKmbr/DDiIXzEOiWrTTUcDm24kQ2vGfZQvM2fwg2vXqR5uW6aapr7ObPtj1th32b9u90/Pf4AItvdTh42fBmVQ==",
"license": "MIT",
"engines": {
"node": ">=10.13.0"
},
"peerDependencies": {
"acorn": "^8.14.0"
}
},
"node_modules/acorn-jsx": {
"version": "5.3.2",
"resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
@@ -7210,18 +7205,6 @@
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
},
"node_modules/baseline-browser-mapping": {
"version": "2.10.13",
"resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.13.tgz",
"integrity": "sha512-BL2sTuHOdy0YT1lYieUxTw/QMtPBC3pmlJC6xk8BBYVv6vcw3SGdKemQ+Xsx9ik2F/lYDO9tqsFQH1r9PFuHKw==",
"license": "Apache-2.0",
"bin": {
"baseline-browser-mapping": "dist/cli.cjs"
},
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/batch": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz",
@@ -7249,23 +7232,23 @@
}
},
"node_modules/body-parser": {
"version": "1.20.4",
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.4.tgz",
"integrity": "sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA==",
"version": "1.20.3",
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz",
"integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==",
"license": "MIT",
"dependencies": {
"bytes": "~3.1.2",
"bytes": "3.1.2",
"content-type": "~1.0.5",
"debug": "2.6.9",
"depd": "2.0.0",
"destroy": "~1.2.0",
"http-errors": "~2.0.1",
"iconv-lite": "~0.4.24",
"on-finished": "~2.4.1",
"qs": "~6.14.0",
"raw-body": "~2.5.3",
"destroy": "1.2.0",
"http-errors": "2.0.0",
"iconv-lite": "0.4.24",
"on-finished": "2.4.1",
"qs": "6.13.0",
"raw-body": "2.5.2",
"type-is": "~1.6.18",
"unpipe": "~1.0.0"
"unpipe": "1.0.0"
},
"engines": {
"node": ">= 0.8",
@@ -7334,10 +7317,9 @@
}
},
"node_modules/brace-expansion": {
"version": "1.1.13",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.13.tgz",
"integrity": "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w==",
"license": "MIT",
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
"dependencies": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
@@ -7355,9 +7337,9 @@
}
},
"node_modules/browserslist": {
"version": "4.28.2",
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz",
"integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==",
"version": "4.25.1",
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.1.tgz",
"integrity": "sha512-KGj0KoOMXLpSNkkEI6Z6mShmQy0bc1I+T7K9N81k4WWMrfz+6fQ6es80B/YLAeRoKvjYE1YSHHOW1qe9xIVzHw==",
"funding": [
{
"type": "opencollective",
@@ -7374,11 +7356,10 @@
],
"license": "MIT",
"dependencies": {
"baseline-browser-mapping": "^2.10.12",
"caniuse-lite": "^1.0.30001782",
"electron-to-chromium": "^1.5.328",
"node-releases": "^2.0.36",
"update-browserslist-db": "^1.2.3"
"caniuse-lite": "^1.0.30001726",
"electron-to-chromium": "^1.5.173",
"node-releases": "^2.0.19",
"update-browserslist-db": "^1.1.3"
},
"bin": {
"browserslist": "cli.js"
@@ -7519,9 +7500,9 @@
}
},
"node_modules/caniuse-lite": {
"version": "1.0.30001782",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001782.tgz",
"integrity": "sha512-dZcaJLJeDMh4rELYFw1tvSn1bhZWYFOt468FcbHHxx/Z/dFidd1I6ciyFdi3iwfQCyOjqo9upF6lGQYtMiJWxw==",
"version": "1.0.30001727",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001727.tgz",
"integrity": "sha512-pB68nIHmbN6L/4C6MH1DokyR3bYqFwjaSs/sWDHGj4CTcFtQUQMuJftVwWkXq7mNWOybD3KhUv3oWHoGxgP14Q==",
"funding": [
{
"type": "opencollective",
@@ -7943,16 +7924,16 @@
}
},
"node_modules/compression": {
"version": "1.8.1",
"resolved": "https://registry.npmjs.org/compression/-/compression-1.8.1.tgz",
"integrity": "sha512-9mAqGPHLakhCLeNyxPkK4xVo746zQ/czLH1Ky+vkitMnWfWZps8r0qXuwhwizagCRttsL4lfG4pIOvaWLpAP0w==",
"version": "1.8.0",
"resolved": "https://registry.npmjs.org/compression/-/compression-1.8.0.tgz",
"integrity": "sha512-k6WLKfunuqCYD3t6AsuPGvQWaKwuLLh2/xHNcX4qE+vIfDNXpSqnrhwA7O53R7WVQUnt8dVAIW+YHr7xTgOgGA==",
"license": "MIT",
"dependencies": {
"bytes": "3.1.2",
"compressible": "~2.0.18",
"debug": "2.6.9",
"negotiator": "~0.6.4",
"on-headers": "~1.1.0",
"on-headers": "~1.0.2",
"safe-buffer": "5.2.1",
"vary": "~1.1.2"
},
@@ -8088,9 +8069,9 @@
"license": "MIT"
},
"node_modules/cookie": {
"version": "0.7.2",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
"integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
"version": "0.7.1",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz",
"integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
@@ -9110,9 +9091,9 @@
"license": "MIT"
},
"node_modules/electron-to-chromium": {
"version": "1.5.329",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.329.tgz",
"integrity": "sha512-/4t+AS1l4S3ZC0Ja7PHFIWeBIxGA3QGqV8/yKsP36v7NcyUCl+bIcmw6s5zVuMIECWwBrAK/6QLzTmbJChBboQ==",
"version": "1.5.182",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.182.tgz",
"integrity": "sha512-Lv65Btwv9W4J9pyODI6EWpdnhfvrve/us5h1WspW8B2Fb0366REPtY3hX7ounk1CkV/TBjWCEvCBBbYbmV0qCA==",
"license": "ISC"
},
"node_modules/emoji-regex": {
@@ -9178,13 +9159,12 @@
}
},
"node_modules/enhanced-resolve": {
"version": "5.20.1",
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.20.1.tgz",
"integrity": "sha512-Qohcme7V1inbAfvjItgw0EaxVX5q2rdVEZHRBrEQdRZTssLDGsL8Lwrznl8oQ/6kuTJONLaDcGjkNP247XEhcA==",
"license": "MIT",
"version": "5.18.0",
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.0.tgz",
"integrity": "sha512-0/r0MySGYG8YqlayBZ6MuCfECmHFdJ5qyPh8s8wa5Hnm6SaFLSK1VYCbj+NKp090Nm1caZhD+QTnmxO7esYGyQ==",
"dependencies": {
"graceful-fs": "^4.2.4",
"tapable": "^2.3.0"
"tapable": "^2.2.0"
},
"engines": {
"node": ">=10.13.0"
@@ -9229,10 +9209,9 @@
}
},
"node_modules/es-module-lexer": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.0.0.tgz",
"integrity": "sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw==",
"license": "MIT"
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.6.0.tgz",
"integrity": "sha512-qqnD1yMU6tk/jnaMosogGySTZP8YtUgAffA9nMN+E/rjxcfRQ6IEk7IiozUjgxKoFHBGjTLnrHB/YC45r/59EQ=="
},
"node_modules/es-object-atoms": {
"version": "1.1.1",
@@ -9554,39 +9533,39 @@
}
},
"node_modules/express": {
"version": "4.22.1",
"resolved": "https://registry.npmjs.org/express/-/express-4.22.1.tgz",
"integrity": "sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==",
"version": "4.21.2",
"resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz",
"integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==",
"license": "MIT",
"dependencies": {
"accepts": "~1.3.8",
"array-flatten": "1.1.1",
"body-parser": "~1.20.3",
"content-disposition": "~0.5.4",
"body-parser": "1.20.3",
"content-disposition": "0.5.4",
"content-type": "~1.0.4",
"cookie": "~0.7.1",
"cookie-signature": "~1.0.6",
"cookie": "0.7.1",
"cookie-signature": "1.0.6",
"debug": "2.6.9",
"depd": "2.0.0",
"encodeurl": "~2.0.0",
"escape-html": "~1.0.3",
"etag": "~1.8.1",
"finalhandler": "~1.3.1",
"fresh": "~0.5.2",
"http-errors": "~2.0.0",
"finalhandler": "1.3.1",
"fresh": "0.5.2",
"http-errors": "2.0.0",
"merge-descriptors": "1.0.3",
"methods": "~1.1.2",
"on-finished": "~2.4.1",
"on-finished": "2.4.1",
"parseurl": "~1.3.3",
"path-to-regexp": "~0.1.12",
"path-to-regexp": "0.1.12",
"proxy-addr": "~2.0.7",
"qs": "~6.14.0",
"qs": "6.13.0",
"range-parser": "~1.2.1",
"safe-buffer": "5.2.1",
"send": "~0.19.0",
"serve-static": "~1.16.2",
"send": "0.19.0",
"serve-static": "1.16.2",
"setprototypeof": "1.2.0",
"statuses": "~2.0.1",
"statuses": "2.0.1",
"type-is": "~1.6.18",
"utils-merge": "1.0.1",
"vary": "~1.1.2"
@@ -9627,9 +9606,9 @@
"license": "MIT"
},
"node_modules/express/node_modules/path-to-regexp": {
"version": "0.1.13",
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.13.tgz",
"integrity": "sha512-A/AGNMFN3c8bOlvV9RreMdrv7jsmF9XIfDeCd87+I8RNg6s78BhJxMu69NEMHBSJFxKidViTEdruRwEk/WIKqA==",
"version": "0.1.12",
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz",
"integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==",
"license": "MIT"
},
"node_modules/express/node_modules/range-parser": {
@@ -10143,8 +10122,7 @@
"node_modules/glob-to-regexp": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz",
"integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==",
"license": "BSD-2-Clause"
"integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw=="
},
"node_modules/global-dirs": {
"version": "3.0.1",
@@ -10257,10 +10235,9 @@
}
},
"node_modules/gray-matter/node_modules/js-yaml": {
"version": "3.14.2",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz",
"integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==",
"license": "MIT",
"version": "3.14.1",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz",
"integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==",
"dependencies": {
"argparse": "^1.0.7",
"esprima": "^4.0.0"
@@ -10781,30 +10758,17 @@
"license": "MIT"
},
"node_modules/http-errors": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz",
"integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==",
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz",
"integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==",
"license": "MIT",
"dependencies": {
"depd": "~2.0.0",
"inherits": "~2.0.4",
"setprototypeof": "~1.2.0",
"statuses": "~2.0.2",
"toidentifier": "~1.0.1"
"depd": "2.0.0",
"inherits": "2.0.4",
"setprototypeof": "1.2.0",
"statuses": "2.0.1",
"toidentifier": "1.0.1"
},
"engines": {
"node": ">= 0.8"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/http-errors/node_modules/statuses": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz",
"integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
@@ -10937,9 +10901,9 @@
"integrity": "sha512-HR7EVodfFUdQCTIeySw+WDRFJlPcLOJbXfwwZ7Oom6tjsvZ3bOkCDJHehQC3nxJrv7+f9XecwazynjU8e4Vw3Q=="
},
"node_modules/immutable": {
"version": "5.1.5",
"resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.5.tgz",
"integrity": "sha512-t7xcm2siw+hlUM68I+UEOK+z84RzmN59as9DZ7P1l0994DKUWV7UXBMQZVxaoMSRQ+PBZbHCOoBt7a2wxOMt+A==",
"version": "5.1.4",
"resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.4.tgz",
"integrity": "sha512-p6u1bG3YSnINT5RQmx/yRZBpenIl30kVxkTLDyHLIMk0gict704Q9n+thfDI7lTRm9vXdDYutVzXhzcThxTnXA==",
"license": "MIT"
},
"node_modules/immutable-json-patch": {
@@ -11418,10 +11382,9 @@
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
},
"node_modules/js-yaml": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
"integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
"license": "MIT",
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
"integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
"dependencies": {
"argparse": "^2.0.1"
},
@@ -11883,16 +11846,11 @@
"license": "MIT"
},
"node_modules/loader-runner": {
"version": "4.3.1",
"resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.1.tgz",
"integrity": "sha512-IWqP2SCPhyVFTBtRcgMHdzlf9ul25NwaFx4wCEH/KjAXuuHY4yNjvPXsBokp8jCB936PyWRaPKUNh8NvylLp2Q==",
"license": "MIT",
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz",
"integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==",
"engines": {
"node": ">=6.11.5"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/webpack"
}
},
"node_modules/loader-utils": {
@@ -11930,10 +11888,9 @@
}
},
"node_modules/lodash": {
"version": "4.18.0",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.0.tgz",
"integrity": "sha512-l1mfj2atMqndAHI3ls7XqPxEjV2J9ZkcNyHpoZA3r2T1LLwDB69jgkMWh71YKwhBbK0G2f4WSn05ahmQXVxupA==",
"license": "MIT"
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
},
"node_modules/lodash-es": {
"version": "4.17.23",
@@ -12413,10 +12370,9 @@
}
},
"node_modules/mdast-util-to-hast": {
"version": "13.2.1",
"resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.1.tgz",
"integrity": "sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==",
"license": "MIT",
"version": "13.2.0",
"resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.0.tgz",
"integrity": "sha512-QGYKEuUsYT9ykKBCMOEDLsU5JRObWQusAolFMeko/tYPufNkRffBAQjIE+99jbA87xv6FgmjLtwjh9wBWajwAA==",
"dependencies": {
"@types/hast": "^3.0.0",
"@types/mdast": "^4.0.0",
@@ -14341,10 +14297,9 @@
"license": "ISC"
},
"node_modules/minimatch": {
"version": "3.1.5",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz",
"integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==",
"license": "ISC",
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
"dependencies": {
"brace-expansion": "^1.1.7"
},
@@ -14471,19 +14426,18 @@
}
},
"node_modules/node-forge": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.4.0.tgz",
"integrity": "sha512-LarFH0+6VfriEhqMMcLX2F7SwSXeWwnEAJEsYm5QKWchiVYVvJyV9v7UDvUv+w5HO23ZpQTXDv/GxdDdMyOuoQ==",
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz",
"integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==",
"license": "(BSD-3-Clause OR GPL-2.0)",
"engines": {
"node": ">= 6.13.0"
}
},
"node_modules/node-releases": {
"version": "2.0.36",
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.36.tgz",
"integrity": "sha512-TdC8FSgHz8Mwtw9g5L4gR/Sh9XhSP/0DEkQxfEFXOpiul5IiHgHan2VhYYb6agDSfp4KuvltmGApc8HMgUrIkA==",
"license": "MIT"
"version": "2.0.19",
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz",
"integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw=="
},
"node_modules/normalize-path": {
"version": "3.0.0",
@@ -14680,9 +14634,9 @@
}
},
"node_modules/on-headers": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.1.0.tgz",
"integrity": "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==",
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz",
"integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
@@ -15111,10 +15065,9 @@
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="
},
"node_modules/picomatch": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz",
"integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==",
"license": "MIT",
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
"engines": {
"node": ">=8.6"
},
@@ -16721,12 +16674,12 @@
}
},
"node_modules/qs": {
"version": "6.14.2",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.14.2.tgz",
"integrity": "sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q==",
"version": "6.13.0",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz",
"integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==",
"license": "BSD-3-Clause",
"dependencies": {
"side-channel": "^1.1.0"
"side-channel": "^1.0.6"
},
"engines": {
"node": ">=0.6"
@@ -16784,15 +16737,15 @@
}
},
"node_modules/raw-body": {
"version": "2.5.3",
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz",
"integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==",
"version": "2.5.2",
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz",
"integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==",
"license": "MIT",
"dependencies": {
"bytes": "~3.1.2",
"http-errors": "~2.0.1",
"iconv-lite": "~0.4.24",
"unpipe": "~1.0.0"
"bytes": "3.1.2",
"http-errors": "2.0.0",
"iconv-lite": "0.4.24",
"unpipe": "1.0.0"
},
"engines": {
"node": ">= 0.8"
@@ -17791,13 +17744,10 @@
}
},
"node_modules/sax": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/sax/-/sax-1.6.0.tgz",
"integrity": "sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA==",
"license": "BlueOak-1.0.0",
"engines": {
"node": ">=11.0.0"
}
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz",
"integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==",
"license": "ISC"
},
"node_modules/scheduler": {
"version": "0.26.0",
@@ -17812,10 +17762,9 @@
"license": "Apache-2.0"
},
"node_modules/schema-utils": {
"version": "4.3.3",
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.3.tgz",
"integrity": "sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==",
"license": "MIT",
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.0.tgz",
"integrity": "sha512-Gf9qqc58SpCA/xdziiHz35F4GNIWYWZrEshUc/G/r5BnLph6xpKuLeoJoQuj5WfBIx/eQLf+hmVPYHaxJu7V2g==",
"dependencies": {
"@types/json-schema": "^7.0.9",
"ajv": "^8.9.0",
@@ -17913,24 +17862,24 @@
}
},
"node_modules/send": {
"version": "0.19.2",
"resolved": "https://registry.npmjs.org/send/-/send-0.19.2.tgz",
"integrity": "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==",
"version": "0.19.0",
"resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz",
"integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==",
"license": "MIT",
"dependencies": {
"debug": "2.6.9",
"depd": "2.0.0",
"destroy": "1.2.0",
"encodeurl": "~2.0.0",
"encodeurl": "~1.0.2",
"escape-html": "~1.0.3",
"etag": "~1.8.1",
"fresh": "~0.5.2",
"http-errors": "~2.0.1",
"fresh": "0.5.2",
"http-errors": "2.0.0",
"mime": "1.6.0",
"ms": "2.1.3",
"on-finished": "~2.4.1",
"on-finished": "2.4.1",
"range-parser": "~1.2.1",
"statuses": "~2.0.2"
"statuses": "2.0.1"
},
"engines": {
"node": ">= 0.8.0"
@@ -17951,6 +17900,15 @@
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
"license": "MIT"
},
"node_modules/send/node_modules/encodeurl": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
"integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/send/node_modules/range-parser": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
@@ -17960,15 +17918,6 @@
"node": ">= 0.6"
}
},
"node_modules/send/node_modules/statuses": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz",
"integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/serialize-javascript": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz",
@@ -17978,15 +17927,15 @@
}
},
"node_modules/serve-handler": {
"version": "6.1.7",
"resolved": "https://registry.npmjs.org/serve-handler/-/serve-handler-6.1.7.tgz",
"integrity": "sha512-CinAq1xWb0vR3twAv9evEU8cNWkXCb9kd5ePAHUKJBkOsUpR1wt/CvGdeca7vqumL1U5cSaeVQ6zZMxiJ3yWsg==",
"version": "6.1.6",
"resolved": "https://registry.npmjs.org/serve-handler/-/serve-handler-6.1.6.tgz",
"integrity": "sha512-x5RL9Y2p5+Sh3D38Fh9i/iQ5ZK+e4xuXRd/pGbM4D13tgo/MGwbttUk8emytcr1YYzBYs+apnUngBDFYfpjPuQ==",
"license": "MIT",
"dependencies": {
"bytes": "3.0.0",
"content-disposition": "0.5.2",
"mime-types": "2.1.18",
"minimatch": "3.1.5",
"minimatch": "3.1.2",
"path-is-inside": "1.0.2",
"path-to-regexp": "3.3.0",
"range-parser": "1.2.0"
@@ -18077,15 +18026,15 @@
}
},
"node_modules/serve-static": {
"version": "1.16.3",
"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.3.tgz",
"integrity": "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==",
"version": "1.16.2",
"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz",
"integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==",
"license": "MIT",
"dependencies": {
"encodeurl": "~2.0.0",
"escape-html": "~1.0.3",
"parseurl": "~1.3.3",
"send": "~0.19.1"
"send": "0.19.0"
},
"engines": {
"node": ">= 0.8.0"
@@ -18647,18 +18596,18 @@
"license": "MIT"
},
"node_modules/svgo": {
"version": "3.3.3",
"resolved": "https://registry.npmjs.org/svgo/-/svgo-3.3.3.tgz",
"integrity": "sha512-+wn7I4p7YgJhHs38k2TNjy1vCfPIfLIJWR5MnCStsN8WuuTcBnRKcMHQLMM2ijxGZmDoZwNv8ipl5aTTen62ng==",
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/svgo/-/svgo-3.3.2.tgz",
"integrity": "sha512-OoohrmuUlBs8B8o6MB2Aevn+pRIH9zDALSR+6hhqVfa6fRwG/Qw9VUMSMW9VNg2CFc/MTIfabtdOVl9ODIJjpw==",
"license": "MIT",
"dependencies": {
"@trysound/sax": "0.2.0",
"commander": "^7.2.0",
"css-select": "^5.1.0",
"css-tree": "^2.3.1",
"css-what": "^6.1.0",
"csso": "^5.0.5",
"picocolors": "^1.0.0",
"sax": "^1.5.0"
"picocolors": "^1.0.0"
},
"bin": {
"svgo": "bin/svgo"
@@ -18700,16 +18649,11 @@
"license": "MIT"
},
"node_modules/tapable": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.2.tgz",
"integrity": "sha512-1MOpMXuhGzGL5TTCZFItxCc0AARf1EZFQkGqMm7ERKj8+Hgr5oLvJOVFcC+lRmR8hCe2S3jC4T5D7Vg/d7/fhA==",
"license": "MIT",
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz",
"integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==",
"engines": {
"node": ">=6"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/webpack"
}
},
"node_modules/terser": {
@@ -18730,14 +18674,14 @@
}
},
"node_modules/terser-webpack-plugin": {
"version": "5.4.0",
"resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.4.0.tgz",
"integrity": "sha512-Bn5vxm48flOIfkdl5CaD2+1CiUVbonWQ3KQPyP7/EuIl9Gbzq/gQFOzaMFUEgVjB1396tcK0SG8XcNJ/2kDH8g==",
"license": "MIT",
"version": "5.3.11",
"resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.11.tgz",
"integrity": "sha512-RVCsMfuD0+cTt3EwX8hSl2Ks56EbFHWmhluwcqoPKtBnfjiT6olaq7PRIRfhyU8nnC2MrnDrBLfrD/RGE+cVXQ==",
"dependencies": {
"@jridgewell/trace-mapping": "^0.3.25",
"jest-worker": "^27.4.5",
"schema-utils": "^4.3.0",
"serialize-javascript": "^6.0.2",
"terser": "^5.31.1"
},
"engines": {
@@ -18949,10 +18893,9 @@
}
},
"node_modules/undici": {
"version": "6.24.1",
"resolved": "https://registry.npmjs.org/undici/-/undici-6.24.1.tgz",
"integrity": "sha512-sC+b0tB1whOCzbtlx20fx3WgCXwkW627p4EA9uM+/tNNPkSS+eSEld6pAs9nDv7WbY1UUljBMYPtu9BCOrCWKA==",
"license": "MIT",
"version": "6.21.1",
"resolved": "https://registry.npmjs.org/undici/-/undici-6.21.1.tgz",
"integrity": "sha512-q/1rj5D0/zayJB2FraXdaWxbhWiNKDvu8naDT2dl1yTlvJp4BLtOcp2a5BvgGNQpYYJzau7tf1WgKv3b+7mqpQ==",
"engines": {
"node": ">=18.17"
}
@@ -19137,9 +19080,9 @@
}
},
"node_modules/update-browserslist-db": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz",
"integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==",
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz",
"integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==",
"funding": [
{
"type": "opencollective",
@@ -19522,10 +19465,9 @@
"license": "MIT"
},
"node_modules/watchpack": {
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.5.1.tgz",
"integrity": "sha512-Zn5uXdcFNIA1+1Ei5McRd+iRzfhENPCe7LeABkJtNulSxjma+l7ltNx55BWZkRlwRnpOgHqxnjyaDgJnNXnqzg==",
"license": "MIT",
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.2.tgz",
"integrity": "sha512-TnbFSbcOCcDgjZ4piURLCbJ3nJhznVh9kw6F6iokjiFPl8ONxe9A6nMDVXDiNbrSfLILs6vB07F7wLBrwPYzJw==",
"dependencies": {
"glob-to-regexp": "^0.4.1",
"graceful-fs": "^4.1.2"
@@ -19554,36 +19496,33 @@
}
},
"node_modules/webpack": {
"version": "5.105.4",
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.105.4.tgz",
"integrity": "sha512-jTywjboN9aHxFlToqb0K0Zs9SbBoW4zRUlGzI2tYNxVYcEi/IPpn+Xi4ye5jTLvX2YeLuic/IvxNot+Q1jMoOw==",
"license": "MIT",
"version": "5.97.1",
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.97.1.tgz",
"integrity": "sha512-EksG6gFY3L1eFMROS/7Wzgrii5mBAFe4rIr3r2BTfo7bcc+DWwFZ4OJ/miOuHJO/A85HwyI4eQ0F6IKXesO7Fg==",
"dependencies": {
"@types/eslint-scope": "^3.7.7",
"@types/estree": "^1.0.8",
"@types/json-schema": "^7.0.15",
"@types/estree": "^1.0.6",
"@webassemblyjs/ast": "^1.14.1",
"@webassemblyjs/wasm-edit": "^1.14.1",
"@webassemblyjs/wasm-parser": "^1.14.1",
"acorn": "^8.16.0",
"acorn-import-phases": "^1.0.3",
"browserslist": "^4.28.1",
"acorn": "^8.14.0",
"browserslist": "^4.24.0",
"chrome-trace-event": "^1.0.2",
"enhanced-resolve": "^5.20.0",
"es-module-lexer": "^2.0.0",
"enhanced-resolve": "^5.17.1",
"es-module-lexer": "^1.2.1",
"eslint-scope": "5.1.1",
"events": "^3.2.0",
"glob-to-regexp": "^0.4.1",
"graceful-fs": "^4.2.11",
"json-parse-even-better-errors": "^2.3.1",
"loader-runner": "^4.3.1",
"loader-runner": "^4.2.0",
"mime-types": "^2.1.27",
"neo-async": "^2.6.2",
"schema-utils": "^4.3.3",
"tapable": "^2.3.0",
"terser-webpack-plugin": "^5.3.17",
"watchpack": "^2.5.1",
"webpack-sources": "^3.3.4"
"schema-utils": "^3.2.0",
"tapable": "^2.1.1",
"terser-webpack-plugin": "^5.3.10",
"watchpack": "^2.4.1",
"webpack-sources": "^3.2.3"
},
"bin": {
"webpack": "bin/webpack.js"
@@ -19784,14 +19723,41 @@
}
},
"node_modules/webpack-sources": {
"version": "3.3.4",
"resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.3.4.tgz",
"integrity": "sha512-7tP1PdV4vF+lYPnkMR0jMY5/la2ub5Fc/8VQrrU+lXkiM6C4TjVfGw7iKfyhnTQOsD+6Q/iKw0eFciziRgD58Q==",
"license": "MIT",
"version": "3.2.3",
"resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz",
"integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==",
"engines": {
"node": ">=10.13.0"
}
},
"node_modules/webpack/node_modules/ajv": {
"version": "6.12.6",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
"dependencies": {
"fast-deep-equal": "^3.1.1",
"fast-json-stable-stringify": "^2.0.0",
"json-schema-traverse": "^0.4.1",
"uri-js": "^4.2.2"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/epoberezkin"
}
},
"node_modules/webpack/node_modules/ajv-keywords": {
"version": "3.5.2",
"resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz",
"integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==",
"peerDependencies": {
"ajv": "^6.9.1"
}
},
"node_modules/webpack/node_modules/json-schema-traverse": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="
},
"node_modules/webpack/node_modules/mime-db": {
"version": "1.52.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
@@ -19811,6 +19777,23 @@
"node": ">= 0.6"
}
},
"node_modules/webpack/node_modules/schema-utils": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz",
"integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==",
"dependencies": {
"@types/json-schema": "^7.0.8",
"ajv": "^6.12.5",
"ajv-keywords": "^3.5.2"
},
"engines": {
"node": ">= 10.13.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/webpack"
}
},
"node_modules/webpackbar": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/webpackbar/-/webpackbar-6.0.1.tgz",
@@ -20103,9 +20086,9 @@
"license": "ISC"
},
"node_modules/yaml": {
"version": "2.8.3",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.3.tgz",
"integrity": "sha512-AvbaCLOO2Otw/lW5bmh9d/WEdcDFdQp2Z2ZUH3pX9U2ihyUY0nvLv7J6TrWowklRGPYbB/IuIMfYgxaCPg5Bpg==",
"version": "2.8.2",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.2.tgz",
"integrity": "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==",
"license": "ISC",
"bin": {
"yaml": "bin.mjs"
+1 -2
View File
@@ -58,7 +58,6 @@
]
},
"engines": {
"node": ">=24.14.0",
"npm": ">=11.12.1"
"node": ">=18.0"
}
}
+8132 -1572
View File
File diff suppressed because it is too large Load Diff
+25 -18
View File
@@ -33,8 +33,8 @@
}
},
"engines": {
"node": ">=24.14.0",
"npm": ">=11.12.1"
"node": ">=18.19.1",
"npm": ">=9.1.0"
},
"repository": {
"type": "git",
@@ -79,12 +79,12 @@
"@types/express-serve-static-core": "^4.19.6",
"@xhayper/discord-rpc": "^1.3.0",
"address": "^1.2.2",
"ajv": "^8.18.0",
"ajv": "^8.12.0",
"ajv-formats": "^3.0.1",
"autoprefixer": "^10.4.17",
"avahi-browse": "^1.1.4",
"better-sse": "^0.8.0",
"body-parser": "^2.2.2",
"body-parser": "^1.19.0",
"cacheable": "^1.10.4",
"castv2": "^0.1.10",
"clone": "^2.1.2",
@@ -95,15 +95,15 @@
"cors": "^2.8.5",
"dayjs": "^1.10.4",
"dbus-ts": "^0.0.7",
"discord.js": "^14.26.0",
"discord.js": "^14.25.1",
"dotenv": "^10.0.0",
"express": "^4.22.1",
"express-session": "^1.19.0",
"express": "^4.17.1",
"express-session": "^1.17.2",
"fast-deep-equal": "^3.1.3",
"fixed-size-list": "^0.3.0",
"flat-cache": "^6.1.13",
"formidable": "^3.5",
"glob": "^11.1.0",
"glob": "^11.0.0",
"google-auth-library": "^9.15.0",
"gotify": "^1.1.0",
"http-cookie-agent": "^7.0.1",
@@ -116,7 +116,6 @@
"jsondiffpatch-react": "^1.0.8",
"keyv": "^5.5.0",
"kodi-api": "^0.2.1",
"kysely-node-native-sqlite": "^1.1.0",
"lastfm-ts-api": "^2.6.0",
"merge-error-cause": "^5.0.2",
"mopidy": "^1.3.0",
@@ -126,20 +125,20 @@
"next-themes": "^0.4.6",
"node-object-hash": "^3.1.1",
"normalize-url": "^8.0.1",
"ntfy": "^1.15.2",
"ntfy": "^1.7.6",
"p-event": "^4.2.0",
"p-map": "^7.0.4",
"p-retry": "^7.1.1",
"passport": "^0.6.0",
"passport-deezer": "^0.2.0",
"patch-package": "^8.0.1",
"patch-package": "^8.0.0",
"prom-client": "^15.1.3",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-error-boundary": "^6.1.1",
"react-icons": "^5.6.0",
"react-redux": "^8.1.2",
"react-router-dom": "^6.30.3",
"react-router-dom": "^6.15.0",
"react-virtuoso": "^4.18.3",
"round-robin-js": "^3.0.10",
"sass": "^1.93.3",
@@ -154,11 +153,10 @@
"tsx": "^4.7.0",
"typeson": "^9.0.4",
"typeson-registry": "^11.1.1",
"undici": "7.24.6",
"undici": "7.10.0",
"vite-express": "^0.16.0",
"vlc-client": "^1.1.1",
"workmatic": "github:FoxxMD/workmatic#testDist",
"xml2js": "0.6.2",
"xml2js": "0.6.1",
"youtubei.js": "^15.0.1"
},
"devDependencies": {
@@ -167,9 +165,15 @@
"@eslint/js": "^8.56.0",
"@faker-js/faker": "^9.0.1",
"@gr2m/net-interceptor": "^1.0.0",
"@istanbuljs/nyc-config-typescript": "^1.0.2",
"@storybook/addon-a11y": "10.1.11",
"@storybook/addon-docs": "10.1.11",
"@storybook/addon-onboarding": "10.1.11",
"@storybook/addon-vitest": "10.1.11",
"@storybook/react-vite": "10.1.11",
"@testing-library/jest-dom": "^5.17.0",
"@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0",
"@tsconfig/node18": "^1.0.1",
"@types/chai": "^4.3.0",
"@types/chai-as-promised": "^7.1.5",
@@ -183,7 +187,7 @@
"@types/jest": "^27.5.2",
"@types/jscodeshift": "^0.11.6",
"@types/mocha": "^9.1.0",
"@types/node": "^24.1.0",
"@types/node": "^20.19.2",
"@types/object-hash": "^3.0.0",
"@types/passport": "^1.0.12",
"@types/react": "^18.2.18",
@@ -194,6 +198,8 @@
"@types/superagent": "^8.1.9",
"@types/xml2js": "^0.4.11",
"@vitejs/plugin-react": "^4.2.1",
"@vitest/browser-playwright": "^4.1.0",
"@vitest/coverage-v8": "^4.1.0",
"chai": "^4.3.6",
"chai-as-promised": "^7.1.1",
"eslint": "^8.56.0",
@@ -207,11 +213,12 @@
"nodemon": "^3.0.3",
"playwright": "^1.58.2",
"sinon": "^21.0.2",
"storybook": "10.2.0",
"storybook": "^10.2.0",
"ts-essentials": "^10.1.1",
"typescript": "^5.9.3",
"typescript-eslint": "^7.0.1",
"vite": "^5.4.21",
"vite": "^5.2.12",
"vitest": "^4.1.0",
"with-local-tmp-dir": "^6.0.0"
},
"browserslist": {
@@ -1,60 +0,0 @@
diff --git a/node_modules/kysely-node-native-sqlite/dist/index.cjs b/node_modules/kysely-node-native-sqlite/dist/index.cjs
index 1015da7..0679a95 100644
--- a/node_modules/kysely-node-native-sqlite/dist/index.cjs
+++ b/node_modules/kysely-node-native-sqlite/dist/index.cjs
@@ -55,7 +55,11 @@ var import_node_sqlite = require("node:sqlite");
var NodeNativeSqliteConnection = class {
#db;
constructor(...args) {
- this.#db = new import_node_sqlite.DatabaseSync(...args);
+ if (args[0] instanceof import_node_sqlite.DatabaseSync) {
+ this.#db = args[0];
+ } else {
+ this.#db = new import_node_sqlite.DatabaseSync(...args);
+ }
}
[Symbol.dispose]() {
this.#db.close();
diff --git a/node_modules/kysely-node-native-sqlite/dist/index.d.cts b/node_modules/kysely-node-native-sqlite/dist/index.d.cts
index 72366bb..32e8464 100644
--- a/node_modules/kysely-node-native-sqlite/dist/index.d.cts
+++ b/node_modules/kysely-node-native-sqlite/dist/index.d.cts
@@ -3,7 +3,7 @@ import { Dialect, SqliteAdapter, Driver, Kysely, DatabaseIntrospector, SqliteQue
declare class NodeNativeSqliteDialect implements Dialect {
#private;
- constructor(...args: ConstructorParameters<typeof DatabaseSync>);
+ constructor(...args: ConstructorParameters<typeof DatabaseSync> | [DatabaseSync]);
createAdapter(): SqliteAdapter;
createDriver(): Driver;
createIntrospector(db: Kysely<unknown>): DatabaseIntrospector;
diff --git a/node_modules/kysely-node-native-sqlite/dist/index.d.ts b/node_modules/kysely-node-native-sqlite/dist/index.d.ts
index 72366bb..32e8464 100644
--- a/node_modules/kysely-node-native-sqlite/dist/index.d.ts
+++ b/node_modules/kysely-node-native-sqlite/dist/index.d.ts
@@ -3,7 +3,7 @@ import { Dialect, SqliteAdapter, Driver, Kysely, DatabaseIntrospector, SqliteQue
declare class NodeNativeSqliteDialect implements Dialect {
#private;
- constructor(...args: ConstructorParameters<typeof DatabaseSync>);
+ constructor(...args: ConstructorParameters<typeof DatabaseSync> | [DatabaseSync]);
createAdapter(): SqliteAdapter;
createDriver(): Driver;
createIntrospector(db: Kysely<unknown>): DatabaseIntrospector;
diff --git a/node_modules/kysely-node-native-sqlite/dist/index.js b/node_modules/kysely-node-native-sqlite/dist/index.js
index 8c2b8cf..17b7eb1 100644
--- a/node_modules/kysely-node-native-sqlite/dist/index.js
+++ b/node_modules/kysely-node-native-sqlite/dist/index.js
@@ -32,7 +32,11 @@ import { DatabaseSync } from "node:sqlite";
var NodeNativeSqliteConnection = class {
#db;
constructor(...args) {
- this.#db = new DatabaseSync(...args);
+ if (args[0] instanceof DatabaseSync) {
+ this.#db = args[0];
+ } else {
+ this.#db = new DatabaseSync(...args);
+ }
}
[Symbol.dispose]() {
this.#db.close();
-20
View File
@@ -1,20 +0,0 @@
import { createDatabase, WorkmaticDb } from 'workmatic';
import { DatabaseSync } from 'node:sqlite';
import { configDir } from '../index.js';
import * as path from 'path';
export const getJobsDb = (dbName: string = ':memory:'): [WorkmaticDb, DatabaseSync] => {
let dbPath: string;
if(dbName === ':memory') {
dbPath = dbName;
} else {
dbPath = path.resolve(configDir, `${dbName}.db`);
}
const database = new DatabaseSync(dbPath);
const db = createDatabase({ db: database });
database.exec(`CREATE INDEX IF NOT EXISTS playdate ON workmatic_jobs((payload->>'$.data.playDate'))`);
database.exec(`CREATE INDEX IF NOT EXISTS seenAt ON workmatic_jobs((payload->>'$.meta.seenAt'))`);
return [db, database];
}
+2 -1
View File
@@ -9,6 +9,7 @@ import { MusicBrainzApi } from 'musicbrainz-api';
import { SourceType } from './config/source/sources.js';
import { ClientType, clientTypes } from './config/client/clients.js';
import assert, { AssertionError } from 'assert';
import { SimpleError } from '../errors/MSErrors.js';
export const lowGranularitySources: SourceType[] = ['subsonic', 'ytmusic'];
@@ -268,7 +269,7 @@ export const asCacheConfig = (val: Record<string, any>): val is CacheConfigType
if(asCacheConnectableConfig(val) || asCacheEphemeralConfig(val)) {
return true;
}
throw new Error(`${val.provider} must have a connection property that is a string`);
throw new SimpleError(`${val.provider} must have a connection property that is a string`);
}
export const asCacheMetadataProvider = (val: any): val is CacheScrobbleProvider => asCacheProvider(val);
export type CacheScrobbleProvider = CacheProvider;
@@ -94,7 +94,6 @@ export default abstract class AbstractScrobbleClient extends AbstractComponent i
scrobbleDelay: number = 1000;
scrobbleSleep: number = 2000;
scrobbleWaitStopInterval: number = 2000;
scrobbleRetries: number = 0;
scrobbling: boolean = false;
userScrobblingStopSignal: undefined | any;
@@ -199,18 +198,6 @@ export default abstract class AbstractScrobbleClient extends AbstractComponent i
this.existingScrobble = (playObjPre: PlayObject, existingScrobbles: PlayObject[], log?: boolean) => existingScrobble(playObjPre, existingScrobbles, existingScrobbleOpts, log);
}
[Symbol.dispose]() {
this.scheduler.stop();
for(const job of this.scheduler.getAllJobs()) {
this.scheduler.removeById(job.id);
}
}
async [Symbol.asyncDispose]() {
this[Symbol.dispose]();
await this.tryStopScrobbling();
}
protected async postCache(): Promise<void> {
await super.postCache();
this.generateStaggerMappers();
@@ -627,11 +614,11 @@ export default abstract class AbstractScrobbleClient extends AbstractComponent i
return;
}
this.userScrobblingStopSignal = true;
let timePasssed = 0;
while(this.userScrobblingStopSignal !== undefined && timePasssed < (this.scrobbleWaitStopInterval * 10)) {
await sleep(this.scrobbleWaitStopInterval);
timePasssed += this.scrobbleWaitStopInterval;
this.logger.verbose(`Waiting for scrobble processing stop signal to be acknowledged (waited ${timePasssed}ms)`);
let secsPassed = 0;
while(this.userScrobblingStopSignal !== undefined && secsPassed < 10) {
await sleep(2000);
secsPassed += 2;
this.logger.verbose(`Waiting for scrobble processing stop signal to be acknowledged (waited ${secsPassed}s)`);
}
if(this.userScrobblingStopSignal !== undefined) {
this.logger.warn('Could not stop scrobble processing! Or signal was lost :(');
-6
View File
@@ -117,12 +117,6 @@ export default abstract class AbstractSource extends AbstractComponent implement
this.discoveredCounter = getRoot().items.sourceMetics.discovered;
}
async [Symbol.asyncDispose]() {
if(this.canPoll) {
await this.tryStopPolling();
}
}
protected async postCache(): Promise<void> {
await super.postCache();
this.generateStaggerMappers();
-21
View File
@@ -71,25 +71,6 @@ export default class MemorySource extends AbstractSource {
})));
}
[Symbol.dispose]() {
this.scheduler.stop();
for(const job of this.scheduler.getAllJobs()) {
this.scheduler.removeById(job.id);
}
for(const p of this.players.keys()) {
this.deletePlayer(p);
}
}
async [Symbol.asyncDispose]() {
await super[Symbol.asyncDispose]();
this.scheduler.stop();
for(const job of this.scheduler.getAllJobs()) {
this.scheduler.removeById(job.id);
}
this[Symbol.dispose]();
}
cleanupPlayers = () => {
for (const key of this.players.keys()) {
this.cleanupPlayer(key);
@@ -207,8 +188,6 @@ export default class MemorySource extends AbstractSource {
if(reason !== undefined) {
this.players.get(id)?.logger.debug(reason);
}
using player = this.players.get(id);
player[Symbol.dispose]();
this.players.delete(id);
this.playerState.delete(id);
this.emitEvent('playerDelete', {platformId: id});
@@ -99,15 +99,6 @@ export abstract class AbstractPlayerState {
this.stateIntervalOptions = {staleInterval, orphanedInterval: orphanedInterval};
}
[Symbol.dispose]() {
if(this.currentListenRange !== undefined) {
this.currentListenRange[Symbol.dispose]();
}
for(const lr of this.listenRanges) {
lr[Symbol.dispose]();
}
}
protected abstract newListenProgress(data?: Partial<PlayProgress>): ListenProgress;
protected abstract newListenRange(start?: ListenProgress, end?: ListenProgress, options?: object): ListenRange;
@@ -15,10 +15,6 @@ export abstract class ListenRange {
this.end = e;
}
[Symbol.dispose]() {
}
public abstract isPositional(): boolean;
public abstract isInitial(): boolean;
public abstract seeked(position?: number, reportedTS?: Dayjs): [boolean, Second?];
@@ -104,11 +100,6 @@ export class ListenRangePositional extends ListenRange {
this.finalized = false;
}
[Symbol.dispose]() {
super[Symbol.dispose]()
this.rtPlayer[Symbol.dispose]();
}
isPositional(): boolean {
return true;
}
@@ -1,10 +1,12 @@
import { Logger } from "@foxxmd/logging";
import { CALCULATED_PLAYER_STATUSES, PlayPlatformId, REPORTED_PLAYER_STATUSES } from "../../common/infrastructure/Atomic.js";
import { AbstractPlayerState, PlayerStateOptions } from "./AbstractPlayerState.js";
import { PlayProgressPositional, Second } from "../../../core/Atomic.js";
import { GenericPlayerState } from "./GenericPlayerState.js";
import { GenericRealtimePlayer, RealtimePlayer } from "./RealtimePlayer.js";
import { PlayProgress, PlayProgressPositional, Second } from "../../../core/Atomic.js";
import { Dayjs } from "dayjs";
import { ListenProgressPositional } from "./ListenProgress.js";
import { ListenRangePositional } from "./ListenRange.js";
import { ListenProgress, ListenProgressPositional } from "./ListenProgress.js";
import { ListenRange, ListenRangePositional } from "./ListenRange.js";
export class PositionalPlayerState extends AbstractPlayerState {
@@ -33,13 +33,6 @@ export abstract class RealtimePlayer {
this.scheduler.stop();
}
[Symbol.dispose]() {
this.scheduler.stop();
for(const job of this.scheduler.getAllJobs()) {
this.scheduler.removeById(job.id);
}
}
public play(position?: number) {
if (position !== undefined) {
this.position = position;
+6 -3
View File
@@ -4,8 +4,9 @@ import asPromised from 'chai-as-promised';
import { after, before, describe, it } from 'mocha';
import dayjs from "dayjs";
import withLocalTmpDir from 'with-local-tmp-dir';
import path from 'path';
import { initFileCache, initMemoryCache, initValkeyCache, MSCache } from "../../common/Cache.js";
import { generatePlays } from "../../../core/PlayTestUtils.js";
import { generatePlay, generatePlayerStateData, generatePlays, normalizePlays } from "../../../core/PlayTestUtils.js";
import { ListenProgressPositional, ListenProgressTS } from "../../sources/PlayerState/ListenProgress.js";
import { isPortReachableConnect } from "../../utils/NetworkUtils.js";
import { getRoot } from "../../ioc.js";
@@ -164,7 +165,7 @@ describe('#Caching', function () {
const root = getRoot();
root.upsert({ cache: () => () => new MSCache(loggerTest, { scrobble: { provider: 'file', connection: process.cwd(), persistInterval: 100 } }) });
await using test = new TestScrobbler();
const test = new TestScrobbler();
await test.initialize();
const plays = generatePlays(100);
await test.queueScrobble(plays, 'testSource');
@@ -173,11 +174,13 @@ describe('#Caching', function () {
const dirContents = await promises.readdir('.');
const hasCache = dirContents.some(x => x === 'ms-scrobble.cache');
expect(hasCache).is.true;
test.scheduler.stop();
await using newTest = new TestScrobbler();
const newTest = new TestScrobbler();
await newTest.initialize();
expect(newTest.queuedScrobbles.length).to.eq(plays.length);
expect(newTest.queuedScrobbles[0].play.data.track).to.eq(queued[0].data.track);
newTest.scheduler.stop();
}, { unsafeCleanup: true });
-19
View File
@@ -1,19 +0,0 @@
import chai, { assert, expect } from 'chai';
import asPromised from 'chai-as-promised';
import { after, before, describe, it } from 'mocha';
import { createDatabase, createClient, createWorker } from 'workmatic';
import { DatabaseSync } from 'node:sqlite';
import { getJobsDb } from '../../common/database/JobDb.js';
import { generatePlay } from '../../../core/PlayTestUtils.js';
chai.use(asPromised);
it('does stuff', async function () {
const [db, nodeDb] = getJobsDb(':memory:');
const client = createClient({ db, queue: 'scrobble' });
const { id } = await client.add(generatePlay());
console.log(`Job created: ${id}`);
expect(true).to.eq(true);
});
@@ -21,7 +21,6 @@ export class TestScrobbler extends AbstractScrobbleClient {
this.getScrobblesForTimeRange = async (_) => this.testRecentScrobbles;
this.scrobbleDelay = 10;
this.scrobbleSleep = 20;
this.scrobbleWaitStopInterval = 20;
}
doScrobble(playObj: PlayObject) {
+70 -74
View File
@@ -50,6 +50,8 @@ const generateTestScrobbler = () => {
return testScrobbler;
}
let testScrobbler: TestScrobbler = generateTestScrobbler()
describe('Networking', function () {
describe('Authentication', function () {
@@ -116,9 +118,12 @@ describe('Detects duplicate and unique scrobbles from client recent history', fu
describe('When scrobble is unique', function () {
beforeEach(function() {
testScrobbler = generateTestScrobbler();
});
it('It is not detected as duplicate when play date is newer than most recent', async function () {
await using testScrobbler = generateTestScrobbler();
testScrobbler.testRecentScrobbles = normalizedWithMixedDur;
const newScrobble = generatePlay({
@@ -131,7 +136,6 @@ describe('Detects duplicate and unique scrobbles from client recent history', fu
it('It is not detected as duplicate when play date is close to an existing scrobble', async function () {
await using testScrobbler = generateTestScrobbler();
testScrobbler.testRecentScrobbles = normalizedWithMixedDur;
const newScrobble = generatePlay({
@@ -143,7 +147,6 @@ describe('Detects duplicate and unique scrobbles from client recent history', fu
it('It handles unique detection when no existing scrobble matches above a score of 0', async function () {
await using testScrobbler = generateTestScrobbler();
testScrobbler.testRecentScrobbles = normalizedWithMixedDur;
const uniquePlay = generatePlay({
@@ -162,9 +165,12 @@ describe('Detects duplicate and unique scrobbles from client recent history', fu
describe('When scrobble track/artist/album matches existing but is a new scrobble', function () {
beforeEach(function() {
testScrobbler = generateTestScrobbler();
});
it('Is not detected as duplicate when artist is same, time is similar, but track is different', async function () {
await using testScrobbler = generateTestScrobbler();
testScrobbler.testRecentScrobbles = normalizedWithMixedDur;
const diffPlay = clone(normalizedWithMixedDur[1]);
@@ -176,7 +182,6 @@ describe('Detects duplicate and unique scrobbles from client recent history', fu
it('Is not detected as duplicate when track is same, time is similar, but artist is different', async function () {
await using testScrobbler = generateTestScrobbler();
testScrobbler.testRecentScrobbles = normalizedWithMixedDur;
const diffPlay = clone(normalizedWithMixedDur[1]);
@@ -189,7 +194,6 @@ describe('Detects duplicate and unique scrobbles from client recent history', fu
it('Is not detected as duplicate when play date is different by more than 10 seconds (high granularity source)', async function () {
await using testScrobbler = generateTestScrobbler();
testScrobbler.testRecentScrobbles = normalizedWithMixedDur;
const timeOffPos = clone(normalizedWithMixedDur[normalizedWithMixedDur.length - 1]);
@@ -208,7 +212,6 @@ describe('Detects duplicate and unique scrobbles from client recent history', fu
initialDate: firstPlayDate,
defaultMeta: {source: 'subsonic'}
});
await using testScrobbler = generateTestScrobbler();
testScrobbler.testRecentScrobbles = recent;
const timeOffPos = clone(recent[recent.length - 1]);
@@ -223,9 +226,12 @@ describe('Detects duplicate and unique scrobbles from client recent history', fu
describe('When existing has duration', function () {
beforeEach(function() {
testScrobbler = generateTestScrobbler();
});
it('A track with continuity to the previous track is not detected as a duplicate', async function () {
await using testScrobbler = generateTestScrobbler();
testScrobbler.testRecentScrobbles = normalizedWithDur;
const brickPt1 = normalizedWithDur.find(x => x.data.track.includes('Another Brick'));
@@ -255,7 +261,6 @@ describe('Detects duplicate and unique scrobbles from client recent history', fu
initialDate: firstPlayDate,
defaultMeta: {source: 'jellyfin'}
});
await using testScrobbler = generateTestScrobbler();
testScrobbler.testRecentScrobbles = recent;
const repeatPlay = clone(recent[recent.length - 1]);
@@ -276,7 +281,6 @@ describe('Detects duplicate and unique scrobbles from client recent history', fu
playDate: dayjs().subtract(179, 's'),
duration: 179
});
await using testScrobbler = generateTestScrobbler();
testScrobbler.testRecentScrobbles = [play];
const newPlay = clone(play);
@@ -292,14 +296,16 @@ describe('Detects duplicate and unique scrobbles from client recent history', fu
describe('When scrobble is a duplicate (title/artists/album)', function () {
beforeEach(function() {
testScrobbler = generateTestScrobbler();
});
it('Is detected as duplicate when an exact match', async function () {
await using testScrobbler = generateTestScrobbler();
testScrobbler.testRecentScrobbles = normalizedWithMixedDur;
assert.isTrue((await testScrobbler.alreadyScrobbled(normalizedWithMixedDur[normalizedWithMixedDur.length - 1]))[0]);
});
it('Is detected as duplicate when artist/title differences are whitespace or case', async function () {
await using testScrobbler = generateTestScrobbler();
testScrobbler.testRecentScrobbles = normalizedWithMixedDur;
const ref = normalizedWithMixedDur[3];
@@ -324,7 +330,6 @@ describe('Detects duplicate and unique scrobbles from client recent history', fu
});
it('Is detected as duplicate when artist/title differences are from unicode normalization', async function () {
await using testScrobbler = generateTestScrobbler();
testScrobbler.testRecentScrobbles = normalizedWithMixedDur;
const ref = normalizedWithMixedDur.find(x => x.data.track === 'Jimbó');
@@ -336,7 +341,6 @@ describe('Detects duplicate and unique scrobbles from client recent history', fu
it('Is detected as duplicate when play date is off by 10 seconds or less (high granularity source)', async function () {
await using testScrobbler = generateTestScrobbler();
testScrobbler.testRecentScrobbles = normalizedWithMixedDur;
const timeOffPos = clone(normalizedWithMixedDur[normalizedWithMixedDur.length - 1]);
@@ -366,7 +370,6 @@ describe('Detects duplicate and unique scrobbles from client recent history', fu
initialDate: firstPlayDate,
defaultMeta: {source: 'subsonic'}
});
await using testScrobbler = generateTestScrobbler();
testScrobbler.testRecentScrobbles = recent;
const timeOffPos = clone(recent[recent.length - 1]);
@@ -380,7 +383,6 @@ describe('Detects duplicate and unique scrobbles from client recent history', fu
});
it('Is detected as duplicate when title is exact, artist is similar, and time is similar', async function () {
await using testScrobbler = generateTestScrobbler();
testScrobbler.testRecentScrobbles = normalizedWithMixedDur;
const ref = normalizedWithMixedDur[3];
@@ -425,7 +427,7 @@ describe('Detects duplicate and unique scrobbles from client recent history', fu
lifecycle: defaultLifecycle()
}
}
await using testScrobbler = generateTestScrobbler();
testScrobbler.testRecentScrobbles = normalizedWithMixedDurOlder.concat(ref);
assert.isTrue((await testScrobbler.alreadyScrobbled(spotifyPlay))[0]);
@@ -433,9 +435,12 @@ describe('Detects duplicate and unique scrobbles from client recent history', fu
describe('When at least one play has duration', function () {
beforeEach(function() {
testScrobbler = generateTestScrobbler();
});
it('Is detected as duplicate when play date is close to the end of an existing scrobble', async function () {
await using testScrobbler = generateTestScrobbler();
testScrobbler.testRecentScrobbles = normalizedWithDur;
const timeEnd = clone(normalizedWithDur[normalizedWithMixedDur.length - 2]);
@@ -460,13 +465,16 @@ describe('Detects duplicate and unique scrobbles from client recent history', fu
describe('Detects duplicate and unique scrobbles using actively tracked scrobbles', function() {
beforeEach(function() {
testScrobbler = generateTestScrobbler();
testScrobbler.testRecentScrobbles = normalizedWithMixedDur;
});
it('Detects a unique play', async function() {
const newScrobble = generatePlay({
playDate: normalizedWithMixedDur[normalizedWithMixedDur.length - 3].data.playDate.add(3, 'seconds')
});
await using testScrobbler = generateTestScrobbler();
testScrobbler.testRecentScrobbles = normalizedWithMixedDur;
const [matchedPlay, matchedData] = await testScrobbler.findExistingSubmittedPlayObj(newScrobble);
assert.isUndefined(matchedPlay);
@@ -477,8 +485,6 @@ describe('Detects duplicate and unique scrobbles using actively tracked scrobble
const newScrobble = generatePlay({
playDate: normalizedWithMixedDur[normalizedWithMixedDur.length - 3].data.playDate.add(3, 'seconds')
});
await using testScrobbler = generateTestScrobbler();
testScrobbler.testRecentScrobbles = normalizedWithMixedDur;
testScrobbler.addScrobbledTrack(newScrobble, newScrobble);
const [matchedPlay, matchedData] = await testScrobbler.findExistingSubmittedPlayObj(newScrobble);
@@ -491,8 +497,6 @@ describe('Detects duplicate and unique scrobbles using actively tracked scrobble
const newScrobble = generatePlay({
playDate: normalizedWithMixedDur[normalizedWithMixedDur.length - 3].data.playDate.add(3, 'seconds')
});
await using testScrobbler = generateTestScrobbler();
testScrobbler.testRecentScrobbles = normalizedWithMixedDur;
testScrobbler.addScrobbledTrack(newScrobble, newScrobble);
const dupScrobble = clone(newScrobble);
@@ -515,7 +519,7 @@ describe('Upstream Scrobbles', function() {
it('Calls timerange func to get SOT scrobbles when none exists', async function() {
const existingPlays = normalizePlays(generatePlays(3), {initialDate: dayjs().subtract(1, 'hour')});
await using scrobbler = generateTestScrobbler();
const scrobbler = generateTestScrobbler();
scrobbler.testRecentScrobbles = [];
await scrobbler.tryInitialize();
scrobbler.testRecentScrobbles = existingPlays;
@@ -529,12 +533,11 @@ describe('Upstream Scrobbles', function() {
await emptied;
scrobbler.tryStopScrobbling().then(() => null);
expect(sp.called).is.true;
return;
});
it('Uses cached timerange for closely grouped scrobbles', async function() {
const existingPlays = normalizePlays(generatePlays(3), {initialDate: dayjs().subtract(1, 'hour')});
await using scrobbler = generateTestScrobbler();
const scrobbler = generateTestScrobbler();
scrobbler.testRecentScrobbles = [];
await scrobbler.tryInitialize();
scrobbler.testRecentScrobbles = existingPlays;
@@ -549,12 +552,11 @@ describe('Upstream Scrobbles', function() {
await emptied;
scrobbler.tryStopScrobbling().then(() => null);
expect(sp.callCount).to.eq(1);
return;
});
it('Uses separate timerange calls when scrobbles are not closely grouped', async function() {
const existingPlays = normalizePlays(generatePlays(3), {initialDate: dayjs().subtract(1, 'hour')});
await using scrobbler = generateTestScrobbler();
const scrobbler = generateTestScrobbler();
scrobbler.testRecentScrobbles = [];
await scrobbler.tryInitialize();
scrobbler.testRecentScrobbles = existingPlays;
@@ -570,12 +572,11 @@ describe('Upstream Scrobbles', function() {
await emptied;
scrobbler.tryStopScrobbling().then(() => null);
expect(sp.callCount).to.eq(2);
return;
});
it('Gets fresh timerange if TTL of staleAfter has passed', async function() {
const existingPlays = normalizePlays(generatePlays(3), {initialDate: dayjs().subtract(1, 'hour')});
await using scrobbler = generateTestScrobbler();
const scrobbler = generateTestScrobbler();
scrobbler.testRecentScrobbles = [];
await scrobbler.tryInitialize();
scrobbler.testRecentScrobbles = existingPlays;
@@ -596,7 +597,6 @@ describe('Upstream Scrobbles', function() {
await emptied2;
scrobbler.tryStopScrobbling().then(() => null);
expect(sp.calledTwice).is.true;
return;
});
});
@@ -604,7 +604,7 @@ describe('Upstream Scrobbles', function() {
describe('Dead Scrobbles', function() {
it('Processes all dead scrobbles', async function () {
await using testScrobbler = generateTestScrobbler();
testScrobbler = generateTestScrobbler();
await testScrobbler.initialize();
testScrobbler.testRecentScrobbles = [];
@@ -619,19 +619,21 @@ describe('Dead Scrobbles', function() {
});
const normalizedScrobbler = async () => {
await using testScrobbler = generateTestScrobbler();
await testScrobbler.initialize();
testScrobbler.testRecentScrobbles = normalizedWithMixedDur;
testScrobbler.scrobbleSleep = 500;
testScrobbler.scrobbleDelay = 0;
//testScrobbler.lastScrobbleCheck = dayjs().subtract(60, 'seconds');
testScrobbler.config.options = {};
return testScrobbler;
}
describe('Scrobble client uses transform plays correctly', function() {
beforeEach(async function() {
testScrobbler = generateTestScrobbler();
await testScrobbler.initialize();
testScrobbler.testRecentScrobbles = normalizedWithMixedDur;
testScrobbler.scrobbleSleep = 500;
testScrobbler.scrobbleDelay = 0;
//testScrobbler.lastScrobbleCheck = dayjs().subtract(60, 'seconds');
testScrobbler.config.options = {};
//testScrobbler.initScrobbleMonitoring().catch(console.error);
});
// TODO need to find a better way to detect this
// since we are now doing it in the processing loop instead of before queue
@@ -654,7 +656,6 @@ describe('Scrobble client uses transform plays correctly', function() {
// });
it('Transforms play on scrobble when postCompare is present', async function() {
await using testScrobbler = await normalizedScrobbler();
testScrobbler.config.options = {
playTransform: {
postCompare: {
@@ -678,7 +679,6 @@ describe('Scrobble client uses transform plays correctly', function() {
});
it('Transforms candidate play on comparison', async function() {
await using testScrobbler = await normalizedScrobbler();
testScrobbler.config.options = {
playTransform: {
compare: {
@@ -701,7 +701,6 @@ describe('Scrobble client uses transform plays correctly', function() {
});
it('Transforms existing play on comparison', async function() {
await using testScrobbler = await normalizedScrobbler();
testScrobbler.config.options = {
playTransform: {
compare: {
@@ -725,25 +724,24 @@ describe('Scrobble client uses transform plays correctly', function() {
afterEach(async function () {
this.timeout(3500);
//await testScrobbler.tryStopScrobbling()
await testScrobbler.tryStopScrobbling()
});
});
const normalizedMonitoringScrobbler = async () => {
const testScrobbler = generateTestScrobbler();
await testScrobbler.initialize();
testScrobbler.testRecentScrobbles = normalizedWithMixedDur;
testScrobbler.scrobbleSleep = 100;
testScrobbler.scrobbleDelay = 0;
testScrobbler.initScrobbleMonitoring().catch(console.error);
return testScrobbler;
}
describe('Manages scrobble queue', function() {
beforeEach(async function() {
testScrobbler = generateTestScrobbler();
await testScrobbler.initialize();
testScrobbler.testRecentScrobbles = normalizedWithMixedDur;
testScrobbler.testRecentScrobbles = normalizedWithMixedDur;
testScrobbler.scrobbleSleep = 500;
testScrobbler.scrobbleDelay = 0;
testScrobbler.initScrobbleMonitoring().catch(console.error);
});
it('Scrobbles a uniquely queued play', async function() {
await using testScrobbler = await normalizedMonitoringScrobbler();
const newScrobble = generatePlay({
playDate: normalizedWithMixedDur[normalizedWithMixedDur.length - 3].data.playDate.add(3, 'seconds')
});
@@ -755,7 +753,6 @@ describe('Manages scrobble queue', function() {
});
it('Does not Scrobble a duplicate play queued after original is scrobbled', async function() {
await using testScrobbler = await normalizedMonitoringScrobbler();
this.timeout(20000);
const newScrobble = generatePlay({
@@ -777,7 +774,6 @@ describe('Manages scrobble queue', function() {
});
it('Does not Scrobble a duplicate play queued before original is scrobbled', async function() {
await using testScrobbler = await normalizedMonitoringScrobbler();
this.timeout(3500);
const newScrobble = generatePlay({
@@ -799,7 +795,6 @@ describe('Manages scrobble queue', function() {
});
it('Delays scrobbles when many are queued', async function () {
await using testScrobbler = await normalizedMonitoringScrobbler();
this.timeout(3500);
const newScrobble1 = generatePlay({
@@ -828,6 +823,7 @@ describe('Manages scrobble queue', function() {
after(async function () {
this.timeout(3500);
await testScrobbler.tryStopScrobbling()
});
});
@@ -837,7 +833,7 @@ describe('Now Playing', function() {
it('When no Now Playing exists, chooses play based on sorted platform id', async function () {
await using npScrobbler = new NowPlayingScrobbler();
const npScrobbler = new NowPlayingScrobbler();
await npScrobbler.initialize();
const firstPlatform: PlayPlatformId = ['aaa', 'NO_USER'];
@@ -856,7 +852,7 @@ describe('Now Playing', function() {
it('When Now Playing platform does not exist in queued plays, chooses play based on sorted platform id', async function () {
await using npScrobbler = new NowPlayingScrobbler();
const npScrobbler = new NowPlayingScrobbler();
await npScrobbler.initialize();
const firstPlatform: PlayPlatformId = ['aaa', 'NO_USER'];
@@ -877,7 +873,7 @@ describe('Now Playing', function() {
it('Chooses play based on existing Now Playing', async function () {
await using npScrobbler = new NowPlayingScrobbler();
const npScrobbler = new NowPlayingScrobbler();
await npScrobbler.initialize();
const firstPlatform: PlayPlatformId = ['aaa', 'NO_USER'];
@@ -901,7 +897,7 @@ describe('Now Playing', function() {
it('Sorts Sources alphabetically when using default Source sorting', async function () {
await using npScrobbler = new NowPlayingScrobbler();
const npScrobbler = new NowPlayingScrobbler();
await npScrobbler.initialize();
const a = generateSourcePlayerObj({play:generatePlay({}, {deviceId: genGroupIdStr(generatePlayPlatformId())})});
@@ -918,7 +914,7 @@ describe('Now Playing', function() {
it('Sorts Sources based on user config', async function () {
await using npScrobbler = new NowPlayingScrobbler({name: 'test', options: {nowPlaying: ['btest', 'atest']}});
const npScrobbler = new NowPlayingScrobbler({name: 'test', options: {nowPlaying: ['btest', 'atest']}});
await npScrobbler.initialize();
const a = generateSourcePlayerObj({play:generatePlay({}, {deviceId: genGroupIdStr(generatePlayPlatformId())})});
@@ -935,7 +931,7 @@ describe('Now Playing', function() {
it('Does not report if source is not in user config', async function () {
await using npScrobbler = new NowPlayingScrobbler({name: 'test', options: {nowPlaying: ['btest', 'atest']}});
const npScrobbler = new NowPlayingScrobbler({name: 'test', options: {nowPlaying: ['btest', 'atest']}});
await npScrobbler.initialize();
const c = generateSourcePlayerObj({play:generatePlay({}, {deviceId: genGroupIdStr(generatePlayPlatformId())})});
@@ -953,7 +949,7 @@ describe('Now Playing', function() {
it('Should update if no existing Now Playing', async function () {
await using npScrobbler = new NowPlayingScrobbler();
const npScrobbler = new NowPlayingScrobbler();
await npScrobbler.initialize();
const res = npScrobbler.shouldUpdatePlayingNow(generateSourcePlayerObj({play:generatePlay({}, {deviceId: genGroupIdStr(generatePlayPlatformId())})}));
@@ -962,7 +958,7 @@ describe('Now Playing', function() {
it('Should update if previous Now Playing matches updated and last updated diff is greater than upper limit', async function () {
await using npScrobbler = new NowPlayingScrobbler();
const npScrobbler = new NowPlayingScrobbler();
await npScrobbler.initialize();
const lastUpdate = generateSourcePlayerObj({play:generatePlay({}, {deviceId: genGroupIdStr(generatePlayPlatformId())})});
@@ -975,7 +971,7 @@ describe('Now Playing', function() {
it('Should NOT update if previous Now Playing matches updated and last updated diff is less than upper limit', async function () {
await using npScrobbler = new NowPlayingScrobbler();
const npScrobbler = new NowPlayingScrobbler();
await npScrobbler.initialize();
const lastUpdate = generateSourcePlayerObj({play:generatePlay({}, {deviceId: genGroupIdStr(generatePlayPlatformId())})});
@@ -988,7 +984,7 @@ describe('Now Playing', function() {
it('Should update if previous Now Playing does NOT match updated and last updated diff is greater than lower limit', async function () {
await using npScrobbler = new NowPlayingScrobbler();
const npScrobbler = new NowPlayingScrobbler();
await npScrobbler.initialize();
const lastUpdate = generateSourcePlayerObj({play:generatePlay({}, {deviceId: genGroupIdStr(generatePlayPlatformId())})});
@@ -1001,7 +997,7 @@ describe('Now Playing', function() {
it('Should NOT update if previous Now Playing does NOT match updated and last updated diff is less than than lower limit', async function () {
await using npScrobbler = new NowPlayingScrobbler();
const npScrobbler = new NowPlayingScrobbler();
await npScrobbler.initialize();
const lastUpdate = generateSourcePlayerObj({play:generatePlay({}, {deviceId: genGroupIdStr(generatePlayPlatformId())})});
@@ -1022,7 +1018,7 @@ describe('Now Playing', function() {
it('Should update when no existing Now Playing', async function () {
await using npScrobbler = new NowPlayingScrobbler();
const npScrobbler = new NowPlayingScrobbler();
npScrobbler.nowPlayingTaskInterval = 10;
await npScrobbler.initialize();
npScrobbler.scheduler.startById('pn_task');
@@ -1036,7 +1032,7 @@ describe('Now Playing', function() {
it('Should update when updated does not match Now Playing', async function () {
await using npScrobbler = new NowPlayingScrobbler();
const npScrobbler = new NowPlayingScrobbler();
npScrobbler.nowPlayingTaskInterval = 10;
await npScrobbler.initialize();
npScrobbler.scheduler.startById('pn_task');
+18 -16
View File
@@ -27,6 +27,8 @@ const emitter = new EventEmitter();
const generateSource = () => {
return new TestSource('spotify', 'test', {}, {localUrl: new URL('https://example.com'), configDir: 'fake', logger: loggerTest, version: 'test'}, emitter);
}
let source: TestSource = generateSource();
const generateMemorySource = (config: SourceConfig = {}) => {
const s = new TestMemorySource('spotify', 'test', config, {localUrl: new URL('https://example.com'), configDir: 'fake', logger: loggerTest, version: 'test'}, emitter);
s.buildTransformRules();
@@ -43,8 +45,11 @@ const generateMemoryPositionalSource = (config: SourceConfig = {}) => {
describe('Sources use transform plays correctly', function () {
beforeEach(function() {
source = generateSource();
});
it('Transforms play on preCompare', async function() {
await using source = generateSource();
source.config.options = {
playTransform: {
preCompare: {
@@ -67,7 +72,6 @@ describe('Sources use transform plays correctly', function () {
});
it('Transforms play on postCompare', async function() {
await using source = generateSource();
source.config.options = {
playTransform: {
postCompare: {
@@ -96,7 +100,6 @@ describe('Sources use transform plays correctly', function () {
});
it('Transforms play existing comparison', async function() {
await using source = generateSource();
source.config.options = {
playTransform: {
compare: {
@@ -123,7 +126,6 @@ describe('Sources use transform plays correctly', function () {
});
it('Transforms play candidate comparison', async function() {
await using source = generateSource();
source.config.options = {
playTransform: {
compare: {
@@ -193,7 +195,7 @@ describe('Player Cleanup', function () {
});
const cleanedUpDuration = async (generateSource: (config: SourceConfig) => MemorySource) => {
await using source = generateSource({data: {staleAfter: 21, orphanedAfter: 40}, options: {}});
const source = generateSource({data: {staleAfter: 21, orphanedAfter: 40}, options: {}});
const initialDate = dayjs();
const initialState = generatePlayerStateData({position: 0, playData: {duration: 50}, timestamp: initialDate, status: REPORTED_PLAYER_STATUSES.playing});
expect((await source.processRecentPlays([initialState])).length).to.be.eq(0);
@@ -237,7 +239,7 @@ describe('Player Cleanup', function () {
const noScrobbleRediscoveryOnActive = async (generateSource: (config: SourceConfig) => MemorySource) => {
await using source = generateSource({data: {staleAfter: 21, orphanedAfter: 40}, options: {}});
const source = generateSource({data: {staleAfter: 21, orphanedAfter: 40}, options: {}});
const initialDate = dayjs();
const initialState = generatePlayerStateData({position: 0, playData: {duration: 50}, timestamp: initialDate, status: REPORTED_PLAYER_STATUSES.playing});
expect((await source.processRecentPlays([initialState])).length).to.be.eq(0);
@@ -305,7 +307,7 @@ describe('Player Cleanup', function () {
const noScrobbleStale = async (generateSource: (config: SourceConfig) => MemorySource) => {
await using source = generateSource({data: {staleAfter: 21, orphanedAfter: 40}, options: {}});
const source = generateSource({data: {staleAfter: 21, orphanedAfter: 40}, options: {}});
const initialDate = dayjs();
// if player incorrectly counted stale time then 30s of actual play + 20s of stale time > scrobble threshold of 50% of 90s
@@ -350,7 +352,7 @@ describe('Player Cleanup', function () {
const scrobbleRediscoveryOnActive = async (generateSource: (config: SourceConfig) => MemorySource) => {
await using source = generateSource({data: {staleAfter: 21, orphanedAfter: 40}, options: {}});
const source = generateSource({data: {staleAfter: 21, orphanedAfter: 40}, options: {}});
const initialDate = dayjs();
// if player incorrectly counted stale time then 30s of actual play + 20s of stale time > scrobble threshold of 50% of 90s
@@ -455,7 +457,7 @@ describe('Deezer Internal Source', function() {
const fuzzyPlay = clone(targetPlay);
fuzzyPlay.data.playDate = targetPlay.data.playDate.add(targetPlay.data.duration, 's');
await using source = generateDeezerSource({fuzzyDiscoveryIgnore: true});
const source = generateDeezerSource({fuzzyDiscoveryIgnore: true});
await source.discover([...normalizedPlays, interimPlay]);
const discovered = await source.discover([fuzzyPlay]);
@@ -468,7 +470,7 @@ describe('Deezer Internal Source', function() {
const fuzzyPlay = clone(targetPlay);
fuzzyPlay.data.playDate = targetPlay.data.playDate.add(targetPlay.data.duration, 's');
await using source = generateDeezerSource({fuzzyDiscoveryIgnore: true});
const source = generateDeezerSource({fuzzyDiscoveryIgnore: true});
await source.discover(normalizedPlays);
const discovered = await source.discover([fuzzyPlay]);
@@ -482,7 +484,7 @@ describe('Deezer Internal Source', function() {
fuzzyPlay.data.playDate = targetPlay.data.playDate.add(targetPlay.data.duration, 's');
const morePlays = normalizePlays([...normalizedPlays, fuzzyPlay, ...generatePlays(2)], {initialDate: firstPlayDate});
await using source = generateDeezerSource({fuzzyDiscoveryIgnore: true});
const source = generateDeezerSource({fuzzyDiscoveryIgnore: true});
const discovered = await source.discover(morePlays);
expect(discovered.length).to.eq(morePlays.length);
@@ -497,7 +499,7 @@ describe('Deezer Internal Source', function() {
const fuzzyPlay = clone(targetPlay);
fuzzyPlay.data.playDate = targetPlay.data.playDate.add(targetPlay.data.duration, 's');
await using source = generateDeezerSource({fuzzyDiscoveryIgnore: 'aggressive'});
const source = generateDeezerSource({fuzzyDiscoveryIgnore: 'aggressive'});
await source.discover([...normalizedPlays, interimPlay]);
const discovered = await source.discover([fuzzyPlay]);
@@ -511,7 +513,7 @@ describe('Deezer Internal Source', function() {
const duringPlay = clone(targetPlay);
duringPlay.data.playDate = targetPlay.data.playDate.add(targetPlay.data.duration * 0.5, 's');
await using source = generateDeezerSource({fuzzyDiscoveryIgnore: 'aggressive'});
const source = generateDeezerSource({fuzzyDiscoveryIgnore: 'aggressive'});
await source.discover([...normalizedPlays, interimPlay]);
const discovered = await source.discover([duringPlay]);
@@ -525,7 +527,7 @@ describe('Deezer Internal Source', function() {
const fuzzyPlay = clone(targetPlay);
fuzzyPlay.data.playDate = targetPlay.data.playDate.add(targetPlay.data.duration + 39, 's');
await using source = generateDeezerSource({fuzzyDiscoveryIgnore: 'aggressive'});
const source = generateDeezerSource({fuzzyDiscoveryIgnore: 'aggressive'});
await source.discover([...normalizedPlays, interimPlay]);
const discovered = await source.discover([fuzzyPlay]);
@@ -538,7 +540,7 @@ describe('Deezer Internal Source', function() {
const fuzzyPlay = clone(targetPlay);
fuzzyPlay.data.playDate = targetPlay.data.playDate.add(targetPlay.data.duration, 's');
await using source = generateDeezerSource({fuzzyDiscoveryIgnore: 'aggressive'});
const source = generateDeezerSource({fuzzyDiscoveryIgnore: 'aggressive'});
await source.discover(normalizedPlays);
const discovered = await source.discover([fuzzyPlay]);
@@ -552,7 +554,7 @@ describe('Deezer Internal Source', function() {
fuzzyPlay.data.playDate = targetPlay.data.playDate.add(targetPlay.data.duration, 's');
const morePlays = normalizePlays([...normalizedPlays, fuzzyPlay, ...generatePlays(2)], {initialDate: firstPlayDate});
await using source = generateDeezerSource({fuzzyDiscoveryIgnore: 'aggressive'});
const source = generateDeezerSource({fuzzyDiscoveryIgnore: 'aggressive'});
const discovered = await source.discover(morePlays);
expect(discovered.length).to.eq(morePlays.length - 1);
+2 -2
View File
@@ -20,11 +20,10 @@ export const createGetScrobblesForTimeRangeFunc = <T extends PaginatedTimeRangeS
const logger = childLogger(pLogger, ['Pagination']);
const reqLabel = () => `Request ${requestCount}`;
const reqLogger = childLogger(logger, [reqLabel]);
let plays: PlayObject[] = [];
if (hasPagelessTimeRangeListens(fetcher)) {
return async (opts: PaginatedTimeRangeCommonOptions): Promise<PlayObject[]> => {
let plays: PlayObject[] = [];
requestCount = 0;
let more = true;
let currOpts = { ...opts };
@@ -108,6 +107,7 @@ export const createGetScrobblesForTimeRangeFunc = <T extends PaginatedTimeRangeS
}
} else if (hasPaginatedTimeRangeListens(fetcher)) {
return async (opts: PaginatedListensTimeRangeOptions): Promise<PlayObject[]> => {
let plays: PlayObject[] = [];
requestCount = 0;
let more = true;
let currOpts: PaginatedListensTimeRangeOptions = opts;
+1
View File
@@ -0,0 +1 @@
/// <reference types="@vitest/browser-playwright" />