@atlaskit/smart-card 44.7.0 → 44.7.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.
Files changed (59) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/cjs/__tests__/vr-tests/__snapshots__/block-card/block-card-social-proof-cold-cache--default.png +3 -0
  3. package/dist/cjs/__tests__/vr-tests/__snapshots__/block-card/block-card-social-proof-message-low-tier--default.png +3 -0
  4. package/dist/cjs/__tests__/vr-tests/__snapshots__/block-card/block-card-social-proof-message-not-low-tier--default.png +3 -0
  5. package/dist/cjs/messages.js +12 -1
  6. package/dist/cjs/state/hooks/use-current-site-cloud-id/index.js +0 -30
  7. package/dist/cjs/state/hooks/use-incoming-outgoing-links/index.js +3 -3
  8. package/dist/cjs/state/hooks/use-social-proof/index.js +44 -28
  9. package/dist/cjs/state/hooks/use-social-proof-experiment/index.js +48 -0
  10. package/dist/cjs/state/services/current-site-cloud-id/index.js +61 -74
  11. package/dist/cjs/state/services/personalization/index.js +59 -60
  12. package/dist/cjs/utils/analytics/analytics.js +1 -1
  13. package/dist/cjs/view/BlockCard/views/SocialProofMessage.js +36 -0
  14. package/dist/cjs/view/BlockCard/views/UnauthorisedView.js +85 -20
  15. package/dist/cjs/view/LinkUrl/index.js +1 -1
  16. package/dist/es2019/__tests__/vr-tests/__snapshots__/block-card/block-card-social-proof-cold-cache--default.png +3 -0
  17. package/dist/es2019/__tests__/vr-tests/__snapshots__/block-card/block-card-social-proof-message-low-tier--default.png +3 -0
  18. package/dist/es2019/__tests__/vr-tests/__snapshots__/block-card/block-card-social-proof-message-not-low-tier--default.png +3 -0
  19. package/dist/es2019/messages.js +12 -1
  20. package/dist/es2019/state/hooks/use-current-site-cloud-id/index.js +0 -1
  21. package/dist/es2019/state/hooks/use-incoming-outgoing-links/index.js +2 -2
  22. package/dist/es2019/state/hooks/use-social-proof/index.js +41 -22
  23. package/dist/es2019/state/hooks/use-social-proof-experiment/index.js +41 -0
  24. package/dist/es2019/state/services/current-site-cloud-id/index.js +43 -67
  25. package/dist/es2019/state/services/personalization/index.js +40 -39
  26. package/dist/es2019/utils/analytics/analytics.js +1 -1
  27. package/dist/es2019/view/BlockCard/views/SocialProofMessage.js +27 -0
  28. package/dist/es2019/view/BlockCard/views/UnauthorisedView.js +83 -15
  29. package/dist/es2019/view/LinkUrl/index.js +1 -1
  30. package/dist/esm/__tests__/vr-tests/__snapshots__/block-card/block-card-social-proof-cold-cache--default.png +3 -0
  31. package/dist/esm/__tests__/vr-tests/__snapshots__/block-card/block-card-social-proof-message-low-tier--default.png +3 -0
  32. package/dist/esm/__tests__/vr-tests/__snapshots__/block-card/block-card-social-proof-message-not-low-tier--default.png +3 -0
  33. package/dist/esm/messages.js +12 -1
  34. package/dist/esm/state/hooks/use-current-site-cloud-id/index.js +0 -1
  35. package/dist/esm/state/hooks/use-incoming-outgoing-links/index.js +2 -2
  36. package/dist/esm/state/hooks/use-social-proof/index.js +45 -29
  37. package/dist/esm/state/hooks/use-social-proof-experiment/index.js +41 -0
  38. package/dist/esm/state/services/current-site-cloud-id/index.js +59 -72
  39. package/dist/esm/state/services/personalization/index.js +56 -59
  40. package/dist/esm/utils/analytics/analytics.js +1 -1
  41. package/dist/esm/view/BlockCard/views/SocialProofMessage.js +29 -0
  42. package/dist/esm/view/BlockCard/views/UnauthorisedView.js +85 -20
  43. package/dist/esm/view/LinkUrl/index.js +1 -1
  44. package/dist/types/messages.d.ts +1 -1
  45. package/dist/types/state/hooks/use-current-site-cloud-id/index.d.ts +0 -1
  46. package/dist/types/state/hooks/use-social-proof/index.d.ts +17 -4
  47. package/dist/types/state/hooks/use-social-proof-experiment/index.d.ts +39 -0
  48. package/dist/types/state/services/current-site-cloud-id/index.d.ts +9 -33
  49. package/dist/types/state/services/personalization/index.d.ts +14 -24
  50. package/dist/types/view/BlockCard/views/SocialProofMessage.d.ts +14 -0
  51. package/dist/types-ts4.5/messages.d.ts +1 -1
  52. package/dist/types-ts4.5/state/hooks/use-current-site-cloud-id/index.d.ts +0 -1
  53. package/dist/types-ts4.5/state/hooks/use-social-proof/index.d.ts +17 -4
  54. package/dist/types-ts4.5/state/hooks/use-social-proof-experiment/index.d.ts +39 -0
  55. package/dist/types-ts4.5/state/services/current-site-cloud-id/index.d.ts +9 -33
  56. package/dist/types-ts4.5/state/services/personalization/index.d.ts +14 -24
  57. package/dist/types-ts4.5/view/BlockCard/views/SocialProofMessage.d.ts +14 -0
  58. package/package.json +8 -2
  59. package/smart-card.docs.tsx +2 -2
