@openchamber/sdk 2.0.4 → 2.1.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/API.md +33 -6
- package/DOCUMENTATION.md +14 -4
- package/README.md +13 -1
- package/dist/contract.d.ts +41 -3
- package/dist/contract.js +1 -0
- package/dist/host.d.ts +15 -5
- package/dist/host.js +144 -6
- package/dist/index.d.ts +7 -3
- package/dist/index.js +3 -1
- package/dist/manifest.d.ts +2 -0
- package/dist/parse.d.ts +8 -4
- package/dist/parse.js +2 -0
- package/dist/popover-schemas.d.ts +33 -0
- package/dist/popover-schemas.js +74 -0
- package/dist/popover.d.ts +37 -0
- package/dist/popover.js +57 -0
- package/dist/protocol.d.ts +125 -0
- package/dist/protocol.js +32 -1
- package/dist/schemas.d.ts +2 -0
- package/dist/schemas.js +2 -0
- package/dist/status-control-schemas.d.ts +21 -0
- package/dist/status-control-schemas.js +29 -0
- package/dist/status-controls.d.ts +31 -0
- package/dist/status-controls.js +48 -0
- package/dist/ui/index.d.ts +2 -0
- package/dist/ui/index.js +1 -0
- package/dist/ui/popover-anchor.d.ts +16 -0
- package/dist/ui/popover-anchor.js +198 -0
- package/dist/ui/theme.js +15 -0
- package/dist/workspace-schemas.d.ts +16 -0
- package/dist/workspace-schemas.js +4 -4
- package/dist/workspace.d.ts +10 -4
- package/dist/workspace.js +2 -0
- package/package.json +1 -1
package/dist/host.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { OPENCHAMBER_SDK_API_VERSION, OPENCHAMBER_SDK_CHANNEL } from "./api-version.js";
|
|
2
2
|
import { GUEST_STORAGE_KEY_MAX, GUEST_STORAGE_VALUE_BYTES } from "./workspace.js";
|
|
3
|
+
import { isGuestStatusControls } from "./status-controls.js";
|
|
4
|
+
import { isGuestPopoverRequest } from "./popover.js";
|
|
3
5
|
import { GUEST_FILE_EDITOR_CONTENT_MAX, GUEST_FILE_EDITOR_VERSION_MAX, fileEditorPayloadSize, sameFileEditorDocument, } from "./file-editor.js";
|
|
4
6
|
import { GUEST_FILE_CONTENT_MAX, GUEST_CLIPBOARD_TEXT_MAX, GUEST_TOAST_MAX, GUEST_FILE_PATH_MAX, GUEST_GENERATE_OUTPUT_TOKENS_MAX, GUEST_GENERATE_PROMPT_MAX, GUEST_GENERATE_SYSTEM_MAX, GUEST_GENERATE_TIMEOUT_MS, GUEST_REQUEST_TIMEOUT_MS, GUEST_RESOLVE_ERROR_MAX, isGuestFilePath, isGuestRequestPath, clampAttachRequest, clampBadgeCount, clampFrameHeight, isGuestCommitSha, clampPromptRequest, clampStartSessionRequest, readHostMessage, isGenerateResult, isFileListResult, isFileReadResult, isFileStatResult, isJsonValue, isFileWriteResult, isServiceStatusResult, isGuestRequestResult, isPromptResult, isStartSessionResult, } from "./contract.js";
|
|
5
7
|
// Structural, not `instanceof KeyboardEvent`: the frame's own realm owns that
|
|
@@ -18,6 +20,16 @@ export class HostRequestError extends Error {
|
|
|
18
20
|
// path would otherwise surface only as HOST_TIMEOUT twenty seconds later.
|
|
19
21
|
const rejectBadPath = () => Promise.reject(new HostRequestError('BAD_PATH', 'Request path must start with "/" and stay on the declared origin.'));
|
|
20
22
|
const rejectBadFilePath = () => Promise.reject(new HostRequestError('BAD_PATH', `File path must be 1 to ${GUEST_FILE_PATH_MAX} characters without NUL or backslash.`));
|
|
23
|
+
const deviceStorageScope = (options) => {
|
|
24
|
+
// Typed callers supply GuestStorageOptions, but extensions may call this JavaScript API without TypeScript.
|
|
25
|
+
if (options === undefined)
|
|
26
|
+
return undefined;
|
|
27
|
+
if (Object(options) !== options || Array.isArray(options)
|
|
28
|
+
|| (options.scope !== undefined && options.scope !== 'instance' && options.scope !== 'device')) {
|
|
29
|
+
throw new HostRequestError('HOST_REJECTED', 'Storage scope must be "instance" or "device".');
|
|
30
|
+
}
|
|
31
|
+
return options.scope === 'device' ? 'device' : undefined;
|
|
32
|
+
};
|
|
21
33
|
const nextId = (n) => {
|
|
22
34
|
n.value += 1;
|
|
23
35
|
return `oc-${n.value}`;
|
|
@@ -36,6 +48,8 @@ export const connectHost = (options = {}) => {
|
|
|
36
48
|
const connectionListeners = new Set();
|
|
37
49
|
const settingsListeners = new Set();
|
|
38
50
|
const itemListeners = new Set();
|
|
51
|
+
const statusControlListeners = new Set();
|
|
52
|
+
const popoverClosedListeners = new Set();
|
|
39
53
|
let resolveHandler = null;
|
|
40
54
|
let actionHandler = null;
|
|
41
55
|
const fileOpenListeners = new Set();
|
|
@@ -49,6 +63,9 @@ export const connectHost = (options = {}) => {
|
|
|
49
63
|
const ids = { value: 0 };
|
|
50
64
|
let lastReady = null;
|
|
51
65
|
let lastLifecycle = null;
|
|
66
|
+
let popoverPointerActive = false;
|
|
67
|
+
let popoverFocusActive = false;
|
|
68
|
+
let popoverActivityReported = null;
|
|
52
69
|
const lifecycleFromSession = (session) => {
|
|
53
70
|
if (!session)
|
|
54
71
|
return null;
|
|
@@ -146,6 +163,14 @@ export const connectHost = (options = {}) => {
|
|
|
146
163
|
emit(itemListeners, message.payload.item);
|
|
147
164
|
return;
|
|
148
165
|
}
|
|
166
|
+
if (message.type === 'status-control-event') {
|
|
167
|
+
emit(statusControlListeners, message.payload);
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
if (message.type === 'popover-closed') {
|
|
171
|
+
emit(popoverClosedListeners, message.payload);
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
149
174
|
if (message.type === 'action') {
|
|
150
175
|
const answer = (payload) => {
|
|
151
176
|
if (!disposed)
|
|
@@ -274,6 +299,46 @@ export const connectHost = (options = {}) => {
|
|
|
274
299
|
event.preventDefault();
|
|
275
300
|
requestFileSave();
|
|
276
301
|
};
|
|
302
|
+
const onPopoverEscape = (event) => {
|
|
303
|
+
if (!isKeyEvent(event) || event.key !== 'Escape' || lastReady?.surface !== 'popover' || !lastReady.popover)
|
|
304
|
+
return;
|
|
305
|
+
event.preventDefault();
|
|
306
|
+
void closePopover(lastReady.popover.id, 'escape').catch(() => undefined);
|
|
307
|
+
};
|
|
308
|
+
const reportPopoverActivity = () => {
|
|
309
|
+
const popover = lastReady?.surface === 'popover' ? lastReady.popover : undefined;
|
|
310
|
+
if (!popover)
|
|
311
|
+
return;
|
|
312
|
+
const active = popoverPointerActive || popoverFocusActive;
|
|
313
|
+
if (popoverActivityReported === active)
|
|
314
|
+
return;
|
|
315
|
+
popoverActivityReported = active;
|
|
316
|
+
void setPopoverAnchorActive(popover.id, active).catch(() => undefined);
|
|
317
|
+
};
|
|
318
|
+
const onPopoverPointerOver = () => {
|
|
319
|
+
if (lastReady?.surface !== 'popover' || popoverPointerActive)
|
|
320
|
+
return;
|
|
321
|
+
popoverPointerActive = true;
|
|
322
|
+
reportPopoverActivity();
|
|
323
|
+
};
|
|
324
|
+
const onPopoverPointerOut = (event) => {
|
|
325
|
+
if (lastReady?.surface !== 'popover' || event.relatedTarget !== null || !popoverPointerActive)
|
|
326
|
+
return;
|
|
327
|
+
popoverPointerActive = false;
|
|
328
|
+
reportPopoverActivity();
|
|
329
|
+
};
|
|
330
|
+
const onPopoverFocus = () => {
|
|
331
|
+
if (lastReady?.surface !== 'popover' || popoverFocusActive)
|
|
332
|
+
return;
|
|
333
|
+
popoverFocusActive = true;
|
|
334
|
+
reportPopoverActivity();
|
|
335
|
+
};
|
|
336
|
+
const onPopoverBlur = () => {
|
|
337
|
+
if (lastReady?.surface !== 'popover' || !popoverFocusActive)
|
|
338
|
+
return;
|
|
339
|
+
popoverFocusActive = false;
|
|
340
|
+
reportPopoverActivity();
|
|
341
|
+
};
|
|
277
342
|
const requireIdentity = (value, maximum = 1024) => {
|
|
278
343
|
if (!value.trim() || value.length > maximum)
|
|
279
344
|
throw new HostRequestError('HOST_REJECTED', `Identity must contain 1 to ${maximum} characters.`);
|
|
@@ -317,11 +382,26 @@ export const connectHost = (options = {}) => {
|
|
|
317
382
|
if (payload.op === 'set' && new TextEncoder().encode(JSON.stringify(payload.value)).length > GUEST_STORAGE_VALUE_BYTES) {
|
|
318
383
|
throw new HostRequestError('HOST_REJECTED', 'Storage value exceeds 64 KiB.');
|
|
319
384
|
}
|
|
385
|
+
if (payload.scope === 'device' && lastReady?.features?.deviceStorage !== true) {
|
|
386
|
+
throw new HostRequestError('UNSUPPORTED', 'This host does not support device storage.');
|
|
387
|
+
}
|
|
320
388
|
const result = await send({ ...envelope, type: 'storage', id: nextId(ids), payload });
|
|
321
389
|
if (!result || !('storage' in result) || result.op !== payload.op)
|
|
322
390
|
throw new HostRequestError('HOST_REJECTED', 'Host did not return storage data.');
|
|
323
391
|
return result;
|
|
324
392
|
};
|
|
393
|
+
const closePopover = (id, reason) => (lastReady?.features?.popovers !== true && !(lastReady?.surface === 'popover' && lastReady.popover?.id === id)
|
|
394
|
+
? Promise.reject(new HostRequestError('UNSUPPORTED', 'This host does not support popovers.'))
|
|
395
|
+
: request({ ...envelope, type: 'popover-close', id: nextId(ids), payload: reason ? { id, reason } : { id } }));
|
|
396
|
+
const setPopoverAnchorActive = (id, active) => (lastReady?.features?.popovers !== true && !(lastReady?.surface === 'popover' && lastReady.popover?.id === id)
|
|
397
|
+
? Promise.reject(new HostRequestError('UNSUPPORTED', 'This host does not support popovers.'))
|
|
398
|
+
: request({ ...envelope, type: 'popover-anchor', id: nextId(ids), payload: { id, active } }));
|
|
399
|
+
target.addEventListener('keydown', onPopoverEscape, true);
|
|
400
|
+
target.addEventListener('pointerover', onPopoverPointerOver);
|
|
401
|
+
target.addEventListener('pointermove', onPopoverPointerOver);
|
|
402
|
+
target.addEventListener('pointerout', onPopoverPointerOut);
|
|
403
|
+
target.addEventListener('focus', onPopoverFocus);
|
|
404
|
+
target.addEventListener('blur', onPopoverBlur);
|
|
325
405
|
return {
|
|
326
406
|
onAction: (handler) => {
|
|
327
407
|
actionHandler = handler;
|
|
@@ -357,14 +437,34 @@ export const connectHost = (options = {}) => {
|
|
|
357
437
|
await request({ ...envelope, type: 'open-session', id: nextId(ids), payload: { sessionId } });
|
|
358
438
|
},
|
|
359
439
|
storage: {
|
|
360
|
-
get: async (key) => {
|
|
361
|
-
const
|
|
440
|
+
get: async (key, options) => {
|
|
441
|
+
const scope = deviceStorageScope(options);
|
|
442
|
+
const payload = { op: 'get', key };
|
|
443
|
+
if (scope)
|
|
444
|
+
payload.scope = scope;
|
|
445
|
+
const result = await storage(payload);
|
|
362
446
|
return result.op === 'get' && result.found ? result.value : undefined;
|
|
363
447
|
},
|
|
364
|
-
set: async (key, value) => {
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
448
|
+
set: async (key, value, options) => {
|
|
449
|
+
const scope = deviceStorageScope(options);
|
|
450
|
+
const payload = { op: 'set', key, value };
|
|
451
|
+
if (scope)
|
|
452
|
+
payload.scope = scope;
|
|
453
|
+
await storage(payload);
|
|
454
|
+
},
|
|
455
|
+
delete: async (key, options) => {
|
|
456
|
+
const scope = deviceStorageScope(options);
|
|
457
|
+
const payload = { op: 'delete', key };
|
|
458
|
+
if (scope)
|
|
459
|
+
payload.scope = scope;
|
|
460
|
+
await storage(payload);
|
|
461
|
+
},
|
|
462
|
+
keys: async (options) => {
|
|
463
|
+
const scope = deviceStorageScope(options);
|
|
464
|
+
const payload = { op: 'keys' };
|
|
465
|
+
if (scope)
|
|
466
|
+
payload.scope = scope;
|
|
467
|
+
const result = await storage(payload);
|
|
368
468
|
if (result.op !== 'keys')
|
|
369
469
|
throw new HostRequestError('HOST_REJECTED', 'Expected storage keys.');
|
|
370
470
|
return result.keys;
|
|
@@ -426,6 +526,36 @@ export const connectHost = (options = {}) => {
|
|
|
426
526
|
itemListeners.delete(listener);
|
|
427
527
|
};
|
|
428
528
|
},
|
|
529
|
+
onStatusControl: (listener) => {
|
|
530
|
+
statusControlListeners.add(listener);
|
|
531
|
+
return () => {
|
|
532
|
+
statusControlListeners.delete(listener);
|
|
533
|
+
};
|
|
534
|
+
},
|
|
535
|
+
onPopoverClosed: (listener) => {
|
|
536
|
+
popoverClosedListeners.add(listener);
|
|
537
|
+
return () => { popoverClosedListeners.delete(listener); };
|
|
538
|
+
},
|
|
539
|
+
setStatusControls: (controls) => {
|
|
540
|
+
if (lastReady?.features?.statusControls !== true) {
|
|
541
|
+
return Promise.reject(new HostRequestError('UNSUPPORTED', 'This host does not support status controls.'));
|
|
542
|
+
}
|
|
543
|
+
if (!isGuestStatusControls(controls)) {
|
|
544
|
+
return Promise.reject(new HostRequestError('HOST_REJECTED', 'Status controls must have unique bounded ids and valid selected options.'));
|
|
545
|
+
}
|
|
546
|
+
return request({ ...envelope, type: 'status-controls', id: nextId(ids), payload: { controls } });
|
|
547
|
+
},
|
|
548
|
+
openPopover: (payload) => {
|
|
549
|
+
if (lastReady?.features?.popovers !== true) {
|
|
550
|
+
return Promise.reject(new HostRequestError('UNSUPPORTED', 'This host does not support popovers.'));
|
|
551
|
+
}
|
|
552
|
+
if (!isGuestPopoverRequest(payload)) {
|
|
553
|
+
return Promise.reject(new HostRequestError('HOST_REJECTED', 'Popover requests need bounded geometry and JSON data.'));
|
|
554
|
+
}
|
|
555
|
+
return request({ ...envelope, type: 'popover-open', id: nextId(ids), payload });
|
|
556
|
+
},
|
|
557
|
+
closePopover,
|
|
558
|
+
setPopoverAnchorActive,
|
|
429
559
|
onResolve: (handler) => {
|
|
430
560
|
resolveHandler = handler;
|
|
431
561
|
return () => {
|
|
@@ -735,6 +865,12 @@ export const connectHost = (options = {}) => {
|
|
|
735
865
|
if (saveShortcutInstalled)
|
|
736
866
|
target.removeEventListener('keydown', onSaveShortcut, true);
|
|
737
867
|
target.removeEventListener('message', onMessage);
|
|
868
|
+
target.removeEventListener('keydown', onPopoverEscape, true);
|
|
869
|
+
target.removeEventListener('pointerover', onPopoverPointerOver);
|
|
870
|
+
target.removeEventListener('pointermove', onPopoverPointerOver);
|
|
871
|
+
target.removeEventListener('pointerout', onPopoverPointerOut);
|
|
872
|
+
target.removeEventListener('focus', onPopoverFocus);
|
|
873
|
+
target.removeEventListener('blur', onPopoverBlur);
|
|
738
874
|
for (const waiter of pending.values()) {
|
|
739
875
|
clearTimeout(waiter.timer);
|
|
740
876
|
waiter.reject(new HostRequestError('HOST_UNAVAILABLE', 'Host client was disposed.'));
|
|
@@ -747,6 +883,8 @@ export const connectHost = (options = {}) => {
|
|
|
747
883
|
connectionListeners.clear();
|
|
748
884
|
settingsListeners.clear();
|
|
749
885
|
itemListeners.clear();
|
|
886
|
+
statusControlListeners.clear();
|
|
887
|
+
popoverClosedListeners.clear();
|
|
750
888
|
},
|
|
751
889
|
};
|
|
752
890
|
};
|
package/dist/index.d.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
export { OPENCHAMBER_SDK_API_VERSION, OPENCHAMBER_SDK_CHANNEL, OPENCHAMBER_SDK_MANIFEST_API_VERSIONS } from './api-version.ts';
|
|
2
2
|
export { GUEST_SCROLLBAR_CSS, GUEST_SCROLLBAR_SCRIPT } from './scrollbar-style.ts';
|
|
3
3
|
export { guestFramePolicy } from './frame-policy.ts';
|
|
4
|
-
export type { GuestLoadState, GuestProject, GuestWorktree, GuestSessionActivity, GuestSessionRecord, GuestDirectoryCoverage, GuestProjectsSnapshot, GuestWorktreesSnapshot, GuestSessionsSnapshot, GuestWorkspaceSnapshot, GuestWorkspaceQuery, GuestWorkspaceSubscription, GuestWorkspaceUpdate, GuestStorageRequest, GuestStorageResult, GuestSessionWorktree } from './workspace.ts';
|
|
5
|
-
export { GUEST_STORAGE_KEY_MAX, GUEST_STORAGE_KEYS_MAX, GUEST_STORAGE_VALUE_BYTES, GUEST_STORAGE_TOTAL_BYTES } from './workspace.ts';
|
|
4
|
+
export type { GuestLoadState, GuestProject, GuestWorktree, GuestSessionActivity, GuestSessionRecord, GuestDirectoryCoverage, GuestProjectsSnapshot, GuestWorktreesSnapshot, GuestSessionsSnapshot, GuestWorkspaceSnapshot, GuestWorkspaceQuery, GuestWorkspaceSubscription, GuestWorkspaceUpdate, GuestStorageRequest, GuestStorageResult, GuestStorageOptions, GuestStorageScope, GuestSessionWorktree } from './workspace.ts';
|
|
5
|
+
export { GUEST_STORAGE_KEY_MAX, GUEST_STORAGE_KEYS_MAX, GUEST_STORAGE_VALUE_BYTES, GUEST_STORAGE_TOTAL_BYTES, GUEST_DEVICE_STORAGE_TOTAL_BYTES } from './workspace.ts';
|
|
6
|
+
export { GUEST_STATUS_CONTROLS_MAX, GUEST_STATUS_CONTROL_ID, GUEST_STATUS_CONTROL_LABEL_MAX, GUEST_STATUS_CONTROL_OPTIONS_MAX, GUEST_STATUS_CONTROL_VALUE_MAX } from './status-controls.ts';
|
|
7
|
+
export type { GuestStatusControl, GuestStatusControlEvent, GuestStatusControlOption } from './status-controls.ts';
|
|
6
8
|
export type { OpenChamberManifestApiVersion } from './api-version.ts';
|
|
7
9
|
export { compareOpenChamberVersions, hostMeetsOpenChamberEngine, openChamberEngineMinimum, OPENCHAMBER_ENGINE_PATTERN, parseOpenChamberVersion, } from './host-version.ts';
|
|
8
10
|
export { DECLARED_GUEST_CAPABILITIES, GUEST_ACTIONS_MAX, GUEST_ACTION_LABEL_MAX, GUEST_CAPABILITIES, GUEST_COMMANDS_MAX, GUEST_COMMAND_DESCRIPTION_MAX, GUEST_COMMAND_NAME, GUEST_TOOLS_MAX, GUEST_TOOL_COLUMNS_MAX, GUEST_TOOL_COLUMN_MAX, GUEST_TOOL_LANGUAGE_MAX, GUEST_TOOL_MATCH, GUEST_TOOL_MATCH_MAX, GUEST_TOOL_NAME_MAX, GUEST_TOOL_OUTPUTS, GUEST_TOOL_TEMPLATE_MAX, GUEST_TOOL_TEMPLATE_VALUE_MAX, guestActionsNeedConversation, GUEST_FILESYSTEM_PATTERN_MAX, GUEST_FILESYSTEM_PATTERNS_MAX, GUEST_ORIGINS_MAX, GUEST_SERVICE_PROVIDES, GUEST_SURFACE_DOCKS, GUEST_SURFACE_DOCK_DEFAULT, GUEST_SURFACE_DOCK_SIZE_DEFAULT, GUEST_SURFACE_DOCK_SIZE_MAX, GUEST_SURFACE_DOCK_SIZE_MIN, GUEST_STATUS_SECTION_HEIGHT_DEFAULT, GUEST_STATUS_SECTION_HEIGHT_MAX, GUEST_STATUS_SECTION_HEIGHT_MIN, GUEST_STATUS_SECTION_TITLE_MAX, clampStatusSectionHeight, HOST_LINEAR_API_ORIGIN, isGuestFilesystemPattern, hasGuestCapability, hasGuestPage, isGuestApproved, isGuestPackageSvgIcon, requestedGuestCapabilities, resolveAttachEntry, resolvePageEntry, resolveStatusSectionEntry, resolveAttachMode, resolveIntegrationApi, resolveIntegrationAuth, serviceProvides, toPublicService, toPublicIntegration, } from './manifest.ts';
|
|
@@ -11,8 +13,10 @@ export { GUEST_FILE_EDITORS_MAX, GUEST_FILE_EDITOR_CONTENT_MAX, GUEST_FILE_EDITO
|
|
|
11
13
|
export type { FileEditorChange, FileEditorContentKind, FileEditorContribution, FileEditorDocument, FileEditorSnapshot, FileSnapshotPurpose, FileSnapshotRequest, FileSnapshotResultPayload, } from './file-editor.ts';
|
|
12
14
|
export { connectHost, HostRequestError } from './host.ts';
|
|
13
15
|
export type { HostClient, HostClientOptions, HostFrame } from './host.ts';
|
|
16
|
+
export { GUEST_POPOVER_COORDINATE_MAX, GUEST_POPOVER_DATA_DEPTH_MAX, GUEST_POPOVER_DATA_MAX, GUEST_POPOVER_HEIGHT_MAX, GUEST_POPOVER_HEIGHT_MIN, GUEST_POPOVER_ID, GUEST_POPOVER_SIDES, GUEST_POPOVER_WIDTH_MAX, GUEST_POPOVER_WIDTH_MIN, GUEST_POPOVER_CLOSE_REASONS, isGuestPopoverRequest, } from './popover.ts';
|
|
17
|
+
export type { GuestPopoverAnchor, GuestPopoverClosedEvent, GuestPopoverContext, GuestPopoverRequest, GuestPopoverSide } from './popover.ts';
|
|
14
18
|
export { clampAttachRequest, clampBadgeCount, clampFrameHeight, isGuestCommitSha, GUEST_COMMIT_SHA, clampPromptRequest, clampStartSessionRequest, guestFileScope, isAttachData, isJsonValue, isFileListResult, isFileReadResult, isFileStatResult, isGenerateResult, isFileWriteResult, isGuestFilePath, isGuestAttachItem, isGuestMessageItem, isGuestSessionItem, isServiceStatusResult, isGuestRequestPath, isGuestRequestResult, isHostRequestErrorCode, isPromptResult, isStartSessionResult, readHostMessage, resolveHostRequestErrorCode, } from './contract.ts';
|
|
15
|
-
export type { BadgeRequest, ActionResultPayload, GuestActionItem, GuestActionResultMessage, HostActionMessage, HostFileOpenMessage, HostFileSnapshotMessage, HostFileSavedMessage, GuestFileSnapshotResultMessage, GuestFileChangeMessage, GuestFileSaveMessage, GuestFileUnsupportedMessage, GuestBadgeMessage, GuestResizeMessage, GuestOpenCommitMessage, OpenCommitRequest, ResizeRequest, GuestItem, GuestItemRole, GuestMessageItem, GuestResolveResultMessage, GuestSessionItem, GuestSessionItemMessage, HostResolveMessage, ResolveRequest, ResolveResultPayload, FileListEntry, FileListRequest, FileListResult, FileReadRequest, FileReadResult, FileStatRequest, FileStatResult, FileWriteRequest, FileWriteResult, GenerateRequest, GenerateResult, GuestFileEntryKind, GuestFileListMessage, GuestFileReadMessage, GuestFileScope, GuestFileStatKind, GuestFileStatMessage, GuestGenerateMessage, GuestFileWriteMessage, ServiceStatus, ServiceStatusResult, HostRequestErrorCode, AttachBranches, AttachIssueRequest, AttachThreadKind, JsonValue, HostResultPayload, PromptRequest, PromptResult, SessionLifecycleEvent, SessionLifecyclePhase, StartSessionRequest, StartSessionResult, StartSessionSent, ComposeRequest, GuestServiceRequestMessage, GuestServiceStatusMessage, GuestAttachMessage, GuestClipboardWriteMessage, GuestCloseMessage, GuestComposeMessage, GuestConnection, GuestHelloMessage, GuestMessage, GuestOauthDisconnectMessage, GuestOauthStartMessage, GuestOpenSurfaceMessage, GuestOpenUrlMessage, GuestRequest, GuestRequestMessage, GuestRequestMethod, GuestPromptMessage, GuestSessionLinkMessage, GuestStartSessionMessage, GuestRequestResult, GuestSettings, GuestToastMessage, GuestHostSurface, HostConnectionMessage, HostDirectoryMessage, HostItemMessage, HostMessage, HostReadyContext, HostReadyMessage, HostResultMessage, HostSessionLifecycleMessage, HostSessionMessage, HostSettingsMessage, HostTheme, HostThemeMode, HostThemeTokens, SessionSnapshot, ToastKind, ToastRequest, } from './contract.ts';
|
|
19
|
+
export type { BadgeRequest, ActionResultPayload, GuestActionItem, GuestActionResultMessage, HostActionMessage, HostFileOpenMessage, HostFileSnapshotMessage, HostFileSavedMessage, GuestFileSnapshotResultMessage, GuestFileChangeMessage, GuestFileSaveMessage, GuestFileUnsupportedMessage, GuestBadgeMessage, GuestResizeMessage, GuestOpenCommitMessage, GuestStatusControlsMessage, GuestPopoverAnchorMessage, GuestPopoverCloseMessage, GuestPopoverOpenMessage, OpenCommitRequest, ResizeRequest, GuestItem, GuestItemRole, GuestMessageItem, GuestResolveResultMessage, GuestSessionItem, GuestSessionItemMessage, HostResolveMessage, ResolveRequest, ResolveResultPayload, FileListEntry, FileListRequest, FileListResult, FileReadRequest, FileReadResult, FileStatRequest, FileStatResult, FileWriteRequest, FileWriteResult, GenerateRequest, GenerateResult, GuestFileEntryKind, GuestFileListMessage, GuestFileReadMessage, GuestFileScope, GuestFileStatKind, GuestFileStatMessage, GuestGenerateMessage, GuestFileWriteMessage, ServiceStatus, ServiceStatusResult, HostRequestErrorCode, AttachBranches, AttachIssueRequest, AttachThreadKind, JsonValue, HostResultPayload, PromptRequest, PromptResult, SessionLifecycleEvent, SessionLifecyclePhase, StartSessionRequest, StartSessionResult, StartSessionSent, ComposeRequest, GuestServiceRequestMessage, GuestServiceStatusMessage, GuestAttachMessage, GuestClipboardWriteMessage, GuestCloseMessage, GuestComposeMessage, GuestConnection, GuestHelloMessage, GuestMessage, GuestOauthDisconnectMessage, GuestOauthStartMessage, GuestOpenSurfaceMessage, GuestOpenUrlMessage, GuestRequest, GuestRequestMessage, GuestRequestMethod, GuestPromptMessage, GuestSessionLinkMessage, GuestStartSessionMessage, GuestRequestResult, GuestSettings, GuestToastMessage, GuestHostSurface, HostConnectionMessage, HostDirectoryMessage, HostItemMessage, HostStatusControlEventMessage, HostPopoverClosedMessage, HostMessage, HostReadyContext, HostReadyMessage, HostResultMessage, HostSessionLifecycleMessage, HostSessionMessage, HostSettingsMessage, HostTheme, HostThemeMode, HostThemeTokens, SessionSnapshot, ToastKind, ToastRequest, } from './contract.ts';
|
|
16
20
|
export { EMPTY_GUEST_CONNECTION, GUEST_BADGE_MAX, GUEST_FRAME_HEIGHT_MAX, GUEST_ITEM_MESSAGE_TEXT_MAX, GUEST_ITEM_SESSION_MAX, GUEST_RESOLVE_ERROR_MAX, GUEST_FILE_CONTENT_MAX, GUEST_FILE_ENTRY_KINDS, GUEST_FILE_LIST_MAX, GUEST_FILE_PATH_MAX, GUEST_GENERATE_OUTPUT_TOKENS_MAX, GUEST_GENERATE_PROMPT_MAX, GUEST_GENERATE_SYSTEM_MAX, GUEST_GENERATE_TEXT_MAX, GUEST_GENERATE_TIMEOUT_MS, GUEST_FILE_STAT_KINDS, SERVICE_STATUS_VALUES, HOST_REQUEST_ERROR_CODES, SESSION_LIFECYCLE_PHASES, START_SESSION_SENT, GUEST_ACCOUNT_MAX, GUEST_SESSION_AGENT_MAX, GUEST_SESSION_MODEL_MAX, GUEST_ATTACH_AUTHOR_MAX, GUEST_ATTACH_BRANCH_MAX, GUEST_ATTACH_DATA_MAX, GUEST_ATTACH_ID_MAX, GUEST_ATTACH_TEXT_MAX, GUEST_ATTACH_TITLE_MAX, GUEST_ATTACH_URL_MAX, GUEST_CLIPBOARD_TEXT_MAX, GUEST_TOAST_MAX, GUEST_COMPOSE_TEXT_MAX, GUEST_REQUEST_BODY_MAX, GUEST_REQUEST_PATH_MAX, GUEST_REQUEST_RESPONSE_MAX, GUEST_REQUEST_TIMEOUT_MS, GUEST_SETTING_VALUE_MAX, } from './contract.ts';
|
|
17
21
|
export { BROWSER_CONTROL_ACTIONS, BROWSER_PROVIDER_ACTION_TIMEOUT_MS, BROWSER_PROVIDER_IDLE_MS, BROWSER_PROVIDER_OPEN_TIMEOUT_MS, BROWSER_PROVIDER_PATH, BROWSER_PROVIDER_RESPONSE_MAX, BROWSER_SCROLL_DIRECTIONS, BROWSER_VIEWPORT_MODES, isBrowserControlAction, readBrowserProviderRequest, } from './service-providers.ts';
|
|
18
22
|
export type { BrowserCaptureData, BrowserCaptureParameters, BrowserClickData, BrowserClickParameters, BrowserConsoleProblem, BrowserControlAction, BrowserElementBounds, BrowserInspectData, BrowserInspectParameters, BrowserNavigationData, BrowserOpenData, BrowserOpenParameters, BrowserProviderContext, BrowserProviderData, BrowserProviderRequest, BrowserProviderResult, BrowserResizeData, BrowserResizeParameters, BrowserScrollData, BrowserScrollDirection, BrowserScrollParameters, BrowserSnapshotData, BrowserSnapshotElement, BrowserSnapshotParameters, BrowserTab, BrowserTabTarget, BrowserTypeData, BrowserTypeParameters, BrowserViewportMode, BrowserViewportSummary, } from './service-providers.ts';
|
package/dist/index.js
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
export { OPENCHAMBER_SDK_API_VERSION, OPENCHAMBER_SDK_CHANNEL, OPENCHAMBER_SDK_MANIFEST_API_VERSIONS } from "./api-version.js";
|
|
2
2
|
export { GUEST_SCROLLBAR_CSS, GUEST_SCROLLBAR_SCRIPT } from "./scrollbar-style.js";
|
|
3
3
|
export { guestFramePolicy } from "./frame-policy.js";
|
|
4
|
-
export { GUEST_STORAGE_KEY_MAX, GUEST_STORAGE_KEYS_MAX, GUEST_STORAGE_VALUE_BYTES, GUEST_STORAGE_TOTAL_BYTES } from "./workspace.js";
|
|
4
|
+
export { GUEST_STORAGE_KEY_MAX, GUEST_STORAGE_KEYS_MAX, GUEST_STORAGE_VALUE_BYTES, GUEST_STORAGE_TOTAL_BYTES, GUEST_DEVICE_STORAGE_TOTAL_BYTES } from "./workspace.js";
|
|
5
|
+
export { GUEST_STATUS_CONTROLS_MAX, GUEST_STATUS_CONTROL_ID, GUEST_STATUS_CONTROL_LABEL_MAX, GUEST_STATUS_CONTROL_OPTIONS_MAX, GUEST_STATUS_CONTROL_VALUE_MAX } from "./status-controls.js";
|
|
5
6
|
export { compareOpenChamberVersions, hostMeetsOpenChamberEngine, openChamberEngineMinimum, OPENCHAMBER_ENGINE_PATTERN, parseOpenChamberVersion, } from "./host-version.js";
|
|
6
7
|
export { DECLARED_GUEST_CAPABILITIES, GUEST_ACTIONS_MAX, GUEST_ACTION_LABEL_MAX, GUEST_CAPABILITIES, GUEST_COMMANDS_MAX, GUEST_COMMAND_DESCRIPTION_MAX, GUEST_COMMAND_NAME, GUEST_TOOLS_MAX, GUEST_TOOL_COLUMNS_MAX, GUEST_TOOL_COLUMN_MAX, GUEST_TOOL_LANGUAGE_MAX, GUEST_TOOL_MATCH, GUEST_TOOL_MATCH_MAX, GUEST_TOOL_NAME_MAX, GUEST_TOOL_OUTPUTS, GUEST_TOOL_TEMPLATE_MAX, GUEST_TOOL_TEMPLATE_VALUE_MAX, guestActionsNeedConversation, GUEST_FILESYSTEM_PATTERN_MAX, GUEST_FILESYSTEM_PATTERNS_MAX, GUEST_ORIGINS_MAX, GUEST_SERVICE_PROVIDES, GUEST_SURFACE_DOCKS, GUEST_SURFACE_DOCK_DEFAULT, GUEST_SURFACE_DOCK_SIZE_DEFAULT, GUEST_SURFACE_DOCK_SIZE_MAX, GUEST_SURFACE_DOCK_SIZE_MIN, GUEST_STATUS_SECTION_HEIGHT_DEFAULT, GUEST_STATUS_SECTION_HEIGHT_MAX, GUEST_STATUS_SECTION_HEIGHT_MIN, GUEST_STATUS_SECTION_TITLE_MAX, clampStatusSectionHeight, HOST_LINEAR_API_ORIGIN, isGuestFilesystemPattern, hasGuestCapability, hasGuestPage, isGuestApproved, isGuestPackageSvgIcon, requestedGuestCapabilities, resolveAttachEntry, resolvePageEntry, resolveStatusSectionEntry, resolveAttachMode, resolveIntegrationApi, resolveIntegrationAuth, serviceProvides, toPublicService, toPublicIntegration, } from "./manifest.js";
|
|
7
8
|
export { GUEST_FILE_EDITORS_MAX, GUEST_FILE_EDITOR_CONTENT_MAX, GUEST_FILE_EDITOR_PATTERNS_MAX, GUEST_FILE_EDITOR_PATTERN_MAX, GUEST_FILE_EDITOR_TITLE_MAX, GUEST_FILE_EDITOR_VERSION_MAX, createFileSaveTracker, fileEditorPayloadSize, isFileEditorPattern, matchesFileEditorPattern, } from "./file-editor.js";
|
|
8
9
|
export { connectHost, HostRequestError } from "./host.js";
|
|
10
|
+
export { GUEST_POPOVER_COORDINATE_MAX, GUEST_POPOVER_DATA_DEPTH_MAX, GUEST_POPOVER_DATA_MAX, GUEST_POPOVER_HEIGHT_MAX, GUEST_POPOVER_HEIGHT_MIN, GUEST_POPOVER_ID, GUEST_POPOVER_SIDES, GUEST_POPOVER_WIDTH_MAX, GUEST_POPOVER_WIDTH_MIN, GUEST_POPOVER_CLOSE_REASONS, isGuestPopoverRequest, } from "./popover.js";
|
|
9
11
|
export { clampAttachRequest, clampBadgeCount, clampFrameHeight, isGuestCommitSha, GUEST_COMMIT_SHA, clampPromptRequest, clampStartSessionRequest, guestFileScope, isAttachData, isJsonValue, isFileListResult, isFileReadResult, isFileStatResult, isGenerateResult, isFileWriteResult, isGuestFilePath, isGuestAttachItem, isGuestMessageItem, isGuestSessionItem, isServiceStatusResult, isGuestRequestPath, isGuestRequestResult, isHostRequestErrorCode, isPromptResult, isStartSessionResult, readHostMessage, resolveHostRequestErrorCode, } from "./contract.js";
|
|
10
12
|
export { EMPTY_GUEST_CONNECTION, GUEST_BADGE_MAX, GUEST_FRAME_HEIGHT_MAX, GUEST_ITEM_MESSAGE_TEXT_MAX, GUEST_ITEM_SESSION_MAX, GUEST_RESOLVE_ERROR_MAX, GUEST_FILE_CONTENT_MAX, GUEST_FILE_ENTRY_KINDS, GUEST_FILE_LIST_MAX, GUEST_FILE_PATH_MAX, GUEST_GENERATE_OUTPUT_TOKENS_MAX, GUEST_GENERATE_PROMPT_MAX, GUEST_GENERATE_SYSTEM_MAX, GUEST_GENERATE_TEXT_MAX, GUEST_GENERATE_TIMEOUT_MS, GUEST_FILE_STAT_KINDS, SERVICE_STATUS_VALUES, HOST_REQUEST_ERROR_CODES, SESSION_LIFECYCLE_PHASES, START_SESSION_SENT, GUEST_ACCOUNT_MAX, GUEST_SESSION_AGENT_MAX, GUEST_SESSION_MODEL_MAX, GUEST_ATTACH_AUTHOR_MAX, GUEST_ATTACH_BRANCH_MAX, GUEST_ATTACH_DATA_MAX, GUEST_ATTACH_ID_MAX, GUEST_ATTACH_TEXT_MAX, GUEST_ATTACH_TITLE_MAX, GUEST_ATTACH_URL_MAX, GUEST_CLIPBOARD_TEXT_MAX, GUEST_TOAST_MAX, GUEST_COMPOSE_TEXT_MAX, GUEST_REQUEST_BODY_MAX, GUEST_REQUEST_PATH_MAX, GUEST_REQUEST_RESPONSE_MAX, GUEST_REQUEST_TIMEOUT_MS, GUEST_SETTING_VALUE_MAX, } from "./contract.js";
|
|
11
13
|
export { BROWSER_CONTROL_ACTIONS, BROWSER_PROVIDER_ACTION_TIMEOUT_MS, BROWSER_PROVIDER_IDLE_MS, BROWSER_PROVIDER_OPEN_TIMEOUT_MS, BROWSER_PROVIDER_PATH, BROWSER_PROVIDER_RESPONSE_MAX, BROWSER_SCROLL_DIRECTIONS, BROWSER_VIEWPORT_MODES, isBrowserControlAction, readBrowserProviderRequest, } from "./service-providers.js";
|
package/dist/manifest.d.ts
CHANGED
|
@@ -60,6 +60,8 @@ export type StatusSectionContribution = true | {
|
|
|
60
60
|
entry: string;
|
|
61
61
|
title?: string;
|
|
62
62
|
height?: number;
|
|
63
|
+
defaultExpanded?: boolean;
|
|
64
|
+
requiresProject?: boolean;
|
|
63
65
|
};
|
|
64
66
|
/** Characters in a status section title. */
|
|
65
67
|
export declare const GUEST_STATUS_SECTION_TITLE_MAX = 60;
|
package/dist/parse.d.ts
CHANGED
|
@@ -12,10 +12,10 @@ export declare const openChamberManifestSchema: z.ZodObject<{
|
|
|
12
12
|
icon: z.ZodString;
|
|
13
13
|
entry: z.ZodOptional<z.ZodString>;
|
|
14
14
|
dock: z.ZodOptional<z.ZodEnum<{
|
|
15
|
-
top: "top";
|
|
16
|
-
bottom: "bottom";
|
|
17
15
|
left: "left";
|
|
18
16
|
right: "right";
|
|
17
|
+
top: "top";
|
|
18
|
+
bottom: "bottom";
|
|
19
19
|
}>>;
|
|
20
20
|
size: z.ZodOptional<z.ZodNumber>;
|
|
21
21
|
}, z.core.$strip>;
|
|
@@ -40,6 +40,8 @@ export declare const openChamberManifestSchema: z.ZodObject<{
|
|
|
40
40
|
entry: z.ZodString;
|
|
41
41
|
title: z.ZodOptional<z.ZodString>;
|
|
42
42
|
height: z.ZodOptional<z.ZodNumber>;
|
|
43
|
+
defaultExpanded: z.ZodOptional<z.ZodBoolean>;
|
|
44
|
+
requiresProject: z.ZodOptional<z.ZodBoolean>;
|
|
43
45
|
}, z.core.$strip>]>>;
|
|
44
46
|
capabilities: z.ZodOptional<z.ZodArray<z.ZodEnum<{
|
|
45
47
|
sessions: "sessions";
|
|
@@ -184,10 +186,10 @@ export declare const packageManifestSchema: z.ZodObject<{
|
|
|
184
186
|
icon: z.ZodString;
|
|
185
187
|
entry: z.ZodOptional<z.ZodString>;
|
|
186
188
|
dock: z.ZodOptional<z.ZodEnum<{
|
|
187
|
-
top: "top";
|
|
188
|
-
bottom: "bottom";
|
|
189
189
|
left: "left";
|
|
190
190
|
right: "right";
|
|
191
|
+
top: "top";
|
|
192
|
+
bottom: "bottom";
|
|
191
193
|
}>>;
|
|
192
194
|
size: z.ZodOptional<z.ZodNumber>;
|
|
193
195
|
}, z.core.$strip>;
|
|
@@ -212,6 +214,8 @@ export declare const packageManifestSchema: z.ZodObject<{
|
|
|
212
214
|
entry: z.ZodString;
|
|
213
215
|
title: z.ZodOptional<z.ZodString>;
|
|
214
216
|
height: z.ZodOptional<z.ZodNumber>;
|
|
217
|
+
defaultExpanded: z.ZodOptional<z.ZodBoolean>;
|
|
218
|
+
requiresProject: z.ZodOptional<z.ZodBoolean>;
|
|
215
219
|
}, z.core.$strip>]>>;
|
|
216
220
|
capabilities: z.ZodOptional<z.ZodArray<z.ZodEnum<{
|
|
217
221
|
sessions: "sessions";
|
package/dist/parse.js
CHANGED
|
@@ -215,6 +215,8 @@ const contributesSchema = z.object({
|
|
|
215
215
|
entry: z.string().trim().refine((value) => isSafeAssetPath(value) && value.toLowerCase().endsWith('.html')),
|
|
216
216
|
title: z.string().trim().min(1).max(GUEST_STATUS_SECTION_TITLE_MAX).optional(),
|
|
217
217
|
height: z.number().int().min(GUEST_STATUS_SECTION_HEIGHT_MIN).max(GUEST_STATUS_SECTION_HEIGHT_MAX).optional(),
|
|
218
|
+
defaultExpanded: z.boolean().optional(),
|
|
219
|
+
requiresProject: z.boolean().optional(),
|
|
218
220
|
})]).optional(),
|
|
219
221
|
capabilities: z.array(z.enum(DECLARED_GUEST_CAPABILITIES)).max(8).optional(),
|
|
220
222
|
integration: integrationSchema.optional(),
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import type { JsonValue } from './contract.ts';
|
|
3
|
+
export declare const guestPopoverDataSchema: z.ZodCustom<JsonValue, JsonValue>;
|
|
4
|
+
export declare const guestPopoverRequestSchema: z.ZodObject<{
|
|
5
|
+
id: z.ZodString;
|
|
6
|
+
anchor: z.ZodObject<{
|
|
7
|
+
x: z.ZodNumber;
|
|
8
|
+
y: z.ZodNumber;
|
|
9
|
+
width: z.ZodNumber;
|
|
10
|
+
height: z.ZodNumber;
|
|
11
|
+
}, z.core.$strict>;
|
|
12
|
+
width: z.ZodNumber;
|
|
13
|
+
height: z.ZodNumber;
|
|
14
|
+
side: z.ZodOptional<z.ZodEnum<{
|
|
15
|
+
left: "left";
|
|
16
|
+
right: "right";
|
|
17
|
+
top: "top";
|
|
18
|
+
bottom: "bottom";
|
|
19
|
+
}>>;
|
|
20
|
+
focus: z.ZodOptional<z.ZodBoolean>;
|
|
21
|
+
data: z.ZodCustom<JsonValue, JsonValue>;
|
|
22
|
+
}, z.core.$strict>;
|
|
23
|
+
export declare const guestPopoverClosedEventSchema: z.ZodObject<{
|
|
24
|
+
id: z.ZodString;
|
|
25
|
+
reason: z.ZodEnum<{
|
|
26
|
+
escape: "escape";
|
|
27
|
+
outside: "outside";
|
|
28
|
+
anchor: "anchor";
|
|
29
|
+
owner: "owner";
|
|
30
|
+
closed: "closed";
|
|
31
|
+
replaced: "replaced";
|
|
32
|
+
}>;
|
|
33
|
+
}, z.core.$strict>;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { GUEST_POPOVER_CLOSE_REASONS, GUEST_POPOVER_COORDINATE_MAX, GUEST_POPOVER_DATA_MAX, GUEST_POPOVER_DATA_DEPTH_MAX, GUEST_POPOVER_HEIGHT_MAX, GUEST_POPOVER_HEIGHT_MIN, GUEST_POPOVER_ID, GUEST_POPOVER_SIDES, GUEST_POPOVER_WIDTH_MAX, GUEST_POPOVER_WIDTH_MIN, } from "./popover.js";
|
|
3
|
+
const jsonPrimitive = z.union([z.string().max(GUEST_POPOVER_DATA_MAX), z.number(), z.boolean(), z.null()]);
|
|
4
|
+
const jsonRecord = z.record(z.string().max(GUEST_POPOVER_DATA_MAX), z.unknown());
|
|
5
|
+
// Parse one level at a time: recursive z.json() would traverse a hostile cycle
|
|
6
|
+
// or deeply nested structured-clone payload before its size/depth refinement.
|
|
7
|
+
export const guestPopoverDataSchema = z.custom((value) => {
|
|
8
|
+
const pending = [{ value, depth: 0 }];
|
|
9
|
+
let size = 0;
|
|
10
|
+
while (pending.length > 0) {
|
|
11
|
+
const current = pending.pop();
|
|
12
|
+
if (!current)
|
|
13
|
+
break;
|
|
14
|
+
const item = current.value;
|
|
15
|
+
if (current.depth >= GUEST_POPOVER_DATA_DEPTH_MAX)
|
|
16
|
+
return false;
|
|
17
|
+
if (Array.isArray(item)) {
|
|
18
|
+
if (item.length > GUEST_POPOVER_DATA_MAX)
|
|
19
|
+
return false;
|
|
20
|
+
if (Object.keys(item).some((key) => !/^(0|[1-9]\d*)$/.test(key) || Number(key) >= item.length))
|
|
21
|
+
return false;
|
|
22
|
+
size += 2 + Math.max(0, item.length - 1);
|
|
23
|
+
for (const child of item)
|
|
24
|
+
pending.push({ value: child, depth: current.depth + 1 });
|
|
25
|
+
}
|
|
26
|
+
else {
|
|
27
|
+
const primitive = jsonPrimitive.safeParse(item);
|
|
28
|
+
if (primitive.success) {
|
|
29
|
+
size += JSON.stringify(primitive.data).length;
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
const record = jsonRecord.safeParse(item);
|
|
33
|
+
if (!record.success)
|
|
34
|
+
return false;
|
|
35
|
+
const prototype = Object.getPrototypeOf(item);
|
|
36
|
+
if (prototype !== null && prototype !== Object.prototype)
|
|
37
|
+
return false;
|
|
38
|
+
// z.record normalizes prototype-named keys. Validate the original
|
|
39
|
+
// structured-clone values so those keys cannot escape the budget.
|
|
40
|
+
const entries = Object.entries(Object.getOwnPropertyDescriptors(item)).filter(([, descriptor]) => descriptor.enumerable);
|
|
41
|
+
size += 2 + Math.max(0, entries.length - 1);
|
|
42
|
+
for (const [key, descriptor] of entries) {
|
|
43
|
+
if (!('value' in descriptor))
|
|
44
|
+
return false;
|
|
45
|
+
size += JSON.stringify(key).length + 1;
|
|
46
|
+
if (size > GUEST_POPOVER_DATA_MAX)
|
|
47
|
+
return false;
|
|
48
|
+
pending.push({ value: descriptor.value, depth: current.depth + 1 });
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
if (size > GUEST_POPOVER_DATA_MAX)
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
return true;
|
|
56
|
+
}, 'Popover data must be finite JSON within its size and depth limits.');
|
|
57
|
+
export const guestPopoverRequestSchema = z.object({
|
|
58
|
+
id: z.string().regex(GUEST_POPOVER_ID),
|
|
59
|
+
anchor: z.object({
|
|
60
|
+
x: z.number().finite().min(-GUEST_POPOVER_COORDINATE_MAX).max(GUEST_POPOVER_COORDINATE_MAX),
|
|
61
|
+
y: z.number().finite().min(-GUEST_POPOVER_COORDINATE_MAX).max(GUEST_POPOVER_COORDINATE_MAX),
|
|
62
|
+
width: z.number().finite().positive().max(GUEST_POPOVER_COORDINATE_MAX),
|
|
63
|
+
height: z.number().finite().positive().max(GUEST_POPOVER_COORDINATE_MAX),
|
|
64
|
+
}).strict(),
|
|
65
|
+
width: z.number().int().min(GUEST_POPOVER_WIDTH_MIN).max(GUEST_POPOVER_WIDTH_MAX),
|
|
66
|
+
height: z.number().int().min(GUEST_POPOVER_HEIGHT_MIN).max(GUEST_POPOVER_HEIGHT_MAX),
|
|
67
|
+
side: z.enum(GUEST_POPOVER_SIDES).optional(),
|
|
68
|
+
focus: z.boolean().optional(),
|
|
69
|
+
data: guestPopoverDataSchema,
|
|
70
|
+
}).strict();
|
|
71
|
+
export const guestPopoverClosedEventSchema = z.object({
|
|
72
|
+
id: z.string().regex(GUEST_POPOVER_ID),
|
|
73
|
+
reason: z.enum(GUEST_POPOVER_CLOSE_REASONS),
|
|
74
|
+
}).strict();
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { JsonValue } from './contract.ts';
|
|
2
|
+
export declare const GUEST_POPOVER_ID: RegExp;
|
|
3
|
+
export declare const GUEST_POPOVER_COORDINATE_MAX = 100000;
|
|
4
|
+
export declare const GUEST_POPOVER_WIDTH_MIN = 160;
|
|
5
|
+
export declare const GUEST_POPOVER_WIDTH_MAX = 640;
|
|
6
|
+
export declare const GUEST_POPOVER_HEIGHT_MIN = 48;
|
|
7
|
+
export declare const GUEST_POPOVER_HEIGHT_MAX = 480;
|
|
8
|
+
export declare const GUEST_POPOVER_DATA_MAX = 16000;
|
|
9
|
+
export declare const GUEST_POPOVER_DATA_DEPTH_MAX = 32;
|
|
10
|
+
export declare const GUEST_POPOVER_SIDES: readonly ["left", "right", "top", "bottom"];
|
|
11
|
+
export type GuestPopoverSide = (typeof GUEST_POPOVER_SIDES)[number];
|
|
12
|
+
export type GuestPopoverAnchor = {
|
|
13
|
+
x: number;
|
|
14
|
+
y: number;
|
|
15
|
+
width: number;
|
|
16
|
+
height: number;
|
|
17
|
+
};
|
|
18
|
+
export type GuestPopoverRequest = {
|
|
19
|
+
id: string;
|
|
20
|
+
anchor: GuestPopoverAnchor;
|
|
21
|
+
width: number;
|
|
22
|
+
height: number;
|
|
23
|
+
side?: GuestPopoverSide;
|
|
24
|
+
focus?: boolean;
|
|
25
|
+
data: JsonValue;
|
|
26
|
+
};
|
|
27
|
+
export type GuestPopoverContext = {
|
|
28
|
+
id: string;
|
|
29
|
+
data: JsonValue;
|
|
30
|
+
};
|
|
31
|
+
export declare const GUEST_POPOVER_CLOSE_REASONS: readonly ["escape", "outside", "anchor", "owner", "closed", "replaced"];
|
|
32
|
+
export type GuestPopoverClosedEvent = {
|
|
33
|
+
id: string;
|
|
34
|
+
reason: (typeof GUEST_POPOVER_CLOSE_REASONS)[number];
|
|
35
|
+
};
|
|
36
|
+
/** Guest-side fast-fail guard. The host schema remains authoritative for untyped callers. */
|
|
37
|
+
export declare const isGuestPopoverRequest: (request: GuestPopoverRequest) => boolean;
|
package/dist/popover.js
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
export const GUEST_POPOVER_ID = /^[A-Za-z0-9_-]{1,80}$/;
|
|
2
|
+
export const GUEST_POPOVER_COORDINATE_MAX = 100_000;
|
|
3
|
+
export const GUEST_POPOVER_WIDTH_MIN = 160;
|
|
4
|
+
export const GUEST_POPOVER_WIDTH_MAX = 640;
|
|
5
|
+
export const GUEST_POPOVER_HEIGHT_MIN = 48;
|
|
6
|
+
export const GUEST_POPOVER_HEIGHT_MAX = 480;
|
|
7
|
+
export const GUEST_POPOVER_DATA_MAX = 16_000;
|
|
8
|
+
export const GUEST_POPOVER_DATA_DEPTH_MAX = 32;
|
|
9
|
+
export const GUEST_POPOVER_SIDES = ['left', 'right', 'top', 'bottom'];
|
|
10
|
+
export const GUEST_POPOVER_CLOSE_REASONS = ['escape', 'outside', 'anchor', 'owner', 'closed', 'replaced'];
|
|
11
|
+
const hasBoundedJsonTree = (value, depth = 0, budget = { remaining: GUEST_POPOVER_DATA_MAX }) => {
|
|
12
|
+
if (--budget.remaining < 0 || depth >= GUEST_POPOVER_DATA_DEPTH_MAX)
|
|
13
|
+
return false;
|
|
14
|
+
if (value === null)
|
|
15
|
+
return true;
|
|
16
|
+
const children = Array.isArray(value) ? value : Object(value) === value ? Object.values(value) : [];
|
|
17
|
+
for (const item of children) {
|
|
18
|
+
if (!hasBoundedJsonTree(item, depth + 1, budget))
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
return true;
|
|
22
|
+
};
|
|
23
|
+
// Used only after the depth bound above, including for JavaScript consumers
|
|
24
|
+
// whose values need not satisfy the public JsonValue type.
|
|
25
|
+
const isFiniteJson = (value) => {
|
|
26
|
+
if (value === null || value === true || value === false)
|
|
27
|
+
return true;
|
|
28
|
+
if (Array.isArray(value))
|
|
29
|
+
return Object.keys(value).every((key) => /^(0|[1-9]\d*)$/.test(key) && Number(key) < value.length)
|
|
30
|
+
&& value.every(isFiniteJson);
|
|
31
|
+
if (Object(value) === value) {
|
|
32
|
+
const prototype = Object.getPrototypeOf(value);
|
|
33
|
+
return (prototype === null || prototype === Object.prototype) && Object.values(value).every(isFiniteJson);
|
|
34
|
+
}
|
|
35
|
+
return String(value) === value || Number.isFinite(value);
|
|
36
|
+
};
|
|
37
|
+
/** Guest-side fast-fail guard. The host schema remains authoritative for untyped callers. */
|
|
38
|
+
export const isGuestPopoverRequest = (request) => {
|
|
39
|
+
try {
|
|
40
|
+
if (String(request.id) !== request.id || !GUEST_POPOVER_ID.test(request.id)
|
|
41
|
+
|| !Number.isFinite(request.anchor.x) || Math.abs(request.anchor.x) > GUEST_POPOVER_COORDINATE_MAX
|
|
42
|
+
|| !Number.isFinite(request.anchor.y) || Math.abs(request.anchor.y) > GUEST_POPOVER_COORDINATE_MAX
|
|
43
|
+
|| !Number.isFinite(request.anchor.width) || !Number.isFinite(request.anchor.height)
|
|
44
|
+
|| request.anchor.width <= 0 || request.anchor.height <= 0
|
|
45
|
+
|| request.anchor.width > GUEST_POPOVER_COORDINATE_MAX || request.anchor.height > GUEST_POPOVER_COORDINATE_MAX
|
|
46
|
+
|| !Number.isInteger(request.width) || request.width < GUEST_POPOVER_WIDTH_MIN || request.width > GUEST_POPOVER_WIDTH_MAX
|
|
47
|
+
|| !Number.isInteger(request.height) || request.height < GUEST_POPOVER_HEIGHT_MIN || request.height > GUEST_POPOVER_HEIGHT_MAX
|
|
48
|
+
|| (request.side !== undefined && !GUEST_POPOVER_SIDES.includes(request.side))
|
|
49
|
+
|| (request.focus !== undefined && request.focus !== true && request.focus !== false)
|
|
50
|
+
|| !hasBoundedJsonTree(request.data) || !isFiniteJson(request.data))
|
|
51
|
+
return false;
|
|
52
|
+
return JSON.stringify(request.data).length <= GUEST_POPOVER_DATA_MAX;
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
};
|