Hi Im relatively new to coding with Javascript, and Im trying to work out how I can access Spotify API's access_token within different javascript files. Im running two local server, one React and the other in node. The React server has a button that allows the user to login with Spotify's Oauth in the node server and then the access token and refresh token are redirect successfully back to the react server. The OAuth works quite well as I got it from the Spotify API guide they provide. The way I set up these files is that in my main project I have a auther-server older which includes the Spotify OAuth files and a client folder which is where i create my React app in. Here is where i got the OAuth files( https://github.com/spotify/web-api-auth-examples ). The only things changed in these files was in the app.js found in authorization_code folder where I entered my client and secret id with the correct redirect url, and I also did:
res.redirect('http://localhost:3000/#' +
querystring.stringify({
access_token: access_token,
refresh_token: refresh_token
}));
} else {
res.redirect('/#' +
querystring.stringify({
error: 'invalid_token'
}));
where I made the redirect link go to http://localhost:3000/# with the tokens which is my React app is. From there I go to my React directory and in the App.js file I follow a guide i found on youtube which helps me get what the user is now playing ( https://www.youtube.com/watch?v=prayNyuN3w0&t=1496s). Here is the code:
import React, { Component, useEffect } from "react";
import "./App.css";
import Spotify from 'spotify-web-api-js';
const spotifyWebApi = new Spotify();
class App extends Component {
constructor() {
super();
const params = this.getHashParams();
this.state ={
loggedIn: params.access_token ? true : false,
nowPlaying: {
name: 'Not Checked',
image: '',
}
}
if (params.access_token){
spotifyWebApi.setAccessToken(params.access_token)
}
}
getHashParams() {
var hashParams = {};
var e,
r = /([^&;=]+)=?([^&;]*)/g,
q = window.location.hash.substring(1);
while ((e = r.exec(q))) {
hashParams[e[1]] = decodeURIComponent(e[2]);
}
return hashParams;
}
getNowPlaying(){
spotifyWebApi.getMyCurrentPlaybackState()
.then((response) => {
this.setState({
nowPlaying:{
name: response.item.name,
image: response.item.album.images[0].url
}
})
})
}
render() {
return (
<div className="App">
<a href="http://localhost:8888">
<button>Login With Spotify</button>
</a>
<div>Now Playing: {this.state.nowPlaying.name}</div>
<div>
<img src={ this.state.nowPlaying.image } style={{width: 100}}/>
</div>
<button onClick={() => this.getNowPlaying()}>Check Now Playing</button>
<br></br>
</div>
);
}
}
export default App;
This all works nicely, but Im trying to now access the user's data( playlists) so I can later make new recommendations using Naive Bayes and KNN classifiers, but Ill tackled that after I get over this bit first. Ive looked up ways of storing the tokens and found localStorage.set(token....) to store it in the browser but I havent had success with it as I am confused on where to set this in my code. I think its also worth to note im using the spotify web api js libary, https://github.com/jmperez/spotify-web-api-js . Im guessing that i would make a similar function in the class App like the getNowPlaying() to get the playlists, but I keep getting lost. This is my first question on here, and I probably didnt do a good job of explaining my issue here, but i hope i can find some help on here. Thanks and let me know if I need to provide any more information. :)
You are probably on a good lead to do what you want with localStorage.set.
It is globally accessible. You can to the localStorage.set from wherever you retrieve the token to store it. Then you will be able to do the localStorage.get from wherever you are in your app.
Using the localStorage is a good idea because it will help you keep this token between page refresh. You might want to remove it from localStorage at some point though for security purpose if someone might have access to the machine you use.
Another way if you do not want to use the localStorage is setting it on a separate JavaScript file that you would import wherever you might want to use it.
You can even look for store principles if you want to make that data centralization a step further.
I have one 'main' route with current data presented and another route with 'favorites'
when user click on a favorite component im sending him to the main route and need to present the new data fetched from an api and updating via redux.
So im using the code below in the main component , but what i get is that the previous data is presented for a few second and only then it changes :
async componentWillMount(){
let {cityName} = this.props.match.params
if (cityName) return await this.props.onDataLoad(cityName)
if (!dailyForecasts) await
this.props.onDataLoad(this.state.defaultCity)
}
How can i avoid this behavior ?
I write a small app with React and back-end is PHP. I have 2 type user in database (admin and student). After user login, I save information in session Storage like that ( user: { username:'abcxyz123', role: 'admin' } ). The component render based user.role. This working good. But If I open DevTools and change user.role, my app will wrong render (because user.role is very simple text). How can I avoid this ??? My code is look like that.
class MyApp extends Component {
constructor(props) {
super(props);
this.state = {
user: (window.sessionStorage.user)
? JSON.parse(window.sessionStorage.user)
: false,
};
}
checkUserToLogin = (e) => {
if( loginSuccess ){
// return data of user in variable finalData
// finalData = { username:'abcxyz123', role: 'admin' }
window.sessionStorage.setItem('user', JSON.stringify(finalData));
this.setState({ user: JSON.parse(window.sessionStorage.user) });
}
}
render() {
const {
user
} = this.state;
return (
<div>
<form onSubmit={ this.checkUserToLogin }>
<input type="text" />
<input type="password" />
<button type="submit"> Login </button>
</form>
{/*Component will render based props user.role */}
<Component user={user} />
</div>
)
}
}
I can't change my database. The data of role always 'admin' or 'student'.
If a check is done from your backend on every authenticated action, it shouldn't be a problem.
What I think you do wrong
I think you send authenticated information to the front and let it handle if they should be shown or not. Which is really bad. Every informations sent in request can possibly be read, even if it's not render in DOM. The php backend should filter information based on database role.
The solution
Only keep a token or something that authenticates your front user in its XHR request. JWT is a really great way to do it cause it can not be mutate from the front.
Handle the render or not of admin action but continue to check it in every backend request.
If the information are changed by a malicious user, it's going to be kick from backend and you don't care if the front is broken for him.
To go further
It can be interesting too to keep this token and information in a global context. For example you can use React.Context or Redux and synchronise it with your local storage.
So you don't need to go through props driling with your user data.
Is it possible to conditionally redirect a user to another Url based on cookie value?
I know I can inspect cookie on a server & then redirect.
But what should I do if a user came through Link. (I can't use Route.push because it's undefined on the server)
Is there any way to use Router only on the browser?
I know at least one way to do this: to create simple button and add Router push & check cookies inside onClick handler, but is it a correct way to do this?
you can check if user has accessed the page via server or client side.
and after that you can conditionally redirect with the proper tool.
getInitialProps function gets a ctx object. you can check whether its on server or client like this:
import Router from 'next/router'
export default class Browse extends Component {
static async getInitialProps (ctx) {
if (ctx && ctx.req) {
console.log('server side')
ctx.res.writeHead(302, {Location: `/`})
ctx.res.end()
} else {
console.log('client side')
Router.push(`/`)
}
...
In fully server side based rendering (non Web 2.0), deploying server side code would directly update client side pages upon page reload. In contrast, in React based Single Page Application, even after React components were updated, there would be still some clients using old version of the components (they only get the new version upon browser reload, which should rarely happen) -> If the pages are fully SPA, it's possible that some clients only refresh the pages after a few hours.
What techniques should be employed to make sure the old components versions are not used anymore by any clients?
Update: the API doesn't changed, only React Component is updated with newer version.
You can have a React component make an ajax request to your server, when the application loads, to fetch "interface version". In the server API, you can maintain an incremental value for the client version. The React component can store this value on the client (cookie/local storage/etc). When it detects a change, it can invoke window.location.reload(true); which should force the browser to discard client cache and reload the SPA. Or better still, inform the end-user that a new version will be loaded and ask them if they wish to save the work and then reload etc. Depends on what you wanna do.
Similar to Steve Taylor's answer but instead of versioning API endpoints I would version the client app, in the following way.
With each HTTP request send a custom header, such as:
X-Client-Version: 1.0.0
The server would then be able to intercept such header and respond accordingly.
If the server is aware that the client's version is stale, for example if the current version is 1.1.0, respond with an HTTP status code that will be appropriately handled by the client, such as:
418 - I'm a Teapot
The client can then be programmed to react to such a response by refreshing the app with:
window.location.reload(true)
The underlying premise is that the server is aware of the latest client version.
EDIT:
A similar answer is given here.
What techniques should be employed to make sure the old components
versions are not used anymore by any clients?
today (2018), many front apps use service workers. With it, it's possible to manage your app lifecycle by several means.
Here is a first example, by using a ui notification, asking your visitors to refresh webpage in order to get latest application version.
import * as SnackBar from 'node-snackbar';
// ....
// Service Worker
// https://github.com/GoogleChrome/sw-precache/blob/master/demo/app/js/service-worker-registration.js
const offlineMsg = 'Vous êtes passé(e) en mode déconnecté.';
const onlineMsg = 'Vous êtes de nouveau connecté(e).';
const redundantMsg = 'SW : The installing service worker became redundant.';
const errorMsg = 'SW : Error during service worker registration : ';
const refreshMsg = 'Du nouveau contenu est disponible sur le site, vous pouvez y accéder en rafraichissant cette page.';
const availableMsg = 'SW : Content is now available offline.';
const close = 'Fermer';
const refresh = 'Rafraîchir';
if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
function updateOnlineStatus() {
SnackBar.show({
text: navigator.onLine ? onlineMsg : offlineMsg,
backgroundColor: '#000000',
actionText: close,
});
}
window.addEventListener('online', updateOnlineStatus);
window.addEventListener('offline', updateOnlineStatus);
navigator.serviceWorker.register('sw.js').then((reg) => {
reg.onupdatefound = () => {
const installingWorker = reg.installing;
installingWorker.onstatechange = () => {
switch (installingWorker.state) {
case 'installed':
if (navigator.serviceWorker.controller) {
SnackBar.show({
text: refreshMsg,
backgroundColor: '#000000',
actionText: refresh,
onActionClick: () => { location.reload(); },
});
} else {
console.info(availableMsg);
}
break;
case 'redundant':
console.info(redundantMsg);
break;
default:
break;
}
};
};
}).catch((e) => {
console.error(errorMsg, e);
});
});
}
// ....
There's also an elegant way to check for upgrades in background and then silently upgrade app when user clicks an internal link. This method is presented on zach.codes and discussed on this thread as well.
You can send app’s version with every response from any endpoint of your API. So that when the app makes any API request you can easily check there’s a new version and you need a hard reload. If the version in the API response is newer than the one stored in localStorage, set window.updateRequired = true. And you can have the following react component that wraps react-router's Link:
import React from 'react';
import { Link, browserHistory } from 'react-router';
const CustomLink = ({ to, onClick, ...otherProps }) => (
<Link
to={to}
onClick={e => {
e.preventDefault();
if (window.updateRequired) return (window.location = to);
return browserHistory.push(to);
}}
{...otherProps}
/>
);
export default CustomLink;
And use it instead of react-router's Link throughout the app. So whenever there's an update and the user navigates to another page, there will be a hard reload and the user will get the latest version of the app.
Also you can show a popup saying: "There's an update, click [here] to enable it." if you have only one page or your users navigate very rarely. Or just reload the app without asking. It depends on you app and users.
I know this is an old thread, and service workers are probably the best answer. But I have a simple approach that appears to work:
I added a meta tag to my "index.html" file :
<meta name="version" content="0.0.3"/>
I then have a very simple php scrip in the same folder as the index.html that responds to a simple REST request. The PHP script parses the server copy of the index.html file, extracts the version number and returns it. In my SPA code, every time a new page is rendered I make an ajax call to the PHP script, extract the version from the local meta tag and compare the two. If different I trigger an alert to the user.
PHP script:
<?php
include_once('simplehtmldom_1_9/simple_html_dom.php');
header("Content-Type:application/json");
/*
blantly stolen from: https://shareurcodes.com/blog/creating%20a%20simple%20rest%20api%20in%20php
*/
if(!empty($_GET['name']))
{
$name=$_GET['name'];
$price = get_meta($name);
if(empty($price))
{
response(200,"META Not Found",NULL);
}
else
{
response(200,"META Found",$price);
}
}
else
{
response(400,"Invalid Request",NULL);
}
function response($status,$status_message,$data)
{
header("HTTP/1.1 ".$status);
$response['status']=$status;
$response['status_message']=$status_message;
$response['content']=$data;
$json_response = json_encode($response);
echo $json_response;
}
function get_meta($name)
{
$html = file_get_html('index.html');
foreach($html->find('meta') as $e){
if ( $e->name == $name){
return $e->content ;
}
}
}
Yes in server side rendering if you need to update a small part of the page also you need to reload whole page. But in SPAs you update your stuffs using ajax, hence no need to reload the page. Seeing your problem I have some assumptions:
You see one of your component got updated but other components getting data from same API didn't update. Here comes Flux Architecture. where you have your data in store and your component listen to store's changes, whenever data in your store changes all your components listening to it's change will be updated (no scene of caching).
Or
You need to control your component to be updated automatically. For that
You can request your server for data in specific intervals
Websockets can help you updating component data from server.