Common authentication mechanism with server-to-server flow works..

This commit is contained in:
KRTirtho 2021-02-22 11:03:40 +06:00
parent 114bd0838a
commit 67bb01526e
11 changed files with 116 additions and 14 deletions

1
deploy/config.json Normal file
View File

@ -0,0 +1 @@
{"appName":"spotube"}

View File

@ -0,0 +1,8 @@
[Desktop Entry]
Type=Application
Name=Application
Exec=AppRun %F
Icon=default
Comment=Edit this default file
Terminal=true
Categories=Utility;

View File

View File

@ -0,0 +1,6 @@
const path = require("path");
// Fix so that linux resources are found correctly
// since webpack will bundle them such that the expected path is /dist from cwd
process.chdir(path.resolve(path.dirname(process.execPath)));
// Now start loading the actual bundle
require("./dist");

View File

@ -0,0 +1,3 @@
{
"distPath": "./index.js"
}

View File

@ -1,6 +1,6 @@
import React, { useState, useEffect, useRef } from "react";
import { Window, hot, View } from "@nodegui/react-nodegui";
import { QIcon, QMainWindow, WidgetEventTypes, WindowState } from "@nodegui/nodegui";
import { Window, hot, View, useEventHandler } from "@nodegui/react-nodegui";
import { QIcon, QKeyEvent, QMainWindow, QMainWindowSignals, WidgetEventTypes, WindowState } from "@nodegui/nodegui";
import nodeguiIcon from "../assets/nodegui.jpg";
import { MemoryRouter } from "react-router";
import Routes from "./routes";
@ -13,7 +13,7 @@ import { redirectURI } from "./conf";
export enum CredentialKeys {
credentials = "credentials",
refresh_token = "refresh_token"
refresh_token = "refresh_token",
}
export interface Credentials {
@ -27,15 +27,52 @@ global.localStorage = new LocalStorage("./local");
function RootApp() {
const windowRef = useRef<QMainWindow>();
const [currentTrack, setCurrentTrack] = useState<CurrentTrack>();
const windowEvents = useEventHandler<QMainWindowSignals>(
{
async KeyRelease(nativeEv) {
try {
if (nativeEv) {
const event = new QKeyEvent(nativeEv);
const eventKey = event.key();
console.log('eventKey:', eventKey)
if(audioPlayer.isRunning() && currentTrack)
switch (eventKey) {
case 32: //space
await audioPlayer.isPaused() ?
await audioPlayer.play() : await audioPlayer.pause();
break;
case 16777236: //arrow-right
await audioPlayer.isSeekable() && await audioPlayer.seek(+5);
break;
case 16777234: //arrow-left
await audioPlayer.isSeekable() && await audioPlayer.seek(-5);
break;
default:
break;
}
}
} catch (error) {
console.error("Error in window events: ", error)
}
},
},
[currentTrack]
);
const [isLoggedIn, setIsLoggedIn] = useState<boolean>(false);
const [credentials, setCredentials] = useState<Credentials>({ clientId: "", clientSecret: "" });
const [expires_in, setExpires_in] = useState<number>(0);
const [access_token, setAccess_token] = useState<string>("");
const [currentPlaylist, setCurrentPlaylist] = useState<CurrentPlaylist>();
const [currentTrack, setCurrentTrack] = useState<CurrentTrack>();
const spotifyApi = new SpotifyWebApi({ redirectUri: redirectURI, ...credentials });
const cachedCredentials = localStorage.getItem(CredentialKeys.credentials);
const setExpireTime = (expirationDuration: number) => setExpires_in(Date.now() + expirationDuration);
useEffect(() => {
setIsLoggedIn(!!cachedCredentials);
}, []);
@ -70,9 +107,9 @@ function RootApp() {
}, [isLoggedIn]);
return (
<Window ref={windowRef} windowState={WindowState.WindowMaximized} windowIcon={winIcon} windowTitle="Spotube" minSize={minSize}>
<Window ref={windowRef} on={windowEvents} windowState={WindowState.WindowMaximized} windowIcon={winIcon} windowTitle="Spotube" minSize={minSize}>
<MemoryRouter>
<authContext.Provider value={{ isLoggedIn, setIsLoggedIn, access_token }}>
<authContext.Provider value={{ isLoggedIn, setIsLoggedIn, access_token, expires_in, setAccess_token, setExpires_in: setExpireTime, ...credentials }}>
<playerContext.Provider value={{ spotifyApi, currentPlaylist, currentTrack, setCurrentPlaylist, setCurrentTrack }}>
<View style={`flex: 1; flex-direction: 'column'; justify-content: 'center'; align-items: 'stretch'; height: '100%';`}>
<Routes />

View File

@ -26,6 +26,7 @@ function Player(): ReactElement {
const { currentTrack, currentPlaylist, setCurrentTrack, setCurrentPlaylist, spotifyApi } = useContext(playerContext);
const { access_token } = useContext(authContext);
const initVolume = parseFloat(localStorage.getItem("volume") ?? "55");
const [isPaused, setIsPaused] = useState(true);
const [volume, setVolume] = useState<number>(initVolume);
const [totalDuration, setTotalDuration] = useState(0);
const [shuffle, setShuffle] = useState<boolean>(false);
@ -88,11 +89,13 @@ function Player(): ReactElement {
const youtubeTrack = await getYoutubeTrack(currentTrack);
await audioPlayer.load(youtubeTrack.youtube_uri, "replace");
await audioPlayer.play();
setIsPaused(false);
}
setIsStopped(false);
} catch (error) {
if (error.errcode !== 5) {
setIsStopped(true);
setIsPaused(true);
}
console.error(error);
}
@ -128,17 +131,28 @@ function Player(): ReactElement {
};
const stopListener = () => {
setIsStopped(true);
setIsPaused(true);
// go to next track
if (currentTrack && playlistTracksIds && currentPlaylist?.tracks.length !== 0) {
const index = playlistTracksIds?.indexOf(currentTrack.id) + 1;
setCurrentTrack(currentPlaylist?.tracks[index > playlistTracksIds.length - 1 ? 0 : index].track);
}
};
const pauseListener = () => {
setIsPaused(true);
}
const resumeListener = () => {
setIsPaused(false);
};
audioPlayer.on("status", statusListener);
audioPlayer.on("stopped", stopListener);
audioPlayer.on("paused", pauseListener)
audioPlayer.on("resumed", resumeListener)
return () => {
audioPlayer.off("status", statusListener);
audioPlayer.off("stopped", stopListener);
audioPlayer.off("paused", pauseListener);
audioPlayer.off("resumed", resumeListener);
};
}
});
@ -148,9 +162,11 @@ function Player(): ReactElement {
if ((await audioPlayer.isPaused()) && playerRunning) {
await audioPlayer.play();
setIsStopped(false);
setIsPaused(false);
} else {
await audioPlayer.pause();
setIsStopped(true);
setIsPaused(true);
}
} catch (error) {
console.error(error);
@ -195,7 +211,7 @@ function Player(): ReactElement {
<BoxView direction={Direction.LeftToRight}>
<IconButton style={`background-color: ${shuffle ? "orange" : "rgba(255, 255, 255, 0.055)"}`} on={{ clicked: () => setShuffle(!shuffle) }} icon={new QIcon(shuffleIcon)} />
<IconButton on={{ clicked: () => prevOrNext(-1) }} icon={new QIcon(backward)} />
<IconButton on={{ clicked: handlePlayPause }} icon={new QIcon(isStopped || !currentTrack ? play : pause)} />
<IconButton on={{ clicked: handlePlayPause }} icon={new QIcon(isStopped || isPaused || !currentTrack ? play : pause)} />
<IconButton on={{ clicked: () => prevOrNext(1) }} icon={new QIcon(forward)} />
<IconButton icon={new QIcon(stop)} on={{ clicked: stopPlayback }} />
</BoxView>

View File

@ -3,6 +3,8 @@ import React, { Dispatch, SetStateAction } from "react";
export interface AuthContext {
isLoggedIn: boolean;
setIsLoggedIn: Dispatch<SetStateAction<boolean>>;
clientId: string;
clientSecret: string;
access_token: string;
/**
* the time when the current access token will expire \
@ -13,7 +15,7 @@ export interface AuthContext {
* sets the time when the current access token will expire \
* always update this with `Date.now() + expires_in`
*/
setExpiresIn: Dispatch<SetStateAction<number>>;
setExpires_in: (arg: number)=>void;
setAccess_token: Dispatch<SetStateAction<string>>;
}
@ -22,7 +24,9 @@ const authContext = React.createContext<AuthContext>({
setIsLoggedIn() {},
access_token: "",
expires_in: 0,
setExpiresIn() {},
clientId: "",
clientSecret: "",
setExpires_in() {},
setAccess_token() {},
});

View File

@ -2,14 +2,14 @@ import { DependencyList, useContext, useEffect } from "react";
import authContext from "../context/authContext";
import { CredentialKeys } from "../app";
import playerContext from "../context/playerContext";
import SpotifyWebApi from "spotify-web-api-node";
interface UseAuthResult {
interface UseAccessTokenResult {
access_token: string;
}
export default (deps: DependencyList = []): UseAuthResult => {
const { access_token, expires_in, isLoggedIn, setExpiresIn, setAccess_token } = useContext(authContext);
const { spotifyApi } = useContext(playerContext);
export default (spotifyApi: SpotifyWebApi, deps: DependencyList = []): UseAccessTokenResult => {
const { access_token, expires_in, isLoggedIn, setExpires_in, setAccess_token } = useContext(authContext);
const refreshToken = localStorage.getItem(CredentialKeys.refresh_token);
useEffect(() => {
@ -20,7 +20,7 @@ export default (deps: DependencyList = []): UseAuthResult => {
.refreshAccessToken()
.then(({ body: { access_token, expires_in } }) => {
setAccess_token(access_token);
setExpiresIn(Date.now() + expires_in);
setExpires_in(expires_in);
})
.catch();
}

View File

@ -0,0 +1,21 @@
import { useContext, useEffect } from "react";
import authContext from "../context/authContext";
import spotifyApi from "../initializations/spotifyApi";
import useAccessToken from "./useAccessToken";
function useSpotifyApi() {
const { isLoggedIn, clientId, clientSecret } = useContext(authContext);
const { access_token } = useAccessToken(spotifyApi, [clientId, clientSecret]);
useEffect(() => {
if (isLoggedIn && clientId && clientSecret) {
spotifyApi.setClientId(clientId);
spotifyApi.setClientSecret(clientSecret);
spotifyApi.setAccessToken(access_token);
}
}, [access_token, clientId, clientSecret]);
return spotifyApi;
}
export default useSpotifyApi;

View File

@ -0,0 +1,6 @@
import SpotifyWebApi from "spotify-web-api-node";
import { redirectURI } from "../conf";
const spotifyApi = new SpotifyWebApi({ redirectUri: redirectURI });
export default spotifyApi;