From d33788e2b1aaba306637576ed826889d9cf5cadc Mon Sep 17 00:00:00 2001 From: delivan Date: Fri, 23 Dec 2022 22:30:35 +0900 Subject: [PATCH] Fix some logic handling error --- components/error-boundary/index.tsx | 3 +- next.config.js | 11 ++-- pages/api/icns-verification.ts | 31 +++++++---- pages/api/twitter-auth-info.ts | 84 ++++++++++++++++------------- pages/api/twitter-auth-url.ts | 10 ++-- pages/verification/index.tsx | 12 ++--- queries/twitter.ts | 23 ++++---- types/api-response.ts | 24 +++++++++ utils/url.ts | 31 +++++++---- 9 files changed, 143 insertions(+), 86 deletions(-) diff --git a/components/error-boundary/index.tsx b/components/error-boundary/index.tsx index 8bf403e..f3ff76c 100644 --- a/components/error-boundary/index.tsx +++ b/components/error-boundary/index.tsx @@ -23,7 +23,8 @@ class ErrorBoundary extends Component { public componentDidCatch(error: Error, errorInfo: ErrorInfo) { console.error("Uncaught error:", error, errorInfo); - captureException(error); + const errorMessage = error.message || error; + captureException(errorMessage); } public render() { diff --git a/next.config.js b/next.config.js index 5fd1351..8de7981 100644 --- a/next.config.js +++ b/next.config.js @@ -2,12 +2,11 @@ const { withSentryConfig } = require("@sentry/nextjs"); const nextConfig = { - sentry: { - hideSourceMaps: true, - }, - api: { - externalResolver: true, - }, + ...(!!process.env.NEXT_PUBLIC_SENTRY_DSN && { + sentry: { + hideSourceMaps: true, + }, + }), reactStrictMode: false, swcMinify: true, compiler: { diff --git a/pages/api/icns-verification.ts b/pages/api/icns-verification.ts index 8f4d8b4..7f01d2c 100644 --- a/pages/api/icns-verification.ts +++ b/pages/api/icns-verification.ts @@ -1,8 +1,7 @@ +import { captureException } from "@sentry/nextjs"; import { withIronSessionApiRoute } from "iron-session/next"; import type { NextApiRequest, NextApiResponse } from "next"; import { ironOptions } from "../../iron.config"; -import { IcnsVerificationResponse } from "../../types"; -import { request } from "../../utils/url"; export default withIronSessionApiRoute(async function handler( req: NextApiRequest, @@ -10,30 +9,42 @@ export default withIronSessionApiRoute(async function handler( ) { try { if (!process.env.ICNS_VERIFIER_ORIGIN_LIST) { - console.log(".env is not set"); + const errorMessage = ".env is not set"; + console.error(errorMessage); + captureException(errorMessage); return res.status(500).json({ error: "Internal server error" }); } const verifierOriginList = process.env.ICNS_VERIFIER_ORIGIN_LIST.split(","); const verificationList = await Promise.allSettled( - verifierOriginList.map((verfierOrigin) => - request( - `${verfierOrigin}/api/verify_twitter`, - { + verifierOriginList.map(async (verfierOrigin) => + ( + await fetch(`${verfierOrigin}/api/verify_twitter`, { method: "post", headers: { "Content-Type": "application/json", }, body: JSON.stringify(req.body), - }, - ), + }) + ).json(), ), ); + + const errorTrimmedVerificationList = verificationList.map( + (verification) => ({ + ...verification, + ...(verification.status === "rejected" && { + reason: verification.reason.message || verification.reason, + }), + }), + ); + res.status(200).json({ - verificationList, + verificationList: errorTrimmedVerificationList, }); } catch (error) { console.error(error); + captureException(error); res.status(500).json({ error: "Internal server error" }); } }, diff --git a/pages/api/twitter-auth-info.ts b/pages/api/twitter-auth-info.ts index f2bf18d..f935a9f 100644 --- a/pages/api/twitter-auth-info.ts +++ b/pages/api/twitter-auth-info.ts @@ -1,8 +1,8 @@ -import type { NextApiRequest, NextApiResponse } from "next"; +import { captureException } from "@sentry/nextjs"; import { withIronSessionApiRoute } from "iron-session/next"; -import { request } from "../../utils/url"; -import { ironOptions } from "../../iron.config"; +import type { NextApiRequest, NextApiResponse } from "next"; import { twitterApiBaseUrl } from "../../constants/twitter"; +import { ironOptions } from "../../iron.config"; export default withIronSessionApiRoute(async function handler( req: NextApiRequest, @@ -45,53 +45,60 @@ export default withIronSessionApiRoute(async function handler( params.append("code_verifier", req.session.code_verifier); } - const { access_token: accessToken, refresh_token } = - await request( - `${twitterApiBaseUrl}/oauth2/token`, - { - method: "post", - headers: { - "Content-Type": "application/x-www-form-urlencoded", - Authorization: `Basic ${Buffer.from( - `${process.env.TWITTER_CLIENT_ID}:${process.env.TWITTER_CLIENT_SECRET}`, - ).toString("base64")}`, - }, - body: params, + const { + access_token: accessToken, + refresh_token, + }: TwitterOAuth2TokenResponse = await ( + await fetch(`${twitterApiBaseUrl}/oauth2/token`, { + method: "post", + headers: { + "Content-Type": "application/x-www-form-urlencoded", + Authorization: `Basic ${Buffer.from( + `${process.env.TWITTER_CLIENT_ID}:${process.env.TWITTER_CLIENT_SECRET}`, + ).toString("base64")}`, }, - ); + body: params, + }) + ).json(); req.session.refresh_token = refresh_token; await req.session.save(); - const { - data: { - id, - username, - name, - profile_image_url, - description, - public_metrics, - }, - } = await request( - `${twitterApiBaseUrl}/users/me?user.fields=profile_image_url,public_metrics,description`, - { - headers: { - Authorization: `Bearer ${accessToken}`, + const { data, title }: TwitterUsersMeResponse = await ( + await fetch( + `${twitterApiBaseUrl}/users/me?user.fields=profile_image_url,public_metrics,description`, + { + headers: { + Authorization: `Bearer ${accessToken}`, + }, }, - }, - ); + ) + ).json(); + const { + id, + username, + name, + profile_image_url, + description, + public_metrics, + } = data || {}; res.status(200).json({ accessToken, id, username, name, - profile_image_url: profile_image_url.replace("normal.jpg", "400x400.jpg"), + profile_image_url: profile_image_url?.replace( + "normal.jpg", + "400x400.jpg", + ), description, public_metrics, + error: title, }); - } catch (error) { + } catch (error: any) { console.error(error); - res.status(500).json({ error: "Internal server error " }); + captureException(error); + res.status(500).json({ error: "Internal Server Error" }); } }, ironOptions); @@ -105,7 +112,7 @@ interface TwitterOAuth2TokenResponse { } interface TwitterUsersMeResponse { - data: { + data?: { id: string; username: string; name: string; @@ -113,6 +120,11 @@ interface TwitterUsersMeResponse { description: string; public_metrics: TwitterPublicMetrics; }; + // Error data + title?: string; + detail?: string; + type?: string; + status?: string; } export interface TwitterPublicMetrics { diff --git a/pages/api/twitter-auth-url.ts b/pages/api/twitter-auth-url.ts index cb90bfa..da19be9 100644 --- a/pages/api/twitter-auth-url.ts +++ b/pages/api/twitter-auth-url.ts @@ -1,13 +1,14 @@ -import type { NextApiRequest, NextApiResponse } from "next"; +import { captureException } from "@sentry/nextjs"; import crypto from "crypto"; import { withIronSessionApiRoute } from "iron-session/next"; -import { base64URLEncode } from "../../utils/encoding"; -import { buildQueryString } from "../../utils/url"; -import { ironOptions } from "../../iron.config"; +import type { NextApiRequest, NextApiResponse } from "next"; import { twitterOAuthBaseUrl, twitterOAuthScopes, } from "../../constants/twitter"; +import { ironOptions } from "../../iron.config"; +import { base64URLEncode } from "../../utils/encoding"; +import { buildQueryString } from "../../utils/url"; export default withIronSessionApiRoute(async function handler( req: NextApiRequest, @@ -46,6 +47,7 @@ export default withIronSessionApiRoute(async function handler( res.status(200).json({ authUrl }); } catch (error) { console.error(error); + captureException(error); res.status(500).json({ error: "Internal server error" }); } }, diff --git a/pages/verification/index.tsx b/pages/verification/index.tsx index 776b87e..768a3e6 100644 --- a/pages/verification/index.tsx +++ b/pages/verification/index.tsx @@ -239,13 +239,12 @@ export default function VerificationPage() { } } catch (error) { if (error instanceof Error) { - if (error.message === TWITTER_LOGIN_ERROR) { - setErrorMessage({ message: TWITTER_LOGIN_ERROR, path: "/" }); - setErrorModalOpen(true); - } + setErrorMessage({ message: error.message, path: "/" }); + setErrorModalOpen(true); } console.error(error); + captureException(error); } finally { setIsLoadingInit(false); } @@ -404,8 +403,7 @@ export default function VerificationPage() { if (verification.status === "rejected") { if (verification.reason) { - const errorMessage = - verification.reason.message || verification.reason; + const errorMessage = verification.reason; captureException(errorMessage); } } @@ -443,8 +441,6 @@ export default function VerificationPage() { rest: REST_URL, }; - console.log(aminoMsgs); - const simulated = await simulateMsgs( chainInfo, walletKey.bech32Address, diff --git a/queries/twitter.ts b/queries/twitter.ts index 903fe11..b5aa5ad 100644 --- a/queries/twitter.ts +++ b/queries/twitter.ts @@ -1,5 +1,5 @@ import { - IcnsVerificationResponse, + IcnsVerificationResponseOnFrontend, TwitterAuthInfoResponse, TwitterAuthUrlResponse, } from "../types"; @@ -27,15 +27,18 @@ export const verifyTwitterAccount = async ( accessToken: string, ) => { return ( - await request("/api/icns-verification", { - method: "post", - headers: { - "Content-Type": "application/json", + await request( + "/api/icns-verification", + { + method: "post", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + claimer: claimer, + authToken: accessToken, + }), }, - body: JSON.stringify({ - claimer: claimer, - authToken: accessToken, - }), - }) + ) ).verificationList; }; diff --git a/types/api-response.ts b/types/api-response.ts index 5e23327..fa5a3d5 100644 --- a/types/api-response.ts +++ b/types/api-response.ts @@ -38,6 +38,30 @@ export interface IcnsVerificationResponse { )[]; } +export interface IcnsVerificationResponseOnFrontend { + verificationList: ( + | { + status: "fulfilled"; + value: { + errors: Error[]; + data: { + // JSON string + verifying_msg: string; + // Base64 encoded + public_key: string; + // Base64 encoded + signature: string; + algorithm: string; + }; + }; + } + | { + status: "rejected"; + reason: string; + } + )[]; +} + export interface NameByTwitterIdQueryResponse { data: { name: string; diff --git a/utils/url.ts b/utils/url.ts index b09e4f1..299a08e 100644 --- a/utils/url.ts +++ b/utils/url.ts @@ -2,23 +2,32 @@ import { TwitterLoginSuccess } from "../types"; import { TWITTER_LOGIN_ERROR } from "../constants/error-message"; import { WALLET_INSTALL_URL } from "../constants/wallet"; -export function request( +export async function request( url: string, config: RequestInit = {}, customConfig?: { isErrorIgnore?: boolean; }, ): Promise { - return fetch(url, config) - .then((response) => { - if (!response.ok && !customConfig?.isErrorIgnore) { - throw new Error( - `This is an HTTP error: The status is ${response.status} ${response.statusText}`, - ); - } - return response.json(); - }) - .then((data) => data as TResponse); + const response = await fetch(url, config); + const data = await response.json(); + + if ( + (!response.ok || data.error || data.errors) && + !customConfig?.isErrorIgnore + ) { + const { error, errors } = data; + let errorMessage; + if (error && error.error) { + errorMessage = error.error_description; + } else { + errorMessage = + (error && error.toString()) || (errors && errors.toString()); + } + throw new Error(errorMessage); + } + + return data; } export function buildQueryString(query: Record): string {