@xapp/chat-widget 1.87.0 → 1.87.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.es.js CHANGED
@@ -1,4 +1,4 @@
1
- import require$$1, { useCallback, useState, useEffect, useRef, createContext, useContext, useMemo, memo, useLayoutEffect } from 'react';
1
+ import require$$1, { useCallback, useState, useEffect, useRef, createContext, useMemo, useContext, memo, useLayoutEffect } from 'react';
2
2
  export { default as React } from 'react';
3
3
  import require$$0, { jsx, jsxs, Fragment } from 'react/jsx-runtime';
4
4
  import { useSelector, useDispatch, Provider } from 'react-redux';
@@ -313,10 +313,14 @@ var ActionBarCta = function (_a) {
313
313
  var _m = useState(false), isVisible = _m[0], setIsVisible = _m[1];
314
314
  var _o = useState(false), isAnimating = _o[0], setIsAnimating = _o[1];
315
315
  var _p = useState(false), isDismissed = _p[0], setIsDismissed = _p[1];
316
- var message = isMobile && ((_b = config.mobile) === null || _b === void 0 ? void 0 : _b.message) ? config.mobile.message : config.message;
316
+ // For mobile overrides: null means "explicitly disabled", undefined means "use desktop default"
317
+ var mobileMessage = isMobile ? (_b = config.mobile) === null || _b === void 0 ? void 0 : _b.message : undefined;
318
+ var message = mobileMessage !== undefined ? mobileMessage : config.message;
317
319
  var delay = isMobile && ((_c = config.mobile) === null || _c === void 0 ? void 0 : _c.delay) !== undefined ? config.mobile.delay : ((_d = config.delay) !== null && _d !== void 0 ? _d : 0);
318
- var timeout = isMobile && ((_e = config.mobile) === null || _e === void 0 ? void 0 : _e.timeout) !== undefined ? config.mobile.timeout : config.timeout;
319
- var animation = isMobile && ((_f = config.mobile) === null || _f === void 0 ? void 0 : _f.animation) ? config.mobile.animation : config.animation;
320
+ var mobileTimeout = isMobile ? (_e = config.mobile) === null || _e === void 0 ? void 0 : _e.timeout : undefined;
321
+ var timeout = mobileTimeout !== undefined ? mobileTimeout : config.timeout;
322
+ var mobileAnimation = isMobile ? (_f = config.mobile) === null || _f === void 0 ? void 0 : _f.animation : undefined;
323
+ var animation = mobileAnimation !== undefined ? mobileAnimation : config.animation;
320
324
  var animationDelay = isMobile && ((_g = config.mobile) === null || _g === void 0 ? void 0 : _g.animationDelay) !== undefined
321
325
  ? config.mobile.animationDelay
322
326
  : ((_h = config.animationDelay) !== null && _h !== void 0 ? _h : delay);
@@ -1779,14 +1783,14 @@ function useOpenUrlCallback() {
1779
1783
  }, [env]);
1780
1784
  }
1781
1785
 
1782
- function writeMessage(msg, user) {
1786
+ function writeMessage(msg, user, timestamp) {
1783
1787
  return {
1784
1788
  type: "synthetic",
1785
1789
  detail: {
1786
1790
  type: "write_msg",
1787
1791
  user: user,
1788
1792
  msg: msg,
1789
- timestamp: +new Date()
1793
+ timestamp: timestamp !== undefined ? timestamp : +new Date()
1790
1794
  }
1791
1795
  };
1792
1796
  }
@@ -1798,16 +1802,19 @@ function executeAction(text, meta) {
1798
1802
  attributes = __assign(__assign({}, attributes), { currentUrl: window.location.href });
1799
1803
  var _a = meta || {}, token = _a.token, rest = __rest(_a, ["token"]);
1800
1804
  attributes = __assign(__assign({}, attributes), rest);
1805
+ // Add user message to Redux BEFORE sending to server
1806
+ // to ensure it appears above the bot's streaming response
1807
+ var visitor = getState().visitor;
1808
+ var userMsgTimestamp = +new Date();
1809
+ dispatch(writeMessage({
1810
+ text: !token ? text : "".concat(text, " (from the list)"),
1811
+ }, visitor, userMsgTimestamp));
1801
1812
  chatServer.sendChatMsg({ text: text, token: token, attributes: attributes }, function (err) {
1802
1813
  if (err) {
1803
1814
  log("Error sending message", err);
1804
1815
  return;
1805
1816
  }
1806
1817
  });
1807
- var visitor = getState().visitor;
1808
- dispatch(writeMessage({
1809
- text: !token ? text : "".concat(text, " (from the list)"),
1810
- }, visitor));
1811
1818
  }; };
