How to create a Spotify playlist using react? - reactjs

I am trying to create a playlist on localhost and then have the list I created to be saved to Spotify. Can someone help why Save to Spotify button might not be working? Everything else seems fine, I have doubts about the fetching part I used but can't figure out what the issue might be.
Screenshot of the page:
And there is the Spotify.js code:
import { SearchBar } from '../components/SearchBar/SearchBar';
const clientId = 'I've put my client id';
const redirectUri = 'http://localhost:3000/callback/';
let accessToken;
const Spotify = {
getAccessToken() {
if (accessToken) {
return accessToken;
}
//check for access token match
const accessTokenMatch = window.location.href.match(/access_token=([^&]*)/);
const expiresInMatch = window.location.href.match(/expires_in=([^&]*)/);
if (accessTokenMatch && expiresInMatch) {
accessToken = accessTokenMatch[1];
let expiresIn = Number(expiresInMatch[1]);
//This clears the parameters, allowing to grab new access token then it expires
window.setTimeout(() => (accessToken = ''), expiresIn * 1000);
window.history.pushState('Access Token', null, '/');
return accessToken;
} else {
const accessUrl = `https://accounts.spotify.com/authorize?client_id=${clientId}&response_type=token&scope=playlist-modify-public&redirect_uri=${redirectUri}`;
window.location = accessUrl;
}
},
search(term) {
const accessToken = Spotify.getAccessToken();
return fetch(`https://api.spotify.com/v1/search?type=track&q=${term}`, {
headers: { Authorization: `Bearer ${accessToken}` },
})
.then((response) => {
return response.json();
})
.then((jsonResponse) => {
if (!jsonResponse.tracks) {
return [];
}
return jsonResponse.tracks.items.map((track) => ({
id: track.id,
name: track.name,
artists: track.artists[0].name,
album: track.album.name,
uri: track.uri,
}));
});
},
savePlaylist(name, trackUris) {
if (!name || !trackUris.length) {
return;
}
const accessToken = Spotify.getAccessToken();
const headers = { Authorization: `Bearer ${accessToken}` };
let userId;
return fetch(`https://api.spotify.com/v1/me`, { headers: headers })
.then((response) => response.json())
.then((jsonResponse) => (userId = jsonResponse.id))
.then((userId) => {
return fetch(`/v1/users/${userId}/playlists`, {
headers: headers,
method: 'POST',
body: JSON.stringify({ name: name }),
})
.then((response) => response.json())
.then((jsonResponse) => {
const playlistId = jsonResponse.id;
return fetch(`/v1/users/${userId}/playlists/${playlistId}/tracks`, {
headers: headers,
method: 'POST',
body: JSON.stringify({ uris: trackUris }),
});
});
});
},
};
export default Spotify;
Here is the screenshot of Element > Console:

I had an fetch error, updated as below and working now.
let accessToken;
const Spotify = {
getAccessToken() {
if (accessToken) {
return accessToken;
}
//check for access token match
const accessTokenMatch = window.location.href.match(/access_token=([^&]*)/);
const expiresInMatch = window.location.href.match(/expires_in=([^&]*)/);
if (accessTokenMatch && expiresInMatch) {
accessToken = accessTokenMatch[1];
let expiresIn = Number(expiresInMatch[1]);
//This clears the parameters, allowing to grab new access token then it expires
window.setTimeout(() => (accessToken = ''), expiresIn * 1000);
window.history.pushState('Access Token', null, '/');
return accessToken;
} else {
const accessUrl = `https://accounts.spotify.com/authorize?client_id=${clientId}&response_type=token&scope=playlist-modify-public&redirect_uri=${redirectUri}`;
window.location = accessUrl;
}
},
search(term) {
const accessToken = Spotify.getAccessToken();
return fetch(`https://api.spotify.com/v1/search?type=track&q=${term}`, {
headers: { Authorization: `Bearer ${accessToken}` },
})
.then((response) => {
return response.json();
})
.then((jsonResponse) => {
if (!jsonResponse.tracks) {
return [];
}
return jsonResponse.tracks.items.map((track) => ({
id: track.id,
name: track.name,
artists: track.artists[0].name,
album: track.album.name,
uri: track.uri,
}));
});
},
savePlaylist(name, trackUris) {
if (!name || !trackUris.length) {
return;
}
const accessToken = Spotify.getAccessToken();
const headers = { Authorization: `Bearer ${accessToken}` };
let userID;
return fetch('https://api.spotify.com/v1/me', { headers: headers })
.then((response) => response.json())
.then((jsonResponse) => {
userID = jsonResponse.id;
return fetch(`https://api.spotify.com/v1/users/${userID}/playlists`, {
method: 'POST',
headers: headers,
body: JSON.stringify({ name: name }),
})
.then((response) => response.json())
.then((jsonResponse) => {
const playlistID = jsonResponse.id;
return fetch(
`https://api.spotify.com/v1/users/${userID}/playlists/${playlistID}/tracks`,
{
method: 'POST',
headers: headers,
body: JSON.stringify({ uris: trackUris }),
}
);
});
});
}, // end of savePlaylist method
}; // end of Spotify object
export default Spotify;

