@eshal-bot/chat-widget 0.2.37 → 0.2.39

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.
@@ -88751,6 +88751,27 @@
88751
88751
  function fill(text, values) {
88752
88752
  return Object.entries(values).reduce((s, [k, v]) => s.replace(`{${k}}`, String(v)), String(text));
88753
88753
  }
88754
+
88755
+ /** EV-4941: keep a masked address on one line ("+91 ••••• 6138"). */
88756
+ const noBreak = text => String(text ?? '').replace(/ /g, '\u00a0');
88757
+
88758
+ /**
88759
+ * EV-4939: a number whose calling code is not among the allowed ones, so the
88760
+ * card can say so before Send. Only numbers typed with +; a partial code that
88761
+ * could still become an allowed one is not flagged.
88762
+ */
88763
+ function blockedByCountry(address, codes) {
88764
+ if (!Array.isArray(codes) || codes.length === 0 || !String(address).trim().startsWith('+')) return false;
88765
+ const digits = String(address).replace(/\D/g, '');
88766
+ if (!digits) return false;
88767
+ return !codes.some(c => digits.startsWith(c) || c.startsWith(digits));
88768
+ }
88769
+
88770
+ /** "14:59" for a lockout's time left. */
88771
+ function clock(seconds) {
88772
+ const s = Math.max(0, Math.ceil(seconds));
88773
+ return `${Math.floor(s / 60)}:${String(s % 60).padStart(2, '0')}`;
88774
+ }
88754
88775
  function boxStyle(isDark) {
88755
88776
  return {
88756
88777
  border: `1px solid ${BRAND_BLUE$1}`,
@@ -88824,6 +88845,10 @@
88824
88845
  const [error, setError] = React.useState(null);
88825
88846
  const [resendIn, setResendIn] = React.useState(0);
88826
88847
  const [cancelled, setCancelled] = React.useState(false);
88848
+ // EV-4939: a lockout's end from the server, and the allowed calling codes.
88849
+ const [lockUntil, setLockUntil] = React.useState(null);
88850
+ const [lockIn, setLockIn] = React.useState(0);
88851
+ const [callingCodes, setCallingCodes] = React.useState(null);
88827
88852
  const resumedRef = React.useRef(false);
88828
88853
  const pendingKey = React.useRef(Symbol('verify-card'));
88829
88854
  // A card the conversation already moved past (a later visitor turn exists,
@@ -88871,6 +88896,31 @@
88871
88896
  clearInterval(id);
88872
88897
  };
88873
88898
  }, [mode, step, ctx, t]);
88899
+ React.useEffect(() => {
88900
+ if (mode !== 'sms' || !ctx?.config) return undefined;
88901
+ let gone = false;
88902
+ ctx.config().then(res => {
88903
+ const codes = res?.body?.sms_calling_codes;
88904
+ if (!gone && Array.isArray(codes)) setCallingCodes(codes.map(String));
88905
+ }).catch(() => undefined);
88906
+ return () => {
88907
+ gone = true;
88908
+ };
88909
+ }, [mode, ctx]);
88910
+ React.useEffect(() => {
88911
+ if (!lockUntil) return undefined;
88912
+ const tick = () => {
88913
+ const left = (lockUntil - Date.now()) / 1000;
88914
+ if (left <= 0) {
88915
+ setLockUntil(null);
88916
+ setLockIn(0);
88917
+ setError(null);
88918
+ } else setLockIn(left);
88919
+ };
88920
+ tick();
88921
+ const id = setInterval(tick, 1000);
88922
+ return () => clearInterval(id);
88923
+ }, [lockUntil]);
88874
88924
  React.useEffect(() => {
88875
88925
  if (resendIn <= 0) return undefined;
88876
88926
  const id = setTimeout(() => setResendIn(s => s - 1), 1000);
@@ -88908,12 +88958,20 @@
88908
88958
  });
88909
88959
  if (res.ok) {
88910
88960
  setMasked(res.body?.masked || address.trim());
88961
+ setLockUntil(null);
88962
+ // EV-4941: if the card goes back to the number step (lockout, expiry,
88963
+ // a new number), the field reads "+91 90749 76138", not "+919074976138".
88964
+ if (res.body?.formatted) setAddress(String(res.body.formatted));
88911
88965
  setResendIn(Number(res.body?.resend_after_s) || 60);
88912
88966
  setCode('');
88913
88967
  setStep('code');
88914
88968
  } else {
88915
88969
  setError(res.body?.error || t('verifyGenericError'));
88916
88970
  if (res.body?.resendAfterS) setResendIn(Number(res.body.resendAfterS));
88971
+ if (res.body?.code === 'locked' && res.body?.lockedUntil) {
88972
+ setLockUntil(Date.parse(res.body.lockedUntil));
88973
+ setResendIn(0);
88974
+ }
88917
88975
  }
88918
88976
  } catch {
88919
88977
  setError(t('verifyGenericError'));
@@ -88955,6 +89013,11 @@
88955
89013
  } else {
88956
89014
  setError(res.body?.error || t('verifyGenericError'));
88957
89015
  if (res.body?.code === 'expired' || res.body?.code === 'locked') setStep('address');
89016
+ // EV-4939: a lockout holds Send for its whole length, as a countdown.
89017
+ if (res.body?.code === 'locked' && res.body?.lockedUntil) {
89018
+ setLockUntil(Date.parse(res.body.lockedUntil));
89019
+ setResendIn(0);
89020
+ }
88958
89021
  }
