Skip to content

Commit

Permalink
feat: spotify refresh token flow (#79)
Browse files Browse the repository at this point in the history
  • Loading branch information
okanisildar authored Dec 16, 2023
1 parent c0e813e commit 26c124d
Show file tree
Hide file tree
Showing 4 changed files with 85 additions and 9 deletions.
3 changes: 3 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ DEEPGRAM_API_KEY=
OPENAI_API_KEY=
SLACK_POSTMAN_WEBHOOK_URL=

SPOTIFY_CLIENT_ID=
SPOTIFY_CLIENT_SECRET=

STRIPE_SECRET_KEY=
STRIPE_PUBLISHABLE_KEY=
STRIPE_WEBHOOK_SECRET=
4 changes: 4 additions & 0 deletions env.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ export const env = createEnv({
PODCAST_INDEX_API_KEY: process.env.PODCAST_INDEX_API_KEY,
PODCAST_INDEX_SECRET: process.env.PODCAST_INDEX_SECRET,
SLACK_POSTMAN_WEBHOOK_URL: process.env.SLACK_POSTMAN_WEBHOOK_URL,
SPOTIFY_CLIENT_ID: process.env.SPOTIFY_CLIENT_ID,
SPOTIFY_CLIENT_SECRET: process.env.SPOTIFY_CLIENT_SECRET,
STRIPE_PUBLISHABLE_KEY: process.env.STRIPE_PUBLISHABLE_KEY,
STRIPE_SECRET_KEY: process.env.STRIPE_SECRET_KEY,
STRIPE_WEBHOOK_SECRET: process.env.STRIPE_WEBHOOK_SECRET,
Expand All @@ -32,6 +34,8 @@ export const env = createEnv({
PODCAST_INDEX_API_KEY: z.string().min(1),
PODCAST_INDEX_SECRET: z.string().min(1),
SLACK_POSTMAN_WEBHOOK_URL: z.string().min(1),
SPOTIFY_CLIENT_ID: z.string().min(1),
SPOTIFY_CLIENT_SECRET: z.string().min(1),
STRIPE_PUBLISHABLE_KEY: z.string().min(1),
STRIPE_SECRET_KEY: z.string().min(1),
STRIPE_WEBHOOK_SECRET: z.string().min(1),
Expand Down
51 changes: 51 additions & 0 deletions lib/services/spotify/client.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,59 @@
import { env } from '@/env.mjs';
import { DatabaseError } from '@/lib/errors';
import { cookies } from 'next/headers';
import { ofetch } from 'ofetch';

import { fetchAccount } from '../account';
import { createSupabaseServerClient } from '../supabase/server';

type RefreshTokenResponse = {
access_token: string;
expires_in: number;
scope: string;
token_type: string;
};

const SPOTIFY_BASE_URL = 'https://api.spotify.com/v1';

export const spotifyFetchClient = ofetch.create({
baseURL: SPOTIFY_BASE_URL,
params: { limit: 50, offset: 0 },
});

export const refreshToken = async (providerRefreshToken: string) => {
const supabase = createSupabaseServerClient(cookies());
const account = await fetchAccount();

const bearer = Buffer.from(
`${env.SPOTIFY_CLIENT_ID}:${env.SPOTIFY_CLIENT_SECRET}`,
).toString('base64');

try {
const response = await ofetch<RefreshTokenResponse>(
'https://accounts.spotify.com/api/token',
{
body: `grant_type=refresh_token&refresh_token=${providerRefreshToken}`,
headers: {
Authorization: `Basic ${bearer}`,
'content-type': 'application/x-www-form-urlencoded',
},
method: 'post',
},
);

const { data, error } = await supabase
.from('account')
.update({ provider_token: response.access_token })
.eq('id', account.id)
.select()
.single();

if (error) {
throw new DatabaseError(error);
}

return data.provider_token;
} catch (error) {
throw new Error('Could not get new access token');
}
};
36 changes: 27 additions & 9 deletions lib/services/spotify/get-user-shows.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import { spotifyFetchClient } from './client';
import { FetchError } from 'ofetch';

import { fetchAccount } from '../account';
import { refreshToken, spotifyFetchClient } from './client';

type Image = {
height: number;
Expand Down Expand Up @@ -29,12 +32,27 @@ type SpotifyItems = {

export const getUserShows = async (
spotifyToken: string,
): Promise<SpotifyShow[]> => {
const response = await spotifyFetchClient<SpotifyItems>('/me/shows', {
headers: {
Authorization: `Bearer ${spotifyToken}`,
},
});

return response.items;
): Promise<[] | SpotifyShow[]> => {
try {
const response = await spotifyFetchClient<SpotifyItems>('/me/shows', {
headers: {
Authorization: `Bearer ${spotifyToken}`,
},
retry: 2,
retryDelay: 1000,
});
return response.items;
} catch (error) {
if (error instanceof FetchError) {
if (error.status === 401) {
const account = await fetchAccount();
if (account.provider_refresh_token) {
await refreshToken(account.provider_refresh_token);
return [];
}
throw new Error('Authorization error');
}
}
throw new Error('Spotify server error');
}
};

1 comment on commit 26c124d

@vercel
Copy link

@vercel vercel bot commented on 26c124d Dec 16, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Deployment failed with the following error:

Resource is limited - try again in 34 minutes (more than 100, code: "api-deployments-free-per-day").

Please sign in to comment.