alkihis / twitter-api-v2-user-oauth2-flow-example

A working example of beta OAuth2 user-login flow
1 stars 0 forks source link

would love to see this lines translated into code #2

Open deadcoder0904 opened 2 years ago

deadcoder0904 commented 2 years ago

https://github.com/alkihis/twitter-api-v2-user-oauth2-flow-example/blob/ce032d65ab43d6bbaa63eff737775942724c5ad1/src/routes/callback.ts#L54-L64

or another example using v2 as i'm currently making an app using v2 :)

deadcoder0904 commented 2 years ago

@alkihis i'm trying to make sense of these commented lines & i'm wondering if i can just let the user logged in for 365 days & not need to implement this. is that possible? see https://stackoverflow.com/a/14200115/6141587 & https://stackoverflow.com/a/39574303/6141587

if it's not possible, where do i put this code? can i place it in the same place as the callback route or a separate twitter client file where i make a class & put refresh & access token code there?

currently the twitter client file looks like this:

import TwitterApi from 'twitter-api-v2'

import { TWITTER_CONFIG } from '@/utils/index'

export const twitterClient = new TwitterApi({
    clientId: TWITTER_CONFIG.CLIENT_ID,
    clientSecret: TWITTER_CONFIG.CLIENT_SECRET,
})

and my callback looks like with the commented code implemented:

import { NextApiResponse } from 'next'
import TwitterApi from 'twitter-api-v2'

import handler from '@/server/api-route'
import { TWITTER_CONFIG } from '@/utils/index'
import { twitterClient } from '@/utils/twitter'
import { NextIronRequest } from '@/types/index'
// import { createUser } from '@/server/graphql/User/index'

const getVerifierToken = async (req: NextIronRequest, res: NextApiResponse) => {
    const state = req.query.state as string
    const code = req.query.code as string
    const { state: storedState, codeVerifier } = req.session.twitter

    if (storedState !== state) {
        return res
            .status(400)
            .send(
                'OAuth token is not known or invalid. Your request may have expired. Please renew the auth process.'
            )
    }

    const {
        client: loggedClient,
        accessToken,
        refreshToken,
        expiresIn,
    } = await twitterClient.loginWithOAuth2({
        code,
        codeVerifier,
        redirectUri: TWITTER_CONFIG.CALLBACK_URL,
    })

    try {
        const {
            data: { id: userId, name, username, profile_image_url, description, url },
        } = await loggedClient.v2.me()

        console.log({ userId, name, username, profile_image_url, description, url })
        console.log({ loggedClient, accessToken, refreshToken, expiresIn })
        req.session.twitter = {
            codeVerifier,
            state,
            accessToken,
            refreshToken,
        }

        if (expiresIn > 0) {
            console.log('not expired')
            const freshInstance = new TwitterApi(accessToken)
        } else {
            console.log('expired')
            // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
            const { client: renewedInstance, accessToken: newAccessToken } =
                await twitterClient.refreshOAuth2Token(refreshToken!)
        }
        req.session.user = { id: userId, username, name, profile_image: profile_image_url }
        await req.session.save()

        // await createUser({ userId: id, username, name, profile_image })
    } catch (e) {
        console.error(e)
    }

    res.redirect('/dashboard')
}

export default handler().get(getVerifierToken)

this causes me confusion as the renewedInstance can't be used elsewhere outside as it's in the callback file which is a next.js api route.

if i try to put it in a common file, do i need to send it an instance like:

export const getTwitterClient = (
    accessToken?: string,
    expiresIn?: number,
    instance?: TwitterApi
): TwitterApi => {
    let twitterClient: TwitterApi = new TwitterApi({
        clientId: TWITTER_CONFIG.CLIENT_ID,
        clientSecret: TWITTER_CONFIG.CLIENT_SECRET,
    })

    if (accessToken) {
        twitterClient = new TwitterApi(accessToken)
    } else if (expiresIn && instance && expiresIn > 0) {
        twitterClient = instance
    }
    return twitterClient
}

this lets me access twitterClient irrespective of which file i'm in. it also means removing the commented code you have from the callback file.

i think prisma does this to make it accessible in other files:

import { PrismaClient } from "@prisma/client";

// Make global.cachedPrisma work with TypeScript
declare global {
  // NOTE: This actually needs to be a "var", let/const don't work here.
  // eslint-disable-next-line no-var
  var cachedPrisma: PrismaClient;
}

// Workaround to make Prisma Client work well during "next dev"
// @see https://www.prisma.io/docs/support/help-articles/nextjs-prisma-client-dev-practices
let prisma: PrismaClient;
if (process.env.NODE_ENV === "production") {
  prisma = new PrismaClient();
} else {
  if (!global.cachedPrisma) {
    global.cachedPrisma = new PrismaClient();
  }
  prisma = global.cachedPrisma;
}

export default prisma;

the best-case scenario is to avoid implementing this as i can't find anyone who has done it before but would love a little guidance if you've got time :)