PageSourceSearch

https://nystax.gov/src/js/chatbot/settings.js

js nystax.gov collected 2026-10-02 11:17:59 UTC 18,669 bytes, 412 lines download raw bytes

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.