Merge pull request #323 from documenso/feat/promise-safety

feat: promise safety
This commit is contained in:
Lucas Smith
2023-08-30 11:32:32 +10:00
committed by GitHub
35 changed files with 103 additions and 57 deletions

7
.eslintignore Normal file
View File

@ -0,0 +1,7 @@
# Config files
*.config.js
*.config.cjs
# Statically hosted javascript files
apps/*/public/*.js
apps/*/public/*.cjs

View File

@ -5,7 +5,7 @@ import { allDocuments } from 'contentlayer/generated';
import type { MDXComponents } from 'mdx/types'; import type { MDXComponents } from 'mdx/types';
import { useMDXComponent } from 'next-contentlayer/hooks'; import { useMDXComponent } from 'next-contentlayer/hooks';
export const generateStaticParams = async () => export const generateStaticParams = () =>
allDocuments.map((post) => ({ post: post._raw.flattenedPath })); allDocuments.map((post) => ({ post: post._raw.flattenedPath }));
export const generateMetadata = ({ params }: { params: { content: string } }) => { export const generateMetadata = ({ params }: { params: { content: string } }) => {

View File

@ -7,7 +7,7 @@ import { ChevronLeft } from 'lucide-react';
import type { MDXComponents } from 'mdx/types'; import type { MDXComponents } from 'mdx/types';
import { useMDXComponent } from 'next-contentlayer/hooks'; import { useMDXComponent } from 'next-contentlayer/hooks';
export const generateStaticParams = async () => export const generateStaticParams = () =>
allBlogPosts.map((post) => ({ post: post._raw.flattenedPath })); allBlogPosts.map((post) => ({ post: post._raw.flattenedPath }));
export const generateMetadata = ({ params }: { params: { post: string } }) => { export const generateMetadata = ({ params }: { params: { post: string } }) => {

View File

@ -43,7 +43,7 @@ export default async function OpenPage() {
accept: 'application/vnd.github.v3+json', accept: 'application/vnd.github.v3+json',
}, },
}) })
.then((res) => res.json()) .then(async (res) => res.json())
.then((res) => ZGithubStatsResponse.parse(res)); .then((res) => ZGithubStatsResponse.parse(res));
const { total_count: mergedPullRequests } = await fetch( const { total_count: mergedPullRequests } = await fetch(
@ -54,7 +54,7 @@ export default async function OpenPage() {
}, },
}, },
) )
.then((res) => res.json()) .then(async (res) => res.json())
.then((res) => ZMergedPullRequestsResponse.parse(res)); .then((res) => ZMergedPullRequestsResponse.parse(res));
const STARGAZERS_DATA = await fetch('https://stargrazer-live.onrender.com/api/stats', { const STARGAZERS_DATA = await fetch('https://stargrazer-live.onrender.com/api/stats', {
@ -62,7 +62,7 @@ export default async function OpenPage() {
accept: 'application/json', accept: 'application/json',
}, },
}) })
.then((res) => res.json()) .then(async (res) => res.json())
.then((res) => ZStargazersLiveResponse.parse(res)); .then((res) => ZStargazersLiveResponse.parse(res));
return ( return (

View File

@ -24,7 +24,7 @@ export default async function IndexPage() {
accept: 'application/vnd.github.v3+json', accept: 'application/vnd.github.v3+json',
}, },
}) })
.then((res) => res.json()) .then(async (res) => res.json())
.then((res) => (typeof res.stargazers_count === 'number' ? res.stargazers_count : undefined)) .then((res) => (typeof res.stargazers_count === 'number' ? res.stargazers_count : undefined))
.catch(() => undefined); .catch(() => undefined);

View File

@ -63,7 +63,9 @@ export const ClaimPlanDialog = ({ className, planId, children }: ClaimPlanDialog
const onFormSubmit = async ({ name, email }: TClaimPlanDialogFormSchema) => { const onFormSubmit = async ({ name, email }: TClaimPlanDialogFormSchema) => {
try { try {
const delay = new Promise<void>((resolve) => setTimeout(resolve, 1000)); const delay = new Promise<void>((resolve) => {
setTimeout(resolve, 1000);
});
const [redirectUrl] = await Promise.all([ const [redirectUrl] = await Promise.all([
claimPlan({ name, email, planId, signatureText: name, signatureDataUrl: null }), claimPlan({ name, email, planId, signatureText: name, signatureDataUrl: null }),

View File

@ -13,7 +13,7 @@ export const PasswordReveal = ({ password }: PasswordRevealProps) => {
const [, copy] = useCopyToClipboard(); const [, copy] = useCopyToClipboard();
const onCopyClick = () => { const onCopyClick = () => {
copy(password).then(() => { void copy(password).then(() => {
toast({ toast({
title: 'Copied to clipboard', title: 'Copied to clipboard',
description: 'Your password has been copied to your clipboard.', description: 'Your password has been copied to your clipboard.',

View File

@ -124,7 +124,7 @@ export const Widget = ({ className, children, ...props }: WidgetProps) => {
setValue('signatureDataUrl', draftSignatureDataUrl); setValue('signatureDataUrl', draftSignatureDataUrl);
setValue('signatureText', ''); setValue('signatureText', '');
trigger('signatureDataUrl'); void trigger('signatureDataUrl');
setShowSigningDialog(false); setShowSigningDialog(false);
}; };
@ -135,7 +135,9 @@ export const Widget = ({ className, children, ...props }: WidgetProps) => {
signatureText, signatureText,
}: TWidgetFormSchema) => { }: TWidgetFormSchema) => {
try { try {
const delay = new Promise<void>((resolve) => setTimeout(resolve, 1000)); const delay = new Promise<void>((resolve) => {
setTimeout(resolve, 1000);
});
// eslint-disable-next-line turbo/no-undeclared-env-vars // eslint-disable-next-line turbo/no-undeclared-env-vars
const planId = process.env.NEXT_PUBLIC_STRIPE_COMMUNITY_PLAN_MONTHLY_PRICE_ID; const planId = process.env.NEXT_PUBLIC_STRIPE_COMMUNITY_PLAN_MONTHLY_PRICE_ID;

View File

@ -149,7 +149,7 @@ export const NameField = ({ field, recipient }: NameFieldProps) => {
disabled={!localFullName} disabled={!localFullName}
onClick={() => { onClick={() => {
setShowFullNameModal(false); setShowFullNameModal(false);
onSign('local'); void onSign('local');
}} }}
> >
Sign Sign

View File

@ -182,7 +182,7 @@ export const SignatureField = ({ field, recipient }: SignatureFieldProps) => {
disabled={!localSignature} disabled={!localSignature}
onClick={() => { onClick={() => {
setShowSignatureModal(false); setShowSignatureModal(false);
onSign('local'); void onSign('local');
}} }}
> >
Sign Sign

View File

@ -118,7 +118,7 @@ export const ProfileDropdown = ({ user }: ProfileDropdownProps) => {
<DropdownMenuItem <DropdownMenuItem
onSelect={() => onSelect={() =>
signOut({ void signOut({
callbackUrl: '/', callbackUrl: '/',
}) })
} }

View File

@ -63,7 +63,9 @@ export const ClaimPlanDialog = ({ className, planId, children }: ClaimPlanDialog
const onFormSubmit = async ({ name, email }: TClaimPlanDialogFormSchema) => { const onFormSubmit = async ({ name, email }: TClaimPlanDialogFormSchema) => {
try { try {
const delay = new Promise<void>((resolve) => setTimeout(resolve, 1000)); const delay = new Promise<void>((resolve) => {
setTimeout(resolve, 1000);
});
const [redirectUrl] = await Promise.all([ const [redirectUrl] = await Promise.all([
claimPlan({ name, email, planId, signatureText: name, signatureDataUrl: null }), claimPlan({ name, email, planId, signatureText: name, signatureDataUrl: null }),

View File

@ -13,7 +13,7 @@ export const PasswordReveal = ({ password }: PasswordRevealProps) => {
const [, copy] = useCopyToClipboard(); const [, copy] = useCopyToClipboard();
const onCopyClick = () => { const onCopyClick = () => {
copy(password).then(() => { void copy(password).then(() => {
toast({ toast({
title: 'Copied to clipboard', title: 'Copied to clipboard',
description: 'Your password has been copied to your clipboard.', description: 'Your password has been copied to your clipboard.',

View File

@ -124,7 +124,7 @@ export const Widget = ({ className, children, ...props }: WidgetProps) => {
setValue('signatureDataUrl', draftSignatureDataUrl); setValue('signatureDataUrl', draftSignatureDataUrl);
setValue('signatureText', ''); setValue('signatureText', '');
trigger('signatureDataUrl'); void trigger('signatureDataUrl');
setShowSigningDialog(false); setShowSigningDialog(false);
}; };
@ -135,7 +135,9 @@ export const Widget = ({ className, children, ...props }: WidgetProps) => {
signatureText, signatureText,
}: TWidgetFormSchema) => { }: TWidgetFormSchema) => {
try { try {
const delay = new Promise<void>((resolve) => setTimeout(resolve, 1000)); const delay = new Promise<void>((resolve) => {
setTimeout(resolve, 1000);
});
// eslint-disable-next-line turbo/no-undeclared-env-vars // eslint-disable-next-line turbo/no-undeclared-env-vars
const planId = process.env.NEXT_PUBLIC_STRIPE_COMMUNITY_PLAN_MONTHLY_PRICE_ID; const planId = process.env.NEXT_PUBLIC_STRIPE_COMMUNITY_PLAN_MONTHLY_PRICE_ID;

View File

@ -76,10 +76,7 @@ export const SignInForm = ({ className }: SignInFormProps) => {
return ( return (
<form <form
className={cn('flex w-full flex-col gap-y-4', className)} className={cn('flex w-full flex-col gap-y-4', className)}
onSubmit={(e) => { onSubmit={handleSubmit(onFormSubmit)}
e.preventDefault();
handleSubmit(onFormSubmit)();
}}
> >
<div> <div>
<Label htmlFor="email" className="text-slate-500"> <Label htmlFor="email" className="text-slate-500">

View File

@ -32,7 +32,7 @@ export const getFlag = async (
revalidate: 60, revalidate: 60,
}, },
}) })
.then((res) => res.json()) .then(async (res) => res.json())
.then((res) => ZFeatureFlagValueSchema.parse(res)) .then((res) => ZFeatureFlagValueSchema.parse(res))
.catch(() => false); .catch(() => false);
@ -64,7 +64,7 @@ export const getAllFlags = async (
revalidate: 60, revalidate: 60,
}, },
}) })
.then((res) => res.json()) .then(async (res) => res.json())
.then((res) => z.record(z.string(), ZFeatureFlagValueSchema).parse(res)) .then((res) => z.record(z.string(), ZFeatureFlagValueSchema).parse(res))
.catch(() => LOCAL_FEATURE_FLAGS); .catch(() => LOCAL_FEATURE_FLAGS);
}; };

View File

@ -11,6 +11,6 @@ export default function PostHogServerClient() {
return new PostHog(postHogConfig.key, { return new PostHog(postHogConfig.key, {
host: postHogConfig.host, host: postHogConfig.host,
fetch: (...args) => fetch(...args), fetch: async (...args) => fetch(...args),
}); });
} }

View File

@ -1,6 +1,6 @@
import { NextRequest, NextResponse } from 'next/server'; import { NextRequest, NextResponse } from 'next/server';
export default async function middleware(req: NextRequest) { export default function middleware(req: NextRequest) {
if (req.nextUrl.pathname === '/') { if (req.nextUrl.pathname === '/') {
const redirectUrl = new URL('/documents', req.url); const redirectUrl = new URL('/documents', req.url);

View File

@ -4,7 +4,7 @@ import { appRouter } from '@documenso/trpc/server/router';
export default trpcNext.createNextApiHandler({ export default trpcNext.createNextApiHandler({
router: appRouter, router: appRouter,
createContext: ({ req, res }) => createTrpcContext({ req, res }), createContext: async ({ req, res }) => createTrpcContext({ req, res }),
}); });
// export default async function handler(_req: NextApiRequest, res: NextApiResponse) { // export default async function handler(_req: NextApiRequest, res: NextApiResponse) {

View File

@ -67,7 +67,7 @@ export function FeatureFlagProvider({
const interval = setInterval(() => { const interval = setInterval(() => {
if (document.hasFocus()) { if (document.hasFocus()) {
getAllFlags().then((newFlags) => setFlags(newFlags)); void getAllFlags().then((newFlags) => setFlags(newFlags));
} }
}, FEATURE_FLAG_POLL_INTERVAL); }, FEATURE_FLAG_POLL_INTERVAL);
@ -84,7 +84,7 @@ export function FeatureFlagProvider({
return; return;
} }
const onFocus = () => getAllFlags().then((newFlags) => setFlags(newFlags)); const onFocus = () => void getAllFlags().then((newFlags) => setFlags(newFlags));
window.addEventListener('focus', onFocus); window.addEventListener('focus', onFocus);

View File

@ -110,9 +110,10 @@ export class MailChannelsTransport implements Transport<SentMessageInfo> {
}); });
} }
res.json().then((data) => { res
return callback(new Error(`MailChannels error: ${data.message}`), null); .json()
}); .then((data) => callback(new Error(`MailChannels error: ${data.message}`), null))
.catch((err) => callback(err, null));
}) })
.catch((err) => { .catch((err) => {
return callback(err, null); return callback(err, null);

View File

@ -19,6 +19,8 @@ module.exports = {
parser: '@typescript-eslint/parser', parser: '@typescript-eslint/parser',
parserOptions: { parserOptions: {
tsconfigRootDir: __dirname,
project: ['../../apps/*/tsconfig.json', '../../packages/*/tsconfig.json'],
ecmaVersion: 2022, ecmaVersion: 2022,
ecmaFeatures: { ecmaFeatures: {
jsx: true, jsx: true,
@ -32,6 +34,18 @@ module.exports = {
'no-unused-vars': ['error', { argsIgnorePattern: '^_' }], 'no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
'@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }], '@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
// Safety with promises so we aren't running with scissors
'no-promise-executor-return': 'error',
'prefer-promise-reject-errors': 'error',
'require-atomic-updates': 'error',
'@typescript-eslint/no-floating-promises': 'error',
'@typescript-eslint/no-misused-promises': [
'error',
{ checksVoidReturn: { attributes: false } },
],
'@typescript-eslint/promise-function-async': 'error',
'@typescript-eslint/require-await': 'error',
// We never want to use `as` but are required to on occasion to handle // We never want to use `as` but are required to on occasion to handle
// shortcomings in third-party and generated types. // shortcomings in third-party and generated types.
// //

View File

@ -0,0 +1,9 @@
{
"extends": "@documenso/tsconfig/base.json",
"compilerOptions": {
"allowJs": true,
"noEmit": true,
},
"include": ["**/*.cjs", "**/*.js"],
"exclude": ["dist", "build", "node_modules"]
}

View File

@ -89,7 +89,7 @@ export const NEXT_AUTH_OPTIONS: AuthOptions = {
}; };
}, },
async session({ token, session }) { session({ token, session }) {
if (token && token.email) { if (token && token.email) {
return { return {
...session, ...session,

View File

@ -1,9 +1,6 @@
import { GetServerSidePropsContext, NextApiRequest, NextApiResponse } from 'next'; import { GetServerSidePropsContext, NextApiRequest, NextApiResponse } from 'next';
import { headers } from 'next/headers';
import { NextRequest } from 'next/server';
import { getServerSession as getNextAuthServerSession } from 'next-auth'; import { getServerSession as getNextAuthServerSession } from 'next-auth';
import { getToken } from 'next-auth/jwt';
import { prisma } from '@documenso/prisma'; import { prisma } from '@documenso/prisma';
@ -30,18 +27,6 @@ export const getServerSession = async ({ req, res }: GetServerSessionOptions) =>
return user; return user;
}; };
export const getServerComponentToken = async () => {
const requestHeaders = Object.fromEntries(headers().entries());
const req = new NextRequest('http://example.com', {
headers: requestHeaders,
});
const token = await getToken({
req,
});
};
export const getServerComponentSession = async () => { export const getServerComponentSession = async () => {
const session = await getNextAuthServerSession(NEXT_AUTH_OPTIONS); const session = await getNextAuthServerSession(NEXT_AUTH_OPTIONS);

View File

@ -87,6 +87,6 @@ export const completeDocumentWithToken = async ({
if (documents.count > 0) { if (documents.count > 0) {
console.log('sealing document'); console.log('sealing document');
sealDocument({ documentId: document.id }); await sealDocument({ documentId: document.id });
} }
}; };

View File

@ -0,0 +1,9 @@
{
"extends": "@documenso/tsconfig/base.json",
"compilerOptions": {
"allowJs": true,
"noEmit": true,
},
"include": ["**/*.cjs", "**/*.js"],
"exclude": ["dist", "build", "node_modules"]
}

View File

@ -10,7 +10,7 @@ const t = initTRPC.context<TrpcContext>().create({
/** /**
* Middlewares * Middlewares
*/ */
export const authenticatedMiddleware = t.middleware(({ ctx, next }) => { export const authenticatedMiddleware = t.middleware(async ({ ctx, next }) => {
if (!ctx.session) { if (!ctx.session) {
throw new TRPCError({ throw new TRPCError({
code: 'UNAUTHORIZED', code: 'UNAUTHORIZED',
@ -18,7 +18,7 @@ export const authenticatedMiddleware = t.middleware(({ ctx, next }) => {
}); });
} }
return next({ return await next({
ctx: { ctx: {
...ctx, ...ctx,

View File

@ -0,0 +1,8 @@
{
"extends": "./base.json",
"compilerOptions": {
"noEmit": true,
},
"include": ["**/*.ts", "**/*.tsx", "**/*.d.ts", "**/*.json"],
"exclude": ["dist", "build", "node_modules"]
}

View File

@ -86,7 +86,7 @@ export const DocumentDropzone = ({ className, onDrop, ...props }: DocumentDropzo
multiple: false, multiple: false,
onDrop: ([acceptedFile]) => { onDrop: ([acceptedFile]) => {
if (acceptedFile && onDrop) { if (acceptedFile && onDrop) {
onDrop(acceptedFile); void onDrop(acceptedFile);
} }
}, },
}); });

View File

@ -88,6 +88,8 @@ export const AddFieldsFormPartial = ({
}, },
}); });
const onFormSubmit = handleSubmit(onSubmit);
const { const {
append, append,
remove, remove,
@ -500,7 +502,7 @@ export const AddFieldsFormPartial = ({
loading={isSubmitting} loading={isSubmitting}
disabled={isSubmitting} disabled={isSubmitting}
onGoBackClick={documentFlow.onBackStep} onGoBackClick={documentFlow.onBackStep}
onGoNextClick={() => handleSubmit(onSubmit)()} onGoNextClick={() => void onFormSubmit()}
/> />
</DocumentFlowFormContainerFooter> </DocumentFlowFormContainerFooter>
</> </>

View File

@ -68,6 +68,8 @@ export const AddSignersFormPartial = ({
}, },
}); });
const onFormSubmit = handleSubmit(onSubmit);
const { const {
append: appendSigner, append: appendSigner,
fields: signers, fields: signers,
@ -214,7 +216,7 @@ export const AddSignersFormPartial = ({
loading={isSubmitting} loading={isSubmitting}
disabled={isSubmitting} disabled={isSubmitting}
onGoBackClick={documentFlow.onBackStep} onGoBackClick={documentFlow.onBackStep}
onGoNextClick={() => handleSubmit(onSubmit)()} onGoNextClick={() => void onFormSubmit()}
/> />
</DocumentFlowFormContainerFooter> </DocumentFlowFormContainerFooter>
</> </>

View File

@ -47,6 +47,8 @@ export const AddSubjectFormPartial = ({
}, },
}); });
const onFormSubmit = handleSubmit(onSubmit);
return ( return (
<> <>
<DocumentFlowFormContainerContent> <DocumentFlowFormContainerContent>
@ -130,7 +132,7 @@ export const AddSubjectFormPartial = ({
disabled={isSubmitting} disabled={isSubmitting}
goNextLabel={document.status === DocumentStatus.DRAFT ? 'Send' : 'Update'} goNextLabel={document.status === DocumentStatus.DRAFT ? 'Send' : 'Update'}
onGoBackClick={documentFlow.onBackStep} onGoBackClick={documentFlow.onBackStep}
onGoNextClick={() => handleSubmit(onSubmit)()} onGoNextClick={() => void onFormSubmit()}
/> />
</DocumentFlowFormContainerFooter> </DocumentFlowFormContainerFooter>
</> </>

View File

@ -66,7 +66,7 @@ export const PDFViewer = ({ className, document, onPageClick, ...props }: PDFVie
const pageY = event.clientY - top; const pageY = event.clientY - top;
if (onPageClick) { if (onPageClick) {
onPageClick({ void onPageClick({
pageNumber, pageNumber,
numPages, numPages,
originalEvent: event, originalEvent: event,

View File

@ -302,8 +302,8 @@ export class Canvas {
/** /**
* Retrieves the signature as an image blob. * Retrieves the signature as an image blob.
*/ */
public toBlob(type?: string, quality?: number): Promise<Blob> { public async toBlob(type?: string, quality?: number): Promise<Blob> {
return new Promise((resolve, reject) => { const promise = new Promise<Blob>((resolve, reject) => {
this.$canvas.toBlob( this.$canvas.toBlob(
(blob) => { (blob) => {
if (!blob) { if (!blob) {
@ -317,5 +317,7 @@ export class Canvas {
quality, quality,
); );
}); });
return await promise;
} }
} }