feature(websockets) extract web sockets drivers

This commit is contained in:
Kamil Myśliwiec
2018-12-12 23:35:27 +01:00
parent 03b0cf838a
commit 53687ba46a
59 changed files with 620 additions and 375 deletions

View File

@@ -18,6 +18,10 @@ const packages = {
'platform-fastify': ts.createProject( 'platform-fastify': ts.createProject(
'packages/platform-fastify/tsconfig.json', 'packages/platform-fastify/tsconfig.json',
), ),
'platform-socket.io': ts.createProject(
'packages/platform-socket.io/tsconfig.json',
),
'platform-ws': ts.createProject('packages/platform-ws/tsconfig.json'),
}; };
const modules = Object.keys(packages); const modules = Object.keys(packages);
const source = 'packages'; const source = 'packages';
@@ -42,7 +46,9 @@ gulp.task('copy-misc', function() {
.pipe(gulp.dest(`${source}/websockets`)) .pipe(gulp.dest(`${source}/websockets`))
.pipe(gulp.dest(`${source}/testing`)) .pipe(gulp.dest(`${source}/testing`))
.pipe(gulp.dest(`${source}/platform-fastify`)) .pipe(gulp.dest(`${source}/platform-fastify`))
.pipe(gulp.dest(`${source}/platform-express`)); .pipe(gulp.dest(`${source}/platform-express`))
.pipe(gulp.dest(`${source}/platform-ws`))
.pipe(gulp.dest(`${source}/platform-socket.io`));
}); });
gulp.task('clean:output', function() { gulp.task('clean:output', function() {

View File

@@ -1,19 +1,22 @@
import { INestApplication } from '@nestjs/common'; import {
import { INestFastifyApplication } from '@nestjs/common/interfaces/nest-fastify-application.interface'; FastifyAdapter,
import { FastifyAdapter } from '@nestjs/platform-fastify'; INestFastifyApplication,
} from '@nestjs/platform-fastify';
import { Test } from '@nestjs/testing'; import { Test } from '@nestjs/testing';
import { expect } from 'chai'; import { expect } from 'chai';
import { ApplicationModule } from '../src/app.module'; import { ApplicationModule } from '../src/app.module';
describe('Hello world (fastify adapter)', () => { describe('Hello world (fastify adapter)', () => {
let app: INestApplication & INestFastifyApplication; let app: INestFastifyApplication;
beforeEach(async () => { beforeEach(async () => {
const module = await Test.createTestingModule({ const module = await Test.createTestingModule({
imports: [ApplicationModule], imports: [ApplicationModule],
}).compile(); }).compile();
app = module.createNestApplication(new FastifyAdapter()); app = module.createNestApplication<INestFastifyApplication>(
new FastifyAdapter(),
);
await app.init(); await app.init();
}); });

View File

@@ -1,24 +1,30 @@
import * as request from 'supertest'; import {
import { Test } from '@nestjs/testing'; CallHandler,
import { INestApplication, Injectable } from '@nestjs/common'; ExecutionContext,
import { ApplicationModule } from '../src/app.module'; INestApplication,
Injectable,
NestInterceptor,
} from '@nestjs/common';
import { APP_INTERCEPTOR } from '@nestjs/core'; import { APP_INTERCEPTOR } from '@nestjs/core';
import { Test } from '@nestjs/testing';
import { of } from 'rxjs'; import { of } from 'rxjs';
import { map } from 'rxjs/operators'; import { map } from 'rxjs/operators';
import * as request from 'supertest';
import { ApplicationModule } from '../src/app.module';
const RETURN_VALUE = 'test'; const RETURN_VALUE = 'test';
@Injectable() @Injectable()
export class OverrideInterceptor { export class OverrideInterceptor implements NestInterceptor {
intercept(context, stream) { intercept(context: ExecutionContext, next: CallHandler) {
return of(RETURN_VALUE); return of(RETURN_VALUE);
} }
} }
@Injectable() @Injectable()
export class TransformInterceptor { export class TransformInterceptor {
intercept(context, stream) { intercept(context: ExecutionContext, next: CallHandler) {
return stream.pipe(map(data => ({ data }))); return next.handle().pipe(map(data => ({ data })));
} }
} }
@@ -41,7 +47,7 @@ describe('Interceptors', () => {
app = (await createTestModule( app = (await createTestModule(
new OverrideInterceptor(), new OverrideInterceptor(),
)).createNestApplication(); )).createNestApplication();
await app.init(); await app.init();
return request(app.getHttpServer()) return request(app.getHttpServer())
.get('/hello') .get('/hello')
@@ -52,7 +58,7 @@ describe('Interceptors', () => {
app = (await createTestModule( app = (await createTestModule(
new TransformInterceptor(), new TransformInterceptor(),
)).createNestApplication(); )).createNestApplication();
await app.init(); await app.init();
return request(app.getHttpServer()) return request(app.getHttpServer())
.get('/hello') .get('/hello')
@@ -63,7 +69,7 @@ describe('Interceptors', () => {
app = (await createTestModule( app = (await createTestModule(
new TransformInterceptor(), new TransformInterceptor(),
)).createNestApplication(); )).createNestApplication();
await app.init(); await app.init();
return request(app.getHttpServer()) return request(app.getHttpServer())
.get('/hello/stream') .get('/hello/stream')
@@ -74,7 +80,7 @@ describe('Interceptors', () => {
app = (await createTestModule( app = (await createTestModule(
new TransformInterceptor(), new TransformInterceptor(),
)).createNestApplication(); )).createNestApplication();
await app.init(); await app.init();
return request(app.getHttpServer()) return request(app.getHttpServer())
.get('/hello/async') .get('/hello/async')

View File

@@ -15,7 +15,6 @@ async function createNestApp(...gateways): Promise<INestApplication> {
} }
describe('WebSocketGateway', () => { describe('WebSocketGateway', () => {
const event = 'push';
let ws, app; let ws, app;
it(`should handle message (2nd port)`, async () => { it(`should handle message (2nd port)`, async () => {

View File

@@ -1,6 +1,6 @@
import { INestApplication } from '@nestjs/common'; import { INestApplication } from '@nestjs/common';
import { WsAdapter } from '@nestjs/platform-ws';
import { Test } from '@nestjs/testing'; import { Test } from '@nestjs/testing';
import { WsAdapter } from '@nestjs/websockets/adapters/ws-adapter';
import { expect } from 'chai'; import { expect } from 'chai';
import * as WebSocket from 'ws'; import * as WebSocket from 'ws';
import { ApplicationGateway } from '../src/app.gateway'; import { ApplicationGateway } from '../src/app.gateway';

63
package-lock.json generated
View File

@@ -1686,9 +1686,9 @@
"dev": true "dev": true
}, },
"buffer-from": { "buffer-from": {
"version": "1.0.0", "version": "1.1.1",
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.0.0.tgz", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz",
"integrity": "sha512-83apNb8KK0Se60UE1+4Ukbe3HbfELJ6UlI4ldtOGs7So4KD26orJM8hIY9lxdzP+UpItH1Yh/Y8GUvNFWFFRxA==", "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==",
"dev": true "dev": true
}, },
"buffer-more-ints": { "buffer-more-ints": {
@@ -10492,9 +10492,9 @@
} }
}, },
"make-error": { "make-error": {
"version": "1.3.4", "version": "1.3.5",
"resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.4.tgz", "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.5.tgz",
"integrity": "sha512-0Dab5btKVPhibSalc9QGXb559ED7G7iLjFXBaj9Wq8O3vorueR5K5jaE3hkG6ZQINyhA/JgG6Qk4qdFQjsYV6g==", "integrity": "sha512-c3sIjNUow0+8swNwVpqoH4YCShKNFkMaw6oH1mNS2haDZQqkeZFlHS3dhoeEbKKmJB4vXpJucU6oH75aDYeE9g==",
"dev": true "dev": true
}, },
"make-iterator": { "make-iterator": {
@@ -15448,47 +15448,21 @@
} }
}, },
"ts-node": { "ts-node": {
"version": "6.0.1", "version": "7.0.1",
"resolved": "https://registry.npmjs.org/ts-node/-/ts-node-6.0.1.tgz", "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-7.0.1.tgz",
"integrity": "sha512-LDS8WJRTEztvQEXbRK/0l/apWE0BNR/USGUhVcJDe2/hbNiB/v/lCqk6YJzgwKjScgdWOAhPQsfur7hmQ1Y1jA==", "integrity": "sha512-BVwVbPJRspzNh2yfslyT1PSbl5uIk03EZlb493RKHN4qej/D06n1cEhjlOJG69oFsE7OT8XjpTUcYf6pKTLMhw==",
"dev": true, "dev": true,
"requires": { "requires": {
"arrify": "^1.0.0", "arrify": "^1.0.0",
"chalk": "^2.3.0", "buffer-from": "^1.1.0",
"diff": "^3.1.0", "diff": "^3.1.0",
"make-error": "^1.1.1", "make-error": "^1.1.1",
"minimist": "^1.2.0", "minimist": "^1.2.0",
"mkdirp": "^0.5.1", "mkdirp": "^0.5.1",
"source-map-support": "^0.5.3", "source-map-support": "^0.5.6",
"yn": "^2.0.0" "yn": "^2.0.0"
}, },
"dependencies": { "dependencies": {
"ansi-styles": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
"integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
"dev": true,
"requires": {
"color-convert": "^1.9.0"
}
},
"chalk": {
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz",
"integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==",
"dev": true,
"requires": {
"ansi-styles": "^3.2.1",
"escape-string-regexp": "^1.0.5",
"supports-color": "^5.3.0"
}
},
"has-flag": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
"integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=",
"dev": true
},
"source-map": { "source-map": {
"version": "0.6.1", "version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
@@ -15496,23 +15470,14 @@
"dev": true "dev": true
}, },
"source-map-support": { "source-map-support": {
"version": "0.5.5", "version": "0.5.9",
"resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.5.tgz", "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.9.tgz",
"integrity": "sha512-mR7/Nd5l1z6g99010shcXJiNEaf3fEtmLhRB/sBcQVJGodcHCULPp2y4Sfa43Kv2zq7T+Izmfp/WHCR6dYkQCA==", "integrity": "sha512-gR6Rw4MvUlYy83vP0vxoVNzM6t8MUXqNuRsuBmBHQDu1Fh6X015FrLdgoDKcNdkwGubozq0P4N0Q37UyFVr1EA==",
"dev": true, "dev": true,
"requires": { "requires": {
"buffer-from": "^1.0.0", "buffer-from": "^1.0.0",
"source-map": "^0.6.0" "source-map": "^0.6.0"
} }
},
"supports-color": {
"version": "5.4.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz",
"integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==",
"dev": true,
"requires": {
"has-flag": "^3.0.0"
}
} }
} }
}, },

View File

@@ -121,7 +121,7 @@
"sinon-chai": "^2.8.0", "sinon-chai": "^2.8.0",
"socket.io-client": "^2.0.4", "socket.io-client": "^2.0.4",
"supertest": "^3.0.0", "supertest": "^3.0.0",
"ts-node": "^6.0.0", "ts-node": "^7.0.1",
"tslint": "^5.11.0", "tslint": "^5.11.0",
"typescript": "^3.2.2" "typescript": "^3.2.2"
}, },

View File

@@ -40,6 +40,7 @@ export {
ValidationError, ValidationError,
WebSocketAdapter, WebSocketAdapter,
WsExceptionFilter, WsExceptionFilter,
WsMessageHandler,
} from './interfaces'; } from './interfaces';
export * from './pipes'; export * from './pipes';
export * from './serializer'; export * from './serializer';

View File

@@ -1,16 +1,21 @@
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
export interface WebSocketAdapter<T = any> { export interface WsMessageHandler<T = string> {
create(port: number, options?: T): any; message: T;
bindClientConnect(server: any, callback: (...args: any[]) => void): any; callback: (...args: any[]) => Observable<any> | Promise<any>;
bindClientDisconnect?(client: any, callback: (...args: any[]) => void): any; }
export interface WebSocketAdapter<
TServer = any,
TClient = any,
TOptions = any
> {
create(port: number, options?: TOptions): TServer;
bindClientConnect(server: TServer, callback: Function): any;
bindClientDisconnect?(client: TClient, callback: Function): any;
bindMessageHandlers( bindMessageHandlers(
client: any, client: TClient,
handlers: Array<{ handlers: WsMessageHandler[],
message: any;
callback: (...args: any[]) => Observable<any> | Promise<any> | any;
}>,
transform: (data: any) => Observable<any>, transform: (data: any) => Observable<any>,
): any; ): any;
close(server: any): any; close(server: TServer): any;
} }

View File

@@ -3,8 +3,12 @@ import { RequestHandler } from '@nestjs/common/interfaces';
import { CorsOptions } from '@nestjs/common/interfaces/external/cors-options.interface'; import { CorsOptions } from '@nestjs/common/interfaces/external/cors-options.interface';
import { NestApplicationOptions } from '@nestjs/common/interfaces/nest-application-options.interface'; import { NestApplicationOptions } from '@nestjs/common/interfaces/nest-application-options.interface';
export abstract class AbstractHttpAdapter<T = any> implements HttpServer { export abstract class AbstractHttpAdapter<
protected httpServer: T; TServer = any,
TRequest = any,
TResponse = any
> implements HttpServer<TRequest, TResponse> {
protected httpServer: TServer;
constructor(protected readonly instance: any) {} constructor(protected readonly instance: any) {}
@@ -60,11 +64,11 @@ export abstract class AbstractHttpAdapter<T = any> implements HttpServer {
return this.instance.listen(port, hostname, callback); return this.instance.listen(port, hostname, callback);
} }
public getHttpServer(): T { public getHttpServer(): TServer {
return this.httpServer as T; return this.httpServer as TServer;
} }
public setHttpServer(httpServer: T) { public setHttpServer(httpServer: TServer) {
this.httpServer = httpServer; this.httpServer = httpServer;
} }

View File

@@ -29,12 +29,12 @@ export class ExceptionsHandler extends BaseExceptionFilter {
if (isEmpty(this.filters)) return false; if (isEmpty(this.filters)) return false;
const filter = this.filters.find(({ exceptionMetatypes }) => { const filter = this.filters.find(({ exceptionMetatypes }) => {
const hasMetatype = const typeExists =
!exceptionMetatypes.length || !exceptionMetatypes.length ||
exceptionMetatypes.some( exceptionMetatypes.some(
ExceptionMetatype => exception instanceof ExceptionMetatype, ExceptionMetatype => exception instanceof ExceptionMetatype,
); );
return hasMetatype; return typeExists;
}); });
filter && filter.func(exception, ctx); filter && filter.func(exception, ctx);
return !!filter; return !!filter;

View File

@@ -2,7 +2,7 @@ import { CanActivate } from '@nestjs/common';
import { Controller } from '@nestjs/common/interfaces'; import { Controller } from '@nestjs/common/interfaces';
import { isEmpty } from '@nestjs/common/utils/shared.utils'; import { isEmpty } from '@nestjs/common/utils/shared.utils';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import { ExecutionContextHost } from '../helpers/execution-context.host'; import { ExecutionContextHost } from '../helpers/execution-context-host';
export class GuardsConsumer { export class GuardsConsumer {
public async tryActivate( public async tryActivate(

View File

@@ -0,0 +1,18 @@
import { Logger } from '@nestjs/common';
const MISSING_REQUIRED_DEPENDENCY = (
defaultPlatform: string,
transport: string,
) =>
`No driver (${transport}) has been selected. In order to take advantage of the default driver, please, ensure to install the "${defaultPlatform}" package ($ npm install ${defaultPlatform}).`;
const logger = new Logger('PackageLoader');
export function loadAdapter(defaultPlatform: string, transport: string) {
try {
return require(defaultPlatform);
} catch (e) {
logger.error(MISSING_REQUIRED_DEPENDENCY(defaultPlatform, transport));
process.exit(1);
}
}

View File

@@ -3,7 +3,7 @@ import { CallHandler, Controller } from '@nestjs/common/interfaces';
import { isEmpty } from '@nestjs/common/utils/shared.utils'; import { isEmpty } from '@nestjs/common/utils/shared.utils';
import { defer, from as fromPromise, Observable } from 'rxjs'; import { defer, from as fromPromise, Observable } from 'rxjs';
import { mergeAll, switchMap } from 'rxjs/operators'; import { mergeAll, switchMap } from 'rxjs/operators';
import { ExecutionContextHost } from '../helpers/execution-context.host'; import { ExecutionContextHost } from '../helpers/execution-context-host';
export class InterceptorsConsumer { export class InterceptorsConsumer {
public async intercept( public async intercept(

View File

@@ -18,6 +18,7 @@ import iterate from 'iterare';
import * as optional from 'optional'; import * as optional from 'optional';
import { ApplicationConfig } from './application-config'; import { ApplicationConfig } from './application-config';
import { MESSAGES } from './constants'; import { MESSAGES } from './constants';
import { loadAdapter } from './helpers/load-adapter';
import { NestContainer } from './injector/container'; import { NestContainer } from './injector/container';
import { MiddlewareContainer } from './middleware/container'; import { MiddlewareContainer } from './middleware/container';
import { MiddlewareModule } from './middleware/middleware-module'; import { MiddlewareModule } from './middleware/middleware-module';
@@ -29,8 +30,6 @@ const { SocketModule } =
optional('@nestjs/websockets/socket-module') || ({} as any); optional('@nestjs/websockets/socket-module') || ({} as any);
const { MicroservicesModule } = const { MicroservicesModule } =
optional('@nestjs/microservices/microservices-module') || ({} as any); optional('@nestjs/microservices/microservices-module') || ({} as any);
const { IoAdapter } =
optional('@nestjs/websockets/adapters/io-adapter') || ({} as any);
export class NestApplication extends NestApplicationContext export class NestApplication extends NestApplicationContext
implements INestApplication { implements INestApplication {
@@ -68,6 +67,7 @@ export class NestApplication extends NestApplicationContext
public registerHttpServer() { public registerHttpServer() {
this.httpServer = this.createServer(); this.httpServer = this.createServer();
const { IoAdapter } = optional('@nestjs/platform-socket.io') || ({} as any);
const ioAdapter = IoAdapter ? new IoAdapter(this.httpServer) : null; const ioAdapter = IoAdapter ? new IoAdapter(this.httpServer) : null;
this.config.setIoAdapter(ioAdapter); this.config.setIoAdapter(ioAdapter);
} }
@@ -93,8 +93,7 @@ export class NestApplication extends NestApplicationContext
} }
public async registerModules() { public async registerModules() {
this.socketModule && this.registerWsModule();
this.socketModule.register(this.container, this.config);
if (this.microservicesModule) { if (this.microservicesModule) {
this.microservicesModule.register(this.container, this.config); this.microservicesModule.register(this.container, this.config);
@@ -107,6 +106,17 @@ export class NestApplication extends NestApplicationContext
); );
} }
public registerWsModule() {
if (!this.socketModule) {
return;
}
const adapter = this.config.getIoAdapter();
if (!adapter) {
return loadAdapter('@nestjs/platform-socket.io', 'WebSockets');
}
this.socketModule.register(this.container, this.config);
}
public async init(): Promise<this> { public async init(): Promise<this> {
const useBodyParser = const useBodyParser =
this.appOptions && this.appOptions.bodyParser !== false; this.appOptions && this.appOptions.bodyParser !== false;

View File

@@ -14,6 +14,7 @@ import { isFunction, isNil } from '@nestjs/common/utils/shared.utils';
import { ApplicationConfig } from './application-config'; import { ApplicationConfig } from './application-config';
import { MESSAGES } from './constants'; import { MESSAGES } from './constants';
import { ExceptionsZone } from './errors/exceptions-zone'; import { ExceptionsZone } from './errors/exceptions-zone';
import { loadAdapter } from './helpers/load-adapter';
import { NestContainer } from './injector/container'; import { NestContainer } from './injector/container';
import { InstanceLoader } from './injector/instance-loader'; import { InstanceLoader } from './injector/instance-loader';
import { MetadataScanner } from './metadata-scanner'; import { MetadataScanner } from './metadata-scanner';
@@ -58,8 +59,8 @@ export class NestFactoryStatic {
applicationConfig, applicationConfig,
appOptions, appOptions,
); );
const target = this.createAdapterProxy<T>(instance, httpServer); const target = this.createNestInstance(instance);
return this.createNestInstance<T>(target); return this.createAdapterProxy<T>(target, httpServer);
} }
/** /**
@@ -155,18 +156,25 @@ export class NestFactoryStatic {
return; return;
} }
if (isFunction(receiver[prop])) { if (isFunction(receiver[prop])) {
return (...args: any[]) => { return this.createExceptionZone(receiver, prop);
let result;
ExceptionsZone.run(() => {
result = receiver[prop](...args);
});
return result;
};
} }
return receiver[prop]; return receiver[prop];
}; };
} }
private createExceptionZone(
receiver: Record<string, any>,
prop: string,
): Function {
return (...args: unknown[]) => {
let result;
ExceptionsZone.run(() => {
result = receiver[prop](...args);
});
return result;
};
}
private applyLogger(options: NestApplicationContextOptions | undefined) { private applyLogger(options: NestApplicationContextOptions | undefined) {
if (!options) { if (!options) {
return; return;
@@ -175,10 +183,7 @@ export class NestFactoryStatic {
} }
private createHttpAdapter<T = any>(httpServer?: T): HttpServer { private createHttpAdapter<T = any>(httpServer?: T): HttpServer {
const { ExpressAdapter } = loadPackage( const { ExpressAdapter } = loadAdapter('@nestjs/platform-express', 'HTTP');
'@nestjs/platform-express',
'NestFactory',
);
return new ExpressAdapter(httpServer); return new ExpressAdapter(httpServer);
} }
@@ -192,11 +197,11 @@ export class NestFactoryStatic {
return (new Proxy(app, { return (new Proxy(app, {
get: (receiver: Record<string, any>, prop: string) => { get: (receiver: Record<string, any>, prop: string) => {
if (!(prop in receiver) && prop in adapter) { if (!(prop in receiver) && prop in adapter) {
return adapter[prop]; return this.createExceptionZone(receiver, prop);
} }
return receiver[prop]; return receiver[prop];
}, },
}) as any) as T; }) as unknown) as T;
} }
} }

View File

@@ -1,5 +1,5 @@
import { ExceptionsHandler } from '../exceptions/exceptions-handler'; import { ExceptionsHandler } from '../exceptions/exceptions-handler';
import { ExecutionContextHost } from '../helpers/execution-context.host'; import { ExecutionContextHost } from '../helpers/execution-context-host';
export type RouterProxyCallback = <TRequest, TResponse>( export type RouterProxyCallback = <TRequest, TResponse>(
req?: TRequest, req?: TRequest,

View File

@@ -7,7 +7,7 @@ import { Logger } from '../../../common/services/logger.service';
import { AbstractHttpAdapter } from '../../adapters'; import { AbstractHttpAdapter } from '../../adapters';
import { InvalidExceptionFilterException } from '../../errors/exceptions/invalid-exception-filter.exception'; import { InvalidExceptionFilterException } from '../../errors/exceptions/invalid-exception-filter.exception';
import { ExceptionsHandler } from '../../exceptions/exceptions-handler'; import { ExceptionsHandler } from '../../exceptions/exceptions-handler';
import { ExecutionContextHost } from '../../helpers/execution-context.host'; import { ExecutionContextHost } from '../../helpers/execution-context-host';
import { NoopHttpAdapter } from './../utils/noop-adapter'; import { NoopHttpAdapter } from './../utils/noop-adapter';
describe('ExceptionsHandler', () => { describe('ExceptionsHandler', () => {

View File

@@ -1,6 +1,6 @@
import * as sinon from 'sinon'; import * as sinon from 'sinon';
import { expect } from 'chai'; import { expect } from 'chai';
import { ExecutionContextHost } from '../../helpers/execution-context.host'; import { ExecutionContextHost } from '../../helpers/execution-context-host';
describe('ExecutionContextHost', () => { describe('ExecutionContextHost', () => {
let contextHost: ExecutionContextHost; let contextHost: ExecutionContextHost;

View File

@@ -73,7 +73,7 @@ describe('ExternalContextCreator', () => {
describe('when can not activate', () => { describe('when can not activate', () => {
it('should throw exception when "tryActivate" returns false', () => { it('should throw exception when "tryActivate" returns false', () => {
sinon.stub(guardsConsumer, 'tryActivate', () => false); sinon.stub(guardsConsumer, 'tryActivate', () => false);
expect(proxyContext(1, 2, 3)).to.eventually.throw; proxyContext(1, 2, 3).catch(err => expect(err).to.not.be.undefined);
}); });
}); });
describe('when can activate', () => { describe('when can activate', () => {

View File

@@ -127,7 +127,9 @@ describe('RouterExecutionContext', () => {
}); });
it('should throw exception when "tryActivate" returns false', () => { it('should throw exception when "tryActivate" returns false', () => {
sinon.stub(guardsConsumer, 'tryActivate').callsFake(() => false); sinon.stub(guardsConsumer, 'tryActivate').callsFake(() => false);
expect(proxyContext(request, response, next)).to.eventually.throw(); proxyContext(request, response, next).catch(
error => expect(error).to.not.be.undefined,
);
}); });
}); });
}); });
@@ -257,7 +259,7 @@ describe('RouterExecutionContext', () => {
it('should throw exception when "tryActivate" returns false', () => { it('should throw exception when "tryActivate" returns false', () => {
const guardsFn = contextCreator.createGuardsFn([null], null, null); const guardsFn = contextCreator.createGuardsFn([null], null, null);
sinon.stub(guardsConsumer, 'tryActivate').callsFake(() => false); sinon.stub(guardsConsumer, 'tryActivate').callsFake(() => false);
expect(guardsFn([])).to.eventually.throw(); guardsFn([]).catch(err => expect(err).to.not.be.undefined);
}); });
}); });
describe('createHandleResponseFn', () => { describe('createHandleResponseFn', () => {

View File

@@ -165,9 +165,9 @@ describe('DependenciesScanner', () => {
}); });
describe('when "related" is nil', () => { describe('when "related" is nil', () => {
it('should throw exception', () => { it('should throw exception', () => {
expect( scanner
scanner.insertRelatedModule(undefined, [] as any, 'test'), .insertRelatedModule(undefined, [] as any, 'test')
).to.eventually.throws(); .catch(err => expect(err).to.not.be.undefined);
}); });
}); });
}); });

View File

@@ -1,5 +1,5 @@
import { isFunction } from '@nestjs/common/utils/shared.utils'; import { isFunction } from '@nestjs/common/utils/shared.utils';
import { ExecutionContextHost } from '@nestjs/core/helpers/execution-context.host'; import { ExecutionContextHost } from '@nestjs/core/helpers/execution-context-host';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import { catchError } from 'rxjs/operators'; import { catchError } from 'rxjs/operators';
import { RpcExceptionsHandler } from '../exceptions/rpc-exceptions-handler'; import { RpcExceptionsHandler } from '../exceptions/rpc-exceptions-handler';

View File

@@ -21,8 +21,6 @@ import { ServerFactory } from './server/server-factory';
const { SocketModule } = const { SocketModule } =
optional('@nestjs/websockets/socket-module') || ({} as any); optional('@nestjs/websockets/socket-module') || ({} as any);
const { IoAdapter } =
optional('@nestjs/websockets/adapters/io-adapter') || ({} as any);
export class NestMicroservice extends NestApplicationContext export class NestMicroservice extends NestApplicationContext
implements INestMicroservice { implements INestMicroservice {
@@ -49,6 +47,7 @@ export class NestMicroservice extends NestApplicationContext
} }
public registerWsAdapter() { public registerWsAdapter() {
const { IoAdapter } = optional('@nestjs/platform-socket.io') || ({} as any);
const ioAdapter = IoAdapter ? new IoAdapter() : null; const ioAdapter = IoAdapter ? new IoAdapter() : null;
this.applicationConfig.setIoAdapter(ioAdapter); this.applicationConfig.setIoAdapter(ioAdapter);
} }

View File

@@ -1,3 +1,4 @@
import { Logger } from '@nestjs/common';
import { expect } from 'chai'; import { expect } from 'chai';
import { join } from 'path'; import { join } from 'path';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
@@ -7,6 +8,11 @@ import { InvalidGrpcPackageException } from '../../errors/invalid-grpc-package.e
import { InvalidGrpcServiceException } from '../../errors/invalid-grpc-service.exception'; import { InvalidGrpcServiceException } from '../../errors/invalid-grpc-service.exception';
import { InvalidProtoDefinitionException } from '../../errors/invalid-proto-definition.exception'; import { InvalidProtoDefinitionException } from '../../errors/invalid-proto-definition.exception';
// tslint:disable:no-string-literal // tslint:disable:no-string-literal
class NoopLogger extends Logger {
log(message: any, context?: string): void {}
error(message: any, trace?: string, context?: string): void {}
warn(message: any, context?: string): void {}
}
class GrpcService { class GrpcService {
test = null; test = null;
@@ -188,9 +194,13 @@ describe('ClientGrpcProxy', () => {
describe('when package does not exist', () => { describe('when package does not exist', () => {
it('should throw "InvalidGrpcPackageException"', () => { it('should throw "InvalidGrpcPackageException"', () => {
sinon.stub(client, 'lookupPackage').callsFake(() => null); sinon.stub(client, 'lookupPackage').callsFake(() => null);
expect(() => client.createClient()).to.throw( (client as any).logger = new NoopLogger();
InvalidGrpcPackageException,
); try {
client.createClient();
} catch (err) {
expect(err).to.be.instanceof(InvalidGrpcPackageException);
}
}); });
}); });
}); });
@@ -201,6 +211,7 @@ describe('ClientGrpcProxy', () => {
sinon.stub(client, 'getOptionsProp').callsFake(() => { sinon.stub(client, 'getOptionsProp').callsFake(() => {
throw new Error(); throw new Error();
}); });
(client as any).logger = new NoopLogger();
expect(() => client.loadProto()).to.throws( expect(() => client.loadProto()).to.throws(
InvalidProtoDefinitionException, InvalidProtoDefinitionException,
); );
@@ -231,7 +242,7 @@ describe('ClientGrpcProxy', () => {
describe('connect', () => { describe('connect', () => {
it('should throw exception', () => { it('should throw exception', () => {
expect(client.connect()).to.eventually.throws(Error); client.connect().catch(error => expect(error).to.be.instanceof(Error));
}); });
}); });
}); });

View File

@@ -133,7 +133,9 @@ describe('RpcContextCreator', () => {
); );
const data = 'test'; const data = 'test';
expect(proxy(data)).to.eventually.rejectedWith(RpcException); proxy(null, data).catch(err =>
expect(err).to.be.instanceOf(RpcException),
);
}); });
}); });
}); });
@@ -167,7 +169,7 @@ describe('RpcContextCreator', () => {
it('should throw exception when "tryActivate" returns false', () => { it('should throw exception when "tryActivate" returns false', () => {
const guardsFn = contextCreator.createGuardsFn([null], null, null); const guardsFn = contextCreator.createGuardsFn([null], null, null);
sinon.stub(guardsConsumer, 'tryActivate').callsFake(() => false); sinon.stub(guardsConsumer, 'tryActivate').callsFake(() => false);
expect(guardsFn([])).to.eventually.throw(); guardsFn([]).catch(err => expect(err).to.not.be.undefined);
}); });
}); });
}); });

View File

@@ -1,3 +1,4 @@
import { Logger } from '@nestjs/common';
import { expect } from 'chai'; import { expect } from 'chai';
import { join } from 'path'; import { join } from 'path';
import { of } from 'rxjs'; import { of } from 'rxjs';
@@ -5,6 +6,12 @@ import * as sinon from 'sinon';
import { InvalidGrpcPackageException } from '../../errors/invalid-grpc-package.exception'; import { InvalidGrpcPackageException } from '../../errors/invalid-grpc-package.exception';
import { ServerGrpc } from '../../server/server-grpc'; import { ServerGrpc } from '../../server/server-grpc';
class NoopLogger extends Logger {
log(message: any, context?: string): void {}
error(message: any, trace?: string, context?: string): void {}
warn(message: any, context?: string): void {}
}
describe('ServerGrpc', () => { describe('ServerGrpc', () => {
let server: ServerGrpc; let server: ServerGrpc;
beforeEach(() => { beforeEach(() => {
@@ -42,11 +49,14 @@ describe('ServerGrpc', () => {
describe('bindEvents', () => { describe('bindEvents', () => {
describe('when package does not exist', () => { describe('when package does not exist', () => {
it('should throw "InvalidGrpcPackageException"', () => { it('should throw "InvalidGrpcPackageException"', async () => {
sinon.stub(server, 'lookupPackage').callsFake(() => null); sinon.stub(server, 'lookupPackage').callsFake(() => null);
expect(server.bindEvents()).to.eventually.throws( (server as any).logger = new NoopLogger();
InvalidGrpcPackageException, try {
); await server.bindEvents();
} catch (err) {
expect(err).to.be.instanceof(InvalidGrpcPackageException);
}
}); });
}); });
describe('when package exist', () => { describe('when package exist', () => {

View File

@@ -10,7 +10,6 @@
"url": "https://github.com/nestjs/nest" "url": "https://github.com/nestjs/nest"
}, },
"dependencies": { "dependencies": {
"@types/express": "^4.0.39",
"body-parser": "1.18.3", "body-parser": "1.18.3",
"cors": "2.8.4", "cors": "2.8.4",
"express": "4.16.3", "express": "4.16.3",

View File

@@ -1,5 +1,5 @@
import { CallHandler } from '@nestjs/common'; import { CallHandler } from '@nestjs/common';
import { ExecutionContextHost } from '@nestjs/core/helpers/execution-context.host'; import { ExecutionContextHost } from '@nestjs/core/helpers/execution-context-host';
import { expect } from 'chai'; import { expect } from 'chai';
import { of } from 'rxjs'; import { of } from 'rxjs';
import * as sinon from 'sinon'; import * as sinon from 'sinon';
@@ -57,9 +57,9 @@ describe('FileFieldsInterceptor', () => {
(target as any).fields = { (target as any).fields = {
array: () => callback, array: () => callback,
}; };
expect( (target.intercept(new ExecutionContextHost([]), handler) as any).catch(
target.intercept(new ExecutionContextHost([]), handler), error => expect(error).to.not.be.undefined,
).to.eventually.throw(); );
}); });
}); });
}); });

View File

@@ -1,5 +1,5 @@
import { CallHandler } from '@nestjs/common'; import { CallHandler } from '@nestjs/common';
import { ExecutionContextHost } from '@nestjs/core/helpers/execution-context.host'; import { ExecutionContextHost } from '@nestjs/core/helpers/execution-context-host';
import { expect } from 'chai'; import { expect } from 'chai';
import { of } from 'rxjs'; import { of } from 'rxjs';
import * as sinon from 'sinon'; import * as sinon from 'sinon';
@@ -39,9 +39,9 @@ describe('FileInterceptor', () => {
(target as any).multer = { (target as any).multer = {
single: () => callback, single: () => callback,
}; };
expect( (target.intercept(new ExecutionContextHost([]), handler) as any).catch(
target.intercept(new ExecutionContextHost([]), handler), error => expect(error).to.not.be.undefined,
).to.eventually.throw(); );
}); });
}); });
}); });

View File

@@ -1,5 +1,5 @@
import { CallHandler } from '@nestjs/common'; import { CallHandler } from '@nestjs/common';
import { ExecutionContextHost } from '@nestjs/core/helpers/execution-context.host'; import { ExecutionContextHost } from '@nestjs/core/helpers/execution-context-host';
import { expect } from 'chai'; import { expect } from 'chai';
import { of } from 'rxjs'; import { of } from 'rxjs';
import * as sinon from 'sinon'; import * as sinon from 'sinon';
@@ -41,9 +41,9 @@ describe('FilesInterceptor', () => {
(target as any).multer = { (target as any).multer = {
array: () => callback, array: () => callback,
}; };
expect( (target.intercept(new ExecutionContextHost([]), handler) as any).catch(
target.intercept(new ExecutionContextHost([]), handler), error => expect(error).to.not.be.undefined,
).to.eventually.throw(); );
}); });
}); });
}); });

View File

@@ -1,5 +1,4 @@
import { RequestMethod } from '@nestjs/common'; import { RequestMethod } from '@nestjs/common';
import { ErrorHandler, RequestHandler } from '@nestjs/common/interfaces';
import { CorsOptions } from '@nestjs/common/interfaces/external/cors-options.interface'; import { CorsOptions } from '@nestjs/common/interfaces/external/cors-options.interface';
import { NestApplicationOptions } from '@nestjs/common/interfaces/nest-application-options.interface'; import { NestApplicationOptions } from '@nestjs/common/interfaces/nest-application-options.interface';
import { loadPackage } from '@nestjs/common/utils/load-package.util'; import { loadPackage } from '@nestjs/common/utils/load-package.util';
@@ -24,54 +23,6 @@ export class FastifyAdapter extends AbstractHttpAdapter {
super(instance); super(instance);
} }
public use(handler: RequestHandler | ErrorHandler);
public use(path: any, handler: RequestHandler | ErrorHandler);
public use(...args: any[]) {
return this.instance.use(...args);
}
public get(handler: RequestHandler);
public get(path: any, handler: RequestHandler);
public get(...args: any[]) {
return this.instance.get(...args);
}
public post(handler: RequestHandler);
public post(path: any, handler: RequestHandler);
public post(...args: any[]) {
return this.instance.post(...args);
}
public head(handler: RequestHandler);
public head(path: any, handler: RequestHandler);
public head(...args: any[]) {
return this.instance.head(...args);
}
public delete(handler: RequestHandler);
public delete(path: any, handler: RequestHandler);
public delete(...args: any[]) {
return this.instance.delete(...args);
}
public put(handler: RequestHandler);
public put(path: any, handler: RequestHandler);
public put(...args: any[]) {
return this.instance.put(...args);
}
public patch(handler: RequestHandler);
public patch(path: any, handler: RequestHandler);
public patch(...args: any[]) {
return this.instance.patch(...args);
}
public options(handler: RequestHandler);
public options(path: any, handler: RequestHandler);
public options(...args: any[]) {
return this.instance.options(...args);
}
public listen(port: string | number, callback?: () => void); public listen(port: string | number, callback?: () => void);
public listen(port: string | number, hostname: string, callback?: () => void); public listen(port: string | number, hostname: string, callback?: () => void);
public listen(port: any, hostname?: any, callback?: any) { public listen(port: any, hostname?: any, callback?: any) {

View File

@@ -0,0 +1,76 @@
<p align="center">
<a href="http://nestjs.com/" target="blank"><img src="https://nestjs.com/img/logo_text.svg" width="320" alt="Nest Logo" /></a>
</p>
[travis-image]: https://api.travis-ci.org/nestjs/nest.svg?branch=master
[travis-url]: https://travis-ci.org/nestjs/nest
[linux-image]: https://img.shields.io/travis/nestjs/nest/master.svg?label=linux
[linux-url]: https://travis-ci.org/nestjs/nest
<p align="center">A progressive <a href="http://nodejs.org" target="blank">Node.js</a> framework for building efficient and scalable server-side applications, heavily inspired by <a href="https://angular.io" target="blank">Angular</a>.</p>
<p align="center">
<a href="https://www.npmjs.com/~nestjscore"><img src="https://img.shields.io/npm/v/@nestjs/core.svg" alt="NPM Version" /></a>
<a href="https://www.npmjs.com/~nestjscore"><img src="https://img.shields.io/npm/l/@nestjs/core.svg" alt="Package License" /></a>
<a href="https://www.npmjs.com/~nestjscore"><img src="https://img.shields.io/npm/dm/@nestjs/core.svg" alt="NPM Downloads" /></a>
<a href="https://travis-ci.org/nestjs/nest"><img src="https://api.travis-ci.org/nestjs/nest.svg?branch=master" alt="Travis" /></a>
<a href="https://travis-ci.org/nestjs/nest"><img src="https://img.shields.io/travis/nestjs/nest/master.svg?label=linux" alt="Linux" /></a>
<a href="https://coveralls.io/github/nestjs/nest?branch=master"><img src="https://coveralls.io/repos/github/nestjs/nest/badge.svg?branch=master#8" alt="Coverage" /></a>
<a href="https://gitter.im/nestjs/nestjs?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=body_badge"><img src="https://badges.gitter.im/nestjs/nestjs.svg" alt="Gitter" /></a>
<a href="https://opencollective.com/nest#backer"><img src="https://opencollective.com/nest/backers/badge.svg" alt="Backers on Open Collective" /></a>
<a href="https://opencollective.com/nest#sponsor"><img src="https://opencollective.com/nest/sponsors/badge.svg" alt="Sponsors on Open Collective" /></a>
<a href="https://paypal.me/kamilmysliwiec"><img src="https://img.shields.io/badge/Donate-PayPal-ff3f59.svg"/></a>
<a href="https://twitter.com/nestframework"><img src="https://img.shields.io/twitter/follow/nestframework.svg?style=social&label=Follow"></a>
</p>
<!--[![Backers on Open Collective](https://opencollective.com/nest/backers/badge.svg)](https://opencollective.com/nest#backer)
[![Sponsors on Open Collective](https://opencollective.com/nest/sponsors/badge.svg)](https://opencollective.com/nest#sponsor)-->
## Description
<p>Nest is a framework for building efficient, scalable <a href="http://nodejs.org" target="_blank">Node.js</a> server-side applications. It uses modern JavaScript, is built with <a href="http://www.typescriptlang.org" target="_blank">TypeScript</a> (preserves compatibility with pure JavaScript) and combines elements of OOP (Object Oriented Programming), FP (Functional Programming), and FRP (Functional Reactive Programming).</p>
<p>Under the hood, Nest makes use of <a href="https://expressjs.com/" target="_blank">Express</a>, but also, provides compatibility with a wide range of other libraries, like e.g. <a href="https://github.com/fastify/fastify" target="blank">Fastify</a>, allowing for easy use of the myriad third-party plugins which are available.</p>
## Philosophy
<p>In recent years, thanks to Node.js, JavaScript has become the “lingua franca” of the web for both front and backend applications, giving rise to awesome projects like <a href="https://angular.io/" target="_blank">Angular</a>, <a href="https://github.com/facebook/react" target="_blank">React</a> and <a href="https://github.com/vuejs/vue" target="_blank">Vue</a> which improve developer productivity and enable the construction of fast, testable, extensible frontend applications. However, on the server-side, while there are a lot of superb libraries, helpers and tools for Node, none of them effectively solve the main problem - the architecture.</p>
<p>Nest aims to provide an application architecture out of the box which allows for effortless creation of highly testable, scalable, loosely coupled and easily maintainable applications.</p>
## Getting started
* To check out the [guide](https://docs.nestjs.com), visit [docs.nestjs.com](https://docs.nestjs.com). :books:
* 要查看中文 [指南](readme_zh.md), 请访问 [docs.nestjs.cn](https://docs.nestjs.cn). :books:
## Support
Nest is an MIT-licensed open source project. It can grow thanks to the sponsors and support by the amazing backers. If you'd like to join them, please [read more here](https://docs.nestjs.com/support).
#### Principal Sponsor
<a href="https://valor-software.com/"><img src="https://docs.nestjs.com/assets/sponsors/valor-software.png" width="320" /></a>
#### Gold Sponsors
<a href="http://xtremis.com/"><img src="https://nestjs.com/img/logo-xtremis.svg" width="220" /></a>
#### Silver Sponsors
<a href="https://neoteric.eu/"><img src="https://nestjs.com/img/neoteric-cut.png" width="120" /></a> &nbsp;
<a href="http://gojob.com"><img src="http://nestjs.com/img/gojob-logo.png" valign="bottom" height="95" /></a> &nbsp; <a href="https://www.swingdev.io"><img src="https://nestjs.com/img/swingdev-logo.svg#1" width="150" /> </a>
#### Sponsors
<a href="https://scal.io"><img src="https://nestjs.com/img/scalio-logo.svg" width="110" /></a> &nbsp; <a href="http://angularity.io"><img src="http://angularity.io/media/logo.svg" height="30" /></a> &nbsp; <!--<a href="https://keycdn.com"><img src="https://nestjs.com/img/keycdn.svg" height="30" /></a> &nbsp;--> <a href="https://hostpresto.com"><img src="https://nestjs.com/img/hostpresto.png" height="30" /></a> &nbsp; <a href="https://genuinebee.com/"><img src="https://nestjs.com/img/genuinebee.svg" height="38" /></a> &nbsp; <a href="http://architectnow.net/"><img src="https://nestjs.com/img/architectnow.png" height="24" /></a> &nbsp; <a href="https://quander.io/"><img src="https://nestjs.com/img/quander.png" height="28" /></a>
## Backers
<a href="https://opencollective.com/nest"><img src="https://opencollective.com/nest/backers.svg?width=890"></a>
## Stay in touch
* Author - [Kamil Myśliwiec](https://kamilmysliwiec.com)
* Website - [https://nestjs.com](https://nestjs.com/)
* Twitter - [@nestframework](https://twitter.com/nestframework)
## License
Nest is [MIT licensed](LICENSE).

View File

@@ -0,0 +1 @@
export * from './io-adapter';

View File

@@ -1,26 +1,14 @@
import { INestApplicationContext, WebSocketAdapter } from '@nestjs/common';
import { isFunction, isNil } from '@nestjs/common/utils/shared.utils'; import { isFunction, isNil } from '@nestjs/common/utils/shared.utils';
import { NestApplication } from '@nestjs/core'; import {
import { Server } from 'http'; AbstractWsAdapter,
MessageMappingProperties,
} from '@nestjs/websockets';
import { DISCONNECT_EVENT } from '@nestjs/websockets/constants';
import { fromEvent, Observable } from 'rxjs'; import { fromEvent, Observable } from 'rxjs';
import { filter, first, map, mergeMap, share, takeUntil } from 'rxjs/operators'; import { filter, first, map, mergeMap, share, takeUntil } from 'rxjs/operators';
import * as io from 'socket.io'; import * as io from 'socket.io';
import { CONNECTION_EVENT, DISCONNECT_EVENT } from '../constants';
import { MessageMappingProperties } from '../gateway-metadata-explorer';
import { BaseWsExceptionFilter } from './../exceptions/base-ws-exception-filter';
export class IoAdapter implements WebSocketAdapter {
protected readonly baseExceptionFilter = new BaseWsExceptionFilter();
protected readonly httpServer: Server;
constructor(appOrHttpServer?: INestApplicationContext | Server) {
if (appOrHttpServer && appOrHttpServer instanceof NestApplication) {
this.httpServer = appOrHttpServer.getUnderlyingHttpServer();
} else {
this.httpServer = appOrHttpServer as Server;
}
}
export class IoAdapter extends AbstractWsAdapter {
public create( public create(
port: number, port: number,
options?: any & { namespace?: string; server?: any }, options?: any & { namespace?: string; server?: any },
@@ -32,8 +20,8 @@ export class IoAdapter implements WebSocketAdapter {
return server && isFunction(server.of) return server && isFunction(server.of)
? server.of(namespace) ? server.of(namespace)
: namespace : namespace
? this.createIOServer(port, opt).of(namespace) ? this.createIOServer(port, opt).of(namespace)
: this.createIOServer(port, opt); : this.createIOServer(port, opt);
} }
public createIOServer(port: number, options?: any): any { public createIOServer(port: number, options?: any): any {
@@ -43,14 +31,6 @@ export class IoAdapter implements WebSocketAdapter {
return io(port, options); return io(port, options);
} }
public bindClientConnect(server: any, callback: (...args: any[]) => void) {
server.on(CONNECTION_EVENT, callback);
}
public bindClientDisconnect(client: any, callback: (...args: any[]) => void) {
client.on(DISCONNECT_EVENT, callback);
}
public bindMessageHandlers( public bindMessageHandlers(
client: any, client: any,
handlers: MessageMappingProperties[], handlers: MessageMappingProperties[],
@@ -72,15 +52,12 @@ export class IoAdapter implements WebSocketAdapter {
}), }),
takeUntil(disconnect$), takeUntil(disconnect$),
); );
const onMessage = ([response, ack]) => { source$.subscribe(([response, ack]) => {
if (response.event) { if (response.event) {
return client.emit(response.event, response.data); return client.emit(response.event, response.data);
} }
isFunction(ack) && ack(response); isFunction(ack) && ack(response);
}; });
const onError = (err: any) =>
this.baseExceptionFilter.handleError(client, err);
source$.subscribe(onMessage as any, onError);
}); });
} }
@@ -99,12 +76,4 @@ export class IoAdapter implements WebSocketAdapter {
} }
return { data: payload }; return { data: payload };
} }
public bindMiddleware(server, middleware: (socket, next) => void) {
server.use(middleware);
}
public close(server: any) {
isFunction(server.close) && server.close();
}
} }

View File

@@ -0,0 +1,8 @@
/*
* Nest @platform-socket.io
* Copyright(c) 2017 - 2018 Kamil Mysliwiec
* https://nestjs.com
* MIT Licensed
*/
export * from './adapters';

View File

@@ -0,0 +1,19 @@
{
"name": "@nestjs/platform-socket.io",
"version": "5.4.1",
"description": "Nest - modern, fast, powerful node.js web framework (@platform-socket.io)",
"author": "Kamil Mysliwiec",
"license": "MIT",
"repository": {
"type": "git",
"url": "https://github.com/nestjs/nest"
},
"dependencies": {
"@types/express": "^4.0.39",
"body-parser": "1.18.3",
"cors": "2.8.4",
"express": "4.16.3",
"multer": "1.3.0"
},
"peerDependencies": {}
}

View File

@@ -0,0 +1,7 @@
{
"extends": "./../tsconfig.base.json",
"compilerOptions": {
"baseUrl": "./"
},
"include": ["*.ts", "**/*.ts"]
}

View File

@@ -0,0 +1,76 @@
<p align="center">
<a href="http://nestjs.com/" target="blank"><img src="https://nestjs.com/img/logo_text.svg" width="320" alt="Nest Logo" /></a>
</p>
[travis-image]: https://api.travis-ci.org/nestjs/nest.svg?branch=master
[travis-url]: https://travis-ci.org/nestjs/nest
[linux-image]: https://img.shields.io/travis/nestjs/nest/master.svg?label=linux
[linux-url]: https://travis-ci.org/nestjs/nest
<p align="center">A progressive <a href="http://nodejs.org" target="blank">Node.js</a> framework for building efficient and scalable server-side applications, heavily inspired by <a href="https://angular.io" target="blank">Angular</a>.</p>
<p align="center">
<a href="https://www.npmjs.com/~nestjscore"><img src="https://img.shields.io/npm/v/@nestjs/core.svg" alt="NPM Version" /></a>
<a href="https://www.npmjs.com/~nestjscore"><img src="https://img.shields.io/npm/l/@nestjs/core.svg" alt="Package License" /></a>
<a href="https://www.npmjs.com/~nestjscore"><img src="https://img.shields.io/npm/dm/@nestjs/core.svg" alt="NPM Downloads" /></a>
<a href="https://travis-ci.org/nestjs/nest"><img src="https://api.travis-ci.org/nestjs/nest.svg?branch=master" alt="Travis" /></a>
<a href="https://travis-ci.org/nestjs/nest"><img src="https://img.shields.io/travis/nestjs/nest/master.svg?label=linux" alt="Linux" /></a>
<a href="https://coveralls.io/github/nestjs/nest?branch=master"><img src="https://coveralls.io/repos/github/nestjs/nest/badge.svg?branch=master#8" alt="Coverage" /></a>
<a href="https://gitter.im/nestjs/nestjs?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=body_badge"><img src="https://badges.gitter.im/nestjs/nestjs.svg" alt="Gitter" /></a>
<a href="https://opencollective.com/nest#backer"><img src="https://opencollective.com/nest/backers/badge.svg" alt="Backers on Open Collective" /></a>
<a href="https://opencollective.com/nest#sponsor"><img src="https://opencollective.com/nest/sponsors/badge.svg" alt="Sponsors on Open Collective" /></a>
<a href="https://paypal.me/kamilmysliwiec"><img src="https://img.shields.io/badge/Donate-PayPal-ff3f59.svg"/></a>
<a href="https://twitter.com/nestframework"><img src="https://img.shields.io/twitter/follow/nestframework.svg?style=social&label=Follow"></a>
</p>
<!--[![Backers on Open Collective](https://opencollective.com/nest/backers/badge.svg)](https://opencollective.com/nest#backer)
[![Sponsors on Open Collective](https://opencollective.com/nest/sponsors/badge.svg)](https://opencollective.com/nest#sponsor)-->
## Description
<p>Nest is a framework for building efficient, scalable <a href="http://nodejs.org" target="_blank">Node.js</a> server-side applications. It uses modern JavaScript, is built with <a href="http://www.typescriptlang.org" target="_blank">TypeScript</a> (preserves compatibility with pure JavaScript) and combines elements of OOP (Object Oriented Programming), FP (Functional Programming), and FRP (Functional Reactive Programming).</p>
<p>Under the hood, Nest makes use of <a href="https://expressjs.com/" target="_blank">Express</a>, but also, provides compatibility with a wide range of other libraries, like e.g. <a href="https://github.com/fastify/fastify" target="blank">Fastify</a>, allowing for easy use of the myriad third-party plugins which are available.</p>
## Philosophy
<p>In recent years, thanks to Node.js, JavaScript has become the “lingua franca” of the web for both front and backend applications, giving rise to awesome projects like <a href="https://angular.io/" target="_blank">Angular</a>, <a href="https://github.com/facebook/react" target="_blank">React</a> and <a href="https://github.com/vuejs/vue" target="_blank">Vue</a> which improve developer productivity and enable the construction of fast, testable, extensible frontend applications. However, on the server-side, while there are a lot of superb libraries, helpers and tools for Node, none of them effectively solve the main problem - the architecture.</p>
<p>Nest aims to provide an application architecture out of the box which allows for effortless creation of highly testable, scalable, loosely coupled and easily maintainable applications.</p>
## Getting started
* To check out the [guide](https://docs.nestjs.com), visit [docs.nestjs.com](https://docs.nestjs.com). :books:
* 要查看中文 [指南](readme_zh.md), 请访问 [docs.nestjs.cn](https://docs.nestjs.cn). :books:
## Support
Nest is an MIT-licensed open source project. It can grow thanks to the sponsors and support by the amazing backers. If you'd like to join them, please [read more here](https://docs.nestjs.com/support).
#### Principal Sponsor
<a href="https://valor-software.com/"><img src="https://docs.nestjs.com/assets/sponsors/valor-software.png" width="320" /></a>
#### Gold Sponsors
<a href="http://xtremis.com/"><img src="https://nestjs.com/img/logo-xtremis.svg" width="220" /></a>
#### Silver Sponsors
<a href="https://neoteric.eu/"><img src="https://nestjs.com/img/neoteric-cut.png" width="120" /></a> &nbsp;
<a href="http://gojob.com"><img src="http://nestjs.com/img/gojob-logo.png" valign="bottom" height="95" /></a> &nbsp; <a href="https://www.swingdev.io"><img src="https://nestjs.com/img/swingdev-logo.svg#1" width="150" /> </a>
#### Sponsors
<a href="https://scal.io"><img src="https://nestjs.com/img/scalio-logo.svg" width="110" /></a> &nbsp; <a href="http://angularity.io"><img src="http://angularity.io/media/logo.svg" height="30" /></a> &nbsp; <!--<a href="https://keycdn.com"><img src="https://nestjs.com/img/keycdn.svg" height="30" /></a> &nbsp;--> <a href="https://hostpresto.com"><img src="https://nestjs.com/img/hostpresto.png" height="30" /></a> &nbsp; <a href="https://genuinebee.com/"><img src="https://nestjs.com/img/genuinebee.svg" height="38" /></a> &nbsp; <a href="http://architectnow.net/"><img src="https://nestjs.com/img/architectnow.png" height="24" /></a> &nbsp; <a href="https://quander.io/"><img src="https://nestjs.com/img/quander.png" height="28" /></a>
## Backers
<a href="https://opencollective.com/nest"><img src="https://opencollective.com/nest/backers.svg?width=890"></a>
## Stay in touch
* Author - [Kamil Myśliwiec](https://kamilmysliwiec.com)
* Website - [https://nestjs.com](https://nestjs.com/)
* Twitter - [@nestframework](https://twitter.com/nestframework)
## License
Nest is [MIT licensed](LICENSE).

View File

@@ -0,0 +1 @@
export * from './ws-adapter';

View File

@@ -0,0 +1,103 @@
import { INestApplicationContext, Logger } from '@nestjs/common';
import { loadPackage } from '@nestjs/common/utils/load-package.util';
import { AbstractWsAdapter } from '@nestjs/websockets';
import {
CLOSE_EVENT,
CONNECTION_EVENT,
ERROR_EVENT,
} from '@nestjs/websockets/constants';
import { MessageMappingProperties } from '@nestjs/websockets/gateway-metadata-explorer';
import { EMPTY as empty, fromEvent, Observable } from 'rxjs';
import { filter, first, mergeMap, share, takeUntil } from 'rxjs/operators';
let wsPackage: any = {};
enum READY_STATE {
CONNECTING_STATE = 0,
OPEN_STATE = 1,
CLOSING_STATE = 2,
CLOSED_STATE = 3,
}
export class WsAdapter extends AbstractWsAdapter {
protected readonly logger = new Logger(WsAdapter.name);
constructor(appOrHttpServer?: INestApplicationContext | any) {
super(appOrHttpServer);
wsPackage = loadPackage('ws', 'WsAdapter');
}
public create(
port: number,
options?: any & { namespace?: string; server?: any },
): any {
const { server, ...wsOptions } = options;
if (port === 0 && this.httpServer) {
return this.bindErrorHandler(
new wsPackage.Server({
server: this.httpServer,
...wsOptions,
}),
);
}
return server
? server
: this.bindErrorHandler(
new wsPackage.Server({
port,
...wsOptions,
}),
);
}
public bindMessageHandlers(
client: any,
handlers: MessageMappingProperties[],
transform: (data: any) => Observable<any>,
) {
const close$ = fromEvent(client, CLOSE_EVENT).pipe(
share(),
first(),
);
const source$ = fromEvent(client, 'message').pipe(
mergeMap(data =>
this.bindMessageHandler(data, handlers, transform).pipe(
filter(result => result),
),
),
takeUntil(close$),
);
const onMessage = (response: any) => {
if (client.readyState !== READY_STATE.OPEN_STATE) {
return;
}
client.send(JSON.stringify(response));
};
source$.subscribe(onMessage);
}
public bindMessageHandler(
buffer: any,
handlers: MessageMappingProperties[],
transform: (data: any) => Observable<any>,
): Observable<any> {
try {
const message = JSON.parse(buffer.data);
const messageHandler = handlers.find(
handler => handler.message === message.event,
);
const { callback } = messageHandler;
return transform(callback(message.data));
} catch {
return empty;
}
}
public bindErrorHandler(server: any) {
server.on(CONNECTION_EVENT, ws =>
ws.on(ERROR_EVENT, (err: any) => this.logger.error(err)),
);
server.on(ERROR_EVENT, (err: any) => this.logger.error(err));
return server;
}
}

View File

@@ -0,0 +1,8 @@
/*
* Nest @platform-ws
* Copyright(c) 2017 - 2018 Kamil Mysliwiec
* https://nestjs.com
* MIT Licensed
*/
export * from './adapters';

View File

@@ -0,0 +1,19 @@
{
"name": "@nestjs/platform-ws",
"version": "5.4.1",
"description": "Nest - modern, fast, powerful node.js web framework (@platform-ws)",
"author": "Kamil Mysliwiec",
"license": "MIT",
"repository": {
"type": "git",
"url": "https://github.com/nestjs/nest"
},
"dependencies": {
"@types/express": "^4.0.39",
"body-parser": "1.18.3",
"cors": "2.8.4",
"express": "4.16.3",
"multer": "1.3.0"
},
"peerDependencies": {}
}

View File

@@ -0,0 +1,7 @@
{
"extends": "./../tsconfig.base.json",
"compilerOptions": {
"baseUrl": "./"
},
"include": ["*.ts", "**/*.ts"]
}

View File

@@ -1 +1 @@
export * from './ws-adapter'; export * from './ws-adapter';

View File

@@ -1,123 +1,47 @@
import { import { INestApplicationContext, WebSocketAdapter } from '@nestjs/common';
INestApplicationContext, import { WsMessageHandler } from '@nestjs/common/interfaces';
Logger,
WebSocketAdapter,
} from '@nestjs/common';
import { loadPackage } from '@nestjs/common/utils/load-package.util';
import { isFunction } from '@nestjs/common/utils/shared.utils'; import { isFunction } from '@nestjs/common/utils/shared.utils';
import { NestApplication } from '@nestjs/core'; import { NestApplication } from '@nestjs/core';
import { Server } from 'http'; import { Observable } from 'rxjs';
import { EMPTY as empty, fromEvent, Observable } from 'rxjs'; import { CONNECTION_EVENT, DISCONNECT_EVENT } from '../constants';
import { filter, first, mergeMap, share, takeUntil } from 'rxjs/operators';
import { CLOSE_EVENT, CONNECTION_EVENT, ERROR_EVENT } from '../constants';
import { MessageMappingProperties } from '../gateway-metadata-explorer';
import { BaseWsExceptionFilter } from './../exceptions/base-ws-exception-filter';
let wsPackage: any = {}; export interface BaseWsInstance {
on: (event: string, callback: Function) => void;
enum READY_STATE { close: Function;
CONNECTING_STATE = 0,
OPEN_STATE = 1,
CLOSING_STATE = 2,
CLOSED_STATE = 3,
} }
export class WsAdapter implements WebSocketAdapter { export abstract class AbstractWsAdapter<
protected readonly baseExceptionFilter = new BaseWsExceptionFilter(); TServer extends BaseWsInstance = any,
protected readonly logger = new Logger(WsAdapter.name); TClient extends BaseWsInstance = any,
protected readonly httpServer: Server; TOptions = any
> implements WebSocketAdapter<TServer, TClient, TOptions> {
protected readonly httpServer: any;
constructor(appOrHttpServer?: INestApplicationContext | Server) { constructor(appOrHttpServer?: INestApplicationContext | any) {
wsPackage = loadPackage('ws', 'WsAdapter');
if (appOrHttpServer && appOrHttpServer instanceof NestApplication) { if (appOrHttpServer && appOrHttpServer instanceof NestApplication) {
this.httpServer = appOrHttpServer.getUnderlyingHttpServer(); this.httpServer = appOrHttpServer.getUnderlyingHttpServer();
} else { } else {
this.httpServer = appOrHttpServer as Server; this.httpServer = appOrHttpServer;
} }
} }
public create( public bindClientConnect(server: TServer, callback: Function) {
port: number,
options?: any & { namespace?: string; server?: any },
): any {
const { server, ...wsOptions } = options;
if (port === 0 && this.httpServer) {
return this.bindErrorHandler(
new wsPackage.Server({
server: this.httpServer,
...wsOptions,
}),
);
}
return server
? server
: this.bindErrorHandler(
new wsPackage.Server({
port,
...wsOptions,
}),
);
}
public bindClientConnect(server, callback: (...args: any[]) => void) {
server.on(CONNECTION_EVENT, callback); server.on(CONNECTION_EVENT, callback);
} }
public bindClientDisconnect(client, callback: (...args: any[]) => void) { public bindClientDisconnect(client: TClient, callback: Function) {
client.on(CLOSE_EVENT, callback); client.on(DISCONNECT_EVENT, callback);
} }
public bindMessageHandlers( public close(server: TServer) {
client: any, const isCallable = server && isFunction(server.close);
handlers: MessageMappingProperties[], isCallable && server.close();
}
public abstract create(port: number, options?: TOptions): TServer;
public abstract bindMessageHandlers(
client: TClient,
handlers: WsMessageHandler[],
transform: (data: any) => Observable<any>, transform: (data: any) => Observable<any>,
) { );
const close$ = fromEvent(client, CLOSE_EVENT).pipe(share(), first());
const source$ = fromEvent(client, 'message').pipe(
mergeMap(data =>
this.bindMessageHandler(data, handlers, transform).pipe(
filter(result => result),
),
),
takeUntil(close$),
);
const onMessage = (response: any) => {
if (client.readyState !== READY_STATE.OPEN_STATE) {
return;
}
client.send(JSON.stringify(response));
};
const onError = (err: any) =>
this.baseExceptionFilter.handleError(client, err);
source$.subscribe(onMessage, onError);
}
public bindMessageHandler(
buffer: any,
handlers: MessageMappingProperties[],
transform: (data: any) => Observable<any>,
): Observable<any> {
try {
const message = JSON.parse(buffer.data);
const messageHandler = handlers.find(
handler => handler.message === message.event,
);
const { callback } = messageHandler;
return transform(callback(message.data));
} catch {
return empty;
}
}
public close(server: any) {
server && isFunction(server.close) && server.close();
}
public bindErrorHandler(server: any) {
server.on(CONNECTION_EVENT, ws =>
ws.on(ERROR_EVENT, (err: any) => this.logger.error(err)),
);
server.on(ERROR_EVENT, (err: any) => this.logger.error(err));
return server;
}
} }

View File

@@ -1,5 +1,5 @@
import { isFunction } from '@nestjs/common/utils/shared.utils'; import { isFunction } from '@nestjs/common/utils/shared.utils';
import { ExecutionContextHost } from '@nestjs/core/helpers/execution-context.host'; import { ExecutionContextHost } from '@nestjs/core/helpers/execution-context-host';
import { empty } from 'rxjs'; import { empty } from 'rxjs';
import { catchError } from 'rxjs/operators'; import { catchError } from 'rxjs/operators';
import { WsExceptionsHandler } from '../exceptions/ws-exceptions-handler'; import { WsExceptionsHandler } from '../exceptions/ws-exceptions-handler';

View File

@@ -2,6 +2,6 @@ import { RuntimeException } from '@nestjs/core/errors/exceptions/runtime.excepti
export class InvalidSocketPortException extends RuntimeException { export class InvalidSocketPortException extends RuntimeException {
constructor(port: number | string, type: any) { constructor(port: number | string, type: any) {
super(`Invalid port (${port}) in gateway ${type}!`); super(`Invalid port (${port}) in gateway ${type}`);
} }
} }

View File

@@ -6,8 +6,7 @@
*/ */
import 'reflect-metadata'; import 'reflect-metadata';
export * from './adapters/io-adapter'; export * from './adapters';
export * from './adapters/ws-adapter';
export * from './errors'; export * from './errors';
export * from './exceptions'; export * from './exceptions';
export { MessageMappingProperties } from './gateway-metadata-explorer'; export { MessageMappingProperties } from './gateway-metadata-explorer';

View File

@@ -1,7 +1,8 @@
{ {
"name": "@nestjs/websockets", "name": "@nestjs/websockets",
"version": "5.5.0", "version": "5.5.0",
"description": "Nest - modern, fast, powerful node.js web framework (@websockets)", "description":
"Nest - modern, fast, powerful node.js web framework (@websockets)",
"author": "Kamil Mysliwiec", "author": "Kamil Mysliwiec",
"license": "MIT", "license": "MIT",
"repository": { "repository": {
@@ -9,8 +10,7 @@
"url": "https://github.com/nestjs/nest" "url": "https://github.com/nestjs/nest"
}, },
"dependencies": { "dependencies": {
"iterare": "0.0.8", "iterare": "0.0.8"
"socket.io": "^2.1.1"
}, },
"peerDependencies": { "peerDependencies": {
"@nestjs/common": "^5.0.0", "@nestjs/common": "^5.0.0",

View File

@@ -27,27 +27,32 @@ export class SocketModule {
public register(container: NestContainer, config: ApplicationConfig) { public register(container: NestContainer, config: ApplicationConfig) {
this.applicationConfig = config; this.applicationConfig = config;
this.webSocketsController = new WebSocketsController( const serverProvider = new SocketServerProvider(
new SocketServerProvider(this.socketsContainer, config), this.socketsContainer,
config, config,
this.getContextCreator(container), );
const contextCreator = this.getContextCreator(container);
this.webSocketsController = new WebSocketsController(
serverProvider,
config,
contextCreator,
); );
const modules = container.getModules(); const modules = container.getModules();
modules.forEach(({ providers }, moduleName: string) => modules.forEach(({ providers }, moduleName: string) =>
this.hookGatewaysIntoServers(providers, moduleName), this.mergeAllGateways(providers, moduleName),
); );
} }
public hookGatewaysIntoServers( public mergeAllGateways(
providers: Map<string, InstanceWrapper<Injectable>>, providers: Map<string, InstanceWrapper<Injectable>>,
moduleName: string, moduleName: string,
) { ) {
providers.forEach(wrapper => providers.forEach(wrapper =>
this.hookGatewayIntoServer(wrapper, moduleName), this.mergeGatewayAndServer(wrapper, moduleName),
); );
} }
public hookGatewayIntoServer( public mergeGatewayAndServer(
wrapper: InstanceWrapper<Injectable>, wrapper: InstanceWrapper<Injectable>,
moduleName: string, moduleName: string,
) { ) {
@@ -59,7 +64,7 @@ export class SocketModule {
if (!metadataKeys.includes(GATEWAY_METADATA)) { if (!metadataKeys.includes(GATEWAY_METADATA)) {
return; return;
} }
this.webSocketsController.hookGatewayIntoServer( this.webSocketsController.mergeGatewayAndServer(
instance as NestGateway, instance as NestGateway,
metatype, metatype,
moduleName, moduleName,

View File

@@ -129,8 +129,9 @@ describe('WsContextCreator', () => {
module, module,
); );
const data = 'test'; const data = 'test';
proxy(null, data).catch(err =>
expect(proxy(null, data)).to.eventually.rejectedWith(WsException); expect(err).to.be.instanceOf(WsException),
);
}); });
}); });
}); });
@@ -164,7 +165,7 @@ describe('WsContextCreator', () => {
it('should throw exception when "tryActivate" returns false', () => { it('should throw exception when "tryActivate" returns false', () => {
const guardsFn = contextCreator.createGuardsFn([null], null, null); const guardsFn = contextCreator.createGuardsFn([null], null, null);
sinon.stub(guardsConsumer, 'tryActivate').callsFake(() => false); sinon.stub(guardsConsumer, 'tryActivate').callsFake(() => false);
expect(guardsFn([])).to.eventually.throw(); guardsFn([]).catch(err => expect(err).to.not.be.undefined);
}); });
}); });
}); });

View File

@@ -2,7 +2,7 @@ import * as sinon from 'sinon';
import { expect } from 'chai'; import { expect } from 'chai';
import { WsExceptionsHandler } from '../../exceptions/ws-exceptions-handler'; import { WsExceptionsHandler } from '../../exceptions/ws-exceptions-handler';
import { WsException } from '../../errors/ws-exception'; import { WsException } from '../../errors/ws-exception';
import { ExecutionContextHost } from '@nestjs/core/helpers/execution-context.host'; import { ExecutionContextHost } from '@nestjs/core/helpers/execution-context-host';
describe('WsExceptionsHandler', () => { describe('WsExceptionsHandler', () => {
let handler: WsExceptionsHandler; let handler: WsExceptionsHandler;

View File

@@ -1,9 +1,14 @@
import * as sinon from 'sinon';
import { expect } from 'chai';
import { SocketServerProvider } from '../socket-server-provider';
import { SocketsContainer } from '../container';
import { ApplicationConfig } from '@nestjs/core/application-config'; import { ApplicationConfig } from '@nestjs/core/application-config';
import { IoAdapter } from '@nestjs/websockets/adapters/io-adapter'; import { expect } from 'chai';
import * as sinon from 'sinon';
import { SocketsContainer } from '../container';
import { SocketServerProvider } from '../socket-server-provider';
import { AbstractWsAdapter } from './../adapters/ws-adapter';
class NoopAdapter extends AbstractWsAdapter {
public create(port: number, options?: any) {}
public bindMessageHandlers(client: any, handlers) {}
}
describe('SocketServerProvider', () => { describe('SocketServerProvider', () => {
let instance: SocketServerProvider; let instance: SocketServerProvider;
@@ -14,7 +19,7 @@ describe('SocketServerProvider', () => {
mockContainer = sinon.mock(socketsContainer); mockContainer = sinon.mock(socketsContainer);
instance = new SocketServerProvider( instance = new SocketServerProvider(
socketsContainer, socketsContainer,
new ApplicationConfig(new IoAdapter()), new ApplicationConfig(new NoopAdapter()),
); );
}); });
describe('scanForSocketServer', () => { describe('scanForSocketServer', () => {

View File

@@ -1,17 +1,31 @@
import { ApplicationConfig } from '@nestjs/core/application-config'; import { ApplicationConfig } from '@nestjs/core/application-config';
import { expect } from 'chai'; import { expect } from 'chai';
import { of } from 'rxjs'; import { fromEvent, Observable, of } from 'rxjs';
import * as sinon from 'sinon'; import * as sinon from 'sinon';
import { MetadataScanner } from '../../core/metadata-scanner'; import { MetadataScanner } from '../../core/metadata-scanner';
import { AbstractWsAdapter } from '../adapters/ws-adapter';
import { PORT_METADATA } from '../constants'; import { PORT_METADATA } from '../constants';
import { WsContextCreator } from '../context/ws-context-creator'; import { WsContextCreator } from '../context/ws-context-creator';
import { InvalidSocketPortException } from '../errors/invalid-socket-port.exception'; import { InvalidSocketPortException } from '../errors/invalid-socket-port.exception';
import { GatewayMetadataExplorer } from '../gateway-metadata-explorer'; import { GatewayMetadataExplorer } from '../gateway-metadata-explorer';
import { IoAdapter } from '../index';
import { SocketServerProvider } from '../socket-server-provider'; import { SocketServerProvider } from '../socket-server-provider';
import { WebSocketGateway } from '../utils/socket-gateway.decorator'; import { WebSocketGateway } from '../utils/socket-gateway.decorator';
import { WebSocketsController } from '../web-sockets-controller'; import { WebSocketsController } from '../web-sockets-controller';
class NoopAdapter extends AbstractWsAdapter {
public create(port: number, options?: any) {}
public bindMessageHandlers(
client: any,
handlers,
transform: (data: any) => Observable<any>,
) {
handlers.forEach(({ message, callback }) => {
const source$ = fromEvent(client, message);
source$.subscribe(data => null);
});
}
}
describe('WebSocketsController', () => { describe('WebSocketsController', () => {
let instance: WebSocketsController; let instance: WebSocketsController;
let provider: SocketServerProvider, let provider: SocketServerProvider,
@@ -24,7 +38,7 @@ describe('WebSocketsController', () => {
class Test {} class Test {}
beforeEach(() => { beforeEach(() => {
config = new ApplicationConfig(new IoAdapter()); config = new ApplicationConfig(new NoopAdapter());
provider = new SocketServerProvider(null, config); provider = new SocketServerProvider(null, config);
mockProvider = sinon.mock(provider); mockProvider = sinon.mock(provider);
instance = new WebSocketsController( instance = new WebSocketsController(
@@ -33,7 +47,7 @@ describe('WebSocketsController', () => {
sinon.createStubInstance(WsContextCreator), sinon.createStubInstance(WsContextCreator),
); );
}); });
describe('hookGatewayIntoServer', () => { describe('mergeGatewayAndServer', () => {
let subscribeObservableServer: sinon.SinonSpy; let subscribeObservableServer: sinon.SinonSpy;
@WebSocketGateway('test' as any) @WebSocketGateway('test' as any)
@@ -49,7 +63,7 @@ describe('WebSocketsController', () => {
it('should throws "InvalidSocketPortException" when port is not a number', () => { it('should throws "InvalidSocketPortException" when port is not a number', () => {
Reflect.defineMetadata(PORT_METADATA, 'test', InvalidGateway); Reflect.defineMetadata(PORT_METADATA, 'test', InvalidGateway);
expect(() => expect(() =>
instance.hookGatewayIntoServer( instance.mergeGatewayAndServer(
new InvalidGateway(), new InvalidGateway(),
InvalidGateway, InvalidGateway,
'', '',
@@ -58,13 +72,13 @@ describe('WebSocketsController', () => {
}); });
it('should call "subscribeObservableServer" with default values when metadata is empty', () => { it('should call "subscribeObservableServer" with default values when metadata is empty', () => {
const gateway = new DefaultGateway(); const gateway = new DefaultGateway();
instance.hookGatewayIntoServer(gateway, DefaultGateway, ''); instance.mergeGatewayAndServer(gateway, DefaultGateway, '');
expect(subscribeObservableServer.calledWith(gateway, {}, 0, '')).to.be expect(subscribeObservableServer.calledWith(gateway, {}, 0, '')).to.be
.true; .true;
}); });
it('should call "subscribeObservableServer" when metadata is valid', () => { it('should call "subscribeObservableServer" when metadata is valid', () => {
const gateway = new Test(); const gateway = new Test();
instance.hookGatewayIntoServer(gateway, Test, ''); instance.mergeGatewayAndServer(gateway, Test, '');
expect( expect(
subscribeObservableServer.calledWith(gateway, { namespace }, port, ''), subscribeObservableServer.calledWith(gateway, { namespace }, port, ''),
).to.be.true; ).to.be.true;
@@ -303,7 +317,7 @@ describe('WebSocketsController', () => {
}); });
it('should bind each handler to client', () => { it('should bind each handler to client', () => {
instance.subscribeMessages(handlers, client, gateway); instance.subscribeMessages(handlers, client, gateway);
expect(onSpy.calledThrice).to.be.true; expect(onSpy.calledTwice).to.be.true;
}); });
}); });
describe('pickResult', () => { describe('pickResult', () => {

View File

@@ -26,7 +26,7 @@ export class WebSocketsController {
private readonly contextCreator: WsContextCreator, private readonly contextCreator: WsContextCreator,
) {} ) {}
public hookGatewayIntoServer( public mergeGatewayAndServer(
instance: NestGateway, instance: NestGateway,
metatype: Type<any>, metatype: Type<any>,
module: string, module: string,

View File

@@ -1,10 +1,17 @@
import { NestFactory, FastifyAdapter } from '@nestjs/core';
import { ValidationPipe } from '@nestjs/common'; import { ValidationPipe } from '@nestjs/common';
import { NestFactory } from '@nestjs/core';
import {
FastifyAdapter,
INestFastifyApplication,
} from '@nestjs/platform-fastify';
import { ApplicationModule } from './app.module'; import { ApplicationModule } from './app.module';
async function bootstrap() { async function bootstrap() {
const app = await NestFactory.create(ApplicationModule, new FastifyAdapter()); const app = await NestFactory.create<INestFastifyApplication>(
ApplicationModule,
new FastifyAdapter(),
);
app.useGlobalPipes(new ValidationPipe()); app.useGlobalPipes(new ValidationPipe());
await app.listen(3000); await app.listen(3000);
} }
bootstrap(); bootstrap();