Fix some logic handling error

This commit is contained in:
delivan 2022-12-23 22:30:35 +09:00
parent da01e40550
commit d33788e2b1
9 changed files with 143 additions and 86 deletions

View File

@ -23,7 +23,8 @@ class ErrorBoundary extends Component<Props, State> {
public componentDidCatch(error: Error, errorInfo: ErrorInfo) { public componentDidCatch(error: Error, errorInfo: ErrorInfo) {
console.error("Uncaught error:", error, errorInfo); console.error("Uncaught error:", error, errorInfo);
captureException(error); const errorMessage = error.message || error;
captureException(errorMessage);
} }
public render() { public render() {

View File

@ -2,12 +2,11 @@
const { withSentryConfig } = require("@sentry/nextjs"); const { withSentryConfig } = require("@sentry/nextjs");
const nextConfig = { const nextConfig = {
...(!!process.env.NEXT_PUBLIC_SENTRY_DSN && {
sentry: { sentry: {
hideSourceMaps: true, hideSourceMaps: true,
}, },
api: { }),
externalResolver: true,
},
reactStrictMode: false, reactStrictMode: false,
swcMinify: true, swcMinify: true,
compiler: { compiler: {

View File

@ -1,8 +1,7 @@
import { captureException } from "@sentry/nextjs";
import { withIronSessionApiRoute } from "iron-session/next"; import { withIronSessionApiRoute } from "iron-session/next";
import type { NextApiRequest, NextApiResponse } from "next"; import type { NextApiRequest, NextApiResponse } from "next";
import { ironOptions } from "../../iron.config"; import { ironOptions } from "../../iron.config";
import { IcnsVerificationResponse } from "../../types";
import { request } from "../../utils/url";
export default withIronSessionApiRoute(async function handler( export default withIronSessionApiRoute(async function handler(
req: NextApiRequest, req: NextApiRequest,
@ -10,30 +9,42 @@ export default withIronSessionApiRoute(async function handler(
) { ) {
try { try {
if (!process.env.ICNS_VERIFIER_ORIGIN_LIST) { 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" }); return res.status(500).json({ error: "Internal server error" });
} }
const verifierOriginList = process.env.ICNS_VERIFIER_ORIGIN_LIST.split(","); const verifierOriginList = process.env.ICNS_VERIFIER_ORIGIN_LIST.split(",");
const verificationList = await Promise.allSettled( const verificationList = await Promise.allSettled(
verifierOriginList.map((verfierOrigin) => verifierOriginList.map(async (verfierOrigin) =>
request<IcnsVerificationResponse>( (
`${verfierOrigin}/api/verify_twitter`, await fetch(`${verfierOrigin}/api/verify_twitter`, {
{
method: "post", method: "post",
headers: { headers: {
"Content-Type": "application/json", "Content-Type": "application/json",
}, },
body: JSON.stringify(req.body), 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({ res.status(200).json({
verificationList, verificationList: errorTrimmedVerificationList,
}); });
} catch (error) { } catch (error) {
console.error(error); console.error(error);
captureException(error);
res.status(500).json({ error: "Internal server error" }); res.status(500).json({ error: "Internal server error" });
} }
}, },

View File

@ -1,8 +1,8 @@
import type { NextApiRequest, NextApiResponse } from "next"; import { captureException } from "@sentry/nextjs";
import { withIronSessionApiRoute } from "iron-session/next"; import { withIronSessionApiRoute } from "iron-session/next";
import { request } from "../../utils/url"; import type { NextApiRequest, NextApiResponse } from "next";
import { ironOptions } from "../../iron.config";
import { twitterApiBaseUrl } from "../../constants/twitter"; import { twitterApiBaseUrl } from "../../constants/twitter";
import { ironOptions } from "../../iron.config";
export default withIronSessionApiRoute(async function handler( export default withIronSessionApiRoute(async function handler(
req: NextApiRequest, req: NextApiRequest,
@ -45,10 +45,11 @@ export default withIronSessionApiRoute(async function handler(
params.append("code_verifier", req.session.code_verifier); params.append("code_verifier", req.session.code_verifier);
} }
const { access_token: accessToken, refresh_token } = const {
await request<TwitterOAuth2TokenResponse>( access_token: accessToken,
`${twitterApiBaseUrl}/oauth2/token`, refresh_token,
{ }: TwitterOAuth2TokenResponse = await (
await fetch(`${twitterApiBaseUrl}/oauth2/token`, {
method: "post", method: "post",
headers: { headers: {
"Content-Type": "application/x-www-form-urlencoded", "Content-Type": "application/x-www-form-urlencoded",
@ -57,41 +58,47 @@ export default withIronSessionApiRoute(async function handler(
).toString("base64")}`, ).toString("base64")}`,
}, },
body: params, body: params,
}, })
); ).json();
req.session.refresh_token = refresh_token; req.session.refresh_token = refresh_token;
await req.session.save(); await req.session.save();
const { const { data, title }: TwitterUsersMeResponse = await (
data: { await fetch(
id,
username,
name,
profile_image_url,
description,
public_metrics,
},
} = await request<TwitterUsersMeResponse>(
`${twitterApiBaseUrl}/users/me?user.fields=profile_image_url,public_metrics,description`, `${twitterApiBaseUrl}/users/me?user.fields=profile_image_url,public_metrics,description`,
{ {
headers: { headers: {
Authorization: `Bearer ${accessToken}`, Authorization: `Bearer ${accessToken}`,
}, },
}, },
); )
).json();
const {
id,
username,
name,
profile_image_url,
description,
public_metrics,
} = data || {};
res.status(200).json({ res.status(200).json({
accessToken, accessToken,
id, id,
username, username,
name, name,
profile_image_url: profile_image_url.replace("normal.jpg", "400x400.jpg"), profile_image_url: profile_image_url?.replace(
"normal.jpg",
"400x400.jpg",
),
description, description,
public_metrics, public_metrics,
error: title,
}); });
} catch (error) { } catch (error: any) {
console.error(error); console.error(error);
res.status(500).json({ error: "Internal server error " }); captureException(error);
res.status(500).json({ error: "Internal Server Error" });
} }
}, },
ironOptions); ironOptions);
@ -105,7 +112,7 @@ interface TwitterOAuth2TokenResponse {
} }
interface TwitterUsersMeResponse { interface TwitterUsersMeResponse {
data: { data?: {
id: string; id: string;
username: string; username: string;
name: string; name: string;
@ -113,6 +120,11 @@ interface TwitterUsersMeResponse {
description: string; description: string;
public_metrics: TwitterPublicMetrics; public_metrics: TwitterPublicMetrics;
}; };
// Error data
title?: string;
detail?: string;
type?: string;
status?: string;
} }
export interface TwitterPublicMetrics { export interface TwitterPublicMetrics {

View File

@ -1,13 +1,14 @@
import type { NextApiRequest, NextApiResponse } from "next"; import { captureException } from "@sentry/nextjs";
import crypto from "crypto"; import crypto from "crypto";
import { withIronSessionApiRoute } from "iron-session/next"; import { withIronSessionApiRoute } from "iron-session/next";
import { base64URLEncode } from "../../utils/encoding"; import type { NextApiRequest, NextApiResponse } from "next";
import { buildQueryString } from "../../utils/url";
import { ironOptions } from "../../iron.config";
import { import {
twitterOAuthBaseUrl, twitterOAuthBaseUrl,
twitterOAuthScopes, twitterOAuthScopes,
} from "../../constants/twitter"; } from "../../constants/twitter";
import { ironOptions } from "../../iron.config";
import { base64URLEncode } from "../../utils/encoding";
import { buildQueryString } from "../../utils/url";
export default withIronSessionApiRoute(async function handler( export default withIronSessionApiRoute(async function handler(
req: NextApiRequest, req: NextApiRequest,
@ -46,6 +47,7 @@ export default withIronSessionApiRoute(async function handler(
res.status(200).json({ authUrl }); res.status(200).json({ authUrl });
} catch (error) { } catch (error) {
console.error(error); console.error(error);
captureException(error);
res.status(500).json({ error: "Internal server error" }); res.status(500).json({ error: "Internal server error" });
} }
}, },

View File

@ -239,13 +239,12 @@ export default function VerificationPage() {
} }
} catch (error) { } catch (error) {
if (error instanceof Error) { if (error instanceof Error) {
if (error.message === TWITTER_LOGIN_ERROR) { setErrorMessage({ message: error.message, path: "/" });
setErrorMessage({ message: TWITTER_LOGIN_ERROR, path: "/" });
setErrorModalOpen(true); setErrorModalOpen(true);
} }
}
console.error(error); console.error(error);
captureException(error);
} finally { } finally {
setIsLoadingInit(false); setIsLoadingInit(false);
} }
@ -404,8 +403,7 @@ export default function VerificationPage() {
if (verification.status === "rejected") { if (verification.status === "rejected") {
if (verification.reason) { if (verification.reason) {
const errorMessage = const errorMessage = verification.reason;
verification.reason.message || verification.reason;
captureException(errorMessage); captureException(errorMessage);
} }
} }
@ -443,8 +441,6 @@ export default function VerificationPage() {
rest: REST_URL, rest: REST_URL,
}; };
console.log(aminoMsgs);
const simulated = await simulateMsgs( const simulated = await simulateMsgs(
chainInfo, chainInfo,
walletKey.bech32Address, walletKey.bech32Address,

View File

@ -1,5 +1,5 @@
import { import {
IcnsVerificationResponse, IcnsVerificationResponseOnFrontend,
TwitterAuthInfoResponse, TwitterAuthInfoResponse,
TwitterAuthUrlResponse, TwitterAuthUrlResponse,
} from "../types"; } from "../types";
@ -27,7 +27,9 @@ export const verifyTwitterAccount = async (
accessToken: string, accessToken: string,
) => { ) => {
return ( return (
await request<IcnsVerificationResponse>("/api/icns-verification", { await request<IcnsVerificationResponseOnFrontend>(
"/api/icns-verification",
{
method: "post", method: "post",
headers: { headers: {
"Content-Type": "application/json", "Content-Type": "application/json",
@ -36,6 +38,7 @@ export const verifyTwitterAccount = async (
claimer: claimer, claimer: claimer,
authToken: accessToken, authToken: accessToken,
}), }),
}) },
)
).verificationList; ).verificationList;
}; };

View File

@ -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 { export interface NameByTwitterIdQueryResponse {
data: { data: {
name: string; name: string;

View File

@ -2,23 +2,32 @@ import { TwitterLoginSuccess } from "../types";
import { TWITTER_LOGIN_ERROR } from "../constants/error-message"; import { TWITTER_LOGIN_ERROR } from "../constants/error-message";
import { WALLET_INSTALL_URL } from "../constants/wallet"; import { WALLET_INSTALL_URL } from "../constants/wallet";
export function request<TResponse>( export async function request<TResponse>(
url: string, url: string,
config: RequestInit = {}, config: RequestInit = {},
customConfig?: { customConfig?: {
isErrorIgnore?: boolean; isErrorIgnore?: boolean;
}, },
): Promise<TResponse> { ): Promise<TResponse> {
return fetch(url, config) const response = await fetch(url, config);
.then((response) => { const data = await response.json();
if (!response.ok && !customConfig?.isErrorIgnore) {
throw new Error( if (
`This is an HTTP error: The status is ${response.status} ${response.statusText}`, (!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());
} }
return response.json(); throw new Error(errorMessage);
}) }
.then((data) => data as TResponse);
return data;
} }
export function buildQueryString(query: Record<string, any>): string { export function buildQueryString(query: Record<string, any>): string {