7 Commits
Author SHA1 Message Date
SansanandClaude Opus 5.5 36329ca5cc Release 1.3.0
Build and Release / build (push) Successful in 31s
Build and Release / release (push) Successful in 3m22s
- Send the play queue ID (X-Queue-Id) with every request, and keep the
  queue across page reloads.
- Bypass browser caches for random songs.
- Start the sync time at 00:00, to match the server's minutes and seconds.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 22:10:34 +02:00
SansanandClaude Opus 5.5 9c72f89703 Start the sync time at 00:00
Build and Release / build (push) Successful in 35s
Build and Release / release (push) Successful in 6s
The server now sends sync times as minutes and seconds, so the placeholder
shown before the first progress update matches.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 22:09:32 +02:00
Sansan f26438abe0 Send the play queue ID to the server
Build and Release / build (push) Successful in 33s
Build and Release / release (push) Successful in 9s
The server keeps a play queue per client, identified by the X-Queue-Id
header. Send the stored ID with every request and save the ID from every
response, so the client switches to the new queue after a reset or a sync.
The ID is kept in localStorage so a page reload keeps the same queue.

Also send Cache-Control: no-cache, so browsers that cached a random song
before the server sent no-store fetch a new one.
2026-10-07 20:09:31 +02:00
Sansan b3ffef9fd9 Use /api/v1 with token authentication
Build and Release / build (push) Successful in 50s
Build and Release / release (push) Successful in 3m18s
All server calls go through a shared axios instance (src/api.js) that gets a token
with API_PASSWORD from the runtime config, renews it before it expires and retries
once on 401. Character images are loaded with the token as blobs. Removes the
unused express and cors dependencies and bumps the version to 1.2.0.

Closes #2
2026-10-06 19:01:13 +02:00
Sansan d061e02d81 Always search with /find, which now includes fuzzy matches
Closes #3
2026-10-06 18:57:02 +02:00
Sansan 2e7be01487 Start sync with POST and show progress when a sync is running
Closes #1
2026-10-06 18:57:02 +02:00
Sansan f972b579fc Release automatically when package.json version changes
Build and Release / build (push) Successful in 23s
Build and Release / release (push) Successful in 6s
Build and release now run in one workflow on push to main: after the
build passes, if no tag exists for the package.json version, the image
is pushed and the tag and Gitea release are created. Running them in
sequence avoids the action-cache race between the separate build and
tag-triggered release workflows.
2026-10-04 21:56:15 +02:00
16 changed files with 368 additions and 236 deletions

No files matched your search

