@xapp/chat-widget 1.53.2 → 1.54.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -2031,12 +2031,20 @@ function executeAction(text, token) {
2031
2031
  }; };
2032
2032
  }
2033
2033
 
2034
+ /**
2035
+ * This is not used anymore. It was part of the handshake with the legacy "chat server"
2036
+ *
2037
+ * @param token
2038
+ * @returns
2039
+ */
2034
2040
  function receiveToken(token) {
2035
2041
  return {
2036
2042
  type: "receiveToken",
2037
2043
  detail: {
2038
2044
  token: token,
2039
- timestamp: +Date()
2045
+ timestamp: +Date(),
2046
+ type: "receiveToken",
2047
+ user: undefined
2040
2048
  }
2041
2049
  };
2042
2050
  }
@@ -2155,6 +2163,26 @@ function setConnectionStatus(status) {
2155
2163
  };
2156
2164
  }
2157
2165
 
2166
+ function sendBargeIn(agentName) {
2167
+ return function (chatServer) { return function () {
2168
+ chatServer.bargeIn(agentName, function (err) {
2169
+ if (err) {
2170
+ log(err);
2171
+ }
2172
+ });
2173
+ }; };
2174
+ }
2175
+
2176
+ function sendBargeOut() {
2177
+ return function (chatServer) { return function () {
2178
+ chatServer.bargeOut(function (err) {
2179
+ if (err) {
2180
+ log(err);
2181
+ }
2182
+ });
2183
+ }; };
2184
+ }
2185
+
2158
2186
  function reset() {
2159
2187
  return {
2160
2188
  type: "reset"
@@ -2169,6 +2197,14 @@ var LogChat = /** @class */ (function () {
2169
2197
  function LogChat(inner) {
2170
2198
  this.inner = inner;
2171
2199
  }
2200
+ LogChat.prototype.bargeOut = function (cb) {
2201
+ log("CLIENT: bargeOut");
2202
+ this.inner.bargeOut(cb);
2203
+ };
2204
+ LogChat.prototype.bargeIn = function (agentName, cb) {
2205
+ log("CLIENT: bargeIn: ".concat(agentName));
2206
+ this.inner.bargeIn(agentName, cb);
2207
+ };
2172
2208
  LogChat.prototype.init = function (dispatch) {
2173
2209
  this.inner.init(dispatch);
2174
2210
  };
@@ -2554,10 +2590,15 @@ function getItemsLength(result) {
2554
2590
 
2555
2591
  var SuggestionsList = function (props) {
2556
2592
  var suggestions = props.suggestions, itemActions = props.itemActions, className = props.className;
2557
- var length = React.useMemo(function () { return getItemsLength(suggestions); }, [suggestions]);
2593
+ var length = React.useMemo(function () {
2594
+ if (suggestions) {
2595
+ return getItemsLength(suggestions);
2596
+ }
2597
+ return 0;
2598
+ }, [suggestions]);
2558
2599
  var currentIndex = length >= 0 ? props.index : NaN;
2559
2600
  var indexWalker = 0;
2560
- return (React__default["default"].createElement("div", { className: "xappw-suggestions-list ".concat(className || "") }, suggestions.map(function (group, index) {
2601
+ return (React__default["default"].createElement("div", { className: "xappw-suggestions-list ".concat(className || "") }, suggestions === null || suggestions === void 0 ? void 0 : suggestions.map(function (group, index) {
2561
2602
  var res = (React__default["default"].createElement(SuggestionsGroup, { key: index, group: group, currentIndex: currentIndex - indexWalker, itemActions: itemActions, onItemClick: props.onItemClick, onItemHover: props.onItemHover, onSpanClick: props.onSpanClick }));
2562
2603
  indexWalker += group.items.length;
2563
2604
  return res;
@@ -2639,7 +2680,7 @@ function parseSuggestionsResponse(response, direction) {
2639
2680
  }, []);
2640
2681
  }
2641
2682
 
2642
- /*! *****************************************************************************
2683
+ /******************************************************************************
2643
2684
  Copyright (c) Microsoft Corporation.
2644
2685
 
2645
2686
  Permission to use, copy, modify, and/or distribute this software for any
@@ -2670,7 +2711,7 @@ function __generator(thisArg, body) {
2670
2711
  function verb(n) { return function (v) { return step([n, v]); }; }
2671
2712
  function step(op) {
2672
2713
  if (f) throw new TypeError("Generator is already executing.");
2673
- while (_) try {
2714
+ while (g && (g = 0, op[0] && (_ = 0)), _) try {
2674
2715
  if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
2675
2716
  if (y = 0, t) op = [op[0] & 2, t.value];
2676
2717
  switch (op[0]) {
@@ -3241,6 +3282,20 @@ var StentorDirectChat = /** @class */ (function () {
3241
3282
  });
3242
3283
  });
3243
3284
  };
3285
+ StentorDirectChat.prototype.bargeOut = function (_cb) {
3286
+ return __awaiter$1(this, void 0, void 0, function () {
3287
+ return __generator$1(this, function (_a) {
3288
+ throw new Error("Method not implemented: bargeOut");
3289
+ });
3290
+ });
3291
+ };
3292
+ StentorDirectChat.prototype.bargeIn = function (_agentName, _cb) {
3293
+ return __awaiter$1(this, void 0, void 0, function () {
3294
+ return __generator$1(this, function (_a) {
3295
+ throw new Error("Method not implemented: bargeIn");
3296
+ });
3297
+ });
3298
+ };
3244
3299
  StentorDirectChat.prototype.getBot = function (user) {
3245
3300
  var _a;
3246
3301
  var bot = (_a = this.options) === null || _a === void 0 ? void 0 : _a.bot;
@@ -3560,12 +3615,16 @@ var StentorLocalChat = /** @class */ (function () {
3560
3615
  };
3561
3616
  StentorLocalChat.prototype.dispose = function () {
3562
3617
  };
3618
+ StentorLocalChat.prototype.bargeOut = function () {
3619
+ };
3620
+ StentorLocalChat.prototype.bargeIn = function () {
3621
+ };
3563
3622
  return StentorLocalChat;
3564
3623
  }());
3565
3624
 
3566
3625
  /*! Copyright (c) 2022, XAPPmedia */
3567
3626
  var PERMISSION_QUESTION_EXPIRATION_MS = 300000; // 5 minutes
3568
- function requestFromMessage(message, userId, isNewSession, sessionId, accessToken, attributes) {
3627
+ function requestFromMessage(message, userId, isNewSession, sessionId, accessToken, attributes, visitorInfo) {
3569
3628
  var _a, _b, _c, _d;
3570
3629
  var request;
3571
3630
  var now = new Date().getTime();
@@ -3591,13 +3650,13 @@ function requestFromMessage(message, userId, isNewSession, sessionId, accessToke
3591
3650
  var expired = now - permissionRequest.time > PERMISSION_QUESTION_EXPIRATION_MS;
3592
3651
  var text = (_d = message.msg.text) === null || _d === void 0 ? void 0 : _d.toLowerCase();
3593
3652
  var granted = !expired;
3594
- var userProfile = { id: this._userId };
3653
+ var userProfile = { id: userId };
3595
3654
  if (!expired) {
3596
3655
  if (permissionRequest.type === "EMAIL") {
3597
3656
  var isEmail = looksLikeEmail(text);
3598
3657
  granted = isEmail;
3599
- userProfile.email = this.visitorInfo.email;
3600
- userProfile.name = this.visitorInfo.displayName;
3658
+ userProfile.email = visitorInfo.email;
3659
+ userProfile.name = visitorInfo.displayName;
3601
3660
  this.visitorInfo.email = text;
3602
3661
  }
3603
3662
  if (permissionRequest.type === "LOCATION_PRECISE") {
@@ -3613,8 +3672,8 @@ function requestFromMessage(message, userId, isNewSession, sessionId, accessToke
3613
3672
  request = {
3614
3673
  type: "PERMISSION_GRANT",
3615
3674
  rawQuery: message.msg.text,
3616
- sessionId: this._sessionId,
3617
- userId: this._userId,
3675
+ sessionId: sessionId,
3676
+ userId: userId,
3618
3677
  isNewSession: false,
3619
3678
  granted: granted,
3620
3679
  userProfile: userProfile,
@@ -3622,13 +3681,13 @@ function requestFromMessage(message, userId, isNewSession, sessionId, accessToke
3622
3681
  platform: "stentor-platform",
3623
3682
  channel: "widget",
3624
3683
  accessToken: accessToken,
3625
- attributes: attributes
3684
+ attributes: attributes,
3626
3685
  };
3627
3686
  }
3628
3687
  else if (message.type === "custom") {
3629
3688
  request = __assign(__assign({}, JSON.parse(message.payload)), {
3630
3689
  // token: message.msg.token,
3631
- sessionId: this._sessionId, userId: this._userId, isNewSession: false,
3690
+ sessionId: sessionId, userId: userId, isNewSession: false,
3632
3691
  // intentId: "OptionSelect",
3633
3692
  platform: "stentor-platform", channel: "widget", accessToken: accessToken, attributes: attributes });
3634
3693
  }
@@ -3636,28 +3695,28 @@ function requestFromMessage(message, userId, isNewSession, sessionId, accessToke
3636
3695
  request = {
3637
3696
  type: "OPTION_SELECT_REQUEST",
3638
3697
  token: message.msg.token,
3639
- sessionId: this._sessionId,
3640
- userId: this._userId,
3698
+ sessionId: sessionId,
3699
+ userId: userId,
3641
3700
  isNewSession: false,
3642
3701
  intentId: "OptionSelect",
3643
3702
  platform: "stentor-platform",
3644
3703
  channel: "widget",
3645
3704
  accessToken: accessToken,
3646
- attributes: attributes
3705
+ attributes: attributes,
3647
3706
  };
3648
3707
  }
3649
3708
  else {
3650
3709
  request = {
3651
3710
  type: "INTENT_REQUEST",
3652
3711
  rawQuery: message.msg.text,
3653
- sessionId: this._sessionId,
3654
- userId: this._userId,
3712
+ sessionId: sessionId,
3713
+ userId: userId,
3655
3714
  isNewSession: false,
3656
3715
  intentId: "NLU_RESULT_PLACEHOLDER",
3657
3716
  platform: "stentor-platform",
3658
3717
  channel: "widget",
3659
3718
  accessToken: accessToken,
3660
- attributes: attributes
3719
+ attributes: attributes,
3661
3720
  };
3662
3721
  }
3663
3722
  }
@@ -3707,29 +3766,37 @@ var StentorRouterChat = /** @class */ (function () {
3707
3766
  this.handlers = {};
3708
3767
  this.config = config;
3709
3768
  this.options = options;
3710
- // Dig out the path parameters. Put them into the attributes.
3769
+ // Dig out the path parameters. Put them into the attributes.
3711
3770
  // The WS url is the barebone domain.
3712
3771
  var url = new URL(this.config.url);
3713
3772
  var urlPath = (_a = url.pathname) === null || _a === void 0 ? void 0 : _a.split("/");
3714
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
3715
- this.isAdmin = (url.searchParams.get('isAdmin') || window.xaIsAdmin) === "true";
3773
+ var locationUrl = new URL(window.location.href);
3774
+ this.isAdmin = locationUrl.searchParams.get("isAdmin") === "true";
3716
3775
  this.serverUrl = "".concat(url.origin);
3717
3776
  if (urlPath && urlPath.length > 1) {
3718
3777
  this.serverUrl = "".concat(this.serverUrl, "/").concat(urlPath[1]); // the first path (after the initial "") is the "dev" or "prod"
3719
- this.urlAttributes.path = urlPath.slice(2);
3778
+ this.urlAttributes.path = urlPath.slice(1);
3720
3779
  }
3721
- log("WS url: ".concat(this.serverUrl, " attributes: ").concat(JSON.stringify(this.attributes)));
3722
- // this.configurableMessages = options.configurableMessages;
3780
+ log("WS url: ".concat(this.serverUrl, " isAdmin: ").concat(this.isAdmin, " attributes: ").concat(JSON.stringify(this.attributes)));
3781
+ this.configurableMessages = getConfigurableMessagesConfig(options.configurableMessages);
3723
3782
  }
3724
3783
  StentorRouterChat.prototype.init = function (dispatch) {
3725
3784
  var _this = this;
3726
- // Fire up the WebSocket
3727
- this.autoReconnect(this.wsCreate.bind(this));
3728
3785
  this.dispatch = dispatch;
3786
+ this.setAccountStatus("offline");
3729
3787
  // Register "handlers" for the incoming events
3730
- this.handlers["account_status"] = function (_data, _sender) {
3731
- // Register the "endpoint" with the server (send visitor data)
3732
- _this.emit("connection update");
3788
+ this.handlers["connection update"] = function (data, _sender) {
3789
+ log("Connection update received. Session created: ".concat(data.sessionCreated, " Error: ").concat(data.errorMessage || ""));
3790
+ if (data.sessionCreated) {
3791
+ _this.sessionCreated = true;
3792
+ }
3793
+ else {
3794
+ // Shut down
3795
+ _this.dispatch(setAccountStatus("offline"));
3796
+ _this.dispatch(setConnectionStatus("offline"));
3797
+ }
3798
+ };
3799
+ this.handlers["account status"] = function (_data, _sender) {
3733
3800
  dispatch({
3734
3801
  type: "account_status",
3735
3802
  detail: {
@@ -3738,6 +3805,11 @@ var StentorRouterChat = /** @class */ (function () {
3738
3805
  },
3739
3806
  });
3740
3807
  };
3808
+ this.handlers["failure"] = function (data, sender) {
3809
+ err("Router says something failed: type: ".concat(data.type, " tries: ").concat(data.tries, " error: ").concat(data.error));
3810
+ var _a = _this.configurableMessages[data.tries], retry = _a.retry, delay = _a.delay, text = _a.text;
3811
+ _this.sendFailureMessage(retry, data.delay || delay, text, sender);
3812
+ };
3741
3813
  this.handlers["new message"] = function (data, sender) {
3742
3814
  // Because the router's internal message format is Stentor channel compatible.
3743
3815
  // So the data is either a stentor Request (from widget) or a stentor Response (from bot)
@@ -3763,7 +3835,7 @@ var StentorRouterChat = /** @class */ (function () {
3763
3835
  displayName: sender.displayName || sender.email || sender.userId,
3764
3836
  nick: senderToNick(sender),
3765
3837
  },
3766
- msg: message,
3838
+ msg: message.msg,
3767
3839
  timestamp: +new Date(),
3768
3840
  },
3769
3841
  });
@@ -3829,27 +3901,30 @@ var StentorRouterChat = /** @class */ (function () {
3829
3901
  _this.dispatch(setAccountStatus("online"));
3830
3902
  _this.dispatch(setConnectionStatus("online"));
3831
3903
  };
3832
- this.handlers["reconnect_failed"] = function (_data) {
3904
+ this.handlers["reconnect failed"] = function (_data) {
3833
3905
  _this.dispatch(setAccountStatus("offline"));
3834
3906
  _this.dispatch(setConnectionStatus("offline"));
3835
3907
  };
3836
- this.handlers["reconnect_error"] = function (_data) {
3908
+ this.handlers["reconnect error"] = function (_data) {
3837
3909
  _this.dispatch(setAccountStatus("offline"));
3838
3910
  };
3839
- this.setAccountStatus("offline");
3911
+ // pretend we are online. The emit() will connect
3912
+ this.setAccountStatus("online");
3913
+ this.setConnectionStatus("online");
3840
3914
  };
3841
3915
  StentorRouterChat.prototype.autoReconnect = function (wsCreate) {
3842
3916
  var _this = this;
3843
- var setStatus = this.setAccountStatus.bind(this);
3917
+ var setActStatus = this.setAccountStatus.bind(this);
3918
+ var setConnStatus = this.setConnectionStatus.bind(this);
3844
3919
  function startReconnecting() {
3845
- setStatus("offline");
3846
3920
  var interval = setInterval(function () {
3847
3921
  log("Re-creating WS connection");
3848
3922
  var ws = wsCreate();
3849
3923
  ws.onopen = function () {
3850
3924
  log("Re-opened WS connection");
3851
3925
  // We are connected
3852
- setStatus("online");
3926
+ setActStatus("online");
3927
+ setConnStatus("online");
3853
3928
  ws.onclose = startReconnecting;
3854
3929
  clearInterval(interval);
3855
3930
  };
@@ -3862,26 +3937,77 @@ var StentorRouterChat = /** @class */ (function () {
3862
3937
  log("Opened WS connection");
3863
3938
  // We are connected
3864
3939
  _this.setAccountStatus("online");
3940
+ _this.setConnectionStatus("online");
3865
3941
  };
3866
3942
  };
3867
3943
  StentorRouterChat.prototype.wsCreate = function () {
3868
- this.ws = new WebSocket("".concat(this.serverUrl, "?userId=").concat(this._userId));
3869
- this.ws.onmessage = function (data) {
3870
- console.log("SERVER says: ".concat(JSON.stringify(data)));
3871
- // const message: RouterMessage = JSON.parse(data);
3872
- // this.handlers[message.event](message.data, message.sender);
3944
+ var _this = this;
3945
+ this.setConnectionStatus("pending");
3946
+ this.ws = new WebSocket("".concat(this.serverUrl, "?userId=").concat(this._userId, "&isAdmin=").concat(this.isAdmin));
3947
+ this.ws.onerror = function (ev) {
3948
+ var _a;
3949
+ log("Error in WS connection. Type: ".concat(!ev ? "?" : ev.type, " Ready State: ").concat((_a = _this.ws) === null || _a === void 0 ? void 0 : _a.readyState));
3950
+ _this.setConnectionStatus("pending");
3951
+ };
3952
+ this.ws.onmessage = function (me) {
3953
+ log("ROUTER says: ".concat(me.data));
3954
+ var message = JSON.parse(me.data);
3955
+ var handler = _this.handlers[message.event];
3956
+ if (handler) {
3957
+ _this.handlers[message.event](message.data, message.sender);
3958
+ }
3959
+ else {
3960
+ log("Unknown router message event: ".concat(message.event));
3961
+ }
3873
3962
  };
3874
3963
  return this.ws;
3875
3964
  };
3965
+ StentorRouterChat.prototype.checkConnection = function () {
3966
+ return this.ws.readyState === WebSocket.OPEN;
3967
+ };
3876
3968
  StentorRouterChat.prototype.emit = function (event, data) {
3877
- var payloadData = JSON.stringify({ event: event, data: data, sender: this.visitorInfo });
3878
- log("Widget says: ".concat(payloadData));
3879
- this.ws.send(payloadData);
3969
+ return __awaiter$1(this, void 0, void 0, function () {
3970
+ var payloadData;
3971
+ var _this = this;
3972
+ return __generator$1(this, function (_a) {
3973
+ switch (_a.label) {
3974
+ case 0:
3975
+ payloadData = JSON.stringify({
3976
+ event: event,
3977
+ data: data,
3978
+ sender: this.visitorInfo,
3979
+ sessionId: this._sessionId,
3980
+ });
3981
+ log("Widget says: ".concat(payloadData));
3982
+ if (!this.ws) {
3983
+ // Fire up the WebSocket
3984
+ this.autoReconnect(this.wsCreate.bind(this));
3985
+ }
3986
+ return [4 /*yield*/, waitFor(this.checkConnection.bind(this), 500, 5000, "connection")
3987
+ .then(function (t) {
3988
+ if (t > 0) {
3989
+ log("Connection is ready in ".concat(t, " ms"));
3990
+ }
3991
+ _this.ws.send(payloadData);
3992
+ })
3993
+ .catch(function (t) {
3994
+ console.log("Connection wait timed out in ".concat(t, " ms"));
3995
+ })];
3996
+ case 1:
3997
+ _a.sent();
3998
+ return [2 /*return*/];
3999
+ }
4000
+ });
4001
+ });
3880
4002
  };
3881
4003
  StentorRouterChat.prototype.setAccountStatus = function (status) {
3882
4004
  log("SERVER account_status: ".concat(JSON.stringify(status)));
3883
4005
  this.dispatch(setAccountStatus(status));
3884
4006
  };
4007
+ StentorRouterChat.prototype.setConnectionStatus = function (status) {
4008
+ log("SERVER: connection_update: ".concat(JSON.stringify(status)));
4009
+ this.dispatch(setConnectionStatus(status));
4010
+ };
3885
4011
  StentorRouterChat.prototype.sendOfflineMsg = function (_, cb) {
3886
4012
  cb();
3887
4013
  };
@@ -3898,11 +4024,48 @@ var StentorRouterChat = /** @class */ (function () {
3898
4024
  });
3899
4025
  });
3900
4026
  };
4027
+ StentorRouterChat.prototype.sendFailureMessage = function (retry, delay, text, sender) {
4028
+ this.dispatch({
4029
+ type: "chat",
4030
+ detail: {
4031
+ type: "chat.typing",
4032
+ user: {
4033
+ nick: senderToNick(sender),
4034
+ },
4035
+ typing: false,
4036
+ timestamp: +new Date(),
4037
+ },
4038
+ });
4039
+ this.dispatch({
4040
+ type: "chat",
4041
+ detail: {
4042
+ type: "chat.failureMsg",
4043
+ user: {
4044
+ nick: senderToNick(sender)
4045
+ },
4046
+ failureMsg: { retry: retry, delay: delay, text: text },
4047
+ timestamp: +new Date()
4048
+ }
4049
+ });
4050
+ };
4051
+ StentorRouterChat.prototype.checkSession = function () {
4052
+ return this.sessionCreated;
4053
+ };
3901
4054
  StentorRouterChat.prototype.sendChatMsgRequest = function (serviceRequest, cb) {
3902
4055
  return __awaiter$1(this, void 0, void 0, function () {
4056
+ var _this = this;
3903
4057
  return __generator$1(this, function (_a) {
3904
4058
  switch (_a.label) {
3905
- case 0: return [4 /*yield*/, this.postMessage(serviceRequest)];
4059
+ case 0: return [4 /*yield*/, waitFor(this.checkSession.bind(this), 500, 5000, "session creation")
4060
+ .then(function (t) {
4061
+ if (t > 0) {
4062
+ log("Session creation is ready in ".concat(t, " ms"));
4063
+ }
4064
+ _this.postMessage(serviceRequest);
4065
+ })
4066
+ .catch(function (t) {
4067
+ console.log("Session creation wait timed out in ".concat(t, " ms"));
4068
+ })];
3906
4069
  case 1:
3907
4070
  _a.sent();
3908
4071
  cb();
@@ -3911,23 +4074,24 @@ var StentorRouterChat = /** @class */ (function () {
3911
4074
  });
3912
4075
  });
3913
4076
  };
3914
- StentorRouterChat.prototype.sendTyping = function (isTyping) {
3915
- this.emit(isTyping ? "typing" : "stop typing");
4077
+ StentorRouterChat.prototype.sendTyping = function (_isTyping) {
4078
+ // TODO: Is this too much traffic?
4079
+ // this.emit(isTyping ? "typing" : "stop typing");
3916
4080
  };
3917
4081
  StentorRouterChat.prototype.setVisitorInfo = function (visitorInfoMessage, cb) {
3918
- log("Visitor Info Mesage = ".concat(visitorInfoMessage));
4082
+ log("Visitor Info Message = ".concat(JSON.stringify(visitorInfoMessage)));
3919
4083
  this.visitorInfo = {
3920
4084
  deviceId: "Widget",
3921
4085
  userId: visitorInfoMessage.userId,
3922
4086
  displayName: visitorInfoMessage.displayName || visitorInfoMessage.name,
3923
4087
  email: visitorInfoMessage.email,
3924
4088
  isAdmin: this.isAdmin,
3925
- urlAttributes: this.urlAttributes
4089
+ urlAttributes: this.urlAttributes,
3926
4090
  };
3927
4091
  this.attributes = __assign(__assign(__assign({}, visitorInfoMessage.attributes), this.attributes), { currentUrl: window.location.href });
3928
4092
  this.accessToken = visitorInfoMessage.accessToken;
3929
- this.emit("add user");
3930
4093
  this.startSession();
4094
+ this.postVisitorInfo(); // we, the widget, joined the server
3931
4095
  cb();
3932
4096
  };
3933
4097
  StentorRouterChat.prototype.sendChatRating = function () { };
@@ -3939,6 +4103,32 @@ var StentorRouterChat = /** @class */ (function () {
3939
4103
  };
3940
4104
  StentorRouterChat.prototype.flush = function () { };
3941
4105
  StentorRouterChat.prototype.dispose = function () { };
4106
+ StentorRouterChat.prototype.bargeOut = function (_cb) {
4107
+ return __awaiter$1(this, void 0, void 0, function () {
4108
+ return __generator$1(this, function (_a) {
4109
+ this.emit("barge out");
4110
+ return [2 /*return*/];
4111
+ });
4112
+ });
4113
+ };
4114
+ StentorRouterChat.prototype.bargeIn = function (agentName, _cb) {
4115
+ return __awaiter$1(this, void 0, void 0, function () {
4116
+ return __generator$1(this, function (_a) {
4117
+ this.visitorInfo.displayName = agentName;
4118
+ this.emit("barge in");
4119
+ return [2 /*return*/];
4120
+ });
4121
+ });
4122
+ };
4123
+ StentorRouterChat.prototype.postVisitorInfo = function () {
4124
+ return __awaiter$1(this, void 0, void 0, function () {
4125
+ return __generator$1(this, function (_a) {
4126
+ this.sessionCreated = false;
4127
+ this.emit("user joined");
4128
+ return [2 /*return*/];
4129
+ });
4130
+ });
4131
+ };
3942
4132
  StentorRouterChat.prototype.postMessage = function (message) {
3943
4133
  return __awaiter$1(this, void 0, void 0, function () {
3944
4134
  var userId, sessionId, accessToken, attributes, request;
@@ -3947,7 +4137,7 @@ var StentorRouterChat = /** @class */ (function () {
3947
4137
  sessionId = this._sessionId;
3948
4138
  accessToken = this.accessToken;
3949
4139
  attributes = this.attributes || {};
3950
- request = requestFromMessage(message, userId, this.isNewSession, sessionId, accessToken, attributes);
4140
+ request = requestFromMessage(message, userId, this.isNewSession, sessionId, accessToken, attributes, this.visitorInfo);
3951
4141
  this.emit("new message", request);
3952
4142
  return [2 /*return*/];
3953
4143
  });
@@ -3963,34 +4153,61 @@ var StentorRouterChat = /** @class */ (function () {
3963
4153
  else {
3964
4154
  this._userId = "stentor-widget-user-".concat(uuid_1());
3965
4155
  }
3966
- if (get("sessionId") !== "") {
4156
+ var joinSessionId = new URL(window.location.href).searchParams.get("sessionId");
4157
+ if (joinSessionId) {
4158
+ this._sessionId = joinSessionId;
4159
+ set$1("sessionId", this._sessionId);
4160
+ log("Using enforced session id: ".concat(this._sessionId));
4161
+ }
4162
+ else if (get("sessionId")) {
3967
4163
  this._sessionId = get("sessionId");
4164
+ log("Using persisted session id: ".concat(this._sessionId));
3968
4165
  }
3969
4166
  else {
3970
4167
  this._sessionId = "stentor-widget-session-".concat(uuid_1());
3971
4168
  set$1("sessionId", this._sessionId);
4169
+ log("Using generated session id: ".concat(this._sessionId));
3972
4170
  }
3973
- // This is a flag that is cleared after the first message is sent
4171
+ // Always. This triggers a LAUNCH_REQUEST most of the time
3974
4172
  this.isNewSession = true;
4173
+ log("Started session: ".concat(this._sessionId));
3975
4174
  };
3976
- Object.defineProperty(StentorRouterChat.prototype, "userId", {
3977
- get: function () {
3978
- return this._userId;
3979
- },
3980
- enumerable: false,
3981
- configurable: true
3982
- });
3983
- Object.defineProperty(StentorRouterChat.prototype, "sessionId", {
3984
- get: function () {
3985
- return this._sessionId;
3986
- },
3987
- enumerable: false,
3988
- configurable: true
3989
- });
3990
4175
  return StentorRouterChat;
3991
4176
  }());
3992
4177
  function senderToNick(sender) {
3993
- return "".concat(sender.deviceId, "-").concat(sender.userId);
4178
+ // Both are agents
4179
+ return sender.deviceId === "Bot" ?
4180
+ "agent:bot:".concat(sender.userId) : "agent:widget:".concat(sender.userId);
4181
+ }
4182
+ /**
4183
+ *
4184
+ * @param check Function that return true/false if the resource is available
4185
+ * @param stepMs check period in ms
4186
+ * @param timeoutMs max wait in ms
4187
+ * @param resource optional resource name. you will see a log message for every check (step)
4188
+ *
4189
+ * @returns the elapsed time in ms (both resolve/reject)
4190
+ */
4191
+ function waitFor(check, stepMs, timeoutMs, resource) {
4192
+ var elapsedMs = 0;
4193
+ var poll = function (resolve, reject) {
4194
+ if (elapsedMs > timeoutMs) {
4195
+ reject(elapsedMs);
4196
+ }
4197
+ else {
4198
+ if (check()) {
4199
+ resolve(elapsedMs);
4200
+ }
4201
+ else {
4202
+ elapsedMs += stepMs;
4203
+ if (resource) {
4204
+ log("Still waiting for ".concat(resource, " (").concat(elapsedMs, " ms)"));
4205
+ }
4206
+ setTimeout(function (_) { return poll(resolve, reject); }, stepMs);
4207
+ }
4208
+ }
4209
+ };
4210
+ return new Promise(poll);
3994
4211
  }
3995
4212
 
3996
4213
  const PACKET_TYPES = Object.create(null); // no Map = no polyfill
@@ -4042,11 +4259,12 @@ const encodeBlobAsBase64 = (data, callback) => {
4042
4259
  const fileReader = new FileReader();
4043
4260
  fileReader.onload = function () {
4044
4261
  const content = fileReader.result.split(",")[1];
4045
- callback("b" + content);
4262
+ callback("b" + (content || ""));
4046
4263
  };
4047
4264
  return fileReader.readAsDataURL(data);
4048
4265
  };
4049
4266
 
4267
+ // imported from https://github.com/socketio/base64-arraybuffer
4050
4268
  const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
4051
4269
  // Use a lookup table to find the index.
4052
4270
  const lookup$1 = typeof Uint8Array === 'undefined' ? [] : new Uint8Array(256);
@@ -5214,10 +5432,10 @@ function parse$2(str) {
5214
5432
  }
5215
5433
  function pathNames(obj, path) {
5216
5434
  const regx = /\/{2,9}/g, names = path.replace(regx, "/").split("/");
5217
- if (path.substr(0, 1) == '/' || path.length === 0) {
5435
+ if (path.slice(0, 1) == '/' || path.length === 0) {
5218
5436
  names.splice(0, 1);
5219
5437
  }
5220
- if (path.substr(path.length - 1, 1) == '/') {
5438
+ if (path.slice(-1) == '/') {
5221
5439
  names.splice(names.length - 1, 1);
5222
5440
  }
5223
5441
  return names;
@@ -5310,13 +5528,14 @@ class Socket$1 extends Emitter_1 {
5310
5528
  // Firefox closes the connection when the "beforeunload" event is emitted but not Chrome. This event listener
5311
5529
  // ensures every browser behaves the same (no "disconnect" event at the Socket.IO level when the page is
5312
5530
  // closed/reloaded)
5313
- addEventListener("beforeunload", () => {
5531
+ this.beforeunloadEventListener = () => {
5314
5532
  if (this.transport) {
5315
5533
  // silently close the transport
5316
5534
  this.transport.removeAllListeners();
5317
5535
  this.transport.close();
5318
5536
  }
5319
- }, false);
5537
+ };
5538
+ addEventListener("beforeunload", this.beforeunloadEventListener, false);
5320
5539
  }
5321
5540
  if (this.hostname !== "localhost") {
5322
5541
  this.offlineEventListener = () => {
@@ -5770,6 +5989,7 @@ class Socket$1 extends Emitter_1 {
5770
5989
  // ignore further transport communication
5771
5990
  this.transport.removeAllListeners();
5772
5991
  if (typeof removeEventListener === "function") {
5992
+ removeEventListener("beforeunload", this.beforeunloadEventListener, false);
5773
5993
  removeEventListener("offline", this.offlineEventListener, false);
5774
5994
  }
5775
5995
  // set ready state
@@ -5967,14 +6187,22 @@ function _deconstructPacket(data, buffers) {
5967
6187
  */
5968
6188
  function reconstructPacket(packet, buffers) {
5969
6189
  packet.data = _reconstructPacket(packet.data, buffers);
5970
- packet.attachments = undefined; // no longer useful
6190
+ delete packet.attachments; // no longer useful
5971
6191
  return packet;
5972
6192
  }
5973
6193
  function _reconstructPacket(data, buffers) {
5974
6194
  if (!data)
5975
6195
  return data;
5976
- if (data && data._placeholder) {
5977
- return buffers[data.num]; // appropriate buffer (should be natural order anyway)
6196
+ if (data && data._placeholder === true) {
6197
+ const isIndexValid = typeof data.num === "number" &&
6198
+ data.num >= 0 &&
6199
+ data.num < buffers.length;
6200
+ if (isIndexValid) {
6201
+ return buffers[data.num]; // appropriate buffer (should be natural order anyway)
6202
+ }
6203
+ else {
6204
+ throw new Error("illegal attachments");
6205
+ }
5978
6206
  }
5979
6207
  else if (Array.isArray(data)) {
5980
6208
  for (let i = 0; i < data.length; i++) {
@@ -6028,11 +6256,14 @@ class Encoder {
6028
6256
  encode(obj) {
6029
6257
  if (obj.type === PacketType.EVENT || obj.type === PacketType.ACK) {
6030
6258
  if (hasBinary(obj)) {
6031
- obj.type =
6032
- obj.type === PacketType.EVENT
6259
+ return this.encodeAsBinary({
6260
+ type: obj.type === PacketType.EVENT
6033
6261
  ? PacketType.BINARY_EVENT
6034
- : PacketType.BINARY_ACK;
6035
- return this.encodeAsBinary(obj);
6262
+ : PacketType.BINARY_ACK,
6263
+ nsp: obj.nsp,
6264
+ data: obj.data,
6265
+ id: obj.id,
6266
+ });
6036
6267
  }
6037
6268
  }
6038
6269
  return [this.encodeAsString(obj)];
@@ -6099,9 +6330,13 @@ class Decoder extends Emitter_1 {
6099
6330
  add(obj) {
6100
6331
  let packet;
6101
6332
  if (typeof obj === "string") {
6333
+ if (this.reconstructor) {
6334
+ throw new Error("got plaintext data when reconstructing a packet");
6335
+ }
6102
6336
  packet = this.decodeString(obj);
6103
- if (packet.type === PacketType.BINARY_EVENT ||
6104
- packet.type === PacketType.BINARY_ACK) {
6337
+ const isBinaryEvent = packet.type === PacketType.BINARY_EVENT;
6338
+ if (isBinaryEvent || packet.type === PacketType.BINARY_ACK) {
6339
+ packet.type = isBinaryEvent ? PacketType.EVENT : PacketType.ACK;
6105
6340
  // binary packet's json
6106
6341
  this.reconstructor = new BinaryReconstructor(packet);
6107
6342
  // no attachments, labeled binary but no binary data to follow
@@ -6230,6 +6465,7 @@ class Decoder extends Emitter_1 {
6230
6465
  destroy() {
6231
6466
  if (this.reconstructor) {
6232
6467
  this.reconstructor.finishedReconstruction();
6468
+ this.reconstructor = null;
6233
6469
  }
6234
6470
  }
6235
6471
  }
@@ -7498,6 +7734,20 @@ var StentorServerChat = /** @class */ (function () {
7498
7734
  var _a;
7499
7735
  (_a = this.socket) === null || _a === void 0 ? void 0 : _a.disconnect();
7500
7736
  };
7737
+ StentorServerChat.prototype.bargeOut = function (_cb) {
7738
+ return __awaiter$1(this, void 0, void 0, function () {
7739
+ return __generator$1(this, function (_a) {
7740
+ throw new Error("Method not implemented: bargeOut");
7741
+ });
7742
+ });
7743
+ };
7744
+ StentorServerChat.prototype.bargeIn = function (_agentName, _cb) {
7745
+ return __awaiter$1(this, void 0, void 0, function () {
7746
+ return __generator$1(this, function (_a) {
7747
+ throw new Error("Method not implemented: bargeIn");
7748
+ });
7749
+ });
7750
+ };
7501
7751
  return StentorServerChat;
7502
7752
  }());
7503
7753
 
@@ -8364,6 +8614,8 @@ function useChatServerVisitorId() {
8364
8614
  function useGreeting(active) {
8365
8615
  var curr = useChatServerVisitorId();
8366
8616
  var snapshotRef = React$1.useRef(null);
8617
+ var ctx = React$1.useContext(ChatConfigContext);
8618
+ var isAdmin = ctx.env.isAdmin;
8367
8619
  React$1.useEffect(function () {
8368
8620
  if (active) {
8369
8621
  if (snapshotRef.current !== curr) {
@@ -8376,18 +8628,23 @@ function useGreeting(active) {
8376
8628
  accessToken: curr.accessToken,
8377
8629
  attributes: __assign(__assign({}, curr.attributes), { currentUrl: window.location.href })
8378
8630
  }));
8379
- var timeoutId_1 = setTimeout(function () {
8380
- var greetingAction = sendGreeting();
8381
- curr.dispatch(greetingAction);
8382
- }, 1000);
8383
- return function () {
8384
- clearTimeout(timeoutId_1);
8385
- };
8631
+ if (!isAdmin) {
8632
+ var timeoutId_1 = setTimeout(function () {
8633
+ var greetingAction = sendGreeting();
8634
+ curr.dispatch(greetingAction);
8635
+ }, 1000);
8636
+ return function () {
8637
+ clearTimeout(timeoutId_1);
8638
+ };
8639
+ }
8640
+ else {
8641
+ log("Admin widget. Not sending greeting.");
8642
+ }
8386
8643
  }
8387
8644
  }
8388
8645
  }
8389
8646
  return undefined;
8390
- }, [curr, active]);
8647
+ }, [curr, active, isAdmin]);
8391
8648
  }
8392
8649
 
8393
8650
  function useSuggestionsFetch(search, context) {
@@ -8465,6 +8722,39 @@ function useSuggestions(search, context) {
8465
8722
  }); }, [suggestions, suggestionIndex, execute, suggestionItem]);
8466
8723
  }
8467
8724
 
8725
+ var AdminBar = function (_props) {
8726
+ var name = React$1.useRef(null);
8727
+ // We can manage this locally
8728
+ // const hasAdminJoined = useSelector<ChatState, boolean | undefined>(state => state.hasAdminJoined);
8729
+ var _a = React$1.useState(false), joined = _a[0], setJoined = _a[1];
8730
+ var dispatch = useChatServerDispatch();
8731
+ function onSubmit(event) {
8732
+ var _a;
8733
+ event.preventDefault();
8734
+ if (!joined) {
8735
+ dispatch(sendBargeIn((_a = name.current) === null || _a === void 0 ? void 0 : _a.value));
8736
+ setJoined(true);
8737
+ }
8738
+ else {
8739
+ dispatch(sendBargeOut());
8740
+ setJoined(false);
8741
+ }
8742
+ }
8743
+ function renderJoin() {
8744
+ return (React__default$1["default"].createElement("form", { className: "xappw-admin-input-form", onSubmit: onSubmit },
8745
+ React__default$1["default"].createElement("div", { className: "xappw-admin-input-form__buttons" },
8746
+ React__default$1["default"].createElement(ActionButton, { addClass: "xappw-admin-input-form__btn", type: "submit", label: "Join" })),
8747
+ React__default$1["default"].createElement("div", { className: "xappw-admin-input" },
8748
+ React__default$1["default"].createElement("input", { ref: name, id: "adminBarInput", placeholder: "Type your name here...", className: "xappw-admin-input__input" }))));
8749
+ }
8750
+ function renderLeave() {
8751
+ return (React__default$1["default"].createElement("form", { className: "xappw-admin-input-form", onSubmit: onSubmit },
8752
+ React__default$1["default"].createElement("div", { className: "xappw-admin-input-form__buttons" },
8753
+ React__default$1["default"].createElement(ActionButton, { addClass: "xappw-admin-input-form__btn", type: "submit", label: "Leave" }))));
8754
+ }
8755
+ return (React__default$1["default"].createElement("div", { className: "xappw-admin-input-container visible" }, joined ? renderLeave() : renderJoin()));
8756
+ };
8757
+
8468
8758
  var ChatBranding = function (props) {
8469
8759
  var text = props.text;
8470
8760
  return (React__default$1["default"].createElement("div", { className: "chat-footer__branding" }, text !== null && text !== void 0 ? text : "Powered by XAPP AI"));
@@ -8585,10 +8875,19 @@ function createActions(onItemUse) {
8585
8875
  };
8586
8876
  }
8587
8877
  var Suggestions = function (props) {
8588
- var data = props.data, onItemUse = props.onItemUse;
8589
- var len = data.length;
8878
+ var data = props.data, onItemUse = props.onItemUse, searchTerms = props.searchTerms;
8879
+ var _a = React$1.useState(), fixedSuggestions = _a[0], setFixedSuggestions = _a[1];
8880
+ React$1.useEffect(function () {
8881
+ if (data) {
8882
+ setFixedSuggestions(data);
8883
+ }
8884
+ if (!searchTerms) {
8885
+ setFixedSuggestions(undefined);
8886
+ }
8887
+ }, [data, searchTerms]);
8888
+ var len = data === null || data === void 0 ? void 0 : data.length;
8590
8889
  var currentIndex = len > 0 ? props.index : NaN;
8591
- var _a = React$1.useState(), activeItem = _a[0], setActiveItem = _a[1];
8890
+ var _b = React$1.useState(), activeItem = _b[0], setActiveItem = _b[1];
8592
8891
  var item = React$1.useMemo(function () { return findItemByIndex_1(data, currentIndex); }, [data, currentIndex]);
8593
8892
  var handleSpanClick = React$1.useCallback(function (target, span) {
8594
8893
  if (span.type === "inputText") {
@@ -8603,8 +8902,8 @@ var Suggestions = function (props) {
8603
8902
  var actions = React$1.useMemo(function () { return createActions(onItemUse); }, [onItemUse]);
8604
8903
  return (React__default$1["default"].createElement("div", { className: "xappw-suggestions ".concat(props.className || "") },
8605
8904
  (activeItem === null || activeItem === void 0 ? void 0 : activeItem.content) && React__default$1["default"].createElement("div", { className: "xappw-suggestions__answer" }, activeItem.content),
8606
- !!data.length &&
8607
- React__default$1["default"].createElement(SuggestionsList_1, { suggestions: data, index: currentIndex, className: "xappw-suggestions__groups", itemActions: actions, onItemClick: props.onItemClick, onItemHover: setActiveItem, onSpanClick: handleSpanClick })));
8905
+ fixedSuggestions && fixedSuggestions.length > 0 &&
8906
+ React__default$1["default"].createElement(SuggestionsList_1, { suggestions: fixedSuggestions, index: currentIndex, className: "xappw-suggestions__groups", itemActions: actions, onItemClick: props.onItemClick, onItemHover: setActiveItem, onSpanClick: handleSpanClick })));
8608
8907
  };
8609
8908
 
8610
8909
  var ChatFooter = function (props) {
@@ -8663,6 +8962,7 @@ var ChatFooter = function (props) {
8663
8962
  React__default$1["default"].createElement("div", { className: "chat-footer__menu-icon" },
8664
8963
  React__default$1["default"].createElement(DrawerBars, { tabIndex: menuButtonTabIndex, onToggle: toggleDrawer }))) : React__default$1["default"].createElement(React__default$1["default"].Fragment, null),
8665
8964
  ((_d = suggestions.suggestions) === null || _d === void 0 ? void 0 : _d.length) > 0 && React__default$1["default"].createElement(Suggestions, { className: "xappw-chat-footer__suggestions", data: suggestions.suggestions, index: suggestions.index, onItemClick: handleItemClick, onItemUse: handleItemUse }),
8965
+ props.isAdmin && props.isChatting && props.visible && React__default$1["default"].createElement(AdminBar, null),
8666
8966
  React__default$1["default"].createElement(Input, { addClass: "chat-footer__input " + (props.isChatting && props.visible ? "visible" : ""), suggestion: suggestions.item, value: input, placeholder: placeholder, sendButtonIcon: sendButtonIcon, footerConfig: footerConfig, inputConfig: inputConfig, onSubmit: handleSubmit, onChange: handleChange, onSuggestionCommand: suggestions.execute,
8667
8967
  // onFocus={this.inputOnFocus}
8668
8968
  onFileUpload: props.onFileUpload }),
@@ -31070,15 +31370,15 @@ var TypingIndicator = function (_) {
31070
31370
  * Show the agent is typing (or the bot - for fun)
31071
31371
  */
31072
31372
  var TypingStatus = function (props) {
31073
- var agent_names = Object.values(props.agents).filter(function (agent) { return agent.typing; });
31074
- return (React__default$1["default"].createElement(React__default$1["default"].Fragment, null, agent_names.map(function (agent) {
31373
+ var agentsTyping = Object.values(props.agents).filter(function (agent) { return agent.typing; });
31374
+ return (React__default$1["default"].createElement(React__default$1["default"].Fragment, null, agentsTyping.map(function (agent) {
31075
31375
  var _a, _b;
31076
31376
  return (React__default$1["default"].createElement(React__default$1["default"].Fragment, null, !props.textTypingStatusEnabled ? (React__default$1["default"].createElement("div", { className: "chat-msg-container-wrapper", key: "typing-status-".concat(agent.user.nick) },
31077
31377
  React__default$1["default"].createElement("div", { key: agent.user.nick, className: "chat-msg-container agent chat-typing-progress" },
31078
31378
  React__default$1["default"].createElement(ChatMessagePart, { user: agent.user, showAvatar: true },
31079
31379
  React__default$1["default"].createElement("div", { className: "chat-msg" },
31080
31380
  React__default$1["default"].createElement(ChatMessageBubble, { owner: "others", hasTail: true },
31081
- React__default$1["default"].createElement(TypingIndicator, null))))))) : (React__default$1["default"].createElement("div", { key: "typing-status-".concat(agent.user.nick), className: "chat-msg-agent-typing" }, (_b = (_a = agent.user) === null || _a === void 0 ? void 0 : _a.displayName) !== null && _b !== void 0 ? _b : "Bot",
31381
+ React__default$1["default"].createElement(TypingIndicator, null))))))) : (React__default$1["default"].createElement("div", { key: "typing-status-".concat(agent.user.nick), className: "chat-msg-agent-typing" }, (_b = (_a = agent.user) === null || _a === void 0 ? void 0 : _a.displayName) !== null && _b !== void 0 ? _b : "Somebody",
31082
31382
  " is typing"))));
31083
31383
  })));
31084
31384
  };
@@ -31127,9 +31427,18 @@ var MessageList = function (props) {
31127
31427
  */
31128
31428
  function renderByType(msg, sibling) {
31129
31429
  var _a;
31130
- var user = ((_a = props.agents[msg.user.nick]) === null || _a === void 0 ? void 0 : _a.user) || props.agent;
31131
- if (!user) {
31132
- console.warn("Could not get a user from agents list from ".concat(msg.user.nick, " or has an "));
31430
+ var user;
31431
+ // visitor is this widget
31432
+ if (msg.user.nick.startsWith("visitor:")) {
31433
+ user = msg.user;
31434
+ }
31435
+ else {
31436
+ user = ((_a = props.agents[msg.user.nick]) === null || _a === void 0 ? void 0 : _a.user) || props.agent;
31437
+ // Still nothing?
31438
+ if (!user) {
31439
+ console.warn("Could not get a user from agents list with nick: \"".concat(msg.user.nick, "\""));
31440
+ user = msg.user;
31441
+ }
31133
31442
  }
31134
31443
  switch (msg.type) {
31135
31444
  case "chat.file":
@@ -31731,8 +32040,8 @@ var ChatWidget = function (props) {
31731
32040
  });
31732
32041
  }
31733
32042
  }
31734
- useGreeting(!isOffline && !props.preChatFormEnabled && visible);
31735
32043
  var config = props.config, onConnectionStatusChange = props.onConnectionStatusChange;
32044
+ useGreeting(!isOffline && !props.preChatFormEnabled && visible);
31736
32045
  var connectionStatus = chatState.connection.connectionStatus;
31737
32046
  React$1.useEffect(function () {
31738
32047
  if (onConnectionStatusChange) {
@@ -31748,7 +32057,7 @@ var ChatWidget = function (props) {
31748
32057
  React__default$1["default"].createElement("div", { className: "spinner-container ".concat(visible && connectionStatus === "pending" ? "visible" : "") },
31749
32058
  React__default$1["default"].createElement("div", { className: "spinner" })),
31750
32059
  connectionStatus === "offline" && React__default$1["default"].createElement(ServerOffline, null),
31751
- React__default$1["default"].createElement(ChatFooter, { isChatting: chatState.isChatting, placeholder: (_r = config === null || config === void 0 ? void 0 : config.input) === null || _r === void 0 ? void 0 : _r.placeholder, sendButtonIcon: (_t = (_s = config === null || config === void 0 ? void 0 : config.footer) === null || _s === void 0 ? void 0 : _s.sendButton) === null || _t === void 0 ? void 0 : _t.icon, visible: visible, menuConfig: props.config.menu, footerConfig: (_u = props.config) === null || _u === void 0 ? void 0 : _u.footer, inputConfig: (_v = props.config) === null || _v === void 0 ? void 0 : _v.input, onChange: handleOnChange, onSubmit: handleOnSubmit, onFileUpload: handleFileUpload }),
32060
+ React__default$1["default"].createElement(ChatFooter, { isAdmin: config === null || config === void 0 ? void 0 : config.isAdmin, isChatting: chatState.isChatting, placeholder: (_r = config === null || config === void 0 ? void 0 : config.input) === null || _r === void 0 ? void 0 : _r.placeholder, sendButtonIcon: (_t = (_s = config === null || config === void 0 ? void 0 : config.footer) === null || _s === void 0 ? void 0 : _s.sendButton) === null || _t === void 0 ? void 0 : _t.icon, visible: visible, menuConfig: props.config.menu, footerConfig: (_u = props.config) === null || _u === void 0 ? void 0 : _u.footer, inputConfig: (_v = props.config) === null || _v === void 0 ? void 0 : _v.input, onChange: handleOnChange, onSubmit: handleOnSubmit, onFileUpload: handleFileUpload }),
31752
32061
  React__default$1["default"].createElement("div", { className: "restartModal", ref: modalRef, onClick: closeModal },
31753
32062
  React__default$1["default"].createElement(ModalContent, { onClose: closeModal, onReset: refreshOnClick }))),
31754
32063
  React__default$1["default"].createElement(ChatButton, { addClass: getVisibilityClass(), onClick: chatButtonOnClick, config: (_w = props.config) === null || _w === void 0 ? void 0 : _w.cta, visible: visible })));
@@ -31866,7 +32175,6 @@ function persistStateReducer(storage, initialState, innerReducer) {
31866
32175
  }
31867
32176
 
31868
32177
  function connectionReducer(state, action) {
31869
- log("action", action);
31870
32178
  switch (action.type) {
31871
32179
  case "connection_update":
31872
32180
  return __assign(__assign({}, state), { connectionStatus: action.detail.status });
@@ -31883,31 +32191,32 @@ function joinMessages(messages, msg) {
31883
32191
  }
31884
32192
 
31885
32193
  function memberJoin(state, detail) {
31886
- var _a;
31887
32194
  if (state.chats.length === 0) {
31888
32195
  set$1("sessionId", "");
31889
32196
  }
32197
+ var agents = __assign({}, state.agents);
32198
+ var prevAgentInfo = state.agents[detail.user.nick];
32199
+ agents[detail.user.nick] = __assign(__assign({}, prevAgentInfo), { user: __assign(__assign({}, detail.user), { displayName: detail.user.displayName || "Agent" }), joined: true, typing: false });
31890
32200
  if (isAgent(detail.user.nick)) {
31891
- var prevAgentInfo = state.agents[detail.user.nick];
31892
- return __assign(__assign({}, state), { isChatting: true, chats: (prevAgentInfo === null || prevAgentInfo === void 0 ? void 0 : prevAgentInfo.joined) ? state.chats : joinMessages(state.chats, detail), agents: (_a = {},
31893
- _a[detail.user.nick] = __assign(__assign({}, prevAgentInfo), { user: __assign(__assign({}, detail.user), { displayName: detail.user.displayName || "Bot" }), joined: true, typing: false }),
31894
- _a) });
32201
+ return __assign(__assign({}, state), { isChatting: true, chats: (prevAgentInfo === null || prevAgentInfo === void 0 ? void 0 : prevAgentInfo.joined) ? state.chats : joinMessages(state.chats, detail), agents: agents });
31895
32202
  }
31896
32203
  else {
31897
- return __assign(__assign({}, state), { isChatting: true, chats: joinMessages(state.chats, detail) });
32204
+ return __assign(__assign({}, state), { isChatting: true, chats: joinMessages(state.chats, detail), agents: agents });
31898
32205
  }
31899
32206
  }
31900
32207
 
31901
32208
  function memberLeave(state, detail) {
31902
- var _a;
32209
+ var agents = __assign({}, state.agents);
32210
+ var prevAgentInfo = state.agents[detail.user.nick];
32211
+ agents[detail.user.nick] = __assign(__assign({}, prevAgentInfo), { joined: false, typing: false });
32212
+ // count number of agents that are still joined
32213
+ var agentsJoined = Object.values(agents).filter(function (agent) { return agent.joined; });
32214
+ var isChatting = agentsJoined.length > 0;
31903
32215
  if (isAgent(detail.user.nick)) {
31904
- var prevAgentInfo = state.agents[detail.user.nick];
31905
- return __assign(__assign({}, state), { isChatting: false, chats: (prevAgentInfo === null || prevAgentInfo === void 0 ? void 0 : prevAgentInfo.joined) ? joinMessages(state.chats, detail) : state.chats, agents: (_a = {},
31906
- _a[detail.user.nick] = __assign(__assign({}, prevAgentInfo), { joined: false }),
31907
- _a) });
32216
+ return __assign(__assign({}, state), { isChatting: isChatting, chats: (prevAgentInfo === null || prevAgentInfo === void 0 ? void 0 : prevAgentInfo.joined) ? joinMessages(state.chats, detail) : state.chats, agents: agents });
31908
32217
  }
31909
32218
  else {
31910
- return __assign(__assign({}, state), { isChatting: false, chats: joinMessages(state.chats, detail) });
32219
+ return __assign(__assign({}, state), { isChatting: isChatting, chats: joinMessages(state.chats, detail), agents: agents });
31911
32220
  }
31912
32221
  }
31913
32222
 
@@ -31937,15 +32246,19 @@ function update(state, action) {
31937
32246
  if (action.type === "reset") {
31938
32247
  return resetReducer(state);
31939
32248
  }
31940
- // workaround until state is split. Use combineReducers afterwards
31941
- var newConnectionState = connectionReducer(state.connection, action);
31942
- if (state.connection !== newConnectionState) {
31943
- state = __assign(__assign({}, state), { connection: newConnectionState });
31944
- }
31945
32249
  if (action.detail) {
31946
32250
  state.lastTimestamp = action.detail.timestamp;
31947
32251
  }
31948
32252
  switch (action.type) {
32253
+ case "connection_update":
32254
+ case "receiveToken":
32255
+ case "sendGreeting":
32256
+ // workaround until state is split. Use combineReducers afterwards
32257
+ var newConnectionState = connectionReducer(state.connection, action);
32258
+ if (state.connection !== newConnectionState) {
32259
+ state = __assign(__assign({}, state), { connection: newConnectionState });
32260
+ }
32261
+ return state;
31949
32262
  case "account_status":
31950
32263
  return __assign(__assign({}, state), { accountStatus: action.detail.status });
31951
32264
  case "department_update":
@@ -32049,7 +32362,10 @@ function storeHandler(state, action) {
32049
32362
  }
32050
32363
  }
32051
32364
 
32365
+ var ADMIN_STORE_KEY = "AdminAccessTimes";
32366
+ var ADMIN_STORE_MAX = 20;
32052
32367
  function createChatStore(config, dataStorage) {
32368
+ var _a;
32053
32369
  if (!dataStorage) {
32054
32370
  if (typeof window !== "undefined") {
32055
32371
  dataStorage = localStorage;
@@ -32059,7 +32375,39 @@ function createChatStore(config, dataStorage) {
32059
32375
  }
32060
32376
  }
32061
32377
  var connection = config.connection;
32062
- var storage = new BrowserStateStorage(dataStorage, "xappchat.".concat(connection.serverUrl, ".").concat(connection.accountKey));
32378
+ var joinSessionId = (_a = new URL(window.location.href).searchParams) === null || _a === void 0 ? void 0 : _a.get("sessionId");
32379
+ var dataStorageKey;
32380
+ var now = new Date().getTime();
32381
+ if (joinSessionId) {
32382
+ var adminTimesData = dataStorage.getItem(ADMIN_STORE_KEY);
32383
+ // Create
32384
+ var adminTimes = adminTimesData ? JSON.parse(adminTimesData) : {};
32385
+ // if exists, use it. If not, check if we are maxed out. evict one session if we are.
32386
+ if (adminTimes[joinSessionId]) {
32387
+ log("ChatStore: reusing session: ".concat(joinSessionId));
32388
+ adminTimes[joinSessionId].lastAccessMs = now;
32389
+ dataStorageKey = adminTimes[joinSessionId].dataStorageKey;
32390
+ }
32391
+ else {
32392
+ if (Object.keys(adminTimes).length >= ADMIN_STORE_MAX) {
32393
+ var evictedSessionId = evictAdminSession(adminTimes);
32394
+ log("ChatStore: evicting session: ".concat(evictedSessionId));
32395
+ dataStorage.removeItem(adminTimes[evictedSessionId].dataStorageKey);
32396
+ adminTimes[evictedSessionId] = undefined;
32397
+ }
32398
+ // We now have room
32399
+ dataStorageKey = generateKey(connection, joinSessionId);
32400
+ adminTimes[joinSessionId] = {
32401
+ lastAccessMs: now,
32402
+ dataStorageKey: dataStorageKey,
32403
+ };
32404
+ }
32405
+ dataStorage.setItem(ADMIN_STORE_KEY, JSON.stringify(adminTimes));
32406
+ }
32407
+ else {
32408
+ dataStorageKey = generateKey(connection);
32409
+ }
32410
+ var storage = new BrowserStateStorage(dataStorage, dataStorageKey);
32063
32411
  var defaultState = createDefaultState({
32064
32412
  accessToken: config.accessToken,
32065
32413
  userId: config.userId,
@@ -32073,6 +32421,36 @@ function createChatStore(config, dataStorage) {
32073
32421
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
32074
32422
  var composeEnhancers = globalThis.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || redux.compose;
32075
32423
  return redux.createStore(chatReducer, composeEnhancers(redux.applyMiddleware.apply(void 0, middlewares)));
32424
+ }
32425
+ /**
32426
+ * Pick one admin session for eviction
32427
+ *
32428
+ * @param adminTimes
32429
+ * @returns
32430
+ */
32431
+ function evictAdminSession(adminTimes) {
32432
+ // Find the oldest
32433
+ var minTimeTs = Number.MAX_VALUE;
32434
+ var minSessionId;
32435
+ for (var _i = 0, _a = Object.keys(adminTimes); _i < _a.length; _i++) {
32436
+ var sessionId = _a[_i];
32437
+ if (adminTimes[sessionId].lastAccessMs < minTimeTs) {
32438
+ minSessionId = sessionId;
32439
+ minTimeTs = adminTimes[sessionId].lastAccessMs;
32440
+ }
32441
+ }
32442
+ return minSessionId;
32443
+ }
32444
+ function generateKey(connection, sessionId) {
32445
+ var base = "xappchat.".concat(connection.serverUrl);
32446
+ if (sessionId) {
32447
+ return connection.accountKey
32448
+ ? "".concat(base, ".").concat(connection.accountKey, ".").concat(sessionId)
32449
+ : "".concat(base, ".").concat(sessionId);
32450
+ }
32451
+ else {
32452
+ return connection.accountKey ? "".concat(base, ".").concat(connection.accountKey) : base;
32453
+ }
32076
32454
  }
32077
32455
 
32078
32456
  var ChatWidgetContainer = function (props) {