LastFM middleware; Window.onmessage needs origin to prevent clashing between auth popups
This commit is contained in:
@ -1,5 +1,4 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Spotify Authentication proxy
|
||||
*
|
||||
@ -31,36 +30,41 @@ if (isset($_GET['code'])){
|
||||
|
||||
// go get our credentials
|
||||
$response = getToken($_GET['code']);
|
||||
$responseArray = json_decode( $response, true );
|
||||
$response = json_decode( $response, true );
|
||||
|
||||
// add our code to the array of credentials, etc
|
||||
$responseArray['authorization_code'] = $_GET['code'];
|
||||
$response = json_encode($responseArray);
|
||||
|
||||
$response['authorization_code'] = $_GET['code'];
|
||||
$response['origin'] = "auth_spotify";
|
||||
|
||||
// make sure we have a successful response
|
||||
if( !isset($responseArray['access_token']) ){
|
||||
if( !isset($response['access_token']) ){
|
||||
echo 'Error!';
|
||||
die();
|
||||
}
|
||||
|
||||
// Pass our error back to the popup opener
|
||||
// Pass our error back to the popup opener
|
||||
?>
|
||||
<script type="text/javascript">
|
||||
window.opener.postMessage( '<?php echo $response ?>', "*");
|
||||
window.opener.postMessage( '<?php echo json_encode($response) ?>', "*");
|
||||
window.close();
|
||||
</script>
|
||||
<?php
|
||||
|
||||
// authorization error
|
||||
} else if (isset($_GET['error'])){
|
||||
|
||||
$response = array(
|
||||
"error" => $_GET["error"],
|
||||
"origin" => "auth_spotify"
|
||||
);
|
||||
|
||||
// Pass our error back to the popup opener
|
||||
?>
|
||||
<script type="text/javascript">
|
||||
window.opener.postMessage("{\"error\": \"<?php echo $_GET['error'] ?>\"}", "*");
|
||||
window.close();
|
||||
</script>
|
||||
<?php
|
||||
// Pass our error back to the popup opener
|
||||
?>
|
||||
<script type="text/javascript">
|
||||
window.opener.postMessage('<?php echo json_encode($response) ?>', "*");
|
||||
window.close();
|
||||
</script>
|
||||
<?php
|
||||
|
||||
// refresh existing token
|
||||
} else if (isset($_GET['action']) && $_GET['action'] == 'refresh' && $_GET['refresh_token']){
|
||||
@ -171,4 +175,5 @@ function refreshToken($refresh_token){
|
||||
curl_close ($ch);
|
||||
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
9
src/js/bootstrap.js
vendored
9
src/js/bootstrap.js
vendored
@ -14,6 +14,7 @@ import coreMiddleware from './services/core/middleware'
|
||||
import uiMiddleware from './services/ui/middleware'
|
||||
import pusherMiddleware from './services/pusher/middleware'
|
||||
import mopidyMiddleware from './services/mopidy/middleware'
|
||||
import lastfmMiddleware from './services/lastfm/middleware'
|
||||
import spotifyMiddleware from './services/spotify/middleware'
|
||||
import localstorageMiddleware from './services/localstorage/middleware'
|
||||
|
||||
@ -108,12 +109,18 @@ if (localStorage.getItem('spotify')){
|
||||
initialState.spotify = Object.assign(initialState.spotify, storedSpotify );
|
||||
}
|
||||
|
||||
// if we've got a stored version of lastfm state, load and merge
|
||||
if (localStorage.getItem('lastfm')){
|
||||
var storedLastfm = JSON.parse(localStorage.getItem('lastfm') );
|
||||
initialState.lastfm = Object.assign(initialState.lastfm, storedLastfm );
|
||||
}
|
||||
|
||||
console.log('Bootstrapping', initialState)
|
||||
|
||||
let store = createStore(
|
||||
reducers,
|
||||
initialState,
|
||||
applyMiddleware(thunk, localstorageMiddleware, coreMiddleware, uiMiddleware, mopidyMiddleware, pusherMiddleware, spotifyMiddleware )
|
||||
applyMiddleware(thunk, localstorageMiddleware, coreMiddleware, uiMiddleware, mopidyMiddleware, pusherMiddleware, spotifyMiddleware, lastfmMiddleware )
|
||||
);
|
||||
|
||||
export default store;
|
||||
|
||||
@ -21,20 +21,20 @@ class LastfmAuthenticationFrame extends React.Component{
|
||||
}
|
||||
|
||||
componentDidMount(){
|
||||
|
||||
let self = this;
|
||||
|
||||
// Listen for incoming messages from the authorization iframe
|
||||
// This is triggered when the popup posts a message, which is then passed to
|
||||
// the iframe, and then passed on to the parent frame (our application)
|
||||
// Listen for incoming messages from the authorization popup
|
||||
window.addEventListener('message', function(event){
|
||||
self.handleMessage(event)
|
||||
var data = JSON.parse(event.data);
|
||||
|
||||
// Only digest messages relevant to us
|
||||
if (data.origin == 'auth_lastfm'){
|
||||
self.handleMessage(event, data);
|
||||
}
|
||||
}, false);
|
||||
}
|
||||
|
||||
handleMessage(event){
|
||||
|
||||
var data = JSON.parse(event.data)
|
||||
handleMessage(event, data){
|
||||
|
||||
// Only allow incoming data from our authorized authenticator proxy
|
||||
var authorization_domain = this.props.authorization_url.substring(0,this.props.authorization_url.indexOf('/',8))
|
||||
@ -44,13 +44,13 @@ class LastfmAuthenticationFrame extends React.Component{
|
||||
}
|
||||
|
||||
// Bounced with an error
|
||||
if (typeof(data.error) !== 'undefined'){
|
||||
if (data.error !== undefined){
|
||||
this.props.uiActions.createNotification(data.error,'bad')
|
||||
|
||||
// No errors? We're in!
|
||||
} else {
|
||||
this.props.lastfmActions.authorizationGranted(data)
|
||||
//this.props.lastfmActions.getMe()
|
||||
this.props.lastfmActions.getMe()
|
||||
}
|
||||
|
||||
// Turn off our authorizing switch
|
||||
@ -64,7 +64,8 @@ class LastfmAuthenticationFrame extends React.Component{
|
||||
|
||||
// Open an authentication request window
|
||||
var url = this.props.authorization_url+'?action=authorize'
|
||||
var popup = window.open(url,"popup","height=500,width=350");
|
||||
var popup = window.open(url,"popup","height=580,width=350");
|
||||
popup.name = "LastfmAuthenticationWindow";
|
||||
|
||||
// Start timer to check our popup's state
|
||||
var timer = setInterval(checkPopup, 1000);
|
||||
@ -108,7 +109,7 @@ class LastfmAuthenticationFrame extends React.Component{
|
||||
const mapStateToProps = (state, ownProps) => {
|
||||
return {
|
||||
authorization_url: state.lastfm.authorization_url,
|
||||
authorized: state.lastfm.authorization,
|
||||
authorized: state.lastfm.session,
|
||||
authorizing: state.lastfm.authorizing
|
||||
}
|
||||
}
|
||||
|
||||
@ -21,20 +21,20 @@ class SpotifyAuthenticationFrame extends React.Component{
|
||||
}
|
||||
|
||||
componentDidMount(){
|
||||
|
||||
let self = this;
|
||||
|
||||
// Listen for incoming messages from the authorization iframe
|
||||
// This is triggered when the popup posts a message, which is then passed to
|
||||
// the iframe, and then passed on to the parent frame (our application)
|
||||
// Listen for incoming messages from the authorization popup
|
||||
window.addEventListener('message', function(event){
|
||||
self.handleMessage(event)
|
||||
var data = JSON.parse(event.data);
|
||||
|
||||
// Only digest messages relevant to us
|
||||
if (data.origin == 'auth_spotify'){
|
||||
self.handleMessage(event, data);
|
||||
}
|
||||
}, false);
|
||||
}
|
||||
|
||||
handleMessage(event){
|
||||
|
||||
var data = JSON.parse(event.data)
|
||||
handleMessage(event, data){
|
||||
|
||||
// Only allow incoming data from our authorized authenticator proxy
|
||||
var authorization_domain = this.props.authorization_url.substring(0,this.props.authorization_url.indexOf('/',8))
|
||||
@ -44,7 +44,7 @@ class SpotifyAuthenticationFrame extends React.Component{
|
||||
}
|
||||
|
||||
// Spotify bounced with an error
|
||||
if (typeof(data.error) !== 'undefined'){
|
||||
if (data.error !== undefined){
|
||||
this.props.uiActions.createNotification(data.error,'bad')
|
||||
|
||||
// No errors? We're in!
|
||||
@ -80,7 +80,7 @@ class SpotifyAuthenticationFrame extends React.Component{
|
||||
'playlist-read-collaborative',
|
||||
'ugc-image-upload' // playlist image uploading
|
||||
]
|
||||
var popup = window.open(url+'&scope='+scopes.join('%20'),"popup","height=500,width=350");
|
||||
var popup = window.open(url+'&scope='+scopes.join('%20'),"popup","height=580,width=350");
|
||||
|
||||
// Start timer to check our popup's state
|
||||
var timer = setInterval(checkPopup, 1000);
|
||||
|
||||
@ -97,6 +97,7 @@ const CoreMiddleware = (function(){
|
||||
case 'CORE_START_SERVICES':
|
||||
store.dispatch(mopidyActions.connect());
|
||||
store.dispatch(pusherActions.connect());
|
||||
store.dispatch(lastfmActions.connect());
|
||||
|
||||
next(action)
|
||||
break
|
||||
|
||||
@ -59,8 +59,11 @@ export function set(data){
|
||||
**/
|
||||
|
||||
export function authorizationGranted(data){
|
||||
data.token_expiry = new Date().getTime() + data.expires_in;
|
||||
return { type: 'LASTFM_AUTHORIZATION_GRANTED', data: data }
|
||||
data.session.expiry = new Date().getTime() + 3600;
|
||||
return {
|
||||
type: 'LASTFM_AUTHORIZATION_GRANTED',
|
||||
data: data
|
||||
}
|
||||
}
|
||||
|
||||
export function revokeAuthorization(){
|
||||
@ -70,12 +73,37 @@ export function revokeAuthorization(){
|
||||
export function connect(){
|
||||
return (dispatch, getState) => {
|
||||
|
||||
dispatch({ type: 'LASTFM_CONNECTING' })
|
||||
dispatch({ type: 'LASTFM_CONNECTING' });
|
||||
|
||||
sendRequest(dispatch, getState, 'method=artist.getInfo&artist=')
|
||||
// Authorized, dual-purpose our connection to get the current user
|
||||
if (getState().lastfm.session){
|
||||
dispatch(getMe());
|
||||
|
||||
// Not authorized, just use a generic lookup to test our connection
|
||||
} else {
|
||||
sendRequest(dispatch, getState, 'method=artist.getInfo&artist=')
|
||||
.then(
|
||||
response => {
|
||||
dispatch({ type: 'LASTFM_CONNECTED' })
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function getMe(){
|
||||
return (dispatch, getState) => {
|
||||
var params = 'method=user.getInfo&user='+getState().lastfm.session.name
|
||||
sendRequest(dispatch, getState, params)
|
||||
.then(
|
||||
response => {
|
||||
dispatch({ type: 'LASTFM_CONNECTED' })
|
||||
if (response.user){
|
||||
dispatch({
|
||||
type: 'LASTFM_USER_LOADED',
|
||||
user: response.user
|
||||
});
|
||||
dispatch({ type: 'LASTFM_CONNECTED' })
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
43
src/js/services/lastfm/middleware.js
Executable file
43
src/js/services/lastfm/middleware.js
Executable file
@ -0,0 +1,43 @@
|
||||
|
||||
import ReactGA from 'react-ga'
|
||||
|
||||
var helpers = require('./../../helpers')
|
||||
var lastfmActions = require('./actions')
|
||||
var uiActions = require('../ui/actions')
|
||||
var pusherActions = require('../pusher/actions')
|
||||
|
||||
const LastfmMiddleware = (function(){
|
||||
|
||||
/**
|
||||
* The actual middleware inteceptor
|
||||
**/
|
||||
return store => next => action => {
|
||||
var state = store.getState();
|
||||
|
||||
switch(action.type){
|
||||
|
||||
case 'LASTFM_USER_LOADED':
|
||||
var user = Object.assign(
|
||||
{},
|
||||
action.user,
|
||||
{
|
||||
uri: "lastfm:user:"+action.user.name
|
||||
}
|
||||
);
|
||||
store.dispatch({
|
||||
type: "USER_LOADED",
|
||||
key: user.uri,
|
||||
user: user
|
||||
});
|
||||
next(action);
|
||||
break;
|
||||
|
||||
// This action is irrelevant to us, pass it on to the next middleware
|
||||
default:
|
||||
return next(action);
|
||||
}
|
||||
}
|
||||
|
||||
})();
|
||||
|
||||
export default LastfmMiddleware
|
||||
@ -13,22 +13,17 @@ export default function reducer(lastfm = {}, action){
|
||||
return Object.assign({}, lastfm, action.data)
|
||||
|
||||
case 'LASTFM_AUTHORIZATION_GRANTED':
|
||||
return Object.assign({}, lastfm, {
|
||||
enabled: true,
|
||||
return Object.assign({}, lastfm, {
|
||||
authorizing: false,
|
||||
authorization: action.data,
|
||||
api_key: action.data.api_key,
|
||||
token_expiry: action.data.token_expiry
|
||||
session: action.data.session
|
||||
})
|
||||
|
||||
case 'LASTFM_AUTHORIZATION_REVOKED':
|
||||
return Object.assign({}, lastfm, {
|
||||
authorizing: false,
|
||||
authorization: false,
|
||||
api_key: false,
|
||||
token_expiry: 0,
|
||||
authorizing: false,
|
||||
session: false,
|
||||
me: false
|
||||
})
|
||||
});
|
||||
|
||||
default:
|
||||
return lastfm
|
||||
|
||||
@ -167,6 +167,17 @@ const localstorageMiddleware = (function(){
|
||||
);
|
||||
localStorage.setItem('ui', JSON.stringify(ui))
|
||||
break
|
||||
|
||||
case 'LASTFM_AUTHORIZATION_GRANTED':
|
||||
var lastfm = JSON.parse(localStorage.getItem('lastfm') );
|
||||
lastfm = Object.assign(
|
||||
{},
|
||||
{
|
||||
session: action.data.session
|
||||
}
|
||||
);
|
||||
localStorage.setItem('lastfm', JSON.stringify(lastfm));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -15,11 +15,13 @@ import Header from '../components/Header'
|
||||
import Parallax from '../components/Parallax'
|
||||
import Icon from '../components/Icon'
|
||||
import Thumbnail from '../components/Thumbnail'
|
||||
import URILink from '../components/URILink'
|
||||
|
||||
import * as coreActions from '../services/core/actions'
|
||||
import * as uiActions from '../services/ui/actions'
|
||||
import * as pusherActions from '../services/pusher/actions'
|
||||
import * as mopidyActions from '../services/mopidy/actions'
|
||||
import * as lastfmActions from '../services/lastfm/actions'
|
||||
import * as spotifyActions from '../services/spotify/actions'
|
||||
|
||||
class Settings extends React.Component {
|
||||
@ -109,21 +111,45 @@ class Settings extends React.Component {
|
||||
|
||||
if (user){
|
||||
return (
|
||||
<Link className="user" to={global.baseURL+'user/'+user.uri}>
|
||||
<URILink className="user" type="user" uri={user.uri}>
|
||||
<Thumbnail circle={true} size="small" images={user.images} />
|
||||
<span className="user-name">
|
||||
{user.display_name ? user.display_name : user.id}
|
||||
</span>
|
||||
</Link>
|
||||
</URILink>
|
||||
)
|
||||
} else {
|
||||
return (
|
||||
<Link className="user">
|
||||
<URILink className="user">
|
||||
<Thumbnail circle={true} size="small" />
|
||||
<span className="user-name">
|
||||
Unknown
|
||||
</span>
|
||||
</Link>
|
||||
</URILink>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
renderLastfmUser(){
|
||||
var user = this.props.core.users["lastfm:user:"+this.props.lastfm.session.name];
|
||||
|
||||
if (user){
|
||||
return (
|
||||
<URILink className="user" type="user" uri={user.uri}>
|
||||
<Thumbnail circle={true} size="small" images={user.image} />
|
||||
<span className="user-name">
|
||||
{user.realname ? user.realname : user.name}
|
||||
</span>
|
||||
</URILink>
|
||||
)
|
||||
} else {
|
||||
return (
|
||||
<URILink className="user" type="user" uri={false}>
|
||||
<Thumbnail circle={true} size="small" />
|
||||
<span className="user-name">
|
||||
Unknown
|
||||
</span>
|
||||
</URILink>
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -302,6 +328,7 @@ class Settings extends React.Component {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="field">
|
||||
<div className="name">Authorization</div>
|
||||
<div className="input">
|
||||
@ -313,6 +340,15 @@ class Settings extends React.Component {
|
||||
|
||||
<h4 className="underline">LastFM</h4>
|
||||
|
||||
{this.props.lastfm.session ? <div className="field current-user">
|
||||
<div className="name">Current user</div>
|
||||
<div className="input">
|
||||
<div className="text">
|
||||
{ this.renderLastfmUser() }
|
||||
</div>
|
||||
</div>
|
||||
</div> : null}
|
||||
|
||||
<div className="field">
|
||||
<div className="name">Authorization</div>
|
||||
<div className="input">
|
||||
@ -408,6 +444,7 @@ const mapDispatchToProps = (dispatch) => {
|
||||
uiActions: bindActionCreators(uiActions, dispatch),
|
||||
pusherActions: bindActionCreators(pusherActions, dispatch),
|
||||
mopidyActions: bindActionCreators(mopidyActions, dispatch),
|
||||
lastfmActions: bindActionCreators(lastfmActions, dispatch),
|
||||
spotifyActions: bindActionCreators(spotifyActions, dispatch)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user