Skip to content

Commit

Permalink
feat/front-end-robustness-improvements (#121)
Browse files Browse the repository at this point in the history
* feat: added ErorrBoundary

* chore: updated gitignore when build android app

* fix: tricks to make export work statically (ErrorBoundary not working on dev mode)

* feat: first versiuon of ErrorBoundary class

* feat: handle websocket disconnexions

* feat: added hook asyncError to upgrade ErrorBoundary to handle fetch error

* chore: refactor WebsocketError in component

* feat: added RootErrorBoundary page

* refactor: error boudary template became component

* Fix/remote token provider (#128)

* chore: added method room:error in websocket ServerToClient

* fix: catched Spotify SDK error & fix

* fix: on remote creation, added bound services verification

* chore: removed hardcode default fallback error page

* fix : type of props in ErrorBoundary

* chore : resolved conflicts

* fix: duplicated import

* chore: added back GitHub issue

---------

Co-authored-by: MAXOUXAX <[email protected]>
  • Loading branch information
GaspardBBY and MAXOUXAX authored Mar 26, 2024
1 parent 3c1f869 commit eb732e0
Show file tree
Hide file tree
Showing 28 changed files with 470 additions and 149 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ node_modules/
dist/
web-build/

# Expo build
android/

# Metro
.metro-health-check*

Expand Down
32 changes: 26 additions & 6 deletions backend/src/RoomStorage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { QueueableRemote } from "./musicplatform/remotes/Remote";
import { adminSupabase, server } from "./server";
import Room from "./socketio/Room";
import { RoomWithForeignTable } from "./socketio/RoomDatabase";
import { Response } from "commons/socket.io-types";

const STREAMING_SERVICES = {
Spotify: "a2d17b25-d87e-42af-9e79-fd4df6b59222",
Expand Down Expand Up @@ -126,18 +127,26 @@ export default class RoomStorage {
async roomFromUuid(
rawUuid: string,
hostSocket: Socket | null
): Promise<Room | null> {
): Promise<Response<Room>> {
const { data: remoteRoom } = await adminSupabase
.from("rooms")
.select("*, streaming_services(*), room_configurations(*)")
.eq("id", rawUuid)
.eq("is_active", true)
.single();

if (!remoteRoom) return null;
if (!remoteRoom)
return {
data: null,
error: "Room not found",
};

const parseRemote = parseRemoteRoom(remoteRoom);
if (!parseRemote) return null;
if (!parseRemote)
return {
data: null,
error: "Error parsing remote room",
};
const { musicPlatform, roomWithConfig } = parseRemote;

return Room.getOrCreate(
Expand All @@ -149,18 +158,29 @@ export default class RoomStorage {
);
}

async roomFromCode(code: string, hostSocket: Socket): Promise<Room | null> {
async roomFromCode(
code: string,
hostSocket: Socket
): Promise<Response<Room>> {
const { data: remoteRoom } = await adminSupabase
.from("rooms")
.select("*, streaming_services(*), room_configurations(*)")
.eq("code", code)
.eq("is_active", true)
.single();

if (!remoteRoom) return null;
if (!remoteRoom)
return {
data: null,
error: "Room not found",
};

const parseRemote = parseRemoteRoom(remoteRoom);
if (!parseRemote) return null;
if (!parseRemote)
return {
data: null,
error: "Error parsing remote room",
};
const { musicPlatform, roomWithConfig } = parseRemote;

return Room.getOrCreate(
Expand Down
8 changes: 6 additions & 2 deletions backend/src/musicplatform/Deezer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,17 @@ import { JSONTrack } from "commons/backend-types";
import MusicPlatform from "./MusicPlatform";
import { Remote } from "./remotes/Remote";
import Room from "../socketio/Room";
import { Response } from "commons/socket.io-types";

export default class Deezer extends MusicPlatform {
async getRemote(
room: Room,
musicPlatform: MusicPlatform
): Promise<Remote | null> {
return null;
): Promise<Response<Remote>> {
return {
data: null,
error: "Deezer is not implemented",
};
}

constructor() {
Expand Down
3 changes: 2 additions & 1 deletion backend/src/musicplatform/MusicPlatform.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { JSONTrack } from "commons/backend-types";
import { Remote } from "./remotes/Remote";
import Room from "../socketio/Room";
import { Response } from "commons/socket.io-types";

export default abstract class MusicPlatform {
private readonly urlPattern: RegExp;
Expand Down Expand Up @@ -37,7 +38,7 @@ export default abstract class MusicPlatform {
abstract getRemote(
room: Room,
musicPlatform: MusicPlatform
): Promise<Remote | null>;
): Promise<Response<Remote>>;
}

function getNbCapturingGroupRegex(regex: RegExp) {
Expand Down
3 changes: 2 additions & 1 deletion backend/src/musicplatform/SoundCloud.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import Room from "../socketio/Room";
import MusicPlatform from "./MusicPlatform";
import { Remote } from "./remotes/Remote";
import SoundCloudRemote from "./remotes/SoundCloudRemote";
import { Response } from "commons/socket.io-types";

function extractFromTrack(track: SoundcloudTrackV2) {
const artists = track.user.username;
Expand Down Expand Up @@ -43,7 +44,7 @@ export default class SoundCloud extends MusicPlatform {
room: Room,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
musicPlatform: MusicPlatform
): Promise<Remote | null> {
): Promise<Response<Remote>> {
return SoundCloudRemote.createRemote(this, room);
}

Expand Down
3 changes: 2 additions & 1 deletion backend/src/musicplatform/Spotify.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { Remote } from "./remotes/Remote";
import SpotifyRemote from "./remotes/SpotifyRemote";
import Room from "../socketio/Room";
import { Track } from "@spotify/web-api-ts-sdk";
import { Response } from "commons/socket.io-types";

export default class Spotify extends MusicPlatform {
constructor() {
Expand Down Expand Up @@ -59,7 +60,7 @@ export default class Spotify extends MusicPlatform {
async getRemote(
room: Room,
musicPlatform: MusicPlatform
): Promise<Remote | null> {
): Promise<Response<Remote>> {
return await SpotifyRemote.createRemote(room, this);
}

Expand Down
11 changes: 8 additions & 3 deletions backend/src/musicplatform/TrackMetadata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,13 @@ export default class TrackMetadata {
}

async toJSON(): Promise<JSONTrack | null> {
const JSONTrack = await this.platform.getJsonTrack(this.id);
if (JSONTrack) JSONTrack.votes = [];
return JSONTrack;
try {
const JSONTrack = await this.platform.getJsonTrack(this.id);
if (JSONTrack) JSONTrack.votes = [];
return JSONTrack;
} catch (err) {
console.error(`Failed to fetch data for track ${this.id}`);
return null;
}
}
}
10 changes: 5 additions & 5 deletions backend/src/musicplatform/remotes/SoundCloudRemote.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { JSONTrack, PlayingJSONTrack } from "commons/backend-types";
import MusicPlatform from "../MusicPlatform";
import { Remote } from "./Remote";
import { PlayingJSONTrack } from "commons/backend-types";
import {
LocalPlayerServerToClientEvents,
Response,
} from "commons/socket.io-types";
import Room from "../../socketio/Room";
import MusicPlatform from "../MusicPlatform";
import { Remote } from "./Remote";

export default class SoundCloudRemote extends Remote {
room: Room;
Expand All @@ -20,8 +20,8 @@ export default class SoundCloudRemote extends Remote {
static async createRemote(
musicPlatform: MusicPlatform,
room: Room
): Promise<SoundCloudRemote | null> {
return new SoundCloudRemote(room, musicPlatform);
): Promise<Response<Remote>> {
return { data: new SoundCloudRemote(room, musicPlatform), error: null };
}

getHostSocket(): (typeof this.room)["hostSocket"] {
Expand Down
129 changes: 81 additions & 48 deletions backend/src/musicplatform/remotes/SpotifyRemote.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { SimplifiedArtist, SpotifyApi, Track } from "@spotify/web-api-ts-sdk";
import { JSONTrack, PlayingJSONTrack } from "commons/backend-types";
import { adminSupabase } from "../../server";
import MusicPlatform from "../MusicPlatform";
import { QueueableRemote } from "./Remote";
import { QueueableRemote, Remote } from "./Remote";
import Room from "../../socketio/Room";
import { Response } from "commons/socket.io-types";

Expand All @@ -22,70 +22,103 @@ export default class SpotifyRemote extends QueueableRemote {
static async createRemote(
room: Room,
musicPlatform: MusicPlatform
): Promise<SpotifyRemote | null> {
): Promise<Response<Remote>> {
const { data, error } = await adminSupabase
.from("rooms")
.select("*, user_profile(*, bound_services(*))")
.eq("id", room.uuid)
.eq(
"user_profile.bound_services.service_id",
"a2d17b25-d87e-42af-9e79-fd4df6b59222"
)
.single();

if (error)
return {
data: null,
error: "Failed to fetch host's Spotify credentials",
};
if (
error ||
!data ||
!data.user_profile ||
!data.user_profile.bound_services
data.user_profile?.bound_services === undefined ||
data.user_profile?.bound_services.length === 0 ||
!data.user_profile.bound_services[0].access_token ||
!data.user_profile.bound_services[0].expires_in ||
!data.user_profile.bound_services[0].refresh_token
)
return null;
return {
data: null,
error:
"Couldn't find Spotify credentials. Please double check that your Spotify account is linked in your account settings under 'Integrations'",
};

const { access_token, expires_in, refresh_token } =
data.user_profile.bound_services[0];

if (!access_token || !expires_in || !refresh_token) return null;

const expiresIn = parseInt(expires_in);

// TODO: https://github.com/spotify/spotify-web-api-ts-sdk/issues/79
const spotifyClient = SpotifyApi.withAccessToken(
process.env.SPOTIFY_CLIENT_ID as string,
{
access_token,
refresh_token,
expires_in: expiresIn,
token_type: "Bearer",
}
);
return new SpotifyRemote(spotifyClient, musicPlatform);
try {
// TODO: https://github.com/spotify/spotify-web-api-ts-sdk/issues/79
// Seems like the tokens aren't refreshed properly, this has been fixed in the GitHub repo but we're
// awaiting a release of the library to update and fix the issue
const spotifyClient = SpotifyApi.withAccessToken(
process.env.SPOTIFY_CLIENT_ID as string,
{
access_token,
refresh_token,
expires_in: expiresIn,
token_type: "Bearer",
}
);
return {
data: new SpotifyRemote(spotifyClient, musicPlatform),
error: null,
};
} catch (e) {
return {
data: null,
error:
"Failed to authenticate to Spotify using saved credentials. Try disconnecting then reconnecting your Spotify account from your account 'Integrations' page",
};
}
}

async getPlaybackState(): Promise<Response<PlayingJSONTrack | null>> {
return runSpotifyCallback(async () => {
const spotifyPlaybackState =
await this.spotifyClient.player.getPlaybackState();

if (!spotifyPlaybackState || spotifyPlaybackState.item.type === "episode")
return { data: null, error: "No track is currently playing" };

const playbackState = {
...spotifyPlaybackState,
item: spotifyPlaybackState.item as Track,
};

const artistsName = extractArtistsName(playbackState.item.album.artists);

return {
data: {
isPlaying: playbackState.is_playing,
albumName: playbackState.item.album.name,
artistsName: artistsName,
currentTime: playbackState.progress_ms,
duration: playbackState.item.duration_ms,
imgUrl: playbackState.item.album.images[0].url,
title: playbackState.item.name,
url: playbackState.item.external_urls.spotify,
updated_at: Date.now(),
},
error: null,
};
try {
const spotifyPlaybackState =
await this.spotifyClient.player.getPlaybackState();

if (
!spotifyPlaybackState ||
spotifyPlaybackState.item.type === "episode"
)
return { data: null, error: "No track is currently playing" };

const playbackState = {
...spotifyPlaybackState,
item: spotifyPlaybackState.item as Track,
};

const artistsName = extractArtistsName(
playbackState.item.album.artists
);

return {
data: {
isPlaying: playbackState.is_playing,
albumName: playbackState.item.album.name,
artistsName: artistsName,
currentTime: playbackState.progress_ms,
duration: playbackState.item.duration_ms,
imgUrl: playbackState.item.album.images[0].url,
title: playbackState.item.name,
url: playbackState.item.external_urls.spotify,
updated_at: Date.now(),
},
error: null,
};
} catch (e) {
return { data: null, error: "Failed to fetch current Playbackstate" };
}
});
}

Expand Down
18 changes: 0 additions & 18 deletions backend/src/route/AuthCallbackGET.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,24 +117,6 @@ const getUserProfile = async (userId: string): Promise<string | null> => {
return userData?.user_profile_id ?? null;
};

const alreadyBoundService = async ({
service,
user_profile_id,
}: {
service: StreamingService;
user_profile_id: string;
}): Promise<{ alreadyBound: boolean; error: PostgrestError | null }> => {
const { data, error } = await adminSupabase
.from("bound_services")
.select("*")
.eq("user_profile_id", user_profile_id)
.eq("service_id", service);
return {
alreadyBound: data !== null && data.length > 0,
error: error,
};
};

export const createAccount = async ({
displayName,
accountId,
Expand Down
Loading

0 comments on commit eb732e0

Please sign in to comment.