Merge branch 'benphelps:main' into main
This commit is contained in:
commit
331f31fc2b
6 changed files with 48 additions and 43 deletions
|
@ -10,7 +10,7 @@ const proxyName = "homebridgeProxyHandler";
|
||||||
const sessionTokenCacheKey = `${proxyName}__sessionToken`;
|
const sessionTokenCacheKey = `${proxyName}__sessionToken`;
|
||||||
const logger = createLogger(proxyName);
|
const logger = createLogger(proxyName);
|
||||||
|
|
||||||
async function login(widget) {
|
async function login(widget, service) {
|
||||||
const endpoint = "auth/login";
|
const endpoint = "auth/login";
|
||||||
const api = widgets?.[widget.type]?.api
|
const api = widgets?.[widget.type]?.api
|
||||||
const loginUrl = new URL(formatApiCall(api, { endpoint, ...widget }));
|
const loginUrl = new URL(formatApiCall(api, { endpoint, ...widget }));
|
||||||
|
@ -26,7 +26,7 @@ async function login(widget) {
|
||||||
try {
|
try {
|
||||||
const { access_token: accessToken, expires_in: expiresIn } = JSON.parse(data.toString());
|
const { access_token: accessToken, expires_in: expiresIn } = JSON.parse(data.toString());
|
||||||
|
|
||||||
cache.put(sessionTokenCacheKey, accessToken, (expiresIn * 1000) - 5 * 60 * 1000); // expiresIn (s) - 5m
|
cache.put(`${sessionTokenCacheKey}.${service}`, accessToken, (expiresIn * 1000) - 5 * 60 * 1000); // expiresIn (s) - 5m
|
||||||
return { accessToken };
|
return { accessToken };
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.error("Unable to login to Homebridge API: %s", e);
|
logger.error("Unable to login to Homebridge API: %s", e);
|
||||||
|
@ -35,10 +35,11 @@ async function login(widget) {
|
||||||
return { accessToken: false };
|
return { accessToken: false };
|
||||||
}
|
}
|
||||||
|
|
||||||
async function apiCall(widget, endpoint) {
|
async function apiCall(widget, endpoint, service) {
|
||||||
|
const key = `${sessionTokenCacheKey}.${service}`;
|
||||||
const headers = {
|
const headers = {
|
||||||
"content-type": "application/json",
|
"content-type": "application/json",
|
||||||
"Authorization": `Bearer ${cache.get(sessionTokenCacheKey)}`,
|
"Authorization": `Bearer ${cache.get(key)}`,
|
||||||
}
|
}
|
||||||
|
|
||||||
const url = new URL(formatApiCall(widgets[widget.type].api, { endpoint, ...widget }));
|
const url = new URL(formatApiCall(widgets[widget.type].api, { endpoint, ...widget }));
|
||||||
|
@ -51,7 +52,7 @@ async function apiCall(widget, endpoint) {
|
||||||
|
|
||||||
if (status === 401) {
|
if (status === 401) {
|
||||||
logger.debug("Homebridge API rejected the request, attempting to obtain new session token");
|
logger.debug("Homebridge API rejected the request, attempting to obtain new session token");
|
||||||
const { accessToken } = login(widget);
|
const { accessToken } = login(widget, service);
|
||||||
headers.Authorization = `Bearer ${accessToken}`;
|
headers.Authorization = `Bearer ${accessToken}`;
|
||||||
|
|
||||||
// retry the request, now with the new session token
|
// retry the request, now with the new session token
|
||||||
|
@ -83,14 +84,14 @@ export default async function homebridgeProxyHandler(req, res) {
|
||||||
return res.status(400).json({ error: "Invalid proxy service type" });
|
return res.status(400).json({ error: "Invalid proxy service type" });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!cache.get(sessionTokenCacheKey)) {
|
if (!cache.get(`${sessionTokenCacheKey}.${service}`)) {
|
||||||
await login(widget);
|
await login(widget, service);
|
||||||
}
|
}
|
||||||
|
|
||||||
const { data: statusData } = await apiCall(widget, "status/homebridge");
|
const { data: statusData } = await apiCall(widget, "status/homebridge", service);
|
||||||
const { data: versionData } = await apiCall(widget, "status/homebridge-version");
|
const { data: versionData } = await apiCall(widget, "status/homebridge-version", service);
|
||||||
const { data: childBridgeData } = await apiCall(widget, "status/homebridge/child-bridges");
|
const { data: childBridgeData } = await apiCall(widget, "status/homebridge/child-bridges", service);
|
||||||
const { data: pluginsData } = await apiCall(widget, "plugins");
|
const { data: pluginsData } = await apiCall(widget, "plugins", service);
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
status: statusData?.status,
|
status: statusData?.status,
|
||||||
|
|
|
@ -10,7 +10,7 @@ const proxyName = "npmProxyHandler";
|
||||||
const tokenCacheKey = `${proxyName}__token`;
|
const tokenCacheKey = `${proxyName}__token`;
|
||||||
const logger = createLogger(proxyName);
|
const logger = createLogger(proxyName);
|
||||||
|
|
||||||
async function login(loginUrl, username, password) {
|
async function login(loginUrl, username, password, service) {
|
||||||
const authResponse = await httpProxy(loginUrl, {
|
const authResponse = await httpProxy(loginUrl, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
body: JSON.stringify({ identity: username, secret: password }),
|
body: JSON.stringify({ identity: username, secret: password }),
|
||||||
|
@ -27,7 +27,7 @@ async function login(loginUrl, username, password) {
|
||||||
|
|
||||||
if (status === 200) {
|
if (status === 200) {
|
||||||
const expiration = new Date(data.expires) - Date.now();
|
const expiration = new Date(data.expires) - Date.now();
|
||||||
cache.put(tokenCacheKey, data.token, expiration - (5 * 60 * 1000)); // expiration -5 minutes
|
cache.put(`${tokenCacheKey}.${service}`, data.token, expiration - (5 * 60 * 1000)); // expiration -5 minutes
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.error(`Error ${status} logging into npm`, authResponse[2]);
|
logger.error(`Error ${status} logging into npm`, authResponse[2]);
|
||||||
|
@ -53,9 +53,9 @@ export default async function npmProxyHandler(req, res) {
|
||||||
let contentType;
|
let contentType;
|
||||||
let data;
|
let data;
|
||||||
|
|
||||||
let token = cache.get(tokenCacheKey);
|
let token = cache.get(`${tokenCacheKey}.${service}`);
|
||||||
if (!token) {
|
if (!token) {
|
||||||
[status, token] = await login(loginUrl, widget.username, widget.password);
|
[status, token] = await login(loginUrl, widget.username, widget.password, service);
|
||||||
if (status !== 200) {
|
if (status !== 200) {
|
||||||
logger.debug(`HTTTP ${status} logging into npm api: ${token}`);
|
logger.debug(`HTTTP ${status} logging into npm api: ${token}`);
|
||||||
return res.status(status).send(token);
|
return res.status(status).send(token);
|
||||||
|
@ -72,8 +72,8 @@ export default async function npmProxyHandler(req, res) {
|
||||||
|
|
||||||
if (status === 403) {
|
if (status === 403) {
|
||||||
logger.debug(`HTTTP ${status} retrieving data from npm api, logging in and trying again.`);
|
logger.debug(`HTTTP ${status} retrieving data from npm api, logging in and trying again.`);
|
||||||
cache.del(tokenCacheKey);
|
cache.del(`${tokenCacheKey}.${service}`);
|
||||||
[status, token] = await login(loginUrl, widget.username, widget.password);
|
[status, token] = await login(loginUrl, widget.username, widget.password, service);
|
||||||
|
|
||||||
if (status !== 200) {
|
if (status !== 200) {
|
||||||
logger.debug(`HTTTP ${status} logging into npm api: ${data}`);
|
logger.debug(`HTTTP ${status} logging into npm api: ${data}`);
|
||||||
|
|
|
@ -58,6 +58,9 @@ async function fetchFromPlexAPI(endpoint, widget) {
|
||||||
|
|
||||||
export default async function plexProxyHandler(req, res) {
|
export default async function plexProxyHandler(req, res) {
|
||||||
const widget = await getWidget(req);
|
const widget = await getWidget(req);
|
||||||
|
|
||||||
|
const { service } = req.query;
|
||||||
|
|
||||||
if (!widget) {
|
if (!widget) {
|
||||||
return res.status(400).json({ error: "Invalid proxy service type" });
|
return res.status(400).json({ error: "Invalid proxy service type" });
|
||||||
}
|
}
|
||||||
|
@ -74,23 +77,23 @@ export default async function plexProxyHandler(req, res) {
|
||||||
streams = apiData.MediaContainer._attributes.size;
|
streams = apiData.MediaContainer._attributes.size;
|
||||||
}
|
}
|
||||||
|
|
||||||
let libraries = cache.get(librariesCacheKey);
|
let libraries = cache.get(`${librariesCacheKey}.${service}`);
|
||||||
if (libraries === null) {
|
if (libraries === null) {
|
||||||
logger.debug("Getting libraries from Plex API");
|
logger.debug("Getting libraries from Plex API");
|
||||||
[status, apiData] = await fetchFromPlexAPI("/library/sections", widget);
|
[status, apiData] = await fetchFromPlexAPI("/library/sections", widget);
|
||||||
if (apiData && apiData.MediaContainer) {
|
if (apiData && apiData.MediaContainer) {
|
||||||
libraries = apiData.MediaContainer.Directory;
|
libraries = [].concat(apiData.MediaContainer.Directory);
|
||||||
cache.put(librariesCacheKey, libraries, 1000 * 60 * 60 * 6);
|
cache.put(`${librariesCacheKey}.${service}`, libraries, 1000 * 60 * 60 * 6);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let movies = cache.get(moviesCacheKey);
|
let movies = cache.get(`${moviesCacheKey}.${service}`);
|
||||||
let tv = cache.get(tvCacheKey);
|
let tv = cache.get(`${tvCacheKey}.${service}`);
|
||||||
if (movies === null || tv === null) {
|
if (movies === null || tv === null) {
|
||||||
movies = 0;
|
movies = 0;
|
||||||
tv = 0;
|
tv = 0;
|
||||||
logger.debug("Getting movie + tv counts from Plex API");
|
logger.debug("Getting movie + tv counts from Plex API");
|
||||||
libraries.filter(l => ["movie", "show"].includes(l._attributes.type)).forEach(async (library) => {
|
await libraries.filter(l => ["movie", "show"].includes(l._attributes.type)).forEach(async (library) => {
|
||||||
[status, apiData] = await fetchFromPlexAPI(`/library/sections/${library._attributes.key}/all`, widget);
|
[status, apiData] = await fetchFromPlexAPI(`/library/sections/${library._attributes.key}/all`, widget);
|
||||||
if (apiData && apiData.MediaContainer) {
|
if (apiData && apiData.MediaContainer) {
|
||||||
const size = parseInt(apiData.MediaContainer._attributes.size, 10);
|
const size = parseInt(apiData.MediaContainer._attributes.size, 10);
|
||||||
|
@ -100,8 +103,8 @@ export default async function plexProxyHandler(req, res) {
|
||||||
tv += size;
|
tv += size;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
cache.put(tvCacheKey, tv, 1000 * 60 * 10);
|
cache.put(`${tvCacheKey}.${service}`, tv, 1000 * 60 * 10);
|
||||||
cache.put(moviesCacheKey, movies, 1000 * 60 * 10);
|
cache.put(`${moviesCacheKey}.${service}`, movies, 1000 * 60 * 10);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -11,7 +11,7 @@ const logger = createLogger(proxyName);
|
||||||
const sessionCacheKey = `${proxyName}__sessionId`;
|
const sessionCacheKey = `${proxyName}__sessionId`;
|
||||||
const isNgCacheKey = `${proxyName}__isNg`;
|
const isNgCacheKey = `${proxyName}__isNg`;
|
||||||
|
|
||||||
async function fetchFromPyloadAPI(url, sessionId, params) {
|
async function fetchFromPyloadAPI(url, sessionId, params, service) {
|
||||||
const options = {
|
const options = {
|
||||||
body: params
|
body: params
|
||||||
? Object.keys(params)
|
? Object.keys(params)
|
||||||
|
@ -25,10 +25,10 @@ async function fetchFromPyloadAPI(url, sessionId, params) {
|
||||||
};
|
};
|
||||||
|
|
||||||
// see https://github.com/benphelps/homepage/issues/517
|
// see https://github.com/benphelps/homepage/issues/517
|
||||||
const isNg = cache.get(isNgCacheKey);
|
const isNg = cache.get(`${isNgCacheKey}.${service}`);
|
||||||
if (isNg && !params) {
|
if (isNg && !params) {
|
||||||
delete options.body;
|
delete options.body;
|
||||||
options.headers.Cookie = cache.get(sessionCacheKey);
|
options.headers.Cookie = cache.get(`${sessionCacheKey}.${service}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line no-unused-vars
|
// eslint-disable-next-line no-unused-vars
|
||||||
|
@ -43,19 +43,19 @@ async function fetchFromPyloadAPI(url, sessionId, params) {
|
||||||
return [status, returnData, responseHeaders];
|
return [status, returnData, responseHeaders];
|
||||||
}
|
}
|
||||||
|
|
||||||
async function login(loginUrl, username, password = '') {
|
async function login(loginUrl, service, username, password = '') {
|
||||||
const [status, sessionId, responseHeaders] = await fetchFromPyloadAPI(loginUrl, null, { username, password });
|
const [status, sessionId, responseHeaders] = await fetchFromPyloadAPI(loginUrl, null, { username, password }, service);
|
||||||
|
|
||||||
// this API actually returns status 200 even on login failure
|
// this API actually returns status 200 even on login failure
|
||||||
if (status !== 200 || sessionId === false) {
|
if (status !== 200 || sessionId === false) {
|
||||||
logger.error(`HTTP ${status} logging into Pyload API, returned: ${JSON.stringify(sessionId)}`);
|
logger.error(`HTTP ${status} logging into Pyload API, returned: ${JSON.stringify(sessionId)}`);
|
||||||
} else if (responseHeaders['set-cookie']?.join().includes('pyload_session')) {
|
} else if (responseHeaders['set-cookie']?.join().includes('pyload_session')) {
|
||||||
// Support pyload-ng, see https://github.com/benphelps/homepage/issues/517
|
// Support pyload-ng, see https://github.com/benphelps/homepage/issues/517
|
||||||
cache.put(isNgCacheKey, true);
|
cache.put(`${isNgCacheKey}.${service}`, true);
|
||||||
const sessionCookie = responseHeaders['set-cookie'][0];
|
const sessionCookie = responseHeaders['set-cookie'][0];
|
||||||
cache.put(sessionCacheKey, sessionCookie, 60 * 60 * 23 * 1000); // cache for 23h
|
cache.put(`${sessionCacheKey}.${service}`, sessionCookie, 60 * 60 * 23 * 1000); // cache for 23h
|
||||||
} else {
|
} else {
|
||||||
cache.put(sessionCacheKey, sessionId);
|
cache.put(`${sessionCacheKey}.${service}`, sessionId);
|
||||||
}
|
}
|
||||||
|
|
||||||
return sessionId;
|
return sessionId;
|
||||||
|
@ -72,14 +72,14 @@ export default async function pyloadProxyHandler(req, res) {
|
||||||
const url = new URL(formatApiCall(widgets[widget.type].api, { endpoint, ...widget }));
|
const url = new URL(formatApiCall(widgets[widget.type].api, { endpoint, ...widget }));
|
||||||
const loginUrl = `${widget.url}/api/login`;
|
const loginUrl = `${widget.url}/api/login`;
|
||||||
|
|
||||||
let sessionId = cache.get(sessionCacheKey) ?? await login(loginUrl, widget.username, widget.password);
|
let sessionId = cache.get(`${sessionCacheKey}.${service}`) ?? await login(loginUrl, service, widget.username, widget.password);
|
||||||
let [status, data] = await fetchFromPyloadAPI(url, sessionId);
|
let [status, data] = await fetchFromPyloadAPI(url, sessionId, null, service);
|
||||||
|
|
||||||
if (status === 403 || status === 401) {
|
if (status === 403 || status === 401) {
|
||||||
logger.info('Failed to retrieve data from Pyload API, trying to login again...');
|
logger.info('Failed to retrieve data from Pyload API, trying to login again...');
|
||||||
cache.del(sessionCacheKey);
|
cache.del(`${sessionCacheKey}.${service}`);
|
||||||
sessionId = await login(loginUrl, widget.username, widget.password);
|
sessionId = await login(loginUrl, service, widget.username, widget.password);
|
||||||
[status, data] = await fetchFromPyloadAPI(url, sessionId);
|
[status, data] = await fetchFromPyloadAPI(url, sessionId, null, service);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (data?.error || status !== 200) {
|
if (data?.error || status !== 200) {
|
||||||
|
|
|
@ -25,12 +25,12 @@ export default async function transmissionProxyHandler(req, res) {
|
||||||
return res.status(400).json({ error: "Invalid proxy service type" });
|
return res.status(400).json({ error: "Invalid proxy service type" });
|
||||||
}
|
}
|
||||||
|
|
||||||
let headers = cache.get(headerCacheKey);
|
let headers = cache.get(`${headerCacheKey}.${service}`);
|
||||||
if (!headers) {
|
if (!headers) {
|
||||||
headers = {
|
headers = {
|
||||||
"content-type": "application/json",
|
"content-type": "application/json",
|
||||||
}
|
}
|
||||||
cache.put(headerCacheKey, headers);
|
cache.put(`${headerCacheKey}.${service}`, headers);
|
||||||
}
|
}
|
||||||
|
|
||||||
const url = new URL(formatApiCall(widgets[widget.type].api, { endpoint, ...widget }));
|
const url = new URL(formatApiCall(widgets[widget.type].api, { endpoint, ...widget }));
|
||||||
|
@ -55,7 +55,7 @@ export default async function transmissionProxyHandler(req, res) {
|
||||||
if (status === 409) {
|
if (status === 409) {
|
||||||
logger.debug("Transmission is rejecting the request, but returning a CSRF token");
|
logger.debug("Transmission is rejecting the request, but returning a CSRF token");
|
||||||
headers[csrfHeaderName] = responseHeaders[csrfHeaderName];
|
headers[csrfHeaderName] = responseHeaders[csrfHeaderName];
|
||||||
cache.put(headerCacheKey, headers);
|
cache.put(`${headerCacheKey}.${service}`, headers);
|
||||||
|
|
||||||
// retry the request, now with the CSRF token
|
// retry the request, now with the CSRF token
|
||||||
[status, contentType, data, responseHeaders] = await httpProxy(url, {
|
[status, contentType, data, responseHeaders] = await httpProxy(url, {
|
||||||
|
|
|
@ -58,6 +58,7 @@ async function login(widget) {
|
||||||
|
|
||||||
export default async function unifiProxyHandler(req, res) {
|
export default async function unifiProxyHandler(req, res) {
|
||||||
const widget = await getWidget(req);
|
const widget = await getWidget(req);
|
||||||
|
const { service } = req.query;
|
||||||
if (!widget) {
|
if (!widget) {
|
||||||
return res.status(400).json({ error: "Invalid proxy service type" });
|
return res.status(400).json({ error: "Invalid proxy service type" });
|
||||||
}
|
}
|
||||||
|
@ -68,7 +69,7 @@ export default async function unifiProxyHandler(req, res) {
|
||||||
}
|
}
|
||||||
|
|
||||||
let [status, contentType, data, responseHeaders] = [];
|
let [status, contentType, data, responseHeaders] = [];
|
||||||
let prefix = cache.get(prefixCacheKey);
|
let prefix = cache.get(`${prefixCacheKey}.${service}`);
|
||||||
if (prefix === null) {
|
if (prefix === null) {
|
||||||
// auto detect if we're talking to a UDM Pro, and cache the result so that we
|
// auto detect if we're talking to a UDM Pro, and cache the result so that we
|
||||||
// don't make two requests each time data from Unifi is required
|
// don't make two requests each time data from Unifi is required
|
||||||
|
@ -77,7 +78,7 @@ export default async function unifiProxyHandler(req, res) {
|
||||||
if (responseHeaders?.["x-csrf-token"]) {
|
if (responseHeaders?.["x-csrf-token"]) {
|
||||||
prefix = udmpPrefix;
|
prefix = udmpPrefix;
|
||||||
}
|
}
|
||||||
cache.put(prefixCacheKey, prefix);
|
cache.put(`${prefixCacheKey}.${service}`, prefix);
|
||||||
}
|
}
|
||||||
|
|
||||||
widget.prefix = prefix;
|
widget.prefix = prefix;
|
||||||
|
|
Loading…
Add table
Reference in a new issue