+76 -1
View File
@@ -1,4 +1,10 @@
name: Build and Test name: Build and Release
# Releases are driven by the version in package.json: bump it (e.g.
# `npm version patch --no-git-tag-version`) and push to main. If no tag
# v<version> exists yet, the release job pushes the Docker image and creates
# the tag and Gitea release. Build and release run in sequence in one
# workflow, so they never race for the runner's action cache.
on: on:
push: push:
@@ -31,3 +37,72 @@ jobs:
push: false push: false
cache-from: type=gha cache-from: type=gha
cache-to: type=gha,mode=max cache-to: type=gha,mode=max
release:
needs: build
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
steps:
- uses: https://github.com/actions/checkout@v4
with:
fetch-depth: 0
fetch-tags: true
- name: Check version
id: version
run: |
VERSION="v$(node -p "require('./package.json').version")"
echo "tag=$VERSION" >> "$GITHUB_OUTPUT"
if git rev-parse -q --verify "refs/tags/$VERSION" >/dev/null; then
echo "$VERSION already released, nothing to do"
echo "release=false" >> "$GITHUB_OUTPUT"
else
echo "Releasing $VERSION"
echo "release=true" >> "$GITHUB_OUTPUT"
fi
- name: Set up QEMU
if: steps.version.outputs.release == 'true'
uses: https://github.com/docker/setup-qemu-action@v3
- name: Set up Docker Buildx
if: steps.version.outputs.release == 'true'
uses: https://github.com/docker/setup-buildx-action@v3
with:
config-inline: |
[registry."gitea.sanplex.xyz/sansan"]
http = true
insecure = true
- name: Login to Gitea Container Registry
if: steps.version.outputs.release == 'true'
uses: docker/login-action@v3
with:
registry: gitea.sanplex.xyz
username: sansan
password: ${{ secrets.TOKEN }}
- name: Build and push multi-arch image
if: steps.version.outputs.release == 'true'
uses: docker/build-push-action@v5
with:
context: .
file: ./Dockerfile
platforms: linux/amd64,linux/arm64
push: true
tags: |
gitea.sanplex.xyz/sansan/musicfrontend:${{ steps.version.outputs.tag }}
gitea.sanplex.xyz/sansan/musicfrontend:latest
cache-from: type=gha
cache-to: type=gha,mode=max
- name: Create Release
if: steps.version.outputs.release == 'true'
uses: akkuman/gitea-release-action@v1
env:
NODE_OPTIONS: '--experimental-fetch' # if nodejs < 18
with:
tag_name: ${{ steps.version.outputs.tag }}
target_commitish: ${{ github.sha }}
body: |
Docker image: `gitea.sanplex.xyz/sansan/musicfrontend:${{ steps.version.outputs.tag }}`
-32
View File
@@ -1,32 +0,0 @@
name: Publish
on:
push:
branches:
- '*.*'
jobs:
publish:
runs-on: ubuntu-latest
steps:
- uses: https://github.com/actions/checkout@v4
- name: Set up Docker Buildx
uses: https://github.com/docker/setup-buildx-action@v3
with:
config-inline: |
[registry."gitea.sanplex.xyz/sansan"]
http = true
insecure = true
- name: Login to Gitea
uses: docker/login-action@v2
with:
registry: gitea.sanplex.xyz
username: ${{ github.repository_owner }}
password: ${{ secrets.TOKEN }}
- name: Build and push Docker image
uses: https://github.com/docker/build-push-action@v5
with:
context: .
file: ./Dockerfile
push: true
tags: "gitea.sanplex.xyz/sansan/musicfrontend:${{gitea.ref_name}}, gitea.sanplex.xyz/sansan/musicfrontend:latest"
-51
View File
@@ -1,51 +0,0 @@
name: Release Docker Image
on:
push:
tags:
- 'v*'
jobs:
build-and-push:
runs-on: ubuntu-latest
steps:
- uses: https://github.com/actions/checkout@v4
- name: Set up QEMU
uses: https://github.com/docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: https://github.com/docker/setup-buildx-action@v3
with:
config-inline: |
[registry."gitea.sanplex.xyz/sansan"]
http = true
insecure = true
- name: Login to Gitea Container Registry
uses: docker/login-action@v3
with:
registry: gitea.sanplex.xyz
username: sansan
password: ${{ secrets.TOKEN }}
- name: Build and push multi-arch image
uses: docker/build-push-action@v5
with:
context: .
file: ./Dockerfile
platforms: linux/amd64,linux/arm64
push: true
tags: |
gitea.sanplex.xyz/sansan/musicfrontend:${{ gitea.ref_name }}
gitea.sanplex.xyz/sansan/musicfrontend:latest
cache-from: type=gha
cache-to: type=gha,mode=max
- name: Create Release
uses: akkuman/gitea-release-action@v1
env:
NODE_OPTIONS: '--experimental-fetch' # if nodejs < 18
with:
body: |
Docker image: `gitea.sanplex.xyz/sansan/musicfrontend:${{ gitea.ref_name }}`
+3
View File
@@ -19,3 +19,6 @@ pnpm-debug.log*
*.njsproj *.njsproj
*.sln *.sln
*.sw? *.sw?
# Local runtime config for npm run serve
/public/config.js
+15
View File
@@ -20,5 +20,20 @@ npm run build
npm run lint npm run lint
``` ```
## Configuration
The Docker image writes `config.js` from `public/config.template.js` when the container starts, using these environment variables:
| Variable | Description |
|---|---|
| `API_HOSTNAME` | URL of the MusicServer, for example `https://music.example.com`. Empty to use the same host as the frontend. On another host, add the frontend's origin to the server's `CORS_ALLOWED_ORIGINS`. |
| `API_PASSWORD` | The MusicServer's `API_PASSWORD`, used to get an API token. Empty if the server has no password. Anyone who can open the frontend can read it. |
For `npm run serve`, create `public/config.js` (ignored by git):
```js
window.__RUNTIME_CONFIG__ = { API_HOSTNAME: 'http://localhost:8080', API_PASSWORD: '' };
```
When the frontend is served by the MusicServer image, the server provides `config.js` with its own `API_PASSWORD`.
### Customize configuration ### Customize configuration
See [Configuration Reference](https://cli.vuejs.org/config/). See [Configuration Reference](https://cli.vuejs.org/config/).
+62 -21
View File
@@ -1,17 +1,15 @@
{ {
"name": "music-randomizer-vue", "name": "music-randomizer-vue",
"version": "1.1.2", "version": "1.3.0",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "music-randomizer-vue", "name": "music-randomizer-vue",
"version": "1.1.2", "version": "1.3.0",
"dependencies": { "dependencies": {
"axios": "^1.7.2", "axios": "^1.7.2",
"core-js": "^3.37.1", "core-js": "^3.37.1",
"cors": "^2.8.5",
"express": "^4.19.2",
"vue": "^3.4.31", "vue": "^3.4.31",
"vue-axios": "^3.5.2", "vue-axios": "^3.5.2",
"vuex": "^4.1.0" "vuex": "^4.1.0"
@@ -3185,6 +3183,7 @@
"version": "1.3.8", "version": "1.3.8",
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
"integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"mime-types": "~2.1.34", "mime-types": "~2.1.34",
@@ -3436,6 +3435,7 @@
"version": "1.1.1", "version": "1.1.1",
"resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
"integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==",
"dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/array-union": { "node_modules/array-union": {
@@ -3701,6 +3701,7 @@
"version": "1.20.5", "version": "1.20.5",
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.5.tgz", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.5.tgz",
"integrity": "sha512-3grm+/2tUOvu2cjJkvsIxrv/wVpfXQW4PsQHYm7yk4vfpu7Ekl6nEsYBoJUL6qDwZUx8wUhQ8tR2qz+ad9c9OA==", "integrity": "sha512-3grm+/2tUOvu2cjJkvsIxrv/wVpfXQW4PsQHYm7yk4vfpu7Ekl6nEsYBoJUL6qDwZUx8wUhQ8tR2qz+ad9c9OA==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"bytes": "~3.1.2", "bytes": "~3.1.2",
@@ -3725,6 +3726,7 @@
"version": "2.6.9", "version": "2.6.9",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"ms": "2.0.0" "ms": "2.0.0"
@@ -3734,6 +3736,7 @@
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
"dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/bonjour-service": { "node_modules/bonjour-service": {
@@ -3848,6 +3851,7 @@
"version": "3.1.2", "version": "3.1.2",
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
"integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">= 0.8" "node": ">= 0.8"
@@ -3889,6 +3893,7 @@
"version": "1.0.4", "version": "1.0.4",
"resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz",
"integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"call-bind-apply-helpers": "^1.0.2", "call-bind-apply-helpers": "^1.0.2",
@@ -4332,6 +4337,7 @@
"version": "0.5.4", "version": "0.5.4",
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
"integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"safe-buffer": "5.2.1" "safe-buffer": "5.2.1"
@@ -4344,6 +4350,7 @@
"version": "1.0.5", "version": "1.0.5",
"resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
"integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">= 0.6" "node": ">= 0.6"
@@ -4360,6 +4367,7 @@
"version": "0.7.2", "version": "0.7.2",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
"integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">= 0.6" "node": ">= 0.6"
@@ -4369,6 +4377,7 @@
"version": "1.0.7", "version": "1.0.7",
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz", "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz",
"integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==", "integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==",
"dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/copy-webpack-plugin": { "node_modules/copy-webpack-plugin": {
@@ -4447,23 +4456,6 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/cors": {
"version": "2.8.6",
"resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz",
"integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==",
"license": "MIT",
"dependencies": {
"object-assign": "^4",
"vary": "^1"
},
"engines": {
"node": ">= 0.10"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/cosmiconfig": { "node_modules/cosmiconfig": {
"version": "7.1.0", "version": "7.1.0",
"resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz",
@@ -4998,6 +4990,7 @@
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
"integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">= 0.8" "node": ">= 0.8"
@@ -5007,6 +5000,7 @@
"version": "1.2.0", "version": "1.2.0",
"resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz",
"integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">= 0.8", "node": ">= 0.8",
@@ -5201,6 +5195,7 @@
"version": "1.1.1", "version": "1.1.1",
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
"integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==",
"dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/electron-to-chromium": { "node_modules/electron-to-chromium": {
@@ -5231,6 +5226,7 @@
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
"integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">= 0.8" "node": ">= 0.8"
@@ -5358,6 +5354,7 @@
"version": "1.0.3", "version": "1.0.3",
"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
"integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
"dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/escape-string-regexp": { "node_modules/escape-string-regexp": {
@@ -5767,6 +5764,7 @@
"version": "1.8.1", "version": "1.8.1",
"resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
"integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">= 0.6" "node": ">= 0.6"
@@ -5895,6 +5893,7 @@
"version": "4.22.2", "version": "4.22.2",
"resolved": "https://registry.npmjs.org/express/-/express-4.22.2.tgz", "resolved": "https://registry.npmjs.org/express/-/express-4.22.2.tgz",
"integrity": "sha512-IuL+Elrou2ZvCFHs18/CIzy2Nzvo25nZ1/D2eIZlz7c+QUayAcYoiM2BthCjs+EBHVpjYjcuLDAiCWgeIX3X1Q==", "integrity": "sha512-IuL+Elrou2ZvCFHs18/CIzy2Nzvo25nZ1/D2eIZlz7c+QUayAcYoiM2BthCjs+EBHVpjYjcuLDAiCWgeIX3X1Q==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"accepts": "~1.3.8", "accepts": "~1.3.8",
@@ -5941,6 +5940,7 @@
"version": "2.6.9", "version": "2.6.9",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"ms": "2.0.0" "ms": "2.0.0"
@@ -5950,6 +5950,7 @@
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
"dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/fast-deep-equal": { "node_modules/fast-deep-equal": {
@@ -6096,6 +6097,7 @@
"version": "1.3.2", "version": "1.3.2",
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.2.tgz", "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.2.tgz",
"integrity": "sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==", "integrity": "sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"debug": "2.6.9", "debug": "2.6.9",
@@ -6114,6 +6116,7 @@
"version": "2.6.9", "version": "2.6.9",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"ms": "2.0.0" "ms": "2.0.0"
@@ -6123,6 +6126,7 @@
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
"dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/find-cache-dir": { "node_modules/find-cache-dir": {
@@ -6232,6 +6236,7 @@
"version": "0.2.0", "version": "0.2.0",
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
"integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">= 0.6" "node": ">= 0.6"
@@ -6255,6 +6260,7 @@
"version": "0.5.2", "version": "0.5.2",
"resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
"integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">= 0.6" "node": ">= 0.6"
@@ -6784,6 +6790,7 @@
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz",
"integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"depd": "~2.0.0", "depd": "~2.0.0",
@@ -6874,6 +6881,7 @@
"version": "0.4.24", "version": "0.4.24",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
"integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"safer-buffer": ">= 2.1.2 < 3" "safer-buffer": ">= 2.1.2 < 3"
@@ -6969,12 +6977,14 @@
"version": "2.0.4", "version": "2.0.4",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
"dev": true,
"license": "ISC" "license": "ISC"
}, },
"node_modules/ipaddr.js": { "node_modules/ipaddr.js": {
"version": "1.9.1", "version": "1.9.1",
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
"integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">= 0.10" "node": ">= 0.10"
@@ -7781,6 +7791,7 @@
"version": "0.3.0", "version": "0.3.0",
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
"integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">= 0.6" "node": ">= 0.6"
@@ -7803,6 +7814,7 @@
"version": "1.0.3", "version": "1.0.3",
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz",
"integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==",
"dev": true,
"license": "MIT", "license": "MIT",
"funding": { "funding": {
"url": "https://github.com/sponsors/sindresorhus" "url": "https://github.com/sponsors/sindresorhus"
@@ -7839,6 +7851,7 @@
"version": "1.1.2", "version": "1.1.2",
"resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
"integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">= 0.6" "node": ">= 0.6"
@@ -7862,6 +7875,7 @@
"version": "1.6.0", "version": "1.6.0",
"resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
"integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
"dev": true,
"license": "MIT", "license": "MIT",
"bin": { "bin": {
"mime": "cli.js" "mime": "cli.js"
@@ -8107,6 +8121,7 @@
"version": "0.6.3", "version": "0.6.3",
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
"integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">= 0.6" "node": ">= 0.6"
@@ -8264,6 +8279,7 @@
"version": "4.1.1", "version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=0.10.0" "node": ">=0.10.0"
@@ -8273,6 +8289,7 @@
"version": "1.13.4", "version": "1.13.4",
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
"integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">= 0.4" "node": ">= 0.4"
@@ -8323,6 +8340,7 @@
"version": "2.4.1", "version": "2.4.1",
"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
"integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"ee-first": "1.1.1" "ee-first": "1.1.1"
@@ -8591,6 +8609,7 @@
"version": "1.3.3", "version": "1.3.3",
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
"integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">= 0.8" "node": ">= 0.8"
@@ -8648,6 +8667,7 @@
"version": "0.1.13", "version": "0.1.13",
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.13.tgz", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.13.tgz",
"integrity": "sha512-A/AGNMFN3c8bOlvV9RreMdrv7jsmF9XIfDeCd87+I8RNg6s78BhJxMu69NEMHBSJFxKidViTEdruRwEk/WIKqA==", "integrity": "sha512-A/AGNMFN3c8bOlvV9RreMdrv7jsmF9XIfDeCd87+I8RNg6s78BhJxMu69NEMHBSJFxKidViTEdruRwEk/WIKqA==",
"dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/path-type": { "node_modules/path-type": {
@@ -9515,6 +9535,7 @@
"version": "2.0.7", "version": "2.0.7",
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
"integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"forwarded": "0.2.0", "forwarded": "0.2.0",
@@ -9565,6 +9586,7 @@
"version": "6.15.2", "version": "6.15.2",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.15.2.tgz", "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.2.tgz",
"integrity": "sha512-Rzq0KEyX/w/tEybncDgdkZrJgVUsUMk3xjh3t5bv3S1HTAtg+uOYt72+ZfwiQwKdysThkTBdL/rTi6HDmX9Ddw==", "integrity": "sha512-Rzq0KEyX/w/tEybncDgdkZrJgVUsUMk3xjh3t5bv3S1HTAtg+uOYt72+ZfwiQwKdysThkTBdL/rTi6HDmX9Ddw==",
"dev": true,
"license": "BSD-3-Clause", "license": "BSD-3-Clause",
"dependencies": { "dependencies": {
"side-channel": "^1.1.0" "side-channel": "^1.1.0"
@@ -9611,6 +9633,7 @@
"version": "1.2.1", "version": "1.2.1",
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
"integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">= 0.6" "node": ">= 0.6"
@@ -9620,6 +9643,7 @@
"version": "2.5.3", "version": "2.5.3",
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz", "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz",
"integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==", "integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"bytes": "~3.1.2", "bytes": "~3.1.2",
@@ -9990,6 +10014,7 @@
"version": "5.2.1", "version": "5.2.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
"dev": true,
"funding": [ "funding": [
{ {
"type": "github", "type": "github",
@@ -10010,6 +10035,7 @@
"version": "2.1.2", "version": "2.1.2",
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
"dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/sax": { "node_modules/sax": {
@@ -10076,6 +10102,7 @@
"version": "0.19.2", "version": "0.19.2",
"resolved": "https://registry.npmjs.org/send/-/send-0.19.2.tgz", "resolved": "https://registry.npmjs.org/send/-/send-0.19.2.tgz",
"integrity": "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==", "integrity": "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"debug": "2.6.9", "debug": "2.6.9",
@@ -10100,6 +10127,7 @@
"version": "2.6.9", "version": "2.6.9",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"ms": "2.0.0" "ms": "2.0.0"
@@ -10109,6 +10137,7 @@
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
"dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/serialize-javascript": { "node_modules/serialize-javascript": {
@@ -10202,6 +10231,7 @@
"version": "1.16.3", "version": "1.16.3",
"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.3.tgz", "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.3.tgz",
"integrity": "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==", "integrity": "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"encodeurl": "~2.0.0", "encodeurl": "~2.0.0",
@@ -10235,6 +10265,7 @@
"version": "1.2.0", "version": "1.2.0",
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
"integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==",
"dev": true,
"license": "ISC" "license": "ISC"
}, },
"node_modules/shallow-clone": { "node_modules/shallow-clone": {
@@ -10290,6 +10321,7 @@
"version": "1.1.0", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz",
"integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"es-errors": "^1.3.0", "es-errors": "^1.3.0",
@@ -10309,6 +10341,7 @@
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz",
"integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"es-errors": "^1.3.0", "es-errors": "^1.3.0",
@@ -10325,6 +10358,7 @@
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz",
"integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"call-bound": "^1.0.2", "call-bound": "^1.0.2",
@@ -10343,6 +10377,7 @@
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz",
"integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"call-bound": "^1.0.2", "call-bound": "^1.0.2",
@@ -10532,6 +10567,7 @@
"version": "2.0.2", "version": "2.0.2",
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz",
"integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">= 0.8" "node": ">= 0.8"
@@ -10969,6 +11005,7 @@
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
"integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=0.6" "node": ">=0.6"
@@ -11028,6 +11065,7 @@
"version": "1.6.18", "version": "1.6.18",
"resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
"integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"media-typer": "0.3.0", "media-typer": "0.3.0",
@@ -11102,6 +11140,7 @@
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
"integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">= 0.8" "node": ">= 0.8"
@@ -11166,6 +11205,7 @@
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
"integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">= 0.4.0" "node": ">= 0.4.0"
@@ -11197,6 +11237,7 @@
"version": "1.1.2", "version": "1.1.2",
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
"integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">= 0.8" "node": ">= 0.8"
+46 -46
View File
@@ -1,57 +1,57 @@
{ {
"name": "music-randomizer-vue", "name": "music-randomizer-vue",
"version": "1.1.2", "version": "1.3.0",
"private": true, "private": true,
"scripts": { "scripts": {
"serve": "vue-cli-service serve", "serve": "vue-cli-service serve",
"build": "vue-cli-service build", "build": "vue-cli-service build",
"lint": "vue-cli-service lint" "lint": "vue-cli-service lint"
}, },
"dependencies": { "dependencies": {
"axios": "^1.7.2", "axios": "^1.7.2",
"core-js": "^3.37.1", "core-js": "^3.37.1",
"cors": "^2.8.5", "vue": "^3.4.31",
"express": "^4.19.2", "vue-axios": "^3.5.2",
"vue": "^3.4.31", "vuex": "^4.1.0"
"vue-axios": "^3.5.2", },
"vuex": "^4.1.0" "devDependencies": {
}, "@vue/cli-plugin-babel": "^5.0.8",
"devDependencies": { "@vue/cli-plugin-eslint": "^5.0.8",
"@vue/cli-plugin-babel": "^5.0.8", "@vue/cli-service": "^5.0.8",
"@vue/cli-plugin-eslint": "^5.0.8", "@vue/compiler-sfc": "^3.4.31",
"@vue/cli-service": "^5.0.8", "@babel/eslint-parser": "^7.25.1",
"@vue/compiler-sfc": "^3.4.31", "eslint": "^8.57.0",
"@babel/eslint-parser": "^7.25.1", "eslint-plugin-vue": "^9.27.0"
"eslint": "^8.57.0", },
"eslint-plugin-vue": "^9.27.0" "eslintConfig": {
}, "root": true,
"eslintConfig": { "env": {
"root": true, "node": true
"env": { },
"node": true "extends": [
}, "plugin:vue/vue3-essential",
"extends": [ "eslint:recommended"
"plugin:vue/vue3-essential", ],
"eslint:recommended" "parserOptions": {
], "parser": "@babel/eslint-parser",
"parserOptions": { "requireConfigFile": false,
"parser": "@babel/eslint-parser", "ecmaVersion": 2020,
"requireConfigFile": false, "sourceType": "module",
"ecmaVersion": 2020, "ecmaFeatures": {
"sourceType": "module", "globalReturn": false,
"ecmaFeatures": { "impliedStrict": true,
"globalReturn": false, "jsx": true
"impliedStrict": true, },
"jsx": true "babelOptions": {
}, "presets": [
"babelOptions": { "@babel/preset-env"
"presets": ["@babel/preset-env"] ]
} }
}, },
"rules": { "rules": {
"no-debugger": 1 "no-debugger": 1
} }
}, },
"browserslist": [ "browserslist": [
"> 1%", "> 1%",
"last 2 versions", "last 2 versions",
+2 -1
View File
@@ -1,3 +1,4 @@
window.__RUNTIME_CONFIG__ = { window.__RUNTIME_CONFIG__ = {
API_HOSTNAME: '${API_HOSTNAME}' API_HOSTNAME: '${API_HOSTNAME}',
API_PASSWORD: '${API_PASSWORD}'
}; };
+107
View File
@@ -0,0 +1,107 @@
import axios from "axios";
const config = window.__RUNTIME_CONFIG__ || {};
const hostname = config.API_HOSTNAME || "";
// Request a new token this long before the current one expires
const EXPIRY_MARGIN_MS = 5 * 60 * 1000;
// Client for the MusicServer /api/v1 endpoints, with token authentication.
export const api = axios.create({ baseURL: `${hostname}/api/v1` });
// Client for the server routes outside /api/v1 (the /find search).
export const legacyApi = axios.create({ baseURL: hostname });
// The server keeps a play queue per client, identified by this header. Every music
// response returns the queue's ID (a new one after a reset or a sync), and it is sent
// back with every request. It is stored so a page reload keeps the same queue.
const QUEUE_HEADER = "X-Queue-Id";
const QUEUE_STORAGE_KEY = "queueId";
let queueId = readQueueId();
function readQueueId() {
try {
return localStorage.getItem(QUEUE_STORAGE_KEY);
} catch {
return null;
}
}
function rememberQueueId(response) {
const id = response?.headers?.[QUEUE_HEADER.toLowerCase()];
if (!id || id === queueId) {
return;
}
queueId = id;
try {
localStorage.setItem(QUEUE_STORAGE_KEY, id);
} catch {
// Without storage the queue still works until the page is reloaded.
}
}
let token = null;
let expiresAt = 0;
let pendingToken = null;
function isTokenRequest(request) {
return request.url === "/token";
}
// getToken returns a valid token, requesting a new one when needed.
// Concurrent callers share one request, since token creation is rate limited.
function getToken() {
if (token && Date.now() < expiresAt - EXPIRY_MARGIN_MS) {
return Promise.resolve(token);
}
if (!pendingToken) {
pendingToken = api
.post("/token", { password: config.API_PASSWORD || "", client_type: "web" })
.then((response) => {
token = response.data.token;
expiresAt = new Date(response.data.expires_at).getTime();
return token;
})
.finally(() => {
pendingToken = null;
});
}
return pendingToken;
}
api.interceptors.request.use(async (request) => {
if (!isTokenRequest(request)) {
request.headers.Authorization = `Bearer ${await getToken()}`;
}
if (queueId) {
request.headers[QUEUE_HEADER] = queueId;
}
// Random songs come from the same URL every time. Browsers that cached one before
// the server sent Cache-Control: no-store would otherwise replay it.
request.headers["Cache-Control"] = "no-cache";
return request;
});
api.interceptors.response.use(
(response) => {
rememberQueueId(response);
return response;
},
(error) => {
rememberQueueId(error.response);
return Promise.reject(error);
}
);
// On 401 the token is no longer valid (for example after a server restart):
// get a new one and retry the request once.
api.interceptors.response.use(undefined, (error) => {
const request = error.config;
if (error.response?.status !== 401 || !request || isTokenRequest(request) || request.retried) {
return Promise.reject(error);
}
request.retried = true;
token = null;
return api(request);
});
+1 -1
View File
@@ -92,7 +92,7 @@ export default {
reloadSoundtracks() { reloadSoundtracks() {
this.axios({ this.axios({
method: "get", method: "get",
url: `${window.__RUNTIME_CONFIG__.API_HOSTNAME}/music/all`, url: `/music/all`,
}) })
.then((response) => { .then((response) => {
this.allSoundtracksList = response.data; this.allSoundtracksList = response.data;
+2 -44
View File
@@ -5,16 +5,6 @@
<div class="modalWrapper"> <div class="modalWrapper">
<span class="closeModalImg" @click="closeModal">&times;</span> <span class="closeModalImg" @click="closeModal">&times;</span>
<h1>Search Soundtracks</h1> <h1>Search Soundtracks</h1>
<div class="searchTypeContainer">
<label class="radioLabel">
<input type="radio" v-model="searchType" value="fuzzy">
<span>Fuzzy Search</span>
</label>
<label class="radioLabel">
<input type="radio" v-model="searchType" value="exact">
<span>Exact Search</span>
</label>
</div>
<div class="searchContainer"> <div class="searchContainer">
<input <input
type="text" type="text"
@@ -48,7 +38,6 @@ export default {
results: [], results: [],
loading: false, loading: false,
searchTimeout: null, searchTimeout: null,
searchType: "fuzzy",
}; };
}, },
methods: { methods: {
@@ -56,7 +45,6 @@ export default {
this.show = true; this.show = true;
this.searchTerm = ""; this.searchTerm = "";
this.results = []; this.results = [];
this.searchType = "fuzzy";
}, },
closeModal() { closeModal() {
this.show = false; this.show = false;
@@ -85,10 +73,9 @@ export default {
} }
this.loading = true; this.loading = true;
try { try {
const endpoint = this.searchType === 'fuzzy' ? '/findfuzzy' : '/find'; const response = await this.legacyAxios({
const response = await this.axios({
method: "post", method: "post",
url: `${window.__RUNTIME_CONFIG__.API_HOSTNAME}${endpoint}`, url: "/find",
data: new URLSearchParams({ search_term: this.searchTerm }), data: new URLSearchParams({ search_term: this.searchTerm }),
headers: { headers: {
"Content-Type": "application/x-www-form-urlencoded", "Content-Type": "application/x-www-form-urlencoded",
@@ -126,26 +113,6 @@ export default {
</script> </script>
<style scoped> <style scoped>
.searchTypeContainer {
display: flex;
gap: 20px;
margin-top: 15px;
margin-bottom: 15px;
}
.radioLabel {
display: flex;
align-items: center;
gap: 8px;
cursor: pointer;
font-size: 1rem;
}
.radioLabel input[type="radio"] {
margin: 0;
accent-color: #ff9c00;
}
.searchContainer { .searchContainer {
width: 100%; width: 100%;
margin-top: 10px; margin-top: 10px;
@@ -207,15 +174,6 @@ export default {
} }
@media only screen and (max-width: 1000px) { @media only screen and (max-width: 1000px) {
.searchTypeContainer {
flex-wrap: wrap;
gap: 10px;
}
.radioLabel {
font-size: 0.9rem;
}
.searchInput { .searchInput {
font-size: 1rem; font-size: 1rem;
padding: 10px; padding: 10px;
+3 -3
View File
@@ -81,7 +81,7 @@ export default {
data() { data() {
return { return {
progress: 0, progress: 0,
timeSpent: "00:00:00", timeSpent: "00:00",
syncComplete: false, syncComplete: false,
syncData: { syncData: {
soundtracks_added: [], soundtracks_added: [],
@@ -130,7 +130,7 @@ export default {
try { try {
const response = await this.axios({ const response = await this.axios({
method: "get", method: "get",
url: `${window.__RUNTIME_CONFIG__.API_HOSTNAME}/sync/progress`, url: "/sync/progress",
}); });
const data = response.data; const data = response.data;
@@ -181,7 +181,7 @@ export default {
if (this.syncInProgress) { if (this.syncInProgress) {
// New sync in progress - start polling // New sync in progress - start polling
this.progress = 0; this.progress = 0;
this.timeSpent = "00:00:00"; this.timeSpent = "00:00";
this.syncComplete = false; this.syncComplete = false;
this.syncData = { this.syncData = {
soundtracks_added: [], soundtracks_added: [],
+31 -7
View File
@@ -38,6 +38,8 @@ export default {
show: false, show: false,
playerName: "", playerName: "",
characters: [], characters: [],
// Object URLs of the character images, by file name
imageUrls: {},
isLoading: false, isLoading: false,
}; };
}, },
@@ -55,12 +57,13 @@ export default {
try { try {
const response = await this.axios({ const response = await this.axios({
method: "get", method: "get",
url: `${window.__RUNTIME_CONFIG__.API_HOSTNAME}/characters`, url: "/character/list",
}); });
this.characters = response.data || []; this.characters = response.data || [];
await this.fetchImages();
} catch (error) { } catch (error) {
console.error("Failed to fetch characters:", error); console.error("Failed to fetch characters:", error);
// Fallback to hardcoded list if API fails // Fall back to the characters bundled with the frontend
this.characters = [ this.characters = [
"Adol.png", "Adol.png",
"Link.png", "Link.png",
@@ -76,24 +79,45 @@ export default {
"Raiden.png", "Raiden.png",
"PaperMario.png", "PaperMario.png",
]; ];
this.imageUrls = Object.fromEntries(
this.characters.map((character) => [character, `characters/${character}`])
);
} finally { } finally {
this.isLoading = false; this.isLoading = false;
} }
}, },
// Images need the token, which <img src> can't send, so they are
// downloaded once and shown through object URLs.
async fetchImages() {
await Promise.all(
this.characters
.filter((character) => !this.imageUrls[character])
.map(async (character) => {
try {
const response = await this.axios({
method: "get",
url: "/character",
params: { name: character },
responseType: "blob",
});
this.imageUrls[character] = URL.createObjectURL(response.data);
} catch (error) {
console.error(`Failed to fetch character ${character}:`, error);
}
})
);
},
getCharacterImageUrl(character) { getCharacterImageUrl(character) {
return `${window.__RUNTIME_CONFIG__.API_HOSTNAME}/character?name=${encodeURIComponent(character)}`; return this.imageUrls[character] || "characters/noCharacter.png";
}, },
getCharacterName(character) { getCharacterName(character) {
// Remove file extension // Remove file extension
return character.replace(/\.(png|jpg|jpeg|gif)$/i, ""); return character.replace(/\.(png|jpg|jpeg|gif)$/i, "");
}, },
chooseFighter(characterFilename) { chooseFighter(characterFilename) {
const apiHostname = window.__RUNTIME_CONFIG__.API_HOSTNAME;
const profile = `${apiHostname}/character?name=${encodeURIComponent(characterFilename)}`;
let payload = { let payload = {
playerName: this.playerName, playerName: this.playerName,
profile: profile, profile: this.getCharacterImageUrl(characterFilename),
}; };
this.$store.dispatch("changePlayerProfile", payload); this.$store.dispatch("changePlayerProfile", payload);
this.closeModal(); this.closeModal();
+11 -20
View File
@@ -46,25 +46,17 @@ export default {
}, },
async startSync() { async startSync() {
try { try {
// Start the sync await this.APIsyncSoundtracks();
const response = await this.APIsyncSoundtracks();
// Check if sync was actually started or if one is in progress
if (response && (response.status === 423 || (response.data && response.data.includes("in progress")))) {
// Sync is already in progress - show modal with polling
this.syncInProgress = true;
} else {
this.syncInProgress = true;
}
// Show the modal which will poll for progress
this.showSyncModal = true;
} catch (error) { } catch (error) {
console.error("Failed to start sync:", error); // 423 means a sync is already running: show its progress
// If error is 423, sync is already in progress if (!error.response || error.response.status !== 423) {
if (error.response && error.response.status === 423) { console.error("Failed to start sync:", error);
this.syncInProgress = true; return;
this.showSyncModal = true;
} }
} }
// Show the modal which will poll for progress
this.syncInProgress = true;
this.showSyncModal = true;
}, },
handleSyncButtonClick() { handleSyncButtonClick() {
// If we have a completed sync result, just show it // If we have a completed sync result, just show it
@@ -105,7 +97,7 @@ export default {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
this.axios({ this.axios({
method: "get", method: "get",
url: `${window.__RUNTIME_CONFIG__.API_HOSTNAME}/music/reset`, url: `/music/reset`,
}) })
.then(() => { .then(() => {
resolve(); resolve();
@@ -119,14 +111,13 @@ export default {
APIsyncSoundtracks() { APIsyncSoundtracks() {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
this.axios({ this.axios({
method: "get", method: "post",
url: `${window.__RUNTIME_CONFIG__.API_HOSTNAME}/sync`, url: "/sync",
}) })
.then((response) => { .then((response) => {
resolve(response); resolve(response);
}) })
.catch(function(error) { .catch(function(error) {
console.log(error);
reject(error); reject(error);
}); });
}); });
+7 -7
View File
@@ -167,7 +167,7 @@ export default {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
this.axios({ this.axios({
method: "get", method: "get",
url: `${window.__RUNTIME_CONFIG__.API_HOSTNAME}/music/rand`, url: `/music/rand`,
responseType: "blob", responseType: "blob",
onDownloadProgress: (progressEvent) => { onDownloadProgress: (progressEvent) => {
let percentCompleted = Math.round( let percentCompleted = Math.round(
@@ -197,7 +197,7 @@ export default {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
this.axios({ this.axios({
method: "get", method: "get",
url: `${window.__RUNTIME_CONFIG__.API_HOSTNAME}/music/rand/low`, url: `/music/rand/low`,
responseType: "blob", responseType: "blob",
onDownloadProgress: (progressEvent) => { onDownloadProgress: (progressEvent) => {
let percentCompleted = Math.round( let percentCompleted = Math.round(
@@ -227,7 +227,7 @@ export default {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
this.axios({ this.axios({
method: "get", method: "get",
url: `${window.__RUNTIME_CONFIG__.API_HOSTNAME}/music/addPlayed`, url: `/music/addPlayed`,
}) })
.then(() => { .then(() => {
resolve(false); resolve(false);
@@ -242,7 +242,7 @@ export default {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
this.axios({ this.axios({
method: "get", method: "get",
url: `${window.__RUNTIME_CONFIG__.API_HOSTNAME}/music/addQue`, url: `/music/addQue`,
}) })
.then(() => { .then(() => {
resolve(false); resolve(false);
@@ -257,7 +257,7 @@ export default {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
this.axios({ this.axios({
method: "get", method: "get",
url: `${window.__RUNTIME_CONFIG__.API_HOSTNAME}/music/info`, url: `/music/info`,
}) })
.then((response) => { .then((response) => {
let soundtrackInfoObject = { let soundtrackInfoObject = {
@@ -279,7 +279,7 @@ export default {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
this.axios({ this.axios({
method: "get", method: "get",
url: `${window.__RUNTIME_CONFIG__.API_HOSTNAME}/music/list`, url: `/music/list`,
}) })
.then((response) => { .then((response) => {
let tracklistArray = response.data; let tracklistArray = response.data;
@@ -296,7 +296,7 @@ export default {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
this.axios({ this.axios({
method: "get", method: "get",
url: `${window.__RUNTIME_CONFIG__.API_HOSTNAME}/music?song=${trackNumber}`, url: `/music?song=${trackNumber}`,
responseType: "blob", responseType: "blob",
}) })
.then((response) => { .then((response) => {
+2 -2
View File
@@ -1,8 +1,8 @@
import { createApp } from "vue"; import { createApp } from "vue";
import { createStore } from "vuex"; import { createStore } from "vuex";
import App from "./App.vue"; import App from "./App.vue";
import axios from "axios";
import VueAxios from "vue-axios"; import VueAxios from "vue-axios";
import { api, legacyApi } from "./api";
const store = createStore({ const store = createStore({
state() { state() {
@@ -256,6 +256,6 @@ const store = createStore({
const app = createApp(App); const app = createApp(App);
app.use(store); app.use(store);
app.use(VueAxios, axios); app.use(VueAxios, { axios: api, legacyAxios: legacyApi });
app.mount("#app"); app.mount("#app");