Skip to content

Commit

Permalink
feat(next): add error handlers to all methods (#772)
Browse files Browse the repository at this point in the history
  • Loading branch information
wangsijie authored Jul 24, 2024
1 parent ea4555f commit ff8bcbb
Show file tree
Hide file tree
Showing 5 changed files with 95 additions and 91 deletions.
5 changes: 5 additions & 0 deletions .changeset/shy-files-remain.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@logto/next": minor
---

support custom error handler for all methods in pages router
2 changes: 1 addition & 1 deletion packages/next/src/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,7 @@ describe('Next', () => {
},
test: async ({ fetch }) => {
await fetch({ method: 'GET', redirect: 'manual' });
expect(client.handleSignIn).toHaveBeenCalledWith(undefined, 'signUp');
expect(client.handleSignIn).toHaveBeenCalledWith(undefined, 'signUp', undefined);
},
});
});
Expand Down
155 changes: 65 additions & 90 deletions packages/next/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,12 @@ import {
type GetServerSidePropsResult,
type GetServerSidePropsContext,
type NextApiHandler,
type NextApiRequest,
type NextApiResponse,
} from 'next';
import { type NextApiRequestCookies } from 'next/dist/server/api-utils/index.js';

import LogtoNextBaseClient from './client.js';
import type { LogtoNextConfig } from './types.js';
import type { ErrorHandler, LogtoNextConfig } from './types.js';
import { buildHandler } from './utils.js';

export type { LogtoNextConfig } from './types.js';

Expand Down Expand Up @@ -50,36 +49,37 @@ export default class LogtoClient extends LogtoNextBaseClient {
});
}

handleSignIn =
(
redirectUri = `${this.config.baseUrl}/api/logto/sign-in-callback`,
interactionMode?: InteractionMode
): NextApiHandler =>
async (request, response) => {
handleSignIn = (
redirectUri = `${this.config.baseUrl}/api/logto/sign-in-callback`,
interactionMode?: InteractionMode,
onError?: ErrorHandler
): NextApiHandler =>
buildHandler(async (request, response) => {
const nodeClient = await this.createNodeClientFromNextApi(request, response);
await nodeClient.signIn(redirectUri, interactionMode);
await this.storage?.save();

if (this.navigateUrl) {
response.redirect(this.navigateUrl);
}
};
}, onError);

handleSignInCallback =
(redirectTo = this.config.baseUrl): NextApiHandler =>
async (request, response) => {
handleSignInCallback = (
redirectTo = this.config.baseUrl,
onError?: ErrorHandler
): NextApiHandler =>
buildHandler(async (request, response) => {
const nodeClient = await this.createNodeClientFromNextApi(request, response);

if (request.url) {
await nodeClient.handleSignInCallback(`${this.config.baseUrl}${request.url}`);
await this.storage?.save();
response.redirect(redirectTo);
}
};
}, onError);

