Improved plex connection finder
This commit is contained in:
parent
19b47eaa7c
commit
d96d151ad0
|
@ -1552,6 +1552,14 @@
|
|||
"lodash": "^4.17.4",
|
||||
"mkdirp": "^0.5.1",
|
||||
"source-map-support": "^0.4.15"
|
||||
},
|
||||
"dependencies": {
|
||||
"core-js": {
|
||||
"version": "2.6.11",
|
||||
"resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.11.tgz",
|
||||
"integrity": "sha512-5wjnpaT/3dV+XB4borEsnAYQchn00XSgTAWKDkEqv+K8KevjbzmofK6hfJ9TZIlpj2N0xQpazy7PiRQiWHqzWg==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"babel-runtime": {
|
||||
|
@ -1561,6 +1569,13 @@
|
|||
"requires": {
|
||||
"core-js": "^2.4.0",
|
||||
"regenerator-runtime": "^0.11.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"core-js": {
|
||||
"version": "2.6.11",
|
||||
"resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.11.tgz",
|
||||
"integrity": "sha512-5wjnpaT/3dV+XB4borEsnAYQchn00XSgTAWKDkEqv+K8KevjbzmofK6hfJ9TZIlpj2N0xQpazy7PiRQiWHqzWg=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"babel-template": {
|
||||
|
@ -2895,11 +2910,6 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"core-js": {
|
||||
"version": "2.5.5",
|
||||
"resolved": "https://registry.npmjs.org/core-js/-/core-js-2.5.5.tgz",
|
||||
"integrity": "sha1-sU3ek2xkDAV5prUMq8wTLdYSfjs="
|
||||
},
|
||||
"core-util-is": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
|
||||
|
@ -4346,6 +4356,12 @@
|
|||
"integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=",
|
||||
"dev": true
|
||||
},
|
||||
"fast-xml-parser": {
|
||||
"version": "3.17.3",
|
||||
"resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-3.17.3.tgz",
|
||||
"integrity": "sha512-g3OSnHBWq5hrpS4LUgFWOS87F7B6UDklkI6v2VLC/89Ech00fabXleKEHTVXQBkKyVsfOxD+1QY6fkoIZMIO/Q==",
|
||||
"dev": true
|
||||
},
|
||||
"fastparse": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/fastparse/-/fastparse-1.1.1.tgz",
|
||||
|
|
|
@ -58,6 +58,7 @@
|
|||
"eslint-plugin-vue": "^4.3.0",
|
||||
"eventsource-polyfill": "^0.9.6",
|
||||
"extract-text-webpack-plugin": "^2.0.0",
|
||||
"fast-xml-parser": "^3.17.3",
|
||||
"file-loader": "^0.11.1",
|
||||
"friendly-errors-webpack-plugin": "^1.1.3",
|
||||
"html-webpack-plugin": "^2.28.0",
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
import axios from 'axios';
|
||||
const request = require('request');
|
||||
import xmlutils from '@/utils/xmlutils';
|
||||
const parseXMLString = require('xml2js').parseString;
|
||||
|
||||
const _PlexAuth = require('./helpers/PlexAuth.js');
|
||||
|
@ -58,9 +60,9 @@ export default {
|
|||
});
|
||||
}),
|
||||
|
||||
PLEX_GET_DEVICES: ({ state, commit, dispatch }, dontDelete) => new Promise((resolve, reject) => {
|
||||
PLEX_GET_DEVICES: async ({ state, commit, dispatch }, dontDelete) => {
|
||||
if (!state.user) {
|
||||
return reject(new Error('Sign in before getting devices'));
|
||||
throw new Error('Sign in before getting devices');
|
||||
}
|
||||
|
||||
if (!dontDelete) {
|
||||
|
@ -68,112 +70,101 @@ export default {
|
|||
commit('PLEX_SET_VALUE', ['servers', {}]);
|
||||
commit('PLEX_SET_VALUE', ['clients', {}]);
|
||||
}
|
||||
const options = PlexAuth.getApiOptions('https://plex.tv/api/resources?includeHttps=1', state.user.authToken, 5000, 'GET');
|
||||
request(options, (error, response, body) => {
|
||||
if (!error && response.statusCode === 200) {
|
||||
// Valid response
|
||||
parseXMLString(body, async (err, result) => {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
}
|
||||
for (const index in result.MediaContainer.Device) {
|
||||
// Handle the individual device
|
||||
const device = result.MediaContainer.Device[index].$;
|
||||
// Each device can have multiple network connections
|
||||
// Any of them can be viable routes to interacting with the device
|
||||
const connections = result.MediaContainer.Device[index].Connection;
|
||||
const tempConnectionsArray = [];
|
||||
// Create a temporary array of object:PlexConnection
|
||||
for (const i in connections) {
|
||||
const connection = connections[i].$;
|
||||
// Exclude local IPs starting with 169.254
|
||||
if (!connection.address.startsWith('169.254')) {
|
||||
const tempConnection = new PlexConnection();
|
||||
for (const key in connection) {
|
||||
tempConnection[key] = connection[key];
|
||||
}
|
||||
tempConnectionsArray.push(tempConnection);
|
||||
if (connection.local === '1' && connection.uri.indexOf('plex') > -1) {
|
||||
const rawConnection = new PlexConnection();
|
||||
Object.assign(rawConnection, connection);
|
||||
rawConnection.uri = `${connection.protocol}://${connection.address}:${connection.port}`;
|
||||
rawConnection.isManual = true;
|
||||
tempConnectionsArray.push(rawConnection);
|
||||
}
|
||||
}
|
||||
}
|
||||
// If device is a player
|
||||
if (device.provides.indexOf('player') !== -1) {
|
||||
// If device is not Plex Web
|
||||
if(device.product.indexOf('Plex Web') === -1) {
|
||||
// This is a Client
|
||||
// Create a new PlexClient object
|
||||
const tempClient = new PlexClient();
|
||||
for (const key in device) {
|
||||
tempClient[key] = device[key];
|
||||
}
|
||||
tempClient.accessToken = state.user.authToken;
|
||||
tempClient.plexConnections = tempConnectionsArray;
|
||||
dispatch('PLEX_ADD_CLIENT', tempClient);
|
||||
}
|
||||
// If device is a server
|
||||
} else if (device.provides.indexOf('server') !== -1) {
|
||||
// This is a Server
|
||||
// Create a new PlexServer object
|
||||
const tempServer = new PlexServer();
|
||||
for (const key in device) {
|
||||
tempServer[key] = device[key];
|
||||
}
|
||||
// Push a manual connection string for when DNS rebind doesnt work
|
||||
tempServer.plexConnections = tempConnectionsArray;
|
||||
if (tempServer.accessToken == null) {
|
||||
tempServer.accessToken = state.user.authToken;
|
||||
}
|
||||
try {
|
||||
const { data } = await axios.get('https://plex.tv/api/resources?includeHttps=1',
|
||||
PlexAuth.getRequestConfig(state.user.authToken, 5000));
|
||||
const result = xmlutils.parseXML(data);
|
||||
|
||||
dispatch('PLEX_ADD_SERVER', tempServer);
|
||||
result.MediaContainer[0].Device.forEach((device) => {
|
||||
// Create a temporary array of object:PlexConnection
|
||||
// Exclude local IPs starting with 169.254
|
||||
const tempConnectionsArray = device.Connection
|
||||
.filter((connection) => !connection.address.startsWith('169.254'))
|
||||
.flatMap((connection) => {
|
||||
const tempConnection = new PlexConnection();
|
||||
Object.assign(tempConnection, connection);
|
||||
if (connection.local === '1' && connection.uri.indexOf('plex') > -1) {
|
||||
const rawConnection = new PlexConnection();
|
||||
Object.assign(rawConnection, connection);
|
||||
rawConnection.uri = `${connection.protocol}://${connection.address}:${connection.port}`;
|
||||
rawConnection.isManual = true;
|
||||
// Return both
|
||||
return [tempConnection, rawConnection];
|
||||
}
|
||||
|
||||
return [tempConnection];
|
||||
});
|
||||
|
||||
tempConnectionsArray.sort((con1, con2) => parseInt(con1.port, 10) - parseInt(con2.port, 10));
|
||||
|
||||
// If device is a player
|
||||
if (device.provides.indexOf('player') !== -1) {
|
||||
// If device is not Plex Web
|
||||
if (device.product.indexOf('Plex Web') === -1) {
|
||||
// This is a Client
|
||||
// Create a new PlexClient object
|
||||
const tempClient = new PlexClient();
|
||||
Object.assign(tempClient, device);
|
||||
|
||||
tempClient.accessToken = state.user.authToken;
|
||||
tempClient.plexConnections = tempConnectionsArray;
|
||||
dispatch('PLEX_ADD_CLIENT', tempClient);
|
||||
}
|
||||
// Setup our slPlayer
|
||||
const ptplayer = new PlexClient();
|
||||
ptplayer.provides = 'player';
|
||||
ptplayer.clientIdentifier = 'PTPLAYER9PLUS10';
|
||||
ptplayer.platform = 'Web';
|
||||
ptplayer.device = 'Web';
|
||||
ptplayer.product = 'SyncLounge';
|
||||
ptplayer.name = 'SyncLounge Player';
|
||||
ptplayer.labels = [
|
||||
['Recommended', 'green'],
|
||||
];
|
||||
ptplayer.lastSeenAt = Math.round((new Date()).getTime() / 1000);
|
||||
for (const i in state.clients) {
|
||||
const client = state.clients[i];
|
||||
for (const j in client.plexConnections) {
|
||||
const clientConnection = client.plexConnections[j];
|
||||
// Check if this URL matches any server connections
|
||||
for (const x in state.servers) {
|
||||
const server = state.servers[x];
|
||||
for (const y in server.plexConnections) {
|
||||
const serverConnection = server.plexConnections[y];
|
||||
if (serverConnection.uri === clientConnection.uri) {
|
||||
client.accessToken = server.accessToken;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (device.provides.indexOf('server') !== -1) {
|
||||
// This is a Server
|
||||
// Create a new PlexServer object
|
||||
const tempServer = new PlexServer();
|
||||
Object.assign(tempServer, device);
|
||||
// Push a manual connection string for when DNS rebind doesnt work
|
||||
tempServer.plexConnections = tempConnectionsArray;
|
||||
if (tempServer.accessToken == null) {
|
||||
tempServer.accessToken = state.user.authToken;
|
||||
}
|
||||
|
||||
dispatch('PLEX_ADD_CLIENT', ptplayer);
|
||||
commit('PLEX_SET_VALUE', ['gotDevices', true]);
|
||||
dispatch('PLEX_REFRESH_SERVER_CONNECTIONS');
|
||||
return resolve(true);
|
||||
dispatch('PLEX_ADD_SERVER', tempServer);
|
||||
}
|
||||
});
|
||||
|
||||
// Setup our slPlayer
|
||||
const ptplayer = new PlexClient();
|
||||
ptplayer.provides = 'player';
|
||||
ptplayer.clientIdentifier = 'PTPLAYER9PLUS10';
|
||||
ptplayer.platform = 'Web';
|
||||
ptplayer.device = 'Web';
|
||||
ptplayer.product = 'SyncLounge';
|
||||
ptplayer.name = 'SyncLounge Player';
|
||||
ptplayer.labels = [['Recommended', 'green']];
|
||||
ptplayer.lastSeenAt = Math.round(new Date().getTime() / 1000);
|
||||
|
||||
// Get an array of {accessToken, uri} objects
|
||||
const serverConnectionTokens = Object.entries(state.servers)
|
||||
.flatMap(([, server]) => server.plexConnections
|
||||
.map((serverConnection) => ({
|
||||
accessToken: serverConnection.accessToken,
|
||||
uri: serverConnection.uri,
|
||||
})));
|
||||
|
||||
Object.entries(state.clients).forEach(([, client]) => {
|
||||
client.plexConnections.forEach((clientConnection) => {
|
||||
const match = serverConnectionTokens
|
||||
.find((serverCon) => serverCon.uri === clientConnection.uri);
|
||||
if (match !== undefined) {
|
||||
// Yeah it would be better if I didn't have to mutate client but oh well
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
client.accessToken = match.accessToken;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// Invalid response
|
||||
commit('PLEX_SET_VALUE', ['gotDevices', true]);
|
||||
return reject(new Error('Invalid Response'));
|
||||
}
|
||||
});
|
||||
}),
|
||||
});
|
||||
|
||||
dispatch('PLEX_ADD_CLIENT', ptplayer);
|
||||
commit('PLEX_SET_VALUE', ['gotDevices', true]);
|
||||
dispatch('PLEX_REFRESH_SERVER_CONNECTIONS');
|
||||
} catch (e) {
|
||||
// Invalid response
|
||||
commit('PLEX_SET_VALUE', ['gotDevices', true]);
|
||||
throw e;
|
||||
}
|
||||
},
|
||||
|
||||
PLEX_REFRESH_SERVER_CONNECTIONS: ({ state, dispatch }) => {
|
||||
for (const id in state.servers) {
|
||||
|
|
|
@ -21,6 +21,17 @@ module.exports = function PlexAuth() {
|
|||
};
|
||||
};
|
||||
|
||||
this.getRequestConfig = function (accessToken, timeout) {
|
||||
return {
|
||||
headers: {
|
||||
'X-Plex-Client-Identifier': 'SyncLounge',
|
||||
Accept: 'application/json',
|
||||
'X-Plex-Token': accessToken,
|
||||
},
|
||||
timeout,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @param url
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
const request = require('request');
|
||||
const safeParse = require('safe-json-parse/callback');
|
||||
const _PlexAuth = require('./PlexAuth.js');
|
||||
import promiseutils from '@/utils/promiseutils';
|
||||
|
||||
const PlexAuth = new _PlexAuth();
|
||||
|
||||
|
@ -64,56 +65,45 @@ module.exports = function PlexServer() {
|
|||
}
|
||||
});
|
||||
};
|
||||
this.hitApiTestConnection = async function (command, connection) {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
const _url = connection.uri + command;
|
||||
const options = PlexAuth.getApiOptions(_url, this.accessToken, 7500, 'GET');
|
||||
request(options, (error, response, body) => {
|
||||
if (!error) {
|
||||
safeParse(body, (err, json) => {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
}
|
||||
return resolve(json);
|
||||
});
|
||||
} else {
|
||||
return reject(error);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
this.hitApiTestConnection = function (command, connection) {
|
||||
const url = `${connection.uri}${command}`;
|
||||
const config = PlexAuth.getRequestConfig(this.accessToken, 7500);
|
||||
return axios.get(url, config);
|
||||
};
|
||||
|
||||
this.setChosenConnection = function (con) {
|
||||
this.chosenConnection = con;
|
||||
};
|
||||
this.findConnection = function () {
|
||||
|
||||
this.findConnection = async function () {
|
||||
// This function iterates through all available connections and
|
||||
// if any of them return a valid response we'll set that connection
|
||||
// as the chosen connection for future use.
|
||||
let resolved = false;
|
||||
|
||||
return new Promise(async (resolve, reject) => {
|
||||
await Promise.all(this.plexConnections.map(async (connection, index) =>
|
||||
/*eslint-disable */
|
||||
new Promise(async (_resolve, _reject) => {
|
||||
try {
|
||||
let result = await this.hitApiTestConnection('', connection)
|
||||
if (result) {
|
||||
resolved = true
|
||||
// console.log('Succesfully connected to', server, 'via', connection)
|
||||
this.setValue('chosenConnection', connection)
|
||||
resolve(true)
|
||||
}
|
||||
_resolve(false)
|
||||
} catch (e) {
|
||||
_resolve(false)
|
||||
}
|
||||
})
|
||||
/* eslint-enable */
|
||||
));
|
||||
if (!resolved) {
|
||||
reject(new Error('Unable to find a connection'));
|
||||
}
|
||||
});
|
||||
// Prefer secure connections first.
|
||||
const secureConnections = this.plexConnections.filter((connection) => connection.protocol === 'https');
|
||||
|
||||
try {
|
||||
const secureConnection = promiseutils.any(
|
||||
secureConnections.map((connection) => this.hitApiTestConnection('', connection).then(() => connection))
|
||||
);
|
||||
this.setValue('chosenConnection', secureConnection);
|
||||
return true;
|
||||
} catch (e) {
|
||||
console.log('No secure connections found');
|
||||
}
|
||||
|
||||
// If we are using synclounge over https, we can't access connections over http because
|
||||
// most modern web browsers block mixed content
|
||||
if (window.location.protocol === 'http:') {
|
||||
const insecureConnections = this.plexConnections.filter((connection) => connection.protocol === 'http');
|
||||
const insecureConnection = promiseutils.any(insecureConnections.map((connection) => this.hitApiTestConnection('', connection).then(() => connection)));
|
||||
this.setValue('chosenConnection', insecureConnection);
|
||||
return true;
|
||||
}
|
||||
|
||||
throw new Error('Unable to find a connection');
|
||||
};
|
||||
|
||||
// Functions for dealing with media
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
// There doesn't seem to be a valid Promise.any polyfill in the current build config so I found one myself
|
||||
|
||||
export default {
|
||||
any: (promises) => {
|
||||
return Promise.all(promises.map(promise =>
|
||||
promise.then((val) => {
|
||||
throw val;
|
||||
}, reason => reason))).then((reasons) => {
|
||||
throw reasons;
|
||||
}, firstResolved => firstResolved);
|
||||
},
|
||||
};
|
|
@ -0,0 +1,11 @@
|
|||
import parser from 'fast-xml-parser';
|
||||
|
||||
const options = {
|
||||
attributeNamePrefix : "",
|
||||
ignoreAttributes: false,
|
||||
arrayMode: true,
|
||||
};
|
||||
|
||||
export default {
|
||||
parseXML: (xml) => parser.parse(xml, options),
|
||||
};
|
Loading…
Reference in New Issue