-
-
Notifications
You must be signed in to change notification settings - Fork 2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Support Server-Sent Events (SSE) with adapter-node #887
Comments
@babeard After going through https://vitejs.dev/guide/api-plugin.html#configureserver, I believe we can try the below:
I haven't tested it but I think it should work. There's a drawback to this approach though, we'd have to match the SSE routes ourselves. UpdateI had tested it, the approach above works though I'm still trying to figure out how we can pass in the middleware to
const sveltePreprocess = require('svelte-preprocess');
const node = require('@sveltejs/adapter-node');
const pkg = require('./package.json');
const bodyParser = require('body-parser');
const sseMiddleware = require('./src/middlewares/sse.cjs');
/** @type {import('@sveltejs/kit').Config} */
module.exports = {
// Consult https://github.com/sveltejs/svelte-preprocess
// for more information about preprocessors
preprocess: sveltePreprocess(),
kit: {
adapter: node({
out: 'build',
// TODO: We probably can update `adapter-node` to provide this callback which
// is to ensure `node build/index.js` works. As this isn't implemented yet, you can
// simply run `svelte-kit build` and manually add the middleware to polka instance's
// use method at the end of `build/index.js` file.
configureServer(polkaInstance) {
polkaInstance.use(bodyParser.json());
polkaInstance.use(sseMiddleware);
}
}),
...
vite: {
...
// This is to ensure `svelte-kit dev` works.
plugins: [
(() => ({
name: 'configure-server',
configureServer(server) {
server.middlewares.use(bodyParser.json());
server.middlewares.use(sseMiddleware);
}
}))()
],
...
}
}
};
let clients = [];
const messages = [];
module.exports = (req, res, next) => {
if (req.url === '/messages') {
switch (req.method) {
case 'GET':
const headers = {
'Content-Type': 'text/event-stream',
Connection: 'keep-alive',
'Cache-Control': 'no-cache'
};
res.writeHead(200, headers);
const data = `messages: ${JSON.stringify(messages)}\n\n`;
res.write(data);
const clientId = Date.now();
const newClient = {
id: clientId,
res
};
clients.push(newClient);
req.on('close', () => {
console.log(`${clientId} Connection closed`);
clients = clients.filter((client) => client.id !== clientId);
});
break;
case 'POST':
const newMessage = req.body;
messages.push(newMessage);
res.end(`${JSON.stringify(newMessage)}\n`);
clients.forEach((client) =>
client.res.write(`newMessage: ${JSON.stringify(newMessage)}\n`)
);
break;
}
return;
}
next();
}; |
@cayter Do you have any recommendation on how to patch |
I checked the src, it isn't possible at all which means we will have to change how |
yes, thank you! It's not pretty, but it works. |
I ended up adding this to
|
I had come up with a better solution and it's gonna be a monolith like Rails, the toolkit source code is here. Note that this isn't production ready but I plan to continue adding more capabilities to make it easier for ppl to build and deploy app easily just like how Rails/Laravel is. Project Demo: https://codesandbox.io/s/appistkit-demo-4w58d .
├── README.md
├── app (this is a shell script where we will use to run all the app commands, you can run `./app --help` to see what commands are available)
├── configs (12factor app, running `KIT_ENV=staging ./app server` would lead the app to load `configs/.env.staging`, I plan to add the encrypt/decrypt capability with KIT_MASTER_KEY and separate the server/client as client is restricted to use `VITE_` prefix)
├── package-lock.json
├── package.json
├── src
│ ├── client (this is the SvelteKit folder)
│ │ ├── app.html
│ │ ├── global.d.ts
│ │ ├── pages
│ │ │ ├── __error.svelte
│ │ │ ├── __layout.svelte
│ │ │ └── index.svelte
│ │ └── static
│ │ └── favicon.png
│ ├── cmd (this is the command folder which allows you to easily add new commands to `./app`)
│ │ └── now.ts
│ ├── db (this is the DB migrate/seed folder like what Rails have)
│ │ ├── migrate
│ │ │ └── primary
│ │ │ └── 20210422155916_create_users.ts
│ │ └── seed
│ │ └── primary
│ │ └── index.ts
│ ├── server (this is the server folder that is also file-based routing like SvelteKit but for server-side, you can write your SSE handler in any path you want, I'm planning to add `export const ws` to support websocket and `export const graphql` to support GraphQL and probably `export const grpc` to support GRPC too)
│ │ └── api.ts
│ └── worker (this is the worker folder where we would store the BullMQ worker logic, WIP)
├── svelte.config.js
└── tsconfig.json The Polka instance used in:
are exactly the same instance. Originally, I thought of including the server logic as part of the client (SvelteKit) folder but had bumped into lots of bundling issues that Vite has yet to resolve. So to keep things separated in cleaner way, I decided to just have a Let me know what you think. Thanks. |
Thanks to both @cayter and @mromanuk for the information provided above. For development mode, I already followed the instructions from @cayter, but for builds, I had to avoid external scripts (postbuild) and stick to javascript only.
// you many not use 'sse_middleware' inside src/hooks.js, but
// - it has to land in the final bundle during builds.
// - we need to ensure automated server's reload during development
// - the re-export is necessary to avoid dead code removal during builds
export { default as sse_middleware } from '$lib/rtc/sse_middleware'
import adapter_node from '@sveltejs/adapter-node'
import fs from 'fs'
import path, { resolve } from 'path'
import preprocess from 'svelte-preprocess';
import sse_middleware from './src/lib/rtc/sse_middleware.js'
/** @type {import('@sveltejs/kit').Config} */
const config = {
preprocess: preprocess(),
kit: {
// hydrate the <div id="svelte"> element in src/app.html
target: '#svelte',
adapter: {
async adapt (opts) {
const adapter = adapter_node()
await adapter.adapt(opts)
// build/index.js: attach the middleware to polka instance (text replacement)
let content = fs.readFileSync(path.resolve('./build/index.js'), 'utf-8')
content = content.replace(`polka().use(`, `polka().use(sse_middleware).use(`)
fs.writeFileSync('./build/index.js', content, 'utf-8')
}
},
vite: {
// temporary fix for using broadcast-channel
define: { 'process.browser': true },
plugins: [
{ name: 'attach_middlewares', configureServer (server) {
server.middlewares.use(sse_middleware)
}}
],
build: {
rollupOptions: {
plugins: [
{
name: 'postbuild-fixes',
writeBundle () {
// at first attempt, I tried to attach the middleware to polka instance here,
// unfortunately, build/index.js isn't available at this stage
}
}
]
}
}
},
}
};
export default config; |
@jsprog The |
The modules imported by Example of a file imported by import db from '$lib/database' // fails
import db from './lib/database' // fails
import db from './lib/database.js' // works Instead, I had to resort to global
// global.rtc_sse will be used by configuration for vite (svelte.config.js)
// this also ensure that no dead code is removed for builds
global.rtc_sse = rtc_sse
export default async function rtc_sse (req, res, next) {
// ...
}
import '$lib/rtc/server/middlewares/rtc_sse'
vite: {
plugins: [
{ name: 'use-middlewares', configureServer (server) {
server.middlewares.use((req, res, next) => {
global.rtc_sse ? global.rtc_sse(req, res, next) : next()
})
}}
]
} |
as this #3384 changes, are now possible to implement SSE through endpoint? |
Is that now possible? |
It's not possible today, but it's definitely something we want to get fixed for 1.0. This is basically just a special case of #3419. It should be possible to do something like this, I think... const controllers = new Set();
export function get() {
let controller;
return {
body: new ReadableStream({
start: (_) => {
controller = _;
controllers.add(controller);
},
cancel: () => {
controllers.delete(controller);
}
})
};
}
export async function post({ request }) {
const message = await request.text();
// importantly, this use case wouldn't work in serverless environments —
// for these sorts of use cases we need things like this:
// https://deno.com/deploy/docs/runtime-broadcast-channel
//
// ideally Kit would have some kind of abstraction that leveraged
// BroadcastChannel or WebSockets or whatever under the hood
for (const controller of controllers) {
controller.enqueue(message);
}
return { status: 204 };
} ...but I'll close this in favour of #3419. |
Describe the bug
While migrating a sapper app to svelte-kit / adapter-node, I came across a feature that seems to be missing. Namely, the ability to create custom middleware that can handle Server-Sent Events or other long-lived connections that send data to the response stream without closing the connection. Since the handle hook requires returning
Response | Promise<Response>
, thus closing the connection, it does not support this. Nor have I found any other way tores.write();
using endpoints.Information about your SvelteKit Installation:
Diagnostics
Severity
Adds a bit of friction for migrating from Sapper, reduces feature parity, and will require me to either
a) regress to polling the server from the client, or
b) write a custom adapter for every project that needs SSEs. (which I'm not sure that this even helps for the development environment anyways)
Additional context
Issue #334 seems relevant.
Also originally asked within discord chat but no one seems to know the answer, which is why I am submitting this issue.
The text was updated successfully, but these errors were encountered: