Send the play queue ID to the server
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.
This commit is contained in:
1 parent
b3ffef9fd9
commit
f26438abe0
2 files changed
+47
-1
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 |
|
||||
|---|---|
|
||||
| `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. |
|
||||
|
||||
For `npm run serve`, create `public/config.js` (ignored by git):
|
||||
|
||||
+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).
|
||||
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;
|
||||
@@ -45,9 +74,26 @@ 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) => {
|
||||
|
||||
Reference in new issue
Block a user