Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
36329ca5cc | ||
|
|
9c72f89703 | ||
|
|
f26438abe0 |
No files matched your search
@@ -25,7 +25,7 @@ The Docker image writes `config.js` from `public/config.template.js` when the co
|
|||||||
|
|
||||||
| Variable | Description |
|
| Variable | Description |
|
||||||
|---|---|
|
|---|---|
|
||||||
| `API_HOSTNAME` | URL of the MusicServer, for example `https://music.example.com`. Empty to use the same host as the frontend. |
|
| `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. |
|
| `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):
|
For `npm run serve`, create `public/config.js` (ignored by git):
|
||||||
|
|||||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "music-randomizer-vue",
|
"name": "music-randomizer-vue",
|
||||||
"version": "1.2.0",
|
"version": "1.3.0",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "music-randomizer-vue",
|
"name": "music-randomizer-vue",
|
||||||
"version": "1.2.0",
|
"version": "1.3.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"axios": "^1.7.2",
|
"axios": "^1.7.2",
|
||||||
"core-js": "^3.37.1",
|
"core-js": "^3.37.1",
|
||||||
|
|||||||
+46
-44
@@ -1,55 +1,57 @@
|
|||||||
{
|
{
|
||||||
"name": "music-randomizer-vue",
|
"name": "music-randomizer-vue",
|
||||||
"version": "1.2.0",
|
"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",
|
||||||
"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"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@vue/cli-plugin-babel": "^5.0.8",
|
"@vue/cli-plugin-babel": "^5.0.8",
|
||||||
"@vue/cli-plugin-eslint": "^5.0.8",
|
"@vue/cli-plugin-eslint": "^5.0.8",
|
||||||
"@vue/cli-service": "^5.0.8",
|
"@vue/cli-service": "^5.0.8",
|
||||||
"@vue/compiler-sfc": "^3.4.31",
|
"@vue/compiler-sfc": "^3.4.31",
|
||||||
"@babel/eslint-parser": "^7.25.1",
|
"@babel/eslint-parser": "^7.25.1",
|
||||||
"eslint": "^8.57.0",
|
"eslint": "^8.57.0",
|
||||||
"eslint-plugin-vue": "^9.27.0"
|
"eslint-plugin-vue": "^9.27.0"
|
||||||
},
|
},
|
||||||
"eslintConfig": {
|
"eslintConfig": {
|
||||||
"root": true,
|
"root": true,
|
||||||
"env": {
|
"env": {
|
||||||
"node": true
|
"node": true
|
||||||
},
|
},
|
||||||
"extends": [
|
"extends": [
|
||||||
"plugin:vue/vue3-essential",
|
"plugin:vue/vue3-essential",
|
||||||
"eslint:recommended"
|
"eslint:recommended"
|
||||||
],
|
],
|
||||||
"parserOptions": {
|
"parserOptions": {
|
||||||
"parser": "@babel/eslint-parser",
|
"parser": "@babel/eslint-parser",
|
||||||
"requireConfigFile": false,
|
"requireConfigFile": false,
|
||||||
"ecmaVersion": 2020,
|
"ecmaVersion": 2020,
|
||||||
"sourceType": "module",
|
"sourceType": "module",
|
||||||
"ecmaFeatures": {
|
"ecmaFeatures": {
|
||||||
"globalReturn": false,
|
"globalReturn": false,
|
||||||
"impliedStrict": true,
|
"impliedStrict": true,
|
||||||
"jsx": true
|
"jsx": true
|
||||||
},
|
},
|
||||||
"babelOptions": {
|
"babelOptions": {
|
||||||
"presets": ["@babel/preset-env"]
|
"presets": [
|
||||||
}
|
"@babel/preset-env"
|
||||||
},
|
]
|
||||||
"rules": {
|
}
|
||||||
"no-debugger": 1
|
},
|
||||||
}
|
"rules": {
|
||||||
},
|
"no-debugger": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
"browserslist": [
|
"browserslist": [
|
||||||
"> 1%",
|
"> 1%",
|
||||||
"last 2 versions",
|
"last 2 versions",
|
||||||
|
|||||||
+46
@@ -12,6 +12,35 @@ export const api = axios.create({ baseURL: `${hostname}/api/v1` });
|
|||||||
// Client for the server routes outside /api/v1 (the /find search).
|
// Client for the server routes outside /api/v1 (the /find search).
|
||||||
export const legacyApi = axios.create({ baseURL: hostname });
|
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 token = null;
|
||||||
let expiresAt = 0;
|
let expiresAt = 0;
|
||||||
let pendingToken = null;
|
let pendingToken = null;
|
||||||
@@ -45,9 +74,26 @@ api.interceptors.request.use(async (request) => {
|
|||||||
if (!isTokenRequest(request)) {
|
if (!isTokenRequest(request)) {
|
||||||
request.headers.Authorization = `Bearer ${await getToken()}`;
|
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;
|
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):
|
// On 401 the token is no longer valid (for example after a server restart):
|
||||||
// get a new one and retry the request once.
|
// get a new one and retry the request once.
|
||||||
api.interceptors.response.use(undefined, (error) => {
|
api.interceptors.response.use(undefined, (error) => {
|
||||||
|
|||||||
@@ -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: [],
|
||||||
@@ -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: [],
|
||||||
|
|||||||
Reference in new issue
Block a user