88959
89022
  } catch {
88960
89023
  setError(t('verifyGenericError'));
@@ -88972,11 +89035,21 @@
88972
89035
  fontWeight: 600
88973
89036
  },
88974
89037
  children: ["\u2713 ", fill(t('verifyDone'), {
88975
- target: masked
89038
+ target: noBreak(masked)
88976
89039
  })]
88977
89040
  })
88978
89041
  });
88979
89042
  }
89043
+ const countryBlocked = mode === 'sms' && blockedByCountry(address, callingCodes);
89044
+ const errorLine = text => text ? /*#__PURE__*/jsxRuntimeExports.jsx("div", {
89045
+ role: "alert",
89046
+ style: {
89047
+ color: '#b42318',
89048
+ fontSize: 13
89049
+ },
89050
+ children: text
89051
+ }) : null;
89052
+ const sendDisabled = busy || !address.trim() || resendIn > 0 || lockIn > 0 || countryBlocked;
88980
89053
  return /*#__PURE__*/jsxRuntimeExports.jsxs("div", {
88981
89054
  style: boxStyle(isDark),
88982
89055
  "data-testid": "verify-card",
@@ -89006,28 +89079,30 @@
89006
89079
  value: address,
89007
89080
  onChange: e => setAddress(e.target.value),
89008
89081
  onKeyDown: e => {
89009
- if (e.key === 'Enter') send();
89082
+ if (e.key === 'Enter' && !sendDisabled) send();
89010
89083
  }
89011
89084
  })]
89012
- }), /*#__PURE__*/jsxRuntimeExports.jsx("button", {
89085
+ }), errorLine(countryBlocked ? t('verifyCountryNotAllowed') : error), /*#__PURE__*/jsxRuntimeExports.jsx("button", {
89013
89086
  type: "button",
89014
- style: buttonStyle(busy || !address.trim() || resendIn > 0),
89015
- disabled: busy || !address.trim() || resendIn > 0,
89087
+ style: buttonStyle(sendDisabled),
89088
+ disabled: sendDisabled,
89016
89089
  onClick: send,
89017
- children: busy ? t('verifySending') : resendIn > 0 ? fill(t('verifyResendIn'), {
89090
+ children: busy ? t('verifySending') : lockIn > 0 ? fill(t('verifyLockedFor'), {
89091
+ time: clock(lockIn)
89092
+ }) : resendIn > 0 ? fill(t('verifyResendIn'), {
89018
89093
  seconds: resendIn
89019
89094
  }) : t('verifySendCode')
89020
89095
  })]
89021
89096
  }) : /*#__PURE__*/jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, {
89022
89097
  children: [/*#__PURE__*/jsxRuntimeExports.jsx("span", {
89023
89098
  children: fill(t('verifyEnterCode'), {
89024
- target: masked
89099
+ target: noBreak(masked)
89025
89100
  })
89026
89101
  }), /*#__PURE__*/jsxRuntimeExports.jsx("input", {
89102
+ // EV-4941: the widget's own font; spacing keeps the digits apart.
89027
89103
  style: {
89028
89104
  ...inputStyle(isDark),
89029
89105
  letterSpacing: '0.3em',
89030
- fontFamily: 'monospace',
89031
89106
  fontSize: 18
89032
89107
  },
89033
89108
  "aria-label": t('verifyCodeLabel'),
@@ -89039,7 +89114,7 @@
89039
89114
  onKeyDown: e => {
89040
89115
  if (e.key === 'Enter') confirm();
89041
89116
  }
89042
- }), /*#__PURE__*/jsxRuntimeExports.jsx("button", {
89117
+ }), errorLine(error), /*#__PURE__*/jsxRuntimeExports.jsx("button", {
89043
89118
  type: "button",
89044
89119
  style: buttonStyle(busy || code.length !== 6),
89045
89120
  disabled: busy || code.length !== 6,
@@ -89065,23 +89140,22 @@
89065
89140
  onClick: () => {
89066
89141
  setStep('address');
89067
89142
  setError(null);
89143
+ // EV-4939: the wait was for the old address; a new one starts fresh.
89144
+ setResendIn(0);
89068
89145
  },
89069
- children: t('verifyDifferent')
89146
+ children: mode === 'sms' ? t('verifyDifferentNumber') : t('verifyDifferent')
89070
89147
  })]
89071
- }), error && /*#__PURE__*/jsxRuntimeExports.jsx("div", {
89072
- role: "alert",
89073
- style: {
89074
- color: '#b42318',
89075
- fontSize: 12
89076
- },
89077
- children: error
89078
89148
  }), /*#__PURE__*/jsxRuntimeExports.jsx("button", {
89079
89149
  type: "button",
89080
89150
  style: {
89081
89151
  ...linkStyle,
89082
89152
  color: isDark ? '#9ca3af' : '#6b7280'
89083
89153
  },
89084
- onClick: () => setCancelled(true),
89154
+ onClick: () => {
89155
+ // EV-4942: tell the server, so the agent sees the cancel.
89156
+ if (ctx.cancel) Promise.resolve(ctx.cancel()).catch(() => undefined);
89157
+ setCancelled(true);
89158
+ },
89085
89159
  disabled: busy,
89086
89160
  children: t('verifyCancel')
89087
89161
  })]