@atlaskit/rovo-triggers 10.22.0 → 10.23.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +30 -0
- package/dist/cjs/common/utils/extension-context-bridge/ExtensionContextBridgeClient.js +29 -6
- package/dist/cjs/common/utils/extension-context-bridge/ExtensionContextBridgeHost.js +8 -1
- package/dist/cjs/common/utils/extension-context-bridge/policy.js +21 -1
- package/dist/cjs/common/utils/extension-context-bridge/registry.js +5 -0
- package/dist/cjs/common/utils/extension-context-bridge/transport.js +10 -2
- package/dist/cjs/common/utils/params/constants.js +1 -1
- package/dist/es2019/common/utils/extension-context-bridge/ExtensionContextBridgeClient.js +28 -5
- package/dist/es2019/common/utils/extension-context-bridge/ExtensionContextBridgeHost.js +8 -1
- package/dist/es2019/common/utils/extension-context-bridge/policy.js +18 -0
- package/dist/es2019/common/utils/extension-context-bridge/registry.js +5 -0
- package/dist/es2019/common/utils/extension-context-bridge/transport.js +18 -9
- package/dist/es2019/common/utils/params/constants.js +1 -1
- package/dist/esm/common/utils/extension-context-bridge/ExtensionContextBridgeClient.js +29 -6
- package/dist/esm/common/utils/extension-context-bridge/ExtensionContextBridgeHost.js +8 -1
- package/dist/esm/common/utils/extension-context-bridge/policy.js +20 -0
- package/dist/esm/common/utils/extension-context-bridge/registry.js +5 -0
- package/dist/esm/common/utils/extension-context-bridge/transport.js +10 -2
- package/dist/esm/common/utils/params/constants.js +1 -1
- package/dist/types/common/utils/extension-context-bridge/ExtensionContextBridgeClient.d.ts +11 -1
- package/dist/types/common/utils/extension-context-bridge/ExtensionContextBridgeHost.d.ts +11 -1
- package/dist/types/common/utils/extension-context-bridge/policy.d.ts +2 -0
- package/dist/types/common/utils/extension-context-bridge/transport.d.ts +11 -1
- package/dist/types/common/utils/params/types.d.ts +1 -0
- package/package.json +4 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,35 @@
|
|
|
1
1
|
# @atlaskit/rovo-triggers
|
|
2
2
|
|
|
3
|
+
## 10.23.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [`81851ca5f9af6`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/81851ca5f9af6) -
|
|
8
|
+
Restore smart-create preview card invocation selection from Rovo Chat URL params.
|
|
9
|
+
|
|
10
|
+
## 10.23.0
|
|
11
|
+
|
|
12
|
+
### Minor Changes
|
|
13
|
+
|
|
14
|
+
- [`e07b1d5a5706a`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/e07b1d5a5706a) -
|
|
15
|
+
SECASR-8596 / SECASR-8597: pin the extension context bridge transport to a single peer origin, and
|
|
16
|
+
restrict which `set-message-context` context keys may cross it.
|
|
17
|
+
|
|
18
|
+
`createWindowTransport` gains an optional `acceptOrigin`, checked before a message is dispatched
|
|
19
|
+
to a subscriber. It defaults to `targetOrigin`, and the wildcard is refused. The chat-iframe
|
|
20
|
+
client no longer posts to `'*'`: it resolves the side panel's origin from the `extensionOrigin`
|
|
21
|
+
URL parameter and requires it to be a member of a consumer-supplied `allowedOrigins` list, so the
|
|
22
|
+
channel is pinned to the consumer's own extension rather than to any installed one. Absent either,
|
|
23
|
+
it builds no transport.
|
|
24
|
+
|
|
25
|
+
`set-message-context` now relays only allowlisted `contextKey`s. Its `data.value` is arbitrary
|
|
26
|
+
product-supplied context, so an unrecognised key is dropped before `setContext` is invoked.
|
|
27
|
+
|
|
28
|
+
`ExtensionContextBridgeHost` gains an optional `isAiEnabled`, and relays nothing when it is
|
|
29
|
+
explicitly `false`, so a tenant without Rovo entitlement never has page content cross into the
|
|
30
|
+
extension. `ChatOpenerSubscriber` resolves it from the product's Rovo entitlement store and passes
|
|
31
|
+
it down.
|
|
32
|
+
|
|
3
33
|
## 10.22.0
|
|
4
34
|
|
|
5
35
|
### Minor Changes
|
|
@@ -17,6 +17,24 @@ function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbol
|
|
|
17
17
|
function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { (0, _defineProperty2.default)(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
|
|
18
18
|
/** Selection shape preserved across editor focus loss. */
|
|
19
19
|
|
|
20
|
+
/**
|
|
21
|
+
* The side panel's own origin, supplied by `buildIframeUrl()` in the extension's `sidepanel.ts`.
|
|
22
|
+
* Serves as both the `postMessage` target and the only origin this client accepts messages from.
|
|
23
|
+
*
|
|
24
|
+
* The parameter only selects which of `allowedOrigins` is the peer; it can never introduce one. So
|
|
25
|
+
* an absent parameter, an unknown extension, and an absent list all resolve to `undefined`, leaving
|
|
26
|
+
* the Client without a transport. `useSBSEvaluationBridge` reads the same parameter but falls back to
|
|
27
|
+
* `'*'` — tolerable for telemetry, not for a channel that drives editor writes.
|
|
28
|
+
*/
|
|
29
|
+
var getSidePanelOrigin = function getSidePanelOrigin(allowedOrigins) {
|
|
30
|
+
try {
|
|
31
|
+
var value = new URLSearchParams(window.location.search).get('extensionOrigin');
|
|
32
|
+
return value && allowedOrigins !== null && allowedOrigins !== void 0 && allowedOrigins.includes(value) ? value : undefined;
|
|
33
|
+
} catch (_unused) {
|
|
34
|
+
return undefined;
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
|
|
20
38
|
/**
|
|
21
39
|
* Whether focus currently sits inside the chat. Confluence clears `selection` on
|
|
22
40
|
* `editor-context-payload` both when the user clicks into chat and when they genuinely deselect;
|
|
@@ -40,7 +58,8 @@ var isChatFocused = function isChatFocused() {
|
|
|
40
58
|
* Headless.
|
|
41
59
|
*/
|
|
42
60
|
var ExtensionContextBridgeClient = exports.ExtensionContextBridgeClient = function ExtensionContextBridgeClient(_ref) {
|
|
43
|
-
var transport = _ref.transport
|
|
61
|
+
var transport = _ref.transport,
|
|
62
|
+
allowedOrigins = _ref.allowedOrigins;
|
|
44
63
|
// Self-gated. Matches `BRIDGE_FEATURE_GATE` in `./constants`.
|
|
45
64
|
var active = (0, _fg.fg)('rovo-ext_context_bridge');
|
|
46
65
|
var publish = (0, _main.usePublish)('ai-mate');
|
|
@@ -55,14 +74,18 @@ var ExtensionContextBridgeClient = exports.ExtensionContextBridgeClient = functi
|
|
|
55
74
|
if (typeof window === 'undefined') {
|
|
56
75
|
return undefined;
|
|
57
76
|
}
|
|
58
|
-
|
|
59
|
-
|
|
77
|
+
var sidePanelOrigin = getSidePanelOrigin(allowedOrigins);
|
|
78
|
+
if (!sidePanelOrigin) {
|
|
79
|
+
return undefined;
|
|
80
|
+
}
|
|
81
|
+
// The side panel is a different origin to this iframe, so `targetOrigin` cannot fall back to
|
|
82
|
+
// `selfWindow.location.origin`. `acceptOrigin` follows it: the panel is the only peer.
|
|
60
83
|
return (0, _transport.createWindowTransport)({
|
|
61
84
|
targetWindow: window.parent,
|
|
62
85
|
self: window,
|
|
63
|
-
targetOrigin:
|
|
86
|
+
targetOrigin: sidePanelOrigin
|
|
64
87
|
});
|
|
65
|
-
}, [active, transport]);
|
|
88
|
+
}, [active, transport, allowedOrigins]);
|
|
66
89
|
|
|
67
90
|
// Inbound (READ): product context -> iframe bus.
|
|
68
91
|
(0, _react.useEffect)(function () {
|
|
@@ -122,7 +145,7 @@ var ExtensionContextBridgeClient = exports.ExtensionContextBridgeClient = functi
|
|
|
122
145
|
}
|
|
123
146
|
try {
|
|
124
147
|
activeTransport.send((0, _defineProperty2.default)((0, _defineProperty2.default)((0, _defineProperty2.default)({}, _constants.BRIDGE_MESSAGE_MARKER, true), "direction", _constants.BRIDGE_TO_PRODUCT), "payload", serialized));
|
|
125
|
-
} catch (
|
|
148
|
+
} catch (_unused2) {
|
|
126
149
|
// Never let bridging break the chat iframe.
|
|
127
150
|
}
|
|
128
151
|
});
|
|
@@ -41,7 +41,8 @@ var stampProductContentId = function stampProductContentId(serialized, targetCon
|
|
|
41
41
|
*/
|
|
42
42
|
var ExtensionContextBridgeHost = exports.ExtensionContextBridgeHost = function ExtensionContextBridgeHost(_ref) {
|
|
43
43
|
var transport = _ref.transport,
|
|
44
|
-
getContentId = _ref.getContentId
|
|
44
|
+
getContentId = _ref.getContentId,
|
|
45
|
+
isAiEnabled = _ref.isAiEnabled;
|
|
45
46
|
// Self-gated (not taken from the consumer) so this cannot mount by mistake wherever
|
|
46
47
|
// `ChatOpenerSubscriber` renders. Matches `BRIDGE_FEATURE_GATE` in `./constants`.
|
|
47
48
|
var active = (0, _fg.fg)('rovo-ext_context_bridge');
|
|
@@ -63,6 +64,12 @@ var ExtensionContextBridgeHost = exports.ExtensionContextBridgeHost = function E
|
|
|
63
64
|
if (!activeTransport) {
|
|
64
65
|
return;
|
|
65
66
|
}
|
|
67
|
+
// Checked at relay time rather than when the transport is built: the product renders its
|
|
68
|
+
// `RovoEntitlementSetter` as a sibling of `ChatOpenerSubscriber`, so the store is still
|
|
69
|
+
// unpopulated on this component's first render. By the time a payload arrives it has settled.
|
|
70
|
+
if (isAiEnabled === false) {
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
66
73
|
// Loop guard.
|
|
67
74
|
if (payload.interactionSource === _constants.BRIDGE_SOURCE) {
|
|
68
75
|
return;
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
Object.defineProperty(exports, "__esModule", {
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
|
-
exports.shouldRelayToProduct = exports.getInboundDecision = void 0;
|
|
6
|
+
exports.shouldRelayToProduct = exports.isRelayableContextKey = exports.getInboundDecision = void 0;
|
|
7
7
|
/**
|
|
8
8
|
* Bridge relay policy, asymmetric by direction:
|
|
9
9
|
* - Product → extension (context): relayed iff allowlisted and not denylisted. The denylist is a
|
|
@@ -18,6 +18,26 @@ exports.shouldRelayToProduct = exports.getInboundDecision = void 0;
|
|
|
18
18
|
/** Inbound allowlist — payload types the bridge supports. Widen only as a feature needs it. */
|
|
19
19
|
var INBOUND_ALLOWLIST = new Set(['editor-context-payload', 'set-message-context']);
|
|
20
20
|
|
|
21
|
+
/**
|
|
22
|
+
* Inbound `set-message-context` allowlist, keyed on `contextKey`.
|
|
23
|
+
*
|
|
24
|
+
* The type-level allowlist is not enough for this one payload. Its `data.value` is arbitrary
|
|
25
|
+
* product-supplied context, so — unlike `editor-context-payload`, whose content a co-resident script
|
|
26
|
+
* could equally read from the DOM — it can carry data the extension has no other route to. An
|
|
27
|
+
* allowlist rather than a denylist because a denylist cannot cover keys publishers add later, which
|
|
28
|
+
* is the drift this guards against (SECASR-8597).
|
|
29
|
+
*/
|
|
30
|
+
var INBOUND_CONTEXT_KEYS = new Set([
|
|
31
|
+
// The key every Jira view-context publisher uses. Not reachable yet — only Confluence opts into
|
|
32
|
+
// the bridge, and its own publishers are merge-style, which `./registry` drops. Seeded so the
|
|
33
|
+
// Jira read-context work does not have to rediscover this list.
|
|
34
|
+
'jira_view_context']);
|
|
35
|
+
|
|
36
|
+
/** True if a `set-message-context` payload carrying this `contextKey` may cross to the extension. */
|
|
37
|
+
var isRelayableContextKey = exports.isRelayableContextKey = function isRelayableContextKey(contextKey) {
|
|
38
|
+
return INBOUND_CONTEXT_KEYS.has(contextKey);
|
|
39
|
+
};
|
|
40
|
+
|
|
21
41
|
/** Inbound denylist — control-plane/auth events that must never reach the extension chat. */
|
|
22
42
|
var INBOUND_DENYLIST = new Set(['message-send', 'chat-new', 'chat-open', 'chat-close', 'open-smart-creation-modal', 'open-browse-agent-modal', 'open-browse-agent-sidebar', 'open-chat-debug-modal', 'open-chat-feedback-modal', 'forge-auth-success', 'forge-auth-failure', 'chat-smartlink-3p-post-auth-launch']);
|
|
23
43
|
|
|
@@ -8,6 +8,7 @@ exports.serializePayload = exports.deserializePayload = void 0;
|
|
|
8
8
|
var _typeof2 = _interopRequireDefault(require("@babel/runtime/helpers/typeof"));
|
|
9
9
|
var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
|
|
10
10
|
var _constants = require("./constants");
|
|
11
|
+
var _policy = require("./policy");
|
|
11
12
|
function 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; }
|
|
12
13
|
function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { (0, _defineProperty2.default)(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
|
|
13
14
|
var base = function base(payload) {
|
|
@@ -51,6 +52,10 @@ var SHIMS = {
|
|
|
51
52
|
if (!data || typeof data.setContext !== 'function') {
|
|
52
53
|
return null;
|
|
53
54
|
}
|
|
55
|
+
// Checked before `setContext` runs, so a non-allowlisted key never invokes product code.
|
|
56
|
+
if (!(0, _policy.isRelayableContextKey)(data.contextKey)) {
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
54
59
|
var value;
|
|
55
60
|
try {
|
|
56
61
|
value = data.setContext((0, _defineProperty2.default)({}, MERGE_PROBE_KEY, true));
|
|
@@ -29,16 +29,24 @@ var createWindowTransport = exports.createWindowTransport = function createWindo
|
|
|
29
29
|
_ref$self = _ref.self,
|
|
30
30
|
selfWindow = _ref$self === void 0 ? window : _ref$self,
|
|
31
31
|
_ref$targetOrigin = _ref.targetOrigin,
|
|
32
|
-
targetOrigin = _ref$targetOrigin === void 0 ? selfWindow.location.origin : _ref$targetOrigin
|
|
32
|
+
targetOrigin = _ref$targetOrigin === void 0 ? selfWindow.location.origin : _ref$targetOrigin,
|
|
33
|
+
_ref$acceptOrigin = _ref.acceptOrigin,
|
|
34
|
+
acceptOrigin = _ref$acceptOrigin === void 0 ? targetOrigin : _ref$acceptOrigin;
|
|
33
35
|
return {
|
|
34
36
|
send: function send(message) {
|
|
35
37
|
targetWindow.postMessage(message, targetOrigin);
|
|
36
38
|
},
|
|
37
|
-
// `bind` returns its own unbind function, which is exactly the `subscribe` contract.
|
|
38
39
|
subscribe: function subscribe(handler) {
|
|
40
|
+
if (acceptOrigin === '*') {
|
|
41
|
+
return function () {};
|
|
42
|
+
}
|
|
43
|
+
// `bind` returns its own unbind function, which is exactly the `subscribe` contract.
|
|
39
44
|
return (0, _bindEventListener.bind)(selfWindow, {
|
|
40
45
|
type: 'message',
|
|
41
46
|
listener: function listener(event) {
|
|
47
|
+
if (event.origin !== acceptOrigin) {
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
42
50
|
if (isBridgeMessage(event.data)) {
|
|
43
51
|
handler(event.data);
|
|
44
52
|
}
|
|
@@ -5,4 +5,4 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
5
5
|
});
|
|
6
6
|
exports.ROVO_VALID_PARAMS = exports.ROVO_PARAM_PREFIX = void 0;
|
|
7
7
|
var ROVO_PARAM_PREFIX = exports.ROVO_PARAM_PREFIX = 'rovoChat';
|
|
8
|
-
var ROVO_VALID_PARAMS = exports.ROVO_VALID_PARAMS = ['pathway', 'agentId', 'conversationId', 'prompt', 'cloudId', 'triggerOpen', 'insertPrompt', 'promptLibraryOpen', 'openChatMode'];
|
|
8
|
+
var ROVO_VALID_PARAMS = exports.ROVO_VALID_PARAMS = ['pathway', 'agentId', 'conversationId', 'prompt', 'cloudId', 'triggerOpen', 'insertPrompt', 'stagingAreaOpen', 'messageIdSelectedForPreview', 'invocationIdSelectedForPreview', 'promptLibraryOpen', 'openChatMode'];
|
|
@@ -8,6 +8,24 @@ import { createWindowTransport } from './transport';
|
|
|
8
8
|
|
|
9
9
|
/** Selection shape preserved across editor focus loss. */
|
|
10
10
|
|
|
11
|
+
/**
|
|
12
|
+
* The side panel's own origin, supplied by `buildIframeUrl()` in the extension's `sidepanel.ts`.
|
|
13
|
+
* Serves as both the `postMessage` target and the only origin this client accepts messages from.
|
|
14
|
+
*
|
|
15
|
+
* The parameter only selects which of `allowedOrigins` is the peer; it can never introduce one. So
|
|
16
|
+
* an absent parameter, an unknown extension, and an absent list all resolve to `undefined`, leaving
|
|
17
|
+
* the Client without a transport. `useSBSEvaluationBridge` reads the same parameter but falls back to
|
|
18
|
+
* `'*'` — tolerable for telemetry, not for a channel that drives editor writes.
|
|
19
|
+
*/
|
|
20
|
+
const getSidePanelOrigin = allowedOrigins => {
|
|
21
|
+
try {
|
|
22
|
+
const value = new URLSearchParams(window.location.search).get('extensionOrigin');
|
|
23
|
+
return value && allowedOrigins !== null && allowedOrigins !== void 0 && allowedOrigins.includes(value) ? value : undefined;
|
|
24
|
+
} catch {
|
|
25
|
+
return undefined;
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
|
|
11
29
|
/**
|
|
12
30
|
* Whether focus currently sits inside the chat. Confluence clears `selection` on
|
|
13
31
|
* `editor-context-payload` both when the user clicks into chat and when they genuinely deselect;
|
|
@@ -31,7 +49,8 @@ const isChatFocused = () => {
|
|
|
31
49
|
* Headless.
|
|
32
50
|
*/
|
|
33
51
|
export const ExtensionContextBridgeClient = ({
|
|
34
|
-
transport
|
|
52
|
+
transport,
|
|
53
|
+
allowedOrigins
|
|
35
54
|
}) => {
|
|
36
55
|
// Self-gated. Matches `BRIDGE_FEATURE_GATE` in `./constants`.
|
|
37
56
|
const active = fg('rovo-ext_context_bridge');
|
|
@@ -47,14 +66,18 @@ export const ExtensionContextBridgeClient = ({
|
|
|
47
66
|
if (typeof window === 'undefined') {
|
|
48
67
|
return undefined;
|
|
49
68
|
}
|
|
50
|
-
|
|
51
|
-
|
|
69
|
+
const sidePanelOrigin = getSidePanelOrigin(allowedOrigins);
|
|
70
|
+
if (!sidePanelOrigin) {
|
|
71
|
+
return undefined;
|
|
72
|
+
}
|
|
73
|
+
// The side panel is a different origin to this iframe, so `targetOrigin` cannot fall back to
|
|
74
|
+
// `selfWindow.location.origin`. `acceptOrigin` follows it: the panel is the only peer.
|
|
52
75
|
return createWindowTransport({
|
|
53
76
|
targetWindow: window.parent,
|
|
54
77
|
self: window,
|
|
55
|
-
targetOrigin:
|
|
78
|
+
targetOrigin: sidePanelOrigin
|
|
56
79
|
});
|
|
57
|
-
}, [active, transport]);
|
|
80
|
+
}, [active, transport, allowedOrigins]);
|
|
58
81
|
|
|
59
82
|
// Inbound (READ): product context -> iframe bus.
|
|
60
83
|
useEffect(() => {
|
|
@@ -33,7 +33,8 @@ const stampProductContentId = (serialized, targetContentId) => {
|
|
|
33
33
|
*/
|
|
34
34
|
export const ExtensionContextBridgeHost = ({
|
|
35
35
|
transport,
|
|
36
|
-
getContentId
|
|
36
|
+
getContentId,
|
|
37
|
+
isAiEnabled
|
|
37
38
|
}) => {
|
|
38
39
|
// Self-gated (not taken from the consumer) so this cannot mount by mistake wherever
|
|
39
40
|
// `ChatOpenerSubscriber` renders. Matches `BRIDGE_FEATURE_GATE` in `./constants`.
|
|
@@ -56,6 +57,12 @@ export const ExtensionContextBridgeHost = ({
|
|
|
56
57
|
if (!activeTransport) {
|
|
57
58
|
return;
|
|
58
59
|
}
|
|
60
|
+
// Checked at relay time rather than when the transport is built: the product renders its
|
|
61
|
+
// `RovoEntitlementSetter` as a sibling of `ChatOpenerSubscriber`, so the store is still
|
|
62
|
+
// unpopulated on this component's first render. By the time a payload arrives it has settled.
|
|
63
|
+
if (isAiEnabled === false) {
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
59
66
|
// Loop guard.
|
|
60
67
|
if (payload.interactionSource === BRIDGE_SOURCE) {
|
|
61
68
|
return;
|
|
@@ -12,6 +12,24 @@
|
|
|
12
12
|
/** Inbound allowlist — payload types the bridge supports. Widen only as a feature needs it. */
|
|
13
13
|
const INBOUND_ALLOWLIST = new Set(['editor-context-payload', 'set-message-context']);
|
|
14
14
|
|
|
15
|
+
/**
|
|
16
|
+
* Inbound `set-message-context` allowlist, keyed on `contextKey`.
|
|
17
|
+
*
|
|
18
|
+
* The type-level allowlist is not enough for this one payload. Its `data.value` is arbitrary
|
|
19
|
+
* product-supplied context, so — unlike `editor-context-payload`, whose content a co-resident script
|
|
20
|
+
* could equally read from the DOM — it can carry data the extension has no other route to. An
|
|
21
|
+
* allowlist rather than a denylist because a denylist cannot cover keys publishers add later, which
|
|
22
|
+
* is the drift this guards against (SECASR-8597).
|
|
23
|
+
*/
|
|
24
|
+
const INBOUND_CONTEXT_KEYS = new Set([
|
|
25
|
+
// The key every Jira view-context publisher uses. Not reachable yet — only Confluence opts into
|
|
26
|
+
// the bridge, and its own publishers are merge-style, which `./registry` drops. Seeded so the
|
|
27
|
+
// Jira read-context work does not have to rediscover this list.
|
|
28
|
+
'jira_view_context']);
|
|
29
|
+
|
|
30
|
+
/** True if a `set-message-context` payload carrying this `contextKey` may cross to the extension. */
|
|
31
|
+
export const isRelayableContextKey = contextKey => INBOUND_CONTEXT_KEYS.has(contextKey);
|
|
32
|
+
|
|
15
33
|
/** Inbound denylist — control-plane/auth events that must never reach the extension chat. */
|
|
16
34
|
const INBOUND_DENYLIST = new Set(['message-send', 'chat-new', 'chat-open', 'chat-close', 'open-smart-creation-modal', 'open-browse-agent-modal', 'open-browse-agent-sidebar', 'open-chat-debug-modal', 'open-chat-feedback-modal', 'forge-auth-success', 'forge-auth-failure', 'chat-smartlink-3p-post-auth-launch']);
|
|
17
35
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { BRIDGE_SOURCE } from './constants';
|
|
2
|
+
import { isRelayableContextKey } from './policy';
|
|
2
3
|
const base = payload => ({
|
|
3
4
|
type: payload.type,
|
|
4
5
|
source: payload.source,
|
|
@@ -37,6 +38,10 @@ const SHIMS = {
|
|
|
37
38
|
if (!data || typeof data.setContext !== 'function') {
|
|
38
39
|
return null;
|
|
39
40
|
}
|
|
41
|
+
// Checked before `setContext` runs, so a non-allowlisted key never invokes product code.
|
|
42
|
+
if (!isRelayableContextKey(data.contextKey)) {
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
40
45
|
let value;
|
|
41
46
|
try {
|
|
42
47
|
value = data.setContext({
|
|
@@ -18,18 +18,27 @@ export const isBridgeMessage = value => typeof value === 'object' && value !== n
|
|
|
18
18
|
export const createWindowTransport = ({
|
|
19
19
|
targetWindow,
|
|
20
20
|
self: selfWindow = window,
|
|
21
|
-
targetOrigin = selfWindow.location.origin
|
|
21
|
+
targetOrigin = selfWindow.location.origin,
|
|
22
|
+
acceptOrigin = targetOrigin
|
|
22
23
|
}) => ({
|
|
23
24
|
send: message => {
|
|
24
25
|
targetWindow.postMessage(message, targetOrigin);
|
|
25
26
|
},
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
listener: event => {
|
|
30
|
-
if (isBridgeMessage(event.data)) {
|
|
31
|
-
handler(event.data);
|
|
32
|
-
}
|
|
27
|
+
subscribe: handler => {
|
|
28
|
+
if (acceptOrigin === '*') {
|
|
29
|
+
return () => {};
|
|
33
30
|
}
|
|
34
|
-
|
|
31
|
+
// `bind` returns its own unbind function, which is exactly the `subscribe` contract.
|
|
32
|
+
return bind(selfWindow, {
|
|
33
|
+
type: 'message',
|
|
34
|
+
listener: event => {
|
|
35
|
+
if (event.origin !== acceptOrigin) {
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
if (isBridgeMessage(event.data)) {
|
|
39
|
+
handler(event.data);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
}
|
|
35
44
|
});
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
export const ROVO_PARAM_PREFIX = 'rovoChat';
|
|
2
|
-
export const ROVO_VALID_PARAMS = ['pathway', 'agentId', 'conversationId', 'prompt', 'cloudId', 'triggerOpen', 'insertPrompt', 'promptLibraryOpen', 'openChatMode'];
|
|
2
|
+
export const ROVO_VALID_PARAMS = ['pathway', 'agentId', 'conversationId', 'prompt', 'cloudId', 'triggerOpen', 'insertPrompt', 'stagingAreaOpen', 'messageIdSelectedForPreview', 'invocationIdSelectedForPreview', 'promptLibraryOpen', 'openChatMode'];
|
|
@@ -11,6 +11,24 @@ import { createWindowTransport } from './transport';
|
|
|
11
11
|
|
|
12
12
|
/** Selection shape preserved across editor focus loss. */
|
|
13
13
|
|
|
14
|
+
/**
|
|
15
|
+
* The side panel's own origin, supplied by `buildIframeUrl()` in the extension's `sidepanel.ts`.
|
|
16
|
+
* Serves as both the `postMessage` target and the only origin this client accepts messages from.
|
|
17
|
+
*
|
|
18
|
+
* The parameter only selects which of `allowedOrigins` is the peer; it can never introduce one. So
|
|
19
|
+
* an absent parameter, an unknown extension, and an absent list all resolve to `undefined`, leaving
|
|
20
|
+
* the Client without a transport. `useSBSEvaluationBridge` reads the same parameter but falls back to
|
|
21
|
+
* `'*'` — tolerable for telemetry, not for a channel that drives editor writes.
|
|
22
|
+
*/
|
|
23
|
+
var getSidePanelOrigin = function getSidePanelOrigin(allowedOrigins) {
|
|
24
|
+
try {
|
|
25
|
+
var value = new URLSearchParams(window.location.search).get('extensionOrigin');
|
|
26
|
+
return value && allowedOrigins !== null && allowedOrigins !== void 0 && allowedOrigins.includes(value) ? value : undefined;
|
|
27
|
+
} catch (_unused) {
|
|
28
|
+
return undefined;
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
|
|
14
32
|
/**
|
|
15
33
|
* Whether focus currently sits inside the chat. Confluence clears `selection` on
|
|
16
34
|
* `editor-context-payload` both when the user clicks into chat and when they genuinely deselect;
|
|
@@ -34,7 +52,8 @@ var isChatFocused = function isChatFocused() {
|
|
|
34
52
|
* Headless.
|
|
35
53
|
*/
|
|
36
54
|
export var ExtensionContextBridgeClient = function ExtensionContextBridgeClient(_ref) {
|
|
37
|
-
var transport = _ref.transport
|
|
55
|
+
var transport = _ref.transport,
|
|
56
|
+
allowedOrigins = _ref.allowedOrigins;
|
|
38
57
|
// Self-gated. Matches `BRIDGE_FEATURE_GATE` in `./constants`.
|
|
39
58
|
var active = fg('rovo-ext_context_bridge');
|
|
40
59
|
var publish = usePublish('ai-mate');
|
|
@@ -49,14 +68,18 @@ export var ExtensionContextBridgeClient = function ExtensionContextBridgeClient(
|
|
|
49
68
|
if (typeof window === 'undefined') {
|
|
50
69
|
return undefined;
|
|
51
70
|
}
|
|
52
|
-
|
|
53
|
-
|
|
71
|
+
var sidePanelOrigin = getSidePanelOrigin(allowedOrigins);
|
|
72
|
+
if (!sidePanelOrigin) {
|
|
73
|
+
return undefined;
|
|
74
|
+
}
|
|
75
|
+
// The side panel is a different origin to this iframe, so `targetOrigin` cannot fall back to
|
|
76
|
+
// `selfWindow.location.origin`. `acceptOrigin` follows it: the panel is the only peer.
|
|
54
77
|
return createWindowTransport({
|
|
55
78
|
targetWindow: window.parent,
|
|
56
79
|
self: window,
|
|
57
|
-
targetOrigin:
|
|
80
|
+
targetOrigin: sidePanelOrigin
|
|
58
81
|
});
|
|
59
|
-
}, [active, transport]);
|
|
82
|
+
}, [active, transport, allowedOrigins]);
|
|
60
83
|
|
|
61
84
|
// Inbound (READ): product context -> iframe bus.
|
|
62
85
|
useEffect(function () {
|
|
@@ -116,7 +139,7 @@ export var ExtensionContextBridgeClient = function ExtensionContextBridgeClient(
|
|
|
116
139
|
}
|
|
117
140
|
try {
|
|
118
141
|
activeTransport.send(_defineProperty(_defineProperty(_defineProperty({}, BRIDGE_MESSAGE_MARKER, true), "direction", BRIDGE_TO_PRODUCT), "payload", serialized));
|
|
119
|
-
} catch (
|
|
142
|
+
} catch (_unused2) {
|
|
120
143
|
// Never let bridging break the chat iframe.
|
|
121
144
|
}
|
|
122
145
|
});
|
|
@@ -34,7 +34,8 @@ var stampProductContentId = function stampProductContentId(serialized, targetCon
|
|
|
34
34
|
*/
|
|
35
35
|
export var ExtensionContextBridgeHost = function ExtensionContextBridgeHost(_ref) {
|
|
36
36
|
var transport = _ref.transport,
|
|
37
|
-
getContentId = _ref.getContentId
|
|
37
|
+
getContentId = _ref.getContentId,
|
|
38
|
+
isAiEnabled = _ref.isAiEnabled;
|
|
38
39
|
// Self-gated (not taken from the consumer) so this cannot mount by mistake wherever
|
|
39
40
|
// `ChatOpenerSubscriber` renders. Matches `BRIDGE_FEATURE_GATE` in `./constants`.
|
|
40
41
|
var active = fg('rovo-ext_context_bridge');
|
|
@@ -56,6 +57,12 @@ export var ExtensionContextBridgeHost = function ExtensionContextBridgeHost(_ref
|
|
|
56
57
|
if (!activeTransport) {
|
|
57
58
|
return;
|
|
58
59
|
}
|
|
60
|
+
// Checked at relay time rather than when the transport is built: the product renders its
|
|
61
|
+
// `RovoEntitlementSetter` as a sibling of `ChatOpenerSubscriber`, so the store is still
|
|
62
|
+
// unpopulated on this component's first render. By the time a payload arrives it has settled.
|
|
63
|
+
if (isAiEnabled === false) {
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
59
66
|
// Loop guard.
|
|
60
67
|
if (payload.interactionSource === BRIDGE_SOURCE) {
|
|
61
68
|
return;
|
|
@@ -12,6 +12,26 @@
|
|
|
12
12
|
/** Inbound allowlist — payload types the bridge supports. Widen only as a feature needs it. */
|
|
13
13
|
var INBOUND_ALLOWLIST = new Set(['editor-context-payload', 'set-message-context']);
|
|
14
14
|
|
|
15
|
+
/**
|
|
16
|
+
* Inbound `set-message-context` allowlist, keyed on `contextKey`.
|
|
17
|
+
*
|
|
18
|
+
* The type-level allowlist is not enough for this one payload. Its `data.value` is arbitrary
|
|
19
|
+
* product-supplied context, so — unlike `editor-context-payload`, whose content a co-resident script
|
|
20
|
+
* could equally read from the DOM — it can carry data the extension has no other route to. An
|
|
21
|
+
* allowlist rather than a denylist because a denylist cannot cover keys publishers add later, which
|
|
22
|
+
* is the drift this guards against (SECASR-8597).
|
|
23
|
+
*/
|
|
24
|
+
var INBOUND_CONTEXT_KEYS = new Set([
|
|
25
|
+
// The key every Jira view-context publisher uses. Not reachable yet — only Confluence opts into
|
|
26
|
+
// the bridge, and its own publishers are merge-style, which `./registry` drops. Seeded so the
|
|
27
|
+
// Jira read-context work does not have to rediscover this list.
|
|
28
|
+
'jira_view_context']);
|
|
29
|
+
|
|
30
|
+
/** True if a `set-message-context` payload carrying this `contextKey` may cross to the extension. */
|
|
31
|
+
export var isRelayableContextKey = function isRelayableContextKey(contextKey) {
|
|
32
|
+
return INBOUND_CONTEXT_KEYS.has(contextKey);
|
|
33
|
+
};
|
|
34
|
+
|
|
15
35
|
/** Inbound denylist — control-plane/auth events that must never reach the extension chat. */
|
|
16
36
|
var INBOUND_DENYLIST = new Set(['message-send', 'chat-new', 'chat-open', 'chat-close', 'open-smart-creation-modal', 'open-browse-agent-modal', 'open-browse-agent-sidebar', 'open-chat-debug-modal', 'open-chat-feedback-modal', 'forge-auth-success', 'forge-auth-failure', 'chat-smartlink-3p-post-auth-launch']);
|
|
17
37
|
|
|
@@ -3,6 +3,7 @@ import _defineProperty from "@babel/runtime/helpers/defineProperty";
|
|
|
3
3
|
function 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; }
|
|
4
4
|
function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
|
|
5
5
|
import { BRIDGE_SOURCE } from './constants';
|
|
6
|
+
import { isRelayableContextKey } from './policy';
|
|
6
7
|
var base = function base(payload) {
|
|
7
8
|
return {
|
|
8
9
|
type: payload.type,
|
|
@@ -44,6 +45,10 @@ var SHIMS = {
|
|
|
44
45
|
if (!data || typeof data.setContext !== 'function') {
|
|
45
46
|
return null;
|
|
46
47
|
}
|
|
48
|
+
// Checked before `setContext` runs, so a non-allowlisted key never invokes product code.
|
|
49
|
+
if (!isRelayableContextKey(data.contextKey)) {
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
47
52
|
var value;
|
|
48
53
|
try {
|
|
49
54
|
value = data.setContext(_defineProperty({}, MERGE_PROBE_KEY, true));
|
|
@@ -23,16 +23,24 @@ export var createWindowTransport = function createWindowTransport(_ref) {
|
|
|
23
23
|
_ref$self = _ref.self,
|
|
24
24
|
selfWindow = _ref$self === void 0 ? window : _ref$self,
|
|
25
25
|
_ref$targetOrigin = _ref.targetOrigin,
|
|
26
|
-
targetOrigin = _ref$targetOrigin === void 0 ? selfWindow.location.origin : _ref$targetOrigin
|
|
26
|
+
targetOrigin = _ref$targetOrigin === void 0 ? selfWindow.location.origin : _ref$targetOrigin,
|
|
27
|
+
_ref$acceptOrigin = _ref.acceptOrigin,
|
|
28
|
+
acceptOrigin = _ref$acceptOrigin === void 0 ? targetOrigin : _ref$acceptOrigin;
|
|
27
29
|
return {
|
|
28
30
|
send: function send(message) {
|
|
29
31
|
targetWindow.postMessage(message, targetOrigin);
|
|
30
32
|
},
|
|
31
|
-
// `bind` returns its own unbind function, which is exactly the `subscribe` contract.
|
|
32
33
|
subscribe: function subscribe(handler) {
|
|
34
|
+
if (acceptOrigin === '*') {
|
|
35
|
+
return function () {};
|
|
36
|
+
}
|
|
37
|
+
// `bind` returns its own unbind function, which is exactly the `subscribe` contract.
|
|
33
38
|
return bind(selfWindow, {
|
|
34
39
|
type: 'message',
|
|
35
40
|
listener: function listener(event) {
|
|
41
|
+
if (event.origin !== acceptOrigin) {
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
36
44
|
if (isBridgeMessage(event.data)) {
|
|
37
45
|
handler(event.data);
|
|
38
46
|
}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
export var ROVO_PARAM_PREFIX = 'rovoChat';
|
|
2
|
-
export var ROVO_VALID_PARAMS = ['pathway', 'agentId', 'conversationId', 'prompt', 'cloudId', 'triggerOpen', 'insertPrompt', 'promptLibraryOpen', 'openChatMode'];
|
|
2
|
+
export var ROVO_VALID_PARAMS = ['pathway', 'agentId', 'conversationId', 'prompt', 'cloudId', 'triggerOpen', 'insertPrompt', 'stagingAreaOpen', 'messageIdSelectedForPreview', 'invocationIdSelectedForPreview', 'promptLibraryOpen', 'openChatMode'];
|
|
@@ -2,6 +2,16 @@ import { type Transport } from './transport';
|
|
|
2
2
|
type Props = {
|
|
3
3
|
/** Override the transport (tests). Defaults to iframe<->parent `postMessage`. */
|
|
4
4
|
transport?: Transport;
|
|
5
|
+
/**
|
|
6
|
+
* Full origins the chat will talk to, e.g. `chrome-extension://<id>` — not bare ids. Supplied by
|
|
7
|
+
* the consumer, which knows its own published ids, so a new extension channel needs no platform
|
|
8
|
+
* release. Must be a stable reference; the transport is memoised on it.
|
|
9
|
+
*
|
|
10
|
+
* Omitted, the Client is inert. `frame-ancestors` on this app already limits who can frame it, but
|
|
11
|
+
* any extension holding `declarativeNetRequest` can strip that header, so this list is what
|
|
12
|
+
* actually pins the channel to our own extension rather than any installed one.
|
|
13
|
+
*/
|
|
14
|
+
allowedOrigins?: readonly string[];
|
|
5
15
|
};
|
|
6
16
|
/**
|
|
7
17
|
* Chat-iframe half of the bridge.
|
|
@@ -15,5 +25,5 @@ type Props = {
|
|
|
15
25
|
*
|
|
16
26
|
* Headless.
|
|
17
27
|
*/
|
|
18
|
-
export declare const ExtensionContextBridgeClient: ({ transport }: Props) => null;
|
|
28
|
+
export declare const ExtensionContextBridgeClient: ({ transport, allowedOrigins }: Props) => null;
|
|
19
29
|
export {};
|
|
@@ -4,6 +4,16 @@ type Props = {
|
|
|
4
4
|
transport?: Transport;
|
|
5
5
|
/** Resolves the current content id at relay time (a getter, so the Host reads it lazily). */
|
|
6
6
|
getContentId?: () => string | undefined;
|
|
7
|
+
/**
|
|
8
|
+
* Whether the viewer is entitled to Rovo. `false` relays nothing, so a tenant with AI turned off
|
|
9
|
+
* never has page content cross into the extension (SECASR-8597).
|
|
10
|
+
*
|
|
11
|
+
* Supplied by the consumer for the same reason as `getContentId`: the signal lives in
|
|
12
|
+
* `conversation-assistant-entitlement` and this package stays clear of that dependency.
|
|
13
|
+
* `undefined` means the consumer expressed no opinion, and relaying proceeds — every in-tree
|
|
14
|
+
* mount passes it explicitly, so that only covers bespoke embeddings.
|
|
15
|
+
*/
|
|
16
|
+
isAiEnabled?: boolean;
|
|
7
17
|
};
|
|
8
18
|
/**
|
|
9
19
|
* Product-window half of the bridge. Observes the `ai-mate` bus and mirrors allowlisted context out
|
|
@@ -12,5 +22,5 @@ type Props = {
|
|
|
12
22
|
* The reverse direction is handled by the sibling `RovoPostMessagePubsubListener` in
|
|
13
23
|
* `ChatOpenerSubscriber`, not here. Headless; mounts at product root via `ChatOpenerSubscriber`.
|
|
14
24
|
*/
|
|
15
|
-
export declare const ExtensionContextBridgeHost: ({ transport, getContentId }: Props) => null;
|
|
25
|
+
export declare const ExtensionContextBridgeHost: ({ transport, getContentId, isAiEnabled, }: Props) => null;
|
|
16
26
|
export {};
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { type Payload } from '../../../types';
|
|
2
|
+
/** True if a `set-message-context` payload carrying this `contextKey` may cross to the extension. */
|
|
3
|
+
export declare const isRelayableContextKey: (contextKey: string) => boolean;
|
|
2
4
|
/** Why a product-bus payload was or was not relayed. */
|
|
3
5
|
type InboundDecision = 'allow' | 'not-allowlisted' | 'denylisted';
|
|
4
6
|
/** Classify a product-bus payload. Relay iff `allow`. */
|
|
@@ -30,9 +30,19 @@ export type Transport = {
|
|
|
30
30
|
};
|
|
31
31
|
export declare const isBridgeMessage: (value: unknown) => value is BridgeMessage;
|
|
32
32
|
/** `postMessage`-based transport. Callers override `targetWindow`/`self` for the iframe side. */
|
|
33
|
-
export declare const createWindowTransport: ({ targetWindow, self: selfWindow, targetOrigin, }: {
|
|
33
|
+
export declare const createWindowTransport: ({ targetWindow, self: selfWindow, targetOrigin, acceptOrigin, }: {
|
|
34
34
|
targetWindow: Window;
|
|
35
35
|
self?: Window;
|
|
36
36
|
targetOrigin?: string;
|
|
37
|
+
/**
|
|
38
|
+
* The only origin whose messages `subscribe` dispatches. Defaults to `targetOrigin`, which is
|
|
39
|
+
* right for both halves of the bridge: each has exactly one peer, and it is the same peer in
|
|
40
|
+
* both directions.
|
|
41
|
+
*
|
|
42
|
+
* `'*'` is refused. A wildcard here would let anything able to reach this window inject context
|
|
43
|
+
* that drives editor writes. It disables `subscribe` rather than throwing, because the bridge
|
|
44
|
+
* must never break the page it runs on.
|
|
45
|
+
*/
|
|
46
|
+
acceptOrigin?: string;
|
|
37
47
|
}) => Transport;
|
|
38
48
|
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@atlaskit/rovo-triggers",
|
|
3
|
-
"version": "10.
|
|
3
|
+
"version": "10.23.1",
|
|
4
4
|
"description": "Provides various trigger events to drive Rovo Chat functionality, such as a publish-subscribe and URL parameter hooks",
|
|
5
5
|
"author": "Atlassian Pty Ltd",
|
|
6
6
|
"publishConfig": {
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
],
|
|
29
29
|
"atlaskit:src": "src/index.ts",
|
|
30
30
|
"dependencies": {
|
|
31
|
-
"@atlaskit/adf-schema": "^57.
|
|
31
|
+
"@atlaskit/adf-schema": "^57.2.0",
|
|
32
32
|
"@atlaskit/platform-feature-flags": "^2.1.0",
|
|
33
33
|
"@atlaskit/react-compiler-gating": "^0.2.0",
|
|
34
34
|
"@babel/runtime": "^7.0.0",
|
|
@@ -40,11 +40,11 @@
|
|
|
40
40
|
"devDependencies": {
|
|
41
41
|
"@af/integration-testing": "workspace:^",
|
|
42
42
|
"@af/visual-regression": "workspace:^",
|
|
43
|
-
"@atlaskit/css": "^1.
|
|
43
|
+
"@atlaskit/css": "^1.1.0",
|
|
44
44
|
"@atlaskit/primitives": "^22.4.0",
|
|
45
45
|
"@atlaskit/ssr": "workspace:^",
|
|
46
46
|
"@atlassian/a11y-jest-testing": "^0.17.0",
|
|
47
|
-
"@atlassian/conversation-assistant-service-api": "^17.
|
|
47
|
+
"@atlassian/conversation-assistant-service-api": "^17.2.0",
|
|
48
48
|
"@atlassian/feature-flags-test-utils": "^1.2.0",
|
|
49
49
|
"@atlassian/testing-library": "^0.11.0",
|
|
50
50
|
"@testing-library/react": "^16.3.0",
|