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(
'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 source = 'packages';
@@ -42,7 +46,9 @@ gulp.task('copy-misc', function() {
.pipe(gulp.dest(`${source}/websockets`))
.pipe(gulp.dest(`${source}/testing`))
.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() {

View File

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

View File

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

View File

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

View File

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

63
package-lock.json generated
View File

@@ -1686,9 +1686,9 @@
"dev": true
},
"buffer-from": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.0.0.tgz",
"integrity": "sha512-83apNb8KK0Se60UE1+4Ukbe3HbfELJ6UlI4ldtOGs7So4KD26orJM8hIY9lxdzP+UpItH1Yh/Y8GUvNFWFFRxA==",
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz",
"integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==",
"dev": true
},
"buffer-more-ints": {
@@ -10492,9 +10492,9 @@
}
},
"make-error": {
"version": "1.3.4",
"resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.4.tgz",
"integrity": "sha512-0Dab5btKVPhibSalc9QGXb559ED7G7iLjFXBaj9Wq8O3vorueR5K5jaE3hkG6ZQINyhA/JgG6Qk4qdFQjsYV6g==",
"version": "1.3.5",
"resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.5.tgz",
"integrity": "sha512-c3sIjNUow0+8swNwVpqoH4YCShKNFkMaw6oH1mNS2haDZQqkeZFlHS3dhoeEbKKmJB4vXpJucU6oH75aDYeE9g==",
"dev": true
},
"make-iterator": {
@@ -15448,47 +15448,21 @@
}
},
"ts-node": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/ts-node/-/ts-node-6.0.1.tgz",
"integrity": "sha512-LDS8WJRTEztvQEXbRK/0l/apWE0BNR/USGUhVcJDe2/hbNiB/v/lCqk6YJzgwKjScgdWOAhPQsfur7hmQ1Y1jA==",
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/ts-node/-/ts-node-7.0.1.tgz",
"integrity": "sha512-BVwVbPJRspzNh2yfslyT1PSbl5uIk03EZlb493RKHN4qej/D06n1cEhjlOJG69oFsE7OT8XjpTUcYf6pKTLMhw==",
"dev": true,
"requires": {
"arrify": "^1.0.0",
"chalk": "^2.3.0",
"buffer-from": "^1.1.0",
"diff": "^3.1.0",
"make-error": "^1.1.1",
"minimist": "^1.2.0",
"mkdirp": "^0.5.1",
"source-map-support": "^0.5.3",
"source-map-support": "^0.5.6",
"yn": "^2.0.0"
},
"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": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
@@ -15496,23 +15470,14 @@
"dev": true
},
"source-map-support": {
"version": "0.5.5",
"resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.5.tgz",
"integrity": "sha512-mR7/Nd5l1z6g99010shcXJiNEaf3fEtmLhRB/sBcQVJGodcHCULPp2y4Sfa43Kv2zq7T+Izmfp/WHCR6dYkQCA==",
"version": "0.5.9",
"resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.9.tgz",
"integrity": "sha512-gR6Rw4MvUlYy83vP0vxoVNzM6t8MUXqNuRsuBmBHQDu1Fh6X015FrLdgoDKcNdkwGubozq0P4N0Q37UyFVr1EA==",
"dev": true,
"requires": {
"buffer-from": "^1.0.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",
"socket.io-client": "^2.0.4",
"supertest": "^3.0.0",
"ts-node": "^6.0.0",
"ts-node": "^7.0.1",
"tslint": "^5.11.0",
"typescript": "^3.2.2"
},

View File

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

View File

@@ -1,16 +1,21 @@
import { Observable } from 'rxjs';
export interface WebSocketAdapter<T = any> {
create(port: number, options?: T): any;
bindClientConnect(server: any, callback: (...args: any[]) => void): any;
bindClientDisconnect?(client: any, callback: (...args: any[]) => void): any;
export interface WsMessageHandler<T = string> {
message: T;
callback: (...args: any[]) => Observable<any> | Promise<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(
client: any,
handlers: Array<{
message: any;
callback: (...args: any[]) => Observable<any> | Promise<any> | any;
}>,
client: TClient,
handlers: WsMessageHandler[],
transform: (data: any) => Observable<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 { NestApplicationOptions } from '@nestjs/common/interfaces/nest-application-options.interface';
export abstract class AbstractHttpAdapter<T = any> implements HttpServer {
protected httpServer: T;
export abstract class AbstractHttpAdapter<
TServer = any,
TRequest = any,
TResponse = any
> implements HttpServer<TRequest, TResponse> {
protected httpServer: TServer;
constructor(protected readonly instance: any) {}
@@ -60,11 +64,11 @@ export abstract class AbstractHttpAdapter<T = any> implements HttpServer {
return this.instance.listen(port, hostname, callback);
}
public getHttpServer(): T {
return this.httpServer as T;
public getHttpServer(): TServer {
return this.httpServer as TServer;
}
public setHttpServer(httpServer: T) {
public setHttpServer(httpServer: TServer) {
this.httpServer = httpServer;
}

View File

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

View File

@@ -2,7 +2,7 @@ import { CanActivate } from '@nestjs/common';
import { Controller } from '@nestjs/common/interfaces';
import { isEmpty } from '@nestjs/common/utils/shared.utils';
import { Observable } from 'rxjs';
import { ExecutionContextHost } from '../helpers/execution-context.host';
import { ExecutionContextHost } from '../helpers/execution-context-host';
export class GuardsConsumer {
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 { defer, from as fromPromise, Observable } from 'rxjs';
import { mergeAll, switchMap } from 'rxjs/operators';
import { ExecutionContextHost } from '../helpers/execution-context.host';
import { ExecutionContextHost } from '../helpers/execution-context-host';
export class InterceptorsConsumer {
public async intercept(

View File

@@ -18,6 +18,7 @@ import iterate from 'iterare';
import * as optional from 'optional';
import { ApplicationConfig } from './application-config';
import { MESSAGES } from './constants';
import { loadAdapter } from './helpers/load-adapter';
import { NestContainer } from './injector/container';
import { MiddlewareContainer } from './middleware/container';
import { MiddlewareModule } from './middleware/middleware-module';
@@ -29,8 +30,6 @@ const { SocketModule } =
optional('@nestjs/websockets/socket-module') || ({} as any);
const { MicroservicesModule } =
optional('@nestjs/microservices/microservices-module') || ({} as any);
const { IoAdapter } =
optional('@nestjs/websockets/adapters/io-adapter') || ({} as any);
export class NestApplication extends NestApplicationContext
implements INestApplication {
@@ -68,6 +67,7 @@ export class NestApplication extends NestApplicationContext
public registerHttpServer() {
this.httpServer = this.createServer();
const { IoAdapter } = optional('@nestjs/platform-socket.io') || ({} as any);
const ioAdapter = IoAdapter ? new IoAdapter(this.httpServer) : null;
this.config.setIoAdapter(ioAdapter);
}
@@ -93,8 +93,7 @@ export class NestApplication extends NestApplicationContext
}
public async registerModules() {
this.socketModule &&
this.socketModule.register(this.container, this.config);
this.registerWsModule();
if (this.microservicesModule) {
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> {
const useBodyParser =
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 { MESSAGES } from './constants';
import { ExceptionsZone } from './errors/exceptions-zone';
import { loadAdapter } from './helpers/load-adapter';
import { NestContainer } from './injector/container';
import { InstanceLoader } from './injector/instance-loader';
import { MetadataScanner } from './metadata-scanner';
@@ -58,8 +59,8 @@ export class NestFactoryStatic {
applicationConfig,
appOptions,
);
const target = this.createAdapterProxy<T>(instance, httpServer);
return this.createNestInstance<T>(target);
const target = this.createNestInstance(instance);
return this.createAdapterProxy<T>(target, httpServer);
}
/**
@@ -155,18 +156,25 @@ export class NestFactoryStatic {
return;
}
if (isFunction(receiver[prop])) {
return (...args: any[]) => {
let result;
ExceptionsZone.run(() => {
result = receiver[prop](...args);
});
return result;
};
return this.createExceptionZone(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) {
if (!options) {
return;
@@ -175,10 +183,7 @@ export class NestFactoryStatic {
}
private createHttpAdapter<T = any>(httpServer?: T): HttpServer {
const { ExpressAdapter } = loadPackage(
'@nestjs/platform-express',
'NestFactory',
);
const { ExpressAdapter } = loadAdapter('@nestjs/platform-express', 'HTTP');
return new ExpressAdapter(httpServer);
}
@@ -192,11 +197,11 @@ export class NestFactoryStatic {
return (new Proxy(app, {
get: (receiver: Record<string, any>, prop: string) => {
if (!(prop in receiver) && prop in adapter) {
return adapter[prop];
return this.createExceptionZone(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 { ExecutionContextHost } from '../helpers/execution-context.host';
import { ExecutionContextHost } from '../helpers/execution-context-host';
export type RouterProxyCallback = <TRequest, TResponse>(
req?: TRequest,

View File

@@ -7,7 +7,7 @@ import { Logger } from '../../../common/services/logger.service';
import { AbstractHttpAdapter } from '../../adapters';
import { InvalidExceptionFilterException } from '../../errors/exceptions/invalid-exception-filter.exception';
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';
describe('ExceptionsHandler', () => {

View File

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

View File

@@ -73,7 +73,7 @@ describe('ExternalContextCreator', () => {
describe('when can not activate', () => {
it('should throw exception when "tryActivate" returns 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', () => {

View File

@@ -127,7 +127,9 @@ describe('RouterExecutionContext', () => {
});
it('should throw exception when "tryActivate" returns 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', () => {
const guardsFn = contextCreator.createGuardsFn([null], null, null);
sinon.stub(guardsConsumer, 'tryActivate').callsFake(() => false);
expect(guardsFn([])).to.eventually.throw();
guardsFn([]).catch(err => expect(err).to.not.be.undefined);
});
});
describe('createHandleResponseFn', () => {

View File

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

View File

@@ -1,5 +1,5 @@
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 { catchError } from 'rxjs/operators';
import { RpcExceptionsHandler } from '../exceptions/rpc-exceptions-handler';

View File

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

View File

@@ -1,3 +1,4 @@
import { Logger } from '@nestjs/common';
import { expect } from 'chai';
import { join } from 'path';
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 { InvalidProtoDefinitionException } from '../../errors/invalid-proto-definition.exception';
// 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 {
test = null;
@@ -188,9 +194,13 @@ describe('ClientGrpcProxy', () => {
describe('when package does not exist', () => {
it('should throw "InvalidGrpcPackageException"', () => {
sinon.stub(client, 'lookupPackage').callsFake(() => null);
expect(() => client.createClient()).to.throw(
InvalidGrpcPackageException,
);
(client as any).logger = new NoopLogger();
try {
client.createClient();
} catch (err) {
expect(err).to.be.instanceof(InvalidGrpcPackageException);
}
});
});
});
@@ -201,6 +211,7 @@ describe('ClientGrpcProxy', () => {
sinon.stub(client, 'getOptionsProp').callsFake(() => {
throw new Error();
});
(client as any).logger = new NoopLogger();
expect(() => client.loadProto()).to.throws(
InvalidProtoDefinitionException,
);
@@ -231,7 +242,7 @@ describe('ClientGrpcProxy', () => {
describe('connect', () => {
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';
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', () => {
const guardsFn = contextCreator.createGuardsFn([null], null, null);
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 { join } from 'path';
import { of } from 'rxjs';
@@ -5,6 +6,12 @@ import * as sinon from 'sinon';
import { InvalidGrpcPackageException } from '../../errors/invalid-grpc-package.exception';
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', () => {
let server: ServerGrpc;
beforeEach(() => {
@@ -42,11 +49,14 @@ describe('ServerGrpc', () => {
describe('bindEvents', () => {
describe('when package does not exist', () => {
it('should throw "InvalidGrpcPackageException"', () => {
it('should throw "InvalidGrpcPackageException"', async () => {
sinon.stub(server, 'lookupPackage').callsFake(() => null);
expect(server.bindEvents()).to.eventually.throws(
InvalidGrpcPackageException,
);
(server as any).logger = new NoopLogger();
try {
await server.bindEvents();
} catch (err) {
expect(err).to.be.instanceof(InvalidGrpcPackageException);
}
});
});
describe('when package exist', () => {

View File

@@ -10,7 +10,6 @@
"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",

View File

@@ -1,5 +1,5 @@
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 { of } from 'rxjs';
import * as sinon from 'sinon';
@@ -57,9 +57,9 @@ describe('FileFieldsInterceptor', () => {
(target as any).fields = {
array: () => callback,
};
expect(
target.intercept(new ExecutionContextHost([]), handler),
).to.eventually.throw();
(target.intercept(new ExecutionContextHost([]), handler) as any).catch(
error => expect(error).to.not.be.undefined,
);
});
});
});

View File

@@ -1,5 +1,5 @@
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 { of } from 'rxjs';
import * as sinon from 'sinon';
@@ -39,9 +39,9 @@ describe('FileInterceptor', () => {
(target as any).multer = {
single: () => callback,
};
expect(
target.intercept(new ExecutionContextHost([]), handler),
).to.eventually.throw();
(target.intercept(new ExecutionContextHost([]), handler) as any).catch(
error => expect(error).to.not.be.undefined,
);
});
});
});

View File

@@ -1,5 +1,5 @@
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 { of } from 'rxjs';
import * as sinon from 'sinon';
@@ -41,9 +41,9 @@ describe('FilesInterceptor', () => {
(target as any).multer = {
array: () => callback,
};
expect(
target.intercept(new ExecutionContextHost([]), handler),
).to.eventually.throw();
(target.intercept(new ExecutionContextHost([]), handler) as any).catch(
error => expect(error).to.not.be.undefined,
);
});
});
});

View File

@@ -1,5 +1,4 @@
import { RequestMethod } from '@nestjs/common';
import { ErrorHandler, RequestHandler } from '@nestjs/common/interfaces';
import { CorsOptions } from '@nestjs/common/interfaces/external/cors-options.interface';
import { NestApplicationOptions } from '@nestjs/common/interfaces/nest-application-options.interface';
import { loadPackage } from '@nestjs/common/utils/load-package.util';
@@ -24,54 +23,6 @@ export class FastifyAdapter extends AbstractHttpAdapter {
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, hostname: string, callback?: () => void);
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 { NestApplication } from '@nestjs/core';
import { Server } from 'http';
import {
AbstractWsAdapter,
MessageMappingProperties,
} from '@nestjs/websockets';
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 { 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(
port: number,
options?: any & { namespace?: string; server?: any },
@@ -32,8 +20,8 @@ export class IoAdapter implements WebSocketAdapter {
return server && isFunction(server.of)
? server.of(namespace)
: namespace
? this.createIOServer(port, opt).of(namespace)
: this.createIOServer(port, opt);
? this.createIOServer(port, opt).of(namespace)
: this.createIOServer(port, opt);
}
public createIOServer(port: number, options?: any): any {
@@ -43,14 +31,6 @@ export class IoAdapter implements WebSocketAdapter {
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(
client: any,
handlers: MessageMappingProperties[],
@@ -72,15 +52,12 @@ export class IoAdapter implements WebSocketAdapter {
}),
takeUntil(disconnect$),
);
const onMessage = ([response, ack]) => {
source$.subscribe(([response, ack]) => {
if (response.event) {
return client.emit(response.event, response.data);
}
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 };
}
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 {
INestApplicationContext,
Logger,
WebSocketAdapter,
} from '@nestjs/common';
import { loadPackage } from '@nestjs/common/utils/load-package.util';
import { INestApplicationContext, WebSocketAdapter } from '@nestjs/common';
import { WsMessageHandler } from '@nestjs/common/interfaces';
import { isFunction } from '@nestjs/common/utils/shared.utils';
import { NestApplication } from '@nestjs/core';
import { Server } from 'http';
import { EMPTY as empty, fromEvent, Observable } from 'rxjs';
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';
import { Observable } from 'rxjs';
import { CONNECTION_EVENT, DISCONNECT_EVENT } from '../constants';
let wsPackage: any = {};
enum READY_STATE {
CONNECTING_STATE = 0,
OPEN_STATE = 1,
CLOSING_STATE = 2,
CLOSED_STATE = 3,
export interface BaseWsInstance {
on: (event: string, callback: Function) => void;
close: Function;
}
export class WsAdapter implements WebSocketAdapter {
protected readonly baseExceptionFilter = new BaseWsExceptionFilter();
protected readonly logger = new Logger(WsAdapter.name);
protected readonly httpServer: Server;
export abstract class AbstractWsAdapter<
TServer extends BaseWsInstance = any,
TClient extends BaseWsInstance = any,
TOptions = any
> implements WebSocketAdapter<TServer, TClient, TOptions> {
protected readonly httpServer: any;
constructor(appOrHttpServer?: INestApplicationContext | Server) {
wsPackage = loadPackage('ws', 'WsAdapter');
constructor(appOrHttpServer?: INestApplicationContext | any) {
if (appOrHttpServer && appOrHttpServer instanceof NestApplication) {
this.httpServer = appOrHttpServer.getUnderlyingHttpServer();
} else {
this.httpServer = appOrHttpServer as Server;
this.httpServer = appOrHttpServer;
}
}
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 bindClientConnect(server, callback: (...args: any[]) => void) {
public bindClientConnect(server: TServer, callback: Function) {
server.on(CONNECTION_EVENT, callback);
}
public bindClientDisconnect(client, callback: (...args: any[]) => void) {
client.on(CLOSE_EVENT, callback);
public bindClientDisconnect(client: TClient, callback: Function) {
client.on(DISCONNECT_EVENT, callback);
}
public bindMessageHandlers(
client: any,
handlers: MessageMappingProperties[],
public close(server: TServer) {
const isCallable = server && isFunction(server.close);
isCallable && server.close();
}
public abstract create(port: number, options?: TOptions): TServer;
public abstract bindMessageHandlers(
client: TClient,
handlers: WsMessageHandler[],
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 { ExecutionContextHost } from '@nestjs/core/helpers/execution-context.host';
import { ExecutionContextHost } from '@nestjs/core/helpers/execution-context-host';
import { empty } from 'rxjs';
import { catchError } from 'rxjs/operators';
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 {
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';
export * from './adapters/io-adapter';
export * from './adapters/ws-adapter';
export * from './adapters';
export * from './errors';
export * from './exceptions';
export { MessageMappingProperties } from './gateway-metadata-explorer';

View File

@@ -1,7 +1,8 @@
{
"name": "@nestjs/websockets",
"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",
"license": "MIT",
"repository": {
@@ -9,8 +10,7 @@
"url": "https://github.com/nestjs/nest"
},
"dependencies": {
"iterare": "0.0.8",
"socket.io": "^2.1.1"
"iterare": "0.0.8"
},
"peerDependencies": {
"@nestjs/common": "^5.0.0",

View File

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

View File

@@ -129,8 +129,9 @@ describe('WsContextCreator', () => {
module,
);
const data = 'test';
expect(proxy(null, data)).to.eventually.rejectedWith(WsException);
proxy(null, data).catch(err =>
expect(err).to.be.instanceOf(WsException),
);
});
});
});
@@ -164,7 +165,7 @@ describe('WsContextCreator', () => {
it('should throw exception when "tryActivate" returns false', () => {
const guardsFn = contextCreator.createGuardsFn([null], null, null);
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 { WsExceptionsHandler } from '../../exceptions/ws-exceptions-handler';
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', () => {
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 { 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', () => {
let instance: SocketServerProvider;
@@ -14,7 +19,7 @@ describe('SocketServerProvider', () => {
mockContainer = sinon.mock(socketsContainer);
instance = new SocketServerProvider(
socketsContainer,
new ApplicationConfig(new IoAdapter()),
new ApplicationConfig(new NoopAdapter()),
);
});
describe('scanForSocketServer', () => {

View File

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

View File

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

View File

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