1'use strict'; 2 3/** 4 * Set client auth mode - true to enable client auth, false to disable it. 5 * 6 * Disabling authentication is preferred for initial integration of the SDK with the web app. 7 * 8 * When client authentication has been disabled, only connections made from unblocked lists (allowed domains) are 9 * allowed at the server. This use case is recommended when the client application cannot generate a signed JWT (because 10 * of a static website or no authentication mechanism for the web/mobile app) but requires ODA integration. It can also 11 * be used when the chat widget is already secured and visible to only authenticated users in the client platforms (web 12 * application with the protected page). 13 * 14 * For other cases, it is recommended that client auth enabled mode is used when using the SDK for production as it adds 15 * another layer of security when connecting to a DA/skill. 16 * 17 * When client authentication has been enabled, client authentication is enforced by signed JWT tokens in addition to 18 * the unblocked lists. When the SDK needs to establish a connection with the ODA server, it first requests a JWT token 19 * from the client and then sends it along with the connection request. The ODA server validates the token signature and 20 * obtains the claim set from the JWT payload to verify the token to establish the connection. 21 * 22 * The Web channel in ODA must also be enabled to accept client auth enabled connections. 23 */ 24let isClientAuthEnabled = false; 25 26/** 27 * Initializes the SDK and sets a global field with passed name for it the can 28 * be referred later 29 * 30 * @param {string} name Name by which the chat widget should be referred 31 */ 32function initSdk(name) { 33 // Retry initialization later if the web page hasn't finished loading or the WebSDK is not available yet 34 if (!document || !document.body || !WebSDK) { 35 setTimeout(function () { 36 initSdk(name); 37 }, 2000); 38 return; 39 } 40 41 if (!name) { 42 name = 'Bots'; // Set default reference name to 'Bots' 43 } 44 45 let Bots; 46 let customDelegate = { 47 beforeDisplay: function (message) { 48 console.log(`message... ${JSON.stringify(message)}`); 49 // need to understand what this is for .. 50 // resetTimer(); 51 // if (message.messagePayload.channelExtensions) { 52 // if (message.messagePayload.channelExtensions.lastState) { 53 // //ONLY RESPONSES WITH A LAST STATE PROPERTY SET THE TIMER 54 // setTimer(message.messagePayload.channelExtensions.lastState, 55 // message.messagePayload.channelExtensions.botId, 56 // message.messagePayload.channelExtensions.goToState, 57 // message.messagePayload.channelExtensions.idleTimeout, 58 // message.messagePayload.channelExtensions.idleTimeoutText); 59 // } 60 // } 61 // need to understand what this is for .. 62 return message; 63 }, 64 beforeSend: function (message) { 65 console.log(`message... ${JSON.stringify(message)}`); 66 const foundSSNs = findSSN(message); 67 let firstValue; 68 69 if (foundSSNs != null || foundSSNs != undefined) { 70 firstValue = foundSSNs[0]; 71 let userInput = foundSSNs.input; 72 console.log("userInput: ", userInput); 73 let SSNreplacement = "**SSN-MASKED-FOR-SECURITY**"; 74 let newString = userInput.replace(firstValue, SSNreplacement); 75 console.log("newString: ", newString); 76 //alert("sorry you entered what appears to be a SSN. This is not allowed..."); 77 message.messagePayload.text = newString; 78 console.log("the message : ", message); 79 return message; 80 } else { 81 firstValue = "none" 82 //console.log("beforeSend not found: ", firstValue); 83 return message; 84 } 85 }, 86 beforePostbackSend: function (postback) { 87 console.log(`postback... ${JSON.stringify(postback)}`); 88 return postback; 89 }, 90 render: function (message) { 91 if (message.messagePayload.type === 'card') { 92 // Perform custom rendering for card using msgId 93 return true; 94 } 95 //return false; 96 } 97 }; 98 99 100 /** 101 * SDK configuration settings 102 * 103 * Other than URI, all fields are optional with two exceptions for auth modes: 104 * 105 * In client auth disabled mode, 'channelId' must be passed, 'userId' is optional 106 * In client auth enabled mode, 'clientAuthEnabled: true' must be passed 107 */ 108 const chatWidgetSettings = { 109 URI: 'oda-384a05be0a8e46429f663d51a13263d4-da2.data.digitalassistant.oci.oraclecloud.com', 110 channelId: '41696b90-f02a-4aea-bcab-3db9ebb57132', 111 // ODA URI, only the hostname part should be passed, without the https:// 112 //channelId: 'fec46efe-a8c7-4758-9f61-ac8c205461e6', 113 clientAuthEnabled: isClientAuthEnabled, // Enables client auth enabled mode of connection if set true, no need to pass if set false 114 //userId: '<userId>', // User ID, optional field to personalize user experience 115 enableAutocomplete: true, // Enables autocomplete suggestions on user input 116 enableBotAudioResponse: true, // Enables audio utterance of skill responses 117 enableClearMessage: true, // Enables display of button to clear conversation 118 enableSpeech: true, // Enables voice recognition 119 showConnectionStatus: true, // Displays current connection status on the header 120 i18n: { // Provide translations for the strings used in the widget 121 en: { // en locale, can be configured for any locale 122 chatTitle: 'I\'m the Tax Department\'s chatbot. How can I help?' // Set title at chat header 123 } 124 }, 125 timestampMode: 'relative', // Sets the timestamp mode, relative to current time or default (absolute) 126 theme: WebSDK.THEME.DEFAULT, // Redwood dark theme. The default is THEME.DEFAULT, while older theme is available as THEME.CLASSIC 127 128 linkHandler: { target: '_blank' }, 129 openChatOnLoad: false, 130 enableAttachment: false, 131 logoIcon: 'https://www.tax.ny.gov/images/icons/icon-180x180.png', 132 botIcon: 'https://www.tax.ny.gov/images/icons/icon-180x180.png', 133 botButtonIcon: 'https://www.tax.ny.gov/images/chatbot-icon-final.png', //The skill bot button, displayed when the chat widget is minimized. 134 135 // start martin edits 136 icons: { 137 // avatarAgent: '<svg xmlns="http://www.w3.org/2000/svg" height="32" width="32"><path fill="black" d="M12 2c5.523 0 10 4.477 10 10a9.982 9.982 0 01-3.804 7.85L18 20a9.952 9.952 0 01-6 2C6.477 22 2 17.523 2 12S6.477 2 12 2zm2 16h-4a2 2 0 00-1.766 1.06c1.123.6 2.405.94 3.766.94s2.643-.34 3.765-.94a1.997 1.997 0 00-1.616-1.055zM12 4a8 8 0 00-5.404 13.9A3.996 3.996 0 019.8 16.004L10 16h4c1.438 0 2.7.76 3.404 1.899A8 8 0 0012 4zm0 2c2.206 0 4 1.794 4 4s-1.794 4-4 4-4-1.794-4-4 1.794-4 4-4zm0 2c-1.103 0-2 .897-2 2s.897 2 2 2 2-.897 2-2-.897-2-2-2z" fill="#100f0e" fill-rule="evenodd"/></svg>', 138 avatarUser: '<svg xmlns="http://www.w3.org/2000/svg" height="32" width="32"><path fill="black" d="M12 2c5.523 0 10 4.477 10 10a9.982 9.982 0 01-3.804 7.85L18 20a9.952 9.952 0 01-6 2C6.477 22 2 17.523 2 12S6.477 2 12 2zm2 16h-4a2 2 0 00-1.766 1.06c1.123.6 2.405.94 3.766.94s2.643-.34 3.765-.94a1.997 1.997 0 00-1.616-1.055zM12 4a8 8 0 00-5.404 13.9A3.996 3.996 0 019.8 16.004L10 16h4c1.438 0 2.7.76 3.404 1.899A8 8 0 0012 4zm0 2c2.206 0 4 1.794 4 4s-1.794 4-4 4-4-1.794-4-4 1.794-4 4-4zm0 2c-1.103 0-2 .897-2 2s.897 2 2 2 2-.897 2-2-.897-2-2-2z" fill="#100f0e" fill-rule="evenodd"/></svg>', 139 // avatarBot: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36" fill="none"><path d="M0 0h36v36H0V0z" fill="#C74634"/><path fill-rule="evenodd" clip-rule="evenodd" d="M7.875 8.625a2.25 2.25 0 00-2.25 2.25v16c0 .621.504 1.125 1.125 1.125h.284c.298 0 .585-.119.796-.33l2.761-2.76a2.25 2.25 0 011.59-.66h15.944a2.25 2.25 0 002.25-2.25V10.875a2.25 2.25 0 00-2.25-2.25H7.875zM24.75 18a2.25 2.25 0 100-4.5 2.25 2.25 0 000 4.5zm-4.5-2.25a2.25 2.25 0 11-4.5 0 2.25 2.25 0 014.5 0zm-9 2.25a2.25 2.25 0 100-4.5 2.25 2.25 0 000 4.5z" fill="#fff"/></svg>' 140 }, 141 initUserHiddenMessage: { type: 'text', text: 'Hello' }, 142 initMessageOptions: { 143 sendAt: 'expand' 144 }, 145 delegate: customDelegate 146 // emd martin edits 147 }; 148 149 // Enable custom rendering for cards, Make it `true` to enable custom rendering demo 150 let enableCustomCards = false; 151 152 if (enableCustomCards) { 153 chatWidgetSettings.delegate = { 154 render: (message) => { 155 if (message.messagePayload.type === "card") { 156 const msgElem = document.getElementById(message.msgId); 157 // Create custom styles 158 const styles = ` 159 .custom-card { 160 background-color: white; 161 width: 100%; 162 max-width: 100%; 163 border-radius: 10px; 164 margin: 2px 0 0; 165 } 166 167 .custom-card ul { 168 list-style: none; 169 padding: 0; 170 margin: 0; 171 } 172 173 .custom-card li { 174 display: flex; 175 justify-content: space-between; 176 padding: 10px; 177 border-bottom: thin solid #f5f4f2; 178 } 179 180 .custom-card li:last-child { 181 border-bottom: none; 182 } 183 184 .custom-card button { 185 align-self: start; 186 } 187 188 .actions-wrapper { 189 margin-top: 8px; 190 }`; 191 192 // Create custom template 193 const styleElem = document.createElement('style'); 194 styleElem.innerText = styles; 195 document.head.appendChild(styleElem); 196 197 const cardElem = document.createElement('div'); 198 cardElem.classList.add('custom-card'); 199 const cardList = document.createElement('ul'); 200 const cards = message.messagePayload.cards; 201 cards.forEach(card => { 202 const liElem = document.createElement('li'); 203 const titleElem = document.createElement('div');
204 titleElem.innerText = card.title; 205 liElem.appendChild(titleElem); 206 const button = document.createElement('button'); 207 button.innerText = card.actions[0].label; 208 button.addEventListener('click', () => { 209 actionHandler(card.actions[0]); 210 }); 211 liElem.appendChild(button); 212 cardList.appendChild(liElem); 213 }); 214 cardElem.appendChild(cardList); 215 msgElem.appendChild(cardElem); 216 217 const actions = message.messagePayload.actions; 218 const actionsElem = document.createElement('div'); 219 actionsElem.classList.add('actions-wrapper'); 220 if (actions && actions.length) { 221 actions.forEach(action => { 222 const button = document.createElement('button'); 223 button.innerText = action.label; 224 actionsElem.appendChild(button); 225 button.addEventListener('click', () => { 226 actionHandler(action); 227 }); 228 }); 229 } 230 msgElem.appendChild(actionsElem); 231 // Return `true` for customizing rendering for cards 232 return true; 233 } 234 // Return `false` for all other payloads to continue with WebSDK rendering 235 return false; 236 } 237 } 238 } 239 240 // Initialize SDK 241 if (isClientAuthEnabled) { 242 Bots = new WebSDK(chatWidgetSettings, generateToken); 243 } else { 244 Bots = new WebSDK(chatWidgetSettings); 245 } 246 247 // Connect to skill when the widget is expanded for the first time 248 let isFirstConnection = true; 249 250 Bots.on(WebSDK.EVENT.WIDGET_OPENED, function () { 251 if (isFirstConnection) { 252 Bots.connect(); 253 isFirstConnection = false; 254 } 255 }); 256 257 function actionHandler(action) { 258 Bots.sendMessage(action); 259 } 260 261 // Create global object to refer Bots 262 window[name] = Bots; 263} 264/** 265 * Function to generate search for SSN 266 * Martin Deh 267 * 268 * @returns match object 269 */ 270function findSSN(text) { 271 let messageText; 272 if (text.messagePayload.type != 'formSubmission') { 273 messageText = text.messagePayload.text; 274 } else { 275 messageText = text.messagePayload.submittedFields.orderNumber; 276 } 277 278 console.log("message text", messageText); 279 // This regex matches the standard SSN format (XXX-XX-XXXX) 280 // It also includes common validation rules: 281 // - First part (3 digits) cannot be 000, 666, or 9xx 282 // - Second part (2 digits) cannot be 00 283 // - Third part (4 digits) cannot be 0000 284 //const ssnRegex = /\b(?!000|666|9\d{2})\d{3}-(?!00)\d{2}-(?!0{4})\d{4}\b/g; 285 //const ssnRegex = /(?!(\d){3}(-| |)\1{2}\2\1{4})(?!666|000|9\d{2})(\b\d{3}(-| |)(?!00)\d{2}\4(?!0{4})\d{4}\b)/gm; 286 const ssnRegex = /(\d{3}[- ]?\d{2}[- ]?\d{4}|\d{9})/gm; 287 288 let match = ssnRegex.exec(messageText); 289 if (match !== null || match != undefined) { 290 console.log(`findSSN: Found "${match[0]}" at index ${match.index}.`); 291 292 } else { 293 console.log("findSSN: no match found"); 294 295 } 296 return match; 297 298} 299 300/** 301 * Function to generate JWT tokens. It returns a Promise to provide tokens. 302 * The function is passed to SDK which uses it to fetch token whenever it needs 303 * to establish connections to chat server 304 * 305 * @returns {Promise} Promise to provide a signed JWT token 306 */ 307function generateToken() { 308 return new Promise(function (resolve) { 309 mockApiCall('https://mockurl').then(function (token) { 310 resolve(token); 311 }); 312 }); 313} 314 315/** 316 * A function mocking an endpoint call to backend to provide authentication token 317 * The recommended behaviour is fetching the token from backend server 318 * 319 * @returns {Promise} Promise to provide a signed JWT token 320 */ 321function mockApiCall() { 322 return new Promise(function (resolve) { 323 setTimeout(function () { 324 const now = Math.floor(Date.now() / 1000); 325 const payload = { 326 iat: now, 327 exp: now + 3600, 328 channelId: '<channelID>', 329 userId: '<userID>' 330 }; 331 const SECRET = '<channel-secret>'; 332 333 // An unimplemented function generating signed JWT token with given header, payload, and signature 334 const token = generateJWTToken({ alg: 'HS256', typ: 'JWT' }, payload, SECRET); 335 resolve(token); 336 }, Math.floor(Math.random() * 1000) + 1000); 337 });
338} 339 340/** 341 * Unimplemented function to generate signed JWT token. Should be replaced with 342 * actual method to generate the token on the server. 343 * 344 * @param {object} header 345 * @param {object} payload 346 * @param {string} signature 347 */ 348function generateJWTToken(header, payload, signature) { 349 throw new Error('Method not implemented.'); 350} 351 352const connectSkill = (event) => { 353 event.preventDefault(); 354 const elementUri = document.querySelector('#uri'); 355 const elementChannel = document.querySelector('#channelId'); 356 const URI = elementUri.value; 357 const channelId = elementChannel.value; 358 if (URI && channelId) { 359 Bots.connect({ 360 URI, 361 channelId 362 }); 363 Bots.openChat(); 364 } 365 elementUri.value = ''; 366 elementChannel.value = ''; 367 return false; 368}; 369 370//const form = document.querySelector('#config-form'); 371//form.addEventListener('submit', connectSkill); 372 373var timer = null; 374 375function resetTimer() { 376 if (timer != null) { 377 clearTimeout(timer); 378 } 379 return; 380} 381 382 function setTimer(lastState, botId, goToState, idleTimeout, idleTimeoutText) { 383 var _lastState = lastState; 384 var _botId = botId; 385 var _goToState = goToState; 386 var _idleTimeout = idleTimeout * 1000; 387 var _idleTimeoutText = idleTimeoutText; 388 console.log("...." + _lastState + "....." + _botId + "......" + _idleTimeout + "....." + _goToState); 389 timer = setTimeout(() => { 390 //send message after max. idle time inactivity 391 Bots.sendMessage({ 392 "postback": { 393 //communicate to bot what the state is to 394 //continue the conversation with. This is 395 //usually the state that was active when 396 //the timer expired 397 "variables": { 398 "lastState": _lastState, 399 "idleTimeout": _idleTimeout 400 }, 401 "system.botId": _botId, 402 "action": "", 403 "system.state": _goToState 404 }, 405 "text": _idleTimeoutText, 406 "type": "postback" 407 }); 408 }, 409 //set idle time 410 _idleTimeout 411 ); 412}
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.