* Webchat Upgrade and Version number change

Signed-off-by: Srinaath Ravichandran <srravich@microsoft.com>

* Handle change to middleware (#2177)

* Handle change to middleware

* Updated babel preset env

Signed-off-by: Srinaath Ravichandran <srravich@microsoft.com>

* Reverted change to babel

Signed-off-by: Srinaath Ravichandran <srravich@microsoft.com>

Locks updated to pre webchat change

Signed-off-by: Srinaath Ravichandran <srravich@microsoft.com>

Post wechat check

Signed-off-by: Srinaath Ravichandran <srravich@microsoft.com>

Prevent hoisting bf-chatdown

Signed-off-by: Srinaath Ravichandran <srravich@microsoft.com>

* Updated babel preset env

Signed-off-by: Srinaath Ravichandran <srravich@microsoft.com>

* fix: Webchat socket instantiation delay (#2179)

* Updated websocket server code to backup messages if it is not connected
* Refactored more occurances of socket send
* Renaming variables
* Added unit test to make sure backedup messages are cleared before connection starts
* Lint fix

* Updated release date

Signed-off-by: Srinaath Ravichandran <srravich@microsoft.com>

* All tests working

Signed-off-by: Srinaath Ravichandran <srravich@microsoft.com>

Co-authored-by: Srinaath Ravichandran <srravich@microsoft.com>
This commit is contained in:
Srinaath Ravichandran 2020-08-31 08:55:29 -07:00 коммит произвёл GitHub
Родитель 9042e38719
Коммит 7829dbd1f8
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
24 изменённых файлов: 6331 добавлений и 1200 удалений

Просмотреть файл

@ -4,14 +4,18 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## Unreleased
## v4.10.0 - 2020 - 08 - 21
## Added
- [client] Added a log panel entry at the start of a conversation that displays the bot endpoint in PR [2149](https://github.com/microsoft/BotFramework-Emulator/pull/2149)
- [client] - Bumped `botframework-webchat` to v4.10.0 in PR [2177](https://github.com/microsoft/BotFramework-Emulator/pull/2177)
## Fixed
- [client] Added missing content to signed in view of Cosmos DB service dialog and fixed product page link in PR [2150](https://github.com/microsoft/BotFramework-Emulator/pull/2150)
- [docs] Modified CONTRIBUTING.md to include updated information about global dependencies required to build from source in PR [2153](https://github.com/microsoft/BotFramework-Emulator/pull/2153)
- [client] Fixed a bug where trying to open the sign-in link on an OAuth card would open the file explorer if ngrok was not configured in PR [2155](https://github.com/microsoft/BotFramework-Emulator/pull/2155)
- [client] Change to a warning message in inspector when clicking on LUIS trace [2160](https://github.com/microsoft/BotFramework-Emulator/pull/2160)
- [client] Handle result from webchat middleware gracefully [2177](https://github.com/microsoft/BotFramework-Emulator/pull/2177)
- [client] Handle Webchat socket instantiation delay [2179](https://github.com/microsoft/BotFramework-Emulator/pull/2179)
## v4.9.0 - 2020 - 05 - 11
## Added

Просмотреть файл

@ -14,5 +14,12 @@
"packages/extensions/json",
"packages/tools"
],
"command": {
"bootstrap": {
"nohoist": [
"@microsoft/bf-chatdown"
]
}
},
"version": "independent"
}

7268
package-lock.json сгенерированный

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@ -48,7 +48,7 @@
"@babel/plugin-proposal-object-rest-spread": "^7.0.0",
"@babel/plugin-transform-react-jsx": "^7.0.0",
"@babel/plugin-transform-runtime": "^7.4.4",
"@babel/preset-env": "^7.1.0",
"@babel/preset-env": "^7.11.0",
"@babel/preset-typescript": "^7.1.0",
"@types/enzyme": "^3.1.10",
"@types/jest": "24.0.13",
@ -93,10 +93,10 @@
"url-loader": "^1.0.1",
"webpack": "^4.32.2",
"webpack-cli": "^3.3.2",
"webpack-dev-server": "^3.4.1"
"webpack-dev-server": "^3.4.1",
"@babel/runtime": "^7.1.5"
},
"dependencies": {
"@babel/runtime": "^7.1.5",
"@bfemulator/app-shared": "^1.0.0",
"@bfemulator/sdk-client": "^1.0.0",
"@bfemulator/sdk-shared": "^1.0.0",
@ -107,8 +107,8 @@
"base64url": "3.0.0",
"botframework-config": "4.4.0",
"botframework-schema": "^4.3.4",
"botframework-webchat": "4.9.0",
"botframework-webchat-core": "4.9.0",
"botframework-webchat": "4.10.0",
"botframework-webchat-core": "4.10.0",
"eslint-plugin-react": "^7.12.3",
"markdown-it": "^8.4.2",
"react": "16.8.6",

Просмотреть файл

@ -32,7 +32,6 @@
//
import { ValueTypes, RestartConversationStatus } from '@bfemulator/app-shared';
import { User } from '@bfemulator/sdk-shared';
import { Activity, ActivityTypes } from 'botframework-schema';
import ReactWebChat, { createStyleSet } from 'botframework-webchat';
import * as React from 'react';
@ -139,6 +138,11 @@ export class Chat extends PureComponent<ChatProps, ChatState> {
}
private activityWrapper(next, card, children): ReactNode {
let childrenContents = null;
const middlewareResult = next(card);
if (middlewareResult) {
childrenContents = middlewareResult(children);
}
return (
<OuterActivityWrapperContainer
card={card}
@ -147,7 +151,7 @@ export class Chat extends PureComponent<ChatProps, ChatState> {
onItemRendererClick={this.onItemRendererClick}
onItemRendererKeyDown={this.onItemRendererKeyDown}
>
{next(card)(children)}
{childrenContents}
</OuterActivityWrapperContainer>
);
}

Просмотреть файл

@ -1,7 +1,7 @@
{
"name": "@bfemulator/main",
"packagename": "BotFramework-Emulator",
"version": "4.9.0",
"version": "4.10.0",
"private": true,
"description": "Development tool for the Microsoft Bot Framework. Allows developers to test and debug bots on localhost.",
"main": "./app/server/main.js",
@ -79,7 +79,7 @@
"@babel/plugin-proposal-object-rest-spread": "^7.0.0",
"@babel/plugin-transform-react-jsx": "^7.0.0",
"@babel/plugin-transform-runtime": "^7.4.4",
"@babel/preset-env": "^7.1.0",
"@babel/preset-env": "^7.11.0",
"@babel/preset-typescript": "^7.1.0",
"@types/async": "^2.0.47",
"@types/chokidar": "^1.7.5",

Просмотреть файл

@ -42,13 +42,17 @@ jest.mock('../../../../utils/oauthLinkEncoder', () => ({
}),
}));
jest.mock('../../../../webSocketServer', () => ({
WebSocketServer: {
getSocketByConversationId: () => ({
send: jest.fn(),
}),
},
}));
const mockSocket = {
send: jest.fn(),
};
jest.mock('../../../../webSocketServer', () => {
return {
WebSocketServer: {
sendToSubscribers: (...args) => mockSocket.send(...args),
},
};
});
describe('replyToActivity route middleware', () => {
const mockReq: any = {

Просмотреть файл

@ -58,10 +58,7 @@ export function createReplyToActivityHandler(emulatorServer: EmulatorRestServer)
// post activity
activity = conversation.prepActivityToBeSentToUser(conversation.user.id, activity);
const payload = { activities: [activity] };
const socket = WebSocketServer.getSocketByConversationId(conversationId);
socket && socket.send(JSON.stringify(payload));
WebSocketServer.sendToSubscribers(conversation.conversationId, activity);
res.send(HttpStatus.OK, { id: activity.id });
res.end();
};

Просмотреть файл

@ -35,13 +35,17 @@ import * as HttpStatus from 'http-status-codes';
import { sendActivityToConversation } from './sendActivityToConversation';
jest.mock('../../../../webSocketServer', () => ({
WebSocketServer: {
getSocketByConversationId: () => ({
send: jest.fn(),
}),
},
}));
const mockSocket = {
send: jest.fn(),
};
jest.mock('../../../../webSocketServer', () => {
return {
WebSocketServer: {
sendToSubscribers: (...args) => mockSocket.send(...args),
},
};
});
const mockSendErrorResponse = jest.fn();
jest.mock('../../../../utils/sendErrorResponse', () => ({

Просмотреть файл

@ -48,10 +48,7 @@ export function sendActivityToConversation(req: Request, res: Response, next: Ne
// post activity
activity = conversation.prepActivityToBeSentToUser(conversation.user.id, activity);
const payload = { activities: [activity] };
const socket = WebSocketServer.getSocketByConversationId(conversation.conversationId);
socket && socket.send(JSON.stringify(payload));
WebSocketServer.sendToSubscribers(conversation.conversationId, activity);
res.send(HttpStatus.OK, { id: activity.id });
res.end();
} catch (err) {

Просмотреть файл

@ -47,9 +47,7 @@ export function sendHistoryToConversation(req: Request, res: Response, next: Nex
for (const activity of activities) {
try {
const updatedActivity = conversation.prepActivityToBeSentToUser(conversation.user.id, activity);
const payload = { activities: [updatedActivity] };
const socket = WebSocketServer.getSocketByConversationId(conversation.conversationId);
socket && socket.send(JSON.stringify(payload));
WebSocketServer.sendToSubscribers(conversation.conversationId, updatedActivity);
successCount++;
} catch (err) {
if (firstErrorMessage === '') {

Просмотреть файл

@ -43,11 +43,14 @@ jest.mock('../../../utils/sendErrorResponse', () => ({
const mockSocket = {
send: jest.fn(),
};
jest.mock('../../../webSocketServer', () => ({
WebSocketServer: {
getSocketByConversationId: () => mockSocket,
},
}));
jest.mock('../../../webSocketServer', () => {
return {
WebSocketServer: {
sendToSubscribers: (...args) => mockSocket.send(...args),
},
};
});
describe('postActivity handler', () => {
beforeEach(() => {
@ -56,14 +59,16 @@ describe('postActivity handler', () => {
});
it('should return a 200 and the id of the posted activity', async () => {
const activity = {
id: 'activity1',
};
const mockEmulatorServer: any = {
logger: {
logMessage: jest.fn(),
},
};
const activity = {
id: 'activity1',
};
const req: any = {
body: activity,
conversation: {
@ -72,6 +77,7 @@ describe('postActivity handler', () => {
response: {},
statusCode: HttpStatus.OK,
}),
conversationId: 'convo1',
},
params: {
conversationId: 'convo1',
@ -88,11 +94,7 @@ describe('postActivity handler', () => {
expect(res.send).toHaveBeenCalledWith(HttpStatus.OK, activity);
expect(res.end).toHaveBeenCalled();
expect(next).toHaveBeenCalled();
expect(mockSocket.send).toHaveBeenCalledWith(
JSON.stringify({
activities: [{ ...req.body, id: 'activity1' }],
})
);
expect(mockSocket.send).toHaveBeenCalledWith(req.params.conversationId, activity);
});
it('should return a 200 but not send the /INSPECT open command over the web socket', async () => {

Просмотреть файл

@ -85,9 +85,7 @@ export function createPostActivityHandler(emulatorServer: EmulatorRestServer) {
res.end();
return next();
}
const payload = { activities: [{ ...activity, id: activity.id }] };
const socket = WebSocketServer.getSocketByConversationId(conversation.conversationId);
socket && socket.send(JSON.stringify(payload));
WebSocketServer.sendToSubscribers(conversation.conversationId, activity);
}
} catch (err) {
sendErrorResponse(req, res, next, err);

Просмотреть файл

@ -38,11 +38,14 @@ import { createFeedActivitiesAsTranscriptHandler } from './feedActivitiesAsTrans
const mockSocket = {
send: jest.fn(),
};
jest.mock('../../../webSocketServer', () => ({
WebSocketServer: {
getSocketByConversationId: () => mockSocket,
},
}));
jest.mock('../../../webSocketServer', () => {
return {
WebSocketServer: {
sendToSubscribers: (...args) => mockSocket.send(...args),
},
};
});
describe('feedActivitiesAsTranscript handler', () => {
it('should send a 200 after sending all activities over the web socket connection', () => {

Просмотреть файл

@ -52,9 +52,7 @@ export function createFeedActivitiesAsTranscriptHandler(emulatorServer: Emulator
}
activities = conversation.prepTranscriptActivities(activities);
activities.forEach(activity => {
const payload = { activities: [activity] };
const socket = WebSocketServer.getSocketByConversationId(conversation.conversationId);
socket && socket.send(JSON.stringify(payload));
WebSocketServer.sendToSubscribers(conversation.conversationId, activity);
emulatorServer.logger.logActivity(conversation.conversationId, activity, activity.recipient.role);
});
} catch (e) {

Просмотреть файл

@ -31,6 +31,8 @@
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
import { Activity } from 'botframework-schema';
import { WebSocketServer } from './webSocketServer';
const mockWSServer = {
@ -157,4 +159,84 @@ describe('WebSocketServer', () => {
expect(Object.keys((WebSocketServer as any)._servers)).toEqual([mockConversationId]);
expect(mockWSServer.handleUpgrade).toHaveBeenCalledTimes(1);
});
it('should clear the messages backed up before websocket connection is started', async () => {
let onConnectionFunction = null;
let websocketHandler = null;
(WebSocketServer as any)._restServer = undefined;
(WebSocketServer as any)._servers = {};
(WebSocketServer as any)._sockets = {};
mockWSServer.on.mockImplementation((event, implementation) => {
if (event === 'connection') {
onConnectionFunction = implementation;
}
});
mockCreateServer.mockReturnValueOnce({
address: () => ({ port: 55523 }),
get: (route, handler) => {
websocketHandler = handler;
},
listen: jest.fn((_port, cb) => {
cb();
}),
once: jest.fn(),
});
await WebSocketServer.init();
WebSocketServer.queueActivities('conv-123', { id: 'activity-1' } as Activity);
WebSocketServer.queueActivities('conv-234', { id: 'activity-1' } as Activity);
WebSocketServer.queueActivities('conv-123', { id: 'activity-2' } as Activity);
WebSocketServer.queueActivities('conv-234', { id: 'activity-2' } as Activity);
websocketHandler(
{
params: {
conversationId: 'conv-234',
},
},
{
claimUpgrade: jest.fn(() => {
return {
head: jest.fn(),
socket: jest.fn(),
};
}),
}
);
const socketSendMock = jest.fn();
onConnectionFunction({
send: socketSendMock,
on: jest.fn(),
});
expect(socketSendMock).toHaveBeenCalledTimes(2);
expect(socketSendMock).toHaveBeenNthCalledWith(1, JSON.stringify({ activities: [{ id: 'activity-1' }] }));
expect(socketSendMock).toHaveBeenNthCalledWith(2, JSON.stringify({ activities: [{ id: 'activity-2' }] }));
socketSendMock.mockClear();
websocketHandler(
{
params: {
conversationId: 'conv-123',
},
},
{
claimUpgrade: jest.fn(() => {
return {
head: jest.fn(),
socket: jest.fn(),
};
}),
}
);
onConnectionFunction({
send: socketSendMock,
on: jest.fn(),
});
expect(socketSendMock).toHaveBeenCalledTimes(2);
expect(socketSendMock).toHaveBeenNthCalledWith(1, JSON.stringify({ activities: [{ id: 'activity-1' }] }));
expect(socketSendMock).toHaveBeenNthCalledWith(2, JSON.stringify({ activities: [{ id: 'activity-2' }] }));
});
});

Просмотреть файл

@ -33,6 +33,7 @@
import { createServer, Next, Request, Response, Server } from 'restify';
import { Server as WSServer } from 'ws';
import { Activity } from 'botframework-schema';
// can't import WebSocket type from ws types :|
interface WebSocket {
@ -45,11 +46,40 @@ export class WebSocketServer {
private static _restServer: Server;
private static _servers: { [conversationId: string]: WSServer } = {};
private static _sockets: { [conversationId: string]: WebSocket } = {};
private static queuedMessages: { [conversationId: string]: Activity[] } = {};
private static sendBackedUpMessages(conversationId: string, socket: WebSocket) {
if (this.queuedMessages[conversationId]) {
while (this.queuedMessages[conversationId].length > 0) {
const activity: Activity = this.queuedMessages[conversationId].shift();
const payload = { activities: [activity] };
socket.send(JSON.stringify(payload));
}
}
}
public static getSocketByConversationId(conversationId: string): WebSocket {
return this._sockets[conversationId];
}
public static queueActivities(conversationId: string, activity: Activity): void {
if (!this.queuedMessages[conversationId]) {
this.queuedMessages[conversationId] = [];
}
this.queuedMessages[conversationId].push(activity);
}
public static sendToSubscribers(conversationId: string, activity: Activity): void {
const socket = this._sockets[conversationId];
if (socket) {
const payload = { activities: [activity] };
this.sendBackedUpMessages(conversationId, socket);
socket.send(JSON.stringify(payload));
} else {
this.queueActivities(conversationId, activity);
}
}
/** Initializes the server and returns the port it is listening on, or if already initialized,
* is a no-op.
*/
@ -69,10 +99,13 @@ export class WebSocketServer {
noServer: true,
});
wsServer.on('connection', (socket, req) => {
this.sendBackedUpMessages(conversationId, socket);
this._sockets[conversationId] = socket;
socket.on('close', (code, reason) => {
delete this._servers[conversationId];
delete this._sockets[conversationId];
delete this.queuedMessages[conversationId];
});
});
// upgrade the connection to a ws connection

Просмотреть файл

@ -24,7 +24,7 @@
"@babel/plugin-proposal-object-rest-spread": "^7.0.0",
"@babel/plugin-transform-react-jsx": "^7.0.0",
"@babel/plugin-transform-runtime": "^7.4.4",
"@babel/preset-env": "^7.1.0",
"@babel/preset-env": "^7.11.0",
"@babel/preset-typescript": "^7.1.0",
"@types/jest": "24.0.13",
"babel-jest": "24.8.0",

Просмотреть файл

@ -31,7 +31,7 @@
"@babel/plugin-proposal-object-rest-spread": "^7.0.0",
"@babel/plugin-transform-react-jsx": "^7.0.0",
"@babel/plugin-transform-runtime": "^7.4.4",
"@babel/preset-env": "^7.1.0",
"@babel/preset-env": "^7.11.0",
"@babel/preset-typescript": "^7.1.0",
"@types/deep-diff": "^1.0.0",
"@types/jest": "24.0.13",

Просмотреть файл

@ -31,7 +31,7 @@
"@babel/plugin-proposal-object-rest-spread": "^7.0.0",
"@babel/plugin-transform-react-jsx": "^7.0.0",
"@babel/plugin-transform-runtime": "^7.4.4",
"@babel/preset-env": "^7.1.0",
"@babel/preset-env": "^7.11.0",
"@babel/preset-typescript": "^7.1.0",
"@types/jest": "24.0.13",
"@types/lscache": "^1.0.29",

Просмотреть файл

@ -30,7 +30,7 @@
"@babel/plugin-proposal-object-rest-spread": "^7.0.0",
"@babel/plugin-transform-react-jsx": "^7.0.0",
"@babel/plugin-transform-runtime": "^7.4.4",
"@babel/preset-env": "^7.1.0",
"@babel/preset-env": "^7.11.0",
"@babel/preset-typescript": "^7.1.0",
"@babel/runtime": "^7.1.5",
"@types/jest": "24.0.13",

Просмотреть файл

@ -22,7 +22,7 @@
"@babel/plugin-proposal-object-rest-spread": "^7.0.0",
"@babel/plugin-transform-react-jsx": "^7.0.0",
"@babel/plugin-transform-runtime": "^7.4.4",
"@babel/preset-env": "^7.1.0",
"@babel/preset-env": "^7.11.0",
"@babel/preset-typescript": "^7.1.0",
"@types/jest": "24.0.13",
"babel-jest": "24.8.0",

Просмотреть файл

@ -24,7 +24,7 @@
"@babel/plugin-proposal-object-rest-spread": "^7.0.0",
"@babel/plugin-transform-react-jsx": "^7.0.0",
"@babel/plugin-transform-runtime": "^7.4.4",
"@babel/preset-env": "^7.1.0",
"@babel/preset-env": "^7.11.0",
"@babel/preset-typescript": "^7.1.0",
"@types/jest": "24.0.13",
"babel-jest": "24.8.0",

Просмотреть файл

@ -23,7 +23,7 @@
"@babel/plugin-proposal-object-rest-spread": "^7.0.0",
"@babel/plugin-transform-react-jsx": "^7.0.0",
"@babel/plugin-transform-runtime": "^7.4.4",
"@babel/preset-env": "^7.1.0",
"@babel/preset-env": "^7.11.0",
"@babel/preset-typescript": "^7.1.0",
"@types/jest": "24.0.13",
"@types/react": "16.9.17",