diff --git a/Node/core-DirectLineWebSockets/DirectLineBot/.env b/Node/core-DirectLineWebSockets/DirectLineBot/.env new file mode 100644 index 0000000000..d5f29e6150 --- /dev/null +++ b/Node/core-DirectLineWebSockets/DirectLineBot/.env @@ -0,0 +1,4 @@ +# Bot Framework Credentials + +MICROSOFT_APP_ID= +MICROSOFT_APP_PASSWORD= diff --git a/Node/core-DirectLineWebSockets/DirectLineBot/app.js b/Node/core-DirectLineWebSockets/DirectLineBot/app.js new file mode 100644 index 0000000000..71cf9a948a --- /dev/null +++ b/Node/core-DirectLineWebSockets/DirectLineBot/app.js @@ -0,0 +1,71 @@ +// This loads the environment variables from the .env file +require('dotenv-extended').load(); + +var builder = require('botbuilder'); +var restify = require('restify'); + +// Setup Restify Server +var server = restify.createServer(); +server.listen(process.env.port || process.env.PORT || 3978, function () { + console.log('%s listening to %s', server.name, server.url); +}); + +// Create connector and listen for messages +var connector = new builder.ChatConnector({ + appId: process.env.MICROSOFT_APP_ID, + appPassword: process.env.MICROSOFT_APP_PASSWORD +}); +server.post('/api/messages', connector.listen()); + +var instructions = 'Welcome to the Bot to showcase the DirectLine API. Send \'Show me a hero card\' or \'Send me a BotFramework image\' to see how the DirectLine client supports custom channel data. Any other message will be echoed.'; + +var bot = new builder.UniversalBot(connector, function (session) { + + var reply = new builder.Message() + .address(session.message.address); + + var text = session.message.text.toLocaleLowerCase(); + + console.log('[' + session.message.address.conversation.id + '] Message received: ' + text); + + switch (text) { + case 'show me a hero card': + reply.text('Sample message with a HeroCard attachment') + .addAttachment(new builder.HeroCard(session) + .title('Sample Hero Card') + .text('Displayed in the DirectLine client')); + break; + + case 'send me a botframework image': + reply.text('Sample message with an Image attachment') + .addAttachment({ + contentUrl: 'https://docs.botframework.com/en-us/images/faq-overview/botframework_overview_july.png', + contentType: 'image/png', + name: 'BotFrameworkOverview.png' + }); + + break; + + default: + reply.text('You said \'' + session.message.text + '\''); + break; + } + + session.send(reply); + +}); + + +bot.on('conversationUpdate', function (activity) { + // when user joins conversation, send instructions + if (activity.membersAdded) { + activity.membersAdded.forEach(function (identity) { + if (identity.id === activity.address.bot.id) { + var reply = new builder.Message() + .address(activity.address) + .text(instructions); + bot.send(reply); + } + }); + } +}); \ No newline at end of file diff --git a/Node/core-DirectLineWebSockets/DirectLineBot/package.json b/Node/core-DirectLineWebSockets/DirectLineBot/package.json new file mode 100644 index 0000000000..1bc6c97ee7 --- /dev/null +++ b/Node/core-DirectLineWebSockets/DirectLineBot/package.json @@ -0,0 +1,28 @@ +{ + "name": "botbuilder-sample-directline-bot", + "version": "1.0.0", + "description": "Bot Builder Sample - DirectLine Sample - Bot", + "scripts": { + "start": "node app.js" + }, + "author": "Microsoft Corp.", + "license": "MIT", + "keywords": [ + "botbuilder", + "bots", + "chatbots", + "botbuilder-samples" + ], + "bugs": { + "url": "https://github.com/Microsoft/BotBuilder-Samples/issues" + }, + "repository": { + "type": "git", + "url": "https://github.com/Microsoft/BotBuilder-Samples.git" + }, + "dependencies": { + "botbuilder": "^3.7.0", + "dotenv-extended": "^1.0.4", + "restify": "^4.3.0" + } +} diff --git a/Node/core-DirectLineWebSockets/DirectLineClient/app.js b/Node/core-DirectLineWebSockets/DirectLineClient/app.js new file mode 100644 index 0000000000..1abc524524 --- /dev/null +++ b/Node/core-DirectLineWebSockets/DirectLineClient/app.js @@ -0,0 +1,211 @@ +var Swagger = require('swagger-client'); +var open = require('open'); +var rp = require('request-promise'); + +// Config settings +var directLineSecret = 'DIRECTLINE_SECRET'; + +// directLineUserId is the field that identifies which user is sending activities to the Direct Line service. +// Because this value is created and sent within your Direct Line client, your bot should not +// trust the value for any security-sensitive operations. Instead, have the user log in and +// store any sign-in tokens against the Conversation or Private state fields. Those fields +// are secured by the conversation ID, which is protected with a signature. +var directLineUserId = 'DirectLineClient'; + +var useW3CWebSocket = false; +process.argv.forEach(function (val, index, array) { + if (val === 'w3c') { + useW3CWebSocket = true; + } +}); + +var directLineSpecUrl = 'https://docs.botframework.com/en-us/restapi/directline3/swagger.json'; +var directLineClient = rp(directLineSpecUrl) + .then(function (spec) { + // Client + return new Swagger({ + spec: JSON.parse(spec.trim()), + usePromise: true + }); + }) + .then(function (client) { + // Obtain a token using the Direct Line secret + // First, add the Direct Line Secret to the client's auth header + client.clientAuthorizations.add('AuthorizationBotConnector', new Swagger.ApiKeyAuthorization('Authorization', 'Bearer ' + directLineSecret, 'header')); + + // Second, request a token for a new conversation + return client.Tokens.Tokens_GenerateTokenForNewConversation().then(function (response) { + // Then, replace the client's auth secret with the new token + var token = response.obj.token; + client.clientAuthorizations.add('AuthorizationBotConnector', new Swagger.ApiKeyAuthorization('Authorization', 'Bearer ' + token, 'header')); + return client; + }); + }) + .catch(function (err) { + console.error('Error initializing DirectLine client', err); + }); + +// Once the client is ready, create a new conversation +directLineClient.then(function (client) { + client.Conversations.Conversations_StartConversation() + .then(function (response) { + var responseObj = response.obj; + + // Start console input loop from stdin + sendMessagesFromConsole(client, responseObj.conversationId); + + if (useW3CWebSocket) { + // Start receiving messages from WS stream - using W3C client + startReceivingW3CWebSocketClient(responseObj.streamUrl, responseObj.conversationId); + } else { + // Start receiving messages from WS stream - using Node client + startReceivingWebSocketClient(responseObj.streamUrl, responseObj.conversationId); + } + }); +}); + +// Read from console (stdin) and send input to conversation using DirectLine client +function sendMessagesFromConsole(client, conversationId) { + var stdin = process.openStdin(); + process.stdout.write('Command> '); + stdin.addListener('data', function (e) { + var input = e.toString().trim(); + if (input) { + if (input.toLowerCase() === 'exit') { + return process.exit(); + } + + // Send message + client.Conversations.Conversations_PostActivity( + { + conversationId: conversationId, + activity: { + textFormat: 'plain', + text: input, + type: 'message', + from: { + id: directLineUserId, + name: directLineUserId + } + } + }).catch(function (err) { + console.error('Error sending message:', err); + }); + + process.stdout.write('Command> '); + } + }); +} + +function startReceivingWebSocketClient(streamUrl, conversationId) { + console.log('Starting WebSocket Client for message streaming on conversationId: ' + conversationId); + + var ws = new (require('websocket').client)(); + + ws.on('connectFailed', function (error) { + console.log('Connect Error: ' + error.toString()); + }); + + ws.on('connect', function (connection) { + console.log('WebSocket Client Connected'); + connection.on('error', function (error) { + console.log("Connection Error: " + error.toString()); + }); + connection.on('close', function () { + console.log('WebSocket Client Disconnected'); + }); + connection.on('message', function (message) { + // Occasionally, the Direct Line service sends an empty message as a liveness ping + // Ignore these messages + if (message.type === 'utf8' && message.utf8Data.length > 0) { + var data = JSON.parse(message.utf8Data); + printMessages(data.activities); + // var watermark = data.watermark; + } + }); + }); + + ws.connect(streamUrl); +} + +function startReceivingW3CWebSocketClient(streamUrl, conversationId) { + console.log('Starting W3C WebSocket Client for message streaming on conversationId: ' + conversationId); + + var ws = new (require('websocket').w3cwebsocket)(streamUrl); + + ws.onerror = function () { + console.log('Connection Error'); + }; + + ws.onopen = function () { + console.log('W3C WebSocket Client Connected'); + }; + + ws.onclose = function () { + console.log('W3C WebSocket Client Disconnected'); + }; + + ws.onmessage = function (e) { + // Occasionally, the Direct Line service sends an empty message as a liveness ping + // Ignore these messages + if (typeof e.data === 'string' && e.data.length > 0) { + var data = JSON.parse(e.data); + printMessages(data.activities); + // var watermark = data.watermark; + } + }; +} + +// Helpers methods +function printMessages(activities) { + if (activities && activities.length) { + // Ignore own messages + activities = activities.filter(function (m) { return m.from.id !== directLineUserId }); + + if (activities.length) { + process.stdout.clearLine(); + process.stdout.cursorTo(0); + + // Print other messages + activities.forEach(printMessage); + + process.stdout.write('Command> '); + } + } +} + +function printMessage(activity) { + if (activity.text) { + console.log(activity.text); + } + + if (activity.attachments) { + activity.attachments.forEach(function (attachment) { + switch (attachment.contentType) { + case "application/vnd.microsoft.card.hero": + renderHeroCard(attachment); + break; + + case "image/png": + console.log('Opening the requested image ' + attachment.contentUrl); + open(attachment.contentUrl); + break; + } + }); + } +} + +function renderHeroCard(attachment) { + var width = 70; + var contentLine = function (content) { + return ' '.repeat((width - content.length) / 2) + + content + + ' '.repeat((width - content.length) / 2); + } + + console.log('/' + '*'.repeat(width + 1)); + console.log('*' + contentLine(attachment.content.title) + '*'); + console.log('*' + ' '.repeat(width) + '*'); + console.log('*' + contentLine(attachment.content.text) + '*'); + console.log('*'.repeat(width + 1) + '/'); +} \ No newline at end of file diff --git a/Node/core-DirectLineWebSockets/DirectLineClient/package.json b/Node/core-DirectLineWebSockets/DirectLineClient/package.json new file mode 100644 index 0000000000..f75bdf8fa3 --- /dev/null +++ b/Node/core-DirectLineWebSockets/DirectLineClient/package.json @@ -0,0 +1,30 @@ +{ + "name": "botbuilder-sample-directline-client", + "version": "1.0.0", + "description": "Bot Builder Sample - DirectLine Sample - Client", + "scripts": { + "start": "node app.js" + }, + "author": "Microsoft Corp.", + "license": "MIT", + "keywords": [ + "botbuilder", + "bots", + "chatbots", + "botbuilder-samples" + ], + "bugs": { + "url": "https://github.com/Microsoft/BotBuilder-Samples/issues" + }, + "repository": { + "type": "git", + "url": "https://github.com/Microsoft/BotBuilder-Samples.git" + }, + "dependencies": { + "open": "0.0.5", + "request": "^2.79.0", + "request-promise": "^4.1.1", + "swagger-client": "^2.1.18", + "websocket": "^1.0.24" + } +} diff --git a/Node/core-DirectLineWebSockets/README.md b/Node/core-DirectLineWebSockets/README.md new file mode 100644 index 0000000000..21927cbc84 --- /dev/null +++ b/Node/core-DirectLineWebSockets/README.md @@ -0,0 +1,199 @@ +# Direct Line Bot Sample (using client WebSockets) + +A sample bot and a custom client communicating to each other using the Direct Line API. + +> For many developers, the [DirectLineJs](https://github.com/Microsoft/BotFramework-DirectLineJs) project is an easy way to connect to the Direct Line in Node.js. +> +> If you're interested in writing your own client without using DirectLineJs, use this sample. + +[![Deploy to Azure][Deploy Button]][Deploy Node/DirectLineWebSockets] + +[Deploy Button]: https://azuredeploy.net/deploybutton.png +[Deploy Node/DirectLineWebSockets]: https://azuredeploy.net + +### Prerequisites + +The minimum prerequisites to run this sample are: +* Latest Node.js with NPM. Download it from [here](https://nodejs.org/en/download/). +* The Bot Framework Emulator. To install the Bot Framework Emulator, download it from [here](https://emulator.botframework.com/). Please refer to [this documentation article](https://github.com/microsoft/botframework-emulator/wiki/Getting-Started) to know more about the Bot Framework Emulator. +* Register your bot with the Microsoft Bot Framework. Please refer to [this](https://docs.botframework.com/en-us/csharp/builder/sdkreference/gettingstarted.html#registering) for the instructions. Once you complete the registration, update your bot configuration with the registered config values (See [Debugging locally using ngrok](https://docs.botframework.com/en-us/node/builder/guides/core-concepts/#debugging-locally-using-ngrok) or [Deploying to Azure](https://docs.botframework.com/en-us/node/builder/guides/deploying-to-azure/#navtitle])) +* **[Recommended]** Visual Studio Code for IntelliSense and debugging, download it from [here](https://code.visualstudio.com/) for free. + +#### Direct Line API +Credentials for the Direct Line API must be obtained from the Bot Framework developer portal, and will only allow the caller to connect to the bot for which they were generated. +In the Bot Framework developer portal, enable Direct Line in the channels list and then, configure the Direct Line secret and update its value in [DirectLineClient's app.js](DirectLineClient/app.js#L6) (`directLineSecret` variable). Make sure that the checkbox for version 3.0 [PREVIEW] is checked. +Refer to [this](https://docs.botframework.com/en-us/csharp/builder/sdkreference/gettingstarted.html#channels) for more information on how to configure channels. + +![Configure Direct Line](images/outcome-configure.png) + +#### Publish +Also, in order to be able to run and test this sample you must [publish your bot, for example to Azure](https://docs.botframework.com/en-us/node/builder/guides/deploying-to-azure/). Alternatively, you can [Debug locally using ngrok](https://docs.botframework.com/en-us/node/builder/guides/core-concepts/#debugging-locally-using-ngrok). +Remember to update the environment variables with the `MICROSOFT_APP_ID` and `MICROSOFT_APP_PASSWORD` on the [.env](DirectLineBot/.env) file. + +### Code Highlights + +The Direct Line API is a simple REST API for connecting directly to a single bot. This API is intended for developers writing their own client applications, web chat controls, or mobile apps that will talk to their bot. In this sample, we are using the [Direct Line Swagger file](https://docs.botframework.com/en-us/restapi/directline3/swagger.json) and [Swagger JS](https://github.com/swagger-api/swagger-js) to create a client for Node that will simplify access to the underlying REST API. Check out the client's [app.js](DirectLineClient/app.js#L22-L46) to see the client setup & initialization. + +You'll see that we are using the Direct Line secret to [obtain a token](DirectLineClient/app.js#L32-L42). This step is optional, but prevents clients from accessing conversations they aren't participating in. +After the token is obtained, the client's auth secret is replaced with this new token. + +````JavaScript +var directLineSpecUrl = 'https://docs.botframework.com/en-us/restapi/directline3/swagger.json'; +var directLineClient = rp(directLineSpecUrl) + .then(function (spec) { + // Client + return new Swagger({ + spec: JSON.parse(spec.trim()), + usePromise: true + }); + }) + .then(function (client) { + // Obtain a token using the Direct Line secret + // First, add the Direct Line Secret to the client's auth header + client.clientAuthorizations.add('AuthorizationBotConnector', new Swagger.ApiKeyAuthorization('Authorization', 'Bearer ' + directLineSecret, 'header')); + + // Second, request a token for a new conversation + return client.Tokens.Tokens_GenerateTokenForNewConversation().then(function (response) { + // Then, replace the client's auth secret with the new token + var token = response.obj.token; + client.clientAuthorizations.add('AuthorizationBotConnector', new Swagger.ApiKeyAuthorization('Authorization', 'Bearer ' + token, 'header')); + return client; + }); + }) + .catch(function (err) { + console.error('Error initializing DirectLine client', err); + }); +```` + +Each conversation on the Direct Line channel must be explicitly started using the `client.Conversations.Conversations_StartConversation()` function. +Check out the client's following [function call](DirectLineClient/app.js#L49-L65) which creates a new conversation. + +````JavaScript +directLineClient.then(function (client) { + client.Conversations.Conversations_StartConversation() + .then(function (response) { + var responseObj = response.obj; + + // Start console input loop from stdin + sendMessagesFromConsole(client, responseObj.conversationId); + + if (useW3CWebSocket) { + // Start receiving messages from WS stream - using W3C client + startReceivingW3CWebSocketClient(responseObj.streamUrl, responseObj.conversationId); + } else { + // Start receiving messages from WS stream - using Node client + startReceivingWebSocketClient(responseObj.streamUrl, responseObj.conversationId); + } + }); +}); +```` + +Once the conversation is created, a `conversationId` is returned and it can be used to call other endpoints to poll or send messages, as well as other activities. + +User messages are sent to the Bot using the Direct Line Client `client.Conversations.Conversations_PostActivity` function using the `conversationId` generated in the previous step. This uses the HTTPS protocol and hits the REST API. + +````JavaScript +client.Conversations.Conversations_PostActivity( + { + conversationId: conversationId, + activity: { + textFormat: 'plain', + text: input, + type: 'message', + from: { + id: directLineUserId, + name: directLineUserId + } + } + }).catch(function (err) { + console.error('Error sending message:', err); + }); +```` + +Messages from the Bot are being received using the WebSocket protocol (actually WSS). For this, after the conversation was created a `streamUrl` is also returned and it will be the target for the WebSocket connection. + +Check out the client's [startReceivingWebSocketClient](DirectLineClient/app.js#L100-L128) and [startReceivingW3CWebSocketClient](DirectLineClient/app.js#L130-L155) functions which create WebSocket clients hitting the `streamUrl` value returned when the conversation was created (one or other will be called dependening on the `w3c` optional flag when running the console app). Messages are then filtered from anyone but our own client using the [`printMessages`](DirectLineClient/app.js#L160-L175 ) function. + +Each of these functions showcase the two ways you can connect to the `streamUrl` using WebSockets (first one using a custom Node.js implementation, while the second one uses W3C one). If you look closely they are very similar and within the `on message` event handler the bot's response is being parsed to JSON in order to print it. + +For `startReceivingWebSocketClient` we have the following [handler](DirectLineClient/app.js#L117-L125): + +````JavaScript +connection.on('message', function (message) { + // Occasionally, the Direct Line service sends an empty message as a liveness ping + // Ignore these messages + if (message.type === 'utf8' && message.utf8Data.length > 0) { + var data = JSON.parse(message.utf8Data); + printMessages(data.activities); + } +}); +```` + +And for `startReceivingW3CWebSocketClient` we have the following [handler](DirectLineClient/app.js#L148-L156): + +````JavaScript +ws.onmessage = function (e) { + // Occasionally, the Direct Line service sends an empty message as a liveness ping + // Ignore these messages + if (typeof e.data === 'string' && e.data.length > 0) { + var data = JSON.parse(e.data); + printMessages(data.activities); + } +}; +```` + +> Clients should keep track of the `watermark` value from each `ActivitySet` so they can use it on reconnect. Note that a null or missing watermark should be ignored and should not overwrite a prior watermark in the client. + +Direct Line v3.0 (unlike version 1.1) has supports for Attachments (see [Adding Attachments to a Message](https://docs.botframework.com/en-us/core-concepts/attachments) for more information about attachments). + +Check out the [`printMessage`](DirectLineClient/app.js#L177-L196) function to see how the Attachments are retrieved and rendered appropriately based on their type. + +````JavaScript +function printMessage(activity) { + if (activity.text) { + console.log(activity.text); + } + + if (activity.attachments) { + activity.attachments.forEach(function (attachment) { + switch (attachment.contentType) { + case "application/vnd.microsoft.card.hero": + renderHeroCard(attachment); + break; + + case "image/png": + console.log('Opening the requested image ' + attachment.contentUrl); + open(attachment.contentUrl); + break; + } + }); + } +} +```` + +### Outcome + +To run the sample, you'll need to run both Bot and Client apps. + +* Running Bot app + 1. Open a CMD console and CD to sample's `DirectLineBot` directory + 2. Run `node app.js` +* Running Client app + 1. Open a CMD console and CD to sample's `DirectLineClient` directory + 2. Run `node app.js {w3c}` - if you use the `w3c` optional flag then a W3C WebSocket will be used, otherwise a Node.js implementation is instanced + +To test the ChannelData custom messages type in the Client's console `show me a hero card` or `send me a botframework image` and you should see the following outcome. + +![Sample Outcome](images/outcome.png) + +### More Information + +To get more information about how to get started in Bot Builder for Node and Direct Line API please review the following resources: +* [Bot Builder for Node.js Reference](https://docs.botframework.com/en-us/node/builder/overview/#navtitle) +* [Bot Framework FAQ](https://docs.botframework.com/en-us/faq/#i-have-a-communication-channel-id-like-to-be-configurable-with-bot-framework-can-i-work-with-microsoft-to-do-that) +* [Direct Line API - v3.0](https://docs.botframework.com/en-us/restapi/directline3/) +* [Direct Line API - v3.0 - Authentication](https://docs.botframework.com/en-us/restapi/directline3/#authentication-secrets-and-tokens) +* [Direct Line Swagger file - v3.0](https://docs.botframework.com/en-us/restapi/directline3/swagger.json) +* [Swagger-JS](https://github.com/swagger-api/swagger-js) +* [Adding Attachments to a Message](https://docs.botframework.com/en-us/core-concepts/attachments) +* [Debugging locally using ngrok](https://docs.botframework.com/en-us/node/builder/guides/core-concepts/#debugging-locally-using-ngrok) diff --git a/Node/core-DirectLineWebSockets/azuredeploy.json b/Node/core-DirectLineWebSockets/azuredeploy.json new file mode 100644 index 0000000000..7f5121cb4e --- /dev/null +++ b/Node/core-DirectLineWebSockets/azuredeploy.json @@ -0,0 +1,129 @@ +{ + "$schema": "http://schema.management.azure.com/schemas/2014-04-01-preview/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "siteName": { + "defaultValue": "BotBuilder-Samples", + "type": "string" + }, + "hostingPlanName": { + "type": "string" + }, + "siteLocation": { + "type": "string" + }, + "sku": { + "type": "string", + "allowedValues": [ + "Free", + "Shared", + "Basic", + "Standard" + ], + "defaultValue": "Free" + }, + "workerSize": { + "type": "string", + "allowedValues": [ + "0", + "1", + "2" + ], + "defaultValue": "0" + }, + "repoUrl": { + "type": "string" + }, + "branch": { + "type": "string" + }, + "Project": { + "type": "string", + "defaultValue": "Node/core-DirectLineWebSockets/DirectLineBot" + }, + "WEBSITE_NODE_DEFAULT_VERSION": { + "type": "string", + "defaultValue": "5.9.1" + }, + "MICROSOFT_APP_ID": { + "type": "string" + }, + "MICROSOFT_APP_PASSWORD": { + "type": "string" + } + }, + "resources": [ + { + "apiVersion": "2014-06-01", + "name": "[parameters('hostingPlanName')]", + "type": "Microsoft.Web/serverFarms", + "location": "[parameters('siteLocation')]", + "properties": { + "name": "[parameters('hostingPlanName')]", + "sku": "[parameters('sku')]", + "workerSize": "[parameters('workerSize')]", + "numberOfWorkers": 1 + } + }, + { + "apiVersion": "2014-06-01", + "name": "[parameters('siteName')]", + "type": "Microsoft.Web/Sites", + "location": "[parameters('siteLocation')]", + "dependsOn": [ + "[concat('Microsoft.Web/serverFarms/', parameters('hostingPlanName'))]" + ], + "tags": { + "[concat('hidden-related:', resourceGroup().id, '/providers/Microsoft.Web/serverfarms/', parameters('hostingPlanName'))]": "empty" + }, + "properties": { + "name": "[parameters('siteName')]", + "serverFarm": "[parameters('hostingPlanName')]" + }, + "resources": [ + { + "apiVersion": "2014-04-01", + "type": "config", + "name": "web", + "dependsOn": [ + "[concat('Microsoft.Web/Sites/', parameters('siteName'))]" + ], + "properties": { + "appSettings": [ + { + "name": "Project", + "value": "[parameters('Project')]" + }, + { + "name": "WEBSITE_NODE_DEFAULT_VERSION", + "value": "[parameters('WEBSITE_NODE_DEFAULT_VERSION')]" + }, + { + "name": "MICROSOFT_APP_ID", + "value": "[parameters('MICROSOFT_APP_ID')]" + }, + { + "name": "MICROSOFT_APP_PASSWORD", + "value": "[parameters('MICROSOFT_APP_PASSWORD')]" + } + ] + } + }, + { + "apiVersion": "2014-04-01", + "name": "web", + "type": "sourcecontrols", + "dependsOn": [ + "[resourceId('Microsoft.Web/Sites', parameters('siteName'))]", + "[concat('Microsoft.Web/Sites/', parameters('siteName'), '/config/web')]" + ], + "properties": { + "RepoUrl": "[parameters('repoUrl')]", + "branch": "[parameters('branch')]", + "IsManualIntegration": true + } + } + ] + } + ] +} diff --git a/Node/core-DirectLineWebSockets/images/outcome-configure.png b/Node/core-DirectLineWebSockets/images/outcome-configure.png new file mode 100644 index 0000000000..5d51e78a39 Binary files /dev/null and b/Node/core-DirectLineWebSockets/images/outcome-configure.png differ diff --git a/Node/core-DirectLineWebSockets/images/outcome.png b/Node/core-DirectLineWebSockets/images/outcome.png new file mode 100644 index 0000000000..4591fe5d12 Binary files /dev/null and b/Node/core-DirectLineWebSockets/images/outcome.png differ