handleSignOut =
(redirectUri = this.config.baseUrl): NextApiHandler =>
async (request, response) => {
handleSignOut = (redirectUri = this.config.baseUrl, onError?: ErrorHandler): NextApiHandler =>
buildHandler(async (request, response) => {
const nodeClient = await this.createNodeClientFromNextApi(request, response);
await nodeClient.signOut(redirectUri);

Expand All @@ -89,76 +89,60 @@ export default class LogtoClient extends LogtoNextBaseClient {
if (this.navigateUrl) {
response.redirect(this.navigateUrl);
}
};

handleUser = (configs?: GetContextParameters) =>
this.withLogtoApiRoute((request, response) => {
response.json(request.user);
}, configs);
}, onError);

handleUser = (configs?: GetContextParameters, onError?: ErrorHandler) =>
this.withLogtoApiRoute(
(request, response) => {
response.json(request.user);
},
configs,
onError
);

handleAuthRoutes =
(
configs?: GetContextParameters,
onError?: (request: NextApiRequest, response: NextApiResponse, error: unknown) => unknown
): NextApiHandler =>
(configs?: GetContextParameters, onError?: ErrorHandler): NextApiHandler =>
(request, response) => {
try {
const { action } = request.query;

if (action === 'sign-in') {
return this.handleSignIn()(request, response);
}

if (action === 'sign-up') {
return this.handleSignIn(undefined, 'signUp')(request, response);
}
const { action } = request.query;

if (action === 'sign-in-callback') {
return this.handleSignInCallback()(request, response);
}
if (action === 'sign-in') {
return this.handleSignIn(undefined, undefined, onError)(request, response);
}

if (action === 'sign-out') {
return this.handleSignOut()(request, response);
}
if (action === 'sign-up') {
return this.handleSignIn(undefined, 'signUp', onError)(request, response);
}

if (action === 'user') {
return this.handleUser(configs)(request, response);
}
if (action === 'sign-in-callback') {
return this.handleSignInCallback(undefined, onError)(request, response);
}

response.status(404).end();
} catch (error: unknown) {
if (onError) {
return onError(request, response, error);
}
if (action === 'sign-out') {
return this.handleSignOut(undefined, onError)(request, response);
}

throw error;
if (action === 'user') {
return this.handleUser(configs)(request, response);
}
};

withLogtoApiRoute =
(
handler: NextApiHandler,
config: GetContextParameters = {},
onError?: (request: NextApiRequest, response: NextApiResponse, error: unknown) => unknown
): NextApiHandler =>
async (request, response) => {
try {
const nodeClient = await this.createNodeClientFromNextApi(request, response);
const user = await nodeClient.getContext(config);
await this.storage?.save();
response.status(404).end();
};

// eslint-disable-next-line @silverhand/fp/no-mutating-methods
Object.defineProperty(request, 'user', { enumerable: true, get: () => user });
withLogtoApiRoute = (
handler: NextApiHandler,
config: GetContextParameters = {},
onError?: ErrorHandler
): NextApiHandler =>
buildHandler(async (request, response) => {
const nodeClient = await this.createNodeClientFromNextApi(request, response);
const user = await nodeClient.getContext(config);
await this.storage?.save();

return handler(request, response);
} catch (error: unknown) {
if (onError) {
return onError(request, response, error);
}
// eslint-disable-next-line @silverhand/fp/no-mutating-methods
Object.defineProperty(request, 'user', { enumerable: true, get: () => user });

throw error;
}
};
return handler(request, response);
}, onError);

withLogtoSsr =
<P extends Record<string, unknown> = Record<string, unknown>>(
Expand All @@ -169,22 +153,13 @@ export default class LogtoClient extends LogtoNextBaseClient {
onError?: (error: unknown) => unknown
) =>
async (context: GetServerSidePropsContext) => {
try {
const nodeClient = await this.createNodeClientFromNextApi(context.req, context.res);
const user = await nodeClient.getContext(configs);
await this.storage?.save();

// eslint-disable-next-line @silverhand/fp/no-mutating-methods
Object.defineProperty(context.req, 'user', { enumerable: true, get: () => user });

return await handler(context);
} catch (error: unknown) {
if (onError) {
return onError(error);
}

throw error;
}
return this.withLogtoApiRoute(
async () => {
return handler(context);
},
configs,
onError
);
};

async createNodeClientFromNextApi(
Expand Down
7 changes: 7 additions & 0 deletions packages/next/src/types.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { LogtoConfig } from '@logto/node';
import type NodeClient from '@logto/node';
import { type NextApiRequest, type NextApiResponse } from 'next';

export type LogtoNextConfig = LogtoConfig & {
cookieSecret: string;
Expand All @@ -10,3 +11,9 @@ export type LogtoNextConfig = LogtoConfig & {
export type Adapters = {
NodeClient: typeof NodeClient;
};

export type ErrorHandler = (
request: NextApiRequest,
response: NextApiResponse,
error: unknown
) => unknown;
17 changes: 17 additions & 0 deletions packages/next/src/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { type NextApiHandler } from 'next';

import { type ErrorHandler } from './types';

export const buildHandler = (handler: NextApiHandler, onError?: ErrorHandler): NextApiHandler => {
return async (request, response) => {
try {
await handler(request, response);
} catch (error: unknown) {
if (onError) {
return onError(request, response, error);
}

throw error;
}
};
};

0 comments on commit ff8bcbb

Please sign in to comment.