diff options
author | Puyodead1 <puyodead@proton.me> | 2022-12-23 18:34:36 -0500 |
---|---|---|
committer | Puyodead1 <puyodead@proton.me> | 2023-03-18 19:27:39 -0400 |
commit | 0db1fa5f0b2b9b357c1f96178c0e5df7858a99ab (patch) | |
tree | b045eee5f984a40ae47413bad2458cf65eff3c8e /src/connections/Spotify/index.ts | |
parent | Don't try to upload entire config for each connection loaded (diff) | |
download | server-0db1fa5f0b2b9b357c1f96178c0e5df7858a99ab.tar.xz |
Refreshable connections, refactoring, access-token endpoint
- Aded /users/@me/connections/:connection_name/:connection_id/access-token - Replaced `access_token` property on ConnectedAccount with `token_data` object for refreshing tokens - Made a common interface for connection things like ComonOAuthTokenResponse - Added `RefreshableConnection` class - Added token refresh to Spotify connection (disabled)
Diffstat (limited to 'src/connections/Spotify/index.ts')
-rw-r--r-- | src/connections/Spotify/index.ts | 79 |
1 files changed, 62 insertions, 17 deletions
diff --git a/src/connections/Spotify/index.ts b/src/connections/Spotify/index.ts index eb662141..b40d6189 100644 --- a/src/connections/Spotify/index.ts +++ b/src/connections/Spotify/index.ts @@ -1,22 +1,15 @@ import { Config, ConnectedAccount, + ConnectedAccountCommonOAuthTokenResponse, ConnectionCallbackSchema, ConnectionLoader, DiscordApiErrors, } from "@fosscord/util"; import fetch from "node-fetch"; -import Connection from "../../util/connections/Connection"; +import RefreshableConnection from "../../util/connections/RefreshableConnection"; import { SpotifySettings } from "./SpotifySettings"; -interface OAuthTokenResponse { - access_token: string; - token_type: string; - scope: string; - refresh_token?: string; - expires_in?: number; -} - export interface UserResponse { display_name: string; id: string; @@ -34,7 +27,7 @@ export interface ErrorResponse { }; } -export default class SpotifyConnection extends Connection { +export default class SpotifyConnection extends RefreshableConnection { public readonly id = "spotify"; public readonly authorizeUrl = "https://accounts.spotify.com/authorize"; public readonly tokenUrl = "https://accounts.spotify.com/api/token"; @@ -48,6 +41,11 @@ export default class SpotifyConnection extends Connection { settings: SpotifySettings = new SpotifySettings(); init(): void { + /** + * The way Discord shows the currently playing song is by using Spotifys partner API. This is obviously not possible for us. + * So to prevent spamming the spotify api we disable the ability to refresh. + */ + this.refreshEnabled = false; this.settings = ConnectionLoader.getConnectionConfig( this.id, this.settings, @@ -76,7 +74,10 @@ export default class SpotifyConnection extends Connection { return this.tokenUrl; } - async exchangeCode(state: string, code: string): Promise<string> { + async exchangeCode( + state: string, + code: string, + ): Promise<ConnectedAccountCommonOAuthTokenResponse> { this.validateState(state); const url = this.getTokenUrl(); @@ -99,10 +100,15 @@ export default class SpotifyConnection extends Connection { }), }) .then((res) => res.json()) - .then((res: OAuthTokenResponse & TokenErrorResponse) => { - if (res.error) throw new Error(res.error_description); - return res.access_token; - }) + .then( + ( + res: ConnectedAccountCommonOAuthTokenResponse & + TokenErrorResponse, + ) => { + if (res.error) throw new Error(res.error_description); + return res; + }, + ) .catch((e) => { console.error( `Error exchanging token for ${this.id} connection: ${e}`, @@ -111,6 +117,44 @@ export default class SpotifyConnection extends Connection { }); } + async refreshToken(connectedAccount: ConnectedAccount) { + if (!connectedAccount.token_data?.refresh_token) + throw new Error("No refresh token available."); + const refresh_token = connectedAccount.token_data.refresh_token; + const url = this.getTokenUrl(); + + return fetch(url.toString(), { + method: "POST", + headers: { + Accept: "application/json", + "Content-Type": "application/x-www-form-urlencoded", + Authorization: `Basic ${Buffer.from( + `${this.settings.clientId!}:${this.settings.clientSecret!}`, + ).toString("base64")}`, + }, + body: new URLSearchParams({ + grant_type: "refresh_token", + refresh_token, + }), + }) + .then((res) => res.json()) + .then( + ( + res: ConnectedAccountCommonOAuthTokenResponse & + TokenErrorResponse, + ) => { + if (res.error) throw new Error(res.error_description); + return res; + }, + ) + .catch((e) => { + console.error( + `Error refreshing token for ${this.id} connection: ${e}`, + ); + throw DiscordApiErrors.INVALID_OAUTH_TOKEN; + }); + } + async getUser(token: string): Promise<UserResponse> { const url = new URL(this.userInfoUrl); return fetch(url.toString(), { @@ -130,14 +174,15 @@ export default class SpotifyConnection extends Connection { params: ConnectionCallbackSchema, ): Promise<ConnectedAccount | null> { const userId = this.getUserId(params.state); - const token = await this.exchangeCode(params.state, params.code!); - const userInfo = await this.getUser(token); + const tokenData = await this.exchangeCode(params.state, params.code!); + const userInfo = await this.getUser(tokenData.access_token); const exists = await this.hasConnection(userId, userInfo.id); if (exists) return null; return await this.createConnection({ + token_data: tokenData, user_id: userId, external_id: userInfo.id, friend_sync: params.friend_sync, |