Upgrading all request/responses to JSON-RPC

This commit is contained in:
James Barnsley
2018-03-12 21:39:27 +13:00
parent f3e64338a5
commit 6ec5fd388e
15 changed files with 1105 additions and 643 deletions

1
src/js/bootstrap.js vendored
View File

@ -34,6 +34,7 @@ let reducers = combineReducers({
// TODO: Look at using propTypes in the component for these falsy initial states
var initialState = {
core: {
outputs: [],
queue: [],
queue_metadata: {},
current_track_uri: null,

66
src/js/components/Snapcast.js Executable file
View File

@ -0,0 +1,66 @@
import React, { PropTypes } from 'react'
import { connect } from 'react-redux'
import { bindActionCreators } from 'redux'
import { Link, hashHistory } from 'react-router'
import FontAwesome from 'react-fontawesome'
import ArtistSentence from './ArtistSentence'
import * as helpers from '../helpers'
import * as coreActions from '../services/core/actions'
import * as uiActions from '../services/ui/actions'
import * as pusherActions from '../services/pusher/actions'
class Snapcast extends React.Component{
constructor(props){
super(props)
}
render(){
if (!this.props.snapcast){
return null;
}
return (
<div className="snapcast">
{
this.props.snapcast.groups.map(group => {
return (
<div className="group" key={group.id}>
{group.muted ? <FontAwesome name="volume-off" /> : <FontAwesome name="volume-up" />} {group.name ? group.name : 'Untitled'}
<div className="clients">
{
group.clients.map(client => {
return (
<div key={client.id}>
{client.config.volume.muted ? <FontAwesome name="volume-off" onClick={e => this.props.pusherActions.setSnapcastClientVolume(client.id, false, client.config.volume.percent)} /> : <FontAwesome name="volume-up" onClick={e => this.props.pusherActions.setSnapcastClientVolume(client.id, true, client.config.volume.percent)} />} {client.config.name} {client.config.volume.percent}
</div>
);
})
}
</div>
</div>
);
})
}
</div>
);
}
}
const mapStateToProps = (state, ownProps) => {
return {
snapcast: state.pusher.snapcast
}
}
const mapDispatchToProps = (dispatch) => {
return {
coreActions: bindActionCreators(coreActions, dispatch),
uiActions: bindActionCreators(uiActions, dispatch),
pusherActions: bindActionCreators(pusherActions, dispatch)
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Snapcast)

View File

@ -205,10 +205,16 @@ export let digestMopidyImages = function(mopidy, images){
export let generateGuid = function(format = 'xxxxxxxxxxxx'){
// numeric
return new Date().valueOf() + Math.random();
// alpha-numeric
/*
return format.replace(/[xy]/g, function(c){
var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8);
return v.toString(16);
});
*/
}
export let getCurrentPusherConnection = function(connections, connectionid){

View File

@ -48,6 +48,18 @@ export function instruct(data = null){
}
}
export function getConfig(){
return {
type: 'PUSHER_GET_CONFIG'
}
}
export function getVersion(){
return {
type: 'PUSHER_GET_VERSION'
}
}
export function deliverBroadcast(data = null){
return {
type: 'PUSHER_DELIVER_BROADCAST',
@ -69,6 +81,12 @@ export function sendAuthorization(recipient_connectionid, authorization, me){
}
}
export function getRadio(){
return {
type: 'PUSHER_GET_RADIO'
}
}
export function startRadio(uris){
return {
type: 'PUSHER_START_RADIO',
@ -108,4 +126,36 @@ export function addQueueMetadata(tlids = [], from_uri = null){
tlids: tlids,
from_uri: from_uri
}
}
}
/**
* Snapcast actions
* TODO: Figure out how to cleanly split this out to it's own service
* but still share the pusher middleware connection
**/
export function getSnapcast(){
return {
type: 'PUSHER_GET_SNAPCAST',
data: {
method: 'Server.GetStatus'
}
}
}
export function setSnapcastClientVolume(id, muted, percent){
return {
type: 'PUSHER_SET_SNAPCAST_CLIENT_VOLUME',
data: {
method: 'Client.SetVolume',
params: {
id: id,
volume: {
muted: muted,
percent: percent
}
}
}
}
}

View File

@ -58,36 +58,45 @@ const PusherMiddleware = (function(){
message
));
} else {
message.type = 'PUSHER_'+message.type.toUpperCase();
message.type = 'PUSHER_'+message.method.toUpperCase();
store.dispatch(message);
}
}
}
const request = (store, method, data = {}) => {
const request = (store, method, params = {}) => {
return new Promise((resolve, reject) => {
var request_id = helpers.generateGuid()
var id = helpers.generateGuid();
var message = {
jsonrpc: 2.0,
id: id,
method: method,
data: data,
request_id: request_id
params: params,
}
socket.send(JSON.stringify(message));
store.dispatch(uiActions.startLoading(request_id, 'pusher_'+method));
store.dispatch(uiActions.startLoading(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});
store.dispatch(uiActions.stopLoading(id));
reject({
jsonrpc: 2.0,
id: id,
error: {
id: id,
code: 32300,
message: "Request timed out"
}
});
},
30000
);
// add query to our deferred responses
deferredRequests[request_id] = {
deferredRequests[id] = {
resolve: resolve,
reject: reject
};
@ -141,71 +150,14 @@ const PusherMiddleware = (function(){
break;
case 'PUSHER_CONNECTED':
ReactGA.event({ category: 'Pusher', action: 'Connected', label: action.username})
request(store, 'get_config')
.then(
response => {
if (response.error){
console.error(response.error)
return false
}
response.type = 'PUSHER_CONFIG'
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
));
}
);
request(store, 'get_version')
.then(
response => {
if (response.error){
console.error(response.error)
return false
}
response.type = 'PUSHER_VERSION'
store.dispatch(response)
},
error => {
store.dispatch(coreActions.handleException(
'Could not load version',
error
));
}
);
request(store, 'get_radio')
.then(
response => {
if (response.error){
console.error(response.error)
return false
}
response.type = 'PUSHER_RADIO'
store.dispatch(response)
},
error => {
store.dispatch(coreActions.handleException(
'Could not load radio',
error
));
}
);
store.dispatch(pusherActions.getQueueMetadata())
ReactGA.event({ category: 'Pusher', action: 'Connected', label: action.username});
store.dispatch(pusherActions.getConfig());
store.dispatch(pusherActions.getVersion());
store.dispatch(pusherActions.getRadio());
store.dispatch(pusherActions.getSnapcast());
store.dispatch(pusherActions.getQueueMetadata());
return next(action);
break;
@ -319,16 +271,60 @@ const PusherMiddleware = (function(){
return next(action);
break;
case 'PUSHER_GET_VERSION':
request(store, 'get_version')
.then(
response => {
store.dispatch({
type: 'PUSHER_VERSION',
version: response.result
})
},
error => {
store.dispatch(coreActions.handleException(
'Could not load version',
error
));
}
);
break;
case 'PUSHER_GET_CONFIG':
console.log(action)
request(store, 'get_config')
.then(
response => {
console.log(response)
store.dispatch({
type: 'PUSHER_CONFIG',
config: response.result
});
var core = store.getState().core;
if (!core.country || !core.locale){
store.dispatch(coreActions.set({
country: response.result.country,
locale: response.result.locale
}))
}
},
error => {
store.dispatch(coreActions.handleException(
'Could not load config',
error
));
}
);
break;
case 'PUSHER_GET_CONNECTIONS':
request(store, 'get_connections')
.then(
response => {
if (response.error){
console.error(response.error)
return false
}
response.type = 'PUSHER_CONNECTIONS'
store.dispatch(response)
response => {
store.dispatch({
type: 'PUSHER_CONNECTIONS',
connections: response.result
})
},
error => {
store.dispatch(coreActions.handleException(
@ -344,6 +340,24 @@ const PusherMiddleware = (function(){
store.dispatch(uiActions.openModal('receive_authorization', {authorization: action.authorization, user: action.me}))
break
case 'PUSHER_GET_RADIO':
request(store, 'get_radio')
.then(
response => {
store.dispatch({
type: 'PUSHER_RADIO',
radio: response.result.radio
});
},
error => {
store.dispatch(coreActions.handleException(
'Could not load radio',
error
));
}
);
break;
case 'PUSHER_START_RADIO':
case 'PUSHER_UPDATE_RADIO':
ReactGA.event({ category: 'Pusher', action: 'Start radio', label: action.uris.join() })
@ -479,6 +493,47 @@ const PusherMiddleware = (function(){
ReactGA.event({ category: 'Pusher', action: 'Error', label: action.message })
break
/**
* Snapcast actions
**/
case 'PUSHER_GET_SNAPCAST':
request(store, 'snapcast_instruct', action.data)
.then(
response => {
store.dispatch({type: 'PUSHER_SNAPCAST', snapcast: response.result.server})
},
error => {
store.dispatch(coreActions.handleException(
'Could not get Snapcast server',
error,
error.message
));
}
);
break
case 'PUSHER_SET_SNAPCAST_CLIENT_VOLUME':
request(store, 'snapcast_instruct', action.data)
.then(
response => {
console.log(response)
store.dispatch({
type: 'PUSHER_SNAPCAST_CLIENT_UPDATED',
key: response.client.id,
client: response.client
})
},
error => {
store.dispatch(coreActions.handleException(
'ERR',
error,
error.message
));
}
);
break
// This action is irrelevant to us, pass it on to the next middleware
default:
return next(action);

View File

@ -51,6 +51,9 @@ export default function reducer(pusher = {}, action){
case 'PUSHER_START_UPGRADE':
return Object.assign({}, pusher, { upgrading: true });
case 'PUSHER_SNAPCAST':
return Object.assign({}, pusher, { snapcast: action.snapcast });
default:
return pusher
}

View File

@ -12,6 +12,7 @@ import URISchemesList from '../components/URISchemesList'
import VersionManager from '../components/VersionManager'
import Header from '../components/Header'
import Thumbnail from '../components/Thumbnail'
import Snapcast from '../components/Snapcast'
import * as uiActions from '../services/ui/actions'
import * as pusherActions from '../services/pusher/actions'
@ -57,6 +58,11 @@ class Debug extends React.Component{
<div className="content-wrapper">
<h4 className="underline">Output</h4>
<div className="field">
<Snapcast />
</div>
<h4 className="underline">User interface</h4>
<form>
<div className="field checkbox">