Skip to content
This repository has been archived by the owner on Nov 3, 2023. It is now read-only.

Commit

Permalink
fix: add handling for account switching, fix for __getLocalVars() bei…
Browse files Browse the repository at this point in the history
…ng missing on SpotifyStore
  • Loading branch information
Socketlike committed Sep 25, 2023
1 parent 62b1993 commit 42b226e
Show file tree
Hide file tree
Showing 4 changed files with 114 additions and 60 deletions.
15 changes: 11 additions & 4 deletions src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,16 +23,23 @@ export const emitMessage = (msg: MessageEvent<string>, account: SpotifyAccount):
events.emit('message', { accountId: account.accountId, data: raw.payloads[0].events[0] });
};

const postConnectionListener = (): void => {
common.fluxDispatcher.unsubscribe('POST_CONNECTION_OPEN', postConnectionListener);
const postConnectionOpenListener = (): void => {
common.fluxDispatcher.unsubscribe('POST_CONNECTION_OPEN', postConnectionOpenListener);

events.debug('start', ['waited for post connection ready']);
events.debug('start', ['waited for POST_CONNECTION_OPEN']);
events.emit('ready');
};

// to detect account switches - we need to reset the modal
const loginSuccessListener = (): void => {
events.emit('accountSwitch');
common.fluxDispatcher.subscribe('POST_CONNECTION_OPEN', postConnectionOpenListener);
};

export const start = (): void => {
if (!document.getElementById('spotify-modal-root'))
common.fluxDispatcher.subscribe('POST_CONNECTION_OPEN', postConnectionListener);
common.fluxDispatcher.subscribe('POST_CONNECTION_OPEN', postConnectionOpenListener);
common.fluxDispatcher.subscribe('LOGIN_SUCCESS', loginSuccessListener);
};

export const stop = async (): Promise<void> => {
Expand Down
30 changes: 29 additions & 1 deletion src/patches.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { types } from 'replugged';
import { Logger, types } from 'replugged';

const logger = Logger.plugin('SpotifyModal');

const patches: types.PlaintextPatch[] = [
{
Expand All @@ -20,6 +22,32 @@ const patches: types.PlaintextPatch[] = [
replace:
'$1{window.replugged.plugins.getExports("lib.evelyn.SpotifyModal")?.emitMessage?.($2, this);',
},
(source: string): string => {
const storePrototypeVar = source.match(/([^;]{1,3})\.hasConnectedAccount/)[1];

if (!storePrototypeVar)
logger.error(
"couldn't get variable name for SpotifyStore's prototype. please report this on GitHub.",
);

const accountListVar = source.match(
new RegExp(
`${storePrototypeVar}\\.hasConnectedAccount=function\\(\\)\\{.{0,30}return Object\\.keys\\((.{1,3})\\)`,
),
)[1];

if (!accountListVar)
logger.error(
"couldn't get variable name for the Spotify account list. please report this on GitHub.",
);

return storePrototypeVar && accountListVar
? source.replace(
new RegExp(`${storePrototypeVar}=.{1,3}.prototype;`),
`$&${storePrototypeVar}.spotifyModalAccounts=${accountListVar};`,
)
: source;
},
],
},
];
Expand Down
6 changes: 4 additions & 2 deletions src/typings/store.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
export interface SpotifyStore {
shouldShowActivity(): boolean;
spotifyModalAccounts?: Record<string, SpotifyAccount>;
// eslint-disable-next-line @typescript-eslint/naming-convention
__getLocalVars(): {
accounts: Record<string, SpotifyAccount>;
store: SpotifyStore;
};
wasAutoPaused(): boolean;
}

export interface SpotifyAccount {
accessToken: string;
accountId: string;
Expand Down Expand Up @@ -34,10 +34,12 @@ export type SpotifySocketData =
type: 'pong';
}
| {
headers?: { 'Spotify-Connection-Id'?: string; 'Content-Type'?: string };
payloads: [
{
events: [SpotifySocketPayloadEvents];
},
];
method?: 'PUT';
type: 'message';
};
123 changes: 70 additions & 53 deletions src/util/spotify.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,10 @@ export const store = await webpack.waitForModule<SpotifyStore>(
webpack.filters.byProps('getActiveSocketAndDevice'),
);

const spotifyAccounts = store.__getLocalVars().accounts;
export const spotifyAccounts =
store.spotifyModalAccounts ||
store.__getLocalVars?.()?.accounts ||
({} as Record<string, SpotifyAccount>);

export const currentSpotifyAccount = { id: '' };

Expand All @@ -31,7 +34,7 @@ export const getAccessTokenFromAccountId = (accountId?: string): string => {
export const getAccountFromAccountId = (accountId?: string): SpotifyAccount => {
if (!accountId) return spotifyAccounts[currentSpotifyAccount.id];

return spotifyAccounts[accountId];
return spotifyAccounts.get[accountId];
};

export const refreshSpotifyToken = async (
Expand Down Expand Up @@ -60,7 +63,7 @@ export const refreshSpotifyToken = async (
}
};

export const sendSpotifyRequest = (
export const sendSpotifyRequest = async (
accessToken: string,
endpoint: string,
method?: string,
Expand All @@ -84,7 +87,7 @@ export const sendSpotifyRequest = (
url.searchParams.append(key, val);
}

return fetch(
const res = await fetch(
url,
filterObject(
{
Expand All @@ -96,80 +99,85 @@ export const sendSpotifyRequest = (
body,
mode: 'cors',
} as const,
(value) => Boolean(value),
(val) => Boolean(val),
),
).then(async (res: Response): Promise<Response> => {
if (!retrying) {
if (config.get('automaticReauthentication') && res.status === 401) {
events.debug('spotify', ['status 401: deauthed. reauthing', res.clone()]);

const newToken = await refreshSpotifyToken(currentSpotifyAccount.id);

if (!newToken.ok) {
events.debug('controls', ['fetching token status not ok', _.clone(newToken.res)]);

toast.toast(
`An error occurred whilst reauthenticating.${
config.get('debugging') ? ' Check console for details.' : ''
}`,
toast.Kind.FAILURE,
);
} else {
events.debug('controls', [
'retrying action',
res.clone(),
_.clone(url),
method,
_.clone(query),
_.clone(body),
]);

sendSpotifyRequest(
getAccessTokenFromAccountId(),
endpoint,
method,
query,
body,
true,
).then((res: Response): Response => {
if (!res?.ok) {
);

if (!retrying) {
switch (res.status) {
case 401: {
if (config.get('automaticReauthentication')) {
events.debug('spotify', ['status 401: deauthed. reauthing', res.clone()]);

const token = await refreshSpotifyToken(currentSpotifyAccount.id);

if (token.ok) {
events.debug('controls', [
'retrying action',
res.clone(),
_.clone(url),
method,
_.clone(query),
_.clone(body),
]);

const retryRes = await sendSpotifyRequest(
getAccessTokenFromAccountId(),
endpoint,
method,
query,
body,
true,
);

if (!retryRes?.ok) {
toast.toast(
`An error occurred whilst retrying control action.${
config.get('debugging') ? ' Check console for more details.' : ''
}`,
toast.Kind.FAILURE,
);

events.debug('controls', ['retrying action failed', res]);
events.debug('controls', ['retrying action failed', retryRes]);
}

return res;
});
return retryRes;
}
} else {
events.debug('controls', ['status 401: deauthed. not reauthing', res.clone()]);

toast.toast('Access token expired. Please manually update your state.');
}
} else if (res.status === 401) {
events.debug('controls', ['status 401: deauthed. not reauthing', res.clone()]);

toast.toast('Access token expired. Please manually update your state.');
} else if (res.status === 403) {
break;
}

case 403: {
const { error } = (await res.clone().json()) as {
error: { message: string; reason: string };
};

toast.toast(
`Player controls violation: ${error?.reason || 'Unknown'}.${
`Got a 403 whilst handling control action: ${error?.reason || 'Unknown'}.${
config.get('debugging') ? ' Check console for more details.' : ''
}`,
toast.Kind.FAILURE,
);

events.debug('controls', ['status 403: player controls violation.', res.clone(), error]);
}
events.debug('controls', [
'status 403: likely a player controls violation',
res.clone(),
error,
]);

persist = false;
break;
}
}
}

persist = false;

return res;
});
return res;
};

export const spotifyAPI = {
Expand Down Expand Up @@ -307,6 +315,15 @@ events.on<SpotifyApi.CurrentPlaybackResponse>('ready', async (): Promise<void> =
}
});

// reset modal on account switch
events.on<void>('accountSwitch', (): void => {
events.debug('accountSwitch', 'detected account switching - resetting modal');

currentSpotifyAccount.id = '';

events.emit('showUpdate', false);
});

events.on<{
accountId: string;
data: SpotifySocketPayloadEvents;
Expand Down

0 comments on commit 42b226e

Please sign in to comment.