Related

Old login token is not getting removed on re-login after successfull log-out

On Log-out i am clearing cookies like this:
const logoutUser = () => {
setUserData(undefined);
Cookies.remove('admin_details');
};
and also updating headers through auth link in Apollo Client like this:
const authLink = new ApolloLink((operation, forward) => {
// add the authorization to the headers
const token = getToken();
if (token) {
operation.setContext(({headers = {}}) => ({
headers: {
...headers,
authorization: token ? `Bearer ${token}` : '',
},
}));
} else {
operation.setContext(({headers = {}}) => ({
headers: {
...headers,
},
}));
}
return forward(operation);
});
So I am not sure why the previous header token is set even after successfully log-out
Here is my APOLLO graphql setup file
const getToken = (): string | undefined => {
const adminDetails = Cookies.get('admin_details');
if (adminDetails) {
const userDataCookies = JSON.parse(
adminDetails,
) as AdminLoginMutation['login'];
return userDataCookies.access_token;
}
return undefined;
};
const logoutAndRedirectToLogin = () => {
Cookies.remove('admin_details');
window.location.reload();
window.location.replace('/auth/login');
};
const getRequestHeaders = () => {
let header;
const token = getToken();
if (token) {
header = {
authorization: token ? `Bearer ${token}` : undefined,
};
} else {
header = {
isadmin: true,
};
}
return header;
};
const getUrl = (): {
ws: string;
http: string;
} => {
if (
process.env.END_POINT &&
typeof process.env.END_POINT === 'string'
) {
if (process.env.END_POINT.includes('localhost')) {
return {
ws: 'ws://' + process.env.END_POINT,
http: 'http://' + process.env.END_POINT,
};
} else {
return {
ws: 'wss://' + process.env.END_POINT,
http: 'https://' + process.env.END_POINT,
};
}
} else {
return {ws: '', http: ''};
}
};
const httpLink = createHttpLink({
uri: getUrl().http,
headers: getRequestHeaders(),
});
const wsLink = new GraphQLWsLink(
createWsClient({
url: getUrl().ws,
connectionParams: {
headers: getRequestHeaders(),
},
}),
);
const authLink = new ApolloLink((operation, forward) => {
// add the authorization to the headers
const token = getToken();
if (token) {
operation.setContext(({headers = {}}) => ({
headers: {
...headers,
authorization: token ? `Bearer ${token}` : '',
},
}));
} else {
operation.setContext(({headers = {}}) => ({
headers: {
...headers,
},
}));
}
return forward(operation);
});
const returnConnectionLink = (): ApolloLink => {
return split(
({query}) => {
const definition = getMainDefinition(query);
return (
definition.kind === 'OperationDefinition' &&
definition.operation === 'subscription'
);
},
wsLink,
httpLink,
);
};
const splitLink = returnConnectionLink();
const errorLink = onError(({networkError, graphQLErrors}) => {
// To retry on network errors, we recommend the RetryLink
// instead of the onError link. This just logs the error.
if (networkError) {
if (
networkError.message.includes('authorization') ||
networkError.message.includes('unauthorized')
) {
logoutAndRedirectToLogin();
}
}
if (graphQLErrors) {
graphQLErrors.forEach(({extensions: {code}}) => {
if (code === 'UNAUTHENTICATED') {
logoutAndRedirectToLogin();
}
});
}
});
const cache = new InMemoryCache();
const links: Array<ApolloLink | RequestHandler> = [splitLink, errorLink];
export const graphqlClient = new ApolloClient({
cache: cache,
link: authLink.concat(ApolloLink.from(links)),
ssrMode: typeof window === 'undefined',
name: process.env.REACT_PUBLIC_APP_NAME
? process.env.REACT_PUBLIC_APP_NAME + '_web'
: 'web',
version: process.env.REACT_PUBLIC_APP_VERSION,
defaultOptions: {
watchQuery: {
fetchPolicy: 'cache-and-network',
},
},
});
-Basically i want that,
Old login token should removed on re-login after successfull log-out
-NOTE
This issue comes sometimes but not everytime so not sure where am i going wrong
Any help would be appreciated. THANK YOU :)

