PageSourceSearch

https://www.energie-saarlorlux.com/app/uploads/avaya_esll/js/avayaChat.js

js energie-saarlorlux.com collected 2026-09-24 10:15:43 UTC 47,939 bytes, 1,735 lines download raw bytes

1/**
2 * @license global.js
3 *
4 * Copyright 2015 Avaya Inc. All Rights Reserved.
5 *
6 * Usage of this source is bound to the terms described in
7 * licences/License.txt
8 *
9 * Avaya - Confidential & Proprietary. Use pursuant to your signed agreement or
10 * Avaya Policy
11 */
12
13/*
14 * \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
15 * Holds functions and variables that are accessible across the entire site.
16 * Import this before any other script, if not using a concatenated file.
17 * \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
18 */
19
20// Maximum customers per agent
21var customerSoakValue = 2;
22var browserWarning = 'Your browser does not support required features for this website. The earliest supported versions are Internet Explorer 10, Firefox 11, Chrome 31, Safari 7.1 and Opera 12.1';
23
24// If using a popup for chat, set this to true
25var usePopupForChat = false;
26
27// the timeout for the currentqueue REST call
28var queueTimeout = 60000;
29
30// the timeout for the callback REST calls
31var callbackTimeout = 60000;
32
33// Detect browser support when ready
34document.onreadystatechange = function() {
35    'use strict';
36
37    // IE 10 does not appear to recognise 'interactive' - however, IE 11,
38    // Firefox and Chrome do
39    if (document.readyState === 'complete' || document.readyState === 'interactive') {
40        detectBrowserSupport();
41        gatherElements();
42    }
43};
44
45/**
46 * Logs a specified message to the browser console, if it has been defined for a
47 * particular browser.
48 * @param messageToLog
49 */
50function logToConsole(messageToLog) {
51    'use strict';
52    if (console !== undefined) {
53        console.log(messageToLog);
54    }
55}
56
57/**
58 * Returns a specific element
59 * @param el
60 * @returns the element with the specific Id
61 */
62function getEl(el) {
63    'use strict';
64    return document.getElementById(el);
65}
66
67/**
68 * Checks if the user's browser supports the required features. For the chat to
69 * work, the browser <b>MUST</b> support JSON parsing, WebSockets and
70 * XMLHttpRequests. If it does not support any of these, alert the user.
71 */
72function detectBrowserSupport() {
73    'use strict';
74    if (!WebSocket || !XMLHttpRequest || !JSON) {
75        window.alert(browserWarning);
76    }
77
78}
79
80/**
81 * Adds the specified key-value pair to sessionStorage.
82 * @param key
83 * @param value
84 */
85function setSessionStorage(key, value) {
86    'use strict';
87    sessionStorage.setItem(key, value);
88}
89
90/**
91 * Get the specified item from sessionStorage
92 * @param key
93 * @returns
94 */
95function getSessionStorage(key) {
96    'use strict';
97    return sessionStorage.getItem(key);
98}
99
100/**
101 * Gathers all elements on page load.
102 */
103function gatherElements() {
104    'use strict';
105    logToConsole('Gathering elements');
106    sendButton = getEl('sendbutton-chat');
107    callbackRequestButton = getEl('callbackPanel');
108    closeButton = getEl('closebutton-chat');
109    messages = getEl('messages');
110    outMessage = getEl('outmessage');
111    participants = getEl('participants');
112    pagePushDiv = getEl('pagePushDiv');
113}
114
115/**
116 * Clear the user details from session storage.
117 */
118function clearSessionStorage() {
119    'use strict';
120    setSessionStorage('user', '');
121    setSessionStorage('phone', '');
122    setSessionStorage('email', '');
123    setSessionStorage('account', '');
124    setSessionStorage('skillset', '');
125    setSessionStorage('subject', '');
126}
127
128/**
129 * webChatConfig.js
130 *
131 * Copyright 2015 Avaya Inc. All Rights Reserved.
132 *
133 * Usage of this source is bound to the terms described in
134 * licences/License.txt
135 *
136 * Avaya - Confidential & Proprietary. Use pursuant to your signed agreement or
137 * Avaya Policy
138 */
139
140/*
141 * \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
142 *
143 * This section configures the Web Chat.
144 *
145 * \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
146 */
147
148var webSocket;
149
150// Variables for reconnection timeout.
151// Try every 3 seconds for a total of 10 minutes
152var totalNumberOfRetrys = 0;
153var maxNumberOfRetrys = 200;
154var retryInterval = 3000;
155var dontRetryConnection = false;
156var reconnectionTimeout;
157var previouslyConnected = false;
158
159//queue Status delay timeout
160var queueStatusDelayTimeout = 3500;
161
162// Message types
163var messageTypeNewChatAck = 'newChatAcknowledgement';
164var messageTypeAck = 'acknowledgement';
165var messageTypeError = 'error';
166var messageTypeNotification = 'notification';
167
168// Json method names for notification messages
169var jsonMethodRequestChat = 'requestChat';
170var jsonMethodRequestCallBack = 'callBack';
171var jsonMethodRequestNewParticipant = 'newParticipant';
172var jsonMethodRequestisTyping = 'isTyping';
173var jsonMethodRequestNewMessage = 'newMessage';
174var jsonMethodRequestCloseConversation = 'closeConversation';
175var jsonMethodRequestParticipantLeave = 'participantLeave';
176var jsonMethodRequestNewPushMessage = 'newPushPageMessage';
177var jsonMethodRequestRequestTranscript = 'requestTranscript';
178var jsonMethodRequestQueueMetrics = 'queueStatus';
179var jsonMethodPing = 'ping';
180
181// run the Web On Hold messages every 3 seconds
182var webOnHoldTimer = 3000;
183
184// typingTimeouts
185var typingTimeout = 10000;
186var agentTypingTimeout = 3000;
187
188// the ping timer affects how often PING messages are sent
189var pingTimer = 5000;
190
191// Some of these variables will be moved around
192var participants;
193var users = {};
194
195// Write response classes - defined in the sample style.css file
196var writeResponseClassResponse = 'response';
197var writeResponseClassSent = 'sent';
198
199// Images for the users panel
200var agentImage = 'images/agent.png';
201var agentTypingImage = 'images/agent_typing.png';
202var supervisorImage = 'images/supervisor.png';
203var supervisorTypingImage = 'images/supervisor_typing.png';
204
205/**
206 * webChat.js
207 *
208 * Copyright 2015 Avaya Inc. All Rights Reserved.
209 *
210 * Usage of this source is bound to the terms described in
211 * licences/License.txt
212 *
213 * Avaya - Confidential & Proprietary. Use pursuant to your signed agreement or
214 * Avaya Policy
215 */
216
217/*
218 * \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
219 * This section defines the client end of the WebSocket chat.
220 * \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
221 */
222
223// particular elements with particular names
224// These rely on elements in the page, or need to be defined first
225var sendButton;
226var callbackRequestButton;
227var closeButton;
228var messages;
229var outMessage;
230var guid = null;
231var webOnHoldComfortGroups;
232var webOnHoldURLs;
233
234// the authentication key
235var ak = null;
236
237// run the WebOnHold check every 3 seconds
238var webOnHoldInterval;
239
240// PING messages are sent at regular intervals
241var pingInterval;
242
243// these handle the isTyping notifications. By default, allows
244// a 10 second gap between keypresses
245var isTyping = false;
246var typeOut;
247var activeAgentTypeOut;
248var passiveAgentTypeOut;
249var supervisorTypeOut;
250var usersTable;
251
252// the queue status check is sent at a delay
253var queueStatusDelay;
254
255// the following variables require getSessionStorage,
256// but are initialised on chat begin
257var g_email, g_user, g_skillset, g_account, g_phone, g_country, g_subject;
258
259var initCalled = false;
260
261// should page pushes from agent open automatically?
262var autoOpen = false;
263
264// an array of timeouts to clear
265var timeouts = [];
266
267var pagePushDiv;
268
269/**
270 * Toggles the controls. Ignores the close button so the user can leave a chat
271 * at any time.
272 * @param isDisabled is true if controls are disabled
273 */
274function disableControls(isDisabled) {
275    'use strict';
276
277    logToConsole('Disabling controls: ' + isDisabled);
278    sendButton.disabled = isDisabled;
279    outMessage.disabled = isDisabled;
280
281    if (isDisabled === false) {
282        pagePushDiv.style.display = 'block';
283        outMessage.addEventListener('keydown', onType);
284    } else {
285        pagePushDiv.style.display = 'none';
286        outMessage.removeEventListener('keydown', onType);
287
288    }
289}
290
291/**
292 * Called when the customer types
293 * @param e
294 */
295function onType(e) {
296    'use strict';
297    if (e.keyCode === 13) {
298        sendChatMessage();
299    } else {
300        startTypingTimer();
301    }
302}
303
304/**
305 * Creates a new paragraph with a specific class, adds the specified text to it,
306 * and then appends it to the messages div. Also includes an autoscroll
307 * mechanism.
308 * @param text
309 * @param msgClass defined inside the CSS file
310 */
311function writeResponse(text, msgClass) {
312    'use strict';
313    var paragraph = document.createElement('p');
314    paragraph.className = msgClass;
315    paragraph.textContent = text;
316    messages.appendChild(paragraph);
317    // Scroll to bottom of the messages div to show new messages
318    messages.scrollTop = messages.scrollHeight;
319}
320
321/**
322 * Updates the users section.
323 */
324function updateUsers(agents) {
325    'use strict';
326
327    users = {};
328
329    // hide the children immediately, and then reshow those
330    for (var x = 0; x < participants.children.length; x++) {
331        var child = participants.children[x], image = child.getElementsByTagName('img')[0];
332        child.className = 'hidden';
333        image.className = 'hidden';
334        image.src = '';
335        // clear the TextNode after this - i.e. removes the text after the image
336        image.nextSibling.textContent = '';
337    }
338
339    if (agents !== undefined) {
340        for (var i = 0; i < agents.length; i++) {
341            var agent = agents[i];
342            logToConsole('Adding agent with id ' + agent.id + ' and name ' + agent.name);
343
344            var div = participants.children[i];
345            div.className = 'user';
346            // find the first image in the div
347            var typingImage = div.getElementsByTagName('img')[0];
348            typingImage.src = (agent.type === 'supervisor') ? supervisorImage : agentImage;
349            typingImage.className = '';
350
351            // should be the first TextNode in the div after the image
352            // which enters it into the div just after the image
353            typingImage.nextSibling.textContent = agent.name;
354
355            users[agent.id] = {
356                name : agent.name,
357                isTyping : false,
358                agentType : agent.type,
359                image : typingImage
360            };
361        }
362    }
363}
364
365/**
366 * Adds a timer to the specified array.
367 * @param array
368 */
369function addTimerToArray(array) {
370    'use strict';
371    if (array === undefined) {
372        return;
373    }
374
375    for (var p = 0; p < array.length; p++) {
376        var obj = array[p];
377        obj.lastCalled = new Date().valueOf();
378        obj.currentSequence = 0;
379    }
380}
381
382/**
383 * Plays the WebOnHold messages.
384 * @param array
385 */
386function playOnHoldMessages(array) {
387    'use strict';
388    var currentDate = new Date(), currentTime = currentDate.valueOf();
389    for (var i = 0; i < array.length; i++) {
390        var group = array[i];
391        var deltaTime = (currentTime - group.lastCalled) / 1000;
392        if (deltaTime >= group.delay || deltaTime >= group.holdTime) {
393            var currentMsg;
394
395            // if this has a urls array, it's a WebOnHold URL
396            // otherwise, it's a comfort message
397            if (group.urls !== undefined) {
398                currentMsg = group.urls[group.currentSequence];
399
400                writeResponse('Please visit this link: ', writeResponseClassResponse);
401                appendLink(currentMsg.url, '_blank', false);
402
403            } else {
404                currentMsg = group.messages[group.currentSequence];
405                writeResponse(currentMsg.message, writeResponseClassResponse);
406            }
407            group.lastCalled = currentTime;
408            group.currentSequence++;
409            if ((group.numberOfMessages !== undefined && group.currentSequence >= group.numberOfMessages) ||
410                    (group.urls !== undefined && group.currentSequence >= group.urls.length)) {
411                group.currentSequence = 0;
412            }
413        }
414    }
415}
416
417/**
418 * Append a link to the transcript box.
419 * @param url
420 * @param urlDestination destination for the URL
421 * @param openAutomatically if true, should open automatically
422 */
423function appendLink(url, urlDestination, openAutomatically) {
424    'use strict';
425
426    var p = document.createElement('p');
427    var link = document.createElement('a');
428    link.href = url;
429    link.text = url;
430    link.target = '_' + urlDestination;
431    p.appendChild(link);
432    messages.appendChild(link);
433
434    // Scroll to bottom of the messages div to show new messages
435    messages.scrollTop = messages.scrollHeight;
436
437    if (openAutomatically) {
438        window.open(link);
439    }
440}
441
442/**
443 * Logs the user into the chat.
444 * @param user
445 * @param email
446 * @param account
447 * @param phone
448 */
449function chatLogin(user, email, account, skillset, phone) {
450    'use strict';
451    var wantsEmail = getEl('transcript-chat').checked;
452    var custAddress = getEl('address-chat');
453
454    // if the user didn't specify a valid email address, they can't receive a transcript.
455    if (isStringEmpty(email)) {
456        wantsEmail = false;
457    }
458
459//    logToConsole('country is ' + phone.country + ', area is ' + phone.area + ', phone is ' + phone.phone);
460   var msg = {
461        'apiVersion' : '1.0',
462        'type' : 'request',
463        'body' : {
464            'method' : 'requestChat',
465            'guid' : guid,
466            'authenticationKey' : ak,
467            'deviceType' : navigator.userAgent,
468            'requestTranscript' : wantsEmail,
469            'intrinsics' : {
470                'email' : email,
471                'name' : user,
472                //'country' : phone.country,
473                //'area' : phone.area,
474                //'phoneNumber' : phone.phone,
475                'skillset' : g_skillset //,
476                //'customFields' : [ {
477                //    'title' : 'address',
478                //    'value' : custAddress.value
479                //} ]
480            }
481        }
482    };
483    writeResponse('Sende Anmeldung zum Server', writeResponseClassSent);
484    sendMessage(msg);
485}
486
487/**
488 * Sends the chatMessage.
489 */
490function sendChatMessage() {
491    'use strict';
492    var text = outMessage.value;
493
494    if (text !== '') {
495        writeResponse(g_user + ': ' + text, writeResponseClassSent);
496        var message = {
497            'apiVersion' : '1.0',
498            'type' : 'request',
499            'body' : {
500                'method' : 'newMessage',
501                'message' : text
502            }
503        };
504        sendMessage(message);
505        outMessage.value = '';
506
507        // set isTyping to false
508        isTyping = false;
509    }
510
511}
512
513/**
514 * Sends a close conversation request to the server.
515 */
516function quitChat() {
517    'use strict';
518    if (webSocket !== null && webSocket.readyState === webSocket.OPEN) {
519        var closeRequest = {
520            'apiVersion' : '1.0',
521            'type' : 'request',
522            'body' : {
523                'method' : 'closeConversation'
524            }
525        };
526        writeResponse('Close request sent', writeResponseClassSent);
527        sendMessage(closeRequest);
528    }
529}
530
531/**
532 * Sends an IsTyping message to the server
533 * @param isUserTyping
534 */
535function sendIsTyping(isUserTyping) {
536    'use strict';
537    var isTypingMessage = {
538        'apiVersion' : '1.0',
539        'type' : 'request',
540        'body' : {
541            'method' : 'isTyping',
542            'isTyping' : isUserTyping
543        }
544    };
545    sendMessage(isTypingMessage);
546}
547
548/**
549 * Sends a page push request to the agent. Remove if not required.
550 */
551function sendPagePushRequest() {
552    'use strict';
553
554    // get the URL, and then trim whitespace from it
555    var url = getEl('urlInput').value.trim();
556
557    if (url === '') {
558        writeResponse('Please enter a link to send it', writeResponseClassResponse);
559        return;
560    }
561
562    var ppr = {
563        'apiVersion' : '1.0',
564        'type' : 'request',
565        'body' : {
566            'method' : 'newPushPageMessage',
567            'pagePushURL' : url,
568            'pagePushDestination' : 'newTab'
569        }
570    };
571
572    writeResponse('Your link has been sent', writeResponseClassSent);
573    getEl('urlInput').value = '';
574    sendMessage(ppr);
575
576}
577
578/**
579 * Sends a queueStatusRequest upon login.
580 */
581function sendQueueStatusRequest() {
582    'use strict';
583    var statusRequest = {
584        'apiVersion' : 1.0,
585        'type' : 'request',
586        'body' : {
587            'method' : 'queueStatus'
588        }
589    };
590
591    sendMessage(statusRequest);
592
593}
594
595/**
596 * When a message of type requestChat comes in, use it to initialise some
597 * variables and the queue status check.
598 * @param body
599 */
600function notifyRequestChat(body) {
601    'use strict';
602
603    logToConsole(body);
604    guid = body.guid;
605    g_user = body.intrinsics.name;
606    ak = body.authenticationKey;
607    writeResponse('Der nächste freie Mitarbeiter ist gleich für Sie da.', writeResponseClassResponse);
608
609    if (totalNumberOfRetrys > 0) {
610        resetConnectionAttempts();
611    }
612
613    // if the customer has already been connected, don't play the on hold
614    // messages
615    if (!previouslyConnected) {
616        previouslyConnected = true;
617        webOnHoldComfortGroups = body.webOnHoldComfortGroups;
618        webOnHoldURLs = body.webOnHoldURLs;
619        addTimerToArray(webOnHoldComfortGroups);
620        addTimerToArray(webOnHoldURLs);
621        startOnHoldMessages();
622        queueStatusDelay = setTimeout(sendQueueStatusRequest, queueStatusDelayTimeout);
623    }
624
625}
626
627/**
628 * Notifies the user of the new participant.
629 * @param body of the message
630 */
631function notifyNewParticipant(body) {
632    'use strict';
633
634    writeResponse('Ein Mitarbeiter hat den Chat betreten', writeResponseClassResponse);
635    var agents = body.participants;
636    updateUsers(agents);
637
638    // enable the controls and clear the intervals
639    clearInterval(webOnHoldInterval);
640    clearTimeout(queueStatusDelay);
641    disableControls(false);
642}
643
644/**
645 * Notifies the user that another user is typing.
646 * @param body of the message
647 */
648function notifyIsTyping(body) {
649    'use strict';
650    var isAgentTyping = body.isTyping;
651
652    if (isAgentTyping === true) {
653        var agent = users[body.agentId];
654        agent.isTyping = isAgentTyping;
655        updateTypingCell(agent, true);
656
657        var agentTypeOut;
658        if (agent.type === 'active_participant') {
659            agentTypeOut = activeAgentTypeOut;
660        } else if (agent.type === 'passive_participant') {
661            agentTypeOut = passiveAgentTypeOut;
662        } else {
663            agentTypeOut = supervisorTypeOut;
664        }
665
666        if (agentTypeOut !== undefined) {
667            clearTypingTimer(agentTypeOut);
668        }
669
670        agentTypeOut = setTimeout(function() {
671            if (users !== undefined) {
672                agent.isTyping = false;
673                updateTypingCell(agent, false);
674            }
675        }, agentTypingTimeout);
676        timeouts.push(agentTypeOut);
677    }
678}
679
680/**
681 * Updates the typing icon for a specific agent.
682 * @param agent
683 * @param isTyping
684 */
685function updateTypingCell(agent, isTyping) {
686    'use strict';
687    var image = agent.image;
688
689    if (agent.agentType === 'active_participant' || agent.agentType === 'passive_participant') {
690        image.src = isTyping === true ? agentTypingImage : agentImage;
691
692    } else {
693        image.src = isTyping === true ? supervisorTypingImage : supervisorImage;
694    }
695
696    image.nextSibling.textContent = isTyping === true ? agent.name.concat(' schreibt') : agent.name;
697
698}
699
700/**
701 * Notifies the user that an actual message has arrived.
702 * @param body of the message
703 */
704function notifyNewMessage(body) {
705    'use strict';
706    // parse the date first, then write the message out to the div
707    var date = new Date(body.timestamp);
708    writeResponse(body.displayName + ': ' + body.message + ' - gesendet um ' + date.toLocaleTimeString(),
709            writeResponseClassResponse);
710}
711
712/**
713 * Notifies the user of the status of a close request. Mainly used when the
714 * customer decides to leave.
715 * @param body of the message
716 */
717function notifyCloseConversation(body) {
718    'use strict';
719    logToConsole('Soll der Chat geschlossen werden? ' + body.result);
720
721    dontRetryConnection = body.result;
722    if (body.result === true) {
723        webSocket.close(1000, 'Chat erfolgreich geschlossen');
724
725        // clear the user's details from session storage
726        clearSessionStorage();
727    }
728}
729
730/**
731 * Notifies the user that another user has left the chat. If the endChatFlag
732 * variable of the message is set to true, close the webSocket.
733 * @param body of the message
734 */
735function notifyParticipantLeave(body) {
736    'use strict';
737
738    writeResponse('Der Mitarbeiter hat den Chat verlassen', writeResponseClassResponse);
739
740    if (body.endChatFlag === true) {
741        dontRetryConnection = true;
742        webSocket.close(1000, 'Gespräch beenden');
743    } else {
744        // if there is only one user left (i.e. the customer),
745        // play the webOnHold messages
746        if (Object.keys(body.participants).length === 0) {
747            logToConsole('Nur der Kunde ist noch im Chat.');
748            disableControls(true);
749            startOnHoldMessages();
750            participants.textString = '';
751        }
752    }
753
754    var agents = body.participants;
755    updateUsers(agents);
756}
757
758/**
759 * Notifies the user of a new page being pushed.
760 * @param body of the message
761 */
762function notifyNewPagePushMessage(body) {
763    'use strict';
764    var sentDate = new Date(body.timestamp), url = body.pagePushURL, destination = body.pagePushDestination;
765    writeResponse(body.displayName + ' pushed the following URL at ' + sentDate.toLocaleTimeString() + ':',
766            writeResponseClassResponse);
767
768    appendLink(url, destination, autoOpen);
769
770}
771
772/**
773 * Notifies the user of the current queue status.
774 * @param body
775 */
776function notifyQueueStatus(body) {
777    'use strict';
778    var position = body.positionInQueue, wait = body.estimatedWaitTime;
779    logToConsole('Kunde ist in position ' + position + ' und wartet bereits seit ' + wait + ' Sekunden');
780    var waitMinutes = Math.round(wait / 60);
781
782    // if the customer's expected wait time or position in the queue is greater than 0, let them know.
783    if (waitMinutes > 0 && position > 0) {
784        writeResponse('Sie sind auf Position ' + position + ' in der Warteschlange. Die vorraussichtliche Wartezeit beträgt ' + waitMinutes +
785                ' Minuten', writeResponseClassResponse);
786    }
787
788}
789
790/**
791 * Handles notification messages to reduce complexity of handleMessage. This in
792 * turn is broken into subfunctions to further reduce it.
793 * @param message
794 */
795function handleNotification(message) { // NOSONAR Reason: too complex, but cannot be reduced further
796    'use strict';
797    var body = message.body, method = body.method;
798
799    if (method === jsonMethodRequestChat) {
800        notifyRequestChat(body);
801    } else if (method === jsonMethodRequestNewParticipant) {
802        notifyNewParticipant(body);
803    } else if (method === jsonMethodRequestisTyping) {
804        notifyIsTyping(body);
805    } else if (method === jsonMethodRequestNewMessage) {
806        notifyNewMessage(body);
807    } else if (method === jsonMethodRequestCloseConversation) {
808        notifyCloseConversation(body);
809    } else if (method === jsonMethodRequestParticipantLeave) {
810        notifyParticipantLeave(body);
811    } else if (method === jsonMethodRequestNewPushMessage) {
812        notifyNewPagePushMessage(body);
813    } else if (method === jsonMethodRequestQueueMetrics) {
814        notifyQueueStatus(body);
815    } else if (method === jsonMethodPing) {
816        // do nothing with pings. They just confirm that the websocket is open.
817    } else {
818        throw new TypeError('Received notification with unknown method: ' + method);
819    }
820
821}
822
823/**
824 * Opens the chat.
825 */
826function openChat() {
827    'use strict';
828    initChat();
829
830}
831
832/**
833 * Performs the actual initialisation.
834 */
835function initChat() {
836    'use strict';
837
838    // if the chat has already opened, don't bother opening it
839    if (initCalled) {
840        return;
841    }
842
843    g_user = getSessionStorage('user');
844    g_email = getSessionStorage('email');
845    g_skillset = getSessionStorage('skillset');
846    g_account = getSessionStorage('account');
847    g_phone = ''; //JSON.parse(getSessionStorage('phone'));
848    g_country = getSessionStorage('country');
849    g_subject = getSessionStorage('subject');
850    logToConsole('Subject is ' + g_subject);
851
852    // this is sometimes undefined at this point.
853    if (outMessage === undefined) {
854        outMessage = getEl('outmessage');
855    }
856
857    disableControls(true);
858    openSocket();
859    initCalled = true;
860}
861
862/**
863 * Start playing the Web on hold messages.
864 */
865function startOnHoldMessages() {
866    'use strict';
867
868    logToConsole('Starting the On Hold messages');
869    webOnHoldInterval = setInterval(function() {
870        if (webOnHoldURLs !== undefined) {
871            playOnHoldMessages(webOnHoldURLs);
872        } else {
873            logToConsole('On Hold URLS not defined');
874        }
875
876        if (webOnHoldComfortGroups !== undefined) {
877            playOnHoldMessages(webOnHoldComfortGroups);
878        } else {
879            logToConsole('On Hold Messages not defined');
880        }
881
882    }, webOnHoldTimer);
883
884    timeouts.push(webOnHoldInterval);
885}
886
887/**
888 * Clear the keypress timer.
889 * @param obj
890 */
891function clearTypingTimer(obj) {
892    'use strict';
893    if (obj) {
894        clearTimeout(obj);
895    }
896}
897
898/**
899 * Start the keypress timer.
900 * @param obj
901 */
902function startTypingTimer() {
903    'use strict';
904    clearTypingTimer(typeOut);
905
906    if (isTyping === false) {
907        isTyping = true;
908        sendIsTyping(isTyping);
909    }
910
911    typeOut = setTimeout(function() {
912        isTyping = false;
913
914    }, typingTimeout);
915    timeouts.push(typeOut);
916}
917
918/**
919 * Clear all timeouts in the array.
920 * @param timeouts
921 */
922function clearAllTimeouts() {
923    'use strict';
924
925    for (var i = 0; i < timeouts.length; i++) {
926        clearTimeout(timeouts[i]);
927    }
928}
929
930/**
931 * Sends a PING message to check the server. There is no API in JavaScript to do
932 * this, so this is a workaround. Send this every 10 seconds or so.
933 */
934function sendPing() {
935    'use strict';
936
937    var ping = {
938        'apiVersion' : '1.0',
939        'type' : 'request',
940        'body' : {
941            'method' : 'ping'
942        }
943    };
944
945    webSocket.send(JSON.stringify(ping));
946}
947
948/**
949 * webChatSocket.js
950 *
951 * Copyright 2015 Avaya Inc. All Rights Reserved.
952 *
953 * Usage of this source is bound to the terms described in
954 * licences/License.txt
955 *
956 * Avaya - Confidential & Proprietary. Use pursuant to your signed agreement or
957 * Avaya Policy
958 */
959
960/*
961 * \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
962 *
963 * The following section defines the WebSocket and it's interactions
964 *
965 * \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
966 */
967
968/**
969 * Opens the WebSocket.
970 */
971function openSocket() {
972    'use strict';
973    participants = getEl('participants');
974    logToConsole('Opening the WebSocket');
975
976    // Ensures only one connection is open at a time
977    if (webSocket !== undefined && (webSocket.readyState !== WebSocket.CLOSED)) {
978        logToConsole('WebSocket is already opened');
979        return;
980    }
981
982    clearTimeout(reconnectionTimeout);
983
984    // Create a new instance of the WebSocket using the specified url
985    webSocket = new WebSocket(webChatUrl);
986
987    // Binds functions to the listeners for the websocket.
988    webSocket.onopen = handleOpen;
989    webSocket.onmessage = handleMessage;
990    webSocket.onclose = function(event) {
991
992        // disable the controls upon close
993        disableControls(true);
994
995        // If it is an expected/graceful close, do not attempt to reconnect.
996        if (event.code === 1000 || dontRetryConnection) {
997            handleClose(event);
998        } else {
999            reconnect();
1000        }
1001    };
1002    webSocket.onerror = handleError;
1003}
1004
1005/**
1006 * Opens the WebSocket.
1007 * @param event
1008 */
1009function handleOpen(event) {
1010    'use strict';
1011
1012    // if there are agents in the chat, enable the controls
1013    if (Object.keys(users).length <= 0) {
1014        logToConsole('No users in room, disabling controls');
1015        disableControls(true);
1016    } else {
1017        logToConsole('Agents already in chat, enabling controls');
1018        disableControls(false);
1019    }
1020
1021    pingInterval = setInterval(function() {
1022        sendPing();
1023    }, pingTimer);
1024    timeouts.push(pingInterval);
1025
1026    chatLogin(g_user, g_email, g_account, g_skillset, g_phone);
1027}
1028
1029/**
1030 * Handles incoming messages. The message is parsed as a JSON object, and then
1031 * handled according to the message type.
1032 * @param event
1033 */
1034function handleMessage(event) {
1035    'use strict';
1036    var msg = JSON.parse(event.data), body = msg.body, method = body.method;
1037
1038    // Handle the message according to the type and method.
1039    // Notifications are in their own method to reduce complexity.
1040    if (msg.type === messageTypeNotification) {
1041        handleNotification(msg);
1042    } else if (msg.type === messageTypeError) {
1043        writeResponse('An error occurred ' + body.code + ' (' + body.errorMessage + ')', writeResponseClassResponse);
1044    } else if (msg.type === messageTypeAck) {
1045        // Nothing to do for acks
1046    } else if (msg.type === messageTypeNewChatAck) {
1047        // if a newChatAcknowledgement has been received,
1048        // then use it to initialise the guid, webOnHold variables, etc.
1049        guid = body.guid;
1050        // enable the controls and let the user know that their request
1051        // has been approved
1052        disableControls(false);
1053        resetConnectionRetrys();
1054        writeResponse('Chat request approved', writeResponseClassResponse);
1055    } else {
1056        throw new TypeError('Unknown message type:\n' + msg);
1057    }
1058}
1059/**
1060 * Upon closing, inform the user and disable the controls.
1061 * @param event
1062 */
1063function handleClose() {
1064    'use strict';
1065    logToConsole('Definitely closing the WebSocket.');
1066    writeResponse('Verbindung geschlossen', writeResponseClassResponse);
1067    disableControls(true);
1068    outMessage.textContent = '';
1069    clearAllTimeouts();
1070
1071    updateUsers();
1072}
1073
1074/**
1075 * Handles errors by printing them to the browser console and alerting the user.
1076 * @param event
1077 */
1078function handleError(event) {
1079    'use strict';
1080    logToConsole(event);
1081    writeResponse('A connection error has occurred', writeResponseClassResponse);
1082}
1083
1084/**
1085 * Upon disconnection, this will attempt to reconnect to a WebSocket every
1086 * @param retryInterval
1087 * @param totalNumberOfRetrys
1088 */
1089function reconnect() {
1090    'use strict';
1091    if (webSocket.readyState !== webSocket.OPEN) {
1092        reconnectionTimeout = setTimeout(function() {
1093            if (totalNumberOfRetrys <= maxNumberOfRetrys) {
1094                logToConsole('Connection failed. Reconnecting (attempt ' + totalNumberOfRetrys + ' of ' +
1095                        maxNumberOfRetrys + ')');
1096                openSocket();
1097
1098                if (!dontRetryConnection) {
1099                    clearTimeout(reconnectionTimeout);
1100                    totalNumberOfRetrys++;
1101                    reconnect();
1102                }
1103
1104            } else {
1105                dontRetryConnection = true;
1106                writeResponse('Connection failed', writeResponseClassResponse);
1107            }
1108        }, retryInterval);
1109    }
1110
1111}
1112
1113/**
1114 * Called after a successful connection to reset the counter for number of
1115 * socketConnection retries
1116 */
1117function resetConnectionAttempts() {
1118    'use strict';
1119    totalNumberOfRetrys = 0;
1120    dontRetryConnection = false;
1121    clearTimeout(reconnectionTimeout);
1122
1123}
1124
1125/**
1126 * Stringifies a JSON object and sends it over the WebSocket.
1127 * @param outMsg
1128 */
1129function sendMessage(outMsg) {
1130    'use strict';
1131    if (webSocket !== null && webSocket.readyState === WebSocket.OPEN) {
1132        webSocket.send(JSON.stringify(outMsg));
1133    }
1134}
1135
1136/**
1137 * webcheckQueue.js
1138 *
1139 * Copyright 2015 Avaya Inc. All Rights Reserved.
1140 *
1141 * Usage of this source is bound to the terms described in
1142 * licences/License.txt
1143 *
1144 * Avaya - Confidential & Proprietary. Use pursuant to your signed agreement or
1145 * Avaya Policy
1146 */
1147
1148/*
1149 * \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
1150 *
1151 * This section performs a REST call to check the status of a queue
1152 * on the main pages.
1153 *
1154 * \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
1155 */
1156
1157var pageSkillset;
1158
1159/**
1160 * Checks if a particular string is empty
1161 * @param str
1162 * @returns {Boolean}
1163 */
1164function isStringEmpty(str) {
1165    'use strict';
1166if ((str !== undefined) && (str !== null)) {
1167    return (str.length === 0);
1168}
1169else { return 0;
1170}}
1171
1172/**
1173 * Creates a JSON message for a particular skillset value.
1174 * @param value for the skillset
1175 * @returns msg as a JSON string
1176 */
1177function createJson(value) {
1178    'use strict';
1179    var msg = {
1180        'skillset' : value
1181    };
1182    return JSON.stringify(msg);
1183}
1184
1185/**
1186 * Receives a JSON object from the server which contains the queue status.
1187 * @param data from the server
1188 * @param button or elemen that enables the chat window
1189 */
1190function receiveQueueStatus(data, button) {
1191    'use strict';
1192    var msg = JSON.parse(data), metrics = msg.body.metrics;
1193    var queueAvailable = false;
1194    // If the customer is asking for any skillset, check that at least one queue
1195    // is available. Otherwise, check if the specific queue is available
1196    if (isStringEmpty(pageSkillset)) {
1197        queueAvailable = checkAllSkillsets(metrics);
1198    } else {
1199        queueAvailable = checkSkillset(metrics);
1200    }
1201
1202    toggleChatButton(queueAvailable, button, metrics);
1203}
1204
1205/**
1206 * Checks that a specific queue is available.
1207 * @param queueMetrics
1208 * @returns {Boolean}
1209 */
1210function checkSkillset(queueMetrics) {
1211    'use strict';
1212    logToConsole('Asked specifically for ' + pageSkillset);
1213    queueMetrics = removeFullQueues(queueMetrics);
1214
1215    if (queueMetrics.length > 0) {
1216        setSkillsets(queueMetrics);
1217        return true;
1218    }
1219    return false;
1220
1221}
1222
1223/**
1224 * Checks that at least one queue is available.
1225 * @param queueMetrics
1226 * @returns {Boolean}
1227 */
1228function checkAllSkillsets(queueMetrics) {
1229    'use strict';
1230    queueMetrics = removeFullQueues(queueMetrics);
1231    if (queueMetrics.length > 0) {
1232        setSkillsets(queueMetrics);
1233        return true;
1234    }
1235    return false;
1236}
1237
1238/**
1239 * Filters out full queues
1240 * @param metrics
1241 * @returns the filtered and sorted queue
1242 */
1243function removeFullQueues(metrics) {
1244    'use strict';
1245
1246    return metrics
1247            .filter(function(queue) {
1248                return ((queue.customersInQueue <= (queue.availableAgentsInQueue * customerSoakValue) && queue.customersInQueue <= queue.maxConcurrentChats) && queue.availableAgentsInQueue > 0);
1249            });
1250}
1251
1252/**
1253 * Toggles the chat button if there is a queue available.
1254 * @param isQueueAvailable
1255 */
1256function toggleChatButton(isQueueAvailable, chatButton, metrics) {
1257    'use strict';
1258    if (isQueueAvailable === true) {
1259        jQuery('.js-chat').addClass('is-active');
1260        chatButton.removeAttribute('hidden');
1261        chatButton.style.visibility = 'visible';
1262	var chatNotAvailable = getEl('chatNA');
1263	chatNotAvailable.setAttribute('hidden','hidden');
1264	chatNotAvailable.style.visibility = 'hidden';
1265
1266    }
1267}
1268
1269/**
1270 * Toggles the chat button depending on the availability of a particular
1271 * skillset
1272 * @param skillset
1273 */
1274function startQueueCheck() {
1275    'use strict';
1276
1277    // if XMLHttpRequest and WebSocket are supported by the browser,
1278    // perform the queue check. Otherwise, don't bother.
1279    if (XMLHttpRequest && WebSocket) {
1280        var chatButton = getEl('chatPanel'), server = new XMLHttpRequest({
1281            mozSystem : true,
1282            timeout : queueTimeout
1283        });
1284        var chatNotAvailable = getEl('chatNA');
1285        pageSkillset = getEl('pageSkillSet').value;
1286        chatButton.setAttribute('hidden', 'hidden');
1287        chatButton.style.visibility = 'hidden';
1288	chatNotAvailable.removeAttribute('hidden');
1289	chatNotAvailable.style.visibility = 'visible';
1290
1291        // Creates an event listener for the request using the
1292        // requestQueueStatus function
1293        server.onreadystatechange = function() {
1294            if (server.readyState === 4 && server.status === 200) {
1295                receiveQueueStatus(server.response, chatButton);
1296            }
1297        };
1298
1299        server.ontimeout = function() {
1300            logToConsole('Could not connect to the backend');
1301        };
1302
1303        // by default, XMLHttpRequests are asynchronous, and therefore require
1304        // an event listener
1305        server.open('POST', restUrl);
1306        server.setRequestHeader('Content-Type', 'application/json');
1307        server.send(createJson(pageSkillset));
1308    }
1309}
1310
1311/**
1312 * Opens the webChatLogon page in a separate window.
1313 */
1314function createLogonWindow() {
1315    'use strict';
1316    var link = 'webChatLogon.html';
1317    window.open(link, 'Web Chat Logon', 'width=400,height=500,resizable=yes');
1318}
1319
1320/**
1321 * webChatLogon.js
1322 *
1323 * Copyright 2015 Avaya Inc. All Rights Reserved.
1324 *
1325 * Usage of this source is bound to the terms described in
1326 * licences/License.txt
1327 *
1328 * Avaya - Confidential & Proprietary. Use pursuant to your signed agreement or
1329 * Avaya Policy
1330 */
1331
1332/*
1333 * \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
1334 *
1335 * This section defines the functionality for the logon window
1336 *
1337 * \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
1338 */
1339
1340/**
1341 * Parses an email to check that it is valid. Only checks if there is an '@' and
1342 * a dot after the '@', but that will cover the vast majority of all email
1343 * addresses
1344 * @param emailToParse
1345 * @returns {Boolean}
1346 */
1347function parseEmail(emailToParse) {
1348    'use strict';
1349    // this regex allows any alphanumeric character
1350    var re = /[\w\.]+@\w+\.\w+/;
1351    return re.test(emailToParse);
1352}
1353
1354/**
1355 * Logs a user with specific details into the chat.
1356 * @param name
1357 * @param phone
1358 * @param email
1359 * @param skillset
1360 */
1361function logon(name, skillset) {
1362    'use strict';
1363    // if the email address is empty or otherwise invalid, alert the user.
1364    // if they accept this, then continue
1365    var confirm = true;
1366    //var isValid = parseEmail(email);
1367    //if (isValid === false) {
1368    //    confirm = window
1369    //            .confirm('You may use the chat without a valid email, but you will not be able to receive transcripts of the conversation');
1370    //}
1371
1372    if (confirm === true) {
1373
1374        // if the customer's email was invalid, and they went ahead,
1375        // reset it
1376        //if (isValid === false) {
1377        //    logToConsole('Email was invalid, resetting');
1378        //    email = '';
1379        //    setSessionStorage('email', email);
1380        //}
1381
1382        if (usePopupForChat) {
1383            window.open('WebChat.html', '_self');
1384        } else {
1385            switchFormToChat();
1386            openChat();
1387        }
1388    }
1389}
1390
1391/**
1392 * Switches the form to logon mode
1393 */
1394function switchFormToLogon() {
1395    'use strict';
1396    getEl('chatForm').style.display = 'block';
1397    getEl('chatInterface').style.display = 'none';
1398    getEl('chatPanel').style.height = '183px';
1399}
1400
1401/**
1402 * Switches the form to chat mode
1403 */
1404function switchFormToChat() {
1405    'use strict';
1406    getEl('chatForm').style.display = 'none';
1407    getEl('chatInterface').style.display = 'block';
1408    getEl('chatPanel').style.height = '363px';
1409}
1410
1411/**
1412 * Set the skillsets according to the queue information.
1413 */
1414function setSkillsets(queues) {
1415    'use strict';
1416    var skillsetSelect = getEl('skillset-chat');
1417
1418    // add the country codes to the phone callback
1419    //var chatCountry = getEl('country-chat');
1420    //addCountryCodes(chatCountry);
1421
1422    // if a queue has space, show it as being available
1423    for (var l = 0; l < queues.length; l++) {
1424        var theQueue = queues[l];
1425        logToConsole('Adding ' + theQueue.queueName + ' to the logon page');
1426        var newOption = document.createElement('option');
1427        newOption.value = theQueue.queueName;
1428        newOption.text = theQueue.queueName;
1429        skillsetSelect.appendChild(newOption);
1430    }
1431}
1432
1433/**
1434 * Gathers the users details and adds them to sessionStorage.
1435 */
1436function gatherDetails() {
1437    'use strict';
1438    var l_user = getEl('user-chat').value, l_skillset = getEl('skillset-chat').value;
1439
1440    // store the phone details in a JSON object for convenience
1441    //var l_phone = {
1442    //    'country' : getEl('country-chat').value,
1443    //    'area' : getEl('area-chat').value,
1444    //    'phone' : getEl('phone-chat').value
1445    //};
1446
1447    setSessionStorage('user', l_user);
1448    //setSessionStorage('phone', JSON.stringify(l_phone));
1449    //setSessionStorage('country', l_country);
1450    //setSessionStorage('email', l_email);
1451    setSessionStorage('skillset', l_skillset);
1452    logon(l_user, l_skillset);
1453}
1454/**
1455 * callback.js
1456 *
1457 * Copyright 2015 Avaya Inc. All Rights Reserved.
1458 *
1459 * Usage of this source is bound to the terms described in
1460 * licences/License.txt
1461 *
1462 * Avaya - Confidential & Proprietary. Use pursuant to your signed agreement or
1463 * Avaya Policy
1464 */
1465
1466/*
1467 * \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
1468 * This section handles callback requests and does not rely on WebSockets.
1469 * Instead, it sends a JSON object to the backend, which handles the grunt work.
1470 * \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
1471 */
1472
1473var callbackDay;
1474var callbackMonth;
1475var callbackHour;
1476var callbackMinute;
1477
1478var callbackButton;
1479var callbackTime;
1480var callbackDate;
1481
1482var callbackEmail;
1483var callbackPhone;
1484var callbackCountry;
1485var callbackArea;
1486var callbackName;
1487var callbackSkillset;
1488
1489var countryCode = '';
1490var areaCode = '';
1491var email = '';
1492var custName = '';
1493var phone;
1494var skillset;
1495
1496var theDate;
1497
1498// store the user details in a JSON object for convenience
1499var user = {};
1500
1501/**
1502 * Hides the callback date form.
1503 */
1504function hideCallbackDateForm() {
1505    'use strict';
1506
1507    // call jQuery and tell it to slide the div in if it's open. Replace with
1508    // own code if not using jQuery
1509    jQuery(function($) {
1510        var formHandle = $('.callbackHandle');
1511        if ($('.dateInputClass').hasClass('open')) {
1512            formHandle.click();
1513        }
1514    });
1515}
1516
1517/**
1518 * Add the international dialling codes to the specified dropdown menu.
1519 */
1520function addCountryCodes(menu) {
1521    'use strict';
1522    logToConsole('Adding country codes');
1523    var countries = codes.countries;
1524    for ( var entry in countries) {
1525        if (countries.hasOwnProperty(entry)) {
1526            var country = countries[entry];
1527            menu.appendChild(addSelectOption(country.name + ' (' + country.code + ')', country.code));
1528        }
1529    }
1530}
1531
1532/**
1533 * Turns on the form to get the user date
1534 */
1535/**
1536function showCallbackDateForm() {
1537    'use strict';
1538
1539    callbackButton = getEl('button-callback');
1540    callbackTime = getEl('time-callback');
1541    callbackDate = getEl('date-callback');
1542
1543    callbackEmail = getEl('email-callback');
1544    callbackPhone = getEl('phone-callback');
1545    callbackArea = getEl('area-callback');
1546    callbackName = getEl('user-callback');
1547    callbackCountry = getEl('country-callback');
1548    callbackSkillset = getEl('skillset-callback');
1549
1550    // add the country codes
1551    //addCountryCodes(callbackCountry);
1552
1553    // get the skillsets for display purposes
1554    getSkillsets();
1555
1556    callbackButton.onclick = function() {
1557        setCallbackDate();
1558    };
1559
1560    for (var i = 6; i <= 20; i++) {
1561        callbackTime.appendChild(addSelectOption(i + ':00 ', i + ':00 '));
1562        callbackTime.appendChild(addSelectOption(i + ':15 ', i + ':15 '));
1563        callbackTime.appendChild(addSelectOption(i + ':30 ', i + ':30 '));
1564        callbackTime.appendChild(addSelectOption(i + ':45 ', i + ':45 '));
1565    }
1566
1567}
1568*/
1569/**
1570 * Creates and returns a select option with the specified value
1571 * @param value
1572 * @returns {___anonymous1888_1899}
1573 */
1574function addSelectOption(textContent, value) {
1575    'use strict';
1576    var selectOption = document.createElement('option');
1577    selectOption.textContent = textContent;
1578    selectOption.value = value;
1579    return selectOption;
1580}
1581
1582/**
1583 * Allows the user to set the time that suits them for an agent to call them
1584 * back.
1585 */
1586function setCallbackDate() {
1587    'use strict';
1588
1589    phone = callbackPhone.value;
1590    custName = callbackName.value;
1591    email = callbackEmail.value;
1592    skillset = callbackSkillset.value;
1593    countryCode = callbackCountry.value;
1594    areaCode = callbackArea.value;
1595    var currentTime = new Date();
1596
1597    user = {
1598        'method' : 'requestCallback',
1599        'name' : custName,
1600        'phone' : phone,
1601        'email' : email,
1602        'countryCode' : countryCode,
1603        'areaCode' : areaCode,
1604        'skillset' : skillset
1605    };
1606
1607    // the below is dependent on jQuery
1608    theDate = $('#date-callback').datepicker('getDate');
1609    var theTime = callbackTime.value.split(':');
1610    theDate.setHours(theTime[0], theTime[1]);
1611
1612    // create a new UTC date to maintain consistency with the PHP
1613    // version, then reset the customer date to use that
1614    var utcDate = Date.UTC(theDate.getFullYear(), theDate.getMonth(), theDate.getDate(), theTime[0], theTime[1]);
1615    logToConsole('theDate is ' + theDate.toString() + '. utcDate is ' + new Date(utcDate).toString());
1616    theDate = new Date(utcDate);
1617
1618    if (verifyPhone(phone) && verifyDate(currentTime, theDate) && !isCountryCodeEmpty(countryCode)) {
1619
1620        // calculate the offset for the callback date relative to UTC,
1621        // using the system clock for the customer's browser
1622        var rawDate = theDate.getTime();
1623        var offset = theDate.getTimezoneOffset();
1624        var newDate = rawDate + (offset * 60000);
1625        user.date = (newDate);
1626        sendJsonRequest(user);
1627    } else {
1628        alert('Please enter a valid date and phone number');
1629    }
1630}
1631
1632/**
1633 * Verifies that a country code has been selected.
1634 * @param code
1635 */
1636function isCountryCodeEmpty(code) {
1637    'use strict';
1638    return isStringEmpty(code);
1639}
1640
1641/**
1642 * Verifies a phone number. This one makes sure there are no spaces or letters
1643 * @param number
1644 * @returns
1645 */
1646function verifyPhone(number) {
1647    'use strict';
1648
1649    // this should allow calls from any country in the format 00xxx yyy zzzzzzz
1650    var re = /^[\+\d][\s\d]+$/;
1651    return re.test(number);
1652}
1653
1654/**
1655 * Checks that a user hasn't entered a date earlier than their current time.
1656 * @param currentDate
1657 * @param date
1658 * @returns {Boolean}
1659 */
1660function verifyDate(currentDate, date) {
1661    'use strict';
1662
1663    logToConsole('Customer requested time of ' + date.getTime());
1664    logToConsole('Customer\'s current time is ' + currentDate.getTime());
1665    return (date.getTime() > currentDate.getTime());
1666}
1667
1668/**
1669 * Send a JSON request to the server.
1670 * @param user
1671 */
1672function sendJsonRequest(user) {
1673    'use strict';
1674
1675    var request = new XMLHttpRequest();
1676    request.open('POST', callbackUrl);
1677    request.timeout = callbackTimeout;
1678
1679    request.onreadystatechange = function() {
1680        if (request.readyState === 4 && request.status === 200) {
1681            var text = JSON.parse(request.responseText);
1682            alert(text.status);
1683        }
1684    };
1685
1686    request.setRequestHeader('Content-Type', 'application/json');
1687    request.send(JSON.stringify(user));
1688}
1689
1690/**
1691 * Get all the outbound skillsets.
1692 */
1693function getSkillsets() {
1694    'use strict';
1695
1696    if (window.XMLHttpRequest) {
1697        var request = new XMLHttpRequest();
1698        request.open('POST', callbackUrl);
1699        request.timeout = callbackTimeout;
1700
1701        request.onreadystatechange = function() {
1702            if (request.readyState === 4 && request.status === 200) {
1703                var data = JSON.parse(request.response);
1704                processSkillsetsResponse(data);
1705            }
1706        };
1707
1708        // send a basic JSON object
1709        var json = {
1710            'method' : 'getSkillsets'
1711        };
1712
1713        request.setRequestHeader('Content-Type', 'application/json');
1714        request.send(JSON.stringify(json));
1715    }
1716
1717}
1718
1719/**
1720 * Refactors the skillset response processing.
1721 * @param request
1722 */
1723function processSkillsetsResponse(requestData) {
1724    'use strict';
1725
1726    // loop through the keys of the JSON object and pass the value
1727    // into a select.
1728    for ( var skillset in requestData) {
1729        if (requestData.hasOwnProperty(skillset)) {
1730            var name = requestData[skillset];
1731            callbackSkillset.appendChild(addSelectOption(name, name));
1732        }
1733
1734    }
1735}

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.