package/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # @atlaskit/smart-card
2
2
 
3
+ ## 44.7.1
4
+
5
+ ### Patch Changes
6
+
7
+ - [`f8aee5a0e76d3`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/f8aee5a0e76d3) -
8
+ NAVX-4523 NAVX-4519 Adds social proof experiment hook, TAP personalization service,
9
+ SocialProofMessage component, and componentWithFG wiring for unauth 3P block cards. Registers
10
+ social_proof_3p_unauth_block_exp in tmp-editor-statsig. Uses cache-first behavior for social proof
11
+ data, the canonical site-level TAP trait, and avoids experiment exposure before cached provider
12
+ data is available.
13
+ - Updated dependencies
14
+
3
15
  ## 44.7.0
4
16
 
5
17
  ### Minor Changes
@@ -0,0 +1,3 @@
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:b072be1d67bf9de1dce752032500f85882f4e3106047ba57d4944e0e9e123c34
3
+ size 17460
@@ -0,0 +1,3 @@
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:bc4ec248eb4dfc15dc531155a386a12571398ad5c249de38df839c94cc03c4ab
3
+ size 16074
@@ -0,0 +1,3 @@
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:164d340225092b0d1ac5bd3daf47b305aeeadd0544dc8f76b0fbda83e200572a
3
+ size 16863
@@ -994,7 +994,7 @@ var messages = exports.messages = (0, _reactIntl.defineMessages)({
994
994
  },
995
995
  rovo_prompt_message_highlight_relevant_content: {
996
996
  id: 'fabric.linking.rovo_prompt_message_highlight_relevant_content.non-final',
997
- defaultMessage: '<p>Based on this linked item (<a>{url}</a>) and the {context} Im currently viewing, highlight the parts of the linked content that are most relevant to this work. Explain briefly why each part is relevant.</p>',
997
+ defaultMessage: "<p>Based on this linked item (<a>{url}</a>) and the {context} I'm currently viewing, highlight the parts of the linked content that are most relevant to this work. Explain briefly why each part is relevant.</p>",
998
998
  description: 'The prompt message to send to Rovo Chat. {context} refers to the content the user triggered from, e.g. Confluence page or Jira work item. {url} refers to Smart Link that the user triggers this action from. (Please make sure all html tags remain the same.)'
999
999
  },
1000
1000
  rovo_prompt_button_identify_key_trends: {
@@ -1056,5 +1056,16 @@ var messages = exports.messages = (0, _reactIntl.defineMessages)({
1056
1056
  id: 'fabric.linking.rovo_prompt_button_show_me_whats_relevant.non-final',
1057
1057
  defaultMessage: "Show me what's relevant",
1058
1058
  description: 'The name of the action to send prompt message to Rovo Chat in relation to current Smart Link'
1059
+ },
1060
+ // TODO: remove when social-proof-3p-unauth-block-fg is cleaned up
1061
+ pre_auth_block_social_proof_not_low: {
1062
+ id: 'fabric.linking.pre_auth_block_social_proof_not_low',
1063
+ defaultMessage: '<b>{percentage}%</b> of your team is previewing <b>{provider}</b>.',
1064
+ description: 'Social proof message shown on unauthorized 3P block cards when 30% or more of the tenant has connected the provider. {percentage} is a number, {provider} is the 3P app name (e.g. OneDrive).'
1065
+ },
1066
+ pre_auth_block_social_proof_low: {
1067
+ id: 'fabric.linking.pre_auth_block_social_proof_low',
1068
+ defaultMessage: 'Your team is previewing <b>{provider}</b>.',
1069
+ description: 'Social proof message shown on unauthorized 3P block cards when less than 30% of the tenant has connected the provider. {provider} is the 3P app name (e.g. OneDrive).'
1059
1070
  }
1060
1071
  });
@@ -4,37 +4,7 @@ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefau
4
4
  Object.defineProperty(exports, "__esModule", {
5
5
  value: true
6
6
  });
7
- Object.defineProperty(exports, "CURRENT_SITE_CLOUD_ID_LOCAL_STORAGE_KEY", {
8
- enumerable: true,
9
- get: function get() {
10
- return _currentSiteCloudId.CURRENT_SITE_CLOUD_ID_LOCAL_STORAGE_KEY;
11
- }
12
- });
13
- Object.defineProperty(exports, "CURRENT_SITE_CLOUD_ID_STORAGE_ITEM_KEY", {
14
- enumerable: true,
15
- get: function get() {
16
- return _currentSiteCloudId.CURRENT_SITE_CLOUD_ID_STORAGE_ITEM_KEY;
17
- }
18
- });
19
- Object.defineProperty(exports, "currentSiteCloudIdService", {
20
- enumerable: true,
21
- get: function get() {
22
- return _currentSiteCloudId.currentSiteCloudIdService;
23
- }
24
- });
25
7
  exports.default = void 0;
26
- Object.defineProperty(exports, "getCachedCurrentSiteCloudIdAndRefresh", {
27
- enumerable: true,
28
- get: function get() {
29
- return _currentSiteCloudId.getCachedCurrentSiteCloudIdAndRefresh;
30
- }
31
- });
32
- Object.defineProperty(exports, "getCurrentSiteCloudId", {
33
- enumerable: true,
34
- get: function get() {
35
- return _currentSiteCloudId.getCurrentSiteCloudId;
36
- }
37
- });
38
8
  var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime/helpers/slicedToArray"));
39
9
  var _react = require("react");
40
10
  var _currentSiteCloudId = require("../../services/current-site-cloud-id");
@@ -10,7 +10,7 @@ var _asyncToGenerator2 = _interopRequireDefault(require("@babel/runtime/helpers/
10
10
  var _react = require("react");
11
11
  var _linkingCommon = require("@atlaskit/linking-common");
12
12
  var _platformFeatureFlags = require("@atlaskit/platform-feature-flags");
13
- var _useCurrentSiteCloudId = require("../use-current-site-cloud-id");
13
+ var _currentSiteCloudId = require("../../services/current-site-cloud-id");
14
14
  var _query = require("./query");
15
15
  /**
16
16
  * @param baseUriWithNoTrailingSlash base url which will then be appended with /gateway/api/graphql to make requests to AGG
@@ -79,12 +79,12 @@ var useIncomingOutgoingAri = function useIncomingOutgoingAri() {
79
79
  }
80
80
  return _context3.abrupt("return", match[2]);
81
81
  case 4:
82
- if (!(0, _platformFeatureFlags.fg)('platform_sl_3p_preauth_soc_proof_inline_killswitch')) {
82
+ if (!(0, _platformFeatureFlags.fg)('platform_sl_incoming_outgoing_tenant_info_killswitch')) {
83
83
  _context3.next = 10;
84
84
  break;
85
85
  }
86
86
  _context3.next = 7;
87
- return (0, _useCurrentSiteCloudId.getCurrentSiteCloudId)(baseUriWithNoTrailingSlash);
87
+ return (0, _currentSiteCloudId.getCurrentSiteCloudId)(baseUriWithNoTrailingSlash);
88
88
  case 7:
89
89
  _context3.t0 = _context3.sent;
90
90
  _context3.next = 13;
@@ -7,6 +7,7 @@ Object.defineProperty(exports, "__esModule", {
7
7
  exports.default = void 0;
8
8
  var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime/helpers/slicedToArray"));
9
9
  var _react = require("react");
10
+ var _currentSiteCloudId = require("../../services/current-site-cloud-id");
10
11
  var _personalization = require("../../services/personalization");
11
12
  var NOT_ENABLED_RESULT = {
12
13
  connectedPct: undefined,
@@ -15,46 +16,61 @@ var NOT_ENABLED_RESULT = {
15
16
  };
16
17
 
17
18
  /**
18
- * Fetches provider usage percentage from the TAP Delivery personalization service when the
19
- * killswitch allows it. Callers decide separately (e.g. via Statsig experiment) whether to
20
- * surface that data in the UI.
19
+ * Cache-first social proof hook.
20
+ *
21
+ * On mount:
22
+ * 1. Reads localStorage synchronously via `getProviderPctMapSync`.
23
+ * - If data exists (warm cache): sets `providerPctMap` immediately, `isLoading = false`.
24
+ * - If no data (cold cache): leaves `providerPctMap` undefined, `isLoading = false`.
25
+ * 2. Always kicks off an async fetch (fire-and-forget) via `getProviderPctMap` to populate
26
+ * localStorage for next page load. Does NOT update state with the async result.
27
+ *
28
+ * This means:
29
+ * - First page visit (cold): no social proof rendered, no experiment exposure fired.
30
+ * Background fetch populates localStorage for next time.
31
+ * - Second page visit (warm): social proof renders immediately from localStorage.
32
+ * Background refresh keeps localStorage fresh.
33
+ *
34
+ * Callers decide separately (e.g. via Statsig experiment) whether to surface the data.
21
35
  */
22
- var useSocialProof = function useSocialProof(traitName, extensionKey) {
23
- var isKillswitchOn = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;
36
+ var useSocialProof = function useSocialProof(extensionKey) {
37
+ var isKillswitchOn = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
38
+ var baseUriWithNoTrailingSlash = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : '';
24
39
  var isEnabled = isKillswitchOn;
25
- var _useState = (0, _react.useState)(undefined),
26
- _useState2 = (0, _slicedToArray2.default)(_useState, 2),
27
- providerPctMap = _useState2[0],
28
- setProviderPctMap = _useState2[1];
29
- var _useState3 = (0, _react.useState)(false),
30
- _useState4 = (0, _slicedToArray2.default)(_useState3, 2),
31
- isPersonalizationLoading = _useState4[0],
32
- setIsPersonalizationLoading = _useState4[1];
40
+ var _useState = (0, _react.useState)(function () {
41
+ if (!isEnabled) {
42
+ return {
43
+ cloudId: undefined,
44
+ providerPctMap: null
45
+ };
46
+ }
47
+ var cloudId = (0, _currentSiteCloudId.getCurrentSiteCloudIdSync)(baseUriWithNoTrailingSlash);
48
+ return {
49
+ cloudId: cloudId,
50
+ providerPctMap: (0, _personalization.getProviderPctMapSync)(cloudId, _personalization.SOCIAL_PROOF_TRAIT_NAME)
51
+ };
52
+ }),
53
+ _useState2 = (0, _slicedToArray2.default)(_useState, 1),
54
+ snapshot = _useState2[0];
55
+
56
+ // Fire-and-forget: warm caches for future mounts only. Never update this mount's treatment UI.
33
57
  (0, _react.useEffect)(function () {
34
58
  if (!isEnabled) {
35
59
  return;
36
60
  }
37
- var cancelled = false;
38
- setIsPersonalizationLoading(true);
39
- (0, _personalization.getProviderPctMap)(traitName).then(function (pctMap) {
40
- if (!cancelled) {
41
- setProviderPctMap(pctMap);
42
- setIsPersonalizationLoading(false);
43
- }
61
+ void (0, _currentSiteCloudId.getCurrentSiteCloudId)(baseUriWithNoTrailingSlash).then(function (cloudId) {
62
+ void (0, _personalization.getProviderPctMap)(cloudId, _personalization.SOCIAL_PROOF_TRAIT_NAME);
44
63
  });
45
- return function () {
46
- cancelled = true;
47
- };
48
- }, [isEnabled, traitName]);
64
+ }, [baseUriWithNoTrailingSlash, isEnabled]);
49
65
  return (0, _react.useMemo)(function () {
50
66
  if (!isEnabled) {
51
67
  return NOT_ENABLED_RESULT;
52
68
  }
53
69
  return {
54
- connectedPct: extensionKey && providerPctMap ? providerPctMap[extensionKey] : undefined,
55
- isEnabled: isEnabled,
56
- isLoading: isPersonalizationLoading
70
+ connectedPct: extensionKey && snapshot.providerPctMap ? snapshot.providerPctMap[extensionKey] : undefined,
71
+ isEnabled: Boolean(snapshot.cloudId && snapshot.providerPctMap),
72
+ isLoading: false // sync read is instant; never in "loading" state
57
73
  };
58
- }, [extensionKey, isEnabled, isPersonalizationLoading, providerPctMap]);
74
+ }, [extensionKey, isEnabled, snapshot]);
59
75
  };
60
76
  var _default = exports.default = useSocialProof;
@@ -0,0 +1,48 @@
1
+ "use strict";
2
+
3
+ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
4
+ Object.defineProperty(exports, "__esModule", {
5
+ value: true
6
+ });
7
+ exports.default = void 0;
8
+ var _react = require("react");
9
+ var _experiments = require("@atlaskit/tmp-editor-statsig/experiments");
10
+ var _useSocialProof2 = _interopRequireDefault(require("../use-social-proof"));
11
+ var TIER_THRESHOLD = 30;
12
+
13
+ /**
14
+ * Returns enrollment and treatment state for the social proof unauth block card experiment.
15
+ *
16
+ * Delegates data-fetching to `useSocialProof` (Sasha's shared service layer),
17
+ * which handles cloudId resolution, TAP trait fetch, localStorage caching,
18
+ * request deduping, and sync reads.
19
+ *
20
+ * This hook adds the experiment/cohort layer on top:
21
+ * - Only fires exposure (via editorExperiment) when data has loaded.
22
+ * - Derives `tier` and `isTreatment` for rendering decisions.
23
+ *
24
+ * Expected to be called only inside the fg-enabled branch (via componentWithFG).
25
+ *
26
+ * @param extensionKey - The extensionKey of the current 3P provider (e.g. 'google-object-provider').
27
+ */
28
+ var useSocialProofExperiment = function useSocialProofExperiment(extensionKey) {
29
+ var baseUriWithNoTrailingSlash = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : '';
30
+ var _useSocialProof = (0, _useSocialProof2.default)(extensionKey, true, baseUriWithNoTrailingSlash),
31
+ connectedPct = _useSocialProof.connectedPct,
32
+ isEnabled = _useSocialProof.isEnabled,
33
+ isLoading = _useSocialProof.isLoading;
34
+ return (0, _react.useMemo)(function () {
35
+ var hasSocialProofData = connectedPct !== undefined;
36
+
37
+ // Only fire exposure when data has loaded (prevents exposure inflation)
38
+ var isTreatment = isEnabled && !isLoading && hasSocialProofData ? (0, _experiments.editorExperiment)('social_proof_3p_unauth_block_exp', true) : false;
39
+ var tier = connectedPct !== undefined && connectedPct >= TIER_THRESHOLD ? 'not-low' : 'low';
40
+ return {
41
+ isTreatment: isTreatment,
42
+ tier: tier,
43
+ connectedPct: connectedPct,
44
+ isLoading: isLoading
45
+ };
46
+ }, [isEnabled, isLoading, connectedPct]);
47
+ };
48
+ var _default = exports.default = useSocialProofExperiment;
@@ -4,9 +4,8 @@ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefau
4
4
  Object.defineProperty(exports, "__esModule", {
5
5
  value: true
6
6
  });
7
- exports.currentSiteCloudIdService = exports.CurrentSiteCloudIdService = exports.CURRENT_SITE_CLOUD_ID_STORAGE_ITEM_KEY = exports.CURRENT_SITE_CLOUD_ID_LOCAL_STORAGE_KEY = void 0;
8
- exports.getCachedCurrentSiteCloudIdAndRefresh = getCachedCurrentSiteCloudIdAndRefresh;
9
- exports.getCurrentSiteCloudId = void 0;
7
+ exports.getCurrentSiteCloudIdLocalStorageKey = exports.getCurrentSiteCloudId = exports.currentSiteCloudIdService = exports.CurrentSiteCloudIdService = exports.CURRENT_SITE_CLOUD_ID_TTL_MS = exports.CURRENT_SITE_CLOUD_ID_STORAGE_ITEM_KEY_PREFIX = exports.CURRENT_SITE_CLOUD_ID_LOCAL_STORAGE_KEY = void 0;
8
+ exports.getCurrentSiteCloudIdSync = getCurrentSiteCloudIdSync;
10
9
  var _regenerator = _interopRequireDefault(require("@babel/runtime/regenerator"));
11
10
  var _asyncToGenerator2 = _interopRequireDefault(require("@babel/runtime/helpers/asyncToGenerator"));
12
11
  var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck"));
@@ -14,31 +13,38 @@ var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/creat
14
13
  var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
15
14
  var _storageClient = require("@atlaskit/frontend-utilities/storage-client");
16
15
  var _linkingCommon = require("@atlaskit/linking-common");
17
- /**
18
- * Logical key shape matches smart-card storage conventions (see personalization-service). {@link StorageClient}
19
- * stores rows as `<clientKey>_<itemKey>` with `clientKey === '@atlaskit/smart-card'` and
20
- * `itemKey === 'site-cloud-id:v1'` (no further scope segments; unlike `pct-map:v1:`, this is a single fixed row).
21
- */
22
- var SMART_CARD_STORAGE_SCOPE = '@atlaskit/smart-card';
23
- var CURRENT_SITE_CLOUD_ID_STORAGE_ITEM_KEY = exports.CURRENT_SITE_CLOUD_ID_STORAGE_ITEM_KEY = 'site-cloud-id:v1';
16
+ var SMART_CARD_STORAGE_SCOPE = 'smart-card-social-proof';
17
+ var CURRENT_SITE_CLOUD_ID_STORAGE_ITEM_KEY_PREFIX = exports.CURRENT_SITE_CLOUD_ID_STORAGE_ITEM_KEY_PREFIX = 'site-cloud-id:v1:';
18
+ var CURRENT_SITE_CLOUD_ID_TTL_MS = exports.CURRENT_SITE_CLOUD_ID_TTL_MS = 24 * 60 * 60 * 1000;
24
19
  var smartCardStorage = new _storageClient.StorageClient(SMART_CARD_STORAGE_SCOPE);
20
+ function normalizeBaseUri() {
21
+ var baseUriWithNoTrailingSlash = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
22
+ return baseUriWithNoTrailingSlash.replace(/\/$/, '');
23
+ }
24
+ function cloudIdStorageItemKey() {
25
+ var baseUriWithNoTrailingSlash = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
26
+ return "".concat(CURRENT_SITE_CLOUD_ID_STORAGE_ITEM_KEY_PREFIX).concat(encodeURIComponent(normalizeBaseUri(baseUriWithNoTrailingSlash)));
27
+ }
25
28
 
26
29
  /** Keys written by this service in localStorage when using {@link smartCardStorage}. */
27
- var CURRENT_SITE_CLOUD_ID_LOCAL_STORAGE_KEY = exports.CURRENT_SITE_CLOUD_ID_LOCAL_STORAGE_KEY = "".concat(SMART_CARD_STORAGE_SCOPE, "_").concat(CURRENT_SITE_CLOUD_ID_STORAGE_ITEM_KEY);
30
+ var getCurrentSiteCloudIdLocalStorageKey = exports.getCurrentSiteCloudIdLocalStorageKey = function getCurrentSiteCloudIdLocalStorageKey() {
31
+ var baseUriWithNoTrailingSlash = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
32
+ return "".concat(SMART_CARD_STORAGE_SCOPE, "_").concat(cloudIdStorageItemKey(baseUriWithNoTrailingSlash));
33
+ };
34
+
35
+ /** Backwards-compatible default-scope key for existing tests and external assertions. */
36
+ var CURRENT_SITE_CLOUD_ID_LOCAL_STORAGE_KEY = exports.CURRENT_SITE_CLOUD_ID_LOCAL_STORAGE_KEY = getCurrentSiteCloudIdLocalStorageKey();
28
37
  var CurrentSiteCloudIdService = exports.CurrentSiteCloudIdService = /*#__PURE__*/function () {
29
38
  function CurrentSiteCloudIdService() {
30
39
  (0, _classCallCheck2.default)(this, CurrentSiteCloudIdService);
31
- /**
32
- * Holds the shared tenant_info work: one in-flight fetch, then (on success) a settled promise for the session cloud
33
- * id so later callers never trigger another `tenant_info` in the same page lifetime (until {@link clearCache}).
34
- */
35
- (0, _defineProperty2.default)(this, "tenantInfoInflightPromise", null);
40
+ (0, _defineProperty2.default)(this, "tenantInfoInflightPromises", new Map());
36
41
  }
37
42
  return (0, _createClass2.default)(CurrentSiteCloudIdService, [{
38
43
  key: "readStoredCloudId",
39
44
  value: function readStoredCloudId() {
45
+ var baseUriWithNoTrailingSlash = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
40
46
  try {
41
- var cloudId = smartCardStorage.getItem(CURRENT_SITE_CLOUD_ID_STORAGE_ITEM_KEY);
47
+ var cloudId = smartCardStorage.getItem(cloudIdStorageItemKey(baseUriWithNoTrailingSlash));
42
48
  if (typeof cloudId !== 'string' || !cloudId || cloudId === 'undefined') {
43
49
  return undefined;
44
50
  }
@@ -49,12 +55,12 @@ var CurrentSiteCloudIdService = exports.CurrentSiteCloudIdService = /*#__PURE__*
49
55
  }
50
56
  }, {
51
57
  key: "writeStoredCloudId",
52
- value: function writeStoredCloudId(cloudId) {
58
+ value: function writeStoredCloudId(baseUriWithNoTrailingSlash, cloudId) {
53
59
  if (!cloudId) {
54
60
  return;
55
61
  }
56
62
  try {
57
- smartCardStorage.setItemWithExpiry(CURRENT_SITE_CLOUD_ID_STORAGE_ITEM_KEY, cloudId);
63
+ smartCardStorage.setItemWithExpiry(cloudIdStorageItemKey(baseUriWithNoTrailingSlash), cloudId, CURRENT_SITE_CLOUD_ID_TTL_MS);
58
64
  } catch (_unused2) {
59
65
  // Quota, private-mode, SSR, etc. — same intent as personalization-service.
60
66
  }
@@ -63,67 +69,62 @@ var CurrentSiteCloudIdService = exports.CurrentSiteCloudIdService = /*#__PURE__*
63
69
  key: "ensureTenantInfoInflightStarted",
64
70
  value: function ensureTenantInfoInflightStarted(baseUriWithNoTrailingSlash) {
65
71
  var _this = this;
66
- if (this.tenantInfoInflightPromise !== null) {
67
- return;
72
+ var baseUri = normalizeBaseUri(baseUriWithNoTrailingSlash);
73
+ var existing = this.tenantInfoInflightPromises.get(baseUri);
74
+ if (existing) {
75
+ return existing;
68
76
  }
69
- this.tenantInfoInflightPromise = (0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee() {
77
+ var promise = (0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee() {
70
78
  var response, cloudId;
71
79
  return _regenerator.default.wrap(function _callee$(_context) {
72
80
  while (1) switch (_context.prev = _context.next) {
73
81
  case 0:
74
82
  _context.prev = 0;
75
83
  _context.next = 3;
76
- return (0, _linkingCommon.request)('get', baseUriWithNoTrailingSlash + '/_edge/tenant_info');
84
+ return (0, _linkingCommon.request)('get', "".concat(baseUri, "/_edge/tenant_info"));
77
85
  case 3:
78
86
  response = _context.sent;
79
87
  cloudId = response === null || response === void 0 ? void 0 : response.cloudId;
80
88
  if (cloudId) {
81
- _this.writeStoredCloudId(cloudId);
89
+ _this.writeStoredCloudId(baseUri, cloudId);
82
90
  }
83
- return _context.abrupt("return", cloudId ? cloudId : _this.readStoredCloudId());
91
+ return _context.abrupt("return", cloudId ? cloudId : _this.readStoredCloudId(baseUri));
84
92
  case 9:
85
93
  _context.prev = 9;
86
94
  _context.t0 = _context["catch"](0);
87
- _this.tenantInfoInflightPromise = null;
88
- return _context.abrupt("return", _this.readStoredCloudId());
89
- case 13:
95
+ return _context.abrupt("return", _this.readStoredCloudId(baseUri));
96
+ case 12:
97
+ _context.prev = 12;
98
+ _this.tenantInfoInflightPromises.delete(baseUri);
99
+ return _context.finish(12);
100
+ case 15:
90
101
  case "end":
91
102
  return _context.stop();
92
103
  }
93
- }, _callee, null, [[0, 9]]);
104
+ }, _callee, null, [[0, 9, 12, 15]]);
94
105
  }))();
106
+ this.tenantInfoInflightPromises.set(baseUri, promise);
107
+ return promise;
95
108
  }
96
109
 
97
- /**
98
- * Returns the currently cached cloud id synchronously and starts a background refresh.
99
- * The refresh result is persisted for future calls but is not awaited by this call.
100
- */
110
+ /** Pure synchronous read scoped to the given base URI. */
101
111
  }, {
102
- key: "getCachedCloudIdAndRefresh",
103
- value: function getCachedCloudIdAndRefresh() {
104
- this.ensureTenantInfoInflightStarted('');
105
- return this.readStoredCloudId();
112
+ key: "getStoredCloudId",
113
+ value: function getStoredCloudId() {
114
+ var baseUriWithNoTrailingSlash = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
115
+ return this.readStoredCloudId(baseUriWithNoTrailingSlash);
106
116
  }
107
117
 
108
118
  /** Writes tenant cloud id for tests or callers that intentionally warm storage before edge resolves. */
109
119
  }, {
110
120
  key: "persistStoredCloudId",
111
121
  value: function persistStoredCloudId(cloudId) {
112
- this.writeStoredCloudId(cloudId);
122
+ var baseUriWithNoTrailingSlash = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : '';
123
+ this.writeStoredCloudId(baseUriWithNoTrailingSlash, cloudId);
113
124
  }
114
-
115
- /**
116
- * When local storage already has a tenant cloud id, it is returned immediately; a background tenant_info refresh
117
- * is still kicked off unless one is already in flight.
118
- *
119
- * Without storage, this awaits the deduped in-flight tenant_info (first concurrent caller chooses the URL;
120
- * all share one promise regardless of subsequent `baseUriWithNoTrailingSlash`).
121
- *
122
- * On network success with no cloud id, or on failure: falls back via {@link readStoredCloudId}.
123
- */
124
125
  }, {
125
126
  key: "get",
126
- value: (function () {
127
+ value: function () {
127
128
  var _get = (0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee2() {
128
129
  var baseUriWithNoTrailingSlash,
129
130
  fromStorage,
@@ -132,16 +133,15 @@ var CurrentSiteCloudIdService = exports.CurrentSiteCloudIdService = /*#__PURE__*
132
133
  while (1) switch (_context2.prev = _context2.next) {
133
134
  case 0:
134
135
  baseUriWithNoTrailingSlash = _args2.length > 0 && _args2[0] !== undefined ? _args2[0] : '';
135
- fromStorage = this.readStoredCloudId();
136
- this.ensureTenantInfoInflightStarted(baseUriWithNoTrailingSlash);
136
+ fromStorage = this.readStoredCloudId(baseUriWithNoTrailingSlash);
137
137
  if (!fromStorage) {
138
- _context2.next = 5;
138
+ _context2.next = 4;
139
139
  break;
140
140
  }
141
141
  return _context2.abrupt("return", fromStorage);
142
+ case 4:
143
+ return _context2.abrupt("return", this.ensureTenantInfoInflightStarted(baseUriWithNoTrailingSlash));
142
144
  case 5:
143
- return _context2.abrupt("return", this.tenantInfoInflightPromise);
144
- case 6:
145
145
  case "end":
146
146
  return _context2.stop();
147
147
  }
@@ -151,33 +151,20 @@ var CurrentSiteCloudIdService = exports.CurrentSiteCloudIdService = /*#__PURE__*
151
151
  return _get.apply(this, arguments);
152
152
  }
153
153
  return get;
154
- }() /** Clears session pin and persisted storage so the next {@link get} is a fresh tenant_info fetch. */)
154
+ }() /** Clears session pin and persisted storage so the next {@link get} is a fresh tenant_info fetch. */
155
155
  }, {
156
156
  key: "clearCache",
157
157
  value: function clearCache() {
158
- this.tenantInfoInflightPromise = null;
159
- smartCardStorage.removeItem(CURRENT_SITE_CLOUD_ID_STORAGE_ITEM_KEY);
158
+ this.tenantInfoInflightPromises.clear();
160
159
  }
161
160
  }]);
162
161
  }();
163
162
  var currentSiteCloudIdService = exports.currentSiteCloudIdService = new CurrentSiteCloudIdService();
164
-
165
- /**
166
- * Resolves the current site cloud id through the module-level {@link currentSiteCloudIdService}.
167
- * Returns a stored cloud id immediately when one exists; otherwise waits for the shared
168
- * `tenant_info` request and persists the result for subsequent cached reads.
169
- */
163
+ function getCurrentSiteCloudIdSync() {
164
+ var baseUriWithNoTrailingSlash = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
165
+ return currentSiteCloudIdService.getStoredCloudId(baseUriWithNoTrailingSlash);
166
+ }
170
167
  var getCurrentSiteCloudId = exports.getCurrentSiteCloudId = function getCurrentSiteCloudId() {
171
168
  var baseUriWithNoTrailingSlash = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
172
169
  return currentSiteCloudIdService.get(baseUriWithNoTrailingSlash);
173
- };
174
-
175
- /**
176
- * Reads the current site cloud id from browser storage (the `site-cloud-id:v1` row) via the
177
- * module-level {@link currentSiteCloudIdService} singleton, without awaiting network work.
178
- * Calling this also starts the shared `tenant_info` refresh in the background when one is not
179
- * already running, so a later call can observe a refreshed value when available.
180
- */
181
- function getCachedCurrentSiteCloudIdAndRefresh() {
182
- return currentSiteCloudIdService.getCachedCloudIdAndRefresh();
183
- }
170
+ };