From 67bb01526eac8e619078eb205f4bfc65aafdd403 Mon Sep 17 00:00:00 2001 From: KRTirtho Date: Mon, 22 Feb 2021 11:03:40 +0600 Subject: [PATCH] Common authentication mechanism with server-to-server flow works.. --- deploy/config.json | 1 + deploy/linux/spotube/default.desktop | 8 ++++ deploy/linux/spotube/default.png | 0 deploy/linux/spotube/index.js | 6 +++ deploy/linux/spotube/qode.json | 3 ++ src/app.tsx | 49 ++++++++++++++++++--- src/components/Player.tsx | 18 +++++++- src/context/authContext.ts | 8 +++- src/hooks/{useAuth.ts => useAccessToken.ts} | 10 ++--- src/hooks/useSpotifyApi.ts | 21 +++++++++ src/initializations/spotifyApi.ts | 6 +++ 11 files changed, 116 insertions(+), 14 deletions(-) create mode 100644 deploy/config.json create mode 100644 deploy/linux/spotube/default.desktop create mode 100644 deploy/linux/spotube/default.png create mode 100644 deploy/linux/spotube/index.js create mode 100644 deploy/linux/spotube/qode.json rename src/hooks/{useAuth.ts => useAccessToken.ts} (68%) create mode 100644 src/hooks/useSpotifyApi.ts create mode 100644 src/initializations/spotifyApi.ts diff --git a/deploy/config.json b/deploy/config.json new file mode 100644 index 00000000..43d8d8f4 --- /dev/null +++ b/deploy/config.json @@ -0,0 +1 @@ +{"appName":"spotube"} diff --git a/deploy/linux/spotube/default.desktop b/deploy/linux/spotube/default.desktop new file mode 100644 index 00000000..b4ffcef0 --- /dev/null +++ b/deploy/linux/spotube/default.desktop @@ -0,0 +1,8 @@ +[Desktop Entry] +Type=Application +Name=Application +Exec=AppRun %F +Icon=default +Comment=Edit this default file +Terminal=true +Categories=Utility; \ No newline at end of file diff --git a/deploy/linux/spotube/default.png b/deploy/linux/spotube/default.png new file mode 100644 index 00000000..e69de29b diff --git a/deploy/linux/spotube/index.js b/deploy/linux/spotube/index.js new file mode 100644 index 00000000..a631cd51 --- /dev/null +++ b/deploy/linux/spotube/index.js @@ -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"); diff --git a/deploy/linux/spotube/qode.json b/deploy/linux/spotube/qode.json new file mode 100644 index 00000000..7a9ebc58 --- /dev/null +++ b/deploy/linux/spotube/qode.json @@ -0,0 +1,3 @@ +{ + "distPath": "./index.js" +} diff --git a/src/app.tsx b/src/app.tsx index 47dabee2..39f0c45d 100644 --- a/src/app.tsx +++ b/src/app.tsx @@ -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(); + const [currentTrack, setCurrentTrack] = useState(); + + const windowEvents = useEventHandler( + { + 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(false); const [credentials, setCredentials] = useState({ clientId: "", clientSecret: "" }); + const [expires_in, setExpires_in] = useState(0); const [access_token, setAccess_token] = useState(""); const [currentPlaylist, setCurrentPlaylist] = useState(); - const [currentTrack, setCurrentTrack] = useState(); 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 ( - + - + diff --git a/src/components/Player.tsx b/src/components/Player.tsx index af87e30f..18704061 100644 --- a/src/components/Player.tsx +++ b/src/components/Player.tsx @@ -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(initVolume); const [totalDuration, setTotalDuration] = useState(0); const [shuffle, setShuffle] = useState(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 { setShuffle(!shuffle) }} icon={new QIcon(shuffleIcon)} /> prevOrNext(-1) }} icon={new QIcon(backward)} /> - + prevOrNext(1) }} icon={new QIcon(forward)} /> diff --git a/src/context/authContext.ts b/src/context/authContext.ts index 43fdd7c4..8a0d94ce 100644 --- a/src/context/authContext.ts +++ b/src/context/authContext.ts @@ -3,6 +3,8 @@ import React, { Dispatch, SetStateAction } from "react"; export interface AuthContext { isLoggedIn: boolean; setIsLoggedIn: Dispatch>; + 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>; + setExpires_in: (arg: number)=>void; setAccess_token: Dispatch>; } @@ -22,7 +24,9 @@ const authContext = React.createContext({ setIsLoggedIn() {}, access_token: "", expires_in: 0, - setExpiresIn() {}, + clientId: "", + clientSecret: "", + setExpires_in() {}, setAccess_token() {}, }); diff --git a/src/hooks/useAuth.ts b/src/hooks/useAccessToken.ts similarity index 68% rename from src/hooks/useAuth.ts rename to src/hooks/useAccessToken.ts index 410e8956..7e9cf4a6 100644 --- a/src/hooks/useAuth.ts +++ b/src/hooks/useAccessToken.ts @@ -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(); } diff --git a/src/hooks/useSpotifyApi.ts b/src/hooks/useSpotifyApi.ts new file mode 100644 index 00000000..8d7327af --- /dev/null +++ b/src/hooks/useSpotifyApi.ts @@ -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; \ No newline at end of file diff --git a/src/initializations/spotifyApi.ts b/src/initializations/spotifyApi.ts new file mode 100644 index 00000000..f92d3fc9 --- /dev/null +++ b/src/initializations/spotifyApi.ts @@ -0,0 +1,6 @@ +import SpotifyWebApi from "spotify-web-api-node"; +import { redirectURI } from "../conf"; + +const spotifyApi = new SpotifyWebApi({ redirectUri: redirectURI }); + +export default spotifyApi; \ No newline at end of file