yoke-mcp 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/LICENSE +21 -0
- package/MOTIVATION.md +73 -0
- package/README.md +207 -0
- package/ROADMAP.md +111 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +93 -0
- package/dist/cli.js.map +1 -0
- package/dist/doctor.d.ts +10 -0
- package/dist/doctor.js +178 -0
- package/dist/doctor.js.map +1 -0
- package/dist/install.d.ts +47 -0
- package/dist/install.js +165 -0
- package/dist/install.js.map +1 -0
- package/dist/mcp-server.d.ts +370 -0
- package/dist/mcp-server.js +606 -0
- package/dist/mcp-server.js.map +1 -0
- package/dist/native-host.d.ts +2 -0
- package/dist/native-host.js +170 -0
- package/dist/native-host.js.map +1 -0
- package/dist/protocol.d.ts +339 -0
- package/dist/protocol.js +9 -0
- package/dist/protocol.js.map +1 -0
- package/dist/socket-client.d.ts +16 -0
- package/dist/socket-client.js +89 -0
- package/dist/socket-client.js.map +1 -0
- package/dist/socket-path.d.ts +5 -0
- package/dist/socket-path.js +19 -0
- package/dist/socket-path.js.map +1 -0
- package/extension/browser/background.js +371 -0
- package/extension/browser/cdp.js +259 -0
- package/extension/browser/snapshot.js +154 -0
- package/extension/icons/128.png +0 -0
- package/extension/icons/16.png +0 -0
- package/extension/icons/32.png +0 -0
- package/extension/icons/48.png +0 -0
- package/extension/icons/icon.svg +8 -0
- package/extension/manifest.json +28 -0
- package/extension/protocol.js +8 -0
- package/package.json +53 -0
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// The native messaging host: a relay, and nothing more.
|
|
3
|
+
//
|
|
4
|
+
// Chrome owns this process. It spawns it when the extension calls
|
|
5
|
+
// connectNative, talks to it over stdin and stdout using a 4-byte
|
|
6
|
+
// little-endian length prefix per JSON message, and kills it when the extension
|
|
7
|
+
// disconnects. So a client cannot be the one to start it, and needs a second hop
|
|
8
|
+
// to reach the extension at all.
|
|
9
|
+
//
|
|
10
|
+
// That hop is a unix socket in a 0700 directory the user owns, rather than a TCP
|
|
11
|
+
// port. Whoever reaches this endpoint can read every tab in a logged-in browser,
|
|
12
|
+
// so "any local process can connect" is not an acceptable posture; file
|
|
13
|
+
// permissions are the cheapest correct answer. Windows gets a named pipe, where
|
|
14
|
+
// the path namespace plays the same role.
|
|
15
|
+
import { chmodSync, mkdirSync, statSync, unlinkSync } from 'node:fs';
|
|
16
|
+
import { createServer } from 'node:net';
|
|
17
|
+
import { dirname } from 'node:path';
|
|
18
|
+
import { PROTOCOL, isResponse } from './protocol.js';
|
|
19
|
+
import { endpointPath } from './socket-path.js';
|
|
20
|
+
const DEFAULT_TIMEOUT_MS = 15_000;
|
|
21
|
+
/** 0700, and verified after creation rather than assumed. */
|
|
22
|
+
function prepareDirectory(socketPath) {
|
|
23
|
+
const dir = dirname(socketPath);
|
|
24
|
+
mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
25
|
+
if (process.platform === 'win32') {
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
chmodSync(dir, 0o700);
|
|
29
|
+
const mode = statSync(dir).mode & 0o777;
|
|
30
|
+
if (mode !== 0o700) {
|
|
31
|
+
throw new Error(`${dir} is mode ${mode.toString(8)}, refusing to listen where others can reach the socket`);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
/** Chrome's framing: one 4-byte little-endian length, then that many JSON bytes. */
|
|
35
|
+
function writeToChrome(message) {
|
|
36
|
+
const body = Buffer.from(JSON.stringify(message), 'utf8');
|
|
37
|
+
const header = Buffer.alloc(4);
|
|
38
|
+
header.writeUInt32LE(body.length, 0);
|
|
39
|
+
process.stdout.write(Buffer.concat([header, body]));
|
|
40
|
+
}
|
|
41
|
+
function readFromChrome(onMessage) {
|
|
42
|
+
let buffer = Buffer.alloc(0);
|
|
43
|
+
process.stdin.on('data', (chunk) => {
|
|
44
|
+
buffer = Buffer.concat([buffer, chunk]);
|
|
45
|
+
for (;;) {
|
|
46
|
+
if (buffer.length < 4) {
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
const length = buffer.readUInt32LE(0);
|
|
50
|
+
if (buffer.length < 4 + length) {
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
const body = buffer.subarray(4, 4 + length);
|
|
54
|
+
buffer = buffer.subarray(4 + length);
|
|
55
|
+
try {
|
|
56
|
+
onMessage(JSON.parse(body.toString('utf8')));
|
|
57
|
+
}
|
|
58
|
+
catch { /* not ours to fix */ }
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
export function main() {
|
|
63
|
+
const socketPath = endpointPath();
|
|
64
|
+
const waiting = new Map();
|
|
65
|
+
let nextId = 1;
|
|
66
|
+
let server;
|
|
67
|
+
const cleanup = () => {
|
|
68
|
+
try {
|
|
69
|
+
server?.close();
|
|
70
|
+
}
|
|
71
|
+
catch { /* never listened */ }
|
|
72
|
+
if (process.platform !== 'win32') {
|
|
73
|
+
try {
|
|
74
|
+
unlinkSync(socketPath);
|
|
75
|
+
}
|
|
76
|
+
catch { /* already gone */ }
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
readFromChrome((message) => {
|
|
80
|
+
if (!isResponse(message)) {
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
const settle = waiting.get(message.id);
|
|
84
|
+
if (!settle) {
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
waiting.delete(message.id);
|
|
88
|
+
settle(message.ok
|
|
89
|
+
? { ok: true, data: message.data, protocol: PROTOCOL }
|
|
90
|
+
: { ok: false, error: message.error, protocol: PROTOCOL });
|
|
91
|
+
});
|
|
92
|
+
// Chrome closing stdin means the extension went away, so the socket must go
|
|
93
|
+
// too rather than linger and accept callers it can no longer serve.
|
|
94
|
+
process.stdin.on('end', () => { cleanup(); process.exit(0); });
|
|
95
|
+
server = createServer((connection) => {
|
|
96
|
+
let text = '';
|
|
97
|
+
connection.on('data', (chunk) => {
|
|
98
|
+
text += chunk.toString('utf8');
|
|
99
|
+
for (;;) {
|
|
100
|
+
const cut = text.indexOf('\n');
|
|
101
|
+
if (cut === -1) {
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
const line = text.slice(0, cut);
|
|
105
|
+
text = text.slice(cut + 1);
|
|
106
|
+
if (!line.trim()) {
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
let request;
|
|
110
|
+
try {
|
|
111
|
+
request = JSON.parse(line);
|
|
112
|
+
}
|
|
113
|
+
catch {
|
|
114
|
+
const reply = { ok: false, error: 'each line must be one JSON object' };
|
|
115
|
+
connection.write(`${JSON.stringify(reply)}\n`);
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
const id = nextId++;
|
|
119
|
+
// A silent extension must not hang the caller, and a caller that hangs
|
|
120
|
+
// up must not leave the relay waiting forever.
|
|
121
|
+
const timer = setTimeout(() => {
|
|
122
|
+
if (!waiting.delete(id)) {
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
const reply = { ok: false, error: 'the extension did not answer' };
|
|
126
|
+
try {
|
|
127
|
+
connection.write(`${JSON.stringify(reply)}\n`);
|
|
128
|
+
}
|
|
129
|
+
catch { /* gone */ }
|
|
130
|
+
}, request.timeoutMs ?? DEFAULT_TIMEOUT_MS);
|
|
131
|
+
waiting.set(id, (reply) => {
|
|
132
|
+
clearTimeout(timer);
|
|
133
|
+
try {
|
|
134
|
+
connection.write(`${JSON.stringify(reply)}\n`);
|
|
135
|
+
}
|
|
136
|
+
catch { /* caller hung up */ }
|
|
137
|
+
});
|
|
138
|
+
writeToChrome({ id, op: request.op, args: request.args });
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
connection.on('error', () => { });
|
|
142
|
+
});
|
|
143
|
+
prepareDirectory(socketPath);
|
|
144
|
+
// A stale socket from a host Chrome killed would otherwise block the bind.
|
|
145
|
+
if (process.platform !== 'win32') {
|
|
146
|
+
try {
|
|
147
|
+
unlinkSync(socketPath);
|
|
148
|
+
}
|
|
149
|
+
catch { /* nothing there */ }
|
|
150
|
+
}
|
|
151
|
+
server.listen(socketPath, () => {
|
|
152
|
+
if (process.platform !== 'win32') {
|
|
153
|
+
chmodSync(socketPath, 0o600);
|
|
154
|
+
}
|
|
155
|
+
});
|
|
156
|
+
for (const signal of ['SIGINT', 'SIGTERM', 'SIGHUP']) {
|
|
157
|
+
process.on(signal, () => { cleanup(); process.exit(0); });
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
// Runs unconditionally, because this file exists only to be executed.
|
|
161
|
+
//
|
|
162
|
+
// It used to be guarded by comparing process.argv[1] to this module's path,
|
|
163
|
+
// which can never match: Chrome invokes a native messaging host with the calling
|
|
164
|
+
// extension's origin as argv[1], not the script path. So the guard was always
|
|
165
|
+
// false, main() never ran, the process exited instantly, and Chrome reported
|
|
166
|
+
// "Native host has exited" with nothing else to go on. An entry point that the
|
|
167
|
+
// runner invokes with unpredictable arguments cannot detect itself from argv, so
|
|
168
|
+
// it should not try.
|
|
169
|
+
main();
|
|
170
|
+
//# sourceMappingURL=native-host.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"native-host.js","sourceRoot":"","sources":["../src/native-host.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
/** Bumped when a message shape changes in a way an older peer cannot read. */
|
|
2
|
+
export declare const PROTOCOL = 1;
|
|
3
|
+
export interface TabInfo {
|
|
4
|
+
id: number;
|
|
5
|
+
windowId: number;
|
|
6
|
+
/** `-1` when the tab is in no group. */
|
|
7
|
+
groupId: number;
|
|
8
|
+
title: string;
|
|
9
|
+
url: string;
|
|
10
|
+
}
|
|
11
|
+
export interface GroupInfo {
|
|
12
|
+
id: number;
|
|
13
|
+
title: string;
|
|
14
|
+
color: string;
|
|
15
|
+
windowId: number;
|
|
16
|
+
collapsed: boolean;
|
|
17
|
+
}
|
|
18
|
+
/** Every operation the extension implements, and what each one answers with. */
|
|
19
|
+
export interface Operations {
|
|
20
|
+
ping: {
|
|
21
|
+
args: Record<string, never>;
|
|
22
|
+
result: {
|
|
23
|
+
extension: string;
|
|
24
|
+
};
|
|
25
|
+
};
|
|
26
|
+
listTabs: {
|
|
27
|
+
args: Record<string, never>;
|
|
28
|
+
result: {
|
|
29
|
+
tabs: TabInfo[];
|
|
30
|
+
};
|
|
31
|
+
};
|
|
32
|
+
listGroups: {
|
|
33
|
+
args: Record<string, never>;
|
|
34
|
+
result: {
|
|
35
|
+
groups: GroupInfo[];
|
|
36
|
+
};
|
|
37
|
+
};
|
|
38
|
+
/**
|
|
39
|
+
* Navigate one tab, by id, and wait for the load to settle.
|
|
40
|
+
*
|
|
41
|
+
* `tabId` is never implied. There is no acting on the active tab because none
|
|
42
|
+
* was named: that is how a script ends up driving whatever the user happened
|
|
43
|
+
* to be looking at.
|
|
44
|
+
*/
|
|
45
|
+
navigate: {
|
|
46
|
+
args: {
|
|
47
|
+
tabId: number;
|
|
48
|
+
url: string;
|
|
49
|
+
timeoutMs?: number;
|
|
50
|
+
};
|
|
51
|
+
result: {
|
|
52
|
+
tabId: number;
|
|
53
|
+
url: string;
|
|
54
|
+
title: string;
|
|
55
|
+
status: 'complete' | 'timeout';
|
|
56
|
+
};
|
|
57
|
+
};
|
|
58
|
+
/**
|
|
59
|
+
* Open a new tab, optionally at a URL, optionally in the background.
|
|
60
|
+
*
|
|
61
|
+
* A tab opened here always joins a named group, created if absent and reused
|
|
62
|
+
* if present in that window, so the tab strip shows plainly which tabs an
|
|
63
|
+
* automation is working in. Tabs the user already had are never moved into it,
|
|
64
|
+
* and neither is this one moved between windows: marking what we created is
|
|
65
|
+
* honest, rearranging someone's browser is not.
|
|
66
|
+
*
|
|
67
|
+
* `groupId` is `-1` when grouping did not happen, and `groupTitle` is then the
|
|
68
|
+
* title that was asked for rather than one any group carries.
|
|
69
|
+
*/
|
|
70
|
+
openTab: {
|
|
71
|
+
args: {
|
|
72
|
+
url?: string;
|
|
73
|
+
active?: boolean;
|
|
74
|
+
windowId?: number;
|
|
75
|
+
groupTitle?: string;
|
|
76
|
+
};
|
|
77
|
+
result: {
|
|
78
|
+
tab: TabInfo;
|
|
79
|
+
groupId: number;
|
|
80
|
+
groupTitle: string;
|
|
81
|
+
};
|
|
82
|
+
};
|
|
83
|
+
closeTab: {
|
|
84
|
+
args: {
|
|
85
|
+
tabId: number;
|
|
86
|
+
};
|
|
87
|
+
result: {
|
|
88
|
+
closed: number;
|
|
89
|
+
};
|
|
90
|
+
};
|
|
91
|
+
/** The visible text of a page, by tab id. */
|
|
92
|
+
getPageText: {
|
|
93
|
+
args: {
|
|
94
|
+
tabId: number;
|
|
95
|
+
maxChars?: number;
|
|
96
|
+
};
|
|
97
|
+
result: {
|
|
98
|
+
tabId: number;
|
|
99
|
+
url: string;
|
|
100
|
+
title: string;
|
|
101
|
+
text: string;
|
|
102
|
+
truncated: boolean;
|
|
103
|
+
};
|
|
104
|
+
};
|
|
105
|
+
/**
|
|
106
|
+
* The interactive elements of a page, each with a reference to act on.
|
|
107
|
+
*
|
|
108
|
+
* Coordinates are deliberately not the interface. A model that clicks at
|
|
109
|
+
* (412, 233) is guessing, and a page that reflows makes the guess wrong; a
|
|
110
|
+
* reference resolves to the element that was described.
|
|
111
|
+
*/
|
|
112
|
+
readPage: {
|
|
113
|
+
args: {
|
|
114
|
+
tabId: number;
|
|
115
|
+
maxElements?: number;
|
|
116
|
+
};
|
|
117
|
+
result: {
|
|
118
|
+
tabId: number;
|
|
119
|
+
url: string;
|
|
120
|
+
title: string;
|
|
121
|
+
elements: ElementRef[];
|
|
122
|
+
truncated: boolean;
|
|
123
|
+
};
|
|
124
|
+
};
|
|
125
|
+
/** Run JavaScript in the page and return its value. */
|
|
126
|
+
evaluate: {
|
|
127
|
+
args: {
|
|
128
|
+
tabId: number;
|
|
129
|
+
expression: string;
|
|
130
|
+
timeoutMs?: number;
|
|
131
|
+
};
|
|
132
|
+
result: {
|
|
133
|
+
tabId: number;
|
|
134
|
+
value: string;
|
|
135
|
+
type: string;
|
|
136
|
+
threw: boolean;
|
|
137
|
+
};
|
|
138
|
+
};
|
|
139
|
+
/** A screenshot, which works on a background tab because it goes through CDP. */
|
|
140
|
+
screenshot: {
|
|
141
|
+
args: {
|
|
142
|
+
tabId: number;
|
|
143
|
+
format?: 'png' | 'jpeg';
|
|
144
|
+
quality?: number;
|
|
145
|
+
};
|
|
146
|
+
result: {
|
|
147
|
+
tabId: number;
|
|
148
|
+
format: string;
|
|
149
|
+
base64: string;
|
|
150
|
+
bytes: number;
|
|
151
|
+
};
|
|
152
|
+
};
|
|
153
|
+
/**
|
|
154
|
+
* Click, by element reference rather than by coordinate.
|
|
155
|
+
*
|
|
156
|
+
* `hit` is the only part of this a caller can trust as a statement about the
|
|
157
|
+
* page. CDP reports that an input event was dispatched and nothing about what
|
|
158
|
+
* received it, so the element under the point is read at the moment of
|
|
159
|
+
* dispatch: `self` or `nested` mean the click reaches the named element, and
|
|
160
|
+
* `covered` means something else was on top and probably took it instead.
|
|
161
|
+
*/
|
|
162
|
+
click: {
|
|
163
|
+
args: {
|
|
164
|
+
tabId: number;
|
|
165
|
+
ref: string;
|
|
166
|
+
button?: 'left' | 'right' | 'middle';
|
|
167
|
+
clickCount?: number;
|
|
168
|
+
};
|
|
169
|
+
result: {
|
|
170
|
+
tabId: number;
|
|
171
|
+
ref: string;
|
|
172
|
+
dispatched: true;
|
|
173
|
+
hit: 'self' | 'nested' | 'covered' | 'nothing';
|
|
174
|
+
topmost?: string;
|
|
175
|
+
};
|
|
176
|
+
};
|
|
177
|
+
/** Type into whatever holds focus, optionally focusing a reference first. */
|
|
178
|
+
typeText: {
|
|
179
|
+
args: {
|
|
180
|
+
tabId: number;
|
|
181
|
+
text: string;
|
|
182
|
+
ref?: string;
|
|
183
|
+
pressEnter?: boolean;
|
|
184
|
+
};
|
|
185
|
+
result: {
|
|
186
|
+
tabId: number;
|
|
187
|
+
typed: number;
|
|
188
|
+
};
|
|
189
|
+
};
|
|
190
|
+
pressKey: {
|
|
191
|
+
args: {
|
|
192
|
+
tabId: number;
|
|
193
|
+
key: string;
|
|
194
|
+
ref?: string;
|
|
195
|
+
};
|
|
196
|
+
result: {
|
|
197
|
+
tabId: number;
|
|
198
|
+
key: string;
|
|
199
|
+
};
|
|
200
|
+
};
|
|
201
|
+
scroll: {
|
|
202
|
+
args: {
|
|
203
|
+
tabId: number;
|
|
204
|
+
dx?: number;
|
|
205
|
+
dy?: number;
|
|
206
|
+
ref?: string;
|
|
207
|
+
};
|
|
208
|
+
result: {
|
|
209
|
+
tabId: number;
|
|
210
|
+
dx: number;
|
|
211
|
+
dy: number;
|
|
212
|
+
};
|
|
213
|
+
};
|
|
214
|
+
/** Console messages seen since this tab was first attached to. */
|
|
215
|
+
consoleMessages: {
|
|
216
|
+
args: {
|
|
217
|
+
tabId: number;
|
|
218
|
+
limit?: number;
|
|
219
|
+
};
|
|
220
|
+
result: {
|
|
221
|
+
tabId: number;
|
|
222
|
+
messages: ConsoleMessage[];
|
|
223
|
+
attachedNow: boolean;
|
|
224
|
+
};
|
|
225
|
+
};
|
|
226
|
+
/** Network requests seen since this tab was first attached to. */
|
|
227
|
+
networkRequests: {
|
|
228
|
+
args: {
|
|
229
|
+
tabId: number;
|
|
230
|
+
limit?: number;
|
|
231
|
+
};
|
|
232
|
+
result: {
|
|
233
|
+
tabId: number;
|
|
234
|
+
requests: NetworkRequest[];
|
|
235
|
+
attachedNow: boolean;
|
|
236
|
+
};
|
|
237
|
+
};
|
|
238
|
+
/**
|
|
239
|
+
* Put tabs in a group, creating or reusing one by title within their window.
|
|
240
|
+
*
|
|
241
|
+
* Refused when the named tabs span windows, because a group holds tabs from
|
|
242
|
+
* one window and honouring it would mean moving the rest.
|
|
243
|
+
*
|
|
244
|
+
* Cosmetic only, and that is the point. Nothing here addresses a tab through
|
|
245
|
+
* its group, so a group can be added, renamed or removed at any time without
|
|
246
|
+
* anything losing track of anything. The bridge this replaces made the group
|
|
247
|
+
* load-bearing, which is why losing one stranded every tab inside it.
|
|
248
|
+
*/
|
|
249
|
+
groupTabs: {
|
|
250
|
+
args: {
|
|
251
|
+
tabIds: number[];
|
|
252
|
+
title?: string;
|
|
253
|
+
color?: string;
|
|
254
|
+
};
|
|
255
|
+
result: {
|
|
256
|
+
groupId: number;
|
|
257
|
+
title: string;
|
|
258
|
+
tabIds: number[];
|
|
259
|
+
};
|
|
260
|
+
};
|
|
261
|
+
/** Take tabs out of whatever group they are in. Leaves the tabs open. */
|
|
262
|
+
ungroupTabs: {
|
|
263
|
+
args: {
|
|
264
|
+
tabIds: number[];
|
|
265
|
+
};
|
|
266
|
+
result: {
|
|
267
|
+
tabIds: number[];
|
|
268
|
+
};
|
|
269
|
+
};
|
|
270
|
+
/** Stop driving a tab: detaches the debugger and drops its buffers. */
|
|
271
|
+
release: {
|
|
272
|
+
args: {
|
|
273
|
+
tabId: number;
|
|
274
|
+
};
|
|
275
|
+
result: {
|
|
276
|
+
tabId: number;
|
|
277
|
+
released: boolean;
|
|
278
|
+
};
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
export interface ElementRef {
|
|
282
|
+
/** Opaque and stable for the life of the snapshot. */
|
|
283
|
+
ref: string;
|
|
284
|
+
role: string;
|
|
285
|
+
name: string;
|
|
286
|
+
tag: string;
|
|
287
|
+
value?: string;
|
|
288
|
+
disabled?: boolean;
|
|
289
|
+
}
|
|
290
|
+
export interface ConsoleMessage {
|
|
291
|
+
level: string;
|
|
292
|
+
text: string;
|
|
293
|
+
url?: string;
|
|
294
|
+
line?: number;
|
|
295
|
+
at: number;
|
|
296
|
+
}
|
|
297
|
+
export interface NetworkRequest {
|
|
298
|
+
method: string;
|
|
299
|
+
url: string;
|
|
300
|
+
status?: number;
|
|
301
|
+
type?: string;
|
|
302
|
+
at: number;
|
|
303
|
+
}
|
|
304
|
+
export type OperationName = keyof Operations;
|
|
305
|
+
export type ArgsOf<K extends OperationName> = Operations[K]['args'];
|
|
306
|
+
export type ResultOf<K extends OperationName> = Operations[K]['result'];
|
|
307
|
+
/** Host to extension. `id` is the host's, and comes back untouched. */
|
|
308
|
+
export interface Request<K extends OperationName = OperationName> {
|
|
309
|
+
id: number;
|
|
310
|
+
op: K;
|
|
311
|
+
args?: ArgsOf<K>;
|
|
312
|
+
}
|
|
313
|
+
/** Extension to host. */
|
|
314
|
+
export type Response = {
|
|
315
|
+
id: number;
|
|
316
|
+
ok: true;
|
|
317
|
+
data: unknown;
|
|
318
|
+
} | {
|
|
319
|
+
id: number;
|
|
320
|
+
ok: false;
|
|
321
|
+
error: string;
|
|
322
|
+
};
|
|
323
|
+
/** Host to a socket client. */
|
|
324
|
+
export type SocketReply = {
|
|
325
|
+
ok: true;
|
|
326
|
+
data: unknown;
|
|
327
|
+
protocol: number;
|
|
328
|
+
} | {
|
|
329
|
+
ok: false;
|
|
330
|
+
error: string;
|
|
331
|
+
protocol?: number;
|
|
332
|
+
};
|
|
333
|
+
/** A socket client to the host. One JSON object per line. */
|
|
334
|
+
export interface SocketRequest {
|
|
335
|
+
op: OperationName;
|
|
336
|
+
args?: Record<string, unknown>;
|
|
337
|
+
timeoutMs?: number;
|
|
338
|
+
}
|
|
339
|
+
export declare const isResponse: (value: unknown) => value is Response;
|
package/dist/protocol.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
// The shapes the three processes agree on.
|
|
2
|
+
//
|
|
3
|
+
// One file so the extension, the host and the server cannot drift: a request the
|
|
4
|
+
// server can send is exactly a request the extension knows how to answer, and
|
|
5
|
+
// the compiler is what enforces that rather than a comment asking nicely.
|
|
6
|
+
/** Bumped when a message shape changes in a way an older peer cannot read. */
|
|
7
|
+
export const PROTOCOL = 1;
|
|
8
|
+
export const isResponse = (value) => typeof value === 'object' && value !== null && 'id' in value && 'ok' in value;
|
|
9
|
+
//# sourceMappingURL=protocol.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"protocol.js","sourceRoot":"","sources":["../src/protocol.ts"],"names":[],"mappings":"AAAA,2CAA2C;AAC3C,EAAE;AACF,iFAAiF;AACjF,8EAA8E;AAC9E,0EAA0E;AAE1E,8EAA8E;AAC9E,MAAM,CAAC,MAAM,QAAQ,GAAG,CAAC,CAAC;AA+N1B,MAAM,CAAC,MAAM,UAAU,GAAG,CAAC,KAAc,EAAqB,EAAE,CAC9D,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,IAAI,IAAI,KAAK,IAAI,IAAI,IAAI,KAAK,CAAC"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { type ArgsOf, type OperationName, type ResultOf } from './protocol.js';
|
|
2
|
+
/** The extension cannot be reached. Never a bug on its own. */
|
|
3
|
+
export declare class ExtensionUnavailable extends Error {
|
|
4
|
+
readonly name = "ExtensionUnavailable";
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* One request over the socket, one reply.
|
|
8
|
+
*
|
|
9
|
+
* The host owns request matching, so this only has to write a line and read the
|
|
10
|
+
* first one back.
|
|
11
|
+
*/
|
|
12
|
+
export declare function ask<K extends OperationName>(op: K, args?: ArgsOf<K>, { timeoutMs }?: {
|
|
13
|
+
timeoutMs?: number;
|
|
14
|
+
}): Promise<ResultOf<K>>;
|
|
15
|
+
/** Whether the extension answers at all. Used by `status`, never to gate a call. */
|
|
16
|
+
export declare function available(): Promise<boolean>;
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
// Talking to the extension through the host's socket.
|
|
2
|
+
//
|
|
3
|
+
// Every call here is allowed to fail with a reason. The extension is optional
|
|
4
|
+
// infrastructure: it may not be installed, or Chrome may not have started the
|
|
5
|
+
// host yet, and a caller has to be able to tell that apart from a real error.
|
|
6
|
+
import { existsSync } from 'node:fs';
|
|
7
|
+
import { createConnection } from 'node:net';
|
|
8
|
+
import { endpointPath } from './socket-path.js';
|
|
9
|
+
import { PROTOCOL, } from './protocol.js';
|
|
10
|
+
const DEFAULT_TIMEOUT_MS = 15_000;
|
|
11
|
+
/** The extension cannot be reached. Never a bug on its own. */
|
|
12
|
+
export class ExtensionUnavailable extends Error {
|
|
13
|
+
name = 'ExtensionUnavailable';
|
|
14
|
+
}
|
|
15
|
+
const isSocketReply = (value) => typeof value === 'object' && value !== null && 'ok' in value;
|
|
16
|
+
/**
|
|
17
|
+
* One request over the socket, one reply.
|
|
18
|
+
*
|
|
19
|
+
* The host owns request matching, so this only has to write a line and read the
|
|
20
|
+
* first one back.
|
|
21
|
+
*/
|
|
22
|
+
export function ask(op, args = {}, { timeoutMs = DEFAULT_TIMEOUT_MS } = {}) {
|
|
23
|
+
const socketPath = endpointPath();
|
|
24
|
+
return new Promise((resolve, reject) => {
|
|
25
|
+
if (process.platform !== 'win32' && !existsSync(socketPath)) {
|
|
26
|
+
reject(new ExtensionUnavailable('the extension is not connected. Run `yoke install`, then load it in Chrome.'));
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
const socket = createConnection(socketPath);
|
|
30
|
+
let text = '';
|
|
31
|
+
let settled = false;
|
|
32
|
+
const finish = (action) => {
|
|
33
|
+
if (settled) {
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
settled = true;
|
|
37
|
+
socket.destroy();
|
|
38
|
+
action();
|
|
39
|
+
};
|
|
40
|
+
const timer = setTimeout(() => finish(() => reject(new ExtensionUnavailable('the extension did not answer in time'))), timeoutMs + 2_000);
|
|
41
|
+
socket.on('connect', () => {
|
|
42
|
+
socket.write(`${JSON.stringify({ op, args, timeoutMs })}\n`);
|
|
43
|
+
});
|
|
44
|
+
socket.on('data', (chunk) => {
|
|
45
|
+
text += chunk.toString('utf8');
|
|
46
|
+
const cut = text.indexOf('\n');
|
|
47
|
+
if (cut === -1) {
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
clearTimeout(timer);
|
|
51
|
+
let reply;
|
|
52
|
+
try {
|
|
53
|
+
reply = JSON.parse(text.slice(0, cut));
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
finish(() => reject(new ExtensionUnavailable('the host sent something unreadable')));
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
if (!isSocketReply(reply)) {
|
|
60
|
+
finish(() => reject(new ExtensionUnavailable('the host sent an unrecognised reply')));
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
if (reply.protocol !== undefined && reply.protocol !== PROTOCOL) {
|
|
64
|
+
finish(() => reject(new ExtensionUnavailable(`the host speaks protocol ${reply.protocol} and this build speaks ${PROTOCOL}; update whichever is older`)));
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
if (!reply.ok) {
|
|
68
|
+
finish(() => reject(new Error(reply.error)));
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
finish(() => resolve(reply.data));
|
|
72
|
+
});
|
|
73
|
+
socket.on('error', (failure) => {
|
|
74
|
+
clearTimeout(timer);
|
|
75
|
+
finish(() => reject(new ExtensionUnavailable(`the extension is not reachable: ${failure.message}`)));
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
/** Whether the extension answers at all. Used by `status`, never to gate a call. */
|
|
80
|
+
export async function available() {
|
|
81
|
+
try {
|
|
82
|
+
await ask('ping', {}, { timeoutMs: 2_000 });
|
|
83
|
+
return true;
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
//# sourceMappingURL=socket-client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"socket-client.js","sourceRoot":"","sources":["../src/socket-client.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
// Where the host listens, in a module with no side effects.
|
|
2
|
+
//
|
|
3
|
+
// Separate from the host itself because the host is an entry point: Chrome
|
|
4
|
+
// executes it and it must always run. Anything that needs only the path should
|
|
5
|
+
// not have to import a file whose job is to start a server.
|
|
6
|
+
import { homedir } from 'node:os';
|
|
7
|
+
import { join } from 'node:path';
|
|
8
|
+
/**
|
|
9
|
+
* Kept out of /tmp so another user cannot pre-create it, and out of the
|
|
10
|
+
* project so it survives a rebuild.
|
|
11
|
+
*/
|
|
12
|
+
export function endpointPath() {
|
|
13
|
+
if (process.platform === 'win32') {
|
|
14
|
+
return `\\\\.\\pipe\\yoke-${process.env['USERNAME'] ?? 'user'}`;
|
|
15
|
+
}
|
|
16
|
+
const base = process.env['XDG_RUNTIME_DIR'] ?? join(homedir(), '.cache');
|
|
17
|
+
return join(base, 'yoke', 'extension.sock');
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=socket-path.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"socket-path.js","sourceRoot":"","sources":["../src/socket-path.ts"],"names":[],"mappings":"AAAA,4DAA4D;AAC5D,EAAE;AACF,2EAA2E;AAC3E,+EAA+E;AAC/E,4DAA4D;AAC5D,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC;;;GAGG;AACH,MAAM,UAAU,YAAY;IAC1B,IAAI,OAAO,CAAC,QAAQ,KAAK,OAAO,EAAE,CAAC;QACjC,OAAO,qBAAqB,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC,IAAI,MAAM,EAAE,CAAC;IAClE,CAAC;IACD,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,iBAAiB,CAAC,IAAI,IAAI,CAAC,OAAO,EAAE,EAAE,QAAQ,CAAC,CAAC;IACzE,OAAO,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,gBAAgB,CAAC,CAAC;AAC9C,CAAC"}
|