mirror of
https://github.com/KRTirtho/spotube.git
synced 2025-09-13 07:55:18 +00:00
Common authentication mechanism with server-to-server flow works..
This commit is contained in:
parent
114bd0838a
commit
67bb01526e
1
deploy/config.json
Normal file
1
deploy/config.json
Normal file
@ -0,0 +1 @@
|
||||
{"appName":"spotube"}
|
8
deploy/linux/spotube/default.desktop
Normal file
8
deploy/linux/spotube/default.desktop
Normal 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;
|
0
deploy/linux/spotube/default.png
Normal file
0
deploy/linux/spotube/default.png
Normal file
6
deploy/linux/spotube/index.js
Normal file
6
deploy/linux/spotube/index.js
Normal 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");
|
3
deploy/linux/spotube/qode.json
Normal file
3
deploy/linux/spotube/qode.json
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"distPath": "./index.js"
|
||||
}
|
49
src/app.tsx
49
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<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 />
|
||||
|
@ -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>
|
||||
|
@ -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() {},
|
||||
});
|
||||
|
||||
|
@ -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();
|
||||
}
|
21
src/hooks/useSpotifyApi.ts
Normal file
21
src/hooks/useSpotifyApi.ts
Normal 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;
|
6
src/initializations/spotifyApi.ts
Normal file
6
src/initializations/spotifyApi.ts
Normal 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;
|
Loading…
Reference in New Issue
Block a user