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