@oxvo/ai-live-assist 7.5.8 → 7.5.10
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 +190 -79
- 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 +190 -79
- 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.10";
|
|
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' &&
|
|
@@ -1199,15 +1235,18 @@ class PageContextCollector {
|
|
|
1199
1235
|
const visibleText = [];
|
|
1200
1236
|
const capturedSemanticText = new Set();
|
|
1201
1237
|
let totalText = 0;
|
|
1202
|
-
const walker = document.createTreeWalker(document.body ?? document.documentElement, NodeFilter.SHOW_TEXT);
|
|
1238
|
+
const walker = document.createTreeWalker(document.body ?? document.documentElement, NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_TEXT);
|
|
1203
1239
|
let node = walker.nextNode();
|
|
1204
1240
|
while (node && visibleText.length < MAX_TEXT_ITEMS && totalText < MAX_TOTAL_TEXT) {
|
|
1205
|
-
const parent = node.parentElement;
|
|
1206
|
-
const semanticTextElement =
|
|
1241
|
+
const parent = node instanceof Element ? node : node.parentElement;
|
|
1242
|
+
const semanticTextElement = node instanceof Element
|
|
1243
|
+
? node.matches('[role="img"][aria-label]') ? node : null
|
|
1244
|
+
: parent?.closest('[role="img"][aria-label]') ?? null;
|
|
1207
1245
|
const textElement = semanticTextElement && isRendered(semanticTextElement)
|
|
1208
1246
|
? semanticTextElement
|
|
1209
|
-
: parent;
|
|
1210
|
-
const text = normalize(semanticTextElement?.getAttribute('aria-label') ??
|
|
1247
|
+
: node.nodeType === Node.TEXT_NODE ? parent : null;
|
|
1248
|
+
const text = normalize(semanticTextElement?.getAttribute('aria-label') ??
|
|
1249
|
+
(node.nodeType === Node.TEXT_NODE ? node.textContent ?? '' : ''), 500);
|
|
1211
1250
|
if (parent &&
|
|
1212
1251
|
textElement &&
|
|
1213
1252
|
text &&
|
|
@@ -1420,12 +1459,13 @@ class PageContextCollector {
|
|
|
1420
1459
|
grant.targetId.startsWith(`${BOUND_FIELD_TARGET_PREFIX}_`) &&
|
|
1421
1460
|
Boolean(current && isNativeEditableField(current.element) && this.fieldTargetBindings.has(current.element));
|
|
1422
1461
|
const stablePageScroll = (current) => grant.action === 'scroll' && Boolean(current?.pageScroll && current.element === document.scrollingElement);
|
|
1462
|
+
const stableNavigation = (current) => (grant.action === 'click' || grant.action === 'scroll') &&
|
|
1463
|
+
grant.targetId.startsWith(`${BOUND_NAVIGATION_TARGET_PREFIX}_`) &&
|
|
1464
|
+
current?.element instanceof HTMLAnchorElement &&
|
|
1465
|
+
current.target.consequence === 'navigation' && current.target.navigation !== undefined;
|
|
1423
1466
|
const revisionMatchesOrStableTarget = () => grant.domRevision === this.revision ||
|
|
1424
1467
|
(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')));
|
|
1468
|
+
(stableField(handle) || stablePageScroll(handle) || stableNavigation(handle)));
|
|
1429
1469
|
if (expiresAt <= Date.now() ||
|
|
1430
1470
|
grant.pageId !== this.pageId ||
|
|
1431
1471
|
grant.pageUrl !== safePageUrl() ||
|
|
@@ -1462,7 +1502,7 @@ class PageContextCollector {
|
|
|
1462
1502
|
grant.pageUrl !== safePageUrl() ||
|
|
1463
1503
|
grant.policyVersion !== this.policyVersion ||
|
|
1464
1504
|
!revisionMatchesOrStableTarget() ||
|
|
1465
|
-
(handle !== fingerprintedHandle && !((stableField(handle) || stablePageScroll(handle)) &&
|
|
1505
|
+
(handle !== fingerprintedHandle && !((stableField(handle) || stablePageScroll(handle) || stableNavigation(handle)) &&
|
|
1466
1506
|
handle?.element === fingerprintedHandle.element &&
|
|
1467
1507
|
actionTargetFingerprintBinding(handle.target) === actionTargetFingerprintBinding(fingerprintedHandle.target))) ||
|
|
1468
1508
|
!handle ||
|
|
@@ -1548,6 +1588,9 @@ class PageContextCollector {
|
|
|
1548
1588
|
this.releasedNavigation = null;
|
|
1549
1589
|
this.finishReleasedNavigationSettlement('rejected');
|
|
1550
1590
|
let postActionSnapshot = null;
|
|
1591
|
+
let navigationReceipt;
|
|
1592
|
+
const disclosureRoot = handle.target.navigationMenu ? framerNavigationRoot(element) : null;
|
|
1593
|
+
const priorDisclosureLinks = disclosureRoot ? this.exposedNavigationLinks(disclosureRoot) : null;
|
|
1551
1594
|
try {
|
|
1552
1595
|
switch (grant.action) {
|
|
1553
1596
|
case 'scroll': {
|
|
@@ -1658,7 +1701,7 @@ class PageContextCollector {
|
|
|
1658
1701
|
element.focus({ preventScroll: false });
|
|
1659
1702
|
break;
|
|
1660
1703
|
case 'click':
|
|
1661
|
-
this.click(element, handle, grant.actionId, grant.desiredChecked, grant.desiredExpanded, grant.desiredPressed, grant.destinationPath);
|
|
1704
|
+
navigationReceipt = this.click(element, handle, grant.actionId, grant.desiredChecked, grant.desiredExpanded, grant.desiredPressed, grant.destinationPath);
|
|
1662
1705
|
break;
|
|
1663
1706
|
case 'type':
|
|
1664
1707
|
this.type(element, grant.value ?? '');
|
|
@@ -1680,9 +1723,28 @@ class PageContextCollector {
|
|
|
1680
1723
|
if (grant.action !== 'scroll') {
|
|
1681
1724
|
await new Promise((resolve) => setTimeout(resolve, 80));
|
|
1682
1725
|
}
|
|
1726
|
+
if (handle.target.navigationMenu && grant.action === 'click') {
|
|
1727
|
+
const deadline = Date.now() + 1000;
|
|
1728
|
+
for (let frames = 0; frames < 60 && Date.now() < deadline && element.isConnected && !executionStale(); frames++) {
|
|
1729
|
+
if (disclosureRoot && priorDisclosureLinks) {
|
|
1730
|
+
const added = new Set([...this.exposedNavigationLinks(disclosureRoot)]
|
|
1731
|
+
.filter(link => !priorDisclosureLinks.has(link)));
|
|
1732
|
+
if (added.size)
|
|
1733
|
+
this.navigationDisclosureLinks.set(element, added);
|
|
1734
|
+
}
|
|
1735
|
+
if (this.actionApplied(grant, element, handle))
|
|
1736
|
+
break;
|
|
1737
|
+
await nextAnimationFrameOrTimeout(Math.min(50, deadline - Date.now()));
|
|
1738
|
+
}
|
|
1739
|
+
}
|
|
1683
1740
|
const postActionStale = executionStale();
|
|
1684
|
-
if (postActionStale)
|
|
1685
|
-
|
|
1741
|
+
if (postActionStale) {
|
|
1742
|
+
this.deferredNavigation = null;
|
|
1743
|
+
return navigationReceipt ? { ...postActionStale, observed: {
|
|
1744
|
+
...postActionStale.observed, targetState: 'unknown',
|
|
1745
|
+
navigation: { url: navigationReceipt.url, outcome: 'unconfirmed' },
|
|
1746
|
+
} } : postActionStale;
|
|
1747
|
+
}
|
|
1686
1748
|
if (!this.actionApplied(grant, element, handle)) {
|
|
1687
1749
|
throw new Error('The approved value was not applied.');
|
|
1688
1750
|
}
|
|
@@ -1702,16 +1764,16 @@ class PageContextCollector {
|
|
|
1702
1764
|
const attached = element.isConnected;
|
|
1703
1765
|
const after = attached ? this.safeState(element) : '';
|
|
1704
1766
|
const expanded = attached
|
|
1705
|
-
?
|
|
1767
|
+
? this.expandedState(element)
|
|
1706
1768
|
: undefined;
|
|
1707
1769
|
const pressed = attached
|
|
1708
1770
|
? literalBooleanAttribute(element, 'aria-pressed')
|
|
1709
1771
|
: undefined;
|
|
1710
1772
|
const checked = attached ? checkedStateFor(element) : undefined;
|
|
1711
|
-
this.callbacks.onHighlight(attached && !handle.pageScroll ? element : null, 'success');
|
|
1773
|
+
this.callbacks.onHighlight(attached && !handle.pageScroll ? element : null, navigationReceipt && navigationReceipt.outcome !== 'opened' ? 'failure' : 'success');
|
|
1712
1774
|
return {
|
|
1713
1775
|
...base,
|
|
1714
|
-
status: 'executed',
|
|
1776
|
+
status: navigationReceipt && navigationReceipt.outcome !== 'opened' ? 'blocked' : 'executed',
|
|
1715
1777
|
observed: {
|
|
1716
1778
|
url: safePageUrl(),
|
|
1717
1779
|
revision: snapshot.revision,
|
|
@@ -1720,19 +1782,24 @@ class PageContextCollector {
|
|
|
1720
1782
|
: grant.action === 'type' || grant.action === 'select' || before !== after || handle.pageScroll
|
|
1721
1783
|
? 'changed'
|
|
1722
1784
|
: 'unchanged',
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
? '
|
|
1727
|
-
:
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1785
|
+
...(navigationReceipt ? { navigation: navigationReceipt } : {}),
|
|
1786
|
+
safeSummary: navigationReceipt
|
|
1787
|
+
? navigationReceipt.outcome === 'opened'
|
|
1788
|
+
? 'Opened a separate tab for the exact approved link. Its contents and arrival are not observed.'
|
|
1789
|
+
: 'The new tab could not be confirmed. No fallback navigation was attempted.'
|
|
1790
|
+
: handle.pageScroll
|
|
1791
|
+
? `The page moved ${handle.pageScroll} by ${Math.round(Math.abs(window.scrollY - scrollStartY))} pixels.`
|
|
1792
|
+
: grant.action === 'scroll'
|
|
1793
|
+
? 'The requested target is visible. This confirms target visibility, not a directional page scroll.'
|
|
1794
|
+
: grant.action === 'type'
|
|
1795
|
+
? 'The requested text was applied to the approved field.'
|
|
1796
|
+
: grant.action === 'select'
|
|
1797
|
+
? 'The requested option was applied to the approved field.'
|
|
1798
|
+
: grant.desiredChecked !== undefined
|
|
1799
|
+
? 'The requested checkbox state was applied.'
|
|
1800
|
+
: grant.desiredExpanded !== undefined || grant.desiredPressed !== undefined
|
|
1801
|
+
? 'The requested button state was applied.'
|
|
1802
|
+
: `${grant.action} executed on the approved ${roleFor(element)} target.`,
|
|
1736
1803
|
...(checked !== undefined ? { checked } : {}),
|
|
1737
1804
|
...(this.statefulButtonActionProof && expanded !== undefined ? { expanded } : {}),
|
|
1738
1805
|
...(this.statefulButtonActionProof && pressed !== undefined ? { pressed } : {}),
|
|
@@ -1740,6 +1807,7 @@ class PageContextCollector {
|
|
|
1740
1807
|
};
|
|
1741
1808
|
}
|
|
1742
1809
|
catch {
|
|
1810
|
+
this.deferredNavigation = null;
|
|
1743
1811
|
this.callbacks.onHighlight(element, 'failure');
|
|
1744
1812
|
return {
|
|
1745
1813
|
...base,
|
|
@@ -1747,8 +1815,12 @@ class PageContextCollector {
|
|
|
1747
1815
|
observed: {
|
|
1748
1816
|
url: safePageUrl(),
|
|
1749
1817
|
revision: this.revision,
|
|
1750
|
-
targetState: element.isConnected ? 'unchanged' : 'detached',
|
|
1751
|
-
|
|
1818
|
+
targetState: navigationReceipt ? 'unknown' : element.isConnected ? 'unchanged' : 'detached',
|
|
1819
|
+
...(navigationReceipt ? { navigation: { url: navigationReceipt.url,
|
|
1820
|
+
outcome: 'unconfirmed' } } : {}),
|
|
1821
|
+
safeSummary: navigationReceipt
|
|
1822
|
+
? 'The link action could not be fully confirmed. Do not retry automatically.'
|
|
1823
|
+
: 'The approved page action failed safely.',
|
|
1752
1824
|
},
|
|
1753
1825
|
};
|
|
1754
1826
|
}
|
|
@@ -1808,9 +1880,8 @@ class PageContextCollector {
|
|
|
1808
1880
|
return 'rejected';
|
|
1809
1881
|
}
|
|
1810
1882
|
if (window.location.href !== navigation.fromUrl) {
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
: 'rejected';
|
|
1883
|
+
// Arrival before dispatch is not evidence that this action navigated.
|
|
1884
|
+
return 'rejected';
|
|
1814
1885
|
}
|
|
1815
1886
|
this.captureSnapshot(false);
|
|
1816
1887
|
const handle = this.targets.get(navigation.targetId);
|
|
@@ -1826,7 +1897,7 @@ class PageContextCollector {
|
|
|
1826
1897
|
target.origin !== window.location.origin ||
|
|
1827
1898
|
safeDestinationPath(navigation.source) !== target.pathname ||
|
|
1828
1899
|
linkBrowsingTarget(navigation.source) !== navigation.browsingTarget ||
|
|
1829
|
-
!supportsCurrentTabNavigation(navigation.source) ||
|
|
1900
|
+
!supportsCurrentTabNavigation(navigation.source) || navigation.browsingTarget === '_blank' ||
|
|
1830
1901
|
!isRendered(navigation.source) ||
|
|
1831
1902
|
!isVisible(navigation.source) ||
|
|
1832
1903
|
!enabled(navigation.source) ||
|
|
@@ -1840,9 +1911,6 @@ class PageContextCollector {
|
|
|
1840
1911
|
if (current?.actionId !== navigation.actionId)
|
|
1841
1912
|
return;
|
|
1842
1913
|
this.releasedNavigationSettlement = null;
|
|
1843
|
-
if (this.executingActionId === navigation.actionId) {
|
|
1844
|
-
this.executingActionId = null;
|
|
1845
|
-
}
|
|
1846
1914
|
resolve('timed_out');
|
|
1847
1915
|
}, RELEASED_NAVIGATION_SETTLEMENT_TIMEOUT_MS);
|
|
1848
1916
|
this.releasedNavigationSettlement = {
|
|
@@ -1853,16 +1921,8 @@ class PageContextCollector {
|
|
|
1853
1921
|
resolve,
|
|
1854
1922
|
};
|
|
1855
1923
|
});
|
|
1856
|
-
const useCurrentTab = navigation.browsingTarget === '_blank';
|
|
1857
1924
|
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) &&
|
|
1925
|
+
if (navigation.source.closest(FRAMER_GENERATED_PAGE_SELECTOR) &&
|
|
1866
1926
|
typeof window.PointerEvent === 'function') {
|
|
1867
1927
|
const preventNativeNavigation = (event) => {
|
|
1868
1928
|
if (event.target === navigation.source ||
|
|
@@ -1900,7 +1960,7 @@ class PageContextCollector {
|
|
|
1900
1960
|
// failure must not turn the acknowledged action into a failed click.
|
|
1901
1961
|
}
|
|
1902
1962
|
}
|
|
1903
|
-
else if (navigationSettled
|
|
1963
|
+
else if (navigationSettled !== 'settled' &&
|
|
1904
1964
|
this.executingActionId === navigation.actionId) {
|
|
1905
1965
|
// A router may consume the exact click but send the browser somewhere
|
|
1906
1966
|
// other than the approved destination. Complete the context-side action
|
|
@@ -1938,7 +1998,7 @@ class PageContextCollector {
|
|
|
1938
1998
|
}
|
|
1939
1999
|
}
|
|
1940
2000
|
if (desiredExpanded !== undefined) {
|
|
1941
|
-
const expanded =
|
|
2001
|
+
const expanded = this.expandedState(element);
|
|
1942
2002
|
if (roleFor(element) !== 'button' || expanded === undefined) {
|
|
1943
2003
|
throw new Error('Desired expanded state requires an expandable button target.');
|
|
1944
2004
|
}
|
|
@@ -1953,6 +2013,10 @@ class PageContextCollector {
|
|
|
1953
2013
|
}
|
|
1954
2014
|
if (desiredStates.length > 0 && desiredStates.every(Boolean))
|
|
1955
2015
|
return;
|
|
2016
|
+
if (desiredExpanded === true && framerNavigationRoot(element)) {
|
|
2017
|
+
dispatchFramerPrimaryPointerSequence(element, this.trustedGuideRoot);
|
|
2018
|
+
return;
|
|
2019
|
+
}
|
|
1956
2020
|
if (desiredChecked !== undefined && framerToggleDescriptor(element)) {
|
|
1957
2021
|
dispatchFramerTogglePrimaryPointer(element, this.trustedGuideRoot);
|
|
1958
2022
|
return;
|
|
@@ -1963,11 +2027,25 @@ class PageContextCollector {
|
|
|
1963
2027
|
safeDestinationPath(element) !== expectedDestinationPath) {
|
|
1964
2028
|
throw new Error('The navigation destination changed before execution.');
|
|
1965
2029
|
}
|
|
1966
|
-
|
|
1967
|
-
|
|
2030
|
+
const navigation = publicNavigation(element);
|
|
2031
|
+
if (!navigation || canonicalJson(navigation) !== canonicalJson(handle.target.navigation)) {
|
|
2032
|
+
throw new Error('The public link destination or browsing target changed.');
|
|
1968
2033
|
}
|
|
1969
|
-
if (
|
|
1970
|
-
|
|
2034
|
+
if (navigation.target === '_blank' || target.origin !== window.location.origin) {
|
|
2035
|
+
// Keep external pages outside this session's origin/credential boundary.
|
|
2036
|
+
// Only the browser's native noreferrer feature can prevent the source
|
|
2037
|
+
// URL from becoming the Referer on this navigation. With noopener the
|
|
2038
|
+
// return value may be null even when the tab opens, so never call a
|
|
2039
|
+
// null result "blocked" or try the link a second time.
|
|
2040
|
+
try {
|
|
2041
|
+
const popup = window.open(navigation.url, '_blank', 'noopener,noreferrer');
|
|
2042
|
+
return { url: navigation.url, outcome: popup && !popup.closed ? 'opened' : 'unconfirmed' };
|
|
2043
|
+
}
|
|
2044
|
+
catch {
|
|
2045
|
+
// Some browsers throw when popups are disallowed. The exact URL was
|
|
2046
|
+
// attempted at most once, and there is no fallback navigation.
|
|
2047
|
+
return { url: navigation.url, outcome: 'unconfirmed' };
|
|
2048
|
+
}
|
|
1971
2049
|
}
|
|
1972
2050
|
const fromUrl = window.location.href;
|
|
1973
2051
|
if (window.location.href === fromUrl && target.href !== fromUrl) {
|
|
@@ -2065,11 +2143,26 @@ class PageContextCollector {
|
|
|
2065
2143
|
}
|
|
2066
2144
|
form.requestSubmit(submitter);
|
|
2067
2145
|
}
|
|
2146
|
+
exposedNavigationLinks(nav) {
|
|
2147
|
+
const links = nav.querySelectorAll('a[href]');
|
|
2148
|
+
if (links.length > MAX_TARGETS)
|
|
2149
|
+
return new Set();
|
|
2150
|
+
return new Set(Array.from(links).filter(link => isVisible(link) && publicNavigation(link) &&
|
|
2151
|
+
!isCovered(link, this.trustedGuideRoot)));
|
|
2152
|
+
}
|
|
2153
|
+
expandedState(element) {
|
|
2154
|
+
const explicit = literalBooleanAttribute(element, 'aria-expanded');
|
|
2155
|
+
if (explicit !== undefined)
|
|
2156
|
+
return explicit;
|
|
2157
|
+
if (!framerNavigationRoot(element))
|
|
2158
|
+
return undefined;
|
|
2159
|
+
return [...(this.navigationDisclosureLinks.get(element) ?? [])].some(link => link.isConnected && isRendered(link));
|
|
2160
|
+
}
|
|
2068
2161
|
safeState(element) {
|
|
2069
2162
|
return JSON.stringify({
|
|
2070
2163
|
connected: element.isConnected,
|
|
2071
2164
|
disabled: !enabled(element),
|
|
2072
|
-
expanded: element.getAttribute('aria-expanded'),
|
|
2165
|
+
expanded: this.expandedState(element) ?? element.getAttribute('aria-expanded'),
|
|
2073
2166
|
pressed: element.getAttribute('aria-pressed'),
|
|
2074
2167
|
checked: checkedStateFor(element),
|
|
2075
2168
|
selectedIndex: element instanceof HTMLSelectElement ? element.selectedIndex : undefined,
|
|
@@ -2092,6 +2185,7 @@ class PageContextCollector {
|
|
|
2092
2185
|
let documentLeft = expectedHandle.element.getBoundingClientRect().left + window.scrollX;
|
|
2093
2186
|
let layoutCorrections = 0;
|
|
2094
2187
|
let priorScrollY = null;
|
|
2188
|
+
let intersectedViewport = false;
|
|
2095
2189
|
let interrupted = false;
|
|
2096
2190
|
const interrupt = () => {
|
|
2097
2191
|
interrupted = true;
|
|
@@ -2165,10 +2259,25 @@ class PageContextCollector {
|
|
|
2165
2259
|
if (!strictlyIntersectsViewport(rect)) {
|
|
2166
2260
|
priorRect = null;
|
|
2167
2261
|
stableSince = null;
|
|
2168
|
-
//
|
|
2169
|
-
//
|
|
2170
|
-
//
|
|
2171
|
-
|
|
2262
|
+
// A public-link reveal performs exactly one native scroll. If layout
|
|
2263
|
+
// shifts move that link away again, stop instead of chasing it and
|
|
2264
|
+
// accidentally scrolling two or three more times. Other controls
|
|
2265
|
+
// retain the existing bounded lazy-layout correction.
|
|
2266
|
+
if (expectedHandle.target.consequence === 'navigation' && intersectedViewport) {
|
|
2267
|
+
this.captureSnapshot(false);
|
|
2268
|
+
const current = this.targets.get(grant.targetId);
|
|
2269
|
+
if (!current ||
|
|
2270
|
+
current.element !== expectedHandle.element ||
|
|
2271
|
+
actionTargetFingerprintBinding(current.target) !==
|
|
2272
|
+
actionTargetFingerprintBinding(expectedHandle.target) ||
|
|
2273
|
+
grant.pageId !== this.pageId ||
|
|
2274
|
+
grant.pageUrl !== safePageUrl() ||
|
|
2275
|
+
grant.policyVersion !== this.policyVersion) {
|
|
2276
|
+
return { status: 'target_changed' };
|
|
2277
|
+
}
|
|
2278
|
+
return { status: 'unavailable' };
|
|
2279
|
+
}
|
|
2280
|
+
if (layoutShifted && expectedHandle.target.consequence !== 'navigation' &&
|
|
2172
2281
|
layoutCorrections < MAX_SCROLL_LAYOUT_CORRECTIONS) {
|
|
2173
2282
|
this.captureSnapshot(false);
|
|
2174
2283
|
const current = this.targets.get(grant.targetId);
|
|
@@ -2193,6 +2302,7 @@ class PageContextCollector {
|
|
|
2193
2302
|
}
|
|
2194
2303
|
continue;
|
|
2195
2304
|
}
|
|
2305
|
+
intersectedViewport = true;
|
|
2196
2306
|
if (!priorRect || !sameSettledRect(priorRect, rect))
|
|
2197
2307
|
stableSince = Date.now();
|
|
2198
2308
|
priorRect = rect;
|
|
@@ -2215,7 +2325,7 @@ class PageContextCollector {
|
|
|
2215
2325
|
return checkedStateFor(element) === grant.desiredChecked;
|
|
2216
2326
|
}
|
|
2217
2327
|
if (grant.desiredExpanded !== undefined &&
|
|
2218
|
-
|
|
2328
|
+
this.expandedState(element) !== grant.desiredExpanded) {
|
|
2219
2329
|
return false;
|
|
2220
2330
|
}
|
|
2221
2331
|
if (grant.desiredPressed !== undefined &&
|
|
@@ -2398,6 +2508,7 @@ class PageContextCollector {
|
|
|
2398
2508
|
resetBoundTargetIds() {
|
|
2399
2509
|
this.targetIds = new WeakMap();
|
|
2400
2510
|
this.navigationTargetBindings = new WeakMap();
|
|
2511
|
+
this.navigationDisclosureLinks = new WeakMap();
|
|
2401
2512
|
this.pageScrollIds = new WeakMap();
|
|
2402
2513
|
this.fieldTargetBindings = new WeakMap();
|
|
2403
2514
|
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.10";
|
|
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.10";
|