Files
Iris/src/js/services/pusher/middleware.js

470 lines
18 KiB
JavaScript
Raw Normal View History

2016-10-27 16:58:11 +13:00
import ReactGA from 'react-ga'
2016-10-27 21:20:05 +13:00
var helpers = require('../../helpers.js')
var coreActions = require('../core/actions.js')
2017-02-06 17:02:43 +13:00
var uiActions = require('../ui/actions.js')
var pusherActions = require('./actions.js')
var spotifyActions = require('../spotify/actions.js')
2016-10-27 16:58:11 +13:00
const PusherMiddleware = (function(){
2017-03-27 14:23:38 +13:00
// container for the actual websocket
var socket = null
2017-03-27 14:23:38 +13:00
// requests pending
var deferredRequests = []
2016-10-27 16:58:11 +13:00
// handle all manner of socket messages
const handleMessage = (ws, store, message) => {
2017-02-19 21:27:43 +13:00
2017-10-26 19:26:38 +13:00
if (store.getState().ui.log_pusher){
console.log('Pusher', message);
2017-09-19 16:57:59 +12:00
}
2017-10-26 19:26:38 +13:00
// Response with request_id
if (message.request_id !== undefined && message.request_id){
2017-09-19 16:57:59 +12:00
2017-10-26 19:26:38 +13:00
// Response matches a pending request
if (deferredRequests[message.request_id] !== undefined){
store.dispatch(uiActions.stopLoading(message.request_id));
// Response is an error
if (message.status <= 0){
deferredRequests[message.request_id].reject(message);
// Successful response
} else {
deferredRequests[message.request_id].resolve(message);
}
// Hmm, the response doesn't appear to be for us?
2017-02-18 22:04:05 +13:00
} else {
2017-09-19 16:57:59 +12:00
store.dispatch(coreActions.handleException(
'Pusher: Response received with no matching request',
2017-09-19 16:57:59 +12:00
message
));
2017-02-18 22:04:05 +13:00
}
2016-10-27 16:58:11 +13:00
2017-10-26 19:26:38 +13:00
// General broadcast received
2017-02-18 22:04:05 +13:00
} else {
2017-10-26 19:26:38 +13:00
// Broadcast of an error
if (message.status !== undefined && message.status <= 0){
store.dispatch(coreActions.handleException(
'Pusher: '+message.message,
message
));
} else {
message.type = 'PUSHER_'+message.type.toUpperCase();
store.dispatch(message);
}
}
2016-10-27 21:20:05 +13:00
}
2017-03-27 14:23:38 +13:00
const request = (store, method, data = {}) => {
2017-10-10 13:05:28 +13:00
return new Promise((resolve, reject) => {
2017-02-18 22:04:05 +13:00
var request_id = helpers.generateGuid()
var message = {
method: method,
data: data,
request_id: request_id
}
2017-10-26 19:26:38 +13:00
socket.send(JSON.stringify(message));
2017-03-27 14:23:38 +13:00
2017-10-26 19:26:38 +13:00
store.dispatch(uiActions.startLoading(request_id, 'pusher_'+method));
// Start our 15 second timeout
var timeout = setTimeout(
function(){
store.dispatch(uiActions.stopLoading(request_id));
reject({message: "Request timed out", method: method, data: data});
},
2017-10-26 19:26:38 +13:00
5000 // 30000
);
// add query to our deferred responses
2017-02-18 22:04:05 +13:00
deferredRequests[request_id] = {
resolve: resolve,
reject: reject
2017-10-26 19:26:38 +13:00
};
})
}
2016-10-27 16:58:11 +13:00
return store => next => action => {
2017-10-10 13:05:28 +13:00
switch(action.type){
2016-10-27 16:58:11 +13:00
case 'PUSHER_CONNECT':
2017-10-10 13:05:28 +13:00
if (socket != null) socket.close();
2016-10-27 16:58:11 +13:00
store.dispatch({ type: 'PUSHER_CONNECTING' });
var state = store.getState();
var connection = {
2017-02-17 21:36:31 +13:00
clientid: helpers.generateGuid(),
2017-02-18 22:04:05 +13:00
connection_id: helpers.generateGuid(),
2017-02-17 21:36:31 +13:00
username: 'Anonymous'
}
2017-10-10 13:05:28 +13:00
if (state.pusher.username ) connection.username = state.pusher.username;
2017-02-17 21:36:31 +13:00
connection.username = connection.username.replace(/\W/g, '')
2017-02-16 19:51:49 +13:00
2016-10-27 16:58:11 +13:00
socket = new WebSocket(
2017-03-28 18:23:58 +13:00
'ws'+(window.location.protocol === 'https:' ? 's' : '')+'://'+state.mopidy.host+':'+state.mopidy.port+'/iris/ws/',
2017-02-18 22:04:05 +13:00
[ connection.clientid, connection.connection_id, connection.username ]
2016-10-27 16:58:11 +13:00
);
socket.onmessage = (message) => {
2016-10-27 16:58:11 +13:00
var message = JSON.parse(message.data);
2017-10-10 13:05:28 +13:00
handleMessage(socket, store, message )
2016-10-27 16:58:11 +13:00
};
2017-02-21 20:04:45 +13:00
socket.onclose = () => {
store.dispatch({
type: 'PUSHER_DISCONNECTED'
})
2017-10-26 19:26:38 +13:00
// attempt to reconnect every 5 seconds
2017-02-21 20:04:45 +13:00
setTimeout(() => {
store.dispatch(pusherActions.connect())
}, 5000);
};
2016-10-27 16:58:11 +13:00
break;
2016-10-27 21:20:05 +13:00
case 'PUSHER_CONNECTED':
ReactGA.event({ category: 'Pusher', action: 'Connected', label: action.username })
2017-03-27 14:23:38 +13:00
request(store, 'get_config')
.then(
response => {
2017-02-18 22:04:05 +13:00
if (response.error){
console.error(response.error)
2017-02-07 10:21:20 +13:00
return false
}
response.type = 'PUSHER_CONFIG'
2017-02-18 22:04:05 +13:00
store.dispatch(response)
var core = store.getState().core
if (!core.country || !core.locale){
store.dispatch(coreActions.set({
country: response.config.country,
locale: response.config.locale
}))
}
},
error => {
store.dispatch(coreActions.handleException(
'Could not load config',
error
));
}
2017-10-26 19:26:38 +13:00
);
2017-03-27 14:23:38 +13:00
request(store, 'get_version')
.then(
response => {
2017-02-18 22:04:05 +13:00
if (response.error){
console.error(response.error)
2017-02-07 10:21:20 +13:00
return false
}
response.type = 'PUSHER_VERSION'
2017-02-18 22:04:05 +13:00
store.dispatch(response)
},
error => {
store.dispatch(coreActions.handleException(
'Could not load version',
error
));
}
2017-10-26 19:26:38 +13:00
);
2017-03-27 14:23:38 +13:00
request(store, 'get_radio')
.then(
response => {
2017-02-18 22:04:05 +13:00
if (response.error){
console.error(response.error)
2017-02-07 10:21:20 +13:00
return false
}
response.type = 'PUSHER_RADIO'
2017-02-18 22:04:05 +13:00
store.dispatch(response)
},
error => {
store.dispatch(coreActions.handleException(
'Could not load radio',
error
));
}
2017-10-26 19:26:38 +13:00
);
2017-02-14 21:41:13 +13:00
store.dispatch(pusherActions.getQueueMetadata())
return next(action);
break;
case 'PUSHER_INSTRUCT':
2017-10-26 19:26:38 +13:00
request(action)
.then(
response => {
store.dispatch({ type: 'PUSHER_INSTRUCT', data: response.data })
},
error => {
store.dispatch(coreActions.handleException(
'Instruct failed',
error
));
}
2017-10-26 19:26:38 +13:00
);
break
case 'PUSHER_DELIVER_MESSAGE':
2017-03-27 14:23:38 +13:00
request(store, 'deliver_message', action.data)
.then(
response => {
2017-10-10 13:05:28 +13:00
store.dispatch(uiActions.createNotification('Message delivered') )
},
error => {
store.dispatch(coreActions.handleException(
'Could not deliver message',
error
));
}
2017-10-26 19:26:38 +13:00
);
break
2017-03-09 08:04:23 +13:00
case 'PUSHER_DELIVER_BROADCAST':
2017-03-27 14:23:38 +13:00
request(store, 'broadcast', action.data)
2017-03-09 08:04:23 +13:00
break
2017-02-14 12:13:07 +13:00
case 'PUSHER_GET_QUEUE_METADATA':
2017-03-27 14:23:38 +13:00
request(store, 'get_queue_metadata')
2017-02-14 12:13:07 +13:00
.then(
response => {
response.type = 'PUSHER_QUEUE_METADATA'
2017-02-18 22:04:05 +13:00
store.dispatch(response)
},
error => {
store.dispatch(coreActions.handleException(
'Could not load queue metadata',
error
));
2017-02-14 12:13:07 +13:00
}
2017-10-26 19:26:38 +13:00
);
2017-02-14 12:13:07 +13:00
break;
case 'PUSHER_ADD_QUEUE_METADATA':
2017-03-27 14:23:38 +13:00
request(store, 'add_queue_metadata', {
2017-02-18 22:04:05 +13:00
tlids: action.tlids,
added_from: action.from_uri,
added_by: store.getState().pusher.username
})
2017-02-14 12:13:07 +13:00
break;
2017-02-19 21:27:43 +13:00
case 'PUSHER_START_UPGRADE':
ReactGA.event({ category: 'Pusher', action: 'Upgrade', label: '' })
2017-03-27 14:23:38 +13:00
request(store, 'upgrade')
.then(
response => {
if (response.error){
console.error(response.error)
return false
}
2017-02-07 10:21:20 +13:00
if (response.upgrade_successful){
2017-10-10 13:05:28 +13:00
store.dispatch(uiActions.createNotification('Upgrade complete') )
} else {
2017-10-10 13:05:28 +13:00
store.dispatch(uiActions.createNotification('Upgrade failed, please upgrade manually','bad') )
}
2017-02-18 22:04:05 +13:00
response.type = 'PUSHER_VERSION'
store.dispatch(response)
},
error => {
store.dispatch(coreActions.handleException(
'Could not start upgrade',
error
));
}
2017-10-26 19:26:38 +13:00
);
return next(action);
break;
case 'PUSHER_SET_USERNAME':
2017-03-27 14:23:38 +13:00
request(store, 'set_username', {
2017-02-18 22:04:05 +13:00
username: action.username
})
2017-10-26 19:26:38 +13:00
.then(
response => {
if (response.error){
console.error(response.error)
return false
}
response.type = 'PUSHER_USERNAME_CHANGED'
store.dispatch(response)
},
error => {
store.dispatch(coreActions.handleException(
'Could not set username',
error
));
}
2017-10-26 19:26:38 +13:00
);
2016-10-28 10:48:55 +13:00
return next(action);
break;
2017-02-19 21:27:43 +13:00
case 'PUSHER_GET_CONNECTIONS':
2017-03-27 14:23:38 +13:00
request(store, 'get_connections')
2017-10-26 19:26:38 +13:00
.then(
response => {
if (response.error){
console.error(response.error)
return false
}
response.type = 'PUSHER_CONNECTIONS'
store.dispatch(response)
},
error => {
store.dispatch(coreActions.handleException(
'Could not load connections',
error
));
}
2017-10-26 19:26:38 +13:00
);
return next(action);
break
2017-02-21 19:54:00 +13:00
case 'PUSHER_SPOTIFY_AUTHORIZATION':
store.dispatch(uiActions.openModal('receive_authorization', {authorization: action.authorization, user: action.me}))
break
case 'PUSHER_START_RADIO':
2017-04-18 08:52:28 +12:00
case 'PUSHER_UPDATE_RADIO':
ReactGA.event({ category: 'Pusher', action: 'Start radio', label: action.uris.join() })
2017-04-18 08:52:28 +12:00
// start our UI process notification
2017-04-18 04:44:39 +12:00
if (action.type == 'PUSHER_UPDATE_RADIO'){
store.dispatch(uiActions.startProcess('PUSHER_RADIO_PROCESS', 'Updating radio'))
2017-04-18 04:44:39 +12:00
} else {
store.dispatch(uiActions.startProcess('PUSHER_RADIO_PROCESS', 'Starting radio'))
2017-04-18 04:44:39 +12:00
}
2016-12-20 16:58:20 +13:00
var data = {
update: (action.type == 'PUSHER_UPDATE_RADIO'),
2016-12-20 16:58:20 +13:00
seed_artists: [],
seed_genres: [],
seed_tracks: []
}
2017-10-10 13:05:28 +13:00
for(var i = 0; i < action.uris.length; i++){
switch(helpers.uriType(action.uris[i] )){
2016-12-20 16:58:20 +13:00
case 'artist':
2017-10-10 13:05:28 +13:00
data.seed_artists.push(action.uris[i] );
2016-12-20 16:58:20 +13:00
break;
case 'track':
2017-10-10 13:05:28 +13:00
data.seed_tracks.push(action.uris[i] );
2016-12-20 16:58:20 +13:00
break;
case 'genre':
2017-10-10 13:05:28 +13:00
data.seed_genres.push(action.uris[i] );
break;
2016-12-20 16:58:20 +13:00
}
}
2017-03-09 08:04:23 +13:00
2017-04-18 08:52:28 +12:00
request(store, 'change_radio', data)
.then(
response => {
if (response.status == 0){
store.dispatch(uiActions.createNotification(response.message, 'bad'))
}
store.dispatch(uiActions.processFinished('PUSHER_RADIO_PROCESS'))
},
error => {
store.dispatch(coreActions.handleException(
'Could not change radio',
error
));
}
)
2016-12-20 16:58:20 +13:00
break
case 'PUSHER_STOP_RADIO':
store.dispatch(uiActions.createNotification('Stopping radio'))
ReactGA.event({ category: 'Pusher', action: 'Stop radio' })
2017-01-05 13:49:38 +13:00
2016-12-20 19:54:19 +13:00
var data = {
seed_artists: [],
seed_genres: [],
seed_tracks: []
}
2017-03-09 08:04:23 +13:00
// we don't need to wait for response, as change will be broadcast
2017-03-27 14:23:38 +13:00
request(store, 'stop_radio', data)
break
case 'PUSHER_RADIO_STARTED':
case 'PUSHER_RADIO_CHANGED':
if (action.radio && action.radio.enabled && store.getState().spotify.enabled){
store.dispatch(spotifyActions.resolveRadioSeeds(action.radio))
}
next(action)
break
case 'PUSHER_BROWSER_NOTIFICATION':
store.dispatch(uiActions.createBrowserNotification(action))
2016-12-20 19:54:19 +13:00
break
case 'PUSHER_RESTART':
// Hard reload. This doesn't strictly clear the cache, but our compiler's
// cache buster should handle that
window.location.reload(true);
break
case 'PUSHER_VERSION':
ReactGA.event({ category: 'Pusher', action: 'Version', label: action.version.current })
if (action.version.upgrade_available){
2017-10-10 13:05:28 +13:00
store.dispatch(uiActions.createNotification('Version '+action.version.latest+' is available. See settings to upgrade.' ) )
}
2017-10-10 13:05:28 +13:00
next(action )
break
case 'PUSHER_CONFIG':
store.dispatch(spotifyActions.set({
locale: (action.config.locale ? action.config.locale : null),
country: (action.config.country ? action.config.country : null),
authorization_url: (action.config.authorization_url ? action.config.authorization_url : null)
}))
2017-10-10 13:05:28 +13:00
next(action )
break
case 'PUSHER_DEBUG':
2017-03-27 14:23:38 +13:00
request(store, action.message.method, action.message.data )
2017-10-26 19:26:38 +13:00
.then(
response => {
store.dispatch({type: 'DEBUG', response: response})
},
error => {
store.dispatch(coreActions.handleException(
'Could not debug',
error,
error.message
));
}
);
break;
case 'PUSHER_ERROR':
store.dispatch(uiActions.createNotification(action.message, 'bad'))
ReactGA.event({ category: 'Pusher', action: 'Error', label: action.message })
break
2016-10-27 16:58:11 +13:00
// This action is irrelevant to us, pass it on to the next middleware
default:
return next(action);
}
}
})();
export default PusherMiddleware