Skip to content

Commit

Permalink
[Streaming, Preview] rewrite useWebSocket for low-level usage, remove…
Browse files Browse the repository at this point in the history
… from processActivity (#1433)

* rewrite useWebSocket for low-level usage, remove from processActivity

* [4.6.x] Fix set-dependency-versions script (#1438)

* change set-dependency-versions script to use ~ not ^, update changed libs

* use pinned versions for intra-dependencies per @cleemullins feedback

* [4.6.x] Pin TypeScript devDependency in each library. (Fixes #1436) (#1437)

* move pinned [email protected] devDependency into each package
* pin transcripts/ botbuilder dependencies, fix import in Skype Middleware
* bump to [email protected]

* remove unnecessary enableWebSockets flag, cleanup useWebSocket()

* create Node Interfaces for PR feedback
  • Loading branch information
stevengum authored Dec 2, 2019
1 parent ecfcf7d commit e6e7169
Show file tree
Hide file tree
Showing 26 changed files with 259 additions and 236 deletions.
3 changes: 2 additions & 1 deletion libraries/botbuilder-ai/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@
"nock": "^10.0.3",
"nyc": "^11.4.1",
"source-map-support": "^0.5.3",
"ts-node": "^4.1.0"
"ts-node": "^4.1.0",
"typescript": "3.5.3"
},
"scripts": {
"test": "tsc && nyc mocha tests/",
Expand Down
3 changes: 2 additions & 1 deletion libraries/botbuilder-applicationinsights/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@
"mocha": "^5.2.0",
"nyc": "^11.4.1",
"source-map-support": "^0.5.3",
"ts-node": "^4.1.0"
"ts-node": "^4.1.0",
"typescript": "3.5.3"
},
"scripts": {
"test": "tsc && nyc mocha tests/",
Expand Down
5 changes: 3 additions & 2 deletions libraries/botbuilder-azure/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,11 @@
"@types/semaphore": "^1.1.0",
"codelyzer": "^4.1.0",
"mocha": "^5.2.0",
"nyc": "^11.4.1",
"nock": "^10.0.3",
"nyc": "^11.4.1",
"source-map-support": "^0.5.3",
"ts-node": "^4.1.0"
"ts-node": "^4.1.0",
"typescript": "3.5.3"
},
"scripts": {
"test": "tsc && nyc mocha tests/",
Expand Down
1 change: 1 addition & 0 deletions libraries/botbuilder-core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
"nyc": "^11.4.1",
"source-map-support": "^0.5.3",
"ts-node": "^4.1.0",
"typescript": "3.5.3",
"unzip": "^0.1.11"
},
"scripts": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License.
*/
import { Activity, Middleware, TurnContext } from 'botbuilder-core';
import { Activity } from 'botframework-schema';
import { Middleware } from './middlewareSet';
import { TurnContext } from './turnContext';


/**
Expand Down
3 changes: 2 additions & 1 deletion libraries/botbuilder-dialogs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@
"mocha": "^5.2.0",
"nyc": "^11.4.1",
"source-map-support": "^0.5.3",
"ts-node": "^4.1.0"
"ts-node": "^4.1.0",
"typescript": "3.5.3"
},
"scripts": {
"test": "tsc && nyc mocha tests/",
Expand Down
1 change: 1 addition & 0 deletions libraries/botbuilder-testing/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
"nyc": "^11.4.1",
"source-map-support": "^0.5.3",
"ts-node": "^4.1.0",
"typescript": "3.5.3",
"unzip": "^0.1.11",
"uuid": "^3.3.2"
},
Expand Down
1 change: 1 addition & 0 deletions libraries/botbuilder/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
"nyc": "^11.4.1",
"source-map-support": "^0.5.3",
"ts-node": "^4.1.0",
"typescript": "3.5.3",
"uuid": "^3.3.2"
},
"scripts": {
Expand Down
68 changes: 33 additions & 35 deletions libraries/botbuilder/src/botFrameworkAdapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@
* Licensed under the MIT License.
*/

import { IncomingMessage } from 'http';
import { STATUS_CODES } from 'http';
import * as os from 'os';

import { Activity, ActivityTypes, BotAdapter, BotCallbackHandlerKey, ChannelAccount, ConversationAccount, ConversationParameters, ConversationReference, ConversationsResult, IUserTokenProvider, ResourceResponse, TokenResponse, TurnContext } from 'botbuilder-core';
import { AuthenticationConstants, ChannelValidation, ConnectorClient, EmulatorApiClient, GovernmentConstants, GovernmentChannelValidation, JwtTokenValidation, MicrosoftAppCredentials, SimpleCredentialProvider, TokenApiClient, TokenStatus, TokenApiModels } from 'botframework-connector';
import { IReceiveRequest, ISocket, IStreamingTransportServer, NamedPipeServer, NodeWebSocketFactory, NodeWebSocketFactoryBase, RequestHandler, StreamingResponse, WebSocketServer } from 'botframework-streaming';
import { INodeBuffer, INodeSocket, IReceiveRequest, ISocket, IStreamingTransportServer, NamedPipeServer, NodeWebSocketFactory, NodeWebSocketFactoryBase, RequestHandler, StreamingResponse, WebSocketServer } from 'botframework-streaming';

import { StreamingHttpClient, TokenResolver } from './streaming';

Expand Down Expand Up @@ -135,12 +135,7 @@ export interface BotFrameworkAdapterSettings {
channelService?: string;

/**
* Optional. The option to determine if this adapter accepts WebSocket connections
*/
enableWebSockets?: boolean;

/**
* Optional. Used to pass in a NodeWebSocketFactoryBase instance. Allows bot to accept WebSocket connections.
* Optional. Used to pass in a NodeWebSocketFactoryBase instance.
*/
webSocketFactory?: NodeWebSocketFactoryBase;
}
Expand Down Expand Up @@ -268,12 +263,7 @@ export class BotFrameworkAdapter extends BotAdapter implements IUserTokenProvide
this.credentials.oAuthScope = GovernmentConstants.ToChannelFromBotOAuthScope;
}

// If the developer wants to use WebSockets, but didn't provide a WebSocketFactory,
// create a NodeWebSocketFactory.
if (this.settings.enableWebSockets && !this.settings.webSocketFactory) {
this.webSocketFactory = new NodeWebSocketFactory();
}

// If a NodeWebSocketFactoryBase was passed in, set it on the BotFrameworkAdapter.
if (this.settings.webSocketFactory) {
this.webSocketFactory = this.settings.webSocketFactory;
}
Expand Down Expand Up @@ -755,9 +745,6 @@ export class BotFrameworkAdapter extends BotAdapter implements IUserTokenProvide
* ```
*/
public async processActivity(req: WebRequest, res: WebResponse, logic: (context: TurnContext) => Promise<any>): Promise<void> {
if (this.settings.enableWebSockets && req.method === GET && (req.headers.Upgrade || req.headers.upgrade)) {
return this.useWebSocket(req, res, logic);
}

let body: any;
let status: number;
Expand Down Expand Up @@ -1148,39 +1135,41 @@ export class BotFrameworkAdapter extends BotAdapter implements IUserTokenProvide
/**
* Process the initial request to establish a long lived connection via a streaming server.
* @param req The connection request.
* @param res The response sent on error or connection termination.
* @param logic The logic that will handle incoming requests.
* @param socket The raw socket connection between the bot (server) and channel/caller (client).
* @param head The first packet of the upgraded stream.
* @param logic The logic that handles incoming streaming requests for the lifetime of the WebSocket connection.
*/
public async useWebSocket(req: WebRequest, res: WebResponse, logic: (context: TurnContext) => Promise<any>): Promise<void> {
public async useWebSocket(req: WebRequest, socket: INodeSocket, head: INodeBuffer, logic: (context: TurnContext) => Promise<any>): Promise<void> {
// Use the provided NodeWebSocketFactoryBase on BotFrameworkAdapter construction,
// otherwise create a new NodeWebSocketFactory.
const webSocketFactory = this.webSocketFactory || new NodeWebSocketFactory();

if (!logic) {
throw new Error('Streaming logic needs to be provided to `useWebSocket`');
}

if (!this.webSocketFactory || !this.webSocketFactory.createWebSocket) {
throw new Error('BotFrameworkAdapter must have a WebSocketFactory in order to support streaming.');
}

this.logic = logic;

// Restify-specific check.
if (typeof((res as any).claimUpgrade) !== 'function') {
throw new Error('ClaimUpgrade is required for creating WebSocket connection.');
}

try {
await this.authenticateConnection(req, this.settings.channelService);
} catch (err) {
// Set the correct status code for the socket to send back to the channel.
res.status(StatusCodes.UNAUTHORIZED);
res.send(err.message);
// If the authenticateConnection call fails, send back the correct error code and close
// the connection.
if (typeof(err.message) === 'string' && err.message.toLowerCase().startsWith('unauthorized')) {
abortWebSocketUpgrade(socket, 401);
} else if (typeof(err.message) === 'string' && err.message.toLowerCase().startsWith(`'authheader'`)) {
abortWebSocketUpgrade(socket, 400);
} else {
abortWebSocketUpgrade(socket, 500);
}

// Re-throw the error so the developer will know what occurred.
throw err;
}

const upgrade = (res as any).claimUpgrade();
const socket = await this.webSocketFactory.createWebSocket(req as IncomingMessage, upgrade.socket, upgrade.head);
const nodeWebSocket = await webSocketFactory.createWebSocket(req, socket, head);

await this.startWebSocket(socket);
await this.startWebSocket(nodeWebSocket);
}

private async authenticateConnection(req: WebRequest, channelService?: string): Promise<void> {
Expand Down Expand Up @@ -1303,4 +1292,13 @@ function delay(timeout: number): Promise<void> {
return new Promise((resolve) => {
setTimeout(resolve, timeout);
});
}

function abortWebSocketUpgrade(socket: INodeSocket, code: number) {
if (socket.writable) {
const connectionHeader = `Connection: 'close'\r\n`;
socket.write(`HTTP/1.1 ${code} ${STATUS_CODES[code]}\r\n${connectionHeader}\r\n`);
}

socket.destroy();
}
Loading

0 comments on commit e6e7169

Please sign in to comment.