react-admin useGetIdentity return only the fullname, id is undefined avatar is undefined

my app is based on tutorial of React-admin and loopback 4 as a backend
I'm trying to get the id of the logged in user, the login mechanisms works well but when i try to access the id of the logged in user it remains undefined.
in my authProvider, my login function is
login: ({ username, password }) => {
const request = new Request(
process.env.REACT_APP_API_URL + '/users/login',
{
method: 'POST',
body: JSON.stringify({ email: username, password }),
headers: new Headers({ 'Content-Type': 'application/json' }),
},
);
return fetch(request)
.then((response) => {
if (response.status < 200 || response.status >= 300) {
throw new Error(response.statusText);
}
return response.json();
})
.then((auth) => {
localStorage.setItem(
'auth',
JSON.stringify({ ...auth, fullName: username }),
);
})
.catch(() => {
throw new Error('Network error');
});
},
and I use this in one component:
const CurrentUserId = ({ id }) => {
const { identity, isLoading: identityLoading } = useGetIdentity();
console.log(identity);
if (identityLoading) {
return <span>Loading...</span>;
} else {
// find the user_id from the identity
const user_email = identity.fullName;
const user_id = identity.id;
return <span>id: {user_id}</span>;
}
};
but the I console.log returns
{id: undefined, fullName: 'xxx#xxxxx.com', avatar: undefined}
I followed the instructions presented here
https://marmelab.com/react-admin/AuthProviderWriting.html
https://marmelab.com/react-admin/useGetIdentity.html
any ideas how to retrieve the id?
thanks a lot
If you receive a JWT token from the server, you need to decode it and store it like this:
import jwtDecode from 'jwt-decode'
...
function saveLBToken({ token } : { token: string }) {
const decoded = jwtDecode(token)
if (decoded && typeof decoded === 'object') {
sessionStorage.setItem(LB4_TOKEN, JSON.stringify({ token, ...decoded }))
} else {
console.log('Bad LB token:', decoded)
}
}
Thanks to MaxAlex answer I ended up using this in my code:
export const authProvider = {
// authentication
login: ({ username, password }) => {
const request = new Request(
process.env.REACT_APP_API_URL + '/users/login',
{
method: 'POST',
body: JSON.stringify({ email: username, password }),
headers: new Headers({ 'Content-Type': 'application/json' }),
},
);
return fetch(request)
.then((response) => {
if (response.status < 200 || response.status >= 300) {
throw new Error(response.statusText);
}
return response.json();
})
.then((auth) => {
const { id, name, email, exp, iat } = jwtDecode(auth.token);
if (!id || !name || !email || !exp || !iat) {
throw new Error('Invalid token');
}
if (exp < iat) {
throw new Error('Token expired');
}
localStorage.setItem(
'auth',
JSON.stringify({
...auth,
id,
fullName: name,
email,
exp,
iat,
}),
);
})
.catch(() => {
throw new Error('Network error');
});
},

React-Admin Simple Refresh JWT Token

I have an react-admin with an auth provider like the below.
I want to refresh my token, but I don't know how to do it.
I tried to follow this blog post, but my auth is a little different and I can't make it work (the error "httpClient(...).then" is not a function and others make me leave it).
I can make it with a more simple solution, does not need to be in memory. I tried to call my refresh endpoint to get my refresh token, but my call go without the current token.
My endpoint to refresh the token is:
/auth/jwt/refresh
I need to call it like this:
curl -X 'GET' \
'http://localhost:8000/auth/jwt/refresh' \
-H 'accept: application/json' \
-H 'Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'
And My response body would be: (and I need to save it to my localstorage or the in memory way)
{
"access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiZTUwZDdhZDctOWE5Ni00NzQyLTgxNWEtZTNmZmJmNGRiMTVjIiwiYXVkIjpbImZhc3RhcGktdXNlcnM6YXV0aCJdLCJleHAiOjE2Mzk4NDE1MDF9.-o2yk56sCj_MZx_VA6PxH7gZ-KKSMmopbDNDiapHmn0",
"token_type": "bearer"
}
My inMemoryJWTManager file:
const inMemoryJWTManager = () => {
let inMemoryJWT = null;
let isRefreshing = null;
let logoutEventName = 'ra-logout';
let refreshEndpoint = '/auth/jwt/refresh';
let refreshTimeOutId;
const setLogoutEventName = name => logoutEventName = name;
const setRefreshTokenEndpoint = endpoint => refreshEndpoint = endpoint;
// This countdown feature is used to renew the JWT before it's no longer valid
// in a way that is transparent to the user.
const refreshToken = (delay) => {
refreshTimeOutId = window.setTimeout(
getRefreshedToken,
delay * 1000 - 5000
); // Validity period of the token in seconds, minus 5 seconds
};
const abordRefreshToken = () => {
if (refreshTimeOutId) {
window.clearTimeout(refreshTimeOutId);
}
};
const waitForTokenRefresh = () => {
if (!isRefreshing) {
return Promise.resolve();
}
return isRefreshing.then(() => {
isRefreshing = null;
return true;
});
}
// The method make a call to the refresh-token endpoint
// If there is a valid cookie, the endpoint will set a fresh jwt in memory.
const getRefreshedToken = () => {
const request = new Request(refreshEndpoint, {
method: 'GET',
headers: new Headers({ 'Content-Type': 'application/json' }),
credentials: 'include',
});
isRefreshing = fetch(request)
.then((response) => {
if (response.status !== 200) {
ereaseToken();
global.console.log(
'Token renewal failure'
);
return { token: null };
}
return response.json();
})
.then(({ token, tokenExpiry }) => {
if (token) {
setToken(token, tokenExpiry);
return true;
}
ereaseToken();
return false;
});
return isRefreshing;
};
const getToken = () => inMemoryJWT;
const setToken = (token, delay) => {
inMemoryJWT = token;
refreshToken(delay);
return true;
};
const ereaseToken = () => {
inMemoryJWT = null;
abordRefreshToken();
window.localStorage.setItem(logoutEventName, Date.now());
return true;
}
// This listener will allow to disconnect a session of ra started in another tab
window.addEventListener('storage', (event) => {
if (event.key === logoutEventName) {
inMemoryJWT = null;
}
});
return {
ereaseToken,
getRefreshedToken,
getToken,
setLogoutEventName,
setRefreshTokenEndpoint,
setToken,
waitForTokenRefresh,
}
};
export default inMemoryJWTManager();
This is my auth provider: (updated, using inMemoryJWTManager)
import inMemoryJWTManager from './inMemoryJWT'
const apiUrl = 'http://localhost:8000'
const authProvider = {
login: ({username, password}) => {
const oAuthParams = {
username,
password
}
const body = Object.keys(oAuthParams).map((key) => {
return encodeURIComponent(key) + '=' + encodeURIComponent(oAuthParams[key]);
}).join('&');
const request = new Request(`${apiUrl}/auth/jwt/login`, {
method: 'POST',
body: body,
headers: new Headers({'Content-Type': 'application/x-www-form-urlencoded'}),
});
return fetch(request)
.then(response => {
if (response.status < 200 || response.status >= 300) {
throw new Error(response.statusText);
}
return response.json();
})
.then(( {access_token} ) => {
inMemoryJWTManager.setToken(access_token);
});
},
checkError: (error) => {
const status = error.status;
if (status === 401 || status === 403) {
inMemoryJWTManager.ereaseToken();
return Promise.reject({redirectTo: '/login'});
}
// other error code (404, 500, etc): no need to log out
return Promise.resolve();
},
checkAuth: () => inMemoryJWTManager.getToken()
? Promise.resolve()
: Promise.reject({ message: 'Login necessário', redirectTo: 'login' }),
logout: () => {
inMemoryJWTManager.ereaseToken();
return Promise.resolve();
},
getPermissions: () => {
return inMemoryJWTManager.getToken() ? Promise.resolve() : Promise.reject();
},
};
export default authProvider;
My updated httpClient code using inMemoryJWTManager: (and I'm using: const dataProvider = jsonServerProvider(apiUrl, httpClient); with modifications to it, but I think it is irrelevant)
const httpClient = (url) => {
const options = {
headers: new Headers({ Accept: 'application/json' }),
};
const token = inMemoryJWTManager.getToken();
console.log(token)
if (token) {
options.headers.set('Authorization', `Bearer ${token}`);
return fetchUtils.fetchJson(url, options);
} else {
inMemoryJWTManager.setRefreshTokenEndpoint(`${apiUrl}/auth/jwt/refresh`);
return inMemoryJWTManager.getRefreshedToken().then((gotFreshToken) => {
if (gotFreshToken) {
options.headers.set('Authorization', `Bearer ${inMemoryJWTManager.getToken()}`);
};
return fetchUtils.fetchJson(url, options);
});
}
};
My problem is that, when I call my refresh token endpoint, my request go without the {'Authorization': Bearer... and it is not renewed and I got logged out. The other endpoints are fine, they go with the token.
You must check token expire before each requests, if token expired you must get new from /auth/jwt/refresh, then you can send current request. All this information is in the article post. Example:
const httpClient = (url) => {
const options = {
headers: new Headers({ Accept: 'application/json' }),
};
const token = inMemoryJWT.getToken();
if (token) {
options.headers.set('Authorization', `Bearer ${token}`);
return fetchUtils.fetchJson(url, options);
} else {
inMemoryJWT.setRefreshTokenEndpoint('http://localhost:8001/refresh-token');
return inMemoryJWT.getRefreshedToken().then((gotFreshToken) => {
if (gotFreshToken) {
options.headers.set('Authorization', `Bearer ${inMemoryJWT.getToken()}`);
};
return fetchUtils.fetchJson(url, options);
});
}
};

Set local storage in react

I am trying to set localstorage in react, but get undefined in value.
Even if I am using JSON.stringify, it doesn't work.
I think, the value is not reaching there.
My code looks like this:
import fetch from "isomorphic-fetch";
import { API } from "../config";
import cookie from "js-cookie";
export const signup = (user) => {
return fetch(`${API}/signup`, {
method: "POST",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
},
body: JSON.stringify(user),
})
.then((response) => {
return response.json();
})
.catch((err) => console.log(err));
};
// login
export const login = (user) => {
return fetch(`${API}/login`, {
method: "POST",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
},
body: JSON.stringify(user),
})
.then((response) => {
console.log(user)
return response.json();
})
.catch((err) => console.log(err));
};
// logout
export const logout = (next) => {
removeCookie("token");
removeLocalStorage("user");
next();
return fetch(`$${API}/logout`, {
method: "GET",
})
.then((response) => {
console.log("Logged Out");
})
.catch((err) => console.log(err));
};
// set cookie
export const setCookie = (key, value) => {
if (process.browser) {
cookie.set(key, value, {
expires: 1,
});
}
};
// remove cookie
export const removeCookie = (key) => {
if (process.browser) {
cookie.remove(key, {
expires: 1,
});
}
};
// get cookie
export const getCookie = (key) => {
if (process.browser) {
return cookie.get(key);
}
};
// set localstorage
export const setLocalStorage = (key, value) => {
if (process.browser) {
localStorage.setItem(key, JSON.stringify(value));
}
};
// remove localstorage
export const removeLocalStorage = (key) => {
if (process.browser) {
localStorage.removeItem(key);
}
};
// authenticate user by passing data to cookie and localstorage
export const authenticate = (data, next) => {
setCookie("token", data.token);
setLocalStorage("user", data.user);
next();
};
export const isAuth = () => {
if (process.browser) {
const cookieChecked = getCookie("token");
if (cookieChecked) {
if (localStorage.getItem("user")) {
return JSON.parse(localStorage.getItem("user"));
} else {
return false;
}
}
}
};

Post, split props ReactJs

I would like to explain my problem of the day.
this is the part of the code that works
here I recovered 2 data "title" and "quantity" here it works very well
postbackend = () => {
const newItems = this.props.items.map((item) => {
const { title, quantity, } = item;
return {
title,
quantity,
};
});
const config = {
method: "POST",
headers: {
"Content-Type": "application/json",
};
body: JSON.stringify({ ...this.state, items: newItems,
};
const url = entrypoint + "/alluserpls";
fetch(url, config)
.then(res => res.json())
.then(res => {
if (res.error) {
alert(res.error);
this.props.history.replace("/OrderSummaryPaymentFalseScreen"); // Your Error Page
} else {
alert(`film ajouté avec l'ID ${res}!`);
this.props.history.push("/OderSummaryScreen"); // Your Success Page
}
}).catch(e => {
console.error(e);
this.props.history.replace("/OrderSummaryPaymentFalseScreen"); // Your Error Page
}).finally(() => this.setState({
redirect: true
}));
so i tried this ,and I would like to separate "title" and "quantity".
example like this
const newItems = this.props.items.map((item) => {
const { title } = item;
return {
title,
};
});
const newQuantity = this.props.items.map((item) => {
const { quantity } = item;
return {
quantity,
};
});
const config = {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ ...this.state, items: newItems, quantityforproduct: newQuantity,
};
but it doesn't work
Do you have an idea of how to fix this? Neff

Resources