Skip to content

Commit

Permalink
+ universal http handler
Browse files Browse the repository at this point in the history
  • Loading branch information
palkan committed Oct 25, 2023
1 parent a15ee3f commit 4d331ee
Show file tree
Hide file tree
Showing 4 changed files with 90 additions and 15 deletions.
49 changes: 48 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,11 @@ Currently, this package only supports broadcasting over HTTP. However, AnyCable

### HTTP handlers

To glue our HTTP layer with the channels, we need to configure HTTP handlers. Below you can find an example of [Vercel](https://vercel.com) serverless functions:
To glue our HTTP layer with the channels, we need to configure HTTP handlers. Below you can find an examples for popular serverless platforms.

#### Vercel

Define [Vercel](https://vercel.com) serverless functions as follows:

```js
// api/anycable/connect/route.ts
Expand Down Expand Up @@ -224,3 +228,46 @@ export async function POST(request: Request) {
}
}
```

You can also avoid repeatition by using a universal handler and a bit of configuration:

```js
// next.config.js
const nextConfig = {
// ...
rewrites: async () => {
return [
{
source: "/api/anycable/:path*",
destination: "/api/anycable",
},
];
},
};

// ...
```

And then you can use the following handler:

```js
// api/anycable/route.ts
import { NextResponse } from "next/server";
import { handler, Status } from "@/lib/anycable";
import app from "../../cable";

export async function POST(request: Request) {
try {
const response = await handler(request, app);
return NextResponse.json(response, {
status: 200,
});
} catch (e) {
console.error(e);
return NextResponse.json({
status: Status.ERROR,
error_msg: "Server error",
});
}
}
```
7 changes: 6 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,12 @@ export type {
DisconnectResponse
} from './rpc'

export { connectHandler, commandHandler, disconnectHandler } from './service'
export {
handler,
connectHandler,
commandHandler,
disconnectHandler
} from './service'
export { Application, ConnectionHandle } from './application'
export type { IdentifiersMap } from './application'
export { Channel, ChannelHandle } from './channel'
Expand Down
21 changes: 21 additions & 0 deletions src/service/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,27 @@ function extractSessionId(req: Request) {
return req.headers.get('x-anycable-meta-sid')
}

// Universal handler for all AnyCable RPC methods,
// which infers the method from the request path
export const handler = async (
request: Request,
app: Application
): Promise<ConnectionResponse | CommandResponse | DisconnectResponse> => {
const path = new URL(request.url).pathname
const method = path.split('/').pop()

switch (method) {
case 'connect':
return connectHandler(request, app)
case 'disconnect':
return disconnectHandler(request, app)
case 'command':
return commandHandler(request, app)
default:
throw new Error(`Unknown RPC method: ${method}`)
}
}

export const connectHandler = async (
request: Request,
app: Application
Expand Down
28 changes: 15 additions & 13 deletions tests/service.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,16 @@ import {
Channel,
ConnectionHandle,
ChannelHandle,
connectHandler,
commandHandler,
disconnectHandler,
Status
handler,
Status,
ConnectionResponse,
CommandResponse,
DisconnectResponse
} from '../src/index'

const HandlersTest = suite('Handlers')
function createMockRequest(
method: string,
url: string,
options?: Partial<{ body: any; sid: string }>
): Request {
Expand All @@ -29,7 +31,7 @@ function createMockRequest(
headers.set('x-anycable-meta-sid', sid)
}

return new Request('http://cable.test/api', {
return new Request(`http://cable.test/api/${method}`, {
method: 'POST',
body: bodyBuffer,
headers: headers as any
Expand Down Expand Up @@ -77,10 +79,10 @@ class TestChannel extends Channel<TestIdentifiers, { roomId: string }> {

HandlersTest('connect + welcomed', async () => {
const app = new TestApplication()
const request = createMockRequest('http://localhost?user_id=13', {
const request = createMockRequest('connect', 'http://localhost?user_id=13', {
sid: 's42'
})
const response = await connectHandler(request, app)
const response = (await handler(request, app)) as ConnectionResponse

assert.is(response.status, Status.SUCCESS)
assert.is(response.error_msg, '')
Expand All @@ -92,10 +94,10 @@ HandlersTest('connect + welcomed', async () => {

HandlersTest('connect + rejected', async () => {
const app = new TestApplication()
const request = createMockRequest('http://localhost', {
const request = createMockRequest('connect', 'http://localhost', {
sid: 's42'
})
const response = await connectHandler(request, app)
const response = (await handler(request, app)) as ConnectionResponse

assert.is(response.status, Status.FAILURE)
assert.is(response.error_msg, 'Auth failed')
Expand All @@ -111,15 +113,15 @@ HandlersTest('command + subscribed + confirmed', async () => {

const identifier = `{"channel":"TestChannel","roomId":"42"}`

const request = createMockRequest('http://localhost', {
const request = createMockRequest('command', 'http://localhost', {
body: {
connection_identifiers: '{"userId":"13"}',
command: 'subscribe',
identifier
}
})

const response = await commandHandler(request, app)
const response = (await handler(request, app)) as CommandResponse

assert.is(response.status, Status.SUCCESS)
assert.is(response.disconnect, false)
Expand Down Expand Up @@ -157,14 +159,14 @@ HandlersTest('disconnect', async () => {

const identifier = `{"channel":"TestChannel","roomId":"42"}`

const request = createMockRequest('http://localhost', {
const request = createMockRequest('disconnect', 'http://localhost', {
body: {
identifiers: '{"userId":"13"}',
subscriptions: [identifier]
}
})

const response = await disconnectHandler(request, app)
const response = (await handler(request, app)) as DisconnectResponse

assert.is(response.status, Status.SUCCESS)
assert.is(disconnectedId, '13')
Expand Down

0 comments on commit 4d331ee

Please sign in to comment.