From 77e665c84049b21a5d75c7b2f6d43d3f92de8dc1 Mon Sep 17 00:00:00 2001 From: anshg1214 Date: Sun, 14 Jul 2024 11:50:45 +0000 Subject: [PATCH 1/2] feat: Update token in context after being refreshed --- .../src/common/brainzplayer/BrainzPlayer.tsx | 10 +-------- .../common/brainzplayer/SoundcloudPlayer.tsx | 20 ++++++++++++------ .../src/common/brainzplayer/SpotifyPlayer.tsx | 21 ++++++++++++++----- 3 files changed, 31 insertions(+), 20 deletions(-) diff --git a/frontend/js/src/common/brainzplayer/BrainzPlayer.tsx b/frontend/js/src/common/brainzplayer/BrainzPlayer.tsx index f0e07e0bc5..e0e3971fac 100644 --- a/frontend/js/src/common/brainzplayer/BrainzPlayer.tsx +++ b/frontend/js/src/common/brainzplayer/BrainzPlayer.tsx @@ -902,13 +902,7 @@ export default class BrainzPlayer extends React.Component< refreshSoundcloudToken, listenBrainzAPIBaseURI, } = this.props; - const { - youtubeAuth, - spotifyAuth, - soundcloudAuth, - appleAuth, - userPreferences, - } = this.context; + const { youtubeAuth, userPreferences } = this.context; const brainzPlayerDisabled = userPreferences?.brainzplayer?.spotifyEnabled === false && userPreferences?.brainzplayer?.youtubeEnabled === false && @@ -949,7 +943,6 @@ export default class BrainzPlayer extends React.Component< refreshSpotifyToken={refreshSpotifyToken} onInvalidateDataSource={this.invalidateDataSource} ref={this.spotifyPlayer} - spotifyUser={spotifyAuth} playerPaused={playerPaused} onPlayerPausedChange={this.playerPauseChange} onProgressChange={this.progressChange} @@ -994,7 +987,6 @@ export default class BrainzPlayer extends React.Component< } onInvalidateDataSource={this.invalidateDataSource} ref={this.soundcloudPlayer} - soundcloudUser={soundcloudAuth} refreshSoundcloudToken={refreshSoundcloudToken} playerPaused={playerPaused} onPlayerPausedChange={this.playerPauseChange} diff --git a/frontend/js/src/common/brainzplayer/SoundcloudPlayer.tsx b/frontend/js/src/common/brainzplayer/SoundcloudPlayer.tsx index 814c5a2cae..5278cfb5fb 100644 --- a/frontend/js/src/common/brainzplayer/SoundcloudPlayer.tsx +++ b/frontend/js/src/common/brainzplayer/SoundcloudPlayer.tsx @@ -8,6 +8,7 @@ import { getTrackName, searchForSoundcloudTrack, } from "../../utils/utils"; +import GlobalAppContext from "../../utils/GlobalAppContext"; require("../../../lib/soundcloud-player-api"); @@ -61,13 +62,13 @@ export type SoundcloudPlayerState = { }; export type SoundCloudPlayerProps = DataSourceProps & { - soundcloudUser?: SoundCloudUser; refreshSoundcloudToken: () => Promise; }; export default class SoundcloudPlayer extends React.Component implements DataSourceType { + static contextType = GlobalAppContext; static hasPermissions = (soundcloudUser?: SoundCloudUser) => { return Boolean(soundcloudUser?.access_token); }; @@ -112,19 +113,22 @@ export default class SoundcloudPlayer // and it simplifies some of the closure issues we've had with old tokens. private accessToken = ""; private authenticationRetries = 0; + declare context: React.ContextType; constructor(props: SoundCloudPlayerProps) { super(props); - this.accessToken = props.soundcloudUser?.access_token || ""; this.state = { currentSound: undefined }; this.iFrameRef = React.createRef(); + } + + componentDidMount() { + const { soundcloudAuth: soundcloudUser = undefined } = this.context; + this.accessToken = soundcloudUser?.access_token || ""; // initial permissions check - if (!SoundcloudPlayer.hasPermissions(props.soundcloudUser)) { + if (!SoundcloudPlayer.hasPermissions(soundcloudUser)) { this.handleAccountError(); } - } - componentDidMount() { const { onInvalidateDataSource } = this.props; if (!(window as any).SC) { onInvalidateDataSource(this, "Soundcloud JS API did not load properly."); @@ -204,7 +208,7 @@ export default class SoundcloudPlayer }; canSearchAndPlayTracks = (): boolean => { - const { soundcloudUser } = this.props; + const { soundcloudAuth: soundcloudUser = undefined } = this.context; // check if the user is authed to search with the SoundCloud API return Boolean(soundcloudUser) && Boolean(soundcloudUser?.access_token); }; @@ -264,6 +268,7 @@ export default class SoundcloudPlayer callbackFunction: () => void ): Promise => { const { refreshSoundcloudToken, onTrackNotFound, handleError } = this.props; + const { soundcloudAuth: soundcloudUser = undefined } = this.context; if (this.authenticationRetries > 5) { handleError( isString(error) ? error : error?.message, @@ -276,6 +281,9 @@ export default class SoundcloudPlayer try { this.accessToken = await refreshSoundcloudToken(); this.authenticationRetries = 0; + if (soundcloudUser) { + soundcloudUser.access_token = this.accessToken; + } callbackFunction(); } catch (refreshError) { handleError(refreshError, "Error connecting to SoundCloud"); diff --git a/frontend/js/src/common/brainzplayer/SpotifyPlayer.tsx b/frontend/js/src/common/brainzplayer/SpotifyPlayer.tsx index 3edd2ebe8e..6b3522d5f4 100644 --- a/frontend/js/src/common/brainzplayer/SpotifyPlayer.tsx +++ b/frontend/js/src/common/brainzplayer/SpotifyPlayer.tsx @@ -18,6 +18,7 @@ import { getArtistName, } from "../../utils/utils"; import { DataSourceType, DataSourceProps } from "./BrainzPlayer"; +import GlobalAppContext from "../../utils/GlobalAppContext"; // Fix for LB-447 (Player does not play any sound) // https://github.com/spotify/web-playback-sdk/issues/75#issuecomment-487325589 @@ -34,7 +35,6 @@ const fixSpotifyPlayerStyleIssue = () => { }; export type SpotifyPlayerProps = DataSourceProps & { - spotifyUser?: SpotifyUser; refreshSpotifyToken: () => Promise; }; @@ -48,6 +48,7 @@ export type SpotifyPlayerState = { export default class SpotifyPlayer extends React.Component implements DataSourceType { + static contextType = GlobalAppContext; static hasPermissions = (spotifyUser?: SpotifyUser) => { if (!spotifyUser) { return false; @@ -109,11 +110,11 @@ export default class SpotifyPlayer private authenticationRetries = 0; spotifyPlayer?: SpotifyPlayerType; debouncedOnTrackEnd: () => void; + declare context: React.ContextType; constructor(props: SpotifyPlayerProps) { super(props); - this.accessToken = props.spotifyUser?.access_token || ""; this.state = { durationMs: 0, }; @@ -122,9 +123,15 @@ export default class SpotifyPlayer leading: true, trailing: false, }); + } + + async componentDidMount(): Promise { + const { spotifyAuth: spotifyUser = undefined } = this.context; + + this.accessToken = spotifyUser?.access_token || ""; // Do an initial check of the spotify token permissions (scopes) before loading the SDK library - if (SpotifyPlayer.hasPermissions(props.spotifyUser)) { + if (SpotifyPlayer.hasPermissions(spotifyUser)) { window.onSpotifyWebPlaybackSDKReady = this.connectSpotifyPlayer; loadScriptAsync(document, "https://sdk.scdn.co/spotify-player.js"); } else { @@ -287,12 +294,12 @@ export default class SpotifyPlayer }; canSearchAndPlayTracks = (): boolean => { - const { spotifyUser } = this.props; + const { spotifyAuth: spotifyUser = undefined } = this.context; return SpotifyPlayer.hasPermissions(spotifyUser); }; datasourceRecordsListens = (): boolean => { - const { spotifyUser } = this.props; + const { spotifyAuth: spotifyUser = undefined } = this.context; if (!spotifyUser?.permission) { return false; } @@ -428,6 +435,7 @@ export default class SpotifyPlayer return; } const { refreshSpotifyToken } = this.props; + const { spotifyAuth: spotifyUser = undefined } = this.context; this.spotifyPlayer = new window.Spotify.Player({ name: "ListenBrainz Player", @@ -436,6 +444,9 @@ export default class SpotifyPlayer const userToken = await refreshSpotifyToken(); this.accessToken = userToken; this.authenticationRetries = 0; + if (spotifyUser) { + spotifyUser.access_token = userToken; + } authCallback(userToken); } catch (error) { handleError(error, "Error connecting to Spotify"); From 37b4c082efff9ddb9e763a6a038ec3c42d880950 Mon Sep 17 00:00:00 2001 From: anshg1214 Date: Sun, 14 Jul 2024 11:50:54 +0000 Subject: [PATCH 2/2] test: Fix tests to use auth from context --- .../brainzplayer/SoundcloudPlayer.test.tsx | 43 ++++++++----- .../brainzplayer/SpotifyPlayer.test.tsx | 60 +++++++++++++------ 2 files changed, 71 insertions(+), 32 deletions(-) diff --git a/frontend/js/tests/common/brainzplayer/SoundcloudPlayer.test.tsx b/frontend/js/tests/common/brainzplayer/SoundcloudPlayer.test.tsx index 9fdde6e72d..80b493176f 100644 --- a/frontend/js/tests/common/brainzplayer/SoundcloudPlayer.test.tsx +++ b/frontend/js/tests/common/brainzplayer/SoundcloudPlayer.test.tsx @@ -1,22 +1,32 @@ import * as React from "react"; -import { mount, ReactWrapper, shallow } from "enzyme"; +import { mount } from "enzyme"; import { act } from "react-dom/test-utils"; -import SoundcloudPlayer, { - SoundcloudPlayerState, -} from "../../../src/common/brainzplayer/SoundcloudPlayer"; -import { - DataSourceProps, - DataSourceTypes, -} from "../../../src/common/brainzplayer/BrainzPlayer"; +import SoundcloudPlayer from "../../../src/common/brainzplayer/SoundcloudPlayer"; +import { DataSourceTypes } from "../../../src/common/brainzplayer/BrainzPlayer"; import APIService from "../../../src/utils/APIService"; +import RecordingFeedbackManager from "../../../src/utils/RecordingFeedbackManager"; +import GlobalAppContext from "../../../src/utils/GlobalAppContext"; + +// Create a new instance of GlobalAppContext +const defaultContext = { + APIService: new APIService("foo"), + websocketsUrl: "", + youtubeAuth: {} as YoutubeUser, + spotifyAuth: {} as SpotifyUser, + currentUser: {} as ListenBrainzUser, + soundcloudAuth: { + access_token: "heyo-soundcloud", + }, + recordingFeedbackManager: new RecordingFeedbackManager( + new APIService("foo"), + { name: "Fnord" } + ), +}; const props = { show: true, playerPaused: false, - soundcloudUser: { - access_token: "heyo-soundcloud", - }, refreshSoundcloudToken: new APIService("base-uri").refreshSoundcloudToken, onPlayerPausedChange: (paused: boolean) => {}, onProgressChange: (progressMs: number) => {}, @@ -65,11 +75,16 @@ describe("SoundcloudPlayer", () => { onInvalidateDataSource, onTrackNotFound, }; - const wrapper = mount( - + + const wrapper = mount( + + + ); + const instance = wrapper + .find(SoundcloudPlayer) + .instance() as SoundcloudPlayer; - const instance = wrapper.instance(); if (!instance.soundcloudPlayer) { throw new Error("no SoundcloudPlayer"); } diff --git a/frontend/js/tests/common/brainzplayer/SpotifyPlayer.test.tsx b/frontend/js/tests/common/brainzplayer/SpotifyPlayer.test.tsx index 99530e70b2..c69e1db06d 100644 --- a/frontend/js/tests/common/brainzplayer/SpotifyPlayer.test.tsx +++ b/frontend/js/tests/common/brainzplayer/SpotifyPlayer.test.tsx @@ -1,23 +1,31 @@ import * as React from "react"; -import { mount, ReactWrapper, shallow, ShallowWrapper } from "enzyme"; +import { mount, shallow } from "enzyme"; import { act } from "react-dom/test-utils"; import { Link } from "react-router-dom"; -import SpotifyPlayer, { - SpotifyPlayerProps, - SpotifyPlayerState, -} from "../../../src/common/brainzplayer/SpotifyPlayer"; +import SpotifyPlayer from "../../../src/common/brainzplayer/SpotifyPlayer"; import APIService from "../../../src/utils/APIService"; -import { - DataSourceProps, - DataSourceTypes, -} from "../../../src/common/brainzplayer/BrainzPlayer"; +import { DataSourceTypes } from "../../../src/common/brainzplayer/BrainzPlayer"; +import GlobalAppContext from "../../../src/utils/GlobalAppContext"; +import RecordingFeedbackManager from "../../../src/utils/RecordingFeedbackManager"; -const props = { - spotifyUser: { +// Create a new instance of GlobalAppContext +const defaultContext = { + APIService: new APIService("foo"), + websocketsUrl: "", + youtubeAuth: {} as YoutubeUser, + spotifyAuth: { access_token: "heyo", permission: ["user-read-currently-playing"] as SpotifyPermission[], }, + currentUser: {} as ListenBrainzUser, + recordingFeedbackManager: new RecordingFeedbackManager( + new APIService("foo"), + { name: "Fnord" } + ), +}; + +const props = { refreshSpotifyToken: new APIService("base-uri").refreshSpotifyToken, show: true, playerPaused: false, @@ -113,7 +121,7 @@ describe("SpotifyPlayer", () => { ...props, onInvalidateDataSource, }; - expect(SpotifyPlayer.hasPermissions(mockProps.spotifyUser)).toEqual( + expect(SpotifyPlayer.hasPermissions(defaultContext.spotifyAuth)).toEqual( false ); const wrapper = shallow(); @@ -138,10 +146,18 @@ describe("SpotifyPlayer", () => { const mockProps = { ...props, onInvalidateDataSource, - spotifyUser, }; - const wrapper = shallow(); - const instance = wrapper.instance(); + const wrapper = mount( + + + + ); + const instance = wrapper.find(SpotifyPlayer).instance() as SpotifyPlayer; expect.assertions(2); expect(instance.props.onInvalidateDataSource).not.toHaveBeenCalled(); @@ -163,11 +179,19 @@ describe("SpotifyPlayer", () => { const mockProps = { ...props, onInvalidateDataSource, - spotifyUser, checkSpotifyToken, }; - const wrapper = shallow(); - const instance = wrapper.instance(); + const wrapper = mount( + + + + ); + const instance = wrapper.find(SpotifyPlayer).instance() as SpotifyPlayer; instance.handleAccountError(); expect(instance.props.onInvalidateDataSource).toHaveBeenCalledTimes(1);