@ada-support/embed2 1.13.6 → 1.14.4

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.
@@ -15919,7 +15919,7 @@ const client = new BrowserClient({
15919
15919
  return event;
15920
15920
  },
15921
15921
  environment: "production",
15922
- release: "1.13.6-406ab08",
15922
+ release: "1.14.4-57d2690",
15923
15923
  sampleRate: 0.25,
15924
15924
  autoSessionTracking: false,
15925
15925
  // Integrations don't seem to work with Sentry: https://github.com/getsentry/sentry-javascript/issues/2541
@@ -16019,6 +16019,8 @@ const GET_INFO = "GET_INFO";
16019
16019
  const GET_INFO_RESPONSE = "GET_INFO_RESPONSE";
16020
16020
  const SET_META_FIELDS = "SET_META_FIELDS";
16021
16021
  const SET_META_FIELDS_RESPONSE = "SET_META_FIELDS_RESPONSE";
16022
+ const GET_META_FIELDS = "GET_META_FIELDS";
16023
+ const GET_META_FIELDS_RESPONSE = "GET_META_FIELDS_RESPONSE";
16022
16024
  const STOP = "STOP";
16023
16025
  const STOP_RESPONSE = "STOP_RESPONSE";
16024
16026
  const DELETE_HISTORY = "DELETE_HISTORY";
@@ -16058,6 +16060,12 @@ let FetchEventStatus = /*#__PURE__*/function (FetchEventStatus) {
16058
16060
 
16059
16061
 
16060
16062
 
16063
+ // MES-981 P1 #6 follow-up: request events whose responses are surfaced
16064
+ // directly to host-page callers. For these, channel.fetch() differentiates
16065
+ // timeouts (resolves to undefined) from other failures (re-throws with a
16066
+ // descriptive message) so the caller's "response === undefined" check
16067
+ // always means "timeout" specifically, not "something else went wrong".
16068
+ const PRIVILEGED_FETCH_EVENTS = new Set(["SEND_MESSAGE", "SET_COMPOSER_TEXT", "GET_MESSAGES", "GET_CONVERSATION"]);
16061
16069
  class Channel {
16062
16070
  constructor() {
16063
16071
  _defineProperty(this, "trackedListeners", void 0);
@@ -16111,6 +16119,21 @@ class Channel {
16111
16119
  throw new AdaEmbedError(`Failed to respond to "GET_STATE" request. Reason: "${e.message}".`);
16112
16120
  }
16113
16121
 
16122
+ // MES-981 P1 #6 follow-up: privileged transcript-bearing request/
16123
+ // response pairs must distinguish timeouts from other failures.
16124
+ // Without this, the "if (response === undefined)" check in
16125
+ // interface.ts throws a misleading "… timed out" error even when
16126
+ // the real failure was a payload-shape rejection or a handler
16127
+ // exception. Re-throw on any non-timeout error so the caller sees
16128
+ // the actual reason.
16129
+ if (PRIVILEGED_FETCH_EVENTS.has(requestEvent)) {
16130
+ if (e instanceof Error && e.message === "Could not connect frame channel.") {
16131
+ // genuine timeout — fall through, resolves to undefined
16132
+ } else {
16133
+ throw new AdaEmbedError(`"${requestEvent}" failed: ${e instanceof Error ? e.message : String(e)}`);
16134
+ }
16135
+ }
16136
+
16114
16137
  // handling promise
16115
16138
  if (e.message !== "Could not connect frame channel.") {
16116
16139
  error_tracker.trackException(e);
@@ -16395,14 +16418,25 @@ function subscribeEvent(eventKey, callback) {
16395
16418
  });
16396
16419
  return id;
16397
16420
  }
16421
+
16422
+ // MES-981 P2 #11: events that carry transcript bodies (or other
16423
+ // sensitive payloads) must match exactly, never via prefix. Prefix
16424
+ // subscriptions to `ada:` would otherwise leak `ada:message:sent` and
16425
+ // `ada:message:received` to anyone broadly subscribed to the `ada:`
16426
+ // namespace.
16427
+ const EXACT_MATCH_EVENT_KEYS = new Set(["ada:message:sent", "ada:message:received"]);
16398
16428
  function publishEvent(eventKey, data) {
16399
16429
  subscriptions.forEach(subscription => {
16400
- // Using startsWith instead of === allows subscribing to a specific event OR a namespace,
16401
- // e.g. ada:campaigns subscribes to ada:campaigns:opened, ada:campaigns:engaged, and
16402
- // ada:campaigns:shown. Broadcasting this way simultaneously reaches subscriptions
16403
- // that have subscribed to a specific event, and those that have subscribed to a namespace
16404
- if (eventKey.startsWith(subscription.eventKey)) {
16405
- // try/catch to guard against missing or bad callbacks
16430
+ const matches = EXACT_MATCH_EVENT_KEYS.has(eventKey) ?
16431
+ // Sensitive events require an exact subscription key match —
16432
+ // never deliver via a prefix subscription to e.g. "ada:" or
16433
+ // "ada:message".
16434
+ subscription.eventKey === eventKey :
16435
+ // Prefix match remains the default so subscribers can listen to
16436
+ // a namespace like `ada:campaigns` and catch
16437
+ // `ada:campaigns:opened` / `:engaged` / `:shown`.
16438
+ eventKey.startsWith(subscription.eventKey);
16439
+ if (matches) {
16406
16440
  try {
16407
16441
  subscription.callback(data, {
16408
16442
  eventKey
@@ -16509,7 +16543,7 @@ function getEmbedURL(_ref) {
16509
16543
  } else {
16510
16544
  host = `http://${handle}.localhost:${ports.localhost.default}`;
16511
16545
  }
16512
- return `${host}/embed/${frameName}/${"406ab08"}/index.html`;
16546
+ return `${host}/embed/${frameName}/${"57d2690"}/index.html`;
16513
16547
  }
16514
16548
  function constructQueryString(query) {
16515
16549
  return Object.keys(query).map(key => {
@@ -16720,6 +16754,15 @@ const mutations = (state, action) => {
16720
16754
  switch (action.type) {
16721
16755
  case ActionTypes.TOGGLE_CHAT_TYPE:
16722
16756
  {
16757
+ // MES-981 P1 #9 follow-up: defensive guard against future accidental
16758
+ // TOGGLE_CHAT_ACTION dispatches in headless mode. The host page has
16759
+ // no drawer to toggle, so flipping isDrawerOpen / drawerHasBeenOpened
16760
+ // and persisting `ada-embed_is-drawer-open: true` to localStorage
16761
+ // would leak across reloads with no visible effect. start() doesn't
16762
+ // dispatch this in headless any more, but the mutation stays robust.
16763
+ if (state.adaSettings.headless) {
16764
+ return state;
16765
+ }
16723
16766
  let {
16724
16767
  hasChatOpenedAfterProactiveMessagesShown
16725
16768
  } = state;
@@ -16994,11 +17037,192 @@ const ZD_MESSAGING_CHATTER_CREATED_STORAGE_KEY = "ada-embed_zd-messaging-chatter
16994
17037
 
16995
17038
  /** @deprecated - Use CHATTER_TOKEN_STORAGE_KEY */
16996
17039
  const CHATTER_STORAGE_KEY = (/* unused pure expression or super */ null && (CHATTER_TOKEN_STORAGE_KEY));
17040
+ ;// CONCATENATED MODULE: ./src/common/helpers/messaging-auth-state.ts
17041
+
17042
+
17043
+ // Single static key, shared verbatim with messaging + chat and disclosed by
17044
+ // @ada-support/web-storage. Per-bot scoping is implicit in the bot-subdomain
17045
+ // storage origin this runs on, so no apiOrigin/handle-scoped key is needed.
17046
+ const MESSAGING_AUTH_STATE_STORAGE_KEY = "messagingAuthState";
17047
+ function isRecord(value) {
17048
+ return typeof value === "object" && value !== null;
17049
+ }
17050
+
17051
+ // Typed @ada-support/web-storage wrapper for the chosen persistence provider, or
17052
+ // null when persistence is disabled / there is no window. The wrapper is what
17053
+ // puts `messagingAuthState` on the published web-storage disclosure list and
17054
+ // serializes the value, so callers pass/get a typed object rather than a string.
17055
+ function getMessagingAuthStorage(persistence) {
17056
+ if (persistence === "private" || typeof window === "undefined") {
17057
+ return null;
17058
+ }
17059
+ return persistence === "session" ? dist/* adaSessionStorage */.ad : dist/* adaLocalStorage */.BB;
17060
+ }
17061
+ function getApiOrigin(_ref) {
17062
+ let {
17063
+ handle,
17064
+ cluster,
17065
+ domain
17066
+ } = _ref;
17067
+ try {
17068
+ return new window.URL(getURL({
17069
+ name: "api",
17070
+ handle,
17071
+ cluster,
17072
+ domain
17073
+ }), window.location.origin).origin;
17074
+ } catch (error) {
17075
+ return null;
17076
+ }
17077
+ }
17078
+ function buildMessagingAuthStateStorageKey(_ref2) {
17079
+ let {
17080
+ handle,
17081
+ cluster,
17082
+ domain
17083
+ } = _ref2;
17084
+ // The key itself is static; the handle/origin now only gate whether there is a
17085
+ // resolvable session to read/write (preserving the previous no-op behavior
17086
+ // before a handle/origin is known) rather than scoping the key.
17087
+ if (!handle || !getApiOrigin({
17088
+ handle,
17089
+ cluster,
17090
+ domain
17091
+ })) {
17092
+ return null;
17093
+ }
17094
+ return MESSAGING_AUTH_STATE_STORAGE_KEY;
17095
+ }
17096
+ function parseMessagingAuthState(value) {
17097
+ let parsedValue = value;
17098
+ if (typeof value === "string") {
17099
+ try {
17100
+ parsedValue = JSON.parse(value);
17101
+ } catch (error) {
17102
+ return null;
17103
+ }
17104
+ }
17105
+ if (!isRecord(parsedValue)) {
17106
+ return null;
17107
+ }
17108
+ const {
17109
+ messagingToken,
17110
+ messagingTokenExpiresAt,
17111
+ sessionRefreshToken,
17112
+ refreshGeneration
17113
+ } = parsedValue;
17114
+ if (typeof messagingToken !== "string" || !messagingToken || typeof messagingTokenExpiresAt !== "number" || !Number.isFinite(messagingTokenExpiresAt) || messagingTokenExpiresAt <= 0 || typeof sessionRefreshToken !== "string" || !sessionRefreshToken || typeof refreshGeneration !== "number" || !Number.isFinite(refreshGeneration)) {
17115
+ return null;
17116
+ }
17117
+ return {
17118
+ messagingToken,
17119
+ messagingTokenExpiresAt,
17120
+ sessionRefreshToken,
17121
+ refreshGeneration
17122
+ };
17123
+ }
17124
+
17125
+ // Storage-blocked browsers can throw on the storage methods, so each access is
17126
+ // wrapped to fall back gracefully rather than throw into callers.
17127
+ function safeReadMessagingAuthState(persistence) {
17128
+ try {
17129
+ var _getMessagingAuthStor;
17130
+ return parseMessagingAuthState(((_getMessagingAuthStor = getMessagingAuthStorage(persistence)) === null || _getMessagingAuthStor === void 0 ? void 0 : _getMessagingAuthStor.getItem(MESSAGING_AUTH_STATE_STORAGE_KEY)) ?? null);
17131
+ } catch (error) {
17132
+ return null;
17133
+ }
17134
+ }
17135
+ function readMessagingAuthStateFromStorage(_ref3) {
17136
+ let {
17137
+ handle,
17138
+ cluster,
17139
+ domain
17140
+ } = _ref3;
17141
+ if (!buildMessagingAuthStateStorageKey({
17142
+ handle,
17143
+ cluster,
17144
+ domain
17145
+ })) {
17146
+ return undefined;
17147
+ }
17148
+ const persistedAuthStates = ["normal", "session"].map(persistence => safeReadMessagingAuthState(persistence)).filter(authState => authState !== null);
17149
+ return persistedAuthStates.reduce((highestState, authState) => !highestState || authState.refreshGeneration > highestState.refreshGeneration ? authState : highestState, undefined);
17150
+ }
17151
+ function writeMessagingAuthStateToStorage(_ref4) {
17152
+ let {
17153
+ handle,
17154
+ cluster,
17155
+ domain,
17156
+ persistence,
17157
+ state
17158
+ } = _ref4;
17159
+ const storageKey = buildMessagingAuthStateStorageKey({
17160
+ handle,
17161
+ cluster,
17162
+ domain
17163
+ });
17164
+ const storage = getMessagingAuthStorage(persistence);
17165
+ if (!storageKey || !storage || !state) {
17166
+ return false;
17167
+ }
17168
+ try {
17169
+ const existing = readMessagingAuthStateFromStorage({
17170
+ handle,
17171
+ cluster,
17172
+ domain
17173
+ });
17174
+ if (existing && existing.refreshGeneration >= state.refreshGeneration) {
17175
+ return false;
17176
+ }
17177
+ storage.setItem(storageKey, state);
17178
+ return true;
17179
+ } catch (error) {
17180
+ return false;
17181
+ }
17182
+ }
17183
+ function clearAllMessagingAuthStatesFromStorage() {
17184
+ ["normal", "session"].forEach(persistence => {
17185
+ try {
17186
+ var _getMessagingAuthStor2;
17187
+ (_getMessagingAuthStor2 = getMessagingAuthStorage(persistence)) === null || _getMessagingAuthStor2 === void 0 ? void 0 : _getMessagingAuthStor2.removeItem(MESSAGING_AUTH_STATE_STORAGE_KEY);
17188
+ } catch (error) {
17189
+ // Storage blocked; nothing to clear.
17190
+ }
17191
+ });
17192
+ }
17193
+
17194
+ // Removes the messaging auth state (across both persistence providers). Used when
17195
+ // a bot rotates to a new chatter. With the bot-subdomain storage origin there is a
17196
+ // single auth state per origin, so this and `clearAllMessagingAuthStatesFromStorage`
17197
+ // now clear the same key.
17198
+ function clearMessagingAuthStateFromStorage(_ref5) {
17199
+ let {
17200
+ handle,
17201
+ cluster,
17202
+ domain
17203
+ } = _ref5;
17204
+ if (!buildMessagingAuthStateStorageKey({
17205
+ handle,
17206
+ cluster,
17207
+ domain
17208
+ })) {
17209
+ return;
17210
+ }
17211
+ ["normal", "session"].forEach(persistence => {
17212
+ try {
17213
+ var _getMessagingAuthStor3;
17214
+ (_getMessagingAuthStor3 = getMessagingAuthStorage(persistence)) === null || _getMessagingAuthStor3 === void 0 ? void 0 : _getMessagingAuthStor3.removeItem(MESSAGING_AUTH_STATE_STORAGE_KEY);
17215
+ } catch (error) {
17216
+ // Storage blocked; nothing to clear.
17217
+ }
17218
+ });
17219
+ }
16997
17220
  ;// CONCATENATED MODULE: ./src/client/store/state.ts
16998
17221
 
16999
17222
 
17000
17223
 
17001
17224
 
17225
+
17002
17226
  function getValueFromStorage(key) {
17003
17227
  return dist/* adaLocalStorage */.BB.getItem(key) || dist/* adaSessionStorage */.ad.getItem(key);
17004
17228
  }
@@ -17011,6 +17235,8 @@ const getInitialState = adaSettings => ({
17011
17235
  language: undefined,
17012
17236
  greeting: undefined,
17013
17237
  crossWindowPersistence: true,
17238
+ headless: false,
17239
+ enableProgrammaticControl: false,
17014
17240
  hideMask: true,
17015
17241
  metaFields: {},
17016
17242
  sensitiveMetaFields: {},
@@ -17031,6 +17257,11 @@ const getInitialState = adaSettings => ({
17031
17257
  chatterToken: getValueFromStorage(CHATTER_TOKEN_STORAGE_KEY) || undefined,
17032
17258
  chatterCreated: getValueFromStorage(CHATTER_CREATED_STORAGE_KEY) || undefined,
17033
17259
  sessionToken: getValueFromStorage(SESSION_AUTH_TOKEN_STORAGE_KEY) || undefined,
17260
+ messagingAuthState: readMessagingAuthStateFromStorage({
17261
+ handle: adaSettings.handle,
17262
+ cluster: adaSettings.cluster,
17263
+ domain: adaSettings.domain
17264
+ }),
17034
17265
  zdSessionId: getValueFromStorage(ZD_SESSION_STORAGE_KEY) || undefined,
17035
17266
  zdPreviousTags: getValueFromStorage(ZD_PREVIOUS_TAGS_STORAGE_KEY) || undefined,
17036
17267
  zdMessagingExternalUserId: getValueFromStorage(ZD_MESSAGING_EXTERNAL_USER_ID_STORAGE_KEY) || null,
@@ -17052,6 +17283,58 @@ const getInitialState = adaSettings => ({
17052
17283
  deviceToken: null,
17053
17284
  showFallbackOnTimeout: true
17054
17285
  });
17286
+ // EXTERNAL MODULE: ./node_modules/lodash.memoize/index.js
17287
+ var lodash_memoize = __webpack_require__(7654);
17288
+ var lodash_memoize_default = /*#__PURE__*/__webpack_require__.n(lodash_memoize);
17289
+ ;// CONCATENATED MODULE: ./src/services/logger/index.ts
17290
+
17291
+ function logger_ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
17292
+ function logger_objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? logger_ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : logger_ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
17293
+
17294
+
17295
+ const DEFAULT_LOG_SAMPLE_RATE = 0.01;
17296
+ const DD_BASE_URL = "https://browser-http-intake.logs.datadoghq.com/v1/input/";
17297
+ const DD_TOKEN = "pubfe23baedd2ea322bebb5ed2020fa2fa1";
17298
+ let globalLogContext = {};
17299
+ function setGlobalLogContext(context) {
17300
+ globalLogContext = logger_objectSpread({}, context);
17301
+ }
17302
+ function clearGlobalLogContext() {
17303
+ globalLogContext = {};
17304
+ }
17305
+
17306
+ // We need memoization to ensure consistency of logs
17307
+ const shouldLog = lodash_memoize_default()(sampleRate => {
17308
+ if (!DD_TOKEN || sampleRate === 0) {
17309
+ return false;
17310
+ }
17311
+ return Math.random() < (sampleRate || DEFAULT_LOG_SAMPLE_RATE);
17312
+ });
17313
+
17314
+ /**
17315
+ * Sends log to Datadog
17316
+ */
17317
+ async function log(message, extra, options) {
17318
+ if (!shouldLog(options === null || options === void 0 ? void 0 : options.sampleRate)) {
17319
+ return;
17320
+ }
17321
+ await httpRequest({
17322
+ url: `${DD_BASE_URL}${DD_TOKEN}?ddsource=browser&ddtags=version:1.5.0,env:${"production"}`,
17323
+ method: "POST",
17324
+ body: JSON.stringify(logger_objectSpread(logger_objectSpread(logger_objectSpread({
17325
+ message
17326
+ }, extra), globalLogContext), {}, {
17327
+ level: (options === null || options === void 0 ? void 0 : options.level) || "info",
17328
+ sampleRate: (options === null || options === void 0 ? void 0 : options.sampleRate) || DEFAULT_LOG_SAMPLE_RATE,
17329
+ service: "embed",
17330
+ env: "production",
17331
+ embedVersion: 2,
17332
+ version: "1.14.4",
17333
+ isNpm: true,
17334
+ commitHash: "57d2690"
17335
+ }))
17336
+ });
17337
+ }
17055
17338
  ;// CONCATENATED MODULE: ./src/common/helpers/get-intro-for-url.ts
17056
17339
  function getProcessedPath(path) {
17057
17340
  const regex = /^http(s)?:\/\/(www.)?/;
@@ -17651,58 +17934,6 @@ function getButtonText(client) {
17651
17934
  }
17652
17935
  return buttonTextMap[languageKey];
17653
17936
  }
17654
- // EXTERNAL MODULE: ./node_modules/lodash.memoize/index.js
17655
- var lodash_memoize = __webpack_require__(7654);
17656
- var lodash_memoize_default = /*#__PURE__*/__webpack_require__.n(lodash_memoize);
17657
- ;// CONCATENATED MODULE: ./src/services/logger/index.ts
17658
-
17659
- function logger_ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
17660
- function logger_objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? logger_ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : logger_ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
17661
-
17662
-
17663
- const DEFAULT_LOG_SAMPLE_RATE = 0.01;
17664
- const DD_BASE_URL = "https://browser-http-intake.logs.datadoghq.com/v1/input/";
17665
- const DD_TOKEN = "pubfe23baedd2ea322bebb5ed2020fa2fa1";
17666
- let globalLogContext = {};
17667
- function setGlobalLogContext(context) {
17668
- globalLogContext = logger_objectSpread({}, context);
17669
- }
17670
- function clearGlobalLogContext() {
17671
- globalLogContext = {};
17672
- }
17673
-
17674
- // We need memoization to ensure consistency of logs
17675
- const shouldLog = lodash_memoize_default()(sampleRate => {
17676
- if (!DD_TOKEN || sampleRate === 0) {
17677
- return false;
17678
- }
17679
- return Math.random() < (sampleRate || DEFAULT_LOG_SAMPLE_RATE);
17680
- });
17681
-
17682
- /**
17683
- * Sends log to Datadog
17684
- */
17685
- async function log(message, extra, options) {
17686
- if (!shouldLog(options === null || options === void 0 ? void 0 : options.sampleRate)) {
17687
- return;
17688
- }
17689
- await httpRequest({
17690
- url: `${DD_BASE_URL}${DD_TOKEN}?ddsource=browser&ddtags=version:1.5.0,env:${"production"}`,
17691
- method: "POST",
17692
- body: JSON.stringify(logger_objectSpread(logger_objectSpread(logger_objectSpread({
17693
- message
17694
- }, extra), globalLogContext), {}, {
17695
- level: (options === null || options === void 0 ? void 0 : options.level) || "info",
17696
- sampleRate: (options === null || options === void 0 ? void 0 : options.sampleRate) || DEFAULT_LOG_SAMPLE_RATE,
17697
- service: "embed",
17698
- env: "production",
17699
- embedVersion: 2,
17700
- version: "1.13.6",
17701
- isNpm: true,
17702
- commitHash: "406ab08"
17703
- }))
17704
- });
17705
- }
17706
17937
  ;// CONCATENATED MODULE: ./src/client/helpers/alternative-bot-rollout/index.ts
17707
17938
 
17708
17939
  function alternative_bot_rollout_ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
@@ -19263,6 +19494,12 @@ function deriveMessagingMobileShell(mobilePackage) {
19263
19494
  function deriveLegacyMobileShell(surface, hostPlatform) {
19264
19495
  return surface === "mobile" && hostPlatform !== "web" ? "legacy-sdk" : undefined;
19265
19496
  }
19497
+
19498
+ // MES-981 P1 #10: extracted to keep resolveHostTelemetryFromStartOptions
19499
+ // under the sonarjs cognitive-complexity threshold.
19500
+ function deriveMode(adaSettings) {
19501
+ return adaSettings.headless ? "headless" : undefined;
19502
+ }
19266
19503
  function normalizeHostTelemetry(value) {
19267
19504
  const record = asRecord(value);
19268
19505
  if (!record) {
@@ -19345,11 +19582,14 @@ function resolveHostTelemetryFromStartOptions(adaSettings) {
19345
19582
  const mobilePackage = explicit === null || explicit === void 0 ? void 0 : explicit.mobilePackage;
19346
19583
  const mobileVersion = explicit === null || explicit === void 0 ? void 0 : explicit.mobileVersion;
19347
19584
  const mobileShell = deriveMessagingMobileShell(mobilePackage) ?? (explicit === null || explicit === void 0 ? void 0 : explicit.mobileShell) ?? (legacyFingerprint === null || legacyFingerprint === void 0 ? void 0 : legacyFingerprint.mobileShell) ?? (!hasExplicitHostTelemetry ? deriveLegacyMobileShell(surface, hostPlatform) : undefined);
19348
- return host_telemetry_objectSpread(host_telemetry_objectSpread(host_telemetry_objectSpread({
19585
+ const mode = deriveMode(adaSettings);
19586
+ return host_telemetry_objectSpread(host_telemetry_objectSpread(host_telemetry_objectSpread(host_telemetry_objectSpread({
19349
19587
  surface,
19350
19588
  hostPlatform,
19351
19589
  webSdkOrigin
19352
- }, mobileShell ? {
19590
+ }, mode ? {
19591
+ mode
19592
+ } : {}), mobileShell ? {
19353
19593
  mobileShell
19354
19594
  } : {}), mobilePackage ? {
19355
19595
  mobilePackage
@@ -19367,7 +19607,11 @@ function toMonitoringContext(hostTelemetry) {
19367
19607
  return host_telemetry_objectSpread(host_telemetry_objectSpread(host_telemetry_objectSpread({
19368
19608
  surface: hostTelemetry.surface,
19369
19609
  host_platform: hostTelemetry.hostPlatform,
19370
- web_sdk_origin: hostTelemetry.webSdkOrigin
19610
+ web_sdk_origin: hostTelemetry.webSdkOrigin,
19611
+ // MES-981 P1 #10: every log/RUM line carries `mode`; default to
19612
+ // "visible" when not set on the context so dashboards can filter
19613
+ // by mode without a separate query.
19614
+ mode: hostTelemetry.mode ?? "visible"
19371
19615
  }, hostTelemetry.mobileShell ? {
19372
19616
  mobile_shell: hostTelemetry.mobileShell
19373
19617
  } : {}), hostTelemetry.mobilePackage ? {
@@ -19378,23 +19622,101 @@ function toMonitoringContext(hostTelemetry) {
19378
19622
  }
19379
19623
  ;// CONCATENATED MODULE: ./src/common/helpers/is-mobile.ts
19380
19624
  const isMobile = /(iPhone)|(iPod)|(android)|(webOS)/i.exec(navigator.userAgent) !== null;
19625
+ ;// CONCATENATED MODULE: ./src/common/helpers/programmatic-control.ts
19626
+
19627
+
19628
+ // MES-981: privileged transcript-bearing surface is opt-in. Host pages
19629
+ // must explicitly accept the exposure risk via
19630
+ // `start({ enableProgrammaticControl: true })`, because every third-party
19631
+ // script in the host page (analytics, GTM, ad pixels, session replay,
19632
+ // supply-chain XSS) inherits the host's trust.
19633
+ const PROGRAMMATIC_CONTROL_DISABLED_ERROR = new AdaEmbedError("ProgrammaticControlNotEnabled: this API requires `start({ enableProgrammaticControl: true })`.");
19634
+ function isProgrammaticControlEnabled(adaSettings) {
19635
+ return Boolean(adaSettings === null || adaSettings === void 0 ? void 0 : adaSettings.enableProgrammaticControl);
19636
+ }
19637
+ function requireProgrammaticControl(adaSettings) {
19638
+ if (!isProgrammaticControlEnabled(adaSettings)) {
19639
+ throw PROGRAMMATIC_CONTROL_DISABLED_ERROR;
19640
+ }
19641
+ }
19642
+
19643
+ // ada:message:sent and ada:message:received carry user/agent transcript
19644
+ // bodies. Other ada:* events (ready, connection, conversation, typing)
19645
+ // only carry id/state/timing data and are not subject to this gate.
19646
+ const TRANSCRIPT_EVENT_KEYS = new Set(["ada:message:sent", "ada:message:received"]);
19647
+ function shouldDeliverTranscriptEvent(eventKey, adaSettings) {
19648
+ if (!TRANSCRIPT_EVENT_KEYS.has(eventKey)) return true;
19649
+ return isProgrammaticControlEnabled(adaSettings);
19650
+ }
19381
19651
  ;// CONCATENATED MODULE: ./src/client/components/ChatFrame/chatFrameEvents/chatterEvent/index.ts
19382
19652
 
19383
19653
 
19654
+
19384
19655
  function storeChatterEventDataInBrowser(client, payload) {
19385
- const {
19386
- persistence
19387
- } = client;
19656
+ let {
19657
+ cluster,
19658
+ domain,
19659
+ privateMode
19660
+ } = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
19388
19661
  const {
19389
19662
  chatter,
19390
19663
  created,
19391
- sessionToken
19664
+ sessionToken,
19665
+ messagingAuthState
19392
19666
  } = payload;
19667
+ if (privateMode) {
19668
+ return;
19669
+ }
19670
+ const {
19671
+ persistence
19672
+ } = client;
19673
+
19674
+ // A new chatter resets refreshGeneration back to 1, so the generation guard in
19675
+ // writeMessagingAuthStateToStorage would otherwise refuse to overwrite a previous
19676
+ // chatter's higher-generation (now-dead) auth state — leaving the host-page copy
19677
+ // stuck on a refresh token the backend has rotated away. Whenever the persisted
19678
+ // chatter token does not match the incoming one — including when it is absent or in
19679
+ // the other persistence provider, leaving the auth state orphaned — drop the stale
19680
+ // auth state first so the new chatter's credentials persist.
19681
+ const previousChatter = retrieveStorage(CHATTER_TOKEN_STORAGE_KEY, client, privateMode);
19682
+ if (previousChatter !== chatter) {
19683
+ clearMessagingAuthStateFromStorage({
19684
+ handle: client.handle,
19685
+ cluster,
19686
+ domain
19687
+ });
19688
+ }
19393
19689
  setBrowserStorageItem(CHATTER_TOKEN_STORAGE_KEY, chatter, persistence);
19394
19690
  setBrowserStorageItem(CHATTER_CREATED_STORAGE_KEY, created, persistence);
19395
19691
  if (sessionToken) {
19396
19692
  setBrowserStorageItem(SESSION_AUTH_TOKEN_STORAGE_KEY, sessionToken, persistence);
19397
19693
  }
19694
+
19695
+ // embed-2 only PERSISTS / mirrors the chat auth state here; it is the relay that
19696
+ // lets the chat iframe restore the JWT (e.g. across reloads). embed-2 deliberately
19697
+ // never attaches `Authorization` to its own XHR requests — adding headers to
19698
+ // embed-2's cross-origin XHR trips CORS preflight (see common/helpers/http.ts) — so
19699
+ // the chat iframe, not embed-2, is what sends `Bearer <messagingToken>`.
19700
+ const parsedAuthState = parseMessagingAuthState(messagingAuthState);
19701
+ if (parsedAuthState) {
19702
+ writeMessagingAuthStateToStorage({
19703
+ handle: client.handle,
19704
+ cluster,
19705
+ domain,
19706
+ persistence,
19707
+ state: parsedAuthState
19708
+ });
19709
+ } else {
19710
+ // chat published no usable auth state for this chatter (e.g. it cleared a dead
19711
+ // refresh token on unrecoverable invalid_refresh_token). Drop the host-side copy so
19712
+ // a stale token can't be resurrected on reload — a null write would otherwise no-op
19713
+ // and leave it in place.
19714
+ clearMessagingAuthStateFromStorage({
19715
+ handle: client.handle,
19716
+ cluster,
19717
+ domain
19718
+ });
19719
+ }
19398
19720
  }
19399
19721
  ;// CONCATENATED MODULE: ./src/client/components/ChatFrame/chatFrameEvents/index.ts
19400
19722
 
@@ -19548,6 +19870,31 @@ function getFallbackDataURI() {
19548
19870
 
19549
19871
 
19550
19872
 
19873
+
19874
+ // MES-981: route an inbound PUBLISH_EVENT from the chat iframe to the host's
19875
+ // event-subscription registry. Pulled out of handleChatEvent so its switch
19876
+ // stays under the sonarjs cognitive-complexity threshold.
19877
+ //
19878
+ // Two host-facing transforms happen here:
19879
+ // - shouldDeliverTranscriptEvent drops ada:message:* unless the host
19880
+ // opted in via enableProgrammaticControl (P0 #4).
19881
+ // - ada:ready is re-emitted with `{ mode: "headless" | "visible" }` so
19882
+ // subscribers can branch without inspecting adaSettings (P1 #10).
19883
+ function forwardPublishEvent(publishPayload, adaSettings) {
19884
+ const {
19885
+ eventKey,
19886
+ data
19887
+ } = publishPayload;
19888
+ if (!shouldDeliverTranscriptEvent(eventKey, adaSettings)) return;
19889
+ if (eventKey === "ada:ready") {
19890
+ const mode = adaSettings.headless ? "headless" : "visible";
19891
+ publishEvent(eventKey, {
19892
+ mode
19893
+ });
19894
+ return;
19895
+ }
19896
+ publishEvent(eventKey, data);
19897
+ }
19551
19898
  class ChatFrame extends preact_module_d {
19552
19899
  constructor() {
19553
19900
  super(...arguments);
@@ -19597,7 +19944,7 @@ class ChatFrame extends preact_module_d {
19597
19944
  log("Chat frame mount", {
19598
19945
  handle,
19599
19946
  chatUrl: this.url,
19600
- embedVersion: "406ab08".slice(0, 7),
19947
+ embedVersion: "57d2690".slice(0, 7),
19601
19948
  embedSettings: adaSettings
19602
19949
  });
19603
19950
 
@@ -19685,7 +20032,7 @@ class ChatFrame extends preact_module_d {
19685
20032
  const hostPageUrlParams = new URL(window.location.href).searchParams;
19686
20033
  const smsToken = hostPageUrlParams.get("adaSMSToken");
19687
20034
  const queryParams = {
19688
- embedVersion: "406ab08".slice(0, 7),
20035
+ embedVersion: "57d2690".slice(0, 7),
19689
20036
  greeting,
19690
20037
  language,
19691
20038
  skipGreeting,
@@ -20004,11 +20351,7 @@ class ChatFrame extends preact_module_d {
20004
20351
  }
20005
20352
  case PUBLISH_EVENT:
20006
20353
  {
20007
- const {
20008
- eventKey,
20009
- data
20010
- } = payload;
20011
- publishEvent(eventKey, data);
20354
+ forwardPublishEvent(payload, adaSettings);
20012
20355
  break;
20013
20356
  }
20014
20357
  case ANALYTICS_EVENT:
@@ -20027,12 +20370,16 @@ class ChatFrame extends preact_module_d {
20027
20370
  case CHATTER_EVENT:
20028
20371
  {
20029
20372
  const {
20030
- client
20373
+ client,
20374
+ cluster,
20375
+ domain,
20376
+ privateMode
20031
20377
  } = this.props;
20032
20378
  const {
20033
20379
  chatter,
20034
20380
  created,
20035
- sessionToken
20381
+ sessionToken,
20382
+ messagingAuthState
20036
20383
  } = payload;
20037
20384
  const {
20038
20385
  chatterTokenCallback
@@ -20040,14 +20387,19 @@ class ChatFrame extends preact_module_d {
20040
20387
  if (!client) {
20041
20388
  throw new AdaEmbedError("`client` is undefined");
20042
20389
  }
20043
- storeChatterEventDataInBrowser(client, payload);
20390
+ storeChatterEventDataInBrowser(client, payload, {
20391
+ cluster,
20392
+ domain,
20393
+ privateMode
20394
+ });
20044
20395
  if (chatterTokenCallback) {
20045
20396
  chatterTokenCallback(chatter);
20046
20397
  }
20047
20398
  await setGlobalState({
20048
20399
  chatterToken: chatter,
20049
20400
  chatterCreated: created,
20050
- sessionToken
20401
+ sessionToken,
20402
+ messagingAuthState
20051
20403
  });
20052
20404
  break;
20053
20405
  }
@@ -20658,10 +21010,37 @@ class XStorageFrame extends preact_module_d {
20658
21010
  }
20659
21011
  }
20660
21012
  /* harmony default export */ var components_XStorageFrame = (connect()(XStorageFrame));
21013
+ ;// CONCATENATED MODULE: ./src/client/components/Container/host-facing-meta-fields.ts
21014
+
21015
+ function host_facing_meta_fields_ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
21016
+ function host_facing_meta_fields_objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? host_facing_meta_fields_ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : host_facing_meta_fields_ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
21017
+ // MES-981 metaFields helpers. Extracted from Container/index.tsx so the
21018
+ // transforms don't push that already-large module over its max-lines budget.
21019
+
21020
+ /** Strips the internal `proactive_conversation` tracker for getMetaFields(). */
21021
+ function toHostFacingMetaFields(metaFields) {
21022
+ const hostFacing = host_facing_meta_fields_objectSpread({}, metaFields);
21023
+ delete hostFacing.proactive_conversation;
21024
+ return hostFacing;
21025
+ }
21026
+
21027
+ /** Merges a proactive_conversation key onto existing metaFields. */
21028
+ function withProactiveKey(metaFields, key) {
21029
+ return host_facing_meta_fields_objectSpread(host_facing_meta_fields_objectSpread({}, metaFields), {}, {
21030
+ proactive_conversation: key
21031
+ });
21032
+ }
20661
21033
  ;// CONCATENATED MODULE: ./src/client/components/Container/index.tsx
20662
21034
 
20663
21035
  function Container_ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
20664
21036
  function Container_objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? Container_ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : Container_ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
21037
+ /* eslint-disable max-lines -- MES-981: file was already at the 1000-line
21038
+ max-lines cap on main; the additive programmatic-control work pushes it
21039
+ just over. A targeted refactor of this Container is being tracked
21040
+ separately and is out of scope for this PR. */
21041
+
21042
+
21043
+
20665
21044
 
20666
21045
 
20667
21046
 
@@ -20740,11 +21119,11 @@ class Container extends preact_module_d {
20740
21119
  adaSettings
20741
21120
  });
20742
21121
 
20743
- // Nullify any previous conversation's proactive_conversation metavariable value
21122
+ // MES-981: seed state.metaFields with host-supplied values + reset tracker.
20744
21123
  setGlobalState({
20745
- metaFields: {
21124
+ metaFields: Container_objectSpread(Container_objectSpread({}, adaSettings.metaFields ?? {}), {}, {
20746
21125
  proactive_conversation: null
20747
- }
21126
+ })
20748
21127
  });
20749
21128
 
20750
21129
  // We want to make sure this happens before any publishEvent calls
@@ -20853,7 +21232,9 @@ class Container extends preact_module_d {
20853
21232
  // campaigns, we don't need this for engage (response_id) campaigns
20854
21233
  campaignToTrigger.response_id || appConnectionState === ConnectionState.Done) && !isDrawerOpen && !chatterInLiveChat) {
20855
21234
  triggerCampaignImpl({
20856
- adaSettings,
21235
+ adaSettings: Container_objectSpread(Container_objectSpread({}, adaSettings), {}, {
21236
+ handle: (client === null || client === void 0 ? void 0 : client.handle) || adaSettings.handle
21237
+ }),
20857
21238
  chatterToken,
20858
21239
  campaign: campaignToTrigger,
20859
21240
  ignoreStatus: Boolean(campaignTriggerOptions.ignoreStatus),
@@ -20901,12 +21282,11 @@ class Container extends preact_module_d {
20901
21282
  if (isChatWebsocketConnected && isDrawerOpen && recentProactiveConversationConfig) {
20902
21283
  const chatChannel = messageService.getChannel(CHAT_IFRAME);
20903
21284
  if (chatChannel) {
20904
- // Set meta field to indicate that a proactive conversation has been injected
20905
- // This can be used by the AI manager to customize the greeting
21285
+ const {
21286
+ metaFields
21287
+ } = this.props;
20906
21288
  setGlobalState({
20907
- metaFields: {
20908
- proactive_conversation: recentProactiveConversationConfig.key
20909
- }
21289
+ metaFields: withProactiveKey(metaFields, recentProactiveConversationConfig.key)
20910
21290
  });
20911
21291
  chatChannel.postMessage(INJECT_PROACTIVE_CONVERSATION, {
20912
21292
  key: recentProactiveConversationConfig.key,
@@ -20961,6 +21341,13 @@ class Container extends preact_module_d {
20961
21341
  if (hideChatOverride) {
20962
21342
  return false;
20963
21343
  }
21344
+
21345
+ // MES-981: in headless mode the host page never opens the drawer, so
21346
+ // treat headless as an implicit preload to ensure the chat iframe loads
21347
+ // and registers its frame-channel handler.
21348
+ if (adaSettings.headless) {
21349
+ return true;
21350
+ }
20964
21351
  if (preload) {
20965
21352
  return true;
20966
21353
  }
@@ -21101,7 +21488,9 @@ class Container extends preact_module_d {
21101
21488
  if (!client) {
21102
21489
  throw new AdaEmbedError("`client` is not defined");
21103
21490
  }
21104
- const campaignToTrigger = getCampaignToTrigger(adaSettings, client.marketing_campaigns, params, !client.features.afm_proactive_messaging);
21491
+ const campaignToTrigger = getCampaignToTrigger(Container_objectSpread(Container_objectSpread({}, adaSettings), {}, {
21492
+ handle: client.handle
21493
+ }), client.marketing_campaigns, params, !client.features.afm_proactive_messaging);
21105
21494
  if (campaignToTrigger) {
21106
21495
  if (useDelay) {
21107
21496
  setTimeout(() => {
@@ -21238,9 +21627,13 @@ class Container extends preact_module_d {
21238
21627
  drawerHasBeenOpened,
21239
21628
  chatterToken
21240
21629
  } = this.props;
21630
+ // MES-981 P1 #9: in headless mode the UI is intentionally hidden,
21631
+ // so `isChatOpen` always returns false regardless of any drawer
21632
+ // state. Matches the contract that isOpen() returns in headless.
21633
+ const isHeadless = Boolean(adaSettings.headless);
21241
21634
  const contentToReturn = {
21242
- isDrawerOpen,
21243
- isChatOpen: isDrawerOpen || Boolean(parentElement),
21635
+ isDrawerOpen: isHeadless ? false : isDrawerOpen,
21636
+ isChatOpen: isHeadless ? false : isDrawerOpen || Boolean(parentElement),
21244
21637
  hasActiveChatter: Boolean(chatterToken),
21245
21638
  hasClosedChat: drawerHasBeenOpened && !isDrawerOpen
21246
21639
  };
@@ -21261,6 +21654,14 @@ class Container extends preact_module_d {
21261
21654
  localChannel.postMessage(SET_META_FIELDS_RESPONSE, null, id);
21262
21655
  break;
21263
21656
  }
21657
+ case GET_META_FIELDS:
21658
+ {
21659
+ const {
21660
+ metaFields
21661
+ } = this.props;
21662
+ localChannel.postMessage(GET_META_FIELDS_RESPONSE, toHostFacingMetaFields(metaFields), id);
21663
+ break;
21664
+ }
21264
21665
  case "SET_SENSITIVE_META_FIELDS":
21265
21666
  {
21266
21667
  const {
@@ -21325,11 +21726,20 @@ class Container extends preact_module_d {
21325
21726
  dist/* adaSessionStorage */.ad.removeItem(CHATTER_TOKEN_STORAGE_KEY);
21326
21727
  dist/* adaLocalStorage */.BB.removeItem(CHATTER_CREATED_STORAGE_KEY);
21327
21728
  dist/* adaSessionStorage */.ad.removeItem(CHATTER_CREATED_STORAGE_KEY);
21729
+ // Scope the clear to this bot so a sibling Ada bot on the same origin keeps its
21730
+ // own messaging auth state. adaSettings.{handle,cluster,domain} matches the key
21731
+ // storeChatterEventDataInBrowser wrote under.
21732
+ clearMessagingAuthStateFromStorage({
21733
+ handle: adaSettings.handle,
21734
+ cluster: adaSettings.cluster,
21735
+ domain: adaSettings.domain
21736
+ });
21328
21737
 
21329
21738
  // And from state
21330
21739
  newState = Container_objectSpread(Container_objectSpread({}, newState), {}, {
21331
21740
  chatterToken: undefined,
21332
- chatterCreated: undefined
21741
+ chatterCreated: undefined,
21742
+ messagingAuthState: undefined
21333
21743
  });
21334
21744
  }
21335
21745
  await setGlobalState(newState);
@@ -21359,11 +21769,19 @@ class Container extends preact_module_d {
21359
21769
  dist/* adaSessionStorage */.ad.removeItem(CHATTER_TOKEN_STORAGE_KEY);
21360
21770
  dist/* adaLocalStorage */.BB.removeItem(CHATTER_CREATED_STORAGE_KEY);
21361
21771
  dist/* adaSessionStorage */.ad.removeItem(CHATTER_CREATED_STORAGE_KEY);
21772
+ // Scope the clear to this bot so a sibling Ada bot on the same origin keeps its
21773
+ // own messaging auth state (see RESET above).
21774
+ clearMessagingAuthStateFromStorage({
21775
+ handle: adaSettings.handle,
21776
+ cluster: adaSettings.cluster,
21777
+ domain: adaSettings.domain
21778
+ });
21362
21779
 
21363
21780
  // And from state
21364
21781
  await setGlobalState({
21365
21782
  chatterToken: undefined,
21366
- chatterCreated: undefined
21783
+ chatterCreated: undefined,
21784
+ messagingAuthState: undefined
21367
21785
  });
21368
21786
  xStorageChannel.postMessage(DELETE_HISTORY_EVENT, undefined);
21369
21787
  localChannel.postMessage(DELETE_HISTORY_RESPONSE, null, id);
@@ -21594,6 +22012,15 @@ class Container extends preact_module_d {
21594
22012
  adaSettings: Container_objectSpread(Container_objectSpread({}, adaSettings), {}, {
21595
22013
  handle: assignedClient.handle
21596
22014
  }),
22015
+ // Re-read the handle-scoped messaging auth blob under the assigned bot handle.
22016
+ // getInitialState read it under the original adaSettings.handle, which differs
22017
+ // from assignedClient.handle under alternative-bot rollout, so without this the
22018
+ // host-side JWT fallback would miss the assigned bot's persisted token on reload.
22019
+ messagingAuthState: readMessagingAuthStateFromStorage({
22020
+ handle: assignedClient.handle,
22021
+ cluster: cluster,
22022
+ domain
22023
+ }),
21597
22024
  chatterInLiveChat
21598
22025
  }));
21599
22026
  } catch (e) {
@@ -21826,6 +22253,7 @@ function embed_objectSpread(e) { for (var r = 1; r < arguments.length; r++) { va
21826
22253
 
21827
22254
 
21828
22255
 
22256
+
21829
22257
  function renderApp(_ref) {
21830
22258
  let {
21831
22259
  adaSettings,
@@ -21850,6 +22278,25 @@ function renderApp(_ref) {
21850
22278
  if (!parentContainer) {
21851
22279
  throw new AdaEmbedError("`parentContainer` is null");
21852
22280
  }
22281
+
22282
+ // MES-981: in headless mode the React tree still mounts (so the websocket,
22283
+ // Redux, and FC handlers run) but the entry container is moved off-screen
22284
+ // — the host page controls its own UI on top of the programmatic surface.
22285
+ // We use absolute positioning + 0x0 + clip instead of `display: none`
22286
+ // because Chrome (and Safari) defer iframe loads for elements that are
22287
+ // display:none, which would block the chat iframe from ever registering
22288
+ // its frame-channel handlers.
22289
+ if (adaSettings.headless) {
22290
+ entryContainer.style.position = "absolute";
22291
+ entryContainer.style.left = "0";
22292
+ entryContainer.style.top = "0";
22293
+ entryContainer.style.width = "0";
22294
+ entryContainer.style.height = "0";
22295
+ entryContainer.style.overflow = "hidden";
22296
+ entryContainer.style.clip = "rect(0, 0, 0, 0)";
22297
+ entryContainer.style.pointerEvents = "none";
22298
+ entryContainer.setAttribute("aria-hidden", "true");
22299
+ }
21853
22300
  parentContainer.appendChild(entryContainer);
21854
22301
  M(v(StoreContext.Provider, {
21855
22302
  value: store
@@ -21899,6 +22346,31 @@ function createEmbed(adaSettings) {
21899
22346
  initializationReject: reject
21900
22347
  });
21901
22348
  });
22349
+
22350
+ // MES-981 P1 #10: emit a high-priority log entry on every headless
22351
+ // session start so ops can spot abuse patterns (compromised host site
22352
+ // silently running chatter sessions under the visiting user's token).
22353
+ // Sampled at 1.0 so we never miss one; the volume is bounded by the
22354
+ // host explicitly opting in.
22355
+ if (adaSettings.headless) {
22356
+ log("headless_session_initialized", {
22357
+ handle: adaSettings.handle,
22358
+ parent_origin: window.location.origin
22359
+ }, {
22360
+ sampleRate: 1,
22361
+ level: "info"
22362
+ });
22363
+ }
22364
+
22365
+ // MES-981 P1 #9: headless boot no longer reuses TOGGLE_CHAT_ACTION.
22366
+ // The old dispatch persisted drawer-open state to localStorage and set
22367
+ // `drawerHasBeenOpened` / `focusIsOnAda` even though there is no
22368
+ // visible drawer — which then leaked across reloads. The chat iframe
22369
+ // mounts directly via `shouldRenderChatFrame` returning true for
22370
+ // headless (Container/index.tsx), and the iframe's own init drives
22371
+ // chatter creation, pusher subscription, and greeting fire. No drawer
22372
+ // state needs to flip.
22373
+
21902
22374
  const debounceCampaignTrigger = lodash_debounce_default()(async function (campaignKey) {
21903
22375
  let triggerCampaignParams = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
21904
22376
  await initialized;
@@ -21921,6 +22393,10 @@ function createEmbed(adaSettings) {
21921
22393
  await initialized;
21922
22394
  await localChannel.fetch(SET_META_FIELDS, SET_META_FIELDS_RESPONSE, options);
21923
22395
  },
22396
+ async getMetaFields() {
22397
+ await initialized;
22398
+ return await localChannel.fetch(GET_META_FIELDS, GET_META_FIELDS_RESPONSE);
22399
+ },
21924
22400
  async setSensitiveMetaFields(options) {
21925
22401
  await initialized;
21926
22402
  await localChannel.fetch("SET_SENSITIVE_META_FIELDS", "SET_SENSITIVE_META_FIELDS_RESPONSE", options);
@@ -21990,6 +22466,30 @@ function createEmbed(adaSettings) {
21990
22466
  }
21991
22467
  };
21992
22468
  }
22469
+ ;// CONCATENATED MODULE: ./src/common/helpers/with-timeout.ts
22470
+
22471
+
22472
+ /**
22473
+ * Race `promise` against a timeout. If the timeout fires first, reject
22474
+ * with an AdaEmbedError carrying `errorMessage`. If the promise settles
22475
+ * first (resolve or reject), clear the pending timer so it doesn't
22476
+ * keep the event loop alive past resolution.
22477
+ *
22478
+ * MES-981 P1 #7: used to cap host-supplied beforeSend hooks so a
22479
+ * never-resolving delegate can't hang sendMessage forever.
22480
+ */
22481
+ function withTimeout(promise, ms, errorMessage) {
22482
+ return new Promise((resolve, reject) => {
22483
+ const handle = setTimeout(() => reject(new AdaEmbedError(errorMessage)), ms);
22484
+ Promise.resolve(promise).then(value => {
22485
+ clearTimeout(handle);
22486
+ resolve(value);
22487
+ }, err => {
22488
+ clearTimeout(handle);
22489
+ reject(err);
22490
+ });
22491
+ });
22492
+ }
21993
22493
  ;// CONCATENATED MODULE: ./src/interface.ts
21994
22494
 
21995
22495
 
@@ -21998,13 +22498,56 @@ function createEmbed(adaSettings) {
21998
22498
 
21999
22499
 
22000
22500
 
22501
+
22502
+
22001
22503
  const EMBED_NOT_INITIALIZED_ERROR = new AdaEmbedError("Actions cannot be called until Embed has been instantiated. Try running `adaEmbed.start({...})`.");
22002
22504
 
22505
+ // MES-981 P1 #7: run the host's beforeSend hook with a 5s timeout, validate
22506
+ // its return value, and surface DelegateRejected / DelegateTimeout / shape
22507
+ // errors with the same wording as before. Extracted from sendMessage to
22508
+ // keep that method below the sonarjs cognitive-complexity threshold.
22509
+ async function applyBeforeSend(beforeSend, originalText) {
22510
+ let result;
22511
+ try {
22512
+ result = await withTimeout(beforeSend({
22513
+ text: originalText
22514
+ }), 5000, "DelegateTimeout");
22515
+ } catch (err) {
22516
+ if (err instanceof AdaEmbedError && err.message === "DelegateTimeout") {
22517
+ log("Delegate beforeSend timed out", {
22518
+ timeoutMs: 5000
22519
+ });
22520
+ } else {
22521
+ log("Delegate beforeSend threw", {
22522
+ error: err instanceof Error ? err.message : String(err)
22523
+ });
22524
+ }
22525
+ throw err;
22526
+ }
22527
+ if (result === false) {
22528
+ log("Delegate beforeSend canceled the send", {});
22529
+ throw new AdaEmbedError("DelegateRejected");
22530
+ }
22531
+ const candidate = result;
22532
+ if (!candidate || typeof candidate !== "object" || typeof candidate.text !== "string") {
22533
+ throw new AdaEmbedError("Delegate beforeSend must return `{ text: string }`, `false`, or a Promise resolving to one of these.");
22534
+ }
22535
+ const transformed = candidate.text;
22536
+ if (transformed.trim().length === 0) {
22537
+ throw new AdaEmbedError("sendMessage requires non-empty text after delegate transform");
22538
+ }
22539
+ log("Delegate beforeSend completed", {
22540
+ transformed: transformed !== originalText
22541
+ });
22542
+ return transformed;
22543
+ }
22544
+
22003
22545
  /**
22004
22546
  * Returns the public Embed methods to be bound to the window object.
22005
22547
  */
22006
22548
  function createEmbedInterface() {
22007
22549
  let embed = null;
22550
+ let delegate = {};
22008
22551
  const toggle = async () => {
22009
22552
  if (!embed) {
22010
22553
  throw EMBED_NOT_INITIALIZED_ERROR;
@@ -22048,6 +22591,9 @@ function createEmbedInterface() {
22048
22591
  }
22049
22592
  const stopPromise = embed.stop();
22050
22593
  embed = null;
22594
+ // MES-981: clear the registered delegate so a subsequent start() begins
22595
+ // with a clean interception state.
22596
+ delegate = {};
22051
22597
  try {
22052
22598
  await stopPromise;
22053
22599
  } finally {
@@ -22067,6 +22613,12 @@ function createEmbedInterface() {
22067
22613
  }
22068
22614
  return embed.setMetaFields(options);
22069
22615
  },
22616
+ getMetaFields: async () => {
22617
+ if (!embed) {
22618
+ throw EMBED_NOT_INITIALIZED_ERROR;
22619
+ }
22620
+ return embed.getMetaFields();
22621
+ },
22070
22622
  setSensitiveMetaFields: async options => {
22071
22623
  if (!embed) {
22072
22624
  throw EMBED_NOT_INITIALIZED_ERROR;
@@ -22079,6 +22631,20 @@ function createEmbedInterface() {
22079
22631
  }
22080
22632
  return embed.getInfo();
22081
22633
  },
22634
+ isOpen: async () => {
22635
+ if (!embed) {
22636
+ throw EMBED_NOT_INITIALIZED_ERROR;
22637
+ }
22638
+
22639
+ // MES-981: in headless mode the entry container is display:none, so
22640
+ // even a parentElement-mounted embed isn't visible to the user. Treat
22641
+ // it as "not open" regardless of the underlying isDrawerOpen flag.
22642
+ if (embed.adaSettings.headless) {
22643
+ return false;
22644
+ }
22645
+ const info = await embed.getInfo();
22646
+ return info.isChatOpen;
22647
+ },
22082
22648
  reset: async resetParams => {
22083
22649
  if (!embed) {
22084
22650
  throw EMBED_NOT_INITIALIZED_ERROR;
@@ -22120,6 +22686,12 @@ function createEmbedInterface() {
22120
22686
  }
22121
22687
  return embed.evaluateCampaignConditions(options);
22122
22688
  },
22689
+ // MES-981: the public surface accepts both the upstream AdaEventKey set
22690
+ // and the new ada:* keys (ExtendedAdaEventKey). The underlying event
22691
+ // subscription registry is a string-keyed map at runtime, so casting
22692
+ // through any is safe and lets the implementation reuse the existing
22693
+ // subscribeEvent helper.
22694
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
22123
22695
  subscribeEvent: async (eventKey, callback) => subscribeEvent(eventKey, callback),
22124
22696
  unsubscribeEvent: async id => unsubscribeEvent(id),
22125
22697
  closeCampaign: async () => {
@@ -22146,6 +22718,126 @@ function createEmbedInterface() {
22146
22718
  language
22147
22719
  });
22148
22720
  },
22721
+ setComposerText: async text => {
22722
+ if (!embed) {
22723
+ throw EMBED_NOT_INITIALIZED_ERROR;
22724
+ }
22725
+ requireProgrammaticControl(embed.adaSettings);
22726
+ if (embed.adaSettings.headless) {
22727
+ throw new AdaEmbedError("HeadlessModeError: setComposerText is not available in headless mode");
22728
+ }
22729
+
22730
+ // MES-981: wait for chat to finish init before forwarding the
22731
+ // postMessage, matching the existing behaviour of setMetaFields etc.
22732
+ await embed.initialized;
22733
+ const channel = embed.messageService.getChannel("chat");
22734
+ if (!channel) {
22735
+ throw new AdaEmbedError("Chat is not ready yet to have composer text set.");
22736
+ }
22737
+
22738
+ // MES-981 P0 #3: SET_COMPOSER_TEXT is request/response so chat-side
22739
+ // validation failures (INVALID_PAYLOAD, TOO_LONG) reach the caller.
22740
+ const response = await channel.fetch("SET_COMPOSER_TEXT", "SET_COMPOSER_TEXT_RESPONSE", {
22741
+ text
22742
+ });
22743
+ if (!response) {
22744
+ throw new AdaEmbedError("setComposerText timed out — chat frame did not respond in time.");
22745
+ }
22746
+ if ("error" in response) {
22747
+ throw new AdaEmbedError(`setComposerText rejected by chat: ${response.error}`);
22748
+ }
22749
+ },
22750
+ getConversation: async () => {
22751
+ if (!embed) {
22752
+ throw EMBED_NOT_INITIALIZED_ERROR;
22753
+ }
22754
+
22755
+ // MES-981 P0 #4: getConversation returns the live-agent handoff
22756
+ // identity (id / name / avatarUrl) alongside the conversation id.
22757
+ // Same host-page exposure surface as ada:message:* and getMessages,
22758
+ // so the same opt-in gate applies.
22759
+ requireProgrammaticControl(embed.adaSettings);
22760
+ await embed.initialized;
22761
+ const channel = embed.messageService.getChannel("chat");
22762
+ if (!channel) {
22763
+ throw new AdaEmbedError("Chat is not ready yet to read conversation.");
22764
+ }
22765
+ const response = await channel.fetch("GET_CONVERSATION", "GET_CONVERSATION_RESPONSE");
22766
+ if (!response) {
22767
+ throw new AdaEmbedError("getConversation timed out — chat frame did not respond in time.");
22768
+ }
22769
+ return response;
22770
+ },
22771
+ getMessages: async () => {
22772
+ if (!embed) {
22773
+ throw EMBED_NOT_INITIALIZED_ERROR;
22774
+ }
22775
+ requireProgrammaticControl(embed.adaSettings);
22776
+ await embed.initialized;
22777
+ const channel = embed.messageService.getChannel("chat");
22778
+ if (!channel) {
22779
+ throw new AdaEmbedError("Chat is not ready yet to read messages.");
22780
+ }
22781
+ const response = await channel.fetch("GET_MESSAGES", "GET_MESSAGES_RESPONSE");
22782
+
22783
+ // MES-981 P1 #6: channel.fetch resolves to undefined on timeout.
22784
+ // The previous behavior normalized that to [], which made a real
22785
+ // chat-frame failure indistinguishable from an empty transcript.
22786
+ // Fail loudly instead so hosts can branch on the error.
22787
+ if (response === undefined) {
22788
+ throw new AdaEmbedError("getMessages timed out — chat frame did not respond in time.");
22789
+ }
22790
+ return response;
22791
+ },
22792
+ sendMessage: async text => {
22793
+ if (!embed) {
22794
+ throw EMBED_NOT_INITIALIZED_ERROR;
22795
+ }
22796
+ requireProgrammaticControl(embed.adaSettings);
22797
+
22798
+ // MES-981: reject empty AND whitespace-only text so we never POST a
22799
+ // meaningless body to /message/chat/.
22800
+ if (typeof text !== "string" || text.trim().length === 0) {
22801
+ throw new AdaEmbedError("sendMessage requires non-empty text");
22802
+ }
22803
+
22804
+ // MES-981: gate on init BEFORE running the host's beforeSend hook.
22805
+ // Otherwise an init failure (or a missing chat channel) leaves the
22806
+ // host with delegate side-effects — analytics logs, UI optimism — for
22807
+ // a message that can never be sent. Match the gating order used by
22808
+ // every other public method on this surface.
22809
+ await embed.initialized;
22810
+ const channel = embed.messageService.getChannel("chat");
22811
+ if (!channel) {
22812
+ throw new AdaEmbedError("Chat is not ready yet to send a message.");
22813
+ }
22814
+ const {
22815
+ beforeSend
22816
+ } = delegate;
22817
+ const finalText = beforeSend ? await applyBeforeSend(beforeSend, text) : text;
22818
+ const response = await channel.fetch("SEND_MESSAGE", "SEND_MESSAGE_RESPONSE", {
22819
+ text: finalText
22820
+ });
22821
+ if (!response) {
22822
+ throw new AdaEmbedError("sendMessage timed out — chat frame did not respond in time.");
22823
+ }
22824
+
22825
+ // MES-981 P0 #2: chat-side validation/rate-limit/dispatch failures
22826
+ // come back as `{ error: <code> }`. Surface the code to the caller
22827
+ // so they can branch on machine-readable reasons (TOO_LONG,
22828
+ // RATE_LIMITED, EMPTY_TEXT, INVALID_PAYLOAD, DISPATCH_FAILED).
22829
+ if ("error" in response) {
22830
+ throw new AdaEmbedError(`sendMessage rejected by chat: ${response.error}`);
22831
+ }
22832
+ return response;
22833
+ },
22834
+ setDelegate: newDelegate => {
22835
+ if (!embed) {
22836
+ throw EMBED_NOT_INITIALIZED_ERROR;
22837
+ }
22838
+ requireProgrammaticControl(embed.adaSettings);
22839
+ delegate = newDelegate ?? {};
22840
+ },
22149
22841
  handleNotification: async () => {
22150
22842
  if (!embed) {
22151
22843
  throw EMBED_NOT_INITIALIZED_ERROR;