Revamp Pusher server; Simplified broadcasts; Async pusher requests

This commit is contained in:
James Barnsley
2017-01-09 09:29:56 +13:00
parent deb1340141
commit 3b5babf52c
12 changed files with 374 additions and 324 deletions

View File

@ -17,15 +17,7 @@ class SendAuthorizationModal extends React.Component{
handleClick(e, connectionid){
e.preventDefault()
var data = {
recipients: [connectionid],
action: 'send_authorization',
data: {
authorization: this.props.authorization,
me: this.props.me
}
}
this.props.pusherActions.instruct( 'broadcast', data )
this.props.pusherActions.sendAuthorization( connectionid, this.props.authorization, this.props.me )
this.props.uiActions.closeModal()
return false;
}

View File

@ -16,13 +16,13 @@ class PusherConnectionList extends React.Component{
componentDidMount(){
if( this.props.connected ){
this.props.pusherActions.getConnectionList();
this.props.pusherActions.getConnections();
}
}
componentWillReceiveProps(newProps){
if( !this.props.connected && newProps.connected ){
this.props.pusherActions.getConnectionList();
this.props.pusherActions.getConnections();
}
}

View File

@ -12,7 +12,7 @@ const localstorageMiddleware = (function(){
// append our state to a global variable. This gives us access to debug the store at any point
window._store = store
//console.log(action)
console.log(action)
switch( action.type ){
@ -35,9 +35,9 @@ const localstorageMiddleware = (function(){
localStorage.setItem('pusher', JSON.stringify(pusher));
break;
case 'PUSHER_CHANGE_USERNAME':
case 'PUSHER_USERNAME':
var stored_pusher = JSON.parse( localStorage.getItem('pusher') )
var pusher = Object.assign({}, stored_pusher, { username: action.data.connection.username })
var pusher = Object.assign({}, stored_pusher, { username: action.data.username })
localStorage.setItem('pusher', JSON.stringify(pusher))
break;

View File

@ -10,6 +10,13 @@ export function setPort( port ){
}
}
export function setUsername( username ){
return {
type: 'PUSHER_SET_USERNAME',
username: username
}
}
export function connect(){
return {
type: 'PUSHER_CONNECT'
@ -28,21 +35,28 @@ export function performUpgrade(){
}
}
export function getConnectionList(){
export function getConnections(){
return {
type: 'PUSHER_INSTRUCT',
action: 'get_connections'
type: 'PUSHER_GET_CONNECTIONS'
}
}
export function instruct( message_type, data = null ){
export function instruct( data = null ){
return {
type: 'PUSHER_INSTRUCT',
message_type: message_type,
data: data
}
}
export function sendAuthorization( recipient_connectionid, authorization, me ){
return {
type: 'PUSHER_SEND_AUTHORIZATION',
recipient_connectionid: recipient_connectionid,
authorization: authorization,
me: me
}
}
export function startRadio( uris ){
return {
type: 'PUSHER_START_RADIO',
@ -56,10 +70,9 @@ export function stopRadio(){
}
}
export function debug( call, data = null ){
export function debug( data = null ){
return {
type: 'PUSHER_DEBUG',
call: call,
data: data
}
}

View File

@ -7,27 +7,53 @@ const PusherMiddleware = (function(){
// container for the actual Mopidy socket
var socket = null;
var deferredRequests = [];
const resolveRequest = (requestId, message ) => {
var response = JSON.parse( message );
deferredRequests[request_id].resolve( response );
delete deferredRequests[request_id];
}
const rejectRequest = (requestId, message) => {
deferredRequests[requestId].reject( message );
}
// handle all manner of socket messages
const handleMessage = (ws, store, message) => {
switch( message.action ){
default:
var name = 'unspecified'
if( message.action ) name = message.action
name = name.replace('get_','').toUpperCase()
store.dispatch({ type: 'PUSHER_'+name, data: message.data })
console.log('handle', message)
switch (message.action){
case 'response':
if (typeof( deferredRequests[ message.request_id ]) !== 'undefined' ){
deferredRequests[ message.request_id ].resolve( message )
} else {
console.error('Pusher: Response with no matching request', message);
}
break
case 'broadcast':
var type = 'UNRECOGNISED_BROADCAST'
if( message.data.type ) type = message.data.type.toUpperCase()
store.dispatch({ type: type, data: message.data })
break
}
}
const makeRequest = (data) => {
data.type = 'query';
data.message_id = helpers.generateGuid();
socket.send( JSON.stringify(data) );
}
const request = (data) => {
return new Promise( (resolve, reject) => {
const broadcast = (data) => {
data.type = 'broadcast';
socket.send( JSON.stringify(data) );
// send the payload
data.request_id = helpers.generateGuid()
socket.send( JSON.stringify(data) )
// add query to our deferred responses
deferredRequests[ data.request_id ] = {
resolve: resolve,
reject: reject
}
})
}
@ -42,6 +68,15 @@ const PusherMiddleware = (function(){
switch(action.type) {
case 'PUSHER_INSTRUCT':
request( action.data )
.then(
response => {
store.dispatch({ type: 'PUSHER_INSTRUCT', data: response.data })
}
)
break
case 'PUSHER_CONNECT':
if(socket != null) socket.close();
@ -63,7 +98,12 @@ const PusherMiddleware = (function(){
socket.onopen = () => {
store.dispatch({ type: 'PUSHER_CONNECTED', connection: connection });
store.dispatch({ type: 'PUSHER_SET_USERNAME', username: connection.username });
makeRequest({ action: 'get_radio' });
request({ action: 'get_radio' })
.then(
response => {
store.dispatch({ type: 'PUSHER_RADIO', data: response.data })
}
)
};
socket.onmessage = (message) => {
@ -74,84 +114,69 @@ const PusherMiddleware = (function(){
break;
case 'PUSHER_CONNECTED':
makeRequest({ action: 'get_version' });
request({ action: 'get_version' })
.then(
response => {
store.dispatch({ type: 'PUSHER_VERSION', data: response.data })
}
)
return next(action);
break;
case 'PUSHER_UPGRADING':
makeRequest({ action: 'perform_upgrade' });
request({ action: 'perform_upgrade' })
.then(
response => {
store.dispatch({ type: 'PUSHER_UPGRADE', data: response.data })
}
)
return next(action);
break;
case 'PUSHER_SET_USERNAME':
request({ action: 'set_username', username: action.username })
.then(
response => {
store.dispatch({ type: 'PUSHER_USERNAME', data: { username: response.data.username }})
}
)
return next(action);
break;
case 'PUSHER_GET_CONNECTIONS':
case 'PUSHER_CLIENT_CONNECTED':
makeRequest({ action: 'get_connections' });
request({ action: 'get_connections' })
.then(
response => {
store.dispatch({ type: 'PUSHER_CONNECTIONS', data: response.data })
}
)
return next(action);
break;
case 'PUSHER_INSTRUCT':
switch( action.message_type ){
case 'query':
makeRequest( action.data )
break
case 'broadcast':
broadcast( action.data )
break
}
break;
break
case 'PUSHER_DEBUG':
switch( action.call ){
case 'query':
makeRequest( action.data )
// THIS DOES NOT RETURN A PROMISE SO CAN'T DETECT RELATED RESPONSES
/*.then( response => {
store.dispatch({ type: 'DEBUG', response: response })
})*/
break
case 'broadcast':
broadcast( action.data )
/*.then( response => {
store.dispatch({ type: 'DEBUG', response: response })
})*/
break
default:
store.dispatch({ type: 'DEBUG', response: '{ "error": "Invalid call" }' })
break
}
request( action.data )
.then(
response => {
store.dispatch({ type: 'DEBUG', response: response.data })
}
)
break;
case 'PUSHER_SEND_BROADCAST':
broadcast({ action: action.action, data: action.data });
case 'PUSHER_SEND_AUTHORIZATION':
request({
action: 'send_authorization',
recipient_connectionid: action.recipient_connectionid,
authorization: action.authorization,
me: action.me
})
.then(
response => {
uiActions.createNotification('Authorization sent')
}
)
break;
case 'PUSHER_NOTIFICATION':
case 'PUSHER_BROADCAST':
var notification = window.Notification || window.mozNotification || window.webkitNotification;
if ('undefined' === typeof notification) return false;
if ('undefined' !== typeof notification) notification.requestPermission(function(permission){});
// handle nested data objects
var data = {}
if( typeof(action.data) ) data = action.data
if( typeof(data.data) ) data = Object.assign({}, data, data.data)
// construct our browser notification
var title = '';
var options = {
body: '',
dir: 'auto',
lang: 'EN',
tag: 'iris'
};
if( data.title ) title = data.title;
if( data.body ) options.body = data.body;
if( data.icon ) options.icon = data.icon;
// make it so
var notification = new notification( title, options );
break
case 'PUSHER_SEND_AUTHORIZATION':
if( window.confirm('Spotify authorization for user '+action.data.me.id+' received. Do you want to import?') ){
@ -168,19 +193,20 @@ const PusherMiddleware = (function(){
case 'PUSHER_START_RADIO':
store.dispatch({
type: 'PUSHER_SEND_BROADCAST',
request({
action: 'broadcast',
ignore_self: true,
data: {
type: 'notification',
data: {
title: 'Radio started',
body: store.getState().pusher.username +' started radio mode',
icon: ''
}
}
type: 'browser_notification',
title: 'Radio started',
body: store.getState().pusher.username +' started radio mode',
icon: ''
}
})
.then(
response => {
uiActions.createNotification('Starting radio...')
}
)
var data = {
action: 'start_radio',
@ -200,24 +226,25 @@ const PusherMiddleware = (function(){
}
}
makeRequest( data )
request( data )
break
case 'PUSHER_STOP_RADIO':
store.dispatch({
type: 'PUSHER_SEND_BROADCAST',
request({
action: 'broadcast',
ignore_self: true,
data: {
type: 'notification',
data: {
title: 'Radio stopped',
body: store.getState().pusher.username +' stopped radio mode',
icon: ''
}
}
type: 'browser_notification',
title: 'Radio stopped',
body: store.getState().pusher.username +' stopped radio mode',
icon: ''
}
})
.then(
response => {
uiActions.createNotification('Stopping radio')
}
)
store.dispatch( uiActions.createNotification('Stopping radio') )
var data = {
@ -226,7 +253,7 @@ const PusherMiddleware = (function(){
seed_genres: [],
seed_tracks: []
}
makeRequest( data )
request( data )
break
// This action is irrelevant to us, pass it on to the next middleware

View File

@ -20,8 +20,11 @@ export default function reducer(pusher = {}, action){
case 'PUSHER_SET_PORT':
return Object.assign({}, pusher, { port: action.port });
case 'PUSHER_CHANGE_USERNAME':
return Object.assign({}, pusher, { username: action.data.connection.username });
case 'PUSHER_USERNAME':
return Object.assign({}, pusher, { username: action.data.username });
case 'PUSHER_CONNECTIONS':
return Object.assign({}, pusher, { connections: action.data.connections });
case 'PUSHER_CONNECTION_UPDATED':
function byID(connection){
@ -34,9 +37,6 @@ export default function reducer(pusher = {}, action){
return Object.assign({}, pusher, { connections: connections });
case 'PUSHER_CONNECTIONS':
return Object.assign({}, pusher, { connections: action.data.connections });
case 'PUSHER_VERSION':
return Object.assign({}, pusher, {
version: action.data.version,

View File

@ -429,19 +429,44 @@ export function resolveRadioSeeds( radio ){
seed_genres: []
}
var artist_ids = '';
for (var i = 0; i < radio.seed_artists.length; i++){
if (i > 0) artist_ids += ','
artist_ids += helpers.getFromUri('artistid', radio.seed_artists[i])
}
var requests = []
$.when(
sendRequest( dispatch, getState, 'artists/'+ artist_ids )
if (radio.seed_artists.length > 0){
var artist_ids = '';
for (var i = 0; i < radio.seed_artists.length; i++){
if (i > 0) artist_ids += ','
artist_ids += helpers.getFromUri('artistid', radio.seed_artists[i])
}
// add to our list of async requests
requests.push(
sendRequest( dispatch, getState, 'artists/'+ artist_ids )
.then( response => {
if (!(response instanceof Array)) response = [response]
Object.assign(resolved_seeds.seed_artists, response);
})
)
}
if (radio.seed_tracks.length > 0){
var track_ids = '';
for (var i = 0; i < radio.seed_tracks.length; i++){
if (i > 0) track_ids += ','
track_ids += helpers.getFromUri('trackid', radio.seed_tracks[i])
}
// add to our list of async requests
requests.push(
sendRequest( dispatch, getState, 'tracks/'+ track_ids )
.then( response => {
if (!(response instanceof Array)) response = [response]
Object.assign(resolved_seeds.seed_tracks, response);
})
)
}
$.when.apply(
$, requests
).then( () => {
dispatch({
type: 'PUSHER_RADIO_SEEDS_RESOLVED',

View File

@ -130,6 +130,37 @@ const UIMiddleware = (function(){
next(action)
break
case 'RESTART':
location.reload()
break
case 'BROWSER_NOTIFICATION':
var notification = window.Notification || window.mozNotification || window.webkitNotification;
if ('undefined' === typeof notification) return false;
if ('undefined' !== typeof notification) notification.requestPermission(function(permission){});
// handle nested data objects
var data = {}
if( typeof(action.data) ) data = action.data
if( typeof(data.data) ) data = Object.assign({}, data, data.data)
// construct our browser notification
var title = '';
var options = {
body: '',
dir: 'auto',
lang: 'EN',
tag: 'iris'
};
if( data.title ) title = data.title;
if( data.body ) options.body = data.body;
if( data.icon ) options.icon = data.icon;
// make it so
var notification = new notification( title, options );
break
case 'CREATE_NOTIFICATION':
// start a timeout to remove this notification

View File

@ -61,6 +61,9 @@ class Artist extends React.Component{
}else if( source == 'local' && props.mopidy_connected ){
this.props.mopidyActions.getArtist( props.params.uri );
}
// go back to overview
this.setState({ sub_view: 'overview' })
}
loadMore(){

View File

@ -25,7 +25,6 @@ class Debug extends React.Component{
this.state = {
mopidy_call: 'playlists.asList',
mopidy_data: '{}',
pusher_call: 'broadcast',
pusher_data: '{}'
}
}
@ -33,9 +32,9 @@ class Debug extends React.Component{
componentDidMount(){
if( this.props.connectionid ){
var data = {
action: "notification",
recipients: [this.props.connectionid],
action: "broadcast",
data: {
type: 'browser_notification',
title: "Title",
body: "Test notification",
icon: "http://lorempixel.com/100/100/nature/"
@ -47,15 +46,12 @@ class Debug extends React.Component{
callMopidy(e){
e.preventDefault()
console.info('Mopidy Debugger', this.state.mopidy_call, JSON.parse(this.state.mopidy_data) )
this.props.mopidyActions.debug( this.state.mopidy_call, JSON.parse(this.state.mopidy_data) )
}
callPusher(e){
e.preventDefault()
console.info('Pusher Debugger', this.state.pusher_call, JSON.parse(this.state.pusher_data) )
this.props.pusherActions.debug( this.state.pusher_call, JSON.parse(this.state.pusher_data) )
this.props.uiActions.debugResponse({ status: 1, message: 'Sent', call: this.state.pusher_call, data: this.state.pusher_data })
this.props.pusherActions.debug( JSON.parse(this.state.pusher_data) )
}
render(){
@ -82,10 +78,6 @@ class Debug extends React.Component{
</div>
</form>
</section>
<section>
<h4 className="underline">Mopidy</h4>
<form onSubmit={(e) => this.callMopidy(e)}>
<div className="field">
@ -114,21 +106,8 @@ class Debug extends React.Component{
</div>
</form>
</section>
<section>
<h4 className="underline">Pusher</h4>
<form onSubmit={(e) => this.callPusher(e)}>
<div className="field">
<div className="name">Call</div>
<div className="input">
<input
type="text"
onChange={ e => this.setState({ pusher_call: e.target.value })}
value={ this.state.pusher_call } />
</div>
</div>
<div className="field">
<div className="name">Data</div>
<div className="input">
@ -146,13 +125,11 @@ class Debug extends React.Component{
</div>
</form>
</section>
<section>
<h4 className="underline">Response</h4>
<pre>
{ this.props.debug_response ? JSON.stringify(this.props.debug_response, null, 2) : null }
</pre>
</section>
</div>
);

View File

@ -168,7 +168,7 @@ class Settings extends React.Component{
<input
type="text"
onChange={ e => this.setState({ pusher_username: e.target.value }) }
onBlur={ e => this.props.pusherActions.instruct( 'query', { action: 'change_username', username: this.state.pusher_username }) }
onBlur={ e => this.props.pusherActions.setUsername(this.state.pusher_username) }
value={ this.state.pusher_username } />
</div>
</div>
@ -178,7 +178,7 @@ class Settings extends React.Component{
<input
type="text"
onChange={ e => this.setState({ pusher_port: e.target.value })}
onBlur={ e => this.props.pusherActions.setPort( this.state.pusher_port ) }
onBlur={ e => this.props.pusherActions.setPort(this.state.pusher_port) }
value={ this.state.pusher_port } />
</div>
</div>