@oxvo/ai-live-assist 7.5.8 → 7.5.9
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/cjs/AiLiveAssist.d.ts +1 -1
- package/cjs/context.d.ts +3 -0
- package/cjs/context.js +182 -74
- package/cjs/liveControlChannel.d.ts +1 -1
- package/cjs/types.d.ts +8 -0
- package/cjs/ui.js +8 -9
- package/cjs/version.d.ts +1 -1
- package/cjs/version.js +1 -1
- package/cjs/voicePresenceUi.js +8 -2
- package/lib/AiLiveAssist.d.ts +1 -1
- package/lib/context.d.ts +3 -0
- package/lib/context.js +182 -74
- package/lib/liveControlChannel.d.ts +1 -1
- package/lib/types.d.ts +8 -0
- package/lib/ui.js +8 -9
- package/lib/version.d.ts +1 -1
- package/lib/version.js +1 -1
- package/lib/voicePresenceUi.js +8 -2
- package/package.json +1 -1
package/cjs/AiLiveAssist.d.ts
CHANGED
|
@@ -34,7 +34,7 @@ export declare const normalizeAiGuideOpenInput: (input?: AiGuideOpenInput) => Re
|
|
|
34
34
|
export declare const resolveAiGuideProtocolVersion: (bootstrap: Pick<BootstrapConfig, "experienceMode" | "minimumProtocolVersion" | "protocol">) => 1 | 2;
|
|
35
35
|
export default class AiLiveAssist {
|
|
36
36
|
private readonly options;
|
|
37
|
-
readonly version = "7.5.
|
|
37
|
+
readonly version = "7.5.9";
|
|
38
38
|
private readonly host;
|
|
39
39
|
private readonly guideAdmission;
|
|
40
40
|
private readonly standaloneAdmissionPending;
|
package/cjs/context.d.ts
CHANGED
|
@@ -27,6 +27,7 @@ export declare class PageContextCollector {
|
|
|
27
27
|
private targetIds;
|
|
28
28
|
private pageScrollIds;
|
|
29
29
|
private navigationTargetBindings;
|
|
30
|
+
private navigationDisclosureLinks;
|
|
30
31
|
private fieldTargetBindings;
|
|
31
32
|
private fieldAppliedRevisions;
|
|
32
33
|
private textIds;
|
|
@@ -70,6 +71,8 @@ export declare class PageContextCollector {
|
|
|
70
71
|
private type;
|
|
71
72
|
private select;
|
|
72
73
|
private submit;
|
|
74
|
+
private exposedNavigationLinks;
|
|
75
|
+
private expandedState;
|
|
73
76
|
private safeState;
|
|
74
77
|
private scrollTargetIntoView;
|
|
75
78
|
private waitForScrollSettlement;
|
package/cjs/context.js
CHANGED
|
@@ -20,7 +20,7 @@ const RELEASED_NAVIGATION_SETTLEMENT_TIMEOUT_MS = 5000;
|
|
|
20
20
|
const FRAMER_GENERATED_PAGE_SELECTOR = '[data-framer-generated-page]';
|
|
21
21
|
const FRAMER_TOGGLE_ROOT_SELECTOR = '[data-framer-name="Toggle"]';
|
|
22
22
|
const FRAMER_TOGGLE_CONTROL_SELECTOR = 'div[name="Toggle Switch"][data-highlight="true"][tabindex]';
|
|
23
|
-
const ACTIONABLE_MUTATION_SELECTOR = `a[href],button,input,select,textarea,summary,[role="button"],[role="link"],[role="tab"],[role="menuitem"],[role="option"],[role="checkbox"],[role="radio"],[role="switch"],[role="combobox"],[role="listbox"],[role="treeitem"],[role="slider"],[role="spinbutton"],[role="searchbox"],[role="textbox"],${FRAMER_TOGGLE_CONTROL_SELECTOR}`;
|
|
23
|
+
const ACTIONABLE_MUTATION_SELECTOR = `a[href],button,input,select,textarea,summary,[role="button"],[role="link"],[role="tab"],[role="menuitem"],[role="option"],[role="checkbox"],[role="radio"],[role="switch"],[role="combobox"],[role="listbox"],[role="treeitem"],[role="slider"],[role="spinbutton"],[role="searchbox"],[role="textbox"],[data-highlight="true"][tabindex],${FRAMER_TOGGLE_CONTROL_SELECTOR}`;
|
|
24
24
|
const MAX_FRAMER_TOGGLE_LABEL = 120;
|
|
25
25
|
const PRIVATE_SELECTOR = [
|
|
26
26
|
'[data-oxvo-ai-live-assist-root]',
|
|
@@ -80,6 +80,7 @@ const navigationTargetBinding = (target) => canonicalJson({
|
|
|
80
80
|
autocomplete: target.autocomplete ?? null,
|
|
81
81
|
semanticRegion: target.semanticRegion ?? null,
|
|
82
82
|
...(target.navigationMenu === true ? { navigationMenu: true } : {}),
|
|
83
|
+
...(target.navigation ? { navigation: target.navigation } : {}),
|
|
83
84
|
destinationPath: target.destinationPath ?? null,
|
|
84
85
|
sensitivity: target.sensitivity,
|
|
85
86
|
protectedRegion: target.protectedRegion ?? null,
|
|
@@ -125,6 +126,7 @@ const actionTargetFingerprintBinding = (target) => canonicalJson({
|
|
|
125
126
|
autocomplete: target.autocomplete ?? null,
|
|
126
127
|
semanticRegion: target.semanticRegion ?? null,
|
|
127
128
|
...(target.navigationMenu === true ? { navigationMenu: true } : {}),
|
|
129
|
+
...(target.navigation ? { navigation: target.navigation } : {}),
|
|
128
130
|
destinationPath: target.destinationPath ?? null,
|
|
129
131
|
sensitivity: target.sensitivity,
|
|
130
132
|
protectedRegion: target.protectedRegion ?? null,
|
|
@@ -222,8 +224,27 @@ const interactionPoint = (element, trustedGuideRoot = null) => {
|
|
|
222
224
|
return null;
|
|
223
225
|
};
|
|
224
226
|
const isCovered = (element, trustedGuideRoot = null) => interactionPoint(element, trustedGuideRoot) === null;
|
|
227
|
+
const FRAMER_NAVIGATION_CONTROL_SELECTOR = 'div[data-highlight="true"][tabindex]';
|
|
228
|
+
const framerNavigationRoot = (element) => {
|
|
229
|
+
if (!(element instanceof HTMLElement) || !element.matches(FRAMER_NAVIGATION_CONTROL_SELECTOR) ||
|
|
230
|
+
element.tabIndex < 0 || element.hasAttribute('role') || element.hasAttribute('aria-pressed') ||
|
|
231
|
+
element.hasAttribute('aria-checked') || !element.getAttribute('data-framer-name')?.trim() ||
|
|
232
|
+
!element.closest(FRAMER_GENERATED_PAGE_SELECTOR) || element.closest(`form,${PRIVATE_SELECTOR}`) ||
|
|
233
|
+
element.querySelector('a,button,input,select,textarea,summary,form,[role],[tabindex],[contenteditable]') ||
|
|
234
|
+
element.textContent?.trim())
|
|
235
|
+
return null;
|
|
236
|
+
const nav = element.closest('nav,[role="navigation"]');
|
|
237
|
+
if (!nav)
|
|
238
|
+
return null;
|
|
239
|
+
const candidates = nav.querySelectorAll(FRAMER_NAVIGATION_CONTROL_SELECTOR);
|
|
240
|
+
if (candidates.length > MAX_TARGETS)
|
|
241
|
+
return null;
|
|
242
|
+
const rendered = Array.from(candidates).filter(candidate => isRendered(candidate) &&
|
|
243
|
+
candidate.closest('nav,[role="navigation"]') === nav);
|
|
244
|
+
return rendered.length === 1 && rendered[0] === element ? nav : null;
|
|
245
|
+
};
|
|
225
246
|
const isNavigationMenu = (element, role) => {
|
|
226
|
-
if (role !== 'button' || literalBooleanAttribute(element, 'aria-expanded') === undefined ||
|
|
247
|
+
if (role !== 'button' || (literalBooleanAttribute(element, 'aria-expanded') === undefined && !framerNavigationRoot(element)) ||
|
|
227
248
|
element.hasAttribute('aria-pressed') || element.hasAttribute('aria-checked') ||
|
|
228
249
|
element instanceof HTMLButtonElement && element.form !== null && element.type !== 'button')
|
|
229
250
|
return false;
|
|
@@ -246,27 +267,31 @@ const sameNavigationDestination = (actual, expected) => {
|
|
|
246
267
|
const left = new URL(actual), right = new URL(expected);
|
|
247
268
|
return left.origin === right.origin && left.pathname === right.pathname && left.search === right.search;
|
|
248
269
|
};
|
|
249
|
-
const
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
return
|
|
270
|
+
const publicNavigation = (element) => {
|
|
271
|
+
if (!(element instanceof HTMLAnchorElement) || !element.getAttribute('href')?.trim() ||
|
|
272
|
+
!supportsCurrentTabNavigation(element))
|
|
273
|
+
return undefined;
|
|
253
274
|
try {
|
|
254
275
|
const destination = new URL(element.href, window.location.href);
|
|
255
|
-
if (
|
|
256
|
-
destination.
|
|
257
|
-
destination.search ||
|
|
258
|
-
destination.
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
return null;
|
|
263
|
-
}
|
|
264
|
-
return destination.pathname;
|
|
276
|
+
if (!['http:', 'https:'].includes(destination.protocol) ||
|
|
277
|
+
(destination.origin !== window.location.origin && destination.protocol !== 'https:') ||
|
|
278
|
+
destination.username || destination.password || destination.search || destination.hash ||
|
|
279
|
+
destination.href.length > 2048 || destination.pathname.length > 1024 ||
|
|
280
|
+
!/^\/(?:[A-Za-z0-9_~-]+(?:\.[A-Za-z0-9_~-]+)*\/?)*$/.test(destination.pathname))
|
|
281
|
+
return undefined;
|
|
282
|
+
return { url: destination.href, target: linkBrowsingTarget(element) === '_blank' ? '_blank' : '_self' };
|
|
265
283
|
}
|
|
266
284
|
catch {
|
|
267
|
-
return
|
|
285
|
+
return undefined;
|
|
268
286
|
}
|
|
269
287
|
};
|
|
288
|
+
const safeDestinationPath = (element) => {
|
|
289
|
+
const navigation = publicNavigation(element);
|
|
290
|
+
if (!navigation)
|
|
291
|
+
return null;
|
|
292
|
+
const destination = new URL(navigation.url);
|
|
293
|
+
return destination.origin === window.location.origin ? destination.pathname : null;
|
|
294
|
+
};
|
|
270
295
|
const sensitivityFor = (element, name) => {
|
|
271
296
|
const input = element instanceof HTMLInputElement ? element : null;
|
|
272
297
|
const type = input?.type.toLowerCase() ?? '';
|
|
@@ -300,6 +325,8 @@ const sensitivityFor = (element, name) => {
|
|
|
300
325
|
const roleFor = (element) => {
|
|
301
326
|
if (framerToggleDescriptor(element))
|
|
302
327
|
return 'checkbox';
|
|
328
|
+
if (framerNavigationRoot(element))
|
|
329
|
+
return 'button';
|
|
303
330
|
const explicit = normalize(element.getAttribute('role') ?? '', 64);
|
|
304
331
|
if (explicit)
|
|
305
332
|
return explicit;
|
|
@@ -367,6 +394,7 @@ const nameFor = (element) => {
|
|
|
367
394
|
return normalize(semanticName ||
|
|
368
395
|
associatedLabel ||
|
|
369
396
|
attributeName ||
|
|
397
|
+
(framerNavigationRoot(element) ? safeAttribute('data-framer-name', 120) : '') ||
|
|
370
398
|
(buttonValue && sensitivityFor(element, buttonValue) === 'none' ? buttonValue : '') ||
|
|
371
399
|
(safeDestinationPath(element) === '/' ? 'Home' : ''), 300);
|
|
372
400
|
};
|
|
@@ -768,6 +796,7 @@ class PageContextCollector {
|
|
|
768
796
|
this.targetIds = new WeakMap();
|
|
769
797
|
this.pageScrollIds = new WeakMap();
|
|
770
798
|
this.navigationTargetBindings = new WeakMap();
|
|
799
|
+
this.navigationDisclosureLinks = new WeakMap();
|
|
771
800
|
this.fieldTargetBindings = new WeakMap();
|
|
772
801
|
this.fieldAppliedRevisions = new WeakMap();
|
|
773
802
|
this.textIds = new WeakMap();
|
|
@@ -991,6 +1020,9 @@ class PageContextCollector {
|
|
|
991
1020
|
'data-framer-name',
|
|
992
1021
|
'disabled',
|
|
993
1022
|
'href',
|
|
1023
|
+
'target',
|
|
1024
|
+
'download',
|
|
1025
|
+
'data-highlight',
|
|
994
1026
|
'hidden',
|
|
995
1027
|
'inert',
|
|
996
1028
|
'role',
|
|
@@ -1037,6 +1069,7 @@ class PageContextCollector {
|
|
|
1037
1069
|
document.removeEventListener('change', this.handleControlStateChange, true);
|
|
1038
1070
|
this.restoreNavigationHooks();
|
|
1039
1071
|
this.targets.clear();
|
|
1072
|
+
this.navigationDisclosureLinks = new WeakMap();
|
|
1040
1073
|
this.executingActionId = null;
|
|
1041
1074
|
this.pendingNavigation = null;
|
|
1042
1075
|
this.pendingCapture = false;
|
|
@@ -1077,7 +1110,8 @@ class PageContextCollector {
|
|
|
1077
1110
|
continue;
|
|
1078
1111
|
const role = roleFor(element);
|
|
1079
1112
|
const hasStatefulButtonAttribute = role === 'button' &&
|
|
1080
|
-
(element.hasAttribute('aria-expanded') || element.hasAttribute('aria-pressed')
|
|
1113
|
+
(element.hasAttribute('aria-expanded') || element.hasAttribute('aria-pressed') ||
|
|
1114
|
+
Boolean(framerNavigationRoot(element)));
|
|
1081
1115
|
const hasUnsupportedMixedPressedState = role === 'button' &&
|
|
1082
1116
|
element.getAttribute('aria-pressed')?.trim().toLowerCase() === 'mixed';
|
|
1083
1117
|
if (hasUnsupportedMixedPressedState ||
|
|
@@ -1114,7 +1148,8 @@ class PageContextCollector {
|
|
|
1114
1148
|
: accessibleNameSourceSensitivity(element);
|
|
1115
1149
|
const visible = isVisible(element);
|
|
1116
1150
|
const destinationPath = safeDestinationPath(element);
|
|
1117
|
-
const
|
|
1151
|
+
const navigation = publicNavigation(element);
|
|
1152
|
+
const expanded = this.expandedState(element);
|
|
1118
1153
|
const pressed = literalBooleanAttribute(element, 'aria-pressed');
|
|
1119
1154
|
const checked = checkedStateFor(element);
|
|
1120
1155
|
const targetIdentity = {
|
|
@@ -1131,6 +1166,7 @@ class PageContextCollector {
|
|
|
1131
1166
|
...(semanticRegion ? { semanticRegion } : {}),
|
|
1132
1167
|
...(this.statefulButtonActionProof && isNavigationMenu(element, role) ? { navigationMenu: true } : {}),
|
|
1133
1168
|
...(destinationPath ? { destinationPath } : {}),
|
|
1169
|
+
...(navigation ? { navigation } : {}),
|
|
1134
1170
|
sensitivity,
|
|
1135
1171
|
protectedRegion: sensitivity !== 'none' || regionAssessment.matchedRuleDigests.length > 0,
|
|
1136
1172
|
frameOrigin: 'same_origin',
|
|
@@ -1148,7 +1184,7 @@ class PageContextCollector {
|
|
|
1148
1184
|
};
|
|
1149
1185
|
const priorTargetId = this.targetIds.get(element);
|
|
1150
1186
|
const boundNavigation = targetIdentity.consequence === 'navigation' &&
|
|
1151
|
-
|
|
1187
|
+
targetIdentity.navigation !== undefined;
|
|
1152
1188
|
const binding = boundNavigation ? navigationTargetBinding(targetIdentity) : null;
|
|
1153
1189
|
const priorBinding = this.navigationTargetBindings.get(element);
|
|
1154
1190
|
const boundField = isNativeEditableField(element) && sensitivity === 'none' &&
|
|
@@ -1420,12 +1456,13 @@ class PageContextCollector {
|
|
|
1420
1456
|
grant.targetId.startsWith(`${BOUND_FIELD_TARGET_PREFIX}_`) &&
|
|
1421
1457
|
Boolean(current && isNativeEditableField(current.element) && this.fieldTargetBindings.has(current.element));
|
|
1422
1458
|
const stablePageScroll = (current) => grant.action === 'scroll' && Boolean(current?.pageScroll && current.element === document.scrollingElement);
|
|
1459
|
+
const stableNavigation = (current) => (grant.action === 'click' || grant.action === 'scroll') &&
|
|
1460
|
+
grant.targetId.startsWith(`${BOUND_NAVIGATION_TARGET_PREFIX}_`) &&
|
|
1461
|
+
current?.element instanceof HTMLAnchorElement &&
|
|
1462
|
+
current.target.consequence === 'navigation' && current.target.navigation !== undefined;
|
|
1423
1463
|
const revisionMatchesOrStableTarget = () => grant.domRevision === this.revision ||
|
|
1424
1464
|
(grant.domRevision < this.revision &&
|
|
1425
|
-
(stableField(handle) || stablePageScroll(handle) || (
|
|
1426
|
-
handle?.element instanceof HTMLAnchorElement &&
|
|
1427
|
-
handle.target.consequence === 'navigation' &&
|
|
1428
|
-
typeof handle.target.destinationPath === 'string')));
|
|
1465
|
+
(stableField(handle) || stablePageScroll(handle) || stableNavigation(handle)));
|
|
1429
1466
|
if (expiresAt <= Date.now() ||
|
|
1430
1467
|
grant.pageId !== this.pageId ||
|
|
1431
1468
|
grant.pageUrl !== safePageUrl() ||
|
|
@@ -1462,7 +1499,7 @@ class PageContextCollector {
|
|
|
1462
1499
|
grant.pageUrl !== safePageUrl() ||
|
|
1463
1500
|
grant.policyVersion !== this.policyVersion ||
|
|
1464
1501
|
!revisionMatchesOrStableTarget() ||
|
|
1465
|
-
(handle !== fingerprintedHandle && !((stableField(handle) || stablePageScroll(handle)) &&
|
|
1502
|
+
(handle !== fingerprintedHandle && !((stableField(handle) || stablePageScroll(handle) || stableNavigation(handle)) &&
|
|
1466
1503
|
handle?.element === fingerprintedHandle.element &&
|
|
1467
1504
|
actionTargetFingerprintBinding(handle.target) === actionTargetFingerprintBinding(fingerprintedHandle.target))) ||
|
|
1468
1505
|
!handle ||
|
|
@@ -1548,6 +1585,9 @@ class PageContextCollector {
|
|
|
1548
1585
|
this.releasedNavigation = null;
|
|
1549
1586
|
this.finishReleasedNavigationSettlement('rejected');
|
|
1550
1587
|
let postActionSnapshot = null;
|
|
1588
|
+
let navigationReceipt;
|
|
1589
|
+
const disclosureRoot = handle.target.navigationMenu ? framerNavigationRoot(element) : null;
|
|
1590
|
+
const priorDisclosureLinks = disclosureRoot ? this.exposedNavigationLinks(disclosureRoot) : null;
|
|
1551
1591
|
try {
|
|
1552
1592
|
switch (grant.action) {
|
|
1553
1593
|
case 'scroll': {
|
|
@@ -1658,7 +1698,7 @@ class PageContextCollector {
|
|
|
1658
1698
|
element.focus({ preventScroll: false });
|
|
1659
1699
|
break;
|
|
1660
1700
|
case 'click':
|
|
1661
|
-
this.click(element, handle, grant.actionId, grant.desiredChecked, grant.desiredExpanded, grant.desiredPressed, grant.destinationPath);
|
|
1701
|
+
navigationReceipt = this.click(element, handle, grant.actionId, grant.desiredChecked, grant.desiredExpanded, grant.desiredPressed, grant.destinationPath);
|
|
1662
1702
|
break;
|
|
1663
1703
|
case 'type':
|
|
1664
1704
|
this.type(element, grant.value ?? '');
|
|
@@ -1680,9 +1720,28 @@ class PageContextCollector {
|
|
|
1680
1720
|
if (grant.action !== 'scroll') {
|
|
1681
1721
|
await new Promise((resolve) => setTimeout(resolve, 80));
|
|
1682
1722
|
}
|
|
1723
|
+
if (handle.target.navigationMenu && grant.action === 'click') {
|
|
1724
|
+
const deadline = Date.now() + 1000;
|
|
1725
|
+
for (let frames = 0; frames < 60 && Date.now() < deadline && element.isConnected && !executionStale(); frames++) {
|
|
1726
|
+
if (disclosureRoot && priorDisclosureLinks) {
|
|
1727
|
+
const added = new Set([...this.exposedNavigationLinks(disclosureRoot)]
|
|
1728
|
+
.filter(link => !priorDisclosureLinks.has(link)));
|
|
1729
|
+
if (added.size)
|
|
1730
|
+
this.navigationDisclosureLinks.set(element, added);
|
|
1731
|
+
}
|
|
1732
|
+
if (this.actionApplied(grant, element, handle))
|
|
1733
|
+
break;
|
|
1734
|
+
await nextAnimationFrameOrTimeout(Math.min(50, deadline - Date.now()));
|
|
1735
|
+
}
|
|
1736
|
+
}
|
|
1683
1737
|
const postActionStale = executionStale();
|
|
1684
|
-
if (postActionStale)
|
|
1685
|
-
|
|
1738
|
+
if (postActionStale) {
|
|
1739
|
+
this.deferredNavigation = null;
|
|
1740
|
+
return navigationReceipt ? { ...postActionStale, observed: {
|
|
1741
|
+
...postActionStale.observed, targetState: 'unknown',
|
|
1742
|
+
navigation: { url: navigationReceipt.url, outcome: 'unconfirmed' },
|
|
1743
|
+
} } : postActionStale;
|
|
1744
|
+
}
|
|
1686
1745
|
if (!this.actionApplied(grant, element, handle)) {
|
|
1687
1746
|
throw new Error('The approved value was not applied.');
|
|
1688
1747
|
}
|
|
@@ -1702,16 +1761,16 @@ class PageContextCollector {
|
|
|
1702
1761
|
const attached = element.isConnected;
|
|
1703
1762
|
const after = attached ? this.safeState(element) : '';
|
|
1704
1763
|
const expanded = attached
|
|
1705
|
-
?
|
|
1764
|
+
? this.expandedState(element)
|
|
1706
1765
|
: undefined;
|
|
1707
1766
|
const pressed = attached
|
|
1708
1767
|
? literalBooleanAttribute(element, 'aria-pressed')
|
|
1709
1768
|
: undefined;
|
|
1710
1769
|
const checked = attached ? checkedStateFor(element) : undefined;
|
|
1711
|
-
this.callbacks.onHighlight(attached && !handle.pageScroll ? element : null, 'success');
|
|
1770
|
+
this.callbacks.onHighlight(attached && !handle.pageScroll ? element : null, navigationReceipt && navigationReceipt.outcome !== 'opened' ? 'failure' : 'success');
|
|
1712
1771
|
return {
|
|
1713
1772
|
...base,
|
|
1714
|
-
status: 'executed',
|
|
1773
|
+
status: navigationReceipt && navigationReceipt.outcome !== 'opened' ? 'blocked' : 'executed',
|
|
1715
1774
|
observed: {
|
|
1716
1775
|
url: safePageUrl(),
|
|
1717
1776
|
revision: snapshot.revision,
|
|
@@ -1720,19 +1779,24 @@ class PageContextCollector {
|
|
|
1720
1779
|
: grant.action === 'type' || grant.action === 'select' || before !== after || handle.pageScroll
|
|
1721
1780
|
? 'changed'
|
|
1722
1781
|
: 'unchanged',
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
? '
|
|
1727
|
-
:
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1782
|
+
...(navigationReceipt ? { navigation: navigationReceipt } : {}),
|
|
1783
|
+
safeSummary: navigationReceipt
|
|
1784
|
+
? navigationReceipt.outcome === 'opened'
|
|
1785
|
+
? 'Opened a separate tab for the exact approved link. Its contents and arrival are not observed.'
|
|
1786
|
+
: 'The new tab could not be confirmed. No fallback navigation was attempted.'
|
|
1787
|
+
: handle.pageScroll
|
|
1788
|
+
? `The page moved ${handle.pageScroll} by ${Math.round(Math.abs(window.scrollY - scrollStartY))} pixels.`
|
|
1789
|
+
: grant.action === 'scroll'
|
|
1790
|
+
? 'The requested target is visible. This confirms target visibility, not a directional page scroll.'
|
|
1791
|
+
: grant.action === 'type'
|
|
1792
|
+
? 'The requested text was applied to the approved field.'
|
|
1793
|
+
: grant.action === 'select'
|
|
1794
|
+
? 'The requested option was applied to the approved field.'
|
|
1795
|
+
: grant.desiredChecked !== undefined
|
|
1796
|
+
? 'The requested checkbox state was applied.'
|
|
1797
|
+
: grant.desiredExpanded !== undefined || grant.desiredPressed !== undefined
|
|
1798
|
+
? 'The requested button state was applied.'
|
|
1799
|
+
: `${grant.action} executed on the approved ${roleFor(element)} target.`,
|
|
1736
1800
|
...(checked !== undefined ? { checked } : {}),
|
|
1737
1801
|
...(this.statefulButtonActionProof && expanded !== undefined ? { expanded } : {}),
|
|
1738
1802
|
...(this.statefulButtonActionProof && pressed !== undefined ? { pressed } : {}),
|
|
@@ -1740,6 +1804,7 @@ class PageContextCollector {
|
|
|
1740
1804
|
};
|
|
1741
1805
|
}
|
|
1742
1806
|
catch {
|
|
1807
|
+
this.deferredNavigation = null;
|
|
1743
1808
|
this.callbacks.onHighlight(element, 'failure');
|
|
1744
1809
|
return {
|
|
1745
1810
|
...base,
|
|
@@ -1747,8 +1812,12 @@ class PageContextCollector {
|
|
|
1747
1812
|
observed: {
|
|
1748
1813
|
url: safePageUrl(),
|
|
1749
1814
|
revision: this.revision,
|
|
1750
|
-
targetState: element.isConnected ? 'unchanged' : 'detached',
|
|
1751
|
-
|
|
1815
|
+
targetState: navigationReceipt ? 'unknown' : element.isConnected ? 'unchanged' : 'detached',
|
|
1816
|
+
...(navigationReceipt ? { navigation: { url: navigationReceipt.url,
|
|
1817
|
+
outcome: 'unconfirmed' } } : {}),
|
|
1818
|
+
safeSummary: navigationReceipt
|
|
1819
|
+
? 'The link action could not be fully confirmed. Do not retry automatically.'
|
|
1820
|
+
: 'The approved page action failed safely.',
|
|
1752
1821
|
},
|
|
1753
1822
|
};
|
|
1754
1823
|
}
|
|
@@ -1808,9 +1877,8 @@ class PageContextCollector {
|
|
|
1808
1877
|
return 'rejected';
|
|
1809
1878
|
}
|
|
1810
1879
|
if (window.location.href !== navigation.fromUrl) {
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
: 'rejected';
|
|
1880
|
+
// Arrival before dispatch is not evidence that this action navigated.
|
|
1881
|
+
return 'rejected';
|
|
1814
1882
|
}
|
|
1815
1883
|
this.captureSnapshot(false);
|
|
1816
1884
|
const handle = this.targets.get(navigation.targetId);
|
|
@@ -1826,7 +1894,7 @@ class PageContextCollector {
|
|
|
1826
1894
|
target.origin !== window.location.origin ||
|
|
1827
1895
|
safeDestinationPath(navigation.source) !== target.pathname ||
|
|
1828
1896
|
linkBrowsingTarget(navigation.source) !== navigation.browsingTarget ||
|
|
1829
|
-
!supportsCurrentTabNavigation(navigation.source) ||
|
|
1897
|
+
!supportsCurrentTabNavigation(navigation.source) || navigation.browsingTarget === '_blank' ||
|
|
1830
1898
|
!isRendered(navigation.source) ||
|
|
1831
1899
|
!isVisible(navigation.source) ||
|
|
1832
1900
|
!enabled(navigation.source) ||
|
|
@@ -1840,9 +1908,6 @@ class PageContextCollector {
|
|
|
1840
1908
|
if (current?.actionId !== navigation.actionId)
|
|
1841
1909
|
return;
|
|
1842
1910
|
this.releasedNavigationSettlement = null;
|
|
1843
|
-
if (this.executingActionId === navigation.actionId) {
|
|
1844
|
-
this.executingActionId = null;
|
|
1845
|
-
}
|
|
1846
1911
|
resolve('timed_out');
|
|
1847
1912
|
}, RELEASED_NAVIGATION_SETTLEMENT_TIMEOUT_MS);
|
|
1848
1913
|
this.releasedNavigationSettlement = {
|
|
@@ -1853,16 +1918,8 @@ class PageContextCollector {
|
|
|
1853
1918
|
resolve,
|
|
1854
1919
|
};
|
|
1855
1920
|
});
|
|
1856
|
-
const useCurrentTab = navigation.browsingTarget === '_blank';
|
|
1857
1921
|
try {
|
|
1858
|
-
|
|
1859
|
-
// approved URL only after durable release; the normal document-navigation
|
|
1860
|
-
// recovery handles the new page. Dispatching their handlers can open a
|
|
1861
|
-
// popup even if the DOM target is temporarily changed (e.g. React props).
|
|
1862
|
-
if (useCurrentTab) {
|
|
1863
|
-
window.location.assign(navigation.toUrl);
|
|
1864
|
-
}
|
|
1865
|
-
else if (navigation.source.closest(FRAMER_GENERATED_PAGE_SELECTOR) &&
|
|
1922
|
+
if (navigation.source.closest(FRAMER_GENERATED_PAGE_SELECTOR) &&
|
|
1866
1923
|
typeof window.PointerEvent === 'function') {
|
|
1867
1924
|
const preventNativeNavigation = (event) => {
|
|
1868
1925
|
if (event.target === navigation.source ||
|
|
@@ -1900,7 +1957,7 @@ class PageContextCollector {
|
|
|
1900
1957
|
// failure must not turn the acknowledged action into a failed click.
|
|
1901
1958
|
}
|
|
1902
1959
|
}
|
|
1903
|
-
else if (navigationSettled
|
|
1960
|
+
else if (navigationSettled !== 'settled' &&
|
|
1904
1961
|
this.executingActionId === navigation.actionId) {
|
|
1905
1962
|
// A router may consume the exact click but send the browser somewhere
|
|
1906
1963
|
// other than the approved destination. Complete the context-side action
|
|
@@ -1938,7 +1995,7 @@ class PageContextCollector {
|
|
|
1938
1995
|
}
|
|
1939
1996
|
}
|
|
1940
1997
|
if (desiredExpanded !== undefined) {
|
|
1941
|
-
const expanded =
|
|
1998
|
+
const expanded = this.expandedState(element);
|
|
1942
1999
|
if (roleFor(element) !== 'button' || expanded === undefined) {
|
|
1943
2000
|
throw new Error('Desired expanded state requires an expandable button target.');
|
|
1944
2001
|
}
|
|
@@ -1953,6 +2010,10 @@ class PageContextCollector {
|
|
|
1953
2010
|
}
|
|
1954
2011
|
if (desiredStates.length > 0 && desiredStates.every(Boolean))
|
|
1955
2012
|
return;
|
|
2013
|
+
if (desiredExpanded === true && framerNavigationRoot(element)) {
|
|
2014
|
+
dispatchFramerPrimaryPointerSequence(element, this.trustedGuideRoot);
|
|
2015
|
+
return;
|
|
2016
|
+
}
|
|
1956
2017
|
if (desiredChecked !== undefined && framerToggleDescriptor(element)) {
|
|
1957
2018
|
dispatchFramerTogglePrimaryPointer(element, this.trustedGuideRoot);
|
|
1958
2019
|
return;
|
|
@@ -1963,11 +2024,25 @@ class PageContextCollector {
|
|
|
1963
2024
|
safeDestinationPath(element) !== expectedDestinationPath) {
|
|
1964
2025
|
throw new Error('The navigation destination changed before execution.');
|
|
1965
2026
|
}
|
|
1966
|
-
|
|
1967
|
-
|
|
2027
|
+
const navigation = publicNavigation(element);
|
|
2028
|
+
if (!navigation || canonicalJson(navigation) !== canonicalJson(handle.target.navigation)) {
|
|
2029
|
+
throw new Error('The public link destination or browsing target changed.');
|
|
1968
2030
|
}
|
|
1969
|
-
if (
|
|
1970
|
-
|
|
2031
|
+
if (navigation.target === '_blank' || target.origin !== window.location.origin) {
|
|
2032
|
+
// Keep external pages outside this session's origin/credential boundary.
|
|
2033
|
+
// Only the browser's native noreferrer feature can prevent the source
|
|
2034
|
+
// URL from becoming the Referer on this navigation. With noopener the
|
|
2035
|
+
// return value may be null even when the tab opens, so never call a
|
|
2036
|
+
// null result "blocked" or try the link a second time.
|
|
2037
|
+
try {
|
|
2038
|
+
const popup = window.open(navigation.url, '_blank', 'noopener,noreferrer');
|
|
2039
|
+
return { url: navigation.url, outcome: popup && !popup.closed ? 'opened' : 'unconfirmed' };
|
|
2040
|
+
}
|
|
2041
|
+
catch {
|
|
2042
|
+
// Some browsers throw when popups are disallowed. The exact URL was
|
|
2043
|
+
// attempted at most once, and there is no fallback navigation.
|
|
2044
|
+
return { url: navigation.url, outcome: 'unconfirmed' };
|
|
2045
|
+
}
|
|
1971
2046
|
}
|
|
1972
2047
|
const fromUrl = window.location.href;
|
|
1973
2048
|
if (window.location.href === fromUrl && target.href !== fromUrl) {
|
|
@@ -2065,11 +2140,26 @@ class PageContextCollector {
|
|
|
2065
2140
|
}
|
|
2066
2141
|
form.requestSubmit(submitter);
|
|
2067
2142
|
}
|
|
2143
|
+
exposedNavigationLinks(nav) {
|
|
2144
|
+
const links = nav.querySelectorAll('a[href]');
|
|
2145
|
+
if (links.length > MAX_TARGETS)
|
|
2146
|
+
return new Set();
|
|
2147
|
+
return new Set(Array.from(links).filter(link => isVisible(link) && publicNavigation(link) &&
|
|
2148
|
+
!isCovered(link, this.trustedGuideRoot)));
|
|
2149
|
+
}
|
|
2150
|
+
expandedState(element) {
|
|
2151
|
+
const explicit = literalBooleanAttribute(element, 'aria-expanded');
|
|
2152
|
+
if (explicit !== undefined)
|
|
2153
|
+
return explicit;
|
|
2154
|
+
if (!framerNavigationRoot(element))
|
|
2155
|
+
return undefined;
|
|
2156
|
+
return [...(this.navigationDisclosureLinks.get(element) ?? [])].some(link => link.isConnected && isRendered(link));
|
|
2157
|
+
}
|
|
2068
2158
|
safeState(element) {
|
|
2069
2159
|
return JSON.stringify({
|
|
2070
2160
|
connected: element.isConnected,
|
|
2071
2161
|
disabled: !enabled(element),
|
|
2072
|
-
expanded: element.getAttribute('aria-expanded'),
|
|
2162
|
+
expanded: this.expandedState(element) ?? element.getAttribute('aria-expanded'),
|
|
2073
2163
|
pressed: element.getAttribute('aria-pressed'),
|
|
2074
2164
|
checked: checkedStateFor(element),
|
|
2075
2165
|
selectedIndex: element instanceof HTMLSelectElement ? element.selectedIndex : undefined,
|
|
@@ -2092,6 +2182,7 @@ class PageContextCollector {
|
|
|
2092
2182
|
let documentLeft = expectedHandle.element.getBoundingClientRect().left + window.scrollX;
|
|
2093
2183
|
let layoutCorrections = 0;
|
|
2094
2184
|
let priorScrollY = null;
|
|
2185
|
+
let intersectedViewport = false;
|
|
2095
2186
|
let interrupted = false;
|
|
2096
2187
|
const interrupt = () => {
|
|
2097
2188
|
interrupted = true;
|
|
@@ -2165,10 +2256,25 @@ class PageContextCollector {
|
|
|
2165
2256
|
if (!strictlyIntersectsViewport(rect)) {
|
|
2166
2257
|
priorRect = null;
|
|
2167
2258
|
stableSince = null;
|
|
2168
|
-
//
|
|
2169
|
-
//
|
|
2170
|
-
//
|
|
2171
|
-
|
|
2259
|
+
// A public-link reveal performs exactly one native scroll. If layout
|
|
2260
|
+
// shifts move that link away again, stop instead of chasing it and
|
|
2261
|
+
// accidentally scrolling two or three more times. Other controls
|
|
2262
|
+
// retain the existing bounded lazy-layout correction.
|
|
2263
|
+
if (expectedHandle.target.consequence === 'navigation' && intersectedViewport) {
|
|
2264
|
+
this.captureSnapshot(false);
|
|
2265
|
+
const current = this.targets.get(grant.targetId);
|
|
2266
|
+
if (!current ||
|
|
2267
|
+
current.element !== expectedHandle.element ||
|
|
2268
|
+
actionTargetFingerprintBinding(current.target) !==
|
|
2269
|
+
actionTargetFingerprintBinding(expectedHandle.target) ||
|
|
2270
|
+
grant.pageId !== this.pageId ||
|
|
2271
|
+
grant.pageUrl !== safePageUrl() ||
|
|
2272
|
+
grant.policyVersion !== this.policyVersion) {
|
|
2273
|
+
return { status: 'target_changed' };
|
|
2274
|
+
}
|
|
2275
|
+
return { status: 'unavailable' };
|
|
2276
|
+
}
|
|
2277
|
+
if (layoutShifted && expectedHandle.target.consequence !== 'navigation' &&
|
|
2172
2278
|
layoutCorrections < MAX_SCROLL_LAYOUT_CORRECTIONS) {
|
|
2173
2279
|
this.captureSnapshot(false);
|
|
2174
2280
|
const current = this.targets.get(grant.targetId);
|
|
@@ -2193,6 +2299,7 @@ class PageContextCollector {
|
|
|
2193
2299
|
}
|
|
2194
2300
|
continue;
|
|
2195
2301
|
}
|
|
2302
|
+
intersectedViewport = true;
|
|
2196
2303
|
if (!priorRect || !sameSettledRect(priorRect, rect))
|
|
2197
2304
|
stableSince = Date.now();
|
|
2198
2305
|
priorRect = rect;
|
|
@@ -2215,7 +2322,7 @@ class PageContextCollector {
|
|
|
2215
2322
|
return checkedStateFor(element) === grant.desiredChecked;
|
|
2216
2323
|
}
|
|
2217
2324
|
if (grant.desiredExpanded !== undefined &&
|
|
2218
|
-
|
|
2325
|
+
this.expandedState(element) !== grant.desiredExpanded) {
|
|
2219
2326
|
return false;
|
|
2220
2327
|
}
|
|
2221
2328
|
if (grant.desiredPressed !== undefined &&
|
|
@@ -2398,6 +2505,7 @@ class PageContextCollector {
|
|
|
2398
2505
|
resetBoundTargetIds() {
|
|
2399
2506
|
this.targetIds = new WeakMap();
|
|
2400
2507
|
this.navigationTargetBindings = new WeakMap();
|
|
2508
|
+
this.navigationDisclosureLinks = new WeakMap();
|
|
2401
2509
|
this.pageScrollIds = new WeakMap();
|
|
2402
2510
|
this.fieldTargetBindings = new WeakMap();
|
|
2403
2511
|
this.fieldAppliedRevisions = new WeakMap();
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type LiveDuplexMediaOptions, type LiveDuplexMediaDependencies } from './liveDuplexMedia.js';
|
|
2
|
-
export declare const LIVE_CONTROL_CLIENT_VERSION = "7.5.
|
|
2
|
+
export declare const LIVE_CONTROL_CLIENT_VERSION = "7.5.9";
|
|
3
3
|
export declare const LIVE_CONTROL_CLIENT_CAPABILITIES: readonly ["live_duplex_v1", "live_local_wake_v1", "live_fragment_captions_v1"];
|
|
4
4
|
declare const phases: readonly ["voice_off", "local_listening", "wake_buffering", "starting", "active", "tentative_idle", "closing", "uncertain", "muted", "suspended", "budget_blocked", "ended"];
|
|
5
5
|
export type LiveControlState = {
|
package/cjs/types.d.ts
CHANGED
|
@@ -332,6 +332,10 @@ export type SanitizedTarget = {
|
|
|
332
332
|
autocomplete?: string;
|
|
333
333
|
semanticRegion?: string;
|
|
334
334
|
navigationMenu?: true;
|
|
335
|
+
navigation?: {
|
|
336
|
+
url: string;
|
|
337
|
+
target: '_self' | '_blank';
|
|
338
|
+
};
|
|
335
339
|
destinationPath?: string;
|
|
336
340
|
sensitivity: Sensitivity;
|
|
337
341
|
protectedRegion?: boolean;
|
|
@@ -441,6 +445,10 @@ export type ActionExecutionResult = {
|
|
|
441
445
|
revision: number;
|
|
442
446
|
targetState: "unchanged" | "changed" | "detached" | "unknown";
|
|
443
447
|
safeSummary: string;
|
|
448
|
+
navigation?: {
|
|
449
|
+
url: string;
|
|
450
|
+
outcome: 'opened' | 'blocked' | 'unconfirmed';
|
|
451
|
+
};
|
|
444
452
|
checked?: boolean;
|
|
445
453
|
expanded?: boolean;
|
|
446
454
|
pressed?: boolean;
|
package/cjs/ui.js
CHANGED
|
@@ -4885,16 +4885,15 @@ class WidgetView {
|
|
|
4885
4885
|
this.syncSurfaceEffectOwner();
|
|
4886
4886
|
}
|
|
4887
4887
|
setRemoteStream(stream) {
|
|
4888
|
-
|
|
4889
|
-
|
|
4890
|
-
|
|
4888
|
+
if (this.destroyed || this.audio.srcObject === stream)
|
|
4889
|
+
return;
|
|
4890
|
+
// Rebinding the same stream restarts media loading, including its playout buffer.
|
|
4891
|
+
this.invalidateRemotePlayback();
|
|
4891
4892
|
this.audio.srcObject = stream;
|
|
4892
|
-
|
|
4893
|
-
|
|
4894
|
-
|
|
4895
|
-
|
|
4896
|
-
this.bindRemoteAudioTrackState(stream);
|
|
4897
|
-
}
|
|
4893
|
+
this.boundRemoteAudioStreams = new WeakSet();
|
|
4894
|
+
this.boundRemoteAudioTracks = new WeakSet();
|
|
4895
|
+
this.bindPlaybackState();
|
|
4896
|
+
this.bindRemoteAudioTrackState(stream);
|
|
4898
4897
|
void this.playRemoteAudio();
|
|
4899
4898
|
}
|
|
4900
4899
|
bindRemoteAudioTrackState(stream) {
|
package/cjs/version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const VERSION = "7.5.
|
|
1
|
+
export declare const VERSION = "7.5.9";
|
package/cjs/version.js
CHANGED