├── .gitignore
├── secrets.png
├── dockerfile
├── package.json
├── public
├── stylesheets
│ └── style.css
├── index.html
└── index.js
├── GeoPol.xml
├── Web.config
├── SECURITY.md
├── README.md
├── server.js
└── azuredeploy.json
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | .idea
3 | .env
4 | .deployment
5 | .vscode
--------------------------------------------------------------------------------
/secrets.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/HealthBotContainerSample/HEAD/secrets.png
--------------------------------------------------------------------------------
/dockerfile:
--------------------------------------------------------------------------------
1 | FROM node:latest
2 |
3 | # Create app directory
4 | WORKDIR /usr/src/app
5 |
6 | # Install app dependencies
7 | # A wildcard is used to ensure both package.json AND package-lock.json are copied
8 | # where available (npm@5+)
9 | COPY package*.json ./
10 |
11 | RUN npm install
12 | # If you are building your code for production
13 | # RUN npm install --only=production
14 |
15 | # Bundle app source
16 | COPY . .
17 |
18 | # Delete the web.config file, only needed for IIS
19 | RUN rm ./Web.config
20 |
21 | EXPOSE 8080
22 | CMD [ "npm", "start" ]
23 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "healthbot-container",
3 | "version": "1.0.0",
4 | "description": "A simple web page to handoff users to the Microsoft Health bot",
5 | "main": "server.js",
6 | "license": "MIT",
7 | "repository": "https://github.com/Microsoft/HealthBotContainerSample.git",
8 | "dependencies": {
9 | "dotenv": "^8.2.0",
10 | "cookie-parser": "^1.4.5",
11 | "express": "^4.17.1",
12 | "jsonwebtoken": "^8.5.1",
13 | "node-fetch": "^2.6.7"
14 | },
15 | "scripts": {
16 | "prestart": "npm install"
17 | },
18 | "devDependencies": {}
19 | }
20 |
--------------------------------------------------------------------------------
/public/stylesheets/style.css:
--------------------------------------------------------------------------------
1 | html, body {
2 | height: 100%;
3 | }
4 | body {
5 | margin: 0;
6 | }
7 |
8 | #webchat {
9 | height: 100%;
10 | width: 100%;
11 | }
12 |
13 | #webchat[watermark="true"] [role="complementary"] ul[role="list"]::after {
14 | content: "Powered By ...";
15 | background: linear-gradient(rgba(248, 248, 248, 0), rgba(248, 248, 248, .63), #F8F8F8 40%);
16 | bottom: 0;
17 | right: 0;
18 | color: #707070;
19 | display: block;
20 | font-family: 'Segoe Semibold', Calibri, 'Helvetica Neue', Arial, sans-serif;
21 | font-size: 12px;
22 | padding: 15px 10px 10px;
23 | position: absolute;
24 | position: sticky;
25 | text-align: right;
26 | }
27 |
28 | #webchat[watermark="true"] .webchat__scrollToEndButton {
29 | bottom: 32px;
30 | left: 50%;
31 | right: unset;
32 | transform: translate(-50%, 0);
33 | }
34 |
--------------------------------------------------------------------------------
/GeoPol.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 |
6 | ]>
7 |
8 |
9 |
10 | &GitReposFolder;\AInR_NExT\&GitRepoName;
11 | &GitRepoName;
12 |
13 |
14 | .
15 |
16 |
17 | .gitignore
18 | GeoPol.xml
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
11 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/Web.config:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
57 |
58 |
59 |
60 |
--------------------------------------------------------------------------------
/SECURITY.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | ## Security
4 |
5 | Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), [Xamarin](https://github.com/xamarin), and [our GitHub organizations](https://opensource.microsoft.com/).
6 |
7 | If you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](https://aka.ms/opensource/security/definition), please report it to us as described below.
8 |
9 | ## Reporting Security Issues
10 |
11 | **Please do not report security vulnerabilities through public GitHub issues.**
12 |
13 | Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://aka.ms/opensource/security/create-report).
14 |
15 | If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://aka.ms/opensource/security/pgpkey).
16 |
17 | You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://aka.ms/opensource/security/msrc).
18 |
19 | Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue:
20 |
21 | * Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.)
22 | * Full paths of source file(s) related to the manifestation of the issue
23 | * The location of the affected source code (tag/branch/commit or direct URL)
24 | * Any special configuration required to reproduce the issue
25 | * Step-by-step instructions to reproduce the issue
26 | * Proof-of-concept or exploit code (if possible)
27 | * Impact of the issue, including how an attacker might exploit the issue
28 |
29 | This information will help us triage your report more quickly.
30 |
31 | If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://aka.ms/opensource/security/bounty) page for more details about our active programs.
32 |
33 | ## Preferred Languages
34 |
35 | We prefer all communications to be in English.
36 |
37 | ## Policy
38 |
39 | Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://aka.ms/opensource/security/cvd).
40 |
41 |
42 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Health Bot Container
2 |
3 | A simple web page that allows users to communicate with the [Azure Health Bot](https://azure.microsoft.com/en-us/services/bot-services/health-bot/) through a WebChat.
4 |
5 | **Note:** In order to use this Web Chat with the Health Bot service, you will need to obtain your Web Chat secret by going to `Integration/Secrets` on the navigation panel.
6 |
7 | 
8 |
9 | 1.Deploy the website:
10 |
11 | [](https://portal.azure.com/#create/Microsoft.Template/uri/https%3A%2F%2Fraw.githubusercontent.com%2Fmicrosoft%2FHealthBotContainerSample%2Fmaster%2Fazuredeploy.json)
12 |
13 | 2.Set the following environment variables:
14 |
15 | `AUTH_JWT_SECRET`
16 |
17 | `WEBCHAT_SECRET`
18 |
19 | 3.Configure scenario invocation (optional):
20 |
21 | The Health Bot service uses [language models](https://docs.microsoft.com/HealthBot/language_model_howto) to interpret end user utterances and trigger the relevant scenario logic in response.
22 |
23 | Alternatively, you can programmaticaly invoke a scenario before the end user provides any input.
24 |
25 | To implement this behavior, uncomment the following code from the `function initBotConversation()` in the `/public/index.js` file:
26 | ```javascript
27 | triggeredScenario: {
28 | trigger: "{scenario_id}",
29 | args: {
30 | myVar1: "{custom_arg_1}",
31 | myVar2: "{custom_arg_2}"
32 | }
33 | }
34 | ```
35 | Replace {scenario_id} with the scenario ID of the scenario you would like to invoke.
36 | You can also pass different values through the "args" object.
37 |
38 | You can read more about programmatic client side scenario invocation [here](https://docs.microsoft.com/HealthBot/integrations/programmatic_invocation)
39 |
40 |
41 | 4.Set the Bot service direct line channel endpoint (optional)
42 |
43 | In some cases it is required to set the endpoint URI so that it points to a specific geography. The geographies supported by the bot service each have a unique direct line endpoint URI:
44 |
45 | - `directline.botframework.com` routes your client to the nearest datacenter. This is the best option if you do not know where your client is located.
46 | - `asia.directline.botframework.com` routes only to Direct Line servers in Eastern Asia.
47 | - `europe.directline.botframework.com` routes only to Direct Line servers in Europe.
48 | - `northamerica.directline.botframework.com` routes only to Direct Line servers in North America.
49 |
50 | Pass your preferred geographic endpoint URI by setting the environment variable: `DIRECTLINE_ENDPOINT_URI` in your deployment. If no variable is found it will default to `directline.botframework.com`
51 |
52 | **Note:** If you are deploying the code sample using the "Deploy to Azure" option, you should add the above secrets to the application settings for your App Service.
53 |
54 | ## Agent webchat
55 | If the agent webchat sample is also required, [switch to the live agent handoff branch](https://github.com/Microsoft/HealthBotContainerSample/tree/live_agent_handoff)
56 |
--------------------------------------------------------------------------------
/server.js:
--------------------------------------------------------------------------------
1 | require('dotenv').config();
2 | const crypto = require('crypto');
3 | const express = require("express");
4 | const path = require("path");
5 | const jwt = require("jsonwebtoken");
6 | const fetch = require('node-fetch');
7 | const cookieParser = require('cookie-parser');
8 | const WEBCHAT_SECRET = process.env.WEBCHAT_SECRET;
9 | const DIRECTLINE_ENDPOINT_URI = process.env.DIRECTLINE_ENDPOINT_URI;
10 | const AUTH_JWT_SECRET = process.env.AUTH_JWT_SECRET;
11 | const directLineTokenEp = `https://${DIRECTLINE_ENDPOINT_URI || "directline.botframework.com"}/v3/directline/tokens/generate`;
12 |
13 | // Initialize the web app instance,
14 | const app = express();
15 | app.use(cookieParser());
16 |
17 | let options = {};
18 | // uncomment the line below if you wish to allow only specific domains to embed this page as a frame
19 | //options = {setHeaders: (res, path, stat) => {res.set('Content-Security-Policy', 'frame-ancestors example.com')}};
20 | // Indicate which directory static resources
21 | // (e.g. stylesheets) should be served from.
22 | app.use(express.static(path.join(__dirname, "public"), options));
23 | // begin listening for requests.
24 | const port = process.env.PORT || 8080;
25 | const region = process.env.REGION || "Unknown";
26 |
27 | app.listen(port, function() {
28 | console.log("Express server listening on port " + port);
29 | });
30 |
31 | function isUserAuthenticated(){
32 | // add here the logic to verify the user is authenticated
33 | return true;
34 | }
35 |
36 | const appConfig = {
37 | isHealthy : false,
38 | options : {
39 | method: 'POST',
40 | headers: {
41 | 'Authorization': 'Bearer ' + WEBCHAT_SECRET
42 | }
43 | }
44 | };
45 |
46 | function healthResponse(res, statusCode, message) {
47 | res.status(statusCode).send({
48 | health: message,
49 | region: region
50 | });
51 | }
52 | function healthy(res) {
53 | healthResponse(res, 200, "Ok");
54 | }
55 |
56 | function unhealthy(res) {
57 | healthResponse(res, 503, "Unhealthy");
58 | }
59 |
60 | app.get('/health', async function(req, res){
61 | if (!appConfig.isHealthy) {
62 | try {
63 | const fetchResponse = await fetch(directLineTokenEp, appConfig.options);
64 | const parsedBody = await fetchResponse.json();
65 | appConfig.isHealthy = true;
66 | healthy(res);
67 | }
68 | catch (err) {
69 | unhealthy(res);
70 | }
71 | }
72 | else {
73 | healthy(res);
74 | }
75 | });
76 |
77 | app.post('/chatBot', async function(req, res) {
78 | if (!isUserAuthenticated()) {
79 | res.status(403).send();
80 | return;
81 | }
82 | try {
83 | const fetchResponse = await fetch(directLineTokenEp, appConfig.options);
84 | const parsedBody = await fetchResponse.json();
85 | var userid = req.query.userId || req.cookies.userid;
86 | if (!userid) {
87 | userid = crypto.randomBytes(4).toString('hex');
88 | res.cookie("userid", userid, { sameSite: "none", secure: true, httpOnly: true, expires: new Date(new Date().setFullYear(new Date().getFullYear() + 1)) });
89 | }
90 |
91 | var response = {};
92 | response['userId'] = userid;
93 | response['userName'] = req.query.userName;
94 | response['locale'] = req.query.locale;
95 | response['connectorToken'] = parsedBody.token;
96 |
97 | /*
98 | //Add any additional attributes
99 | response['optionalAttributes'] = {age: 33};
100 | */
101 |
102 | if (req.query.lat && req.query.long) {
103 | response['location'] = {lat: req.query.lat, long: req.query.long};
104 | }
105 | response['directLineURI'] = DIRECTLINE_ENDPOINT_URI;
106 | const jwtToken = jwt.sign(response, AUTH_JWT_SECRET);
107 | res.send(jwtToken);
108 | }
109 | catch (err) {
110 | appConfig.isHealthy = false;
111 | res.status(err.statusCode).send();
112 | console.log("failed");
113 | }
114 | });
115 |
--------------------------------------------------------------------------------
/public/index.js:
--------------------------------------------------------------------------------
1 | const defaultLocale = 'en-US';
2 |
3 | function requestChatBot(loc) {
4 | const params = new URLSearchParams(location.search);
5 | const oReq = new XMLHttpRequest();
6 | oReq.addEventListener("load", initBotConversation);
7 | var path = "/chatBot?locale=" + extractLocale(params.get('locale'));
8 |
9 | if (loc) {
10 | path += "&lat=" + loc.lat + "&long=" + loc.long;
11 | }
12 | if (params.has('userId')) {
13 | path += "&userId=" + params.get('userId');
14 | }
15 | if (params.has('userName')) {
16 | path += "&userName=" + params.get('userName');
17 | }
18 | oReq.open("POST", path);
19 | oReq.send();
20 | }
21 |
22 | function extractLocale(localeParam) {
23 | if (!localeParam) {
24 | return defaultLocale;
25 | }
26 | else if (localeParam === 'autodetect') {
27 | return navigator.language;
28 | }
29 | else {
30 | return localeParam;
31 | }
32 | }
33 |
34 | function chatRequested() {
35 | const params = new URLSearchParams(location.search);
36 | if (params.has('shareLocation')) {
37 | getUserLocation(requestChatBot);
38 | }
39 | else {
40 | requestChatBot();
41 | }
42 | }
43 |
44 | function getUserLocation(callback) {
45 | navigator.geolocation.getCurrentPosition(
46 | function(position) {
47 | var latitude = position.coords.latitude;
48 | var longitude = position.coords.longitude;
49 | var location = {
50 | lat: latitude,
51 | long: longitude
52 | }
53 | callback(location);
54 | },
55 | function(error) {
56 | // user declined to share location
57 | console.log("location error:" + error.message);
58 | callback();
59 | });
60 | }
61 |
62 | function initBotConversation() {
63 | if (this.status >= 400) {
64 | alert(this.statusText);
65 | return;
66 | }
67 | // extract the data from the JWT
68 | const jsonWebToken = this.response;
69 | const tokenPayload = JSON.parse(atob(jsonWebToken.split('.')[1]));
70 | const user = {
71 | id: tokenPayload.userId,
72 | name: tokenPayload.userName,
73 | locale: tokenPayload.locale
74 | };
75 | let domain = undefined;
76 | if (tokenPayload.directLineURI) {
77 | domain = "https://" + tokenPayload.directLineURI + "/v3/directline";
78 | }
79 | let location = undefined;
80 | if (tokenPayload.location) {
81 | location = tokenPayload.location;
82 | } else {
83 | // set default location if desired
84 | /*location = {
85 | lat: 44.86448450671394,
86 | long: -93.32597021107624
87 | }*/
88 | }
89 | var botConnection = window.WebChat.createDirectLine({
90 | token: tokenPayload.connectorToken,
91 | domain: domain
92 | });
93 | const styleOptions = {
94 | botAvatarImage: 'https://docs.microsoft.com/en-us/azure/bot-service/v4sdk/media/logo_bot.svg?view=azure-bot-service-4.0',
95 | // botAvatarInitials: '',
96 | // userAvatarImage: '',
97 | hideSendBox: false, /* set to true to hide the send box from the view */
98 | botAvatarInitials: 'Bot',
99 | userAvatarInitials: 'You',
100 | backgroundColor: '#F8F8F8'
101 | };
102 |
103 | const store = window.WebChat.createStore({}, function(store) { return function(next) { return function(action) {
104 | if (action.type === 'DIRECT_LINE/CONNECT_FULFILLED') {
105 | store.dispatch({
106 | type: 'DIRECT_LINE/POST_ACTIVITY',
107 | meta: {method: 'keyboard'},
108 | payload: {
109 | activity: {
110 | type: "invoke",
111 | name: "InitConversation",
112 | locale: user.locale,
113 | value: {
114 | // must use for authenticated conversation.
115 | jsonWebToken: jsonWebToken,
116 |
117 | // Use the following activity to proactively invoke a bot scenario
118 | /*
119 | triggeredScenario: {
120 | trigger: "{scenario_id}",
121 | args: {
122 | location: location,
123 | myVar1: "{custom_arg_1}",
124 | myVar2: "{custom_arg_2}"
125 | }
126 | }
127 | */
128 | }
129 | }
130 | }
131 | });
132 |
133 | }
134 | else if (action.type === 'DIRECT_LINE/INCOMING_ACTIVITY') {
135 | if (action.payload && action.payload.activity && action.payload.activity.type === "event" && action.payload.activity.name === "ShareLocationEvent") {
136 | // share
137 | getUserLocation(function (location) {
138 | store.dispatch({
139 | type: 'WEB_CHAT/SEND_POST_BACK',
140 | payload: { value: JSON.stringify(location) }
141 | });
142 | });
143 | }
144 | }
145 | return next(action);
146 | }}});
147 | const webchatOptions = {
148 | directLine: botConnection,
149 | styleOptions: styleOptions,
150 | store: store,
151 | userID: user.id,
152 | username: user.name,
153 | locale: user.locale
154 | };
155 | startChat(user, webchatOptions);
156 | }
157 |
158 | function startChat(user, webchatOptions) {
159 | const botContainer = document.getElementById('webchat');
160 | window.WebChat.renderWebChat(webchatOptions, botContainer);
161 | }
162 |
--------------------------------------------------------------------------------
/azuredeploy.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
3 | "contentVersion": "1.0.0.0",
4 | "parameters": {
5 | "siteName": {
6 | "type": "string",
7 | "defaultValue": "[concat('healthcare-bot-', uniqueString(utcNow('F')))]",
8 | "metadata":{
9 | "description": "Web site name. Has to be unique."
10 |
11 | }
12 | },
13 | "operatingSystem": {
14 | "type": "string",
15 | "defaultValue": "windows",
16 | "allowedValues": [
17 | "windows",
18 | "linux"
19 | ],
20 | "metadata": {
21 | "description": "Host type: Windows or Linux. (Windows is recommended)"
22 | }
23 | },
24 | "skuName": {
25 | "type": "string",
26 | "defaultValue": "P1V2",
27 | "allowedValues": [
28 | "B1",
29 | "S1",
30 | "P1V2"
31 | ],
32 | "metadata": {
33 | "description": "Describes plan's pricing tier and instance size. Check details at https://azure.microsoft.com/en-us/pricing/details/app-service/"
34 | }
35 | },
36 | "numberOfInstances": {
37 | "type": "int",
38 | "defaultValue": 2,
39 | "metadata":{
40 | "description": "Number of instances the app service is scaled to. Check the maximum instances your plan can host at https://azure.microsoft.com/en-us/pricing/details/app-service"
41 | }
42 | },
43 | "siteLocation": {
44 | "type": "string",
45 | "defaultValue": "[resourceGroup().location]",
46 | "metadata":{
47 | "description": "Location for all resources."
48 | }
49 | },
50 | "authJwtSecret": {
51 | "type": "securestring",
52 | "metadata":{
53 | "description": "Healthbot authentication JWT secret."
54 | }
55 | },
56 | "webchatSecret": {
57 | "type": "securestring",
58 | "metadata":{
59 | "description": "Healthbot webchat secret."
60 | }
61 | },
62 | "repoUrl": {
63 | "type": "string",
64 | "defaultValue": "https://github.com/microsoft/HealthBotContainerSample.git",
65 | "metadata": {
66 | "description": "The URL for the GitHub repository that contains the project to deploy."
67 | }
68 | },
69 | "branch": {
70 | "type": "string",
71 | "defaultValue": "master",
72 | "metadata": {
73 | "description": "The branch of the GitHub repository to use."
74 | }
75 | }
76 | },
77 | "variables":{
78 | "alwaysOn": true,
79 | "skuCode": "[parameters('skuName')]",
80 | "numberOfWorkers": "[parameters('numberOfInstances')]",
81 | "linuxFxVersion": "NODE|lts",
82 | "hostingPlanNameLinux": "[concat('plan-linux-', parameters('siteName'))]",
83 | "hostingPlanNameWin": "[concat('plan-win-', parameters('siteName'))]",
84 | "kind": "[if(equals(parameters('operatingSystem'), 'windows'), 'app', 'linux')]",
85 | "linuxSiteName": "[if(equals(parameters('operatingSystem'), 'linux'), parameters('siteName'), 'app-na')]",
86 | "windowsSiteName": "[if(equals(parameters('operatingSystem'), 'windows'), parameters('siteName'), 'app-na')]",
87 | "WinSkuCode": "[parameters('skuName')]",
88 | "WinSku": "Standard",
89 | "workerSize": "0",
90 | "workerSizeId": "0",
91 | "hostingEnvironment": "",
92 | "nodeVersion": "~16",
93 | "currentStack": "node"
94 | },
95 | "resources": [
96 | {
97 | "apiVersion": "2018-02-01",
98 | "name": "[variables('linuxSiteName')]",
99 | "condition": "[equals(parameters('operatingSystem'),'linux')]",
100 | "type": "Microsoft.Web/sites",
101 | "location": "[parameters('siteLocation')]",
102 | "dependsOn": [
103 | "[resourceId('Microsoft.Web/serverfarms/', variables('hostingPlanNameLinux'))]"
104 | ],
105 | "properties": {
106 | "name": "[variables('linuxSiteName')]",
107 | "siteConfig": {
108 | "linuxFxVersion": "[variables('linuxFxVersion')]",
109 | "alwaysOn": "[variables('alwaysOn')]",
110 | "appSettings": [
111 | {
112 | "name": "AUTH_JWT_SECRET",
113 | "value": "[parameters('authJwtSecret')]"
114 | },
115 | {
116 | "name": "WEBCHAT_SECRET",
117 | "value": "[parameters('webchatSecret')]"
118 | }
119 | ]
120 | },
121 | "serverFarmId": "[resourceId('Microsoft.Web/serverfarms', variables('hostingPlanNameLinux'))]",
122 | "clientAffinityEnabled": false
123 | },
124 | "resources": [
125 | {
126 | "type": "sourcecontrols",
127 | "condition": "[equals(parameters('operatingSystem'),'linux')]",
128 | "apiVersion": "2018-02-01",
129 | "name": "web",
130 | "location": "[parameters('siteLocation')]",
131 | "dependsOn": [
132 | "[resourceId('Microsoft.Web/sites', variables('linuxSiteName'))]"
133 | ],
134 | "properties": {
135 | "repoUrl": "[parameters('repoURL')]",
136 | "branch": "[parameters('branch')]",
137 | "isManualIntegration": true
138 | }
139 | }
140 | ]
141 | },
142 | {
143 | "apiVersion": "2018-02-01",
144 | "condition": "[equals(parameters('operatingSystem'),'linux')]",
145 | "name": "[variables('hostingPlanNameLinux')]",
146 | "type": "Microsoft.Web/serverfarms",
147 | "location": "[parameters('siteLocation')]",
148 | "kind": "linux",
149 | "sku": {
150 | "Name": "[variables('skuCode')]"
151 | },
152 | "properties": {
153 | "name": "[variables('hostingPlanNameLinux')]",
154 | "numberOfWorkers": "[variables('numberOfWorkers')]",
155 | "reserved": true
156 | }
157 | },
158 | {
159 | "apiVersion": "2018-11-01",
160 | "condition": "[equals(parameters('operatingSystem'),'windows')]",
161 | "name": "[variables('windowsSiteName')]",
162 | "type": "Microsoft.Web/sites",
163 | "location": "[parameters('siteLocation')]",
164 | "tags": null,
165 | "dependsOn": [
166 | "[concat('Microsoft.Web/serverfarms/', variables('hostingPlanNameWin'))]"
167 | ],
168 | "properties": {
169 | "name": "[variables('windowsSiteName')]",
170 | "siteConfig": {
171 | "appSettings": [
172 | {
173 | "name": "AUTH_JWT_SECRET",
174 | "value": "[parameters('authJwtSecret')]"
175 | },
176 | {
177 | "name": "WEBCHAT_SECRET",
178 | "value": "[parameters('webchatSecret')]"
179 | },
180 | {
181 | "name": "WEBSITE_NODE_DEFAULT_VERSION",
182 | "value": "[variables('nodeVersion')]"
183 | }
184 | ],
185 | "metadata": [
186 | {
187 | "name": "CURRENT_STACK",
188 | "value": "[variables('currentStack')]"
189 | }
190 | ],
191 | "nodeVersion": "[variables('nodeVersion')]",
192 | "alwaysOn": "[variables('alwaysOn')]"
193 | },
194 | "serverFarmId": "[resourceId('Microsoft.Web/serverfarms', variables('hostingPlanNameWin'))]",
195 | "hostingEnvironment": "[variables('hostingEnvironment')]",
196 | "clientAffinityEnabled": true
197 | },
198 | "resources": [
199 | {
200 | "type": "sourcecontrols",
201 | "condition": "[equals(parameters('operatingSystem'),'windows')]",
202 | "apiVersion": "2018-11-01",
203 | "name": "web",
204 | "location": "[parameters('siteLocation')]",
205 | "dependsOn": [
206 | "[resourceId('Microsoft.Web/sites', variables('windowsSiteName'))]"
207 | ],
208 | "properties": {
209 | "repoUrl": "[parameters('repoURL')]",
210 | "branch": "[parameters('branch')]",
211 | "isManualIntegration": true
212 | }
213 | }
214 | ]
215 | },
216 | {
217 | "apiVersion": "2018-11-01",
218 | "name": "[variables('hostingPlanNameWin')]",
219 | "condition": "[equals(parameters('operatingSystem'),'windows')]",
220 | "type": "Microsoft.Web/serverfarms",
221 | "location": "[parameters('siteLocation')]",
222 | "kind": "",
223 | "tags": null,
224 | "dependsOn": [],
225 | "properties": {
226 | "name": "[variables('hostingPlanNameWin')]",
227 | "workerSize": "[variables('workerSize')]",
228 | "workerSizeId": "[variables('workerSizeId')]",
229 | "numberOfWorkers": "[variables('numberOfWorkers')]",
230 | "hostingEnvironment": "[variables('hostingEnvironment')]"
231 | },
232 | "sku": {
233 | "Tier": "[variables('WinSku')]",
234 | "Name": "[variables('WinSkuCode')]"
235 | }
236 | }
237 | ]
238 | }
239 |
--------------------------------------------------------------------------------