local-browser-bridge 0.1.0
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/README.md +724 -0
- package/dist/package.json +61 -0
- package/dist/src/browser/chrome.d.ts +19 -0
- package/dist/src/browser/chrome.js +778 -0
- package/dist/src/browser/index.d.ts +3 -0
- package/dist/src/browser/index.js +25 -0
- package/dist/src/browser/safari.d.ts +41 -0
- package/dist/src/browser/safari.js +827 -0
- package/dist/src/browser-attach-ux-helper.d.ts +39 -0
- package/dist/src/browser-attach-ux-helper.js +157 -0
- package/dist/src/capabilities.d.ts +3 -0
- package/dist/src/capabilities.js +182 -0
- package/dist/src/chrome-relay-error-helper.d.ts +19 -0
- package/dist/src/chrome-relay-error-helper.js +78 -0
- package/dist/src/chrome-relay-helper-cli.d.ts +2 -0
- package/dist/src/chrome-relay-helper-cli.js +97 -0
- package/dist/src/chrome-relay-helper.d.ts +29 -0
- package/dist/src/chrome-relay-helper.js +151 -0
- package/dist/src/chrome-relay-state.d.ts +23 -0
- package/dist/src/chrome-relay-state.js +108 -0
- package/dist/src/claude-code.d.ts +20 -0
- package/dist/src/claude-code.js +66 -0
- package/dist/src/cli-reference-adapter.d.ts +13 -0
- package/dist/src/cli-reference-adapter.js +48 -0
- package/dist/src/cli.d.ts +3 -0
- package/dist/src/cli.js +200 -0
- package/dist/src/codex.d.ts +17 -0
- package/dist/src/codex.js +25 -0
- package/dist/src/connection-ux.d.ts +61 -0
- package/dist/src/connection-ux.js +256 -0
- package/dist/src/errors.d.ts +12 -0
- package/dist/src/errors.js +58 -0
- package/dist/src/http-reference-adapter.d.ts +34 -0
- package/dist/src/http-reference-adapter.js +61 -0
- package/dist/src/http.d.ts +3 -0
- package/dist/src/http.js +161 -0
- package/dist/src/index.d.ts +17 -0
- package/dist/src/index.js +43 -0
- package/dist/src/mcp-stdio.d.ts +2 -0
- package/dist/src/mcp-stdio.js +10 -0
- package/dist/src/mcp.d.ts +25 -0
- package/dist/src/mcp.js +483 -0
- package/dist/src/reference-adapter.d.ts +32 -0
- package/dist/src/reference-adapter.js +42 -0
- package/dist/src/service/attach-service.d.ts +28 -0
- package/dist/src/service/attach-service.js +272 -0
- package/dist/src/session-metadata.d.ts +4 -0
- package/dist/src/session-metadata.js +88 -0
- package/dist/src/store/session-store.d.ts +14 -0
- package/dist/src/store/session-store.js +52 -0
- package/dist/src/target.d.ts +9 -0
- package/dist/src/target.js +61 -0
- package/dist/src/types.d.ts +397 -0
- package/dist/src/types.js +2 -0
- package/dist/tests/attach-service.test.d.ts +1 -0
- package/dist/tests/attach-service.test.js +1367 -0
- package/dist/tests/browser-attach-ux-helper.test.d.ts +1 -0
- package/dist/tests/browser-attach-ux-helper.test.js +139 -0
- package/dist/tests/chrome-relay-error-helper.test.d.ts +1 -0
- package/dist/tests/chrome-relay-error-helper.test.js +67 -0
- package/dist/tests/chrome-relay-helper.test.d.ts +1 -0
- package/dist/tests/chrome-relay-helper.test.js +142 -0
- package/dist/tests/chrome-relay-state-schema.test.d.ts +1 -0
- package/dist/tests/chrome-relay-state-schema.test.js +96 -0
- package/dist/tests/claude-code-wrapper.test.d.ts +1 -0
- package/dist/tests/claude-code-wrapper.test.js +170 -0
- package/dist/tests/codex.test.d.ts +1 -0
- package/dist/tests/codex.test.js +210 -0
- package/dist/tests/demo-client-smoke.test.d.ts +1 -0
- package/dist/tests/demo-client-smoke.test.js +405 -0
- package/dist/tests/docs-fixtures.test.d.ts +1 -0
- package/dist/tests/docs-fixtures.test.js +255 -0
- package/dist/tests/doctor-connect-wrapper.test.d.ts +1 -0
- package/dist/tests/doctor-connect-wrapper.test.js +62 -0
- package/dist/tests/fixtures/doctor-connect-cli-stub.d.ts +1 -0
- package/dist/tests/fixtures/doctor-connect-cli-stub.js +93 -0
- package/dist/tests/fixtures/public-root-cli-stub.d.ts +210 -0
- package/dist/tests/fixtures/public-root-cli-stub.js +143 -0
- package/dist/tests/fixtures/public-root-consumer.js +67 -0
- package/dist/tests/mcp.test.d.ts +1 -0
- package/dist/tests/mcp.test.js +345 -0
- package/dist/tests/public-consumer-helpers.test.d.ts +1 -0
- package/dist/tests/public-consumer-helpers.test.js +33 -0
- package/dist/tests/public-package-git-consumption.test.d.ts +1 -0
- package/dist/tests/public-package-git-consumption.test.js +56 -0
- package/dist/tests/public-root-consumer-smoke.test.d.ts +1 -0
- package/dist/tests/public-root-consumer-smoke.test.js +214 -0
- package/dist/tests/reference-adapter.test.d.ts +1 -0
- package/dist/tests/reference-adapter.test.js +220 -0
- package/dist/tests/transport-reference-adapters.test.d.ts +1 -0
- package/dist/tests/transport-reference-adapters.test.js +214 -0
- package/package.json +61 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const strict_1 = __importDefault(require("node:assert/strict"));
|
|
7
|
+
const node_test_1 = __importDefault(require("node:test"));
|
|
8
|
+
const browser_attach_ux_helper_1 = require("../src/browser-attach-ux-helper");
|
|
9
|
+
(0, node_test_1.default)("interpretBrowserAttachUxFromDiagnostics maps chrome relay readiness into a shared-tab-scoped blocked state", () => {
|
|
10
|
+
const interpretation = (0, browser_attach_ux_helper_1.interpretBrowserAttachUxFromDiagnostics)({
|
|
11
|
+
browser: "chrome",
|
|
12
|
+
attachMode: "relay",
|
|
13
|
+
diagnostics: {
|
|
14
|
+
browser: "chrome",
|
|
15
|
+
checkedAt: "2026-03-30T12:00:00.000Z",
|
|
16
|
+
runtime: {
|
|
17
|
+
platform: "darwin",
|
|
18
|
+
arch: "arm64",
|
|
19
|
+
nodeVersion: "v25.8.0",
|
|
20
|
+
safariRunning: false
|
|
21
|
+
},
|
|
22
|
+
host: {
|
|
23
|
+
osascriptAvailable: true,
|
|
24
|
+
screencaptureAvailable: true,
|
|
25
|
+
safariApplicationAvailable: true
|
|
26
|
+
},
|
|
27
|
+
supportedFeatures: {
|
|
28
|
+
inspectTabs: true,
|
|
29
|
+
attach: true,
|
|
30
|
+
activate: false,
|
|
31
|
+
navigate: false,
|
|
32
|
+
screenshot: false,
|
|
33
|
+
savedSessions: true,
|
|
34
|
+
cli: true,
|
|
35
|
+
httpApi: true
|
|
36
|
+
},
|
|
37
|
+
constraints: [],
|
|
38
|
+
attach: {
|
|
39
|
+
direct: {
|
|
40
|
+
mode: "direct",
|
|
41
|
+
source: "user-browser",
|
|
42
|
+
scope: "browser",
|
|
43
|
+
supported: true,
|
|
44
|
+
ready: true,
|
|
45
|
+
state: "ready",
|
|
46
|
+
blockers: []
|
|
47
|
+
},
|
|
48
|
+
relay: {
|
|
49
|
+
mode: "relay",
|
|
50
|
+
source: "extension-relay",
|
|
51
|
+
scope: "tab",
|
|
52
|
+
supported: true,
|
|
53
|
+
ready: false,
|
|
54
|
+
state: "attention-required",
|
|
55
|
+
blockers: [{ code: "relay_share_required", message: "share the tab first" }]
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
strict_1.default.deepEqual(interpretation, {
|
|
61
|
+
state: "blocked",
|
|
62
|
+
browser: "chrome",
|
|
63
|
+
attachMode: "relay",
|
|
64
|
+
operation: "attach",
|
|
65
|
+
label: "Chrome (shared tab, read-only)",
|
|
66
|
+
readOnly: true,
|
|
67
|
+
sharedTabScoped: true,
|
|
68
|
+
readiness: "attention-required",
|
|
69
|
+
prompt: "Chrome relay only works for a tab you explicitly share. Share the tab first, then retry.",
|
|
70
|
+
scopeNote: "Scope note: Chrome relay is still limited to the currently shared tab and remains read-only.",
|
|
71
|
+
retryGuidance: undefined,
|
|
72
|
+
retryable: undefined,
|
|
73
|
+
userActionRequired: undefined,
|
|
74
|
+
relayFailureCategory: undefined
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
(0, node_test_1.default)("interpretBrowserAttachUxFromSession preserves shared-tab resume honesty", () => {
|
|
78
|
+
const interpretation = (0, browser_attach_ux_helper_1.interpretBrowserAttachUxFromSession)({
|
|
79
|
+
operation: "resumeSession",
|
|
80
|
+
session: {
|
|
81
|
+
browser: "chrome",
|
|
82
|
+
kind: "chrome-readonly",
|
|
83
|
+
attach: {
|
|
84
|
+
mode: "relay",
|
|
85
|
+
source: "extension-relay",
|
|
86
|
+
scope: "tab",
|
|
87
|
+
resumable: false,
|
|
88
|
+
resumeRequiresUserGesture: true
|
|
89
|
+
},
|
|
90
|
+
semantics: {
|
|
91
|
+
inspect: "shared-tab-only",
|
|
92
|
+
list: "saved-session",
|
|
93
|
+
resume: "current-shared-tab",
|
|
94
|
+
tabReference: {
|
|
95
|
+
windowIndex: "synthetic-shared-tab-position",
|
|
96
|
+
tabIndex: "synthetic-shared-tab-position"
|
|
97
|
+
}
|
|
98
|
+
},
|
|
99
|
+
status: { state: "read-only", canAct: false }
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
strict_1.default.equal(interpretation.state, "resumed");
|
|
103
|
+
strict_1.default.equal(interpretation.label, "Chrome (shared tab, read-only)");
|
|
104
|
+
strict_1.default.equal(interpretation.readOnly, true);
|
|
105
|
+
strict_1.default.equal(interpretation.sharedTabScoped, true);
|
|
106
|
+
strict_1.default.equal(interpretation.prompt, "That shared-tab grant is no longer active. Click the relay extension again on the original tab, then retry resume.");
|
|
107
|
+
strict_1.default.equal(interpretation.scopeNote, "Scope note: Chrome relay is still limited to the currently shared tab and remains read-only.");
|
|
108
|
+
});
|
|
109
|
+
(0, node_test_1.default)("interpretBrowserAttachUxFromError reuses structured relay failure interpretation", () => {
|
|
110
|
+
const interpretation = (0, browser_attach_ux_helper_1.interpretBrowserAttachUxFromError)({
|
|
111
|
+
details: {
|
|
112
|
+
context: { browser: "chrome", attachMode: "relay", operation: "resumeSession" },
|
|
113
|
+
relay: {
|
|
114
|
+
branch: "use-current-shared-tab",
|
|
115
|
+
retryable: true,
|
|
116
|
+
userActionRequired: true,
|
|
117
|
+
phase: "shared-tab-match",
|
|
118
|
+
sharedTabScope: "current-shared-tab",
|
|
119
|
+
currentSharedTabMatches: false
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
strict_1.default.deepEqual(interpretation, {
|
|
124
|
+
state: "user-action-required",
|
|
125
|
+
browser: "chrome",
|
|
126
|
+
attachMode: "relay",
|
|
127
|
+
operation: "resumeSession",
|
|
128
|
+
label: "Chrome (shared tab, read-only)",
|
|
129
|
+
readOnly: true,
|
|
130
|
+
sharedTabScoped: true,
|
|
131
|
+
readiness: undefined,
|
|
132
|
+
prompt: "Chrome relay attach only works for the tab that is currently shared. Use the shared tab or share a different tab first.",
|
|
133
|
+
scopeNote: "Scope note: Chrome relay is still limited to the currently shared tab and remains read-only.",
|
|
134
|
+
retryGuidance: "Retry guidance: Wait for the user action to complete, then retry the same relay path.",
|
|
135
|
+
retryable: true,
|
|
136
|
+
userActionRequired: true,
|
|
137
|
+
relayFailureCategory: "shared-tab-read-only-scope-limitation"
|
|
138
|
+
});
|
|
139
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const strict_1 = __importDefault(require("node:assert/strict"));
|
|
7
|
+
const node_test_1 = __importDefault(require("node:test"));
|
|
8
|
+
const chrome_relay_error_helper_1 = require("../src/chrome-relay-error-helper");
|
|
9
|
+
(0, node_test_1.default)("interpretChromeRelayFailure maps stable consumer categories without implying browser-wide access", () => {
|
|
10
|
+
const sharedTabScopeFailure = (0, chrome_relay_error_helper_1.interpretChromeRelayFailure)({
|
|
11
|
+
relay: {
|
|
12
|
+
branch: "use-current-shared-tab",
|
|
13
|
+
retryable: true,
|
|
14
|
+
userActionRequired: true,
|
|
15
|
+
phase: "target-selection",
|
|
16
|
+
sharedTabScope: "current-shared-tab",
|
|
17
|
+
currentSharedTabMatches: false
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
strict_1.default.deepEqual(sharedTabScopeFailure, {
|
|
21
|
+
category: "shared-tab-read-only-scope-limitation",
|
|
22
|
+
retryable: true,
|
|
23
|
+
userActionRequired: true,
|
|
24
|
+
branch: "use-current-shared-tab",
|
|
25
|
+
sharedTabScope: "current-shared-tab",
|
|
26
|
+
scopeLimitedToCurrentSharedTab: true,
|
|
27
|
+
readOnly: true
|
|
28
|
+
});
|
|
29
|
+
const shareRequired = (0, chrome_relay_error_helper_1.interpretChromeRelayFailure)({
|
|
30
|
+
relay: {
|
|
31
|
+
branch: "share-original-tab-again",
|
|
32
|
+
retryable: true,
|
|
33
|
+
userActionRequired: true,
|
|
34
|
+
phase: "session-precondition",
|
|
35
|
+
sharedTabScope: "current-shared-tab"
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
strict_1.default.equal(shareRequired?.category, "share-required");
|
|
39
|
+
strict_1.default.equal((0, chrome_relay_error_helper_1.chromeRelayBranchPrompt)(shareRequired?.branch), "That shared-tab grant is no longer active. Click the relay extension again on the original tab, then retry resume.");
|
|
40
|
+
strict_1.default.equal((0, chrome_relay_error_helper_1.chromeRelayRetryGuidance)(shareRequired), "Retry guidance: Wait for the user action to complete, then retry the same relay path.");
|
|
41
|
+
strict_1.default.equal((0, chrome_relay_error_helper_1.chromeRelayScopeNote)(shareRequired), "Scope note: Chrome relay is still limited to the currently shared tab and remains read-only.");
|
|
42
|
+
});
|
|
43
|
+
(0, node_test_1.default)("interpretChromeRelayFailure returns retryability fallbacks and handles missing relay details", () => {
|
|
44
|
+
strict_1.default.equal((0, chrome_relay_error_helper_1.interpretChromeRelayFailure)(undefined), undefined);
|
|
45
|
+
const retryable = (0, chrome_relay_error_helper_1.interpretChromeRelayFailure)({
|
|
46
|
+
relay: {
|
|
47
|
+
branch: "unsupported",
|
|
48
|
+
retryable: true,
|
|
49
|
+
userActionRequired: false,
|
|
50
|
+
phase: "diagnostics",
|
|
51
|
+
sharedTabScope: "current-shared-tab"
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
strict_1.default.equal(retryable?.category, "retryable-relay-failure");
|
|
55
|
+
strict_1.default.equal((0, chrome_relay_error_helper_1.chromeRelayRetryGuidance)(retryable), "Retry guidance: A targeted retry on the same relay path is reasonable.");
|
|
56
|
+
const nonRetryable = (0, chrome_relay_error_helper_1.interpretChromeRelayFailure)({
|
|
57
|
+
relay: {
|
|
58
|
+
branch: "repair-relay-probe",
|
|
59
|
+
retryable: false,
|
|
60
|
+
userActionRequired: false,
|
|
61
|
+
phase: "diagnostics",
|
|
62
|
+
sharedTabScope: "current-shared-tab"
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
strict_1.default.equal(nonRetryable?.category, "non-retryable-relay-failure");
|
|
66
|
+
strict_1.default.equal((0, chrome_relay_error_helper_1.chromeRelayRetryGuidance)(nonRetryable), "Retry guidance: Stop automatic retries and surface the relay failure directly.");
|
|
67
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const strict_1 = __importDefault(require("node:assert/strict"));
|
|
7
|
+
const promises_1 = require("node:fs/promises");
|
|
8
|
+
const node_path_1 = require("node:path");
|
|
9
|
+
const node_test_1 = __importDefault(require("node:test"));
|
|
10
|
+
const chrome_relay_state_1 = require("../src/chrome-relay-state");
|
|
11
|
+
const chrome_relay_helper_1 = require("../src/chrome-relay-helper");
|
|
12
|
+
const chrome_relay_helper_cli_1 = require("../src/chrome-relay-helper-cli");
|
|
13
|
+
async function withCapturedStreams(run) {
|
|
14
|
+
let stdout = "";
|
|
15
|
+
let stderr = "";
|
|
16
|
+
const stdoutWrite = process.stdout.write.bind(process.stdout);
|
|
17
|
+
const stderrWrite = process.stderr.write.bind(process.stderr);
|
|
18
|
+
process.stdout.write = ((chunk) => {
|
|
19
|
+
stdout += typeof chunk === "string" ? chunk : Buffer.from(chunk).toString("utf8");
|
|
20
|
+
return true;
|
|
21
|
+
});
|
|
22
|
+
process.stderr.write = ((chunk) => {
|
|
23
|
+
stderr += typeof chunk === "string" ? chunk : Buffer.from(chunk).toString("utf8");
|
|
24
|
+
return true;
|
|
25
|
+
});
|
|
26
|
+
try {
|
|
27
|
+
await run();
|
|
28
|
+
return { stdout, stderr };
|
|
29
|
+
}
|
|
30
|
+
finally {
|
|
31
|
+
process.stdout.write = stdoutWrite;
|
|
32
|
+
process.stderr.write = stderrWrite;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
(0, node_test_1.default)("chrome relay helper fixture flows validate and preserve truthful relay semantics", () => {
|
|
36
|
+
const flows = [
|
|
37
|
+
"extension-missing",
|
|
38
|
+
"disconnected",
|
|
39
|
+
"click-required",
|
|
40
|
+
"share-required",
|
|
41
|
+
"shared-tab",
|
|
42
|
+
"expired-share",
|
|
43
|
+
"clear-shared-tab"
|
|
44
|
+
];
|
|
45
|
+
for (const flow of flows) {
|
|
46
|
+
const state = (0, chrome_relay_helper_1.buildChromeRelayFixtureState)({ flow });
|
|
47
|
+
const validation = (0, chrome_relay_state_1.validateChromeRelayState)(state);
|
|
48
|
+
strict_1.default.equal(validation.ok, true, flow);
|
|
49
|
+
strict_1.default.equal(validation.probe?.version, state.version, flow);
|
|
50
|
+
strict_1.default.equal(validation.probe?.updatedAt, state.updatedAt, flow);
|
|
51
|
+
}
|
|
52
|
+
const sharedTab = (0, chrome_relay_helper_1.buildChromeRelayFixtureState)({ flow: "shared-tab" });
|
|
53
|
+
strict_1.default.equal(sharedTab.connected, true);
|
|
54
|
+
strict_1.default.equal(sharedTab.shareRequired, false);
|
|
55
|
+
strict_1.default.ok(sharedTab.sharedTab?.id);
|
|
56
|
+
strict_1.default.ok(sharedTab.expiresAt);
|
|
57
|
+
const expiredShare = (0, chrome_relay_helper_1.buildChromeRelayFixtureState)({ flow: "expired-share" });
|
|
58
|
+
strict_1.default.ok(expiredShare.sharedTab?.id);
|
|
59
|
+
strict_1.default.ok(expiredShare.expiresAt);
|
|
60
|
+
strict_1.default.equal(Date.parse(expiredShare.expiresAt ?? ""), Date.parse(expiredShare.expiresAt ?? ""));
|
|
61
|
+
strict_1.default.ok(Date.parse(expiredShare.expiresAt ?? "") <= Date.now());
|
|
62
|
+
strict_1.default.equal(expiredShare.resumable, false);
|
|
63
|
+
strict_1.default.equal(expiredShare.resumeRequiresUserGesture, true);
|
|
64
|
+
const cleared = (0, chrome_relay_helper_1.buildChromeRelayFixtureState)({ flow: "clear-shared-tab" });
|
|
65
|
+
strict_1.default.equal(cleared.connected, true);
|
|
66
|
+
strict_1.default.equal(cleared.sharedTab, null);
|
|
67
|
+
});
|
|
68
|
+
(0, node_test_1.default)("chrome relay helper writes full snapshots to the configured path and overwrites atomically", async () => {
|
|
69
|
+
const baseDir = (0, node_path_1.resolve)(process.cwd(), ".tmp-tests", "relay-helper-write");
|
|
70
|
+
await (0, promises_1.rm)(baseDir, { recursive: true, force: true });
|
|
71
|
+
await (0, promises_1.mkdir)(baseDir, { recursive: true });
|
|
72
|
+
const outputPath = (0, node_path_1.resolve)(baseDir, "chrome-relay-state.json");
|
|
73
|
+
await (0, promises_1.writeFile)(outputPath, '{"stale":true}', "utf8");
|
|
74
|
+
const first = await (0, chrome_relay_helper_1.writeChromeRelayFixtureState)({
|
|
75
|
+
flow: "shared-tab",
|
|
76
|
+
outputPath,
|
|
77
|
+
tabId: "relay-1",
|
|
78
|
+
url: "https://example.com/one",
|
|
79
|
+
title: "One"
|
|
80
|
+
});
|
|
81
|
+
const second = await (0, chrome_relay_helper_1.writeChromeRelayFixtureState)({
|
|
82
|
+
flow: "clear-shared-tab",
|
|
83
|
+
outputPath
|
|
84
|
+
});
|
|
85
|
+
strict_1.default.equal(first.path, outputPath);
|
|
86
|
+
strict_1.default.equal(second.path, outputPath);
|
|
87
|
+
const persisted = JSON.parse(await (0, promises_1.readFile)(outputPath, "utf8"));
|
|
88
|
+
strict_1.default.equal(persisted.sharedTab, null);
|
|
89
|
+
const dirEntries = await (0, promises_1.readdir)(baseDir);
|
|
90
|
+
strict_1.default.deepEqual(dirEntries.sort(), ["chrome-relay-state.json"]);
|
|
91
|
+
});
|
|
92
|
+
(0, node_test_1.default)("chrome relay helper CLI writes a documented flow and emits the final snapshot", async () => {
|
|
93
|
+
const baseDir = (0, node_path_1.resolve)(process.cwd(), ".tmp-tests", "relay-helper-cli");
|
|
94
|
+
await (0, promises_1.rm)(baseDir, { recursive: true, force: true });
|
|
95
|
+
await (0, promises_1.mkdir)(baseDir, { recursive: true });
|
|
96
|
+
const outputPath = (0, node_path_1.resolve)(baseDir, "chrome-relay-state.json");
|
|
97
|
+
const captured = await withCapturedStreams(async () => {
|
|
98
|
+
await (0, chrome_relay_helper_cli_1.runChromeRelayHelperCli)([
|
|
99
|
+
"shared-tab",
|
|
100
|
+
"--output",
|
|
101
|
+
outputPath,
|
|
102
|
+
"--tab-id",
|
|
103
|
+
"relay-42",
|
|
104
|
+
"--title",
|
|
105
|
+
"Shared Docs",
|
|
106
|
+
"--url",
|
|
107
|
+
"https://example.com/docs",
|
|
108
|
+
"--resumable",
|
|
109
|
+
"true",
|
|
110
|
+
"--resume-requires-user-gesture",
|
|
111
|
+
"false"
|
|
112
|
+
]);
|
|
113
|
+
});
|
|
114
|
+
strict_1.default.equal(captured.stderr, "");
|
|
115
|
+
const payload = JSON.parse(captured.stdout);
|
|
116
|
+
strict_1.default.equal(payload.ok, true);
|
|
117
|
+
strict_1.default.equal(payload.flow, "shared-tab");
|
|
118
|
+
strict_1.default.equal(payload.path, outputPath);
|
|
119
|
+
strict_1.default.equal(payload.state.sharedTab?.id, "relay-42");
|
|
120
|
+
strict_1.default.equal(payload.state.sharedTab?.title, "Shared Docs");
|
|
121
|
+
strict_1.default.equal(payload.state.sharedTab?.url, "https://example.com/docs");
|
|
122
|
+
const persisted = JSON.parse(await (0, promises_1.readFile)(outputPath, "utf8"));
|
|
123
|
+
strict_1.default.equal(persisted.sharedTab?.url, "https://example.com/docs");
|
|
124
|
+
});
|
|
125
|
+
(0, node_test_1.default)("chrome relay helper output path resolution honors explicit path then env then conventional default", () => {
|
|
126
|
+
const explicit = (0, node_path_1.resolve)(process.cwd(), ".tmp-tests", "explicit.json");
|
|
127
|
+
strict_1.default.equal((0, chrome_relay_helper_1.resolveChromeRelayStateOutputPath)(explicit), explicit);
|
|
128
|
+
const previous = process.env[chrome_relay_helper_1.CHROME_RELAY_STATE_PATH_ENV];
|
|
129
|
+
process.env[chrome_relay_helper_1.CHROME_RELAY_STATE_PATH_ENV] = (0, node_path_1.resolve)(process.cwd(), ".tmp-tests", "env.json");
|
|
130
|
+
try {
|
|
131
|
+
strict_1.default.equal((0, chrome_relay_helper_1.resolveChromeRelayStateOutputPath)(), (0, node_path_1.resolve)(process.cwd(), ".tmp-tests", "env.json"));
|
|
132
|
+
}
|
|
133
|
+
finally {
|
|
134
|
+
if (previous === undefined) {
|
|
135
|
+
delete process.env[chrome_relay_helper_1.CHROME_RELAY_STATE_PATH_ENV];
|
|
136
|
+
}
|
|
137
|
+
else {
|
|
138
|
+
process.env[chrome_relay_helper_1.CHROME_RELAY_STATE_PATH_ENV] = previous;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
strict_1.default.match((0, chrome_relay_helper_1.resolveChromeRelayStateOutputPath)(), /\.local-browser-bridge\/chrome-relay-state\.json$/);
|
|
142
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const promises_1 = require("node:fs/promises");
|
|
7
|
+
const strict_1 = __importDefault(require("node:assert/strict"));
|
|
8
|
+
const node_test_1 = __importDefault(require("node:test"));
|
|
9
|
+
const node_path_1 = require("node:path");
|
|
10
|
+
const chrome_relay_state_1 = require("../src/chrome-relay-state");
|
|
11
|
+
async function readJson(path) {
|
|
12
|
+
return JSON.parse(await (0, promises_1.readFile)(path, "utf8"));
|
|
13
|
+
}
|
|
14
|
+
(0, node_test_1.default)("chrome relay schema artifact stays present and documented", async () => {
|
|
15
|
+
const root = process.cwd();
|
|
16
|
+
const schema = await readJson((0, node_path_1.resolve)(root, "schema", "chrome-relay-state.schema.json"));
|
|
17
|
+
const producerDoc = await (0, promises_1.readFile)((0, node_path_1.resolve)(root, "docs", "chrome-relay-producer-contract.md"), "utf8");
|
|
18
|
+
const readme = await (0, promises_1.readFile)((0, node_path_1.resolve)(root, "README.md"), "utf8");
|
|
19
|
+
strict_1.default.match(schema.$schema, /json-schema.org/);
|
|
20
|
+
strict_1.default.match(schema.$id, /chrome-relay-state\.schema\.json$/);
|
|
21
|
+
strict_1.default.equal(schema.title, "Local Browser Bridge Chrome Relay State");
|
|
22
|
+
strict_1.default.ok(schema.properties.sharedTab);
|
|
23
|
+
strict_1.default.match(producerDoc, /schema\/chrome-relay-state\.schema\.json/);
|
|
24
|
+
strict_1.default.match(readme, /schema\/chrome-relay-state\.schema\.json/);
|
|
25
|
+
});
|
|
26
|
+
(0, node_test_1.default)("chrome relay example fixture validates as a ready shared-tab probe", async () => {
|
|
27
|
+
const root = process.cwd();
|
|
28
|
+
const example = await readJson((0, node_path_1.resolve)(root, "examples", "chrome-relay-state.example.json"));
|
|
29
|
+
const result = (0, chrome_relay_state_1.validateChromeRelayState)(example);
|
|
30
|
+
strict_1.default.equal(result.ok, true);
|
|
31
|
+
strict_1.default.equal(result.probe?.version, "1.1.0");
|
|
32
|
+
strict_1.default.equal(result.probe?.connected, true);
|
|
33
|
+
strict_1.default.equal(result.probe?.sharedTab?.id, "tab-123");
|
|
34
|
+
strict_1.default.equal(result.probe?.sharedTab?.url, "https://example.com/shared");
|
|
35
|
+
});
|
|
36
|
+
(0, node_test_1.default)("chrome relay validation rejects malformed or contradictory producer payloads", () => {
|
|
37
|
+
const cases = [
|
|
38
|
+
{
|
|
39
|
+
name: "shared tab must carry at least one identity field",
|
|
40
|
+
payload: { extensionInstalled: true, connected: true, sharedTab: {} },
|
|
41
|
+
expected: /sharedTab must include at least one of id, url, or title/
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
name: "gesture required cannot also expose a shared tab",
|
|
45
|
+
payload: {
|
|
46
|
+
extensionInstalled: true,
|
|
47
|
+
connected: true,
|
|
48
|
+
userGestureRequired: true,
|
|
49
|
+
sharedTab: { id: "tab-1" }
|
|
50
|
+
},
|
|
51
|
+
expected: /userGestureRequired=true cannot be combined with sharedTab/
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
name: "share required cannot also expose a shared tab",
|
|
55
|
+
payload: {
|
|
56
|
+
extensionInstalled: true,
|
|
57
|
+
connected: true,
|
|
58
|
+
shareRequired: true,
|
|
59
|
+
sharedTab: { url: "https://example.com" }
|
|
60
|
+
},
|
|
61
|
+
expected: /shareRequired=true cannot be combined with sharedTab/
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
name: "missing extension cannot report connected",
|
|
65
|
+
payload: {
|
|
66
|
+
extensionInstalled: false,
|
|
67
|
+
connected: true
|
|
68
|
+
},
|
|
69
|
+
expected: /extensionInstalled=false cannot be combined with connected=true/
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
name: "timestamps must be parseable",
|
|
73
|
+
payload: {
|
|
74
|
+
extensionInstalled: true,
|
|
75
|
+
connected: true,
|
|
76
|
+
updatedAt: "not-a-date",
|
|
77
|
+
sharedTab: { id: "tab-1" }
|
|
78
|
+
},
|
|
79
|
+
expected: /updatedAt must be an ISO 8601 timestamp/
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
name: "sharedTab must be object or null",
|
|
83
|
+
payload: {
|
|
84
|
+
extensionInstalled: true,
|
|
85
|
+
connected: true,
|
|
86
|
+
sharedTab: "tab-1"
|
|
87
|
+
},
|
|
88
|
+
expected: /sharedTab must be an object or null/
|
|
89
|
+
}
|
|
90
|
+
];
|
|
91
|
+
for (const testCase of cases) {
|
|
92
|
+
const result = (0, chrome_relay_state_1.validateChromeRelayState)(testCase.payload);
|
|
93
|
+
strict_1.default.equal(result.ok, false, testCase.name);
|
|
94
|
+
strict_1.default.match(result.errors.join(" | "), testCase.expected, testCase.name);
|
|
95
|
+
}
|
|
96
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const strict_1 = __importDefault(require("node:assert/strict"));
|
|
7
|
+
const node_test_1 = __importDefault(require("node:test"));
|
|
8
|
+
const src_1 = require("../src");
|
|
9
|
+
function createSharedTabSession() {
|
|
10
|
+
return {
|
|
11
|
+
id: "sess-relay",
|
|
12
|
+
schemaVersion: 1,
|
|
13
|
+
browser: "chrome",
|
|
14
|
+
kind: "chrome-readonly",
|
|
15
|
+
target: { type: "front" },
|
|
16
|
+
tab: {
|
|
17
|
+
browser: "chrome",
|
|
18
|
+
windowIndex: 1,
|
|
19
|
+
tabIndex: 1,
|
|
20
|
+
title: "Shared tab",
|
|
21
|
+
url: "https://example.com",
|
|
22
|
+
attachedAt: "2026-03-30T12:00:00.000Z",
|
|
23
|
+
identity: {
|
|
24
|
+
signature: "sig",
|
|
25
|
+
urlKey: "https://example.com",
|
|
26
|
+
titleKey: "Shared tab",
|
|
27
|
+
origin: "https://example.com",
|
|
28
|
+
pathname: "/"
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
frontTab: {
|
|
32
|
+
browser: "chrome",
|
|
33
|
+
windowIndex: 1,
|
|
34
|
+
tabIndex: 1,
|
|
35
|
+
title: "Shared tab",
|
|
36
|
+
url: "https://example.com",
|
|
37
|
+
attachedAt: "2026-03-30T12:00:00.000Z",
|
|
38
|
+
identity: {
|
|
39
|
+
signature: "sig",
|
|
40
|
+
urlKey: "https://example.com",
|
|
41
|
+
titleKey: "Shared tab",
|
|
42
|
+
origin: "https://example.com",
|
|
43
|
+
pathname: "/"
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
attach: {
|
|
47
|
+
mode: "relay",
|
|
48
|
+
source: "extension-relay",
|
|
49
|
+
scope: "tab",
|
|
50
|
+
resumable: false,
|
|
51
|
+
resumeRequiresUserGesture: true
|
|
52
|
+
},
|
|
53
|
+
semantics: {
|
|
54
|
+
inspect: "shared-tab-only",
|
|
55
|
+
list: "saved-session",
|
|
56
|
+
resume: "current-shared-tab",
|
|
57
|
+
tabReference: {
|
|
58
|
+
windowIndex: "synthetic-shared-tab-position",
|
|
59
|
+
tabIndex: "synthetic-shared-tab-position"
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
capabilities: { resume: true, activate: false, navigate: false, screenshot: false },
|
|
63
|
+
status: { state: "read-only", canAct: false },
|
|
64
|
+
createdAt: "2026-03-30T12:00:00.000Z"
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
(0, node_test_1.default)("normalizeClaudeCodeRoute maps named Claude Code routes onto the shared bridge route contract", () => {
|
|
68
|
+
strict_1.default.deepEqual((0, src_1.normalizeClaudeCodeRoute)({ route: "safari" }), {
|
|
69
|
+
browser: "safari",
|
|
70
|
+
attachMode: "direct"
|
|
71
|
+
});
|
|
72
|
+
strict_1.default.deepEqual((0, src_1.normalizeClaudeCodeRoute)({ route: "chrome-direct" }), {
|
|
73
|
+
browser: "chrome",
|
|
74
|
+
attachMode: "direct"
|
|
75
|
+
});
|
|
76
|
+
strict_1.default.deepEqual((0, src_1.normalizeClaudeCodeRoute)({ route: "chrome-relay", sessionId: "sess-relay" }), {
|
|
77
|
+
browser: "chrome",
|
|
78
|
+
attachMode: "relay",
|
|
79
|
+
sessionId: "sess-relay"
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
(0, node_test_1.default)("prepareClaudeCodeRoute returns a prompt and skips attach when the selected route is blocked", async () => {
|
|
83
|
+
const calls = [];
|
|
84
|
+
const adapter = (0, src_1.createBridgeAdapter)({
|
|
85
|
+
async getCapabilities() {
|
|
86
|
+
calls.push("capabilities");
|
|
87
|
+
return { schemaVersion: 1 };
|
|
88
|
+
},
|
|
89
|
+
async getDiagnostics(browser) {
|
|
90
|
+
calls.push(`diagnostics:${browser}`);
|
|
91
|
+
return {
|
|
92
|
+
browser,
|
|
93
|
+
checkedAt: "2026-03-30T12:00:00.000Z",
|
|
94
|
+
runtime: { platform: "darwin", arch: "arm64", nodeVersion: "v25.8.0" },
|
|
95
|
+
host: {},
|
|
96
|
+
supportedFeatures: {},
|
|
97
|
+
constraints: [],
|
|
98
|
+
attach: {
|
|
99
|
+
direct: {
|
|
100
|
+
mode: "direct",
|
|
101
|
+
ready: false,
|
|
102
|
+
state: "unavailable",
|
|
103
|
+
blockers: [
|
|
104
|
+
{
|
|
105
|
+
code: "direct_unavailable_attach_endpoint_missing",
|
|
106
|
+
message: "missing local DevTools endpoint"
|
|
107
|
+
}
|
|
108
|
+
]
|
|
109
|
+
},
|
|
110
|
+
relay: { mode: "relay", ready: true, state: "ready", blockers: [] }
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
},
|
|
114
|
+
async attach() {
|
|
115
|
+
calls.push("attach");
|
|
116
|
+
throw new Error("attach should not run");
|
|
117
|
+
},
|
|
118
|
+
async resume() {
|
|
119
|
+
calls.push("resume");
|
|
120
|
+
throw new Error("resume should not run");
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
const prepared = await (0, src_1.prepareClaudeCodeRoute)(adapter, { route: "chrome-direct" });
|
|
124
|
+
strict_1.default.equal(prepared.blocked, true);
|
|
125
|
+
strict_1.default.equal(prepared.routeUx.state, "blocked");
|
|
126
|
+
strict_1.default.match(prepared.prompt ?? "", /Chrome direct attach needs a local DevTools endpoint/i);
|
|
127
|
+
strict_1.default.equal(prepared.connection, undefined);
|
|
128
|
+
strict_1.default.deepEqual(calls, ["capabilities", "diagnostics:chrome"]);
|
|
129
|
+
});
|
|
130
|
+
(0, node_test_1.default)("prepareClaudeCodeRoute returns a connected shared-tool result for the Claude Code relay path", async () => {
|
|
131
|
+
const calls = [];
|
|
132
|
+
const adapter = (0, src_1.createBridgeAdapter)({
|
|
133
|
+
async getCapabilities() {
|
|
134
|
+
calls.push("capabilities");
|
|
135
|
+
return { schemaVersion: 1, product: { name: "local-browser-bridge" } };
|
|
136
|
+
},
|
|
137
|
+
async getDiagnostics(browser) {
|
|
138
|
+
calls.push(`diagnostics:${browser}`);
|
|
139
|
+
return {
|
|
140
|
+
browser,
|
|
141
|
+
checkedAt: "2026-03-30T12:00:00.000Z",
|
|
142
|
+
runtime: { platform: "darwin", arch: "arm64", nodeVersion: "v25.8.0" },
|
|
143
|
+
host: {},
|
|
144
|
+
supportedFeatures: {},
|
|
145
|
+
constraints: [],
|
|
146
|
+
attach: {
|
|
147
|
+
direct: { mode: "direct", ready: false, state: "unavailable", blockers: [] },
|
|
148
|
+
relay: { mode: "relay", ready: true, state: "ready", blockers: [] }
|
|
149
|
+
}
|
|
150
|
+
};
|
|
151
|
+
},
|
|
152
|
+
async attach(route) {
|
|
153
|
+
calls.push(`attach:${route.browser}:${route.attachMode}`);
|
|
154
|
+
return {
|
|
155
|
+
session: createSharedTabSession()
|
|
156
|
+
};
|
|
157
|
+
},
|
|
158
|
+
async resume() {
|
|
159
|
+
calls.push("resume");
|
|
160
|
+
throw new Error("resume should not run");
|
|
161
|
+
}
|
|
162
|
+
});
|
|
163
|
+
const prepared = await (0, src_1.prepareClaudeCodeRoute)(adapter, { route: "chrome-relay" });
|
|
164
|
+
strict_1.default.equal(prepared.blocked, false);
|
|
165
|
+
strict_1.default.equal(prepared.connection?.operation, "attach");
|
|
166
|
+
strict_1.default.equal(prepared.connection?.routeUx.label, "Chrome (shared tab, read-only)");
|
|
167
|
+
strict_1.default.equal(prepared.connection?.sessionUx.sharedTabScoped, true);
|
|
168
|
+
strict_1.default.match(prepared.prompt ?? "", /shared-tab grant is no longer active/i);
|
|
169
|
+
strict_1.default.deepEqual(calls, ["capabilities", "diagnostics:chrome", "attach:chrome:relay"]);
|
|
170
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|