Skip to content

Commit

Permalink
feat: handleRequest for custom middlewares. Deprecate `onUnhandledR…
Browse files Browse the repository at this point in the history
…equest` middleware option (#340)
  • Loading branch information
baoshan authored Sep 29, 2022
1 parent 962755d commit f5c1b66
Show file tree
Hide file tree
Showing 7 changed files with 159 additions and 12 deletions.
81 changes: 78 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -939,7 +939,7 @@ All exposed paths will be prefixed with the provided prefix. Defaults to `"/api/
</tr>
<tr>
<th>
<code>options.onUnhandledRequest</code>
<code>options.onUnhandledRequest</code> __deprecated__
</th>
<th>
<code>function</code>
Expand Down Expand Up @@ -1029,7 +1029,7 @@ All exposed paths will be prefixed with the provided prefix. Defaults to `"/api/
</tr>
<tr>
<th>
<code>options.onUnhandledRequest</code>
<code>options.onUnhandledRequest</code> __deprecated__
</th>
<th>
<code>function</code>
Expand Down Expand Up @@ -1119,7 +1119,7 @@ All exposed paths will be prefixed with the provided prefix. Defaults to `"/api/
</tr>
<tr>
<th>
<code>options.onUnhandledRequest</code>
<code>options.onUnhandledRequest</code> __deprecated__
</th>
<th>
<code>function</code>
Expand All @@ -1145,6 +1145,81 @@ function onUnhandledRequest(request) {
</tbody>
</table>

### Build Custom Middlewares

When above middlewares do not meet your needs, you can build your own
using the exported `handleRequest` function.

[`handleRequest`](./src/middleware/handle-request.ts) function is an abstract HTTP handler which accepts an `OctokitRequest` and returns an `OctokitResponse` if the request matches any predefined route.

> Different environments (e.g., Node.js, Cloudflare Workers, Deno, etc.) exposes different APIs when processing HTTP requests (e.g., [`IncomingMessage`](https://nodejs.org/api/http.html#http_class_http_incomingmessage) for Node.js, [`Request`](https://developer.mozilla.org/en-US/docs/Web/API/Request) for Cloudflare workers, etc.). Two HTTP-related types ([`OctokitRequest` and `OctokitResponse`](.src/middleware/types.ts)) are generalized to make an abstract HTTP handler possible.
To share the behavior and capability with the existing Node.js middleware (and be compatible with [OAuth user authentication strategy in the browser](https://github.com/octokit/auth-oauth-user-client.js)), it is better to implement your HTTP handler/middleware based on `handleRequest` function.

`handleRequest` function takes three parameters:

<table width="100%">
<thead align=left>
<tr>
<th width=150>
name
</th>
<th width=70>
type
</th>
<th>
description
</th>
</tr>
</thead>
<tbody align=left valign=top>
<tr>
<th>
<code>app</code>
</th>
<th>
<code>OAuthApp instance</code>
</th>
<td>
<strong>Required</strong>.
</td>
</tr>
<tr>
<th>
<code>options.pathPrefix</code>
</th>
<th>
<code>string</code>
</th>
<td>

All exposed paths will be prefixed with the provided prefix. Defaults to `"/api/github/oauth"`

</td>
</tr>
<tr>
<th>
<code>request</code>
</th>
<th>
<code>OctokitRequest</code>
</th>
<td>
Generalized HTTP request in `OctokitRequest` type.
</td>
</tr>
</tbody>
</table>

Implementing an HTTP handler/middleware for a certain environment involves three steps:

1. Write a function to parse the HTTP request (e.g., `IncomingMessage` in Node.js) into an `OctokitRequest` object. See [`node/parse-request.ts`](.src/middleware/node/parse-request.ts) for reference.
2. Write a function to render an `OctokitResponse` object (e.g., as `ServerResponse` in Node.js). See [`node/send-response.ts`](.src/middleware/node/send-response.ts) for reference.
3. Expose an HTTP handler/middleware in the dialect of the environment which performs three steps:
1. Parse the HTTP request using (1).
2. Process the `OctokitRequest` object using `handleRequest`.
3. Render the `OctokitResponse` object using (2).

## Contributing

See [CONTRIBUTING.md](CONTRIBUTING.md)
Expand Down
11 changes: 11 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,17 @@ import type {
Options,
State,
} from "./types";

// types required by external handlers (aws-lambda, etc)
export type {
HandlerOptions,
OctokitRequest,
OctokitResponse,
} from "./middleware/types";

// generic handlers
export { handleRequest } from "./middleware/handle-request";

export { createNodeMiddleware } from "./middleware/node/index";
export {
createCloudflareHandler,
Expand Down
12 changes: 9 additions & 3 deletions src/middleware/aws-lambda/api-gateway-v2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { handleRequest } from "../handle-request";
import { onUnhandledRequestDefault } from "../on-unhandled-request-default";
import { HandlerOptions } from "../types";
import { OAuthApp } from "../../index";
import { Options, ClientType } from "../../types";
import { ClientType, Options } from "../../types";
import type {
APIGatewayProxyEventV2,
APIGatewayProxyStructuredResultV2,
Expand All @@ -22,16 +22,22 @@ export function createAWSLambdaAPIGatewayV2Handler(
app: OAuthApp<Options<ClientType>>,
{
pathPrefix,
onUnhandledRequest = onUnhandledRequestDefaultAWSAPIGatewayV2,
onUnhandledRequest,
}: HandlerOptions & {
onUnhandledRequest?: (
event: APIGatewayProxyEventV2
) => Promise<APIGatewayProxyStructuredResultV2>;
} = {}
) {
if (onUnhandledRequest) {
app.octokit.log.warn(
"[@octokit/oauth-app] `onUnhandledRequest` is deprecated and will be removed from the next major version."
);
}
onUnhandledRequest ??= onUnhandledRequestDefaultAWSAPIGatewayV2;
return async function (event: APIGatewayProxyEventV2) {
const request = parseRequest(event);
const response = await handleRequest(app, { pathPrefix }, request);
return response ? sendResponse(response) : onUnhandledRequest(event);
return response ? sendResponse(response) : onUnhandledRequest!(event);
};
}
10 changes: 8 additions & 2 deletions src/middleware/node/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,20 @@ export function createNodeMiddleware(
app: OAuthApp<Options<ClientType>>,
{
pathPrefix,
onUnhandledRequest = onUnhandledRequestDefaultNode,
onUnhandledRequest,
}: HandlerOptions & {
onUnhandledRequest?: (
request: IncomingMessage,
response: ServerResponse
) => void;
} = {}
) {
if (onUnhandledRequest) {
app.octokit.log.warn(
"[@octokit/oauth-app] `onUnhandledRequest` is deprecated and will be removed from the next major version."
);
}
onUnhandledRequest ??= onUnhandledRequestDefaultNode;
return async function (
request: IncomingMessage,
response: ServerResponse,
Expand All @@ -50,7 +56,7 @@ export function createNodeMiddleware(
} else if (typeof next === "function") {
next();
} else {
onUnhandledRequest(request, response);
onUnhandledRequest!(request, response);
}
};
}
10 changes: 8 additions & 2 deletions src/middleware/web-worker/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,17 @@ export function createWebWorkerHandler<T extends Options<ClientType>>(
app: OAuthApp<T>,
{
pathPrefix,
onUnhandledRequest = onUnhandledRequestDefaultWebWorker,
onUnhandledRequest,
}: HandlerOptions & {
onUnhandledRequest?: (request: Request) => Response | Promise<Response>;
} = {}
) {
if (onUnhandledRequest) {
app.octokit.log.warn(
"[@octokit/oauth-app] `onUnhandledRequest` is deprecated and will be removed from the next major version."
);
}
onUnhandledRequest ??= onUnhandledRequestDefaultWebWorker;
return async function (request: Request): Promise<Response> {
const octokitRequest = parseRequest(request);
const octokitResponse = await handleRequest(
Expand All @@ -32,7 +38,7 @@ export function createWebWorkerHandler<T extends Options<ClientType>>(
);
return octokitResponse
? sendResponse(octokitResponse)
: await onUnhandledRequest(request);
: await onUnhandledRequest!(request);
};
}

Expand Down
41 changes: 40 additions & 1 deletion test/deprecations.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
import { URL } from "url";
import * as nodeFetch from "node-fetch";
import fromEntries from "fromentries";
import { createCloudflareHandler, OAuthApp } from "../src";
import {
createAWSLambdaAPIGatewayV2Handler,
createCloudflareHandler,
createNodeMiddleware,
createWebWorkerHandler,
OAuthApp,
} from "../src";
import { Octokit } from "@octokit/core";

describe("deprecations", () => {
Expand Down Expand Up @@ -52,4 +58,37 @@ describe("deprecations", () => {
expect(url.searchParams.get("state")).toMatch(/^\w+$/);
expect(url.searchParams.get("scope")).toEqual(null);
});

it("`onUnhandledRequest` is deprecated and will be removed from the next major version", async () => {
const warn = jest.fn().mockResolvedValue(undefined);
const handleRequest = createAWSLambdaAPIGatewayV2Handler(
new OAuthApp({
clientType: "github-app",
clientId: "client_id_123",
clientSecret: "client_secret_456",
Octokit: Octokit.defaults({
log: {
debug: () => undefined,
info: () => undefined,
warn,
error: () => undefined,
},
}),
}),
{
onUnhandledRequest: async (request) => {
return {
statusCode: 404,
headers: {},
body: "",
};
},
}
);

expect(warn.mock.calls.length).toEqual(1);
expect(warn.mock.calls[0][0]).toEqual(
"[@octokit/oauth-app] `onUnhandledRequest` is deprecated and will be removed from the next major version."
);
});
});
6 changes: 5 additions & 1 deletion test/smoke.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { OAuthApp } from "../src";
import { handleRequest, OAuthApp } from "../src";

describe("Smoke test", () => {
it("OAuthApp is a function", () => {
Expand All @@ -12,4 +12,8 @@ describe("Smoke test", () => {
it("OAuthApp.VERSION is set", () => {
expect(OAuthApp.VERSION).toEqual("0.0.0-development");
});

it("handleRequest is a function", () => {
expect(typeof handleRequest).toEqual("function");
});
});

0 comments on commit f5c1b66

Please sign in to comment.