From d0b7f09c0b9ce77cff5bbb485e467f63b2d4d260 Mon Sep 17 00:00:00 2001 From: Luke Hagar Date: Thu, 7 Nov 2024 02:20:53 +0000 Subject: [PATCH 01/11] example crossws implementation with `hooks.server.js` websocketHooks export --- packages/kit/package.json | 1 + packages/kit/src/core/postbuild/analyse.js | 1 + packages/kit/src/core/sync/write_server.js | 4 +- packages/kit/src/exports/public.d.ts | 2 + packages/kit/src/exports/vite/dev/index.js | 70 +++++++++++++------ packages/kit/src/exports/vite/index.js | 5 ++ packages/kit/src/runtime/app/server/index.js | 28 +++++++- packages/kit/src/runtime/server/index.js | 27 ++++--- packages/kit/src/types/ambient-private.d.ts | 2 + packages/kit/src/types/internal.d.ts | 5 ++ .../test/apps/options-2/src/hooks.server.js | 40 +++++++++++ .../apps/options-2/src/routes/+page.svelte | 2 + .../apps/options-2/src/routes/ws/+page.svelte | 34 +++++++++ pnpm-lock.yaml | 15 ++++ 14 files changed, 202 insertions(+), 34 deletions(-) create mode 100644 packages/kit/test/apps/options-2/src/hooks.server.js create mode 100644 packages/kit/test/apps/options-2/src/routes/ws/+page.svelte diff --git a/packages/kit/package.json b/packages/kit/package.json index fb7a3b8d6bfd..da469f1f0cc8 100644 --- a/packages/kit/package.json +++ b/packages/kit/package.json @@ -20,6 +20,7 @@ "dependencies": { "@types/cookie": "^0.6.0", "cookie": "^0.6.0", + "crossws": "^0.3.1", "devalue": "^5.1.0", "esm-env": "^1.0.0", "import-meta-resolve": "^4.1.0", diff --git a/packages/kit/src/core/postbuild/analyse.js b/packages/kit/src/core/postbuild/analyse.js index 1cc928680e9a..db2f2aca99dd 100644 --- a/packages/kit/src/core/postbuild/analyse.js +++ b/packages/kit/src/core/postbuild/analyse.js @@ -56,6 +56,7 @@ async function analyse({ manifest_path, manifest_data, server_manifest, tracked_ internal.set_safe_public_env(public_env); internal.set_manifest(manifest); internal.set_read_implementation((file) => createReadableStream(`${server_root}/server/${file}`)); + internal.set_upgrade_implementation(() => {}); /** @type {import('types').ServerMetadata} */ const metadata = { diff --git a/packages/kit/src/core/sync/write_server.js b/packages/kit/src/core/sync/write_server.js index eb50bfd4735b..28ce4f098485 100644 --- a/packages/kit/src/core/sync/write_server.js +++ b/packages/kit/src/core/sync/write_server.js @@ -31,7 +31,7 @@ const server_template = ({ import root from '../root.${isSvelte5Plus() ? 'js' : 'svelte'}'; import { set_building, set_prerendering } from '__sveltekit/environment'; import { set_assets } from '__sveltekit/paths'; -import { set_manifest, set_read_implementation } from '__sveltekit/server'; +import { set_manifest, set_read_implementation, set_upgrade_implementation } from '__sveltekit/server'; import { set_private_env, set_public_env, set_safe_public_env } from '${runtime_directory}/shared-server.js'; export const options = { @@ -70,7 +70,7 @@ export async function get_hooks() { }; } -export { set_assets, set_building, set_manifest, set_prerendering, set_private_env, set_public_env, set_read_implementation, set_safe_public_env }; +export { set_assets, set_building, set_manifest, set_prerendering, set_private_env, set_public_env, set_read_implementation, set_upgrade_implementation, set_safe_public_env }; `; // TODO need to re-run this whenever src/app.html or src/error.html are diff --git a/packages/kit/src/exports/public.d.ts b/packages/kit/src/exports/public.d.ts index 7db17269967f..e58084829bfc 100644 --- a/packages/kit/src/exports/public.d.ts +++ b/packages/kit/src/exports/public.d.ts @@ -1182,6 +1182,8 @@ export interface ServerInitOptions { env: Record; /** A function that turns an asset filename into a `ReadableStream`. Required for the `read` export from `$app/server` to work */ read?: (file: string) => ReadableStream; + /** A function that upgrades the websocket connection. Required for the `upgrade` export from `$app/server` to work */ + upgrade?: () => void; } export interface SSRManifest { diff --git a/packages/kit/src/exports/vite/dev/index.js b/packages/kit/src/exports/vite/dev/index.js index 0dbc912940a2..31f80889da14 100644 --- a/packages/kit/src/exports/vite/dev/index.js +++ b/packages/kit/src/exports/vite/dev/index.js @@ -2,6 +2,7 @@ import fs from 'node:fs'; import path from 'node:path'; import process from 'node:process'; import { URL } from 'node:url'; +import crossws from 'crossws/adapters/node'; import { AsyncLocalStorage } from 'node:async_hooks'; import colors from 'kleur'; import sirv from 'sirv'; @@ -421,7 +422,7 @@ export async function dev(vite, vite_config, svelte_config) { const env = loadEnv(vite_config.mode, svelte_config.kit.env.dir, ''); const emulator = await svelte_config.kit.adapter?.emulate?.(); - return () => { + return async () => { const serve_static_middleware = vite.middlewares.stack.find( (middleware) => /** @type {function} */ (middleware.handle).name === 'viteServeStaticMiddleware' @@ -431,7 +432,48 @@ export async function dev(vite, vite_config, svelte_config) { // serving routes with those names. See https://github.com/vitejs/vite/issues/7363 remove_static_middlewares(vite.middlewares); + // we have to import `Server` before calling `set_assets` + const { Server } = /** @type {import('types').ServerModule} */ ( + await vite.ssrLoadModule(`${runtime_base}/server/index.js`, { fixStacktrace: true }) + ); + + const { set_fix_stack_trace } = await vite.ssrLoadModule( + `${runtime_base}/shared-server.js` + ); + set_fix_stack_trace(fix_stack_trace); + + const { set_assets } = await vite.ssrLoadModule('__sveltekit/paths'); + set_assets(assets); + + const server = new Server(manifest); + + await server.init({ + env, + read: (file) => createReadableStream(from_fs(file)), + upgrade: () => {} + }); + + /** @type {import('crossws/adapters/node').NodeAdapter | undefined} */ + let ws + + if (server.options.hooks?.websocketHooks) { + ws = crossws({ + hooks: server.options.hooks.websocketHooks + }); + + if(!ws) { + throw new Error('websocket hooks failed to initialize'); + } + + vite.httpServer?.on('upgrade', (req, socket, head) => { + if(req.headers['sec-websocket-protocol'] !== 'vite-hmr'){ + ws.handleUpgrade(req, socket, head); + } + }); + } + vite.middlewares.use(async (req, res) => { + console.log('middleware'); // Vite's base middleware strips out the base path. Restore it const original_url = req.url; req.url = req.originalUrl; @@ -474,26 +516,6 @@ export async function dev(vite, vite_config, svelte_config) { return; } - // we have to import `Server` before calling `set_assets` - const { Server } = /** @type {import('types').ServerModule} */ ( - await vite.ssrLoadModule(`${runtime_base}/server/index.js`, { fixStacktrace: true }) - ); - - const { set_fix_stack_trace } = await vite.ssrLoadModule( - `${runtime_base}/shared-server.js` - ); - set_fix_stack_trace(fix_stack_trace); - - const { set_assets } = await vite.ssrLoadModule('__sveltekit/paths'); - set_assets(assets); - - const server = new Server(manifest); - - await server.init({ - env, - read: (file) => createReadableStream(from_fs(file)) - }); - const request = await getRequest({ base, request: req @@ -534,6 +556,12 @@ export async function dev(vite, vite_config, svelte_config) { return fs.readFileSync(path.join(svelte_config.kit.files.assets, file)); }, + upgrade: () => { + if(!ws) { + throw new Error('websocket hooks failed to initialize'); + } + return { ws, env: { req, res } }; + }, before_handle: (event, config, prerender) => { async_local_storage.enterWith({ event, config, prerender }); }, diff --git a/packages/kit/src/exports/vite/index.js b/packages/kit/src/exports/vite/index.js index 40fa4c6edb56..cd9e187b9886 100644 --- a/packages/kit/src/exports/vite/index.js +++ b/packages/kit/src/exports/vite/index.js @@ -523,6 +523,7 @@ async function kit({ svelte_config }) { case sveltekit_server: { return dedent` export let read_implementation = null; + export let upgrade_implementation = null; export let manifest = null; @@ -530,6 +531,10 @@ async function kit({ svelte_config }) { read_implementation = fn; } + export function set_upgrade_implementation(fn) { + upgrade_implementation = fn; + } + export function set_manifest(_) { manifest = _; } diff --git a/packages/kit/src/runtime/app/server/index.js b/packages/kit/src/runtime/app/server/index.js index 33c9b0a0d1ba..7d067466b6f1 100644 --- a/packages/kit/src/runtime/app/server/index.js +++ b/packages/kit/src/runtime/app/server/index.js @@ -1,4 +1,4 @@ -import { read_implementation, manifest } from '__sveltekit/server'; +import { read_implementation, upgrade_implementation, manifest } from '__sveltekit/server'; import { base } from '__sveltekit/paths'; import { DEV } from 'esm-env'; import { b64_decode } from '../../utils.js'; @@ -71,3 +71,29 @@ export function read(asset) { throw new Error(`Asset does not exist: ${file}`); } + + +/** + * Read the contents of an imported asset from the filesystem + * @example + * ```js + * import { upgrade } from '$app/server'; + * import somefile from './somefile.txt'; + * + * const asset = read(somefile); + * const text = await asset.text(); + * ``` + * @returns {void} + * @since 2.4.0 + */ +export function upgrade() { + __SVELTEKIT_TRACK__('$app/server:upgrade'); + + if (!upgrade_implementation) { + throw new Error( + 'No `upgrade` implementation was provided. Please ensure that your adapter is up to date and supports this feature' + ); + } + + upgrade_implementation(); +} diff --git a/packages/kit/src/runtime/server/index.js b/packages/kit/src/runtime/server/index.js index 36cbd04be16f..9cf3d2ea96b7 100644 --- a/packages/kit/src/runtime/server/index.js +++ b/packages/kit/src/runtime/server/index.js @@ -4,7 +4,7 @@ import { options, get_hooks } from '__SERVER__/internal.js'; import { DEV } from 'esm-env'; import { filter_private_env, filter_public_env } from '../../utils/env.js'; import { prerendering } from '__sveltekit/environment'; -import { set_read_implementation, set_manifest } from '__sveltekit/server'; +import { set_read_implementation, set_upgrade_implementation, set_manifest } from '__sveltekit/server'; /** @type {ProxyHandler<{ type: 'public' | 'private' }>} */ const prerender_env_handler = { @@ -17,7 +17,7 @@ const prerender_env_handler = { export class Server { /** @type {import('types').SSROptions} */ - #options; + options; /** @type {import('@sveltejs/kit').SSRManifest} */ #manifest; @@ -25,7 +25,7 @@ export class Server { /** @param {import('@sveltejs/kit').SSRManifest} manifest */ constructor(manifest) { /** @type {import('types').SSROptions} */ - this.#options = options; + this.options = options; this.#manifest = manifest; set_manifest(manifest); @@ -35,17 +35,18 @@ export class Server { * @param {{ * env: Record; * read?: (file: string) => ReadableStream; + * upgrade?: () => void; * }} opts */ - async init({ env, read }) { + async init({ env, read, upgrade }) { // Take care: Some adapters may have to call `Server.init` per-request to set env vars, // so anything that shouldn't be rerun should be wrapped in an `if` block to make sure it hasn't // been done already. // set env, in case it's used in initialisation const prefixes = { - public_prefix: this.#options.env_public_prefix, - private_prefix: this.#options.env_private_prefix + public_prefix: this.options.env_public_prefix, + private_prefix: this.options.env_private_prefix }; const private_env = filter_private_env(env, prefixes); @@ -63,11 +64,16 @@ export class Server { set_read_implementation(read); } - if (!this.#options.hooks) { + if (upgrade) { + set_upgrade_implementation(upgrade); + } + + if (!this.options.hooks) { try { const module = await get_hooks(); - this.#options.hooks = { + this.options.hooks = { + websocketHooks: module.websocketHooks, handle: module.handle || (({ event, resolve }) => resolve(event)), handleError: module.handleError || (({ error }) => console.error(error)), handleFetch: module.handleFetch || (({ request, fetch }) => fetch(request)), @@ -75,7 +81,8 @@ export class Server { }; } catch (error) { if (DEV) { - this.#options.hooks = { + this.options.hooks = { + websocketHooks: undefined, handle: () => { throw error; }, @@ -95,7 +102,7 @@ export class Server { * @param {import('types').RequestOptions} options */ async respond(request, options) { - return respond(request, this.#options, this.#manifest, { + return respond(request, this.options, this.#manifest, { ...options, error: false, depth: 0 diff --git a/packages/kit/src/types/ambient-private.d.ts b/packages/kit/src/types/ambient-private.d.ts index 4f1491475355..80126a79c1ac 100644 --- a/packages/kit/src/types/ambient-private.d.ts +++ b/packages/kit/src/types/ambient-private.d.ts @@ -23,6 +23,8 @@ declare module '__sveltekit/server' { export let manifest: SSRManifest; export function read_implementation(path: string): ReadableStream; + export function upgrade_implementation(): void; export function set_manifest(manifest: SSRManifest): void; export function set_read_implementation(fn: (path: string) => ReadableStream): void; + export function set_upgrade_implementation(fn: () => void): void; } diff --git a/packages/kit/src/types/internal.d.ts b/packages/kit/src/types/internal.d.ts index c9dbb51ce007..346c0188b4db 100644 --- a/packages/kit/src/types/internal.d.ts +++ b/packages/kit/src/types/internal.d.ts @@ -26,6 +26,7 @@ import { RequestOptions, TrailingSlash } from './private.js'; +import {Hooks} from 'crossws'; export interface ServerModule { Server: typeof InternalServer; @@ -39,6 +40,7 @@ export interface ServerInternalModule { set_private_env(environment: Record): void; set_public_env(environment: Record): void; set_read_implementation(implementation: (path: string) => ReadableStream): void; + set_upgrade_implementation(implementation: () => void): void; set_safe_public_env(environment: Record): void; set_version(version: string): void; set_fix_stack_trace(fix_stack_trace: (error: unknown) => string): void; @@ -105,6 +107,7 @@ export interface Deferred { export type GetParams = (match: RegExpExecArray) => Record; export interface ServerHooks { + websocketHooks?: Hooks; handleFetch: HandleFetch; handle: Handle; handleError: HandleServerError; @@ -123,11 +126,13 @@ export interface Env { export class InternalServer extends Server { init(options: ServerInitOptions): Promise; + options: SSROptions; respond( request: Request, options: RequestOptions & { prerendering?: PrerenderOptions; read: (file: string) => Buffer; + upgrade: () => void; /** A hook called before `handle` during dev, so that `AsyncLocalStorage` can be populated */ before_handle?: (event: RequestEvent, config: any, prerender: PrerenderOption) => void; emulator?: Emulator; diff --git a/packages/kit/test/apps/options-2/src/hooks.server.js b/packages/kit/test/apps/options-2/src/hooks.server.js new file mode 100644 index 000000000000..b69e1783b3f8 --- /dev/null +++ b/packages/kit/test/apps/options-2/src/hooks.server.js @@ -0,0 +1,40 @@ +let sockets = []; + +export const websocketHooks = { + upgrade(req) { + console.log(`[ws] upgrading ${req.url}...`) + return { + headers: {} + } + }, + + open(peer) { + console.log(`[ws] open: ${peer}`); + }, + + message(peer, message) { + console.log('[ws] message', message.text()); + if (message.text().includes('ping')) { + peer.send('pong'); + } + if(message.text().includes('add')) { + sockets.push(peer); + peer.send('added'); + } + if(message.text().includes('broadcast')) { + sockets.forEach(socket => { + socket.send(message.text()); + }); + + } + }, + + close(peer, event) { + console.log('[ws] close', peer, event); + sockets = sockets.filter(socket => socket !== peer); + }, + + error(peer, error) { + console.log('[ws] error', peer, error); + }, +} diff --git a/packages/kit/test/apps/options-2/src/routes/+page.svelte b/packages/kit/test/apps/options-2/src/routes/+page.svelte index badacc8173d8..cef9ba846661 100644 --- a/packages/kit/test/apps/options-2/src/routes/+page.svelte +++ b/packages/kit/test/apps/options-2/src/routes/+page.svelte @@ -8,3 +8,5 @@

assets: {assets}

Go to /hello +
+Go to /ws diff --git a/packages/kit/test/apps/options-2/src/routes/ws/+page.svelte b/packages/kit/test/apps/options-2/src/routes/ws/+page.svelte new file mode 100644 index 000000000000..cc13f57acdac --- /dev/null +++ b/packages/kit/test/apps/options-2/src/routes/ws/+page.svelte @@ -0,0 +1,34 @@ + + +

Messages:

+ + + + + +
+ {#each messages as message} +

{message}

+ {/each} +
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0a360411ab50..927b35ac9355 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -342,6 +342,9 @@ importers: cookie: specifier: ^0.6.0 version: 0.6.0 + crossws: + specifier: ^0.3.1 + version: 0.3.1 devalue: specifier: ^5.1.0 version: 5.1.0 @@ -2295,6 +2298,9 @@ packages: resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} engines: {node: '>= 8'} + crossws@0.3.1: + resolution: {integrity: sha512-HsZgeVYaG+b5zA+9PbIPGq4+J/CJynJuearykPsXx4V/eMhyQ5EDVg3Ak2FBZtVXCiOLu/U7IiwDHTr9MA+IKw==} + css-tree@2.3.1: resolution: {integrity: sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==} engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0} @@ -3675,6 +3681,9 @@ packages: ufo@1.5.3: resolution: {integrity: sha512-Y7HYmWaFwPUmkoQCUIAYpKqkOf+SbVj/2fJJZ4RJMCfZp0rTGwRbzQD+HghfnhKOjL9E01okqz+ncJskGYfBNw==} + uncrypto@0.1.3: + resolution: {integrity: sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q==} + undici-types@5.26.5: resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} @@ -4964,6 +4973,10 @@ snapshots: shebang-command: 2.0.0 which: 2.0.2 + crossws@0.3.1: + dependencies: + uncrypto: 0.1.3 + css-tree@2.3.1: dependencies: mdn-data: 2.0.30 @@ -6312,6 +6325,8 @@ snapshots: ufo@1.5.3: {} + uncrypto@0.1.3: {} + undici-types@5.26.5: {} undici@5.28.4: From b69b2e0f6cb49acaebae1b9f64d86a3b05e0a05e Mon Sep 17 00:00:00 2001 From: Luke Hagar Date: Fri, 8 Nov 2024 04:33:12 +0000 Subject: [PATCH 02/11] Migrated from hooks to server.js export named socket, validated functionality for different handlers at different URLs, added example use cases to options-2 test app, added upgrade function for supporting additional adapters, and much more. --- packages/kit/src/exports/public.d.ts | 6 ++ packages/kit/src/exports/vite/dev/index.js | 33 ++----- packages/kit/src/runtime/app/server/index.js | 4 +- packages/kit/src/runtime/server/index.js | 18 +++- packages/kit/src/runtime/server/resolve.js | 98 +++++++++++++++++++ packages/kit/src/runtime/server/respond.js | 5 + packages/kit/src/types/internal.d.ts | 9 +- packages/kit/src/utils/exports.js | 3 +- .../src/routes/diff-socket/+server.js | 49 ++++++++++ .../apps/options-2/src/routes/ws/+page.svelte | 42 ++++++-- .../{hooks.server.js => routes/ws/+server.js} | 4 +- 11 files changed, 229 insertions(+), 42 deletions(-) create mode 100644 packages/kit/src/runtime/server/resolve.js create mode 100644 packages/kit/test/apps/options-2/src/routes/diff-socket/+server.js rename packages/kit/test/apps/options-2/src/{hooks.server.js => routes/ws/+server.js} (92%) diff --git a/packages/kit/src/exports/public.d.ts b/packages/kit/src/exports/public.d.ts index e58084829bfc..f562ac561e43 100644 --- a/packages/kit/src/exports/public.d.ts +++ b/packages/kit/src/exports/public.d.ts @@ -19,6 +19,7 @@ import { } from '../types/private.js'; import { BuildData, SSRNodeLoader, SSRRoute, ValidatedConfig } from 'types'; import type { PluginOptions } from '@sveltejs/vite-plugin-svelte'; +import { Hooks } from 'crossws'; export { PrerenderOption } from '../types/private.js'; @@ -688,6 +689,11 @@ export type Handle = (input: { resolve(event: RequestEvent, opts?: ResolveOptions): MaybePromise; }) => MaybePromise; +/** + * The WebsocketHooks are used when the SvelteKit server receives a websocket request and specifies how to handle it. + */ +export type WebsocketHooks = Hooks + /** * The server-side [`handleError`](https://svelte.dev/docs/kit/hooks#Shared-hooks-handleError) hook runs when an unexpected error is thrown while responding to a request. * diff --git a/packages/kit/src/exports/vite/dev/index.js b/packages/kit/src/exports/vite/dev/index.js index 31f80889da14..140406630eec 100644 --- a/packages/kit/src/exports/vite/dev/index.js +++ b/packages/kit/src/exports/vite/dev/index.js @@ -437,9 +437,7 @@ export async function dev(vite, vite_config, svelte_config) { await vite.ssrLoadModule(`${runtime_base}/server/index.js`, { fixStacktrace: true }) ); - const { set_fix_stack_trace } = await vite.ssrLoadModule( - `${runtime_base}/shared-server.js` - ); + const { set_fix_stack_trace } = await vite.ssrLoadModule(`${runtime_base}/shared-server.js`); set_fix_stack_trace(fix_stack_trace); const { set_assets } = await vite.ssrLoadModule('__sveltekit/paths'); @@ -447,33 +445,26 @@ export async function dev(vite, vite_config, svelte_config) { const server = new Server(manifest); + // we have to initialize the server before we can call the resolve function to populate the webhook resolver in the websocket handler await server.init({ env, read: (file) => createReadableStream(from_fs(file)), - upgrade: () => {} + upgrade: () => {return { ws, env: 'sadly no data yet'}} }); - /** @type {import('crossws/adapters/node').NodeAdapter | undefined} */ - let ws - - if (server.options.hooks?.websocketHooks) { - ws = crossws({ - hooks: server.options.hooks.websocketHooks + /** @type {import('crossws/adapters/node').NodeAdapter} */ + const ws = crossws({ + resolve: server.resolve(), }); - if(!ws) { - throw new Error('websocket hooks failed to initialize'); - } - vite.httpServer?.on('upgrade', (req, socket, head) => { - if(req.headers['sec-websocket-protocol'] !== 'vite-hmr'){ + if (req.headers['sec-websocket-protocol'] !== 'vite-hmr') { ws.handleUpgrade(req, socket, head); } }); - } + vite.middlewares.use(async (req, res) => { - console.log('middleware'); // Vite's base middleware strips out the base path. Restore it const original_url = req.url; req.url = req.originalUrl; @@ -556,12 +547,8 @@ export async function dev(vite, vite_config, svelte_config) { return fs.readFileSync(path.join(svelte_config.kit.files.assets, file)); }, - upgrade: () => { - if(!ws) { - throw new Error('websocket hooks failed to initialize'); - } - return { ws, env: { req, res } }; - }, + // This is intended to pass through the specific values needed to properly upgrade the websocket connection in other adapters + upgrade: () => {return { ws, env: { req, res } }}, before_handle: (event, config, prerender) => { async_local_storage.enterWith({ event, config, prerender }); }, diff --git a/packages/kit/src/runtime/app/server/index.js b/packages/kit/src/runtime/app/server/index.js index 7d067466b6f1..d8662882d19d 100644 --- a/packages/kit/src/runtime/app/server/index.js +++ b/packages/kit/src/runtime/app/server/index.js @@ -89,11 +89,13 @@ export function read(asset) { export function upgrade() { __SVELTEKIT_TRACK__('$app/server:upgrade'); + console.log('upgrade server function called'); + if (!upgrade_implementation) { throw new Error( 'No `upgrade` implementation was provided. Please ensure that your adapter is up to date and supports this feature' ); } - upgrade_implementation(); + return upgrade_implementation(); } diff --git a/packages/kit/src/runtime/server/index.js b/packages/kit/src/runtime/server/index.js index 9cf3d2ea96b7..5b99b85d9405 100644 --- a/packages/kit/src/runtime/server/index.js +++ b/packages/kit/src/runtime/server/index.js @@ -1,10 +1,15 @@ import { respond } from './respond.js'; +import { resolve } from './resolve.js'; import { set_private_env, set_public_env, set_safe_public_env } from '../shared-server.js'; import { options, get_hooks } from '__SERVER__/internal.js'; import { DEV } from 'esm-env'; import { filter_private_env, filter_public_env } from '../../utils/env.js'; import { prerendering } from '__sveltekit/environment'; -import { set_read_implementation, set_upgrade_implementation, set_manifest } from '__sveltekit/server'; +import { + set_read_implementation, + set_upgrade_implementation, + set_manifest +} from '__sveltekit/server'; /** @type {ProxyHandler<{ type: 'public' | 'private' }>} */ const prerender_env_handler = { @@ -35,7 +40,7 @@ export class Server { * @param {{ * env: Record; * read?: (file: string) => ReadableStream; - * upgrade?: () => void; + * upgrade?: () => { ws: import('crossws').AdapterInstance; env: any }; * }} opts */ async init({ env, read, upgrade }) { @@ -65,6 +70,7 @@ export class Server { } if (upgrade) { + console.log('upgrade server function set'); set_upgrade_implementation(upgrade); } @@ -108,4 +114,12 @@ export class Server { depth: 0 }); } + + /** + * Returns a function that resolves the websocket hooks for a given request + * @returns {(info: Request) => import('types').MaybePromise>} + */ + resolve() { + return resolve(this.options, this.#manifest); + } } diff --git a/packages/kit/src/runtime/server/resolve.js b/packages/kit/src/runtime/server/resolve.js new file mode 100644 index 000000000000..4ce64602f62a --- /dev/null +++ b/packages/kit/src/runtime/server/resolve.js @@ -0,0 +1,98 @@ +import { DEV } from 'esm-env'; +import { + validate_server_exports +} from '../../utils/exports.js'; +import { exec } from '../../utils/routing.js'; +import { decode_pathname } from '../../utils/url.js'; +import { base } from '__sveltekit/paths'; + +/* global __SVELTEKIT_ADAPTER_NAME__ */ + + +/** + * @param {import('types').SSROptions} options + * @param {import('@sveltejs/kit').SSRManifest} manifest + * @returns {(info: Request | import('crossws').Peer) => import('types').MaybePromise>} + */ +export function resolve(options, manifest) { + return async (info) => { + + let req = info + + // These types all need to be straightened out + if(!req.url) { + req = info.request + } + + /** URL but stripped from the potential `/__data.json` suffix and its search param */ + const url = new URL(req.url); + + // reroute could alter the given URL, so we pass a copy + let rerouted_path; + try { + rerouted_path = options.hooks.reroute({ url }) ?? url.pathname; + } catch { + return {}; + } + + let decoded; + try { + decoded = decode_pathname(rerouted_path); + } catch (e) { + console.error(e); + return {}; + } + + if(base && decoded.startsWith(base)) { + decoded = decoded.slice(base.length) || '/'; + } + + /** @type {import('types').SSRRoute | null} */ + let route = null; + + // Should we find a good way to pass the decoded params to some of the websocket hooks? + // /** @type {Record} */ + // let params = {}; + + try { + // TODO this could theoretically break - should probably be inside a try-catch + const matchers = await manifest._.matchers(); + + for (const candidate of manifest._.routes) { + const match = candidate.pattern.exec(decoded); + + if (!match) continue; + + const matched = exec(match, candidate.params, matchers); + if (matched) { + route = candidate; + break; + } + } + }catch (e) { + console.error(e); + return {}; + } + + try { + // determine whether we need to redirect to add/remove a trailing slash + if (route && route.endpoint) { + // if `paths.base === '/a/b/c`, then the root route is `/a/b/c/`, + // regardless of the `trailingSlash` route option + + const node = await route.endpoint(); + + if (DEV) { + validate_server_exports(node, /** @type {string} */ (route.endpoint_id)); + } + + return node.socket ?? {}; + } + + return {}; + } catch (e) { + console.error(e); + return {}; + } +} +} diff --git a/packages/kit/src/runtime/server/respond.js b/packages/kit/src/runtime/server/respond.js index f81f52ef2557..824c2fdf9631 100644 --- a/packages/kit/src/runtime/server/respond.js +++ b/packages/kit/src/runtime/server/respond.js @@ -33,6 +33,7 @@ import { INVALIDATED_PARAM, TRAILING_SLASH_PARAM } from '../shared.js'; import { get_public_env } from './env_module.js'; import { load_page_nodes } from './page/load_page_nodes.js'; import { get_page_config } from '../../utils/route_config.js'; +import { set_upgrade_implementation } from '__sveltekit/server'; /* global __SVELTEKIT_ADAPTER_NAME__ */ @@ -60,6 +61,10 @@ export async function respond(request, options, manifest, state) { /** URL but stripped from the potential `/__data.json` suffix and its search param */ const url = new URL(request.url); + if(state.upgrade) { + set_upgrade_implementation(state.upgrade); + } + if (options.csrf_check_origin) { const forbidden = is_form_content_type(request) && diff --git a/packages/kit/src/types/internal.d.ts b/packages/kit/src/types/internal.d.ts index 346c0188b4db..a9a7bc70f175 100644 --- a/packages/kit/src/types/internal.d.ts +++ b/packages/kit/src/types/internal.d.ts @@ -26,7 +26,7 @@ import { RequestOptions, TrailingSlash } from './private.js'; -import {Hooks} from 'crossws'; +import {AdapterInstance, Hooks, Peer} from 'crossws'; export interface ServerModule { Server: typeof InternalServer; @@ -107,7 +107,7 @@ export interface Deferred { export type GetParams = (match: RegExpExecArray) => Record; export interface ServerHooks { - websocketHooks?: Hooks; + websocketHooks?: Partial; handleFetch: HandleFetch; handle: Handle; handleError: HandleServerError; @@ -132,12 +132,13 @@ export class InternalServer extends Server { options: RequestOptions & { prerendering?: PrerenderOptions; read: (file: string) => Buffer; - upgrade: () => void; + upgrade: () => { ws: AdapterInstance; env: any }; /** A hook called before `handle` during dev, so that `AsyncLocalStorage` can be populated */ before_handle?: (event: RequestEvent, config: any, prerender: PrerenderOption) => void; emulator?: Emulator; } ): Promise; + resolve(): (info: RequestInit | Peer) => Partial | Promise>; } export interface ManifestData { @@ -391,6 +392,7 @@ export interface PageNodeIndexes { export type PrerenderEntryGenerator = () => MaybePromise>>; export type SSREndpoint = Partial> & { + socket?: Partial; prerender?: PrerenderOption; trailingSlash?: TrailingSlash; config?: any; @@ -426,6 +428,7 @@ export interface SSRState { */ prerender_default?: PrerenderOption; read?: (file: string) => Buffer; + upgrade?: () => { ws: AdapterInstance; env: any }; before_handle?: (event: RequestEvent, config: any, prerender: PrerenderOption) => void; emulator?: Emulator; } diff --git a/packages/kit/src/utils/exports.js b/packages/kit/src/utils/exports.js index ed685edb7ded..9cd6e9c9e019 100644 --- a/packages/kit/src/utils/exports.js +++ b/packages/kit/src/utils/exports.js @@ -83,7 +83,8 @@ const valid_server_exports = new Set([ 'prerender', 'trailingSlash', 'config', - 'entries' + 'entries', + 'socket' ]); export const validate_layout_exports = validator(valid_layout_exports); diff --git a/packages/kit/test/apps/options-2/src/routes/diff-socket/+server.js b/packages/kit/test/apps/options-2/src/routes/diff-socket/+server.js new file mode 100644 index 000000000000..6d63e3fe9642 --- /dev/null +++ b/packages/kit/test/apps/options-2/src/routes/diff-socket/+server.js @@ -0,0 +1,49 @@ +import { upgrade } from '$app/server'; +import { text } from '@sveltejs/kit'; + +export const GET = () => { + console.log(upgrade()); + return text('hello from /diff-socket'); +}; + +let sockets = []; + + +export const socket = { + upgrade(req) { + console.log(`[ws] upgrading ${req.url}...`) + return { + headers: {} + } + }, + + open(peer) { + console.log(`[ws] open: ${peer}`); + }, + + message(peer, message) { + console.log('[ws] message', message.text()); + if (message.text().includes('ping')) { + peer.send('pong - from /diff-socket'); + } + if(message.text().includes('add')) { + sockets.push(peer); + peer.send('added'); + } + if(message.text().includes('broadcast')) { + sockets.forEach(socket => { + socket.send(message.text()); + }); + + } + }, + + close(peer, event) { + console.log('[ws] close', peer, event); + sockets = sockets.filter(socket => socket !== peer); + }, + + error(peer, error) { + console.log('[ws] error', peer, error); + }, +} diff --git a/packages/kit/test/apps/options-2/src/routes/ws/+page.svelte b/packages/kit/test/apps/options-2/src/routes/ws/+page.svelte index cc13f57acdac..af2c1f4a81e2 100644 --- a/packages/kit/test/apps/options-2/src/routes/ws/+page.svelte +++ b/packages/kit/test/apps/options-2/src/routes/ws/+page.svelte @@ -2,18 +2,33 @@ import { browser } from '$app/environment'; import { base } from '$app/paths'; let messages = []; - let socket + let socket1, socket2; if (browser) { - socket = new WebSocket(`${base}/ws`); - console.log(socket); - socket.onopen = () => { + socket1 = new WebSocket(`${base}/ws`); + console.log(socket1); + socket1.onopen = () => { console.log('websocket connected'); - socket.send('ping'); + socket1.send('ping'); }; - socket.onclose = () => { + socket1.onclose = () => { console.log('disconnected'); }; - socket.onmessage = (event) => { + socket1.onmessage = (event) => { + console.log(event.data); + messages = [...messages, event.data]; + console.log(messages); + }; + + socket2 = new WebSocket(`${base}/diff-socket`); + console.log(socket2); + socket2.onopen = () => { + console.log('websocket connected'); + socket2.send('ping'); + }; + socket2.onclose = () => { + console.log('disconnected'); + }; + socket2.onmessage = (event) => { console.log(event.data); messages = [...messages, event.data]; console.log(messages); @@ -23,9 +38,16 @@

Messages:

- - - + + +
{#each messages as message} diff --git a/packages/kit/test/apps/options-2/src/hooks.server.js b/packages/kit/test/apps/options-2/src/routes/ws/+server.js similarity index 92% rename from packages/kit/test/apps/options-2/src/hooks.server.js rename to packages/kit/test/apps/options-2/src/routes/ws/+server.js index b69e1783b3f8..d2548e0f500f 100644 --- a/packages/kit/test/apps/options-2/src/hooks.server.js +++ b/packages/kit/test/apps/options-2/src/routes/ws/+server.js @@ -1,6 +1,6 @@ let sockets = []; -export const websocketHooks = { +export const socket = { upgrade(req) { console.log(`[ws] upgrading ${req.url}...`) return { @@ -15,7 +15,7 @@ export const websocketHooks = { message(peer, message) { console.log('[ws] message', message.text()); if (message.text().includes('ping')) { - peer.send('pong'); + peer.send('pong - from /ws'); } if(message.text().includes('add')) { sockets.push(peer); From aa69e1cd5fcd3b70d89b0b88a36363e976fa7011 Mon Sep 17 00:00:00 2001 From: Luke Hagar Date: Fri, 8 Nov 2024 04:50:30 +0000 Subject: [PATCH 03/11] Formatting and fix a test --- packages/kit/src/exports/public.d.ts | 2 +- packages/kit/src/exports/vite/dev/index.js | 25 ++-- packages/kit/src/runtime/app/server/index.js | 1 - packages/kit/src/runtime/server/resolve.js | 124 +++++++++--------- packages/kit/src/runtime/server/respond.js | 2 +- packages/kit/src/types/internal.d.ts | 2 +- packages/kit/src/utils/exports.spec.js | 2 +- .../apps/options-2/src/routes/+page.svelte | 2 +- .../src/routes/diff-socket/+server.js | 56 ++++---- .../apps/options-2/src/routes/ws/+server.js | 51 ++++--- 10 files changed, 131 insertions(+), 136 deletions(-) diff --git a/packages/kit/src/exports/public.d.ts b/packages/kit/src/exports/public.d.ts index f562ac561e43..7f93c7c1aef0 100644 --- a/packages/kit/src/exports/public.d.ts +++ b/packages/kit/src/exports/public.d.ts @@ -692,7 +692,7 @@ export type Handle = (input: { /** * The WebsocketHooks are used when the SvelteKit server receives a websocket request and specifies how to handle it. */ -export type WebsocketHooks = Hooks +export type WebsocketHooks = Hooks; /** * The server-side [`handleError`](https://svelte.dev/docs/kit/hooks#Shared-hooks-handleError) hook runs when an unexpected error is thrown while responding to a request. diff --git a/packages/kit/src/exports/vite/dev/index.js b/packages/kit/src/exports/vite/dev/index.js index 140406630eec..7465b7f3a674 100644 --- a/packages/kit/src/exports/vite/dev/index.js +++ b/packages/kit/src/exports/vite/dev/index.js @@ -449,20 +449,21 @@ export async function dev(vite, vite_config, svelte_config) { await server.init({ env, read: (file) => createReadableStream(from_fs(file)), - upgrade: () => {return { ws, env: 'sadly no data yet'}} + upgrade: () => { + return { ws, env: 'sadly no data yet' }; + } }); /** @type {import('crossws/adapters/node').NodeAdapter} */ - const ws = crossws({ - resolve: server.resolve(), - }); - - vite.httpServer?.on('upgrade', (req, socket, head) => { - if (req.headers['sec-websocket-protocol'] !== 'vite-hmr') { - ws.handleUpgrade(req, socket, head); - } - }); + const ws = crossws({ + resolve: server.resolve() + }); + vite.httpServer?.on('upgrade', (req, socket, head) => { + if (req.headers['sec-websocket-protocol'] !== 'vite-hmr') { + ws.handleUpgrade(req, socket, head); + } + }); vite.middlewares.use(async (req, res) => { // Vite's base middleware strips out the base path. Restore it @@ -548,7 +549,9 @@ export async function dev(vite, vite_config, svelte_config) { return fs.readFileSync(path.join(svelte_config.kit.files.assets, file)); }, // This is intended to pass through the specific values needed to properly upgrade the websocket connection in other adapters - upgrade: () => {return { ws, env: { req, res } }}, + upgrade: () => { + return { ws, env: { req, res } }; + }, before_handle: (event, config, prerender) => { async_local_storage.enterWith({ event, config, prerender }); }, diff --git a/packages/kit/src/runtime/app/server/index.js b/packages/kit/src/runtime/app/server/index.js index d8662882d19d..129c66c835c2 100644 --- a/packages/kit/src/runtime/app/server/index.js +++ b/packages/kit/src/runtime/app/server/index.js @@ -72,7 +72,6 @@ export function read(asset) { throw new Error(`Asset does not exist: ${file}`); } - /** * Read the contents of an imported asset from the filesystem * @example diff --git a/packages/kit/src/runtime/server/resolve.js b/packages/kit/src/runtime/server/resolve.js index 4ce64602f62a..f97078a36f68 100644 --- a/packages/kit/src/runtime/server/resolve.js +++ b/packages/kit/src/runtime/server/resolve.js @@ -1,14 +1,11 @@ import { DEV } from 'esm-env'; -import { - validate_server_exports -} from '../../utils/exports.js'; +import { validate_server_exports } from '../../utils/exports.js'; import { exec } from '../../utils/routing.js'; import { decode_pathname } from '../../utils/url.js'; import { base } from '__sveltekit/paths'; /* global __SVELTEKIT_ADAPTER_NAME__ */ - /** * @param {import('types').SSROptions} options * @param {import('@sveltejs/kit').SSRManifest} manifest @@ -16,83 +13,82 @@ import { base } from '__sveltekit/paths'; */ export function resolve(options, manifest) { return async (info) => { + let req = info; - let req = info - - // These types all need to be straightened out - if(!req.url) { - req = info.request - } + // These types all need to be straightened out + if (!req.url) { + req = info.request; + } - /** URL but stripped from the potential `/__data.json` suffix and its search param */ - const url = new URL(req.url); + /** URL but stripped from the potential `/__data.json` suffix and its search param */ + const url = new URL(req.url); - // reroute could alter the given URL, so we pass a copy - let rerouted_path; - try { - rerouted_path = options.hooks.reroute({ url }) ?? url.pathname; - } catch { - return {}; - } + // reroute could alter the given URL, so we pass a copy + let rerouted_path; + try { + rerouted_path = options.hooks.reroute({ url }) ?? url.pathname; + } catch { + return {}; + } - let decoded; - try { - decoded = decode_pathname(rerouted_path); - } catch (e) { - console.error(e); - return {}; - } + let decoded; + try { + decoded = decode_pathname(rerouted_path); + } catch (e) { + console.error(e); + return {}; + } - if(base && decoded.startsWith(base)) { - decoded = decoded.slice(base.length) || '/'; - } + if (base && decoded.startsWith(base)) { + decoded = decoded.slice(base.length) || '/'; + } - /** @type {import('types').SSRRoute | null} */ - let route = null; + /** @type {import('types').SSRRoute | null} */ + let route = null; - // Should we find a good way to pass the decoded params to some of the websocket hooks? - // /** @type {Record} */ - // let params = {}; + // Should we find a good way to pass the decoded params to some of the websocket hooks? + // /** @type {Record} */ + // let params = {}; - try { - // TODO this could theoretically break - should probably be inside a try-catch - const matchers = await manifest._.matchers(); + try { + // TODO this could theoretically break - should probably be inside a try-catch + const matchers = await manifest._.matchers(); - for (const candidate of manifest._.routes) { - const match = candidate.pattern.exec(decoded); + for (const candidate of manifest._.routes) { + const match = candidate.pattern.exec(decoded); - if (!match) continue; + if (!match) continue; - const matched = exec(match, candidate.params, matchers); - if (matched) { - route = candidate; - break; + const matched = exec(match, candidate.params, matchers); + if (matched) { + route = candidate; + break; + } } + } catch (e) { + console.error(e); + return {}; } - }catch (e) { - console.error(e); - return {}; - } - try { - // determine whether we need to redirect to add/remove a trailing slash - if (route && route.endpoint) { - // if `paths.base === '/a/b/c`, then the root route is `/a/b/c/`, - // regardless of the `trailingSlash` route option + try { + // determine whether we need to redirect to add/remove a trailing slash + if (route && route.endpoint) { + // if `paths.base === '/a/b/c`, then the root route is `/a/b/c/`, + // regardless of the `trailingSlash` route option + + const node = await route.endpoint(); - const node = await route.endpoint(); + if (DEV) { + validate_server_exports(node, /** @type {string} */ (route.endpoint_id)); + } - if (DEV) { - validate_server_exports(node, /** @type {string} */ (route.endpoint_id)); + return node.socket ?? {}; } - return node.socket ?? {}; + return {}; + } catch (e) { + console.error(e); + return {}; } - - return {}; - } catch (e) { - console.error(e); - return {}; - } -} + }; } diff --git a/packages/kit/src/runtime/server/respond.js b/packages/kit/src/runtime/server/respond.js index 824c2fdf9631..a358691b099b 100644 --- a/packages/kit/src/runtime/server/respond.js +++ b/packages/kit/src/runtime/server/respond.js @@ -61,7 +61,7 @@ export async function respond(request, options, manifest, state) { /** URL but stripped from the potential `/__data.json` suffix and its search param */ const url = new URL(request.url); - if(state.upgrade) { + if (state.upgrade) { set_upgrade_implementation(state.upgrade); } diff --git a/packages/kit/src/types/internal.d.ts b/packages/kit/src/types/internal.d.ts index a9a7bc70f175..06870b35649d 100644 --- a/packages/kit/src/types/internal.d.ts +++ b/packages/kit/src/types/internal.d.ts @@ -26,7 +26,7 @@ import { RequestOptions, TrailingSlash } from './private.js'; -import {AdapterInstance, Hooks, Peer} from 'crossws'; +import { AdapterInstance, Hooks, Peer } from 'crossws'; export interface ServerModule { Server: typeof InternalServer; diff --git a/packages/kit/src/utils/exports.spec.js b/packages/kit/src/utils/exports.spec.js index e27817c17b5c..74e403aa697b 100644 --- a/packages/kit/src/utils/exports.spec.js +++ b/packages/kit/src/utils/exports.spec.js @@ -174,7 +174,7 @@ test('validates +server.js', () => { validate_server_exports({ answer: 42 }); - }, "Invalid export 'answer' (valid exports are GET, POST, PATCH, PUT, DELETE, OPTIONS, HEAD, fallback, prerender, trailingSlash, config, entries, or anything with a '_' prefix)"); + }, "Invalid export 'answer' (valid exports are GET, POST, PATCH, PUT, DELETE, OPTIONS, HEAD, fallback, prerender, trailingSlash, config, entries, socket, or anything with a '_' prefix)"); check_error(() => { validate_server_exports({ diff --git a/packages/kit/test/apps/options-2/src/routes/+page.svelte b/packages/kit/test/apps/options-2/src/routes/+page.svelte index cef9ba846661..264600497b42 100644 --- a/packages/kit/test/apps/options-2/src/routes/+page.svelte +++ b/packages/kit/test/apps/options-2/src/routes/+page.svelte @@ -8,5 +8,5 @@

assets: {assets}

Go to /hello -
+
Go to /ws diff --git a/packages/kit/test/apps/options-2/src/routes/diff-socket/+server.js b/packages/kit/test/apps/options-2/src/routes/diff-socket/+server.js index 6d63e3fe9642..3f2f69a4cacd 100644 --- a/packages/kit/test/apps/options-2/src/routes/diff-socket/+server.js +++ b/packages/kit/test/apps/options-2/src/routes/diff-socket/+server.js @@ -8,42 +8,40 @@ export const GET = () => { let sockets = []; - export const socket = { upgrade(req) { - console.log(`[ws] upgrading ${req.url}...`) - return { - headers: {} - } - }, - - open(peer) { - console.log(`[ws] open: ${peer}`); - }, - - message(peer, message) { - console.log('[ws] message', message.text()); - if (message.text().includes('ping')) { - peer.send('pong - from /diff-socket'); - } - if(message.text().includes('add')) { + console.log(`[ws] upgrading ${req.url}...`); + return { + headers: {} + }; + }, + + open(peer) { + console.log(`[ws] open: ${peer}`); + }, + + message(peer, message) { + console.log('[ws] message', message.text()); + if (message.text().includes('ping')) { + peer.send('pong - from /diff-socket'); + } + if (message.text().includes('add')) { sockets.push(peer); peer.send('added'); } - if(message.text().includes('broadcast')) { - sockets.forEach(socket => { + if (message.text().includes('broadcast')) { + sockets.forEach((socket) => { socket.send(message.text()); }); - } - }, + }, - close(peer, event) { - console.log('[ws] close', peer, event); - sockets = sockets.filter(socket => socket !== peer); - }, + close(peer, event) { + console.log('[ws] close', peer, event); + sockets = sockets.filter((socket) => socket !== peer); + }, - error(peer, error) { - console.log('[ws] error', peer, error); - }, -} + error(peer, error) { + console.log('[ws] error', peer, error); + } +}; diff --git a/packages/kit/test/apps/options-2/src/routes/ws/+server.js b/packages/kit/test/apps/options-2/src/routes/ws/+server.js index d2548e0f500f..47ecef884640 100644 --- a/packages/kit/test/apps/options-2/src/routes/ws/+server.js +++ b/packages/kit/test/apps/options-2/src/routes/ws/+server.js @@ -2,39 +2,38 @@ let sockets = []; export const socket = { upgrade(req) { - console.log(`[ws] upgrading ${req.url}...`) - return { - headers: {} - } - }, + console.log(`[ws] upgrading ${req.url}...`); + return { + headers: {} + }; + }, - open(peer) { - console.log(`[ws] open: ${peer}`); - }, + open(peer) { + console.log(`[ws] open: ${peer}`); + }, - message(peer, message) { - console.log('[ws] message', message.text()); - if (message.text().includes('ping')) { - peer.send('pong - from /ws'); - } - if(message.text().includes('add')) { + message(peer, message) { + console.log('[ws] message', message.text()); + if (message.text().includes('ping')) { + peer.send('pong - from /ws'); + } + if (message.text().includes('add')) { sockets.push(peer); peer.send('added'); } - if(message.text().includes('broadcast')) { - sockets.forEach(socket => { + if (message.text().includes('broadcast')) { + sockets.forEach((socket) => { socket.send(message.text()); }); - } - }, + }, - close(peer, event) { - console.log('[ws] close', peer, event); - sockets = sockets.filter(socket => socket !== peer); - }, + close(peer, event) { + console.log('[ws] close', peer, event); + sockets = sockets.filter((socket) => socket !== peer); + }, - error(peer, error) { - console.log('[ws] error', peer, error); - }, -} + error(peer, error) { + console.log('[ws] error', peer, error); + } +}; From 38c919c9e2a9dc5c2c54be7c92b542639adc4a47 Mon Sep 17 00:00:00 2001 From: Luke Hagar Date: Fri, 8 Nov 2024 04:52:42 +0000 Subject: [PATCH 04/11] removed global comment --- packages/kit/src/runtime/server/resolve.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/kit/src/runtime/server/resolve.js b/packages/kit/src/runtime/server/resolve.js index f97078a36f68..86e31a8e467d 100644 --- a/packages/kit/src/runtime/server/resolve.js +++ b/packages/kit/src/runtime/server/resolve.js @@ -4,8 +4,6 @@ import { exec } from '../../utils/routing.js'; import { decode_pathname } from '../../utils/url.js'; import { base } from '__sveltekit/paths'; -/* global __SVELTEKIT_ADAPTER_NAME__ */ - /** * @param {import('types').SSROptions} options * @param {import('@sveltejs/kit').SSRManifest} manifest From 2a022b532b448a939fb133e489047cec62a30114 Mon Sep 17 00:00:00 2001 From: Luke Hagar Date: Fri, 8 Nov 2024 04:55:47 +0000 Subject: [PATCH 05/11] regenerated types --- packages/kit/types/index.d.ts | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/packages/kit/types/index.d.ts b/packages/kit/types/index.d.ts index 237b3e3ff57e..1cecaf9c5547 100644 --- a/packages/kit/types/index.d.ts +++ b/packages/kit/types/index.d.ts @@ -4,6 +4,7 @@ declare module '@sveltejs/kit' { import type { CompileOptions } from 'svelte/compiler'; import type { PluginOptions } from '@sveltejs/vite-plugin-svelte'; + import type { Hooks } from 'crossws'; /** * [Adapters](https://svelte.dev/docs/kit/adapters) are responsible for taking the production build and turning it into something that can be deployed to a platform of your choosing. */ @@ -670,6 +671,11 @@ declare module '@sveltejs/kit' { resolve(event: RequestEvent, opts?: ResolveOptions): MaybePromise; }) => MaybePromise; + /** + * The WebsocketHooks are used when the SvelteKit server receives a websocket request and specifies how to handle it. + */ + export type WebsocketHooks = Hooks; + /** * The server-side [`handleError`](https://svelte.dev/docs/kit/hooks#Shared-hooks-handleError) hook runs when an unexpected error is thrown while responding to a request. * @@ -1164,6 +1170,8 @@ declare module '@sveltejs/kit' { env: Record; /** A function that turns an asset filename into a `ReadableStream`. Required for the `read` export from `$app/server` to work */ read?: (file: string) => ReadableStream; + /** A function that upgrades the websocket connection. Required for the `upgrade` export from `$app/server` to work */ + upgrade?: () => void; } export interface SSRManifest { @@ -1734,6 +1742,7 @@ declare module '@sveltejs/kit' { type PrerenderEntryGenerator = () => MaybePromise>>; type SSREndpoint = Partial> & { + socket?: Partial; prerender?: PrerenderOption; trailingSlash?: TrailingSlash; config?: any; @@ -2206,6 +2215,19 @@ declare module '$app/server' { * @since 2.4.0 */ export function read(asset: string): Response; + /** + * Read the contents of an imported asset from the filesystem + * @example + * ```js + * import { upgrade } from '$app/server'; + * import somefile from './somefile.txt'; + * + * const asset = read(somefile); + * const text = await asset.text(); + * ``` + * @since 2.4.0 + */ + export function upgrade(): void; export {}; } From c3a0bf7190f67357cd8bc29c352705baf658a104 Mon Sep 17 00:00:00 2001 From: Luke Hagar Date: Fri, 8 Nov 2024 21:12:08 +0000 Subject: [PATCH 06/11] removed some log statements --- packages/kit/src/runtime/app/server/index.js | 2 -- packages/kit/src/runtime/server/index.js | 1 - 2 files changed, 3 deletions(-) diff --git a/packages/kit/src/runtime/app/server/index.js b/packages/kit/src/runtime/app/server/index.js index 129c66c835c2..8dd583ce9f60 100644 --- a/packages/kit/src/runtime/app/server/index.js +++ b/packages/kit/src/runtime/app/server/index.js @@ -88,8 +88,6 @@ export function read(asset) { export function upgrade() { __SVELTEKIT_TRACK__('$app/server:upgrade'); - console.log('upgrade server function called'); - if (!upgrade_implementation) { throw new Error( 'No `upgrade` implementation was provided. Please ensure that your adapter is up to date and supports this feature' diff --git a/packages/kit/src/runtime/server/index.js b/packages/kit/src/runtime/server/index.js index 5b99b85d9405..86fea4908855 100644 --- a/packages/kit/src/runtime/server/index.js +++ b/packages/kit/src/runtime/server/index.js @@ -70,7 +70,6 @@ export class Server { } if (upgrade) { - console.log('upgrade server function set'); set_upgrade_implementation(upgrade); } From c86e4e9d7cac46b9f025ca032bfc70536d297d8d Mon Sep 17 00:00:00 2001 From: Luke Hagar Date: Fri, 8 Nov 2024 21:16:11 +0000 Subject: [PATCH 07/11] cleaning up previous implementation --- packages/kit/src/runtime/server/index.js | 2 -- packages/kit/src/types/internal.d.ts | 1 - 2 files changed, 3 deletions(-) diff --git a/packages/kit/src/runtime/server/index.js b/packages/kit/src/runtime/server/index.js index 86fea4908855..91d7b50b9b27 100644 --- a/packages/kit/src/runtime/server/index.js +++ b/packages/kit/src/runtime/server/index.js @@ -78,7 +78,6 @@ export class Server { const module = await get_hooks(); this.options.hooks = { - websocketHooks: module.websocketHooks, handle: module.handle || (({ event, resolve }) => resolve(event)), handleError: module.handleError || (({ error }) => console.error(error)), handleFetch: module.handleFetch || (({ request, fetch }) => fetch(request)), @@ -87,7 +86,6 @@ export class Server { } catch (error) { if (DEV) { this.options.hooks = { - websocketHooks: undefined, handle: () => { throw error; }, diff --git a/packages/kit/src/types/internal.d.ts b/packages/kit/src/types/internal.d.ts index 06870b35649d..a8b27ae5b5e0 100644 --- a/packages/kit/src/types/internal.d.ts +++ b/packages/kit/src/types/internal.d.ts @@ -107,7 +107,6 @@ export interface Deferred { export type GetParams = (match: RegExpExecArray) => Record; export interface ServerHooks { - websocketHooks?: Partial; handleFetch: HandleFetch; handle: Handle; handleError: HandleServerError; From 5858b49820d3cfbb9ed9bdb5573b50eb6d80a436 Mon Sep 17 00:00:00 2001 From: Luke Hagar Date: Sat, 9 Nov 2024 08:31:12 +0000 Subject: [PATCH 08/11] Thoroughly tested handle implementation --- packages/kit/src/exports/public.d.ts | 16 +- packages/kit/src/runtime/app/server/index.js | 25 ---- packages/kit/src/runtime/server/index.js | 9 +- packages/kit/src/runtime/server/resolve.js | 139 ++++++++++++++++-- packages/kit/src/types/ambient-private.d.ts | 3 +- .../test/apps/options-2/src/hooks.server.js | 14 ++ .../apps/options-2/src/routes/ws/+page.svelte | 3 + 7 files changed, 157 insertions(+), 52 deletions(-) create mode 100644 packages/kit/test/apps/options-2/src/hooks.server.js diff --git a/packages/kit/src/exports/public.d.ts b/packages/kit/src/exports/public.d.ts index 7f93c7c1aef0..b5c94f39b055 100644 --- a/packages/kit/src/exports/public.d.ts +++ b/packages/kit/src/exports/public.d.ts @@ -19,7 +19,7 @@ import { } from '../types/private.js'; import { BuildData, SSRNodeLoader, SSRRoute, ValidatedConfig } from 'types'; import type { PluginOptions } from '@sveltejs/vite-plugin-svelte'; -import { Hooks } from 'crossws'; +import { AdapterInstance, Hooks } from 'crossws'; export { PrerenderOption } from '../types/private.js'; @@ -686,13 +686,9 @@ export interface KitConfig { */ export type Handle = (input: { event: RequestEvent; - resolve(event: RequestEvent, opts?: ResolveOptions): MaybePromise; -}) => MaybePromise; + resolve(event: RequestEvent, opts?: ResolveOptions): MaybePromise; +}) => MaybePromise; -/** - * The WebsocketHooks are used when the SvelteKit server receives a websocket request and specifies how to handle it. - */ -export type WebsocketHooks = Hooks; /** * The server-side [`handleError`](https://svelte.dev/docs/kit/hooks#Shared-hooks-handleError) hook runs when an unexpected error is thrown while responding to a request. @@ -1082,6 +1078,10 @@ export interface RequestEvent< * The original request object */ request: Request; + /** + * The two functions used to control the flow of websocket requests + */ + socket?: { accept: (init: ResponseInit) => ResponseInit; reject: (status: number, body: any) => Response }; /** * Info about the current route */ @@ -1188,8 +1188,6 @@ export interface ServerInitOptions { env: Record; /** A function that turns an asset filename into a `ReadableStream`. Required for the `read` export from `$app/server` to work */ read?: (file: string) => ReadableStream; - /** A function that upgrades the websocket connection. Required for the `upgrade` export from `$app/server` to work */ - upgrade?: () => void; } export interface SSRManifest { diff --git a/packages/kit/src/runtime/app/server/index.js b/packages/kit/src/runtime/app/server/index.js index 8dd583ce9f60..288bf49cbd94 100644 --- a/packages/kit/src/runtime/app/server/index.js +++ b/packages/kit/src/runtime/app/server/index.js @@ -71,28 +71,3 @@ export function read(asset) { throw new Error(`Asset does not exist: ${file}`); } - -/** - * Read the contents of an imported asset from the filesystem - * @example - * ```js - * import { upgrade } from '$app/server'; - * import somefile from './somefile.txt'; - * - * const asset = read(somefile); - * const text = await asset.text(); - * ``` - * @returns {void} - * @since 2.4.0 - */ -export function upgrade() { - __SVELTEKIT_TRACK__('$app/server:upgrade'); - - if (!upgrade_implementation) { - throw new Error( - 'No `upgrade` implementation was provided. Please ensure that your adapter is up to date and supports this feature' - ); - } - - return upgrade_implementation(); -} diff --git a/packages/kit/src/runtime/server/index.js b/packages/kit/src/runtime/server/index.js index 91d7b50b9b27..34a5852b054a 100644 --- a/packages/kit/src/runtime/server/index.js +++ b/packages/kit/src/runtime/server/index.js @@ -114,9 +114,14 @@ export class Server { /** * Returns a function that resolves the websocket hooks for a given request + * @param {import('types').RequestOptions} options * @returns {(info: Request) => import('types').MaybePromise>} */ - resolve() { - return resolve(this.options, this.#manifest); + resolve(options) { + return resolve(this.options, this.#manifest, { + ...options, + error: false, + depth: 0 + }); } } diff --git a/packages/kit/src/runtime/server/resolve.js b/packages/kit/src/runtime/server/resolve.js index 86e31a8e467d..8a2fdd207b5a 100644 --- a/packages/kit/src/runtime/server/resolve.js +++ b/packages/kit/src/runtime/server/resolve.js @@ -1,25 +1,32 @@ -import { DEV } from 'esm-env'; +import { BROWSER, DEV } from 'esm-env'; import { validate_server_exports } from '../../utils/exports.js'; import { exec } from '../../utils/routing.js'; -import { decode_pathname } from '../../utils/url.js'; +import { + decode_pathname, + decode_params, +} from '../../utils/url.js'; import { base } from '__sveltekit/paths'; /** * @param {import('types').SSROptions} options * @param {import('@sveltejs/kit').SSRManifest} manifest + * @param {import('types').SSRState} state * @returns {(info: Request | import('crossws').Peer) => import('types').MaybePromise>} */ -export function resolve(options, manifest) { +export function resolve(options, manifest, state) { return async (info) => { - let req = info; + /** @type {Request} */ + let request; // These types all need to be straightened out - if (!req.url) { - req = info.request; + if (info.request) { + request = info.request; + } else { + request = info; } /** URL but stripped from the potential `/__data.json` suffix and its search param */ - const url = new URL(req.url); + const url = new URL(request.url); // reroute could alter the given URL, so we pass a copy let rerouted_path; @@ -44,9 +51,8 @@ export function resolve(options, manifest) { /** @type {import('types').SSRRoute | null} */ let route = null; - // Should we find a good way to pass the decoded params to some of the websocket hooks? - // /** @type {Record} */ - // let params = {}; + /** @type {Record} */ + let params = {}; try { // TODO this could theoretically break - should probably be inside a try-catch @@ -60,6 +66,7 @@ export function resolve(options, manifest) { const matched = exec(match, candidate.params, matchers); if (matched) { route = candidate; + params = decode_params(matched); break; } } @@ -68,6 +75,9 @@ export function resolve(options, manifest) { return {}; } + /** @type {Record} */ + const headers = {}; + try { // determine whether we need to redirect to add/remove a trailing slash if (route && route.endpoint) { @@ -80,13 +90,114 @@ export function resolve(options, manifest) { validate_server_exports(node, /** @type {string} */ (route.endpoint_id)); } - return node.socket ?? {}; + return { + ...node.socket, + upgrade: async (req) => { + + /** @type {import('@sveltejs/kit').RequestEvent} */ + const event = { + // @ts-expect-error `cookies` and `fetch` need to be created after the `event` itself + cookies: null, + // @ts-expect-error + fetch: null, + getClientAddress: + state.getClientAddress || + (() => { + throw new Error( + `${__SVELTEKIT_ADAPTER_NAME__} does not specify getClientAddress. Please raise an issue` + ); + }), + locals: {}, + params, + platform: state.platform, + request: req, + socket: { + /** + * Accept a WebSocket Upgrade request + * @param {RequestInit} init + * @returns {RequestInit} + */ + accept: (init) => { + return {...init}; + }, + /** + * Reject a WebSocket Upgrade request + * @param {number} status The [HTTP status code](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status#client_error_responses). Must be in the range 400-599. + * @param {{ message: string } extends App.Error ? App.Error | string | undefined : never} body An object that conforms to the App.Error type. If a string is passed, it will be used as the message property. + * @return {Response} A Response object + * @throws {Error} If the provided status is invalid (not between 400 and 599). + */ + reject: (status, body) => { + if ((!BROWSER || DEV) && (isNaN(status) || status < 400 || status > 599)) { + throw new Error(`HTTP error status codes must be between 400 and 599 — ${status} is invalid`); + } + + try { + const jsonBody =JSON.stringify(body) + return new Response(jsonBody, { + status, + headers: { + 'content-type': 'application/json' + } + }); + }catch (e) { + console.error(e); + throw new Error('Failed to serialize error body'); + } + } + }, + route: { id: route?.id ?? null }, + setHeaders: (new_headers) => { + for (const key in new_headers) { + const lower = key.toLowerCase(); + const value = new_headers[key]; + + if (lower === 'set-cookie') { + throw new Error( + 'Use `event.cookies.set(name, value, options)` instead of `event.setHeaders` to set cookies' + ); + } else if (lower in headers) { + throw new Error(`"${key}" header is already set`); + } else { + headers[lower] = value; + + if (state.prerendering && lower === 'cache-control') { + state.prerendering.cache = /** @type {string} */ (value); + } + } + } + }, + url, + isDataRequest: false, + isSubRequest: state.depth > 0 + }; + + + const response = await options.hooks.handle({ + event, + resolve: async (event) => { + + if (node.socket && node.socket.upgrade) { + return await node.socket.upgrade(event.request); + } else { + return new Response('Not Implemented', { status: 501 }); + } + } + }); + + return response ?? new Response('Not Implemented', { status: 501 }); + + // if(!response) { + // return new Response('Not Implemented', { status: 501 }); + // }else { + // return response; + // } + } + }; } - - return {}; } catch (e) { console.error(e); - return {}; + return {} } }; } diff --git a/packages/kit/src/types/ambient-private.d.ts b/packages/kit/src/types/ambient-private.d.ts index 80126a79c1ac..1e4d6a8660d0 100644 --- a/packages/kit/src/types/ambient-private.d.ts +++ b/packages/kit/src/types/ambient-private.d.ts @@ -20,11 +20,10 @@ declare module '__sveltekit/paths' { /** Internal version of $app/server */ declare module '__sveltekit/server' { import { SSRManifest } from '@sveltejs/kit'; + import { AdapterInstance } from 'crossws'; export let manifest: SSRManifest; export function read_implementation(path: string): ReadableStream; - export function upgrade_implementation(): void; export function set_manifest(manifest: SSRManifest): void; export function set_read_implementation(fn: (path: string) => ReadableStream): void; - export function set_upgrade_implementation(fn: () => void): void; } diff --git a/packages/kit/test/apps/options-2/src/hooks.server.js b/packages/kit/test/apps/options-2/src/hooks.server.js new file mode 100644 index 000000000000..a3a292776daf --- /dev/null +++ b/packages/kit/test/apps/options-2/src/hooks.server.js @@ -0,0 +1,14 @@ +export async function handle({ event, resolve }) { + console.log('handle called') + console.log(event) + + // if (Math.random() > 0.5) { + // console.log('rejecting socket connection') + // return event.socket.reject(401, { message: 'Unauthorized' }); + // }else { + // console.log('accepting socket connection') + // return event.socket.accept(); + // } + + return await resolve(event); +} diff --git a/packages/kit/test/apps/options-2/src/routes/ws/+page.svelte b/packages/kit/test/apps/options-2/src/routes/ws/+page.svelte index af2c1f4a81e2..3d4bf0fb415e 100644 --- a/packages/kit/test/apps/options-2/src/routes/ws/+page.svelte +++ b/packages/kit/test/apps/options-2/src/routes/ws/+page.svelte @@ -6,6 +6,9 @@ if (browser) { socket1 = new WebSocket(`${base}/ws`); console.log(socket1); + socket1.onerror = (event) => { + console.log(event); + }; socket1.onopen = () => { console.log('websocket connected'); socket1.send('ping'); From 9d56c50060e2acfe8f037f4f4e891b9fa30e583b Mon Sep 17 00:00:00 2001 From: Luke Hagar Date: Sat, 9 Nov 2024 08:47:21 +0000 Subject: [PATCH 09/11] Cleaning --- packages/kit/src/core/postbuild/analyse.js | 1 - packages/kit/src/core/sync/write_server.js | 4 +-- packages/kit/src/exports/vite/dev/index.js | 9 +----- packages/kit/src/exports/vite/index.js | 5 --- packages/kit/src/runtime/app/server/index.js | 2 +- packages/kit/src/runtime/server/index.js | 8 +---- packages/kit/src/runtime/server/resolve.js | 6 ---- packages/kit/src/runtime/server/respond.js | 5 --- packages/kit/src/types/ambient-private.d.ts | 1 - packages/kit/src/types/internal.d.ts | 3 -- packages/kit/types/index.d.ts | 32 ++++---------------- 11 files changed, 11 insertions(+), 65 deletions(-) diff --git a/packages/kit/src/core/postbuild/analyse.js b/packages/kit/src/core/postbuild/analyse.js index db2f2aca99dd..1cc928680e9a 100644 --- a/packages/kit/src/core/postbuild/analyse.js +++ b/packages/kit/src/core/postbuild/analyse.js @@ -56,7 +56,6 @@ async function analyse({ manifest_path, manifest_data, server_manifest, tracked_ internal.set_safe_public_env(public_env); internal.set_manifest(manifest); internal.set_read_implementation((file) => createReadableStream(`${server_root}/server/${file}`)); - internal.set_upgrade_implementation(() => {}); /** @type {import('types').ServerMetadata} */ const metadata = { diff --git a/packages/kit/src/core/sync/write_server.js b/packages/kit/src/core/sync/write_server.js index 28ce4f098485..eb50bfd4735b 100644 --- a/packages/kit/src/core/sync/write_server.js +++ b/packages/kit/src/core/sync/write_server.js @@ -31,7 +31,7 @@ const server_template = ({ import root from '../root.${isSvelte5Plus() ? 'js' : 'svelte'}'; import { set_building, set_prerendering } from '__sveltekit/environment'; import { set_assets } from '__sveltekit/paths'; -import { set_manifest, set_read_implementation, set_upgrade_implementation } from '__sveltekit/server'; +import { set_manifest, set_read_implementation } from '__sveltekit/server'; import { set_private_env, set_public_env, set_safe_public_env } from '${runtime_directory}/shared-server.js'; export const options = { @@ -70,7 +70,7 @@ export async function get_hooks() { }; } -export { set_assets, set_building, set_manifest, set_prerendering, set_private_env, set_public_env, set_read_implementation, set_upgrade_implementation, set_safe_public_env }; +export { set_assets, set_building, set_manifest, set_prerendering, set_private_env, set_public_env, set_read_implementation, set_safe_public_env }; `; // TODO need to re-run this whenever src/app.html or src/error.html are diff --git a/packages/kit/src/exports/vite/dev/index.js b/packages/kit/src/exports/vite/dev/index.js index 7465b7f3a674..5c4c4fd4ee3e 100644 --- a/packages/kit/src/exports/vite/dev/index.js +++ b/packages/kit/src/exports/vite/dev/index.js @@ -448,10 +448,7 @@ export async function dev(vite, vite_config, svelte_config) { // we have to initialize the server before we can call the resolve function to populate the webhook resolver in the websocket handler await server.init({ env, - read: (file) => createReadableStream(from_fs(file)), - upgrade: () => { - return { ws, env: 'sadly no data yet' }; - } + read: (file) => createReadableStream(from_fs(file)) }); /** @type {import('crossws/adapters/node').NodeAdapter} */ @@ -548,10 +545,6 @@ export async function dev(vite, vite_config, svelte_config) { return fs.readFileSync(path.join(svelte_config.kit.files.assets, file)); }, - // This is intended to pass through the specific values needed to properly upgrade the websocket connection in other adapters - upgrade: () => { - return { ws, env: { req, res } }; - }, before_handle: (event, config, prerender) => { async_local_storage.enterWith({ event, config, prerender }); }, diff --git a/packages/kit/src/exports/vite/index.js b/packages/kit/src/exports/vite/index.js index cd9e187b9886..40fa4c6edb56 100644 --- a/packages/kit/src/exports/vite/index.js +++ b/packages/kit/src/exports/vite/index.js @@ -523,7 +523,6 @@ async function kit({ svelte_config }) { case sveltekit_server: { return dedent` export let read_implementation = null; - export let upgrade_implementation = null; export let manifest = null; @@ -531,10 +530,6 @@ async function kit({ svelte_config }) { read_implementation = fn; } - export function set_upgrade_implementation(fn) { - upgrade_implementation = fn; - } - export function set_manifest(_) { manifest = _; } diff --git a/packages/kit/src/runtime/app/server/index.js b/packages/kit/src/runtime/app/server/index.js index 288bf49cbd94..33c9b0a0d1ba 100644 --- a/packages/kit/src/runtime/app/server/index.js +++ b/packages/kit/src/runtime/app/server/index.js @@ -1,4 +1,4 @@ -import { read_implementation, upgrade_implementation, manifest } from '__sveltekit/server'; +import { read_implementation, manifest } from '__sveltekit/server'; import { base } from '__sveltekit/paths'; import { DEV } from 'esm-env'; import { b64_decode } from '../../utils.js'; diff --git a/packages/kit/src/runtime/server/index.js b/packages/kit/src/runtime/server/index.js index 34a5852b054a..423e6dd3225b 100644 --- a/packages/kit/src/runtime/server/index.js +++ b/packages/kit/src/runtime/server/index.js @@ -7,7 +7,6 @@ import { filter_private_env, filter_public_env } from '../../utils/env.js'; import { prerendering } from '__sveltekit/environment'; import { set_read_implementation, - set_upgrade_implementation, set_manifest } from '__sveltekit/server'; @@ -40,10 +39,9 @@ export class Server { * @param {{ * env: Record; * read?: (file: string) => ReadableStream; - * upgrade?: () => { ws: import('crossws').AdapterInstance; env: any }; * }} opts */ - async init({ env, read, upgrade }) { + async init({ env, read }) { // Take care: Some adapters may have to call `Server.init` per-request to set env vars, // so anything that shouldn't be rerun should be wrapped in an `if` block to make sure it hasn't // been done already. @@ -69,10 +67,6 @@ export class Server { set_read_implementation(read); } - if (upgrade) { - set_upgrade_implementation(upgrade); - } - if (!this.options.hooks) { try { const module = await get_hooks(); diff --git a/packages/kit/src/runtime/server/resolve.js b/packages/kit/src/runtime/server/resolve.js index 8a2fdd207b5a..3f6fef524dbc 100644 --- a/packages/kit/src/runtime/server/resolve.js +++ b/packages/kit/src/runtime/server/resolve.js @@ -186,12 +186,6 @@ export function resolve(options, manifest, state) { }); return response ?? new Response('Not Implemented', { status: 501 }); - - // if(!response) { - // return new Response('Not Implemented', { status: 501 }); - // }else { - // return response; - // } } }; } diff --git a/packages/kit/src/runtime/server/respond.js b/packages/kit/src/runtime/server/respond.js index a358691b099b..f81f52ef2557 100644 --- a/packages/kit/src/runtime/server/respond.js +++ b/packages/kit/src/runtime/server/respond.js @@ -33,7 +33,6 @@ import { INVALIDATED_PARAM, TRAILING_SLASH_PARAM } from '../shared.js'; import { get_public_env } from './env_module.js'; import { load_page_nodes } from './page/load_page_nodes.js'; import { get_page_config } from '../../utils/route_config.js'; -import { set_upgrade_implementation } from '__sveltekit/server'; /* global __SVELTEKIT_ADAPTER_NAME__ */ @@ -61,10 +60,6 @@ export async function respond(request, options, manifest, state) { /** URL but stripped from the potential `/__data.json` suffix and its search param */ const url = new URL(request.url); - if (state.upgrade) { - set_upgrade_implementation(state.upgrade); - } - if (options.csrf_check_origin) { const forbidden = is_form_content_type(request) && diff --git a/packages/kit/src/types/ambient-private.d.ts b/packages/kit/src/types/ambient-private.d.ts index 1e4d6a8660d0..4f1491475355 100644 --- a/packages/kit/src/types/ambient-private.d.ts +++ b/packages/kit/src/types/ambient-private.d.ts @@ -20,7 +20,6 @@ declare module '__sveltekit/paths' { /** Internal version of $app/server */ declare module '__sveltekit/server' { import { SSRManifest } from '@sveltejs/kit'; - import { AdapterInstance } from 'crossws'; export let manifest: SSRManifest; export function read_implementation(path: string): ReadableStream; diff --git a/packages/kit/src/types/internal.d.ts b/packages/kit/src/types/internal.d.ts index a8b27ae5b5e0..a0f1084cd471 100644 --- a/packages/kit/src/types/internal.d.ts +++ b/packages/kit/src/types/internal.d.ts @@ -40,7 +40,6 @@ export interface ServerInternalModule { set_private_env(environment: Record): void; set_public_env(environment: Record): void; set_read_implementation(implementation: (path: string) => ReadableStream): void; - set_upgrade_implementation(implementation: () => void): void; set_safe_public_env(environment: Record): void; set_version(version: string): void; set_fix_stack_trace(fix_stack_trace: (error: unknown) => string): void; @@ -131,7 +130,6 @@ export class InternalServer extends Server { options: RequestOptions & { prerendering?: PrerenderOptions; read: (file: string) => Buffer; - upgrade: () => { ws: AdapterInstance; env: any }; /** A hook called before `handle` during dev, so that `AsyncLocalStorage` can be populated */ before_handle?: (event: RequestEvent, config: any, prerender: PrerenderOption) => void; emulator?: Emulator; @@ -427,7 +425,6 @@ export interface SSRState { */ prerender_default?: PrerenderOption; read?: (file: string) => Buffer; - upgrade?: () => { ws: AdapterInstance; env: any }; before_handle?: (event: RequestEvent, config: any, prerender: PrerenderOption) => void; emulator?: Emulator; } diff --git a/packages/kit/types/index.d.ts b/packages/kit/types/index.d.ts index 1cecaf9c5547..86594cf589e3 100644 --- a/packages/kit/types/index.d.ts +++ b/packages/kit/types/index.d.ts @@ -671,11 +671,6 @@ declare module '@sveltejs/kit' { resolve(event: RequestEvent, opts?: ResolveOptions): MaybePromise; }) => MaybePromise; - /** - * The WebsocketHooks are used when the SvelteKit server receives a websocket request and specifies how to handle it. - */ - export type WebsocketHooks = Hooks; - /** * The server-side [`handleError`](https://svelte.dev/docs/kit/hooks#Shared-hooks-handleError) hook runs when an unexpected error is thrown while responding to a request. * @@ -1170,8 +1165,6 @@ declare module '@sveltejs/kit' { env: Record; /** A function that turns an asset filename into a `ReadableStream`. Required for the `read` export from `$app/server` to work */ read?: (file: string) => ReadableStream; - /** A function that upgrades the websocket connection. Required for the `upgrade` export from `$app/server` to work */ - upgrade?: () => void; } export interface SSRManifest { @@ -1844,7 +1837,7 @@ declare module '@sveltejs/kit' { export type NumericRange = Exclude, LessThan>; export const VERSION: string; class HttpError_1 { - + constructor(status: number, body: { message: string; } extends App.Error ? (App.Error | string | undefined) : App.Error); @@ -1853,7 +1846,7 @@ declare module '@sveltejs/kit' { toString(): string; } class Redirect_1 { - + constructor(status: 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308, location: string); status: 301 | 302 | 303 | 307 | 308 | 300 | 304 | 305 | 306; location: string; @@ -2215,30 +2208,17 @@ declare module '$app/server' { * @since 2.4.0 */ export function read(asset: string): Response; - /** - * Read the contents of an imported asset from the filesystem - * @example - * ```js - * import { upgrade } from '$app/server'; - * import somefile from './somefile.txt'; - * - * const asset = read(somefile); - * const text = await asset.text(); - * ``` - * @since 2.4.0 - */ - export function upgrade(): void; export {}; } declare module '$app/stores' { export function getStores(): { - + page: typeof page; - + navigating: typeof navigating; - + updated: typeof updated; }; /** @@ -2348,4 +2328,4 @@ declare module '$service-worker' { export const version: string; } -//# sourceMappingURL=index.d.ts.map \ No newline at end of file +//# sourceMappingURL=index.d.ts.map From 46c868243669f84b7018fb26fed01b599b8ec3d4 Mon Sep 17 00:00:00 2001 From: Luke Hagar Date: Sat, 9 Nov 2024 08:48:14 +0000 Subject: [PATCH 10/11] regenerated types and ran formatter --- packages/kit/src/exports/public.d.ts | 8 ++++--- packages/kit/src/runtime/server/index.js | 5 +---- packages/kit/src/runtime/server/resolve.js | 20 +++++++----------- .../test/apps/options-2/src/hooks.server.js | 4 ++-- packages/kit/types/index.d.ts | 21 ++++++++++++------- 5 files changed, 29 insertions(+), 29 deletions(-) diff --git a/packages/kit/src/exports/public.d.ts b/packages/kit/src/exports/public.d.ts index b5c94f39b055..047dc90cf082 100644 --- a/packages/kit/src/exports/public.d.ts +++ b/packages/kit/src/exports/public.d.ts @@ -689,7 +689,6 @@ export type Handle = (input: { resolve(event: RequestEvent, opts?: ResolveOptions): MaybePromise; }) => MaybePromise; - /** * The server-side [`handleError`](https://svelte.dev/docs/kit/hooks#Shared-hooks-handleError) hook runs when an unexpected error is thrown while responding to a request. * @@ -1080,8 +1079,11 @@ export interface RequestEvent< request: Request; /** * The two functions used to control the flow of websocket requests - */ - socket?: { accept: (init: ResponseInit) => ResponseInit; reject: (status: number, body: any) => Response }; + */ + socket?: { + accept: (init: ResponseInit) => ResponseInit; + reject: (status: number, body: any) => Response; + }; /** * Info about the current route */ diff --git a/packages/kit/src/runtime/server/index.js b/packages/kit/src/runtime/server/index.js index 423e6dd3225b..042560f2cd52 100644 --- a/packages/kit/src/runtime/server/index.js +++ b/packages/kit/src/runtime/server/index.js @@ -5,10 +5,7 @@ import { options, get_hooks } from '__SERVER__/internal.js'; import { DEV } from 'esm-env'; import { filter_private_env, filter_public_env } from '../../utils/env.js'; import { prerendering } from '__sveltekit/environment'; -import { - set_read_implementation, - set_manifest -} from '__sveltekit/server'; +import { set_read_implementation, set_manifest } from '__sveltekit/server'; /** @type {ProxyHandler<{ type: 'public' | 'private' }>} */ const prerender_env_handler = { diff --git a/packages/kit/src/runtime/server/resolve.js b/packages/kit/src/runtime/server/resolve.js index 3f6fef524dbc..f86d76d11974 100644 --- a/packages/kit/src/runtime/server/resolve.js +++ b/packages/kit/src/runtime/server/resolve.js @@ -1,10 +1,7 @@ import { BROWSER, DEV } from 'esm-env'; import { validate_server_exports } from '../../utils/exports.js'; import { exec } from '../../utils/routing.js'; -import { - decode_pathname, - decode_params, -} from '../../utils/url.js'; +import { decode_pathname, decode_params } from '../../utils/url.js'; import { base } from '__sveltekit/paths'; /** @@ -93,7 +90,6 @@ export function resolve(options, manifest, state) { return { ...node.socket, upgrade: async (req) => { - /** @type {import('@sveltejs/kit').RequestEvent} */ const event = { // @ts-expect-error `cookies` and `fetch` need to be created after the `event` itself @@ -118,7 +114,7 @@ export function resolve(options, manifest, state) { * @returns {RequestInit} */ accept: (init) => { - return {...init}; + return { ...init }; }, /** * Reject a WebSocket Upgrade request @@ -129,18 +125,20 @@ export function resolve(options, manifest, state) { */ reject: (status, body) => { if ((!BROWSER || DEV) && (isNaN(status) || status < 400 || status > 599)) { - throw new Error(`HTTP error status codes must be between 400 and 599 — ${status} is invalid`); + throw new Error( + `HTTP error status codes must be between 400 and 599 — ${status} is invalid` + ); } try { - const jsonBody =JSON.stringify(body) + const jsonBody = JSON.stringify(body); return new Response(jsonBody, { status, headers: { 'content-type': 'application/json' } }); - }catch (e) { + } catch (e) { console.error(e); throw new Error('Failed to serialize error body'); } @@ -172,11 +170,9 @@ export function resolve(options, manifest, state) { isSubRequest: state.depth > 0 }; - const response = await options.hooks.handle({ event, resolve: async (event) => { - if (node.socket && node.socket.upgrade) { return await node.socket.upgrade(event.request); } else { @@ -191,7 +187,7 @@ export function resolve(options, manifest, state) { } } catch (e) { console.error(e); - return {} + return {}; } }; } diff --git a/packages/kit/test/apps/options-2/src/hooks.server.js b/packages/kit/test/apps/options-2/src/hooks.server.js index a3a292776daf..321c57904c59 100644 --- a/packages/kit/test/apps/options-2/src/hooks.server.js +++ b/packages/kit/test/apps/options-2/src/hooks.server.js @@ -1,6 +1,6 @@ export async function handle({ event, resolve }) { - console.log('handle called') - console.log(event) + console.log('handle called'); + console.log(event); // if (Math.random() > 0.5) { // console.log('rejecting socket connection') diff --git a/packages/kit/types/index.d.ts b/packages/kit/types/index.d.ts index 86594cf589e3..e8e572625689 100644 --- a/packages/kit/types/index.d.ts +++ b/packages/kit/types/index.d.ts @@ -668,8 +668,9 @@ declare module '@sveltejs/kit' { */ export type Handle = (input: { event: RequestEvent; - resolve(event: RequestEvent, opts?: ResolveOptions): MaybePromise; - }) => MaybePromise; + resolve(event: RequestEvent, opts?: ResolveOptions): MaybePromise; + }) => MaybePromise; + /** * The server-side [`handleError`](https://svelte.dev/docs/kit/hooks#Shared-hooks-handleError) hook runs when an unexpected error is thrown while responding to a request. @@ -1059,6 +1060,10 @@ declare module '@sveltejs/kit' { * The original request object */ request: Request; + /** + * The two functions used to control the flow of websocket requests + */ + socket?: { accept: (init: ResponseInit) => ResponseInit; reject: (status: number, body: any) => Response }; /** * Info about the current route */ @@ -1837,7 +1842,7 @@ declare module '@sveltejs/kit' { export type NumericRange = Exclude, LessThan>; export const VERSION: string; class HttpError_1 { - + constructor(status: number, body: { message: string; } extends App.Error ? (App.Error | string | undefined) : App.Error); @@ -1846,7 +1851,7 @@ declare module '@sveltejs/kit' { toString(): string; } class Redirect_1 { - + constructor(status: 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308, location: string); status: 301 | 302 | 303 | 307 | 308 | 300 | 304 | 305 | 306; location: string; @@ -2214,11 +2219,11 @@ declare module '$app/server' { declare module '$app/stores' { export function getStores(): { - + page: typeof page; - + navigating: typeof navigating; - + updated: typeof updated; }; /** @@ -2328,4 +2333,4 @@ declare module '$service-worker' { export const version: string; } -//# sourceMappingURL=index.d.ts.map +//# sourceMappingURL=index.d.ts.map \ No newline at end of file From 779175948bbb1d2aa2cacef6d0ded5e4dbbb16a3 Mon Sep 17 00:00:00 2001 From: Chew Tee Ming Date: Mon, 11 Nov 2024 10:56:10 +0800 Subject: [PATCH 11/11] generate types --- packages/kit/types/index.d.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/kit/types/index.d.ts b/packages/kit/types/index.d.ts index e8e572625689..a3443a3af3ce 100644 --- a/packages/kit/types/index.d.ts +++ b/packages/kit/types/index.d.ts @@ -671,7 +671,6 @@ declare module '@sveltejs/kit' { resolve(event: RequestEvent, opts?: ResolveOptions): MaybePromise; }) => MaybePromise; - /** * The server-side [`handleError`](https://svelte.dev/docs/kit/hooks#Shared-hooks-handleError) hook runs when an unexpected error is thrown while responding to a request. * @@ -1062,8 +1061,11 @@ declare module '@sveltejs/kit' { request: Request; /** * The two functions used to control the flow of websocket requests - */ - socket?: { accept: (init: ResponseInit) => ResponseInit; reject: (status: number, body: any) => Response }; + */ + socket?: { + accept: (init: ResponseInit) => ResponseInit; + reject: (status: number, body: any) => Response; + }; /** * Info about the current route */