Skip to content

Commit

Permalink
fix(vite-plugin-nitro): enable websocket support & add docs (#1419)
Browse files Browse the repository at this point in the history
  • Loading branch information
nckirik authored Oct 25, 2024
1 parent bdb0dc8 commit 9ac357f
Show file tree
Hide file tree
Showing 3 changed files with 130 additions and 4 deletions.
116 changes: 116 additions & 0 deletions apps/docs-app/docs/features/api/websockets.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
# WebSocket

Analog also supports `WebSockets` and `Server-Sent Events` through Nitro.

## Enabling WebSockets

Currently, WebSocket support in [Nitro](https://nitro.unjs.io/guide/websocket) is experimental and it can be enabled in the `analog` plugin:

`vite.config.ts`

```typescript
import { defineConfig } from 'vite';
import analog from '@analogjs/platform';

export default defineConfig({
// ...
plugins: [
analog({
// ...
nitro: {
experimental: {
websocket: true,
},
},
}),
],
// ...
});
```

**Note:** In development, the Vite HMR WebSocket server runs on the same port as the dev server by default. To prevent conflicts, you need to change this port. The dev server port is usually defined in `project.json`/`angular.json`, which takes precedence over `vite.config.ts`. To allow the port settings in `vite.config.ts` to take effect, remove the port definition from `project.json`/`angular.json`. Additionally, you can specify an optional path to easily differentiate connections in the browser dev tools:

`vite.config.ts`

```typescript
import { defineConfig } from 'vite';
import analog from '@analogjs/platform';

export default defineConfig({
// ...
server: {
port: 3000, // dev-server port
hmr: {
port: 3002, // hmr ws port
path: 'vite-hmr', // optional
},
},
// ...
});
```

## Defining a WebSocket Handler

Similar to [API routes](/docs/features/api/overview), WebSocket Handlers are defined in the `src/server/routes` folder.

```typescript
// src/server/routes/ws/chat.ts
import { defineWebSocketHandler } from 'h3';

export default defineWebSocketHandler({
open(peer) {
peer.send({ user: 'server', message: `Welcome ${peer}!` });
peer.publish('chat', { user: 'server', message: `${peer} joined!` });
peer.subscribe('chat');
},
message(peer, message) {
if (message.text().includes('ping')) {
peer.send({ user: 'server', message: 'pong' });
} else {
const msg = {
user: peer.toString(),
message: message.toString(),
};
peer.send(msg); // echo
peer.publish('chat', msg);
}
},
close(peer) {
peer.publish('chat', { user: 'server', message: `${peer} left!` });
},
});
```

### WebSocket Routes

Analog's internal API middleware is not applied to WebSocket routes, therefore, WebSocket routes are exposed without the `/api` prefix.

For example, `src/server/routes/ws/chat.ts` is exposed as `ws://example.com/ws/chat` instead of `ws://example.com/api/ws/chat`

## Defining a Server-sent Event Handler

Server-sent event handlers can be created using `createEventStream` function in the event handler.

```typescript
// src/server/routes/sse.ts
import { defineEventHandler, createEventStream } from 'h3';

export default defineEventHandler(async (event) => {
const eventStream = createEventStream(event);

const interval = setInterval(async () => {
await eventStream.push(`Message @ ${new Date().toLocaleTimeString()}`);
}, 1000);

eventStream.onClosed(async () => {
clearInterval(interval);
await eventStream.close();
});

return eventStream.send();
});
```

## More Info

WebSockets are powered by [Nitro](https://nitro.unjs.io/guide/websocket), [h3](https://h3.unjs.io/guide/websocket) and [crossws](https://crossws.unjs.io/guide). See the Nitro, h3 and crossws docs for more details.
5 changes: 5 additions & 0 deletions apps/docs-app/sidebars.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,11 @@ const sidebars = {
id: 'features/api/overview',
label: 'Overview',
},
{
type: 'doc',
id: 'features/api/websockets',
label: 'Websockets',
},
{
type: 'doc',
id: 'features/api/og-image-generation',
Expand Down
13 changes: 9 additions & 4 deletions packages/vite-plugin-nitro/src/lib/vite-plugin-nitro.ts
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ export function nitro(options?: Options, nitroOptions?: NitroConfig): Plugin[] {
'#ANALOG_API_MIDDLEWARE': `
import { eventHandler, proxyRequest } from 'h3';
import { useRuntimeConfig } from '#imports';
export default eventHandler(async (event) => {
const apiPrefix = \`/\${useRuntimeConfig().apiPrefix}\`;
Expand Down Expand Up @@ -268,19 +268,19 @@ export function nitro(options?: Options, nitroOptions?: NitroConfig): Plugin[] {
* as it won't resolve the renderer.ts file correctly in node.
*/
import { eventHandler } from 'h3';
// @ts-ignore
import renderer from '${ssrEntry}';
// @ts-ignore
const template = \`${indexContents}\`;
export default eventHandler(async (event) => {
const html = await renderer(event.node.req.url, template, {
req: event.node.req,
res: event.node.res,
});
return html;
});
});
`
);

Expand Down Expand Up @@ -336,6 +336,11 @@ export function nitro(options?: Options, nitroOptions?: NitroConfig): Plugin[] {
process.env['ANALOG_PORT'] = `${viteServer.config.server.port}`;
});

// handle upgrades if websockets are enabled
if (nitroOptions?.experimental?.websocket) {
viteServer.httpServer?.on('upgrade', server.upgrade);
}

console.log(
`\n\nThe server endpoints are accessible under the "${apiPrefix}" path.`
);
Expand Down

0 comments on commit 9ac357f

Please sign in to comment.