Skip to content

Commit

Permalink
Fix: access token unable to refresh due to recursive expiry check & a…
Browse files Browse the repository at this point in the history
…dd empty state in schedule
  • Loading branch information
radityaharya authored Apr 8, 2024
1 parent 3f0ede4 commit 089e34b
Show file tree
Hide file tree
Showing 8 changed files with 129 additions and 112 deletions.
72 changes: 48 additions & 24 deletions app/[uid]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,16 +49,24 @@ export default async function UserPage({ params: { uid } }: PageProps) {
);
const firstShow = nextShow?.items[0] || [];

const video = await tmdb.tv.getTvVideos(firstShow.ids.tmdb);
const episodeVideo = await tmdb.tv.getEpisodeVideos(
firstShow.ids.tmdb,
firstShow.season,
firstShow.number,
);
let video, episodeVideo;

if (firstShow && firstShow.ids) {
video = await tmdb.tv.getTvVideos(firstShow.ids.tmdb);
episodeVideo = await tmdb.tv.getEpisodeVideos(
firstShow.ids.tmdb,
firstShow.season,
firstShow.number,
);
}

let videoId = "";

const sources = [video.results, episodeVideo.results];
const sources = [
video ? video.results : [],
episodeVideo ? episodeVideo.results : [],
];

