diff --git a/integration/websockets/e2e/error-gateway.spec.ts b/integration/websockets/e2e/error-gateway.spec.ts index 57b73be0d..95a7d4d28 100644 --- a/integration/websockets/e2e/error-gateway.spec.ts +++ b/integration/websockets/e2e/error-gateway.spec.ts @@ -1,7 +1,7 @@ import { INestApplication } from '@nestjs/common'; import { Test } from '@nestjs/testing'; import { expect } from 'chai'; -import * as io from 'socket.io-client'; +import { io } from 'socket.io-client'; import { ErrorGateway } from '../src/error.gateway'; describe('ErrorGateway', () => { @@ -16,7 +16,7 @@ describe('ErrorGateway', () => { }); it(`should handle error`, async () => { - const ws = io.connect('http://localhost:8080'); + const ws = io('http://localhost:8080'); ws.emit('push', { test: 'test', }); diff --git a/integration/websockets/e2e/gateway-ack.spec.ts b/integration/websockets/e2e/gateway-ack.spec.ts index e01383493..034a7ae29 100644 --- a/integration/websockets/e2e/gateway-ack.spec.ts +++ b/integration/websockets/e2e/gateway-ack.spec.ts @@ -1,14 +1,14 @@ import { INestApplication } from '@nestjs/common'; import { Test } from '@nestjs/testing'; import { expect } from 'chai'; -import * as io from 'socket.io-client'; +import { io } from 'socket.io-client'; import { AckGateway } from '../src/ack.gateway'; async function createNestApp(...gateways): Promise { const testingModule = await Test.createTestingModule({ providers: gateways, }).compile(); - const app = await testingModule.createNestApplication(); + const app = testingModule.createNestApplication(); return app; } @@ -19,7 +19,7 @@ describe('WebSocketGateway (ack)', () => { app = await createNestApp(AckGateway); await app.listen(3000); - ws = io.connect('http://localhost:8080'); + ws = io('http://localhost:8080'); await new Promise(resolve => ws.emit('push', { test: 'test' }, data => { expect(data).to.be.eql('pong'); @@ -32,7 +32,7 @@ describe('WebSocketGateway (ack)', () => { app = await createNestApp(AckGateway); await app.listen(3000); - ws = io.connect('http://localhost:8080'); + ws = io('http://localhost:8080'); await new Promise(resolve => ws.emit('push', data => { expect(data).to.be.eql('pong'); diff --git a/integration/websockets/e2e/gateway.spec.ts b/integration/websockets/e2e/gateway.spec.ts index c1b004828..cde31c4b2 100644 --- a/integration/websockets/e2e/gateway.spec.ts +++ b/integration/websockets/e2e/gateway.spec.ts @@ -1,7 +1,7 @@ import { INestApplication } from '@nestjs/common'; import { Test } from '@nestjs/testing'; import { expect } from 'chai'; -import * as io from 'socket.io-client'; +import { io } from 'socket.io-client'; import { ApplicationGateway } from '../src/app.gateway'; import { NamespaceGateway } from '../src/namespace.gateway'; import { ServerGateway } from '../src/server.gateway'; @@ -10,7 +10,7 @@ async function createNestApp(...gateways): Promise { const testingModule = await Test.createTestingModule({ providers: gateways, }).compile(); - const app = await testingModule.createNestApplication(); + const app = testingModule.createNestApplication(); return app; } @@ -21,7 +21,7 @@ describe('WebSocketGateway', () => { app = await createNestApp(ApplicationGateway); await app.listen(3000); - ws = io.connect('http://localhost:8080'); + ws = io('http://localhost:8080'); ws.emit('push', { test: 'test', }); @@ -37,7 +37,7 @@ describe('WebSocketGateway', () => { app = await createNestApp(ServerGateway); await app.listen(3000); - ws = io.connect('http://localhost:3000'); + ws = io('http://localhost:3000'); ws.emit('push', { test: 'test', }); @@ -53,8 +53,8 @@ describe('WebSocketGateway', () => { app = await createNestApp(ApplicationGateway, NamespaceGateway); await app.listen(3000); - ws = io.connect('http://localhost:8080'); - io.connect('http://localhost:8080/test').emit('push', {}); + ws = io('http://localhost:8080'); + io('http://localhost:8080/test').emit('push', {}); ws.emit('push', { test: 'test', }); diff --git a/packages/core/helpers/router-method-factory.ts b/packages/core/helpers/router-method-factory.ts index dea46c62f..d9b9a5d97 100644 --- a/packages/core/helpers/router-method-factory.ts +++ b/packages/core/helpers/router-method-factory.ts @@ -18,8 +18,10 @@ export class RouterMethodFactory { return target.options; case RequestMethod.HEAD: return target.head; - default: { + case RequestMethod.GET: return target.get; + default: { + return target.use; } } } diff --git a/packages/core/middleware/builder.ts b/packages/core/middleware/builder.ts index a91154da1..d9d63f20f 100644 --- a/packages/core/middleware/builder.ts +++ b/packages/core/middleware/builder.ts @@ -10,7 +10,6 @@ import { } from '@nestjs/common/interfaces/middleware'; import { MiddlewareConfiguration } from '@nestjs/common/interfaces/middleware/middleware-configuration.interface'; import { iterate } from 'iterare'; -import { NestContainer } from '../injector'; import { RoutesMapper } from './routes-mapper'; import { filterMiddleware } from './utils'; @@ -20,7 +19,6 @@ export class MiddlewareBuilder implements MiddlewareConsumer { constructor( private readonly routesMapper: RoutesMapper, private readonly httpAdapter: HttpServer, - private readonly container: NestContainer, ) {} public apply( diff --git a/packages/core/middleware/middleware-module.ts b/packages/core/middleware/middleware-module.ts index 0f229d623..b02d081f7 100644 --- a/packages/core/middleware/middleware-module.ts +++ b/packages/core/middleware/middleware-module.ts @@ -91,7 +91,6 @@ export class MiddlewareModule { const middlewareBuilder = new MiddlewareBuilder( this.routesMapper, this.httpAdapter, - this.container, ); await instance.configure(middlewareBuilder); diff --git a/packages/core/middleware/routes-mapper.ts b/packages/core/middleware/routes-mapper.ts index 5e9ef6e46..ce4d246e1 100644 --- a/packages/core/middleware/routes-mapper.ts +++ b/packages/core/middleware/routes-mapper.ts @@ -1,4 +1,3 @@ -import { RequestMethod } from '@nestjs/common'; import { MODULE_PATH, PATH_METADATA } from '@nestjs/common/constants'; import { RouteInfo, Type } from '@nestjs/common/interfaces'; import { @@ -23,10 +22,11 @@ export class RoutesMapper { route: Type | RouteInfo | string, ): RouteInfo[] { if (isString(route)) { + const defaultRequestMethod = -1; return [ { path: addLeadingSlash(route), - method: RequestMethod.ALL, + method: defaultRequestMethod, }, ]; } @@ -83,7 +83,7 @@ export class RoutesMapper { private getHostModuleOfController( metatype: Type, ): Module | undefined { - if (!metatype?.name) { + if (!metatype) { return; } const modulesContainer = this.container.getModules(); @@ -95,7 +95,7 @@ export class RoutesMapper { const modules = Array.from(modulesContainer.values()).filter(moduleRef => moduleRefsSet.has(moduleRef), ); - return modules.find(({ routes }) => routes.has(metatype.name)); + return modules.find(({ routes }) => routes.has(metatype)); } private getModulePath( diff --git a/packages/core/middleware/utils.ts b/packages/core/middleware/utils.ts index 0ebd282bf..3b18e5b60 100644 --- a/packages/core/middleware/utils.ts +++ b/packages/core/middleware/utils.ts @@ -2,9 +2,9 @@ import { RequestMethod } from '@nestjs/common'; import { HttpServer, RouteInfo, Type } from '@nestjs/common/interfaces'; import { isFunction } from '@nestjs/common/utils/shared.utils'; +import { iterate } from 'iterare'; import * as pathToRegexp from 'path-to-regexp'; import { v4 as uuid } from 'uuid'; -import { iterate } from 'iterare'; type RouteInfoRegex = RouteInfo & { regex: RegExp }; @@ -85,7 +85,11 @@ export function isRouteExcluded( : originalUrl; const isExcluded = excludedRoutes.some(({ method, regex }) => { - if (RequestMethod.ALL === method || RequestMethod[method] === reqMethod) { + if ( + RequestMethod.ALL === method || + RequestMethod[method] === reqMethod || + method === -1 + ) { return regex.exec(pathname); } return false; diff --git a/packages/core/test/helpers/router-method-factory.spec.ts b/packages/core/test/helpers/router-method-factory.spec.ts index 17e0231b3..242fc51a3 100644 --- a/packages/core/test/helpers/router-method-factory.spec.ts +++ b/packages/core/test/helpers/router-method-factory.spec.ts @@ -29,5 +29,6 @@ describe('RouterMethodFactory', () => { expect(factory.get(target, RequestMethod.PATCH)).to.equal(target.patch); expect(factory.get(target, RequestMethod.OPTIONS)).to.equal(target.options); expect(factory.get(target, RequestMethod.HEAD)).to.equal(target.head); + expect(factory.get(target, -1)).to.equal(target.use); }); }); diff --git a/packages/core/test/middleware/builder.spec.ts b/packages/core/test/middleware/builder.spec.ts index 5e8f96155..1cc6e144d 100644 --- a/packages/core/test/middleware/builder.spec.ts +++ b/packages/core/test/middleware/builder.spec.ts @@ -1,4 +1,3 @@ -import { RequestMethod } from '@nestjs/common'; import { expect } from 'chai'; import { Controller, Get } from '../../../common'; import { NestContainer } from '../../injector/container'; @@ -14,7 +13,6 @@ describe('MiddlewareBuilder', () => { builder = new MiddlewareBuilder( new RoutesMapper(container), new NoopHttpAdapter({}), - container, ); }); describe('apply', () => { @@ -65,7 +63,7 @@ describe('MiddlewareBuilder', () => { expect(proxy.getExcludedRoutes()).to.be.eql([ { path, - method: RequestMethod.ALL, + method: -1, }, ]); }); diff --git a/packages/core/test/middleware/middleware-module.spec.ts b/packages/core/test/middleware/middleware-module.spec.ts index 293540b16..20be3c454 100644 --- a/packages/core/test/middleware/middleware-module.spec.ts +++ b/packages/core/test/middleware/middleware-module.spec.ts @@ -75,7 +75,6 @@ describe('MiddlewareModule', () => { new MiddlewareBuilder( (middlewareModule as any).routesMapper, undefined, - stubContainer, ), ), ).to.be.true; diff --git a/packages/platform-fastify/adapters/fastify-adapter.ts b/packages/platform-fastify/adapters/fastify-adapter.ts index 60f1a8b4f..5f957540e 100644 --- a/packages/platform-fastify/adapters/fastify-adapter.ts +++ b/packages/platform-fastify/adapters/fastify-adapter.ts @@ -312,7 +312,8 @@ export class FastifyAdapter< } if ( requestMethod === RequestMethod.ALL || - req.method === RequestMethod[requestMethod] + req.method === RequestMethod[requestMethod] || + requestMethod === -1 ) { return callback(req, res, next); } diff --git a/packages/platform-socket.io/adapters/io-adapter.ts b/packages/platform-socket.io/adapters/io-adapter.ts index 9699de967..22eb9155c 100644 --- a/packages/platform-socket.io/adapters/io-adapter.ts +++ b/packages/platform-socket.io/adapters/io-adapter.ts @@ -6,13 +6,13 @@ import { import { DISCONNECT_EVENT } from '@nestjs/websockets/constants'; import { fromEvent, Observable } from 'rxjs'; import { filter, first, map, mergeMap, share, takeUntil } from 'rxjs/operators'; -import * as io from 'socket.io'; +import { Server, ServerOptions, Socket } from 'socket.io'; export class IoAdapter extends AbstractWsAdapter { public create( port: number, - options?: any & { namespace?: string; server?: any }, - ): any { + options?: ServerOptions & { namespace?: string; server?: any }, + ): Server { if (!options) { return this.createIOServer(port); } @@ -26,23 +26,23 @@ export class IoAdapter extends AbstractWsAdapter { public createIOServer(port: number, options?: any): any { if (this.httpServer && port === 0) { - return io(this.httpServer, options); + return new Server(this.httpServer, options); } - return io(port, options); + return new Server(port, options); } public bindMessageHandlers( - client: any, + socket: Socket, handlers: MessageMappingProperties[], transform: (data: any) => Observable, ) { - const disconnect$ = fromEvent(client, DISCONNECT_EVENT).pipe( + const disconnect$ = fromEvent(socket, DISCONNECT_EVENT).pipe( share(), first(), ); handlers.forEach(({ message, callback }) => { - const source$ = fromEvent(client, message).pipe( + const source$ = fromEvent(socket, message).pipe( mergeMap((payload: any) => { const { data, ack } = this.mapPayload(payload); return transform(callback(data, ack)).pipe( @@ -54,17 +54,17 @@ export class IoAdapter extends AbstractWsAdapter { ); source$.subscribe(([response, ack]) => { if (response.event) { - return client.emit(response.event, response.data); + return socket.emit(response.event, response.data); } isFunction(ack) && ack(response); }); }); } - public mapPayload(payload: any): { data: any; ack?: Function } { + public mapPayload(payload: unknown): { data: any; ack?: Function } { if (!Array.isArray(payload)) { if (isFunction(payload)) { - return { data: undefined, ack: payload }; + return { data: undefined, ack: payload as Function }; } return { data: payload }; } diff --git a/packages/websockets/interfaces/gateway-metadata.interface.ts b/packages/websockets/interfaces/gateway-metadata.interface.ts index 9ee0c2987..977909f45 100644 --- a/packages/websockets/interfaces/gateway-metadata.interface.ts +++ b/packages/websockets/interfaces/gateway-metadata.interface.ts @@ -1,97 +1,122 @@ /** - * @external https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/socket.io/index.d.ts + * @external https://github.com/socketio/socket.io/blob/master/lib/index.ts */ +import { CorsOptions } from '@nestjs/common/interfaces/external/cors-options.interface'; + export interface GatewayMetadata { /** * The name of a namespace */ namespace?: string | RegExp; - /** - * The path to ws - * @default '/socket.io' + * Name of the path to capture + * @default "/socket.io" */ path?: string; - /** - * Should we serve the client file? + * Whether to serve the client files * @default true */ serveClient?: boolean; - /** - * The adapter to use for handling rooms. NOTE: this should be a class, - * not an object - * @default typeof Adapter + * The adapter to use + * @default the in-memory adapter (https://github.com/socketio/socket.io-adapter) */ adapter?: any; - /** - * Accepted origins - * @default '*:*' + * The parser to use + * @default the default parser (https://github.com/socketio/socket.io-parser) */ - origins?: string; - parser?: any; - /** - * How many milliseconds without a pong packed to consider the connection closed (engine.io) - * @default 60000 + * How many ms before a client without namespace is closed + * @default 45000 + */ + connectTimeout?: number; + /** + * How many ms without a pong packet to consider the connection closed + * @default 5000 */ pingTimeout?: number; - /** - * How many milliseconds before sending a new ping packet (keep-alive) (engine.io) + * How many ms before sending a new ping packet * @default 25000 */ pingInterval?: number; - /** - * How many bytes or characters a message can be when polling, before closing the session - * (to avoid Dos) (engine.io) - * @default 10E7 + * How many ms before an uncompleted transport upgrade is cancelled + * @default 10000 + */ + upgradeTimeout?: number; + /** + * How many bytes or characters a message can be, before closing the session (to avoid DoS). + * @default 1e5 (100 KB) */ maxHttpBufferSize?: number; - /** - * Transports to allow connections to (engine.io) - * @default ['polling','websocket'] + * A function that receives a given handshake or upgrade request as its first parameter, + * and can decide whether to continue or not. The second argument is a function that needs + * to be called with the decided information: fn(err, success), where success is a boolean + * value where false means that the request is rejected, and err is an error code. */ - transports?: string[]; - + allowRequest?: ( + req: any, + fn: (err: string | null | undefined, success: boolean) => void, + ) => void; /** - * Whether to allow transport upgrades (engine.io) + * The low-level transports that are enabled + * @default ["polling", "websocket"] + */ + transports?: Transport[]; + /** + * Whether to allow transport upgrades * @default true */ allowUpgrades?: boolean; - /** - * parameters of the WebSocket permessage-deflate extension (see ws module). - * Set to false to disable (engine.io) + * Parameters of the WebSocket permessage-deflate extension (see ws module api docs). Set to false to disable. + * @default false + */ + perMessageDeflate?: boolean | object; + /** + * Parameters of the http compression for the polling transports (see zlib api docs). Set to false to disable. * @default true */ - perMessageDeflate?: Record | boolean; - + httpCompression?: boolean | object; /** - * Parameters of the http compression for the polling transports (see zlib). - * Set to false to disable, or set an object with parameter "threshold:number" - * to only compress data if the byte size is above this value (1024) (engine.io) - * @default true|1024 + * What WebSocket server implementation to use. Specified module must + * conform to the ws interface (see ws module api docs). Default value is ws. + * An alternative c++ addon is also available by installing uws module. */ - httpCompression?: Record | boolean; - + wsEngine?: string; /** - * Name of the HTTP cookie that contains the client sid to send as part of - * handshake response headers. Set to false to not send one (engine.io) - * @default "io" + * An optional packet which will be concatenated to the handshake packet emitted by Engine.IO. */ - cookie?: string | boolean; - + initialPacket?: any; /** - * Whether to let engine.io handle the OPTIONS requests. - * You can also pass a custom function to handle the requests + * Configuration of the cookie that contains the client sid to send as part of handshake response headers. This cookie + * might be used for sticky-session. Defaults to not sending any cookie. + * @default false + */ + cookie?: any | boolean; + /** + * The options that will be forwarded to the cors module + */ + cors?: CorsOptions; + /** + * Whether to enable compatibility with Socket.IO v2 clients + * @default false + */ + allowEIO3?: boolean; + /** + * Destroy unhandled upgrade requests * @default true */ - handlePreflightRequest?: ((req: any, res: any) => void) | boolean; + destroyUpgrade?: boolean; + /** + * Milliseconds after which unhandled requests are ended + * @default 1000 + */ + destroyUpgradeTimeout?: number; }