From 0f73d2bde77bfeb20b275104386ce85d16759f3e Mon Sep 17 00:00:00 2001 From: Eugene Olonov Date: Wed, 11 Jan 2023 16:16:22 -0800 Subject: [PATCH 1/5] fix: bind composer server to localhost --- .../client/src/components/WebChat/WebChatPanel.tsx | 4 ++-- .../WebChat/utils/conversationService.ts | 2 +- Composer/packages/client/src/constants.tsx | 2 +- Composer/packages/electron-server/src/main.ts | 5 +++-- .../packages/server/src/controllers/publisher.ts | 3 ++- .../server/src/directline/store/dlServerState.ts | 4 +++- .../server/src/directline/utils/webSocketServer.ts | 4 +++- Composer/packages/server/src/server.ts | 9 ++++++--- Composer/packages/server/src/settings/env.ts | 4 +++- extensions/localPublish/src/index.ts | 3 ++- extensions/localPublish/src/runtimeLogServer.ts | 14 +++++++++++--- 11 files changed, 37 insertions(+), 17 deletions(-) diff --git a/Composer/packages/client/src/components/WebChat/WebChatPanel.tsx b/Composer/packages/client/src/components/WebChat/WebChatPanel.tsx index 6dc8adc948..bd914198bc 100644 --- a/Composer/packages/client/src/components/WebChat/WebChatPanel.tsx +++ b/Composer/packages/client/src/components/WebChat/WebChatPanel.tsx @@ -22,7 +22,7 @@ import { WebChatHeader } from './WebChatHeader'; import { WebChatComposer } from './WebChatComposer'; import { BotSecret, ChatData, RestartOption } from './types'; -const BASEPATH = process.env.PUBLIC_URL || 'http://localhost:3000/'; +const BASEPATH = process.env.PUBLIC_URL || `http://${location.hostname}:3000/`; // TODO: Refactor to include Webchat header component as a part of WebchatComposer to avoid this variable. const webChatHeaderHeight = '85px'; @@ -67,7 +67,7 @@ export const WebChatPanel: React.FC = ({ const conversationServerPort = await conversationService.setUpConversationServer(); try { // set up Web Chat traffic listener - webChatTrafficChannel.current = new WebSocket(`ws://localhost:${conversationServerPort}/ws/traffic`); + webChatTrafficChannel.current = new WebSocket(`ws://${location.hostname}:${conversationServerPort}/ws/traffic`); if (webChatTrafficChannel.current) { webChatTrafficChannel.current.onmessage = (event) => { const data: diff --git a/Composer/packages/client/src/components/WebChat/utils/conversationService.ts b/Composer/packages/client/src/components/WebChat/utils/conversationService.ts index e17cbda0e4..541695cbd3 100644 --- a/Composer/packages/client/src/components/WebChat/utils/conversationService.ts +++ b/Composer/packages/client/src/components/WebChat/utils/conversationService.ts @@ -80,7 +80,7 @@ export class ConversationService { secret, domain: `${this.directlineHostUrl}/v3/directline`, webSocket: true, - streamUrl: `ws://localhost:${this.restServerForWSPort}/ws/conversation/${conversationId}`, + streamUrl: `ws://${location.hostname}:${this.restServerForWSPort}/ws/conversation/${conversationId}`, }); return directLine; } diff --git a/Composer/packages/client/src/constants.tsx b/Composer/packages/client/src/constants.tsx index 9532120c5c..9634860dca 100644 --- a/Composer/packages/client/src/constants.tsx +++ b/Composer/packages/client/src/constants.tsx @@ -527,7 +527,7 @@ export const defaultTeamsManifest: TeamsManifest = { }; export const defaultBotPort = 3979; -export const defaultBotEndpoint = `http://localhost:${defaultBotPort}/api/messages`; +export const defaultBotEndpoint = `http://${location.hostname}:${defaultBotPort}/api/messages`; const DAYS_IN_MS = 1000 * 60 * 60 * 24; export const SURVEY_PARAMETERS = { diff --git a/Composer/packages/electron-server/src/main.ts b/Composer/packages/electron-server/src/main.ts index e28be0c972..6f41a923d5 100644 --- a/Composer/packages/electron-server/src/main.ts +++ b/Composer/packages/electron-server/src/main.ts @@ -41,13 +41,14 @@ const waitForMainWindowToShow = new Promise((resolve) => { // webpack dev server runs on :3000 const getBaseUrl = () => { + const host = process.env.COMPOSER_HOSTNAME ?? 'localhost'; if (isDevelopment) { - return 'http://localhost:3000/'; + return `http://${host}:3000/`; } if (!serverPort) { throw new Error('getBaseUrl() called before serverPort is defined.'); } - return `http://localhost:${serverPort}/`; + return `http://${host}:${serverPort}/`; }; // set production flag diff --git a/Composer/packages/server/src/controllers/publisher.ts b/Composer/packages/server/src/controllers/publisher.ts index bba3599b48..6544dae47a 100644 --- a/Composer/packages/server/src/controllers/publisher.ts +++ b/Composer/packages/server/src/controllers/publisher.ts @@ -15,6 +15,7 @@ import AssetService from '../services/asset'; import logger from '../logger'; import { LocationRef } from '../models/bot/interface'; import { TelemetryService } from '../services/telemetry'; +import { serverHostname } from '../settings/env'; const log = logger.extend('publisher-controller'); @@ -318,7 +319,7 @@ export const PublishController = { const pluginMethod = ExtensionContext.extensions.publish[extensionName].methods.setupRuntimeLogServer; if (typeof pluginMethod === 'function') { try { - const runtimeLogUrl = await pluginMethod.call(null, projectId); + const runtimeLogUrl = await pluginMethod.call(null, projectId, serverHostname); return res.status(200).send(runtimeLogUrl); } catch (ex) { res.status(400).json({ diff --git a/Composer/packages/server/src/directline/store/dlServerState.ts b/Composer/packages/server/src/directline/store/dlServerState.ts index 8c79813312..dfe656ce00 100644 --- a/Composer/packages/server/src/directline/store/dlServerState.ts +++ b/Composer/packages/server/src/directline/store/dlServerState.ts @@ -1,6 +1,8 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +import { serverHostname } from '../../settings/env'; + import { BotEndpoint } from './entities/botEndpoint'; import { Attachments } from './entities/attachments'; import { ConversationSet } from './entities/conversationSet'; @@ -27,7 +29,7 @@ class DLServerContext { conversations: new ConversationSet(), endpoints: new EndpointSet(), attachments: new Attachments(), - serviceUrl: serverPort ? `http://localhost:${serverPort}` : '', + serviceUrl: serverPort ? `http://${serverHostname}:${serverPort}` : '', dispatchers: { getDefaultEndpoint: this.getDefaultEndpoint, updateConversation: this.updateConversation, diff --git a/Composer/packages/server/src/directline/utils/webSocketServer.ts b/Composer/packages/server/src/directline/utils/webSocketServer.ts index 68da273c46..4f1073e8b1 100644 --- a/Composer/packages/server/src/directline/utils/webSocketServer.ts +++ b/Composer/packages/server/src/directline/utils/webSocketServer.ts @@ -13,6 +13,8 @@ import { ConversationNetworkTrafficItem, } from '@botframework-composer/types'; +import { serverHostname } from '../../settings/env'; + import log from './logger'; const socketTrafficChannelKey = 'DL_TRAFFIC_SOCKET'; @@ -87,7 +89,7 @@ export class WebSocketServer { }); this.port = port; log(`Using ${port} port for directline`); - this.restServer.listen(port); + this.restServer.listen(port, serverHostname); app.use('/ws/conversation/:conversationId', (req: express.Request, res: express.Response) => { if (!(req as any).claimUpgrade) { diff --git a/Composer/packages/server/src/server.ts b/Composer/packages/server/src/server.ts index ad16cbe04f..d387f6ed1a 100644 --- a/Composer/packages/server/src/server.ts +++ b/Composer/packages/server/src/server.ts @@ -36,6 +36,7 @@ import { mountDirectLineRoutes } from './directline/mountDirectlineRoutes'; import { mountAttachmentRoutes } from './directline/mountAttachmentRoutes'; import { cleanHostedBots } from './utility/cleanHostedBots'; import { getVersion } from './utility/getVersion'; +import { serverHostname } from './settings/env'; // eslint-disable-next-line @typescript-eslint/no-var-requires const session = require('express-session'); @@ -183,12 +184,14 @@ export async function start(electronContext?: ElectronContext): Promise { - server = app.listen(port, () => { + await new Promise((resolve) => { + server = app.listen(port, serverHostname, () => { if (process.env.NODE_ENV === 'production') { // We don't use the debug logger here because we always want it to be shown. // eslint-disable-next-line no-console - console.log(`\n\n${chalk.green('Composer now running at:')}\n\n${chalk.blue(`http://localhost:${port}`)}\n`); + console.log( + `\n\n${chalk.green('Composer now running at:')}\n\n${chalk.blue(`http://${serverHostname}:${port}`)}\n` + ); } resolve(); }); diff --git a/Composer/packages/server/src/settings/env.ts b/Composer/packages/server/src/settings/env.ts index d766182104..e7709b054e 100644 --- a/Composer/packages/server/src/settings/env.ts +++ b/Composer/packages/server/src/settings/env.ts @@ -5,10 +5,12 @@ import childProcess from 'child_process'; import { Path } from '../utility/path'; +export const serverHostname = process.env.COMPOSER_HOSTNAME || 'localhost'; + export const absHosted = process.env.COMPOSER_AUTH_PROVIDER === 'abs-h'; export const absHostRoot = process.env.WEBSITE_HOSTNAME ? `https://${process.env.WEBSITE_HOSTNAME}` - : 'http://localhost:3978'; + : `http://${serverHostname}:3978`; let folder = process.env.COMPOSER_BOTS_FOLDER; if (folder?.endsWith(':')) { diff --git a/extensions/localPublish/src/index.ts b/extensions/localPublish/src/index.ts index 84ef94e938..601f96b3ec 100644 --- a/extensions/localPublish/src/index.ts +++ b/extensions/localPublish/src/index.ts @@ -243,9 +243,10 @@ class LocalPublisher implements PublishPlugin { } }; - setupRuntimeLogServer = async (projectId: string) => { + setupRuntimeLogServer = async (projectId: string, serverHostname?: string) => { await RuntimeLogServer.init({ log: this.composer.log, + hostname: serverHostname, }); return RuntimeLogServer.getRuntimeLogStreamingUrl(projectId); }; diff --git a/extensions/localPublish/src/runtimeLogServer.ts b/extensions/localPublish/src/runtimeLogServer.ts index a891f34fdc..f022fa9dc8 100644 --- a/extensions/localPublish/src/runtimeLogServer.ts +++ b/extensions/localPublish/src/runtimeLogServer.ts @@ -18,12 +18,19 @@ export class RuntimeLogServer { private static servers: WSServer = {}; private static sockets: Record = {}; private static port: number; + private static hostname: string; public static getRuntimeLogStreamingUrl(projectId: string): string { - return `ws://localhost:${this.port}/ws/runtimeLog/${projectId}`; + return `ws://${this.hostname}:${this.port}/ws/runtimeLog/${projectId}`; } - public static async init({ log }: { log: Debugger }): Promise { + public static async init({ + log, + hostname = 'localhost', + }: { + log: Debugger; + hostname?: string; + }): Promise { if (!this.restServer) { const app = express(); this.restServer = http.createServer(app); @@ -42,7 +49,7 @@ export class RuntimeLogServer { return preferredPort; }); log(`Using ${port} port for runtime-log`); - this.restServer.listen(port); + this.restServer.listen(port, hostname); app.use('/ws/runtimeLog/:projectId', (req: Request, res: Response) => { if (!(req as any).claimUpgrade) { @@ -74,6 +81,7 @@ export class RuntimeLogServer { } }); this.port = port; + this.hostname = hostname; return this.port; } } From fd602b0f7ff54b44a78aaa588351934e7986179f Mon Sep 17 00:00:00 2001 From: Eugene Olonov Date: Thu, 12 Jan 2023 10:16:02 -0800 Subject: [PATCH 2/5] Kick CI From 3de20dcdb07f976fad84c6efd936a423f24004d5 Mon Sep 17 00:00:00 2001 From: Eugene Olonov Date: Fri, 13 Jan 2023 09:16:38 -0800 Subject: [PATCH 3/5] Expose host to allow 0.0.0.0 binding --- Composer/packages/electron-server/src/main.ts | 2 +- Composer/packages/server/src/controllers/publisher.ts | 4 ++-- .../server/src/directline/utils/webSocketServer.ts | 4 ++-- Composer/packages/server/src/server.ts | 4 ++-- Composer/packages/server/src/settings/env.ts | 3 ++- Dockerfile | 9 ++++++--- extensions/localPublish/src/index.ts | 3 ++- extensions/localPublish/src/runtimeLogServer.ts | 4 +++- 8 files changed, 20 insertions(+), 13 deletions(-) diff --git a/Composer/packages/electron-server/src/main.ts b/Composer/packages/electron-server/src/main.ts index 6f41a923d5..a3338f5b8a 100644 --- a/Composer/packages/electron-server/src/main.ts +++ b/Composer/packages/electron-server/src/main.ts @@ -41,7 +41,7 @@ const waitForMainWindowToShow = new Promise((resolve) => { // webpack dev server runs on :3000 const getBaseUrl = () => { - const host = process.env.COMPOSER_HOSTNAME ?? 'localhost'; + const host = process.env.COMPOSER_HOST ?? 'localhost'; if (isDevelopment) { return `http://${host}:3000/`; } diff --git a/Composer/packages/server/src/controllers/publisher.ts b/Composer/packages/server/src/controllers/publisher.ts index 6544dae47a..cc04744212 100644 --- a/Composer/packages/server/src/controllers/publisher.ts +++ b/Composer/packages/server/src/controllers/publisher.ts @@ -15,7 +15,7 @@ import AssetService from '../services/asset'; import logger from '../logger'; import { LocationRef } from '../models/bot/interface'; import { TelemetryService } from '../services/telemetry'; -import { serverHostname } from '../settings/env'; +import { serverListenHost, serverHostname } from '../settings/env'; const log = logger.extend('publisher-controller'); @@ -319,7 +319,7 @@ export const PublishController = { const pluginMethod = ExtensionContext.extensions.publish[extensionName].methods.setupRuntimeLogServer; if (typeof pluginMethod === 'function') { try { - const runtimeLogUrl = await pluginMethod.call(null, projectId, serverHostname); + const runtimeLogUrl = await pluginMethod.call(null, projectId, serverHostname, serverListenHost); return res.status(200).send(runtimeLogUrl); } catch (ex) { res.status(400).json({ diff --git a/Composer/packages/server/src/directline/utils/webSocketServer.ts b/Composer/packages/server/src/directline/utils/webSocketServer.ts index 4f1073e8b1..edf12bf65d 100644 --- a/Composer/packages/server/src/directline/utils/webSocketServer.ts +++ b/Composer/packages/server/src/directline/utils/webSocketServer.ts @@ -13,7 +13,7 @@ import { ConversationNetworkTrafficItem, } from '@botframework-composer/types'; -import { serverHostname } from '../../settings/env'; +import { serverListenHost } from '../../settings/env'; import log from './logger'; @@ -89,7 +89,7 @@ export class WebSocketServer { }); this.port = port; log(`Using ${port} port for directline`); - this.restServer.listen(port, serverHostname); + this.restServer.listen(port, serverListenHost); app.use('/ws/conversation/:conversationId', (req: express.Request, res: express.Response) => { if (!(req as any).claimUpgrade) { diff --git a/Composer/packages/server/src/server.ts b/Composer/packages/server/src/server.ts index d387f6ed1a..e9cb49b964 100644 --- a/Composer/packages/server/src/server.ts +++ b/Composer/packages/server/src/server.ts @@ -36,7 +36,7 @@ import { mountDirectLineRoutes } from './directline/mountDirectlineRoutes'; import { mountAttachmentRoutes } from './directline/mountAttachmentRoutes'; import { cleanHostedBots } from './utility/cleanHostedBots'; import { getVersion } from './utility/getVersion'; -import { serverHostname } from './settings/env'; +import { serverListenHost, serverHostname } from './settings/env'; // eslint-disable-next-line @typescript-eslint/no-var-requires const session = require('express-session'); @@ -185,7 +185,7 @@ export async function start(electronContext?: ElectronContext): Promise((resolve) => { - server = app.listen(port, serverHostname, () => { + server = app.listen(port, serverListenHost, () => { if (process.env.NODE_ENV === 'production') { // We don't use the debug logger here because we always want it to be shown. // eslint-disable-next-line no-console diff --git a/Composer/packages/server/src/settings/env.ts b/Composer/packages/server/src/settings/env.ts index e7709b054e..800783bb73 100644 --- a/Composer/packages/server/src/settings/env.ts +++ b/Composer/packages/server/src/settings/env.ts @@ -5,7 +5,8 @@ import childProcess from 'child_process'; import { Path } from '../utility/path'; -export const serverHostname = process.env.COMPOSER_HOSTNAME || 'localhost'; +export const serverListenHost = process.env.COMPOSER_HOST || 'localhost'; +export const serverHostname = serverListenHost === '0.0.0.0' || !serverListenHost ? 'localhost' : serverListenHost; export const absHosted = process.env.COMPOSER_AUTH_PROVIDER === 'abs-h'; export const absHostRoot = process.env.WEBSITE_HOSTNAME diff --git a/Dockerfile b/Dockerfile index d9e0b67a05..ec89ed8cd9 100644 --- a/Dockerfile +++ b/Dockerfile @@ -6,12 +6,14 @@ # before doing yarn install due to yarn workspace symlinking. # ################ -FROM mcr.microsoft.com/dotnet/core/sdk:3.1-focal as base +FROM mcr.microsoft.com/dotnet/core/sdk:3.1-focal as base RUN apt update \ && apt -y install curl dirmngr apt-transport-https lsb-release ca-certificates \ && curl -sL https://deb.nodesource.com/setup_14.x | bash - \ && apt install -y nodejs libgomp1 \ - && npm install -g yarn + && corepack enable \ + && corepack prepare yarn@3.2.1 --activate \ + && yarn --version FROM base as build ARG YARN_ARGS @@ -46,7 +48,7 @@ COPY --from=build /src/Composer/packages ./packages COPY --from=build /src/extensions ../extensions ENV NODE_ENV "production" -RUN yarn --production --immutable --force $YARN_ARGS && yarn cache clean +RUN yarn install --immutable $YARN_ARGS && yarn cache clean FROM base ENV NODE_ENV "production" @@ -59,4 +61,5 @@ ENV COMPOSER_BUILTIN_EXTENSIONS_DIR "/app/extensions" ENV COMPOSER_REMOTE_EXTENSIONS_DIR "/app/remote-extensions" ENV COMPOSER_REMOTE_EXTENSION_DATA_DIR "/app/extension-data" ENV COMPOSER_EXTENSION_MANIFEST "/app/extensions.json" +ENV COMPOSER_HOST="0.0.0.0" CMD ["yarn","start:server"] diff --git a/extensions/localPublish/src/index.ts b/extensions/localPublish/src/index.ts index 601f96b3ec..aa5d8316b6 100644 --- a/extensions/localPublish/src/index.ts +++ b/extensions/localPublish/src/index.ts @@ -243,10 +243,11 @@ class LocalPublisher implements PublishPlugin { } }; - setupRuntimeLogServer = async (projectId: string, serverHostname?: string) => { + setupRuntimeLogServer = async (projectId: string, serverHostname?: string, serverListenHost?: string) => { await RuntimeLogServer.init({ log: this.composer.log, hostname: serverHostname, + boundHost: serverListenHost, }); return RuntimeLogServer.getRuntimeLogStreamingUrl(projectId); }; diff --git a/extensions/localPublish/src/runtimeLogServer.ts b/extensions/localPublish/src/runtimeLogServer.ts index f022fa9dc8..3888602d7b 100644 --- a/extensions/localPublish/src/runtimeLogServer.ts +++ b/extensions/localPublish/src/runtimeLogServer.ts @@ -26,9 +26,11 @@ export class RuntimeLogServer { public static async init({ log, + boundHost = 'localhost', hostname = 'localhost', }: { log: Debugger; + boundHost?: string; hostname?: string; }): Promise { if (!this.restServer) { @@ -49,7 +51,7 @@ export class RuntimeLogServer { return preferredPort; }); log(`Using ${port} port for runtime-log`); - this.restServer.listen(port, hostname); + this.restServer.listen(port, boundHost); app.use('/ws/runtimeLog/:projectId', (req: Request, res: Response) => { if (!(req as any).claimUpgrade) { From dd140624dab5a04543a8edc98ad991c0dbbdbd54 Mon Sep 17 00:00:00 2001 From: Eugene Olonov Date: Fri, 13 Jan 2023 09:51:12 -0800 Subject: [PATCH 4/5] Increase sleep timeout --- .github/workflows/docker.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 4986cc90f2..582e194d34 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -36,7 +36,7 @@ jobs: - name: Health check run: | containerId=$(docker run -d -p "5000:5000" botframework-composer) - sleep 10 + sleep 30 docker logs $containerId curl -Is http://localhost:5000 | grep -q "200 OK" shell: bash From 0da5288a62c8b2c0733edc8d58ecb02de46c4c5f Mon Sep 17 00:00:00 2001 From: Eugene Olonov Date: Fri, 13 Jan 2023 10:22:35 -0800 Subject: [PATCH 5/5] Adjust host for docker CI as well --- .github/workflows/docker.yml | 2 +- Dockerfile | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 582e194d34..4986cc90f2 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -36,7 +36,7 @@ jobs: - name: Health check run: | containerId=$(docker run -d -p "5000:5000" botframework-composer) - sleep 30 + sleep 10 docker logs $containerId curl -Is http://localhost:5000 | grep -q "200 OK" shell: bash diff --git a/Dockerfile b/Dockerfile index ec89ed8cd9..b987c10850 100644 --- a/Dockerfile +++ b/Dockerfile @@ -34,6 +34,7 @@ RUN yarn build:prod $YARN_ARGS ENV COMPOSER_REMOTE_EXTENSIONS_DIR "/src/remote-extensions" ENV COMPOSER_REMOTE_EXTENSION_DATA_DIR "/src/extension-data" ENV COMPOSER_EXTENSION_MANIFEST "/src/extensions.json" +ENV COMPOSER_HOST="0.0.0.0" CMD ["yarn","start:server"] FROM base as composerbasic