const findVideo = (source: any) =>
Array.isArray(source)
? source.find((v: any) => v.site === "YouTube" && v.iso_3166_1 === "US")
Expand Down Expand Up @@ -94,38 +102,54 @@ function hero(itemData: ShowItem, videoId: string) {
backgroundColor: "rgba(0,0,0,0.5)",
}}
/>
<YoutubePlayer
videoId={videoId}
fallbackImg={itemData.background as string}
/>
{videoId && (
<YoutubePlayer
videoId={videoId}
fallbackImg={itemData.background as string}
/>
)}
<div className="self-stretch h-full flex flex-col items-center justify-between z-10">
<div className="self-stretch flex flex-row items-center justify-between">
<div className="flex flex-col items-start justify-start gap-2">
<div className="relative flex flex-row gap-2 font-medium text-gray-300">
Airing in
<span>
<CountDownTimer airsAt={itemData.airsAt} />
</span>
{itemData && itemData.title ? (
<span className="text-gray-300">
Airing in{" "}
<span>
<CountDownTimer airsAt={itemData.airsAt} />
</span>
</span>
) : (
<span className="text-gray-300"></span>
)}
</div>
<div className="flex flex-col items-start justify-start gap-1 text-zinc-200">
<h1 className="self-stretch relative text-4xl font-bold">
{itemData.show}
</h1>
<div className="self-stretch relative text-xl font-medium text-gray-100">
{`S${itemData.number
.toString()
.padStart(2, "0")}E${itemData.season
.toString()
.padStart(2, "0")}: ${itemData.title}`}
{itemData && itemData.title
? `S${
itemData.number
? itemData.number.toString().padStart(2, "0")
: "00"
}E${
itemData.season
? itemData.season.toString().padStart(2, "0")
: "00"
}: ${itemData.title}`
: "No Upcoming"}
</div>
</div>
</div>
<Image
src={
itemData.networkLogo.replace(
"/p/",
"/p/h50_filter(negate,000,666)/",
) as string
itemData.networkLogo
? (itemData.networkLogo.replace(
"/p/",
"/p/h50_filter(negate,000,666)/",
) as string)
: "default_image_path_here"
}
width={100}
height={100}
Expand Down
9 changes: 0 additions & 9 deletions app/api/user/[uid]/calendar/movies/ical/route.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { TraktAPI } from "@/lib/trakt/Trakt";
import { headers } from "next/headers";
import { NextRequest, NextResponse } from "next/server";
import * as Sentry from "@sentry/nextjs";
const Redis = require("ioredis");
Expand All @@ -20,14 +19,6 @@ export async function GET(
? parseInt(request.nextUrl.searchParams.get("period")!)
: 90;

const userAgent = headers().get("user-agent") || "";
Sentry.setTag("user-agent", userAgent);
if (/Mozilla|Chrome|Safari|Firefox|Edge/.test(userAgent)) {
throw new Error(
"Browser not supported for this route, use this link to Import the calendar",
);
}

console.log(`days_ago: ${days_ago} | period: ${period}`);
if (![days_ago, period].every(Number.isSafeInteger)) {
throw new Error("days_ago and period must be safe integers");
Expand Down
10 changes: 0 additions & 10 deletions app/api/user/[uid]/calendar/shows/ical/route.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { TraktAPI } from "@/lib/trakt/Trakt";
import { headers } from "next/headers";
import { NextRequest, NextResponse } from "next/server";
import * as Sentry from "@sentry/nextjs";
const Redis = require("ioredis");
Expand All @@ -20,14 +19,6 @@ export async function GET(
? parseInt(request.nextUrl.searchParams.get("period")!)
: 90;

const userAgent = headers().get("user-agent") || "";
Sentry.setTag("user-agent", userAgent);
if (/Mozilla|Chrome|Safari|Firefox|Edge/.test(userAgent)) {
throw new Error(
"Browser not supported for this route, use this link to Import the calendar",
);
}

console.log(`days_ago: ${days_ago} | period: ${period}`);
if (![days_ago, period].every(Number.isSafeInteger)) {
throw new Error("days_ago and period must be safe integers");
Expand Down Expand Up @@ -55,7 +46,6 @@ export async function GET(
},
});
}

const trakt = new TraktAPI(undefined, params.uid);
const calendarData = await trakt.Shows.getShowsCalendar(days_ago, period);
const cal = new Blob([calendarData.toString()], { type: "text/calendar" });
Expand Down
2 changes: 1 addition & 1 deletion app/auth/success/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { redirect } from "next/navigation";
export default async function Login() {
// const router = useRouter();
const session = (await getServerSession(authOptions)) as customSession;
console.log("Success page session: ", session);
// console.log("Success page session: ", session);
const slug = session.accessToken?.slug;

if (slug) {
Expand Down
5 changes: 5 additions & 0 deletions components/schedule/scheduleView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,11 @@ const ScheduleView: FC<Props> = ({ initItems }) => {
<ScheduleItems Shows={item} />
</div>
))
) : !isLoading && !isValidating ? (
<p className="text-white">
No upcoming items returned within time range. Make sure there are
entries in your trakt account.
</p>
) : (
<p className="text-white">Loading...</p>
)}
Expand Down
126 changes: 68 additions & 58 deletions lib/trakt/utils/Base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export interface AccessToken {
access_token: string;
token_type: string;
expires_in: number;
expires_at: number;
refresh_token: string;
scope: string;
created_at: number;
Expand Down Expand Up @@ -57,84 +58,93 @@ export class BaseUtil {
headers?: any,
query?: any,
) {
const url = new URL(path, this.api_url);
if (query) {
const searchParams = new URLSearchParams(query);
url.search = searchParams.toString();
}

if (this.user_slug !== undefined && this.accessToken === undefined) {
const users = new Users();
const token = await users.getAccessToken(this.user_slug);
if (await this._isAccesTokenExpired(token)) {
console.info("Refreshing access token for", this.user_slug);
await this.refreshAccessToken(token);
try {
const url = new URL(path, this.api_url);
if (query) {
const searchParams = new URLSearchParams(query);
url.search = searchParams.toString();
}
this.accessToken = token;
}

let oauth_token;

if (this.accessToken) {
if (await this._isAccesTokenExpired(this.accessToken)) {
console.info("Refreshing access token for", this.accessToken.slug);
await this.refreshAccessToken(this.accessToken);
if (this.user_slug !== undefined && this.accessToken === undefined) {
const users = new Users();
const token = await users.getAccessToken(this.user_slug);
if (await this._isAccessTokenExpired(token)) {
console.info("Refreshing access token for", this.user_slug);
await this.refreshAccessToken(token);
}
this.accessToken = token;
}
oauth_token = this.accessToken.access_token;
}

if (headers && headers.Authorization) {
oauth_token = headers.Authorization.split(" ")[1];
}
let oauth_token = this.accessToken?.access_token;

const response = await fetch(url.toString(), {
method,
headers: {
"Content-Type": "application/json",
"trakt-api-key": this.client_id,
"trakt-api-version": "2",
...(oauth_token ? { authorization: `Bearer ${oauth_token}` } : {}),
...headers,
},
body: body && typeof body !== "string" ? JSON.stringify(body) : body,
});

const contentType = response.headers.get("content-type");
if (contentType && contentType.includes("application/json")) {
const body = await response.json();
if (headers && headers.Authorization) {
oauth_token = headers.Authorization.split(" ")[1];
}

if (response.ok) {
return body;
const requestOptions = {
method,
headers: {
"Content-Type": "application/json",
"trakt-api-key": this.client_id,
"trakt-api-version": "2",
...(oauth_token ? { Authorization: `Bearer ${oauth_token}` } : {}),
...headers,
},
body: body && typeof body !== "string" ? JSON.stringify(body) : body,
};

const response = await fetch(url.toString(), requestOptions);

const contentType = response.headers.get("content-type");
if (contentType && contentType.includes("application/json")) {
const responseBody = await response.json();
if (response.ok) {
return responseBody;
} else {
throw new Error(responseBody.message);
}
} else {
throw new Error(body.message);
const responseBody = await response.text();
throw new Error(responseBody);
}
} catch (error) {
console.error(error);
throw error;
}

return response.text(); // or handle non-JSON response
}

async _isAccesTokenExpired(access_token: AccessToken) {
const { expires_in, created_at } = access_token.access_token;
const isTokenExpired = Date.now() / 1000 > created_at + expires_in;
async _isAccessTokenExpired(access_token: any) {
const { expires_at } = access_token;
const currentTime = Date.now() / 1000;
const isTokenExpired = currentTime > expires_at;
return isTokenExpired;
}

async refreshAccessToken(access_token: AccessToken) {
const newAccessToken = await this._request("/oauth/token", "POST", {
refresh_token: access_token.access_token.refresh_token,
client_id: this.client_id,
client_secret: this.client_secret,
redirect_uri: this.redirect_uri,
grant_type: "refresh_token",
async refreshAccessToken(access_token: any) {
const response = await fetch(`${this.api_url}/oauth/token`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
refresh_token: access_token.refresh_token,
client_id: this.client_id,
client_secret: this.client_secret,
redirect_uri: this.redirect_uri,
grant_type: "refresh_token",
}),
});

const newAccessToken = await response.json();

if (newAccessToken && this.accessToken) {
this.accessToken.access_token = newAccessToken;
}

const slug = this._request("/users/me", "GET").then(
(res) => res.user.ids.slug,
);
const slug =
this.user_slug ||
this.accessToken?.slug ||
(await this._request("/users/me", "GET")).user.ids.slug;

const users = new Users();
await users.refreshAccessToken(await slug, newAccessToken);
Expand Down
7 changes: 6 additions & 1 deletion lib/trakt/utils/Shows.ts
Original file line number Diff line number Diff line change
Expand Up @@ -290,7 +290,12 @@ export class ShowsUtil extends BaseUtil {
};
return { updateOne: { filter, update, upsert: true } };
});
await calendarStore.bulkWrite(bulkOps, { ordered: false });

if (bulkOps.length > 0) {
await calendarStore.bulkWrite(bulkOps, { ordered: false });
} else {
console.log("No operations to execute, skipping...");
}

await this.redis_client.set(cacheKey, JSON.stringify(data), "EX", 60);
const perfEnd = performance.now();
Expand Down
10 changes: 1 addition & 9 deletions lib/util/users.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,6 @@ export class Users {
async getAccessToken(slug: string, user_id?: string) {
const db = (await clientPromise).db(NEXTAUTH_DB);
const accounts = db.collection(NEXTAUTH_ACCOUNTS_COLLECTION);

// const user = await accounts.findOne({
// // providerAccountId: slug,
// userId: new ObjectId(user_id),
// provider: "trakt",
// });

// if slug is "" use user_id if it exists
let user;
if (!slug && user_id) {
user = await accounts.findOne({
Expand Down Expand Up @@ -56,7 +48,7 @@ export class Users {
const db = (await clientPromise).db(NEXTAUTH_DB);
const accounts = db.collection(NEXTAUTH_ACCOUNTS_COLLECTION);

const updated = accounts.updateOne(
const updated = await accounts.updateOne(
{ slug },
{
$set: {
Expand Down

0 comments on commit 089e34b

Please sign in to comment.