1812
1819
  }
1813
1820
 
@@ -3071,18 +3078,66 @@ function storeIdentity(userId, sessionId) {
3071
3078
  }
3072
3079
  /**
3073
3080
  * Clears stored MCP session (but preserves userId for returning user identification).
3081
+ * Optionally clears the stored lastSeq for the given sessionId.
3074
3082
  */
3075
- function clearStoredSession() {
3083
+ function clearStoredSession(sessionId) {
3076
3084
  if (typeof window === "undefined" || !window.localStorage) {
3077
3085
  return;
3078
3086
  }
3079
3087
  try {
3080
3088
  localStorage.removeItem(MCP_STORAGE_KEYS.SESSION_ID);
3089
+ if (sessionId) {
3090
+ clearStoredLastSeq(sessionId);
3091
+ }
3081
3092
  }
3082
3093
  catch (_a) {
3083
3094
  // localStorage may be unavailable
3084
3095
  }
3085
3096
  }
3097
+ /**
3098
+ * Retrieves stored lastSeq from sessionStorage for replay on reconnect.
3099
+ * Uses sessionStorage (not localStorage) because seq is ephemeral to the browser tab.
3100
+ */
3101
+ function getStoredLastSeq(sessionId) {
3102
+ if (typeof window === "undefined" || !window.sessionStorage) {
3103
+ return undefined;
3104
+ }
3105
+ try {
3106
+ var stored = sessionStorage.getItem("xapp_mcp_lastSeq_".concat(sessionId));
3107
+ return stored ? parseInt(stored, 10) : undefined;
3108
+ }
3109
+ catch (_a) {
3110
+ return undefined;
3111
+ }
3112
+ }
3113
+ /**
3114
+ * Persists lastSeq to sessionStorage for replay on reconnect.
3115
+ */
3116
+ function storeLastSeq(sessionId, seq) {
3117
+ if (typeof window === "undefined" || !window.sessionStorage) {
3118
+ return;
3119
+ }
3120
+ try {
3121
+ sessionStorage.setItem("xapp_mcp_lastSeq_".concat(sessionId), String(seq));
3122
+ }
3123
+ catch (_a) {
3124
+ // sessionStorage may be unavailable
3125
+ }
3126
+ }
3127
+ /**
3128
+ * Clears stored lastSeq from sessionStorage.
3129
+ */
3130
+ function clearStoredLastSeq(sessionId) {
3131
+ if (typeof window === "undefined" || !window.sessionStorage) {
3132
+ return;
3133
+ }
3134
+ try {
3135
+ sessionStorage.removeItem("xapp_mcp_lastSeq_".concat(sessionId));
3136
+ }
3137
+ catch (_a) {
3138
+ // sessionStorage may be unavailable
3139
+ }
3140
+ }
3086
3141
  /**
3087
3142
  * Gets the current page context from browser globals.
3088
3143
  * If an override is provided, uses that instead (useful for testing).
@@ -3143,6 +3198,12 @@ var MCPChat = /** @class */ (function () {
3143
3198
  this.isValidatingSession = false;
3144
3199
  // Tool call tracking for debug mode
3145
3200
  this.currentToolCalls = [];
3201
+ // TODO: isReplaying and lastSeq are tracked for future replay support.
3202
+ // Wire up replay requests in wakeup() once the server endpoint supports it.
3203
+ this.isReplaying = false;
3204
+ // True when userId was assigned by the server (via connected SSE event),
3205
+ // meaning it should not be overwritten by the default client fingerprint.
3206
+ this.userIdIsServerAssigned = false;
3146
3207
  this.config = config;
3147
3208
  this.options = options;
3148
3209
  this.configurableMessages = options === null || options === void 0 ? void 0 : options.configurableMessages;
@@ -3157,6 +3218,7 @@ var MCPChat = /** @class */ (function () {
3157
3218
  var storedIdentity = getStoredIdentity();
3158
3219
  if (storedIdentity.userId) {
3159
3220
  this._userId = storedIdentity.userId;
3221
+ this.userIdIsServerAssigned = true;
3160
3222
  log("[MCPChat] Loaded stored userId: ".concat(this._userId));
3161
3223
  }
3162
3224
  if (storedIdentity.sessionId) {
@@ -3167,6 +3229,13 @@ var MCPChat = /** @class */ (function () {
3167
3229
  if (config.sessionId) {
3168
3230
  this._sessionId = config.sessionId;
3169
3231
  }
3232
+ // Load lastSeq from sessionStorage for replay on reconnect
3233
+ if (this._sessionId) {
3234
+ this.lastSeq = getStoredLastSeq(this._sessionId);
3235
+ if (this.lastSeq !== undefined) {
3236
+ log("[MCPChat] Loaded stored lastSeq: ".concat(this.lastSeq));
3237
+ }
3238
+ }
3170
3239
  }
3171
3240
  MCPChat.prototype.init = function (dispatch) {
3172
3241
  this.dispatch = dispatch;
@@ -3178,6 +3247,9 @@ var MCPChat = /** @class */ (function () {
3178
3247
  }
3179
3248
  this.setConnectionStatus("online");
3180
3249
  this.setAccountStatus("online");
3250
+ // Dispatch bot joined early so it appears before the greeting.
3251
+ // getBot() is config-derived (uses options.bot), so it's safe to call before setVisitorInfo.
3252
+ this.userJoined(this.getBot(undefined));
3181
3253
  // Returning user with existing session - validate session silently
3182
3254
  // New user without session - initialize new session to get greeting
3183
3255
  if (this._sessionId) {
@@ -3221,8 +3293,9 @@ var MCPChat = /** @class */ (function () {
3221
3293
  error_1 = _a.sent();
3222
3294
  err("Failed to validate session: ".concat(error_1));
3223
3295
  // If validation fails (network error, etc.), clear session and start fresh
3296
+ clearStoredSession(this._sessionId);
3224
3297
  this._sessionId = "";
3225
- clearStoredSession();
3298
+ this.lastSeq = undefined;
3226
3299
  this.initializeNewSession();
3227
3300
  return [3 /*break*/, 5];
3228
3301
  case 4:
@@ -3244,7 +3317,9 @@ var MCPChat = /** @class */ (function () {
3244
3317
  // Fingerprint for user identification (can be disabled for privacy)
3245
3318
  fingerprint: this.config.disableFingerprinting ? undefined : getFingerprint(),
3246
3319
  // Page context
3247
- pageContext: getPageContext(this.config.pageContextOverride) }, additionalData);
3320
+ pageContext: getPageContext(this.config.pageContextOverride),
3321
+ // Client timestamp for latency measurement and clock drift detection
3322
+ clientTime: new Date().toISOString() }, additionalData);
3248
3323
  };
3249
3324
  /**
3250
3325
  * Initializes a new session by calling the endpoint with { newSession: true }.
@@ -3429,14 +3504,15 @@ var MCPChat = /** @class */ (function () {
3429
3504
  };
3430
3505
  MCPChat.prototype.dispatchStreamStart = function (messageId) {
3431
3506
  var bot = this.getBot(undefined);
3432
- log("Stream started: ".concat(messageId, ", bot: ").concat(JSON.stringify(bot)));
3507
+ var streamTimestamp = +new Date();
3508
+ log("[dispatchStreamStart] Stream started: ".concat(messageId, ", timestamp: ").concat(streamTimestamp));
3433
3509
  this.dispatch({
3434
3510
  type: "chat",
3435
3511
  detail: {
3436
3512
  type: "chat.stream.start",
3437
3513
  user: bot,
3438
3514
  messageId: messageId,
3439
- timestamp: +new Date(),
3515
+ timestamp: streamTimestamp,
3440
3516
  },
3441
3517
  });
3442
3518
  };
@@ -3857,14 +3933,26 @@ var MCPChat = /** @class */ (function () {
3857
3933
  log("Failed to parse SSE data: ".concat(e));
3858
3934
  return;
3859
3935
  }
3936
+ // Track sequence number for replay on reconnect
3937
+ if (data.seq !== undefined && data.seq !== null) {
3938
+ _this.lastSeq = data.seq;
3939
+ if (_this._sessionId) {
3940
+ storeLastSeq(_this._sessionId, data.seq);
3941
+ }
3942
+ }
3860
3943
  switch (eventType) {
3861
3944
  case "connected":
3862
3945
  // Connection established
3863
- log("[SSE] Connected event received, messageId: ".concat(messageId, ", isReturningUser: ").concat(data.isReturningUser));
3946
+ log("[SSE] Connected event received, messageId: ".concat(messageId, ", isReturningUser: ").concat(data.isReturningUser, ", isReplay: ").concat(data.isReplay));
3947
+ // Track if this is a replay connection
3948
+ if (data.isReplay) {
3949
+ _this.isReplaying = true;
3950
+ }
3864
3951
  // Store identity from server response
3865
3952
  if (data.userId || data.sessionId) {
3866
3953
  if (data.userId) {
3867
3954
  _this._userId = data.userId;
3955
+ _this.userIdIsServerAssigned = true;
3868
3956
  }
3869
3957
  if (data.sessionId) {
3870
3958
  _this._sessionId = data.sessionId;
@@ -3884,9 +3972,10 @@ var MCPChat = /** @class */ (function () {
3884
3972
  case "session_expired":
3885
3973
  // Session no longer exists on server - need to start fresh
3886
3974
  log("[SSE] Session expired: ".concat(data.reason || "Session not found"));
3975
+ // Clear stored session (and lastSeq) but keep userId for returning user identification
3976
+ clearStoredSession(_this._sessionId);
3887
3977
  _this._sessionId = "";
3888
- // Clear stored session but keep userId for returning user identification
3889
- clearStoredSession();
3978
+ _this.lastSeq = undefined;
3890
3979
  _this.stopTyping();
3891
3980
  _this.stopStreamThrottle();
3892
3981
  // Reset validation flag before starting new session
@@ -3898,10 +3987,17 @@ var MCPChat = /** @class */ (function () {
3898
3987
  break;
3899
3988
  case "text":
3900
3989
  if (data.text) {
3901
- log("[SSE] Text chunk received (".concat(data.text.length, " chars), total chunks: ").concat(textChunks.length + 1));
3990
+ log("[SSE] Text chunk received (".concat(data.text.length, " chars), total chunks: ").concat(textChunks.length + 1, ", isReplay: ").concat(data.isReplay));
3902
3991
  textChunks.push(data.text);
3903
- // Buffer text for throttled dispatch
3904
- _this.bufferStreamText(data.text);
3992
+ if (data.isReplay) {
3993
+ // Replayed text — dispatch immediately (no throttle)
3994
+ // so the user sees caught-up content instantly
3995
+ _this.dispatchStreamChunk(messageId, data.text);
3996
+ }
3997
+ else {
3998
+ // Live text — buffer for throttled dispatch
3999
+ _this.bufferStreamText(data.text);
4000
+ }
3905
4001
  }
3906
4002
  break;
3907
4003
  case "tool_start":
@@ -4006,6 +4102,14 @@ var MCPChat = /** @class */ (function () {
4006
4102
  resolve();
4007
4103
  }
4008
4104
  break;
4105
+ case "replay_complete":
4106
+ _this.isReplaying = false;
4107
+ log("[SSE] Replay complete: replayed ".concat(data.replayedCount, " events, currentSeq: ").concat(data.currentSeq));
4108
+ break;
4109
+ case "replay_partial":
4110
+ _this.isReplaying = false;
4111
+ log("[SSE] Replay partial: some events unavailable, oldestAvailableSeq: ".concat(data.oldestAvailableSeq));
4112
+ break;
4009
4113
  case "error":
4010
4114
  hasError = true;
4011
4115
  _this.stopTyping();
@@ -4020,6 +4124,7 @@ var MCPChat = /** @class */ (function () {
4020
4124
  // Only handle error if this is still the current message
4021
4125
  if (_this.currentMessageId === messageId && !hasError) {
4022
4126
  hasError = true;
4127
+ _this.isReplaying = false;
4023
4128
  _this.stopStreamThrottle();
4024
4129
  sseError = error instanceof Error ? error : new Error(String(error));
4025
4130
  // Get status code if available
@@ -4067,9 +4172,8 @@ var MCPChat = /** @class */ (function () {
4067
4172
  this._attributes.currentUrl = window.location.href;
4068
4173
  }
4069
4174
  this._accessToken = this.visitorInfo.accessToken;
4070
- // This is for the bot
4071
- this.userJoined(this.getBot(undefined));
4072
- // Show typing indicator after bot joins if we're still waiting for initial greeting
4175
+ // Bot joined is dispatched in init() to ensure correct ordering
4176
+ // Show typing indicator if we're still waiting for initial greeting
4073
4177
  if (this.isInitializingSession) {
4074
4178
  this.typing();
4075
4179
  }
@@ -4091,20 +4195,40 @@ var MCPChat = /** @class */ (function () {
4091
4195
  };
4092
4196
  MCPChat.prototype.sleep = function () {
4093
4197
  this.stopStreamThrottle();
4198
+ this.isReplaying = false;
4094
4199
  if (this.abortController) {
4095
4200
  this.abortController.abort();
4096
4201
  this.abortController = undefined;
4097
4202
  }
4098
4203
  };
4099
- MCPChat.prototype.wakeup = function () { };
4204
+ MCPChat.prototype.wakeup = function () {
4205
+ // When tab is foregrounded, just restore connection status.
4206
+ // Don't validate/replay yet — the session will be validated
4207
+ // on the next user message. This avoids creating duplicate
4208
+ // greetings if validation fails transiently.
4209
+ if (this._sessionId) {
4210
+ log("[MCPChat] Wakeup - session exists, lastSeq: ".concat(this.lastSeq));
4211
+ this.setConnectionStatus("online");
4212
+ }
4213
+ };
4100
4214
  MCPChat.prototype.startSession = function (sessionId) {
4101
- if (this.visitorInfo.visitorId) {
4215
+ // userId priority:
4216
+ // 1. Existing server-assigned userId (from localStorage or previous connected event)
4217
+ // 2. Explicitly provided visitorId or email from host app
4218
+ // 3. Generate a new one as last resort
4219
+ if (this.userIdIsServerAssigned) {
4220
+ // Keep server-assigned userId — don't overwrite with default fingerprint
4221
+ log("[startSession] Preserving server-assigned userId: ".concat(this._userId));
4222
+ }
4223
+ else if (this.visitorInfo.visitorId) {
4102
4224
  this._userId = "".concat(this.visitorInfo.visitorId);
4103
4225
  }
4104
4226
  else if (this.visitorInfo.email) {
4105
4227
  this._userId = "mcp-widget-user-".concat(this.visitorInfo.email);
4106
4228
  }
4107
- else {
4229
+ else if (!this._userId) {
4230
+ // Only generate a new userId if we don't already have one
4231
+ // (preserves userId loaded from localStorage or received from server)
4108
4232
  this._userId = "mcp-widget-user-".concat(uuid_1());
4109
4233
  }
4110
4234
  // Session ID priority:
@@ -6267,7 +6391,7 @@ function createPacketDecoderStream(maxPayload, binaryType) {
6267
6391
  },
6268
6392
  });
6269
6393
  }
6270
- const protocol$1 = 4;
6394
+ const protocol = 4;
6271
6395
 
6272
6396
  /**
6273
6397
  * Expose `Emitter`.
@@ -7543,7 +7667,7 @@ class SocketWithoutUpgrade extends Emitter_1 {
7543
7667
  createTransport(name) {
7544
7668
  const query = Object.assign({}, this.opts.query);
7545
7669
  // append engine.io protocol identifier
7546
- query.EIO = protocol$1;
7670
+ query.EIO = protocol;
7547
7671
  // transport name
7548
7672
  query.transport = name;
7549
7673
  // session id if we already have one
@@ -7918,7 +8042,7 @@ class SocketWithoutUpgrade extends Emitter_1 {
7918
8042
  }
7919
8043
  }
7920
8044
  }
7921
- SocketWithoutUpgrade.protocol = protocol$1;
8045
+ SocketWithoutUpgrade.protocol = protocol;
7922
8046
  /**
7923
8047
  * This class provides a WebSocket-like interface to connect to an Engine.IO server. The connection will be established
7924
8048
  * with one of the available low-level transports, like HTTP long-polling, WebSocket or WebTransport.
@@ -8314,12 +8438,6 @@ const RESERVED_EVENTS$1 = [
8314
8438
  "newListener",
8315
8439
  "removeListener", // used by the Node.js EventEmitter
8316
8440
  ];
8317
- /**
8318
- * Protocol version.
8319
- *
8320
- * @public
8321
- */
8322
- const protocol = 5;
8323
8441
  var PacketType;
8324
8442
  (function (PacketType) {
8325
8443
  PacketType[PacketType["CONNECT"] = 0] = "CONNECT";
@@ -8616,8 +8734,7 @@ var parser = /*#__PURE__*/Object.freeze({
8616
8734
  __proto__: null,
8617
8735
  Decoder: Decoder,
8618
8736
  Encoder: Encoder,
8619
- get PacketType () { return PacketType; },
8620
- protocol: protocol
8737
+ get PacketType () { return PacketType; }
8621
8738
  });
8622
8739
 
8623
8740
  function on(obj, ev, fn) {