Merge pull request #657 from benphelps/fix-649

Fix: allow multiple instances of certain widgets
pull/664/head
shamoon 2 years ago committed by GitHub
commit f6b6c64b93
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -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,18 +77,18 @@ 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 = [].concat(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;
@ -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…
Cancel
Save