From cc3379d726ee8329bcdcb3123f3c41d093c9786c Mon Sep 17 00:00:00 2001 From: Geomitron <22552797+Geomitron@users.noreply.github.com> Date: Tue, 2 May 2023 18:07:02 -0500 Subject: [PATCH] Updated API endponts, Removed Google Auth --- package-lock.json | 4 +- .../settings/settings.component.html | 19 -- .../components/settings/settings.component.ts | 18 -- .../ipc/browse/AlbumArtHandler.ipc.ts | 18 +- .../ipc/browse/BatchSongDetailsHandler.ipc.ts | 18 +- src/electron/ipc/browse/SearchHandler.ipc.ts | 28 +-- .../ipc/browse/SongDetailsHandler.ipc.ts | 18 +- src/electron/ipc/download/ChartDownload.ts | 2 +- src/electron/ipc/download/FileDownloader.ts | 9 +- src/electron/ipc/google/AuthServer.ts | 62 ------ src/electron/ipc/google/GoogleAuth.ts | 190 ------------------ .../ipc/google/GoogleLoginHandler.ipc.ts | 56 ------ src/electron/shared/IPCHandler.ts | 16 -- 13 files changed, 24 insertions(+), 434 deletions(-) delete mode 100644 src/electron/ipc/google/AuthServer.ts delete mode 100644 src/electron/ipc/google/GoogleAuth.ts delete mode 100644 src/electron/ipc/google/GoogleLoginHandler.ipc.ts diff --git a/package-lock.json b/package-lock.json index 98c81d4..d66da25 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "bridge", - "version": "1.4.2", + "version": "1.4.3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "bridge", - "version": "1.4.2", + "version": "1.4.3", "license": "GPL-3.0", "dependencies": { "@angular/animations": "^14.2.12", diff --git a/src/app/components/settings/settings.component.html b/src/app/components/settings/settings.component.html index b9cbe47..704ba29 100644 --- a/src/app/components/settings/settings.component.html +++ b/src/app/components/settings/settings.component.html @@ -25,25 +25,6 @@ -
- -
- -
- sec -
-
-
-
-
- Sign in with Google -
-
-
-
- Sign out -
-
diff --git a/src/app/components/settings/settings.component.ts b/src/app/components/settings/settings.component.ts index 36c965a..5e4f41e 100644 --- a/src/app/components/settings/settings.component.ts +++ b/src/app/components/settings/settings.component.ts @@ -14,7 +14,6 @@ export class SettingsComponent implements OnInit, AfterViewInit { cacheSize = 'Calculating...' updateAvailable = false - loginAvailable = true loginClicked = false downloadUpdateText = 'Update available' retryUpdateText = 'Failed to check for update' @@ -53,10 +52,6 @@ export class SettingsComponent implements OnInit, AfterViewInit { this.updateAvailable = isAvailable this.ref.detectChanges() }) - this.electronService.invoke('get-auth-status', undefined).then(isAuthenticated => { - this.loginAvailable = !isAuthenticated - this.ref.detectChanges() - }) const cacheSize = await this.settingsService.getCacheSize() this.cacheSize = Math.round(cacheSize / 1000000) + ' MB' @@ -95,19 +90,6 @@ export class SettingsComponent implements OnInit, AfterViewInit { } } - async googleLogin() { - if (this.loginClicked) { return } - this.loginClicked = true - const isAuthenticated = await this.electronService.invoke('google-login', undefined) - this.loginAvailable = !isAuthenticated - this.loginClicked = false - } - - async googleLogout() { - this.loginAvailable = true - await this.electronService.invoke('google-logout', undefined) - } - openLibraryDirectory() { this.electronService.openFolder(this.settingsService.libraryDirectory) } diff --git a/src/electron/ipc/browse/AlbumArtHandler.ipc.ts b/src/electron/ipc/browse/AlbumArtHandler.ipc.ts index 0e781b0..8c3f1db 100644 --- a/src/electron/ipc/browse/AlbumArtHandler.ipc.ts +++ b/src/electron/ipc/browse/AlbumArtHandler.ipc.ts @@ -1,6 +1,5 @@ import { IPCInvokeHandler } from '../../shared/IPCHandler' import { AlbumArtResult } from '../../shared/interfaces/songDetails.interface' -import * as needle from 'needle' import { serverURL } from '../../shared/Paths' /** @@ -12,20 +11,9 @@ class AlbumArtHandler implements IPCInvokeHandler<'album-art'> { /** * @returns an `AlbumArtResult` object containing the album art for the song with `songID`. */ - async handler(songID: number) { - return new Promise((resolve, reject) => { - needle.request( - 'get', - serverURL + `/api/data/albumArt`, { - songID: songID - }, (err, response) => { - if (err) { - reject(err) - } else { - resolve(response.body) - } - }) - }) + async handler(songID: number): Promise { + const response = await fetch(`https://${serverURL}/api/data/album-art/${songID}`) + return await response.json() } } diff --git a/src/electron/ipc/browse/BatchSongDetailsHandler.ipc.ts b/src/electron/ipc/browse/BatchSongDetailsHandler.ipc.ts index 5a64054..0182db3 100644 --- a/src/electron/ipc/browse/BatchSongDetailsHandler.ipc.ts +++ b/src/electron/ipc/browse/BatchSongDetailsHandler.ipc.ts @@ -1,7 +1,6 @@ import { IPCInvokeHandler } from '../../shared/IPCHandler' import { VersionResult } from '../../shared/interfaces/songDetails.interface' import { serverURL } from '../../shared/Paths' -import * as needle from 'needle' /** * Handles the 'batch-song-details' event. @@ -12,20 +11,9 @@ class BatchSongDetailsHandler implements IPCInvokeHandler<'batch-song-details'> /** * @returns an array of all the chart versions with a songID found in `songIDs`. */ - async handler(songIDs: number[]) { - return new Promise((resolve, reject) => { - needle.request( - 'get', - serverURL + `/api/data/songsVersions`, { - songIDs: songIDs - }, (err, response) => { - if (err) { - reject(err) - } else { - resolve(response.body) - } - }) - }) + async handler(songIDs: number[]): Promise { + const response = await fetch(`https://${serverURL}/api/data/song-versions/${songIDs.join(',')}`) + return await response.json() } } diff --git a/src/electron/ipc/browse/SearchHandler.ipc.ts b/src/electron/ipc/browse/SearchHandler.ipc.ts index 4aaed85..838f604 100644 --- a/src/electron/ipc/browse/SearchHandler.ipc.ts +++ b/src/electron/ipc/browse/SearchHandler.ipc.ts @@ -1,6 +1,5 @@ import { IPCInvokeHandler } from '../../shared/IPCHandler' -import { SongSearch, SongResult } from '../../shared/interfaces/search.interface' -import * as needle from 'needle' +import { SongResult, SongSearch } from '../../shared/interfaces/search.interface' import { serverURL } from '../../shared/Paths' /** @@ -12,23 +11,16 @@ class SearchHandler implements IPCInvokeHandler<'song-search'> { /** * @returns the top 50 songs that match `search`. */ - async handler(search: SongSearch) { - return new Promise((resolve, reject) => { - needle.request( - 'get', - serverURL + `/api/search`, search, (err, response) => { - if (err) { - reject(err.message) - } else { - if (response.body.errors) { - console.log(response.body) - resolve([]) - } else { - resolve(response.body) - } - } - }) + async handler(search: SongSearch): Promise { + const response = await fetch(`https://${serverURL}/api/search`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify(search) }) + + return await response.json() } } diff --git a/src/electron/ipc/browse/SongDetailsHandler.ipc.ts b/src/electron/ipc/browse/SongDetailsHandler.ipc.ts index 634c376..8f257fa 100644 --- a/src/electron/ipc/browse/SongDetailsHandler.ipc.ts +++ b/src/electron/ipc/browse/SongDetailsHandler.ipc.ts @@ -1,6 +1,5 @@ import { IPCInvokeHandler } from '../../shared/IPCHandler' import { VersionResult } from '../../shared/interfaces/songDetails.interface' -import * as needle from 'needle' import { serverURL } from '../../shared/Paths' /** @@ -12,20 +11,9 @@ class SongDetailsHandler implements IPCInvokeHandler<'song-details'> { /** * @returns the chart versions with `songID`. */ - async handler(songID: number) { - return new Promise((resolve, reject) => { - needle.request( - 'get', - serverURL + `/api/data/songVersions`, { - songID: songID - }, (err, response) => { - if (err) { - reject(err) - } else { - resolve(response.body) - } - }) - }) + async handler(songID: number): Promise { + const response = await fetch(`https://${serverURL}/api/data/song-versions/${songID}`) + return await response.json() } } diff --git a/src/electron/ipc/download/ChartDownload.ts b/src/electron/ipc/download/ChartDownload.ts index 1586cf1..6797fc2 100644 --- a/src/electron/ipc/download/ChartDownload.ts +++ b/src/electron/ipc/download/ChartDownload.ts @@ -145,7 +145,7 @@ export class ChartDownload { for (let i = 0; i < this.files.length; i++) { let wasCanceled = false this.cancelFn = () => { wasCanceled = true } - const downloader = await getDownloader(this.files[i].webContentLink, join(this.tempPath, this.files[i].name)) + const downloader = getDownloader(this.files[i].webContentLink, join(this.tempPath, this.files[i].name)) if (wasCanceled) { return } this.cancelFn = () => downloader.cancelDownload() diff --git a/src/electron/ipc/download/FileDownloader.ts b/src/electron/ipc/download/FileDownloader.ts index f210d0c..109c120 100644 --- a/src/electron/ipc/download/FileDownloader.ts +++ b/src/electron/ipc/download/FileDownloader.ts @@ -6,7 +6,6 @@ import { Readable } from 'stream' // TODO: replace needle with got (for cancel() method) (if before-headers event is possible?) import { googleTimer } from './GoogleTimer' import { DownloadError } from './ChartDownload' -import { googleAuth } from '../google/GoogleAuth' import { google } from 'googleapis' import Bottleneck from 'bottleneck' import { promisify } from 'util' @@ -49,12 +48,8 @@ const downloadErrors = { * @param url The download link. * @param fullPath The full path to where this file should be stored (including the filename). */ -export async function getDownloader(url: string, fullPath: string): Promise { - if (await googleAuth.attemptToAuthenticate()) { - return new APIFileDownloader(url, fullPath) - } else { - return new SlowFileDownloader(url, fullPath) - } +export function getDownloader(url: string, fullPath: string): FileDownloader { + return new SlowFileDownloader(url, fullPath) } /** diff --git a/src/electron/ipc/google/AuthServer.ts b/src/electron/ipc/google/AuthServer.ts deleted file mode 100644 index 927a533..0000000 --- a/src/electron/ipc/google/AuthServer.ts +++ /dev/null @@ -1,62 +0,0 @@ -import * as http from 'http' -import { URL } from 'url' -import { REDIRECT_PATH, REDIRECT_BASE, SERVER_PORT } from '../../shared/Paths' - -type EventCallback = { - 'listening': () => void - 'authCode': (authCode: string) => Promise -} -type Callbacks = { [E in keyof EventCallback]: EventCallback[E] } - -class AuthServer { - - private server: http.Server - private callbacks = {} as Callbacks - private connections = {} - - /** - * Calls `callback` when `event` fires. (no events will be fired after `this.cancelDownload()` is called) - */ - on(event: E, callback: EventCallback[E]) { - this.callbacks[event] = callback - } - - /** - * Starts listening on `SERVER_PORT` for the authentication callback. - * Emits the 'listening' event when the server is ready to listen. - * Emits the 'authCode' event when the callback request provides the authentication code. - */ - startServer() { - if (this.server != null) { - this.callbacks.listening() - } else { - this.server = http.createServer(this.requestListener.bind(this)) - this.server.on('connection', (conn) => { - const key = conn.remoteAddress + ':' + conn.remotePort - this.connections[key] = conn - conn.on('close', () => delete this.connections[key]) - }) - - this.server.listen(SERVER_PORT, () => this.callbacks.listening()) - } - } - - private requestListener(req: http.IncomingMessage, res: http.ServerResponse) { - if (req.url.includes(REDIRECT_PATH)) { - const searchParams = new URL(req.url, REDIRECT_BASE).searchParams - res.end() - this.destroyServer() - this.callbacks.authCode(searchParams.get('code')) - } - } - - private destroyServer() { - this.server.close() - for (const key in this.connections) { - this.connections[key].destroy() - } - this.server = null - } -} - -export const authServer = new AuthServer() \ No newline at end of file diff --git a/src/electron/ipc/google/GoogleAuth.ts b/src/electron/ipc/google/GoogleAuth.ts deleted file mode 100644 index 7c28fe1..0000000 --- a/src/electron/ipc/google/GoogleAuth.ts +++ /dev/null @@ -1,190 +0,0 @@ -/* eslint-disable @typescript-eslint/no-misused-promises */ -/* eslint-disable @typescript-eslint/camelcase */ -import { dataPath, REDIRECT_URI } from '../../shared/Paths' -import { mainWindow } from '../../main' -import { join } from 'path' -import { readFile, writeFile } from 'jsonfile' -import { google } from 'googleapis' -import { Credentials } from 'googleapis/node_modules/google-auth-library/build/src/auth/credentials' -import { OAuth2Client } from 'googleapis-common/node_modules/google-auth-library/build/src/auth/oauth2client' -import * as needle from 'needle' -import { authServer } from './AuthServer' -import { BrowserWindow } from 'electron' -import { serverURL } from '../../shared/Paths' -import * as fs from 'fs' -import { promisify } from 'util' -import { devLog } from '../../shared/ElectronUtilFunctions' -import { serializeError } from 'serialize-error' - -const unlink = promisify(fs.unlink) - -const TOKEN_PATH = join(dataPath, 'token.json') - -export class GoogleAuth { - - private hasAuthenticated = false - - private oAuth2Client: OAuth2Client = null - private token: Credentials = null - - /** - * Attempts to authenticate the googleapis library using the token stored at `TOKEN_PATH`. - * @returns `true` if the user is authenticated, and `false` otherwise. - */ - async attemptToAuthenticate() { - - if (this.hasAuthenticated) { - return true - } - - // Get client info from server - if (!await this.getOAuth2Client()) { - return false - } - - // Get stored token - if (!await this.getStoredToken()) { - return false - } - - // Token has been restored from a previous session - this.authenticateWithToken() - return true - } - - /** - * Uses OAuth2 to generate a token that can be used to authenticate download requests. - * Involves displaying a popup window to the user. - * @returns true if the auth token was generated, and false otherwise. - */ - async generateAuthToken() { - - if (this.hasAuthenticated) { - return true - } - - // Get client info from server - if (!await this.getOAuth2Client()) { - return false - } - - let popupWindow: BrowserWindow - - return new Promise(resolve => { - authServer.on('listening', () => { - const authUrl = this.oAuth2Client.generateAuthUrl({ - access_type: 'offline', - // This scope is too broad, but is the only one that will actually download files for some dumb reason. - // If you want this fixed, please upvote/star my issue on the Google bug tracker so they will fix it faster: - // https://issuetracker.google.com/issues/168687448 - scope: ['https://www.googleapis.com/auth/drive.readonly'], - redirect_uri: REDIRECT_URI - }) - - popupWindow = new BrowserWindow({ - fullscreenable: false, - modal: true, - maximizable: false, - minimizable: false, - show: false, - parent: mainWindow, - autoHideMenuBar: true, - center: true, - thickFrame: true, - useContentSize: true, - width: 400 - }) - popupWindow.loadURL(authUrl, { userAgent: 'Chrome' }) - popupWindow.on('ready-to-show', () => popupWindow.show()) - popupWindow.on('closed', () => resolve(this.hasAuthenticated)) - }) - - authServer.on('authCode', async (authCode) => { - this.token = (await this.oAuth2Client.getToken(authCode)).tokens - writeFile(TOKEN_PATH, this.token).catch(err => devLog('Got token, but failed to write it to TOKEN_PATH:', serializeError(err))) - - this.authenticateWithToken() - - popupWindow.close() - }) - - authServer.startServer() - }) - } - - /** - * Use this.token as the credentials for this.oAuth2Client, and make the google library use this authentication. - * Assumes these have already been defined correctly. - */ - private authenticateWithToken() { - this.oAuth2Client.setCredentials(this.token) - google.options({ auth: this.oAuth2Client }) - this.hasAuthenticated = true - } - - /** - * Attempts to get Bridge's client info from the server. - * @returns true if this.clientID and this.clientSecret have been set, and false if that failed. - */ - private async getOAuth2Client() { - if (this.oAuth2Client != null) { - return true - } else { - return new Promise(resolve => { - needle.request( - 'get', - serverURL + `/api/data/client`, null, (err, response) => { - if (err) { - devLog('Could not authenticate because client info could not be retrieved from the server:', serializeError(err)) - resolve(false) - } else { - this.oAuth2Client = new google.auth.OAuth2(response.body.CLIENT_ID, response.body.CLIENT_SECRET, REDIRECT_URI) - resolve(true) - } - }) - }) - } - } - - /** - * Attempts to retrieve a previously stored auth token at `TOKEN_PATH`. - * Note: will not try again if this.token === undefined. - * @returns true if this.token has been set, and false if that failed or the token didn't exist. - */ - private async getStoredToken() { - if (this.token === undefined) { - return false // undefined means no token file was found - } else if (this.token !== null) { - return true - } else { - try { - this.token = await readFile(TOKEN_PATH) - return true - } catch (err) { - if (err?.code && err?.code != 'ENOENT') { - this.token = null // File exists but could not be accessed; next attempt should try again - } else { - this.token = undefined - } - - return false - } - } - } - - /** - * Removes a previously stored auth token from `TOKEN_PATH`. - */ - async deleteStoredToken() { - this.token = undefined - this.hasAuthenticated = false - try { - await unlink(TOKEN_PATH) - } catch (err) { - devLog('Failed to delete token:', serializeError(err)) - return - } - } -} - -export const googleAuth = new GoogleAuth() \ No newline at end of file diff --git a/src/electron/ipc/google/GoogleLoginHandler.ipc.ts b/src/electron/ipc/google/GoogleLoginHandler.ipc.ts deleted file mode 100644 index 76a5489..0000000 --- a/src/electron/ipc/google/GoogleLoginHandler.ipc.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { IPCInvokeHandler } from '../../shared/IPCHandler' -import { googleAuth } from './GoogleAuth' - -/** - * Handles the 'google-login' event. - */ -class GoogleLoginHandler implements IPCInvokeHandler<'google-login'> { - event: 'google-login' = 'google-login' - - /** - * @returns `true` if the user has been authenticated. - */ - async handler() { - return new Promise(resolve => { - googleAuth.generateAuthToken().then((isLoggedIn) => resolve(isLoggedIn)) - }) - } -} - -export const googleLoginHandler = new GoogleLoginHandler() - -/** - * Handles the 'google-login' event. - */ -class GoogleLogoutHandler implements IPCInvokeHandler<'google-logout'> { - event: 'google-logout' = 'google-logout' - - /** - * @returns `true` if the user has been authenticated. - */ - async handler() { - return new Promise(resolve => { - googleAuth.deleteStoredToken().then(() => resolve(undefined)) - }) - } -} - -export const googleLogoutHandler = new GoogleLogoutHandler() - -/** - * Handles the 'get-auth-status' event. - */ -class GetAuthStatusHandler implements IPCInvokeHandler<'get-auth-status'> { - event: 'get-auth-status' = 'get-auth-status' - - /** - * @returns `true` if the user is authenticated with Google. - */ - handler() { - return new Promise(resolve => { - googleAuth.attemptToAuthenticate().then(isAuthenticated => resolve(isAuthenticated)) - }) - } -} - -export const getAuthStatusHandler = new GetAuthStatusHandler() \ No newline at end of file diff --git a/src/electron/shared/IPCHandler.ts b/src/electron/shared/IPCHandler.ts index 913cc3f..718b51a 100644 --- a/src/electron/shared/IPCHandler.ts +++ b/src/electron/shared/IPCHandler.ts @@ -9,7 +9,6 @@ import { Settings } from './Settings' import { batchSongDetailsHandler } from '../ipc/browse/BatchSongDetailsHandler.ipc' import { getSettingsHandler, setSettingsHandler } from '../ipc/SettingsHandler.ipc' import { clearCacheHandler } from '../ipc/CacheHandler.ipc' -import { googleLoginHandler, getAuthStatusHandler, googleLogoutHandler } from '../ipc/google/GoogleLoginHandler.ipc' import { updateChecker, UpdateProgress, getCurrentVersionHandler, downloadUpdateHandler, quitAndInstallHandler, getUpdateAvailableHandler } from '../ipc/UpdateHandler.ipc' import { UpdateInfo } from 'electron-updater' import { openURLHandler } from '../ipc/OpenURLHandler.ipc' @@ -32,9 +31,6 @@ export function getIPCInvokeHandlers(): IPCInvokeHandler[ albumArtHandler, getCurrentVersionHandler, getUpdateAvailableHandler, - googleLoginHandler, - googleLogoutHandler, - getAuthStatusHandler ] } @@ -74,18 +70,6 @@ export type IPCInvokeEvents = { input: undefined output: boolean } - 'google-login': { - input: undefined - output: boolean - } - 'google-logout': { - input: undefined - output: undefined - } - 'get-auth-status': { - input: undefined - output: boolean - } } /**