portholejs 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.
Files changed (116) hide show
  1. package/client-dist/assets/index-C_qSS_Gy.js +49 -0
  2. package/client-dist/index.html +12 -0
  3. package/dist/adb-actions.d.ts +4 -0
  4. package/dist/adb-actions.d.ts.map +1 -0
  5. package/dist/adb-actions.js +40 -0
  6. package/dist/adb-actions.js.map +1 -0
  7. package/dist/cli-errors.d.ts +5 -0
  8. package/dist/cli-errors.d.ts.map +1 -0
  9. package/dist/cli-errors.js +19 -0
  10. package/dist/cli-errors.js.map +1 -0
  11. package/dist/cli.d.ts +3 -0
  12. package/dist/cli.d.ts.map +1 -0
  13. package/dist/cli.js +542 -0
  14. package/dist/cli.js.map +1 -0
  15. package/dist/control-client.d.ts +22 -0
  16. package/dist/control-client.d.ts.map +1 -0
  17. package/dist/control-client.js +100 -0
  18. package/dist/control-client.js.map +1 -0
  19. package/dist/crashes.d.ts +8 -0
  20. package/dist/crashes.d.ts.map +1 -0
  21. package/dist/crashes.js +38 -0
  22. package/dist/crashes.js.map +1 -0
  23. package/dist/device-manager.d.ts +29 -0
  24. package/dist/device-manager.d.ts.map +1 -0
  25. package/dist/device-manager.js +213 -0
  26. package/dist/device-manager.js.map +1 -0
  27. package/dist/doctor.d.ts +8 -0
  28. package/dist/doctor.d.ts.map +1 -0
  29. package/dist/doctor.js +115 -0
  30. package/dist/doctor.js.map +1 -0
  31. package/dist/engine/scrcpy-engine.d.ts +38 -0
  32. package/dist/engine/scrcpy-engine.d.ts.map +1 -0
  33. package/dist/engine/scrcpy-engine.js +216 -0
  34. package/dist/engine/scrcpy-engine.js.map +1 -0
  35. package/dist/engine/types.d.ts +26 -0
  36. package/dist/engine/types.d.ts.map +1 -0
  37. package/dist/engine/types.js +2 -0
  38. package/dist/engine/types.js.map +1 -0
  39. package/dist/index.d.ts +14 -0
  40. package/dist/index.d.ts.map +1 -0
  41. package/dist/index.js +9 -0
  42. package/dist/index.js.map +1 -0
  43. package/dist/input-validation.d.ts +5 -0
  44. package/dist/input-validation.d.ts.map +1 -0
  45. package/dist/input-validation.js +62 -0
  46. package/dist/input-validation.js.map +1 -0
  47. package/dist/input.d.ts +22 -0
  48. package/dist/input.d.ts.map +1 -0
  49. package/dist/input.js +2 -0
  50. package/dist/input.js.map +1 -0
  51. package/dist/keycodes.d.ts +19 -0
  52. package/dist/keycodes.d.ts.map +1 -0
  53. package/dist/keycodes.js +32 -0
  54. package/dist/keycodes.js.map +1 -0
  55. package/dist/log.d.ts +3 -0
  56. package/dist/log.d.ts.map +1 -0
  57. package/dist/log.js +10 -0
  58. package/dist/log.js.map +1 -0
  59. package/dist/mcp/server.d.ts +2 -0
  60. package/dist/mcp/server.d.ts.map +1 -0
  61. package/dist/mcp/server.js +401 -0
  62. package/dist/mcp/server.js.map +1 -0
  63. package/dist/middleware.d.ts +13 -0
  64. package/dist/middleware.d.ts.map +1 -0
  65. package/dist/middleware.js +31 -0
  66. package/dist/middleware.js.map +1 -0
  67. package/dist/paths.d.ts +5 -0
  68. package/dist/paths.d.ts.map +1 -0
  69. package/dist/paths.js +27 -0
  70. package/dist/paths.js.map +1 -0
  71. package/dist/port-check.d.ts +5 -0
  72. package/dist/port-check.d.ts.map +1 -0
  73. package/dist/port-check.js +27 -0
  74. package/dist/port-check.js.map +1 -0
  75. package/dist/profiles.d.ts +4 -0
  76. package/dist/profiles.d.ts.map +1 -0
  77. package/dist/profiles.js +25 -0
  78. package/dist/profiles.js.map +1 -0
  79. package/dist/protocol.d.ts +10 -0
  80. package/dist/protocol.d.ts.map +1 -0
  81. package/dist/protocol.js +27 -0
  82. package/dist/protocol.js.map +1 -0
  83. package/dist/server/frame-convert.d.ts +6 -0
  84. package/dist/server/frame-convert.d.ts.map +1 -0
  85. package/dist/server/frame-convert.js +33 -0
  86. package/dist/server/frame-convert.js.map +1 -0
  87. package/dist/server/http.d.ts +20 -0
  88. package/dist/server/http.d.ts.map +1 -0
  89. package/dist/server/http.js +509 -0
  90. package/dist/server/http.js.map +1 -0
  91. package/dist/server/mjpeg.d.ts +21 -0
  92. package/dist/server/mjpeg.d.ts.map +1 -0
  93. package/dist/server/mjpeg.js +92 -0
  94. package/dist/server/mjpeg.js.map +1 -0
  95. package/dist/server/ws.d.ts +15 -0
  96. package/dist/server/ws.d.ts.map +1 -0
  97. package/dist/server/ws.js +102 -0
  98. package/dist/server/ws.js.map +1 -0
  99. package/dist/session.d.ts +45 -0
  100. package/dist/session.d.ts.map +1 -0
  101. package/dist/session.js +188 -0
  102. package/dist/session.js.map +1 -0
  103. package/dist/state.d.ts +27 -0
  104. package/dist/state.d.ts.map +1 -0
  105. package/dist/state.js +70 -0
  106. package/dist/state.js.map +1 -0
  107. package/dist/target-resolution.d.ts +14 -0
  108. package/dist/target-resolution.d.ts.map +1 -0
  109. package/dist/target-resolution.js +37 -0
  110. package/dist/target-resolution.js.map +1 -0
  111. package/dist/ui-tree.d.ts +47 -0
  112. package/dist/ui-tree.d.ts.map +1 -0
  113. package/dist/ui-tree.js +179 -0
  114. package/dist/ui-tree.js.map +1 -0
  115. package/package.json +63 -0
  116. package/scripts/download-scrcpy-server.mjs +67 -0
@@ -0,0 +1,401 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
3
+ import { z } from "zod";
4
+ import { adbBin, listDevices, findAndroidSdk, bootDevice } from "../device-manager.js";
5
+ import { ScrcpyEngine } from "../engine/scrcpy-engine.js";
6
+ import { scrcpyServerPath } from "../paths.js";
7
+ import { openUrl, stopApp, clearApp } from "../adb-actions.js";
8
+ import { parseCrashes } from "../crashes.js";
9
+ import { dumpUi, findElement, getFocusedNode, waitForUiText } from "../ui-tree.js";
10
+ let engine = null;
11
+ let activeSerial = null;
12
+ export async function startMcpServer() {
13
+ const server = new McpServer({
14
+ name: "porthole",
15
+ version: "0.0.1",
16
+ });
17
+ server.tool("list_devices", "List available AVDs and running emulators with their profiles", {}, async () => {
18
+ const devices = await listDevices();
19
+ return {
20
+ content: [
21
+ {
22
+ type: "text",
23
+ text: JSON.stringify({ devices }, null, 2),
24
+ },
25
+ ],
26
+ };
27
+ });
28
+ server.tool("boot_device", "Boot an AVD by name", { avdName: z.string().describe("AVD name to boot") }, async ({ avdName }) => {
29
+ const sdk = findAndroidSdk();
30
+ const serial = await bootDevice({ sdk, avdName });
31
+ return {
32
+ content: [
33
+ {
34
+ type: "text",
35
+ text: JSON.stringify({ ok: true, avdName, serial }),
36
+ },
37
+ ],
38
+ };
39
+ });
40
+ server.tool("wait_for_boot", "Wait until a running emulator reports sys.boot_completed=1", {
41
+ serial: z
42
+ .string()
43
+ .optional()
44
+ .describe("Device serial; defaults to the attached device"),
45
+ timeoutMs: z.number().optional().describe("Timeout in milliseconds"),
46
+ }, async ({ serial, timeoutMs }) => {
47
+ const targetSerial = serial ?? activeSerial;
48
+ if (!targetSerial) {
49
+ return { content: [{ type: "text", text: "No serial provided or attached." }] };
50
+ }
51
+ await waitForBootCompleted(targetSerial, timeoutMs ?? 120_000);
52
+ return {
53
+ content: [
54
+ {
55
+ type: "text",
56
+ text: JSON.stringify({ ok: true, serial: targetSerial }),
57
+ },
58
+ ],
59
+ };
60
+ });
61
+ server.tool("attach_device", "Attach to a running emulator for input and screenshots", {
62
+ serial: z.string().describe("Device serial (e.g. emulator-5554)"),
63
+ }, async ({ serial }) => {
64
+ const devices = await listDevices();
65
+ const device = devices.find((d) => d.serial === serial);
66
+ if (!device) {
67
+ return { content: [{ type: "text", text: `Device ${serial} not found` }] };
68
+ }
69
+ if (engine) {
70
+ await engine.stop();
71
+ }
72
+ engine = new ScrcpyEngine({
73
+ serial,
74
+ serverPath: scrcpyServerPath(),
75
+ });
76
+ await engine.start();
77
+ activeSerial = serial;
78
+ return {
79
+ content: [
80
+ {
81
+ type: "text",
82
+ text: `Attached to ${device.name} [${device.profile}] (${serial})`,
83
+ },
84
+ ],
85
+ };
86
+ });
87
+ server.tool("tap", "Touch at normalized 0..1 coordinates", {
88
+ x: z.number().min(0).max(1).describe("X coordinate (0..1)"),
89
+ y: z.number().min(0).max(1).describe("Y coordinate (0..1)"),
90
+ }, async ({ x, y }) => {
91
+ if (!engine) {
92
+ return {
93
+ content: [
94
+ { type: "text", text: "No active session. Call attach_device first." },
95
+ ],
96
+ };
97
+ }
98
+ const events = [
99
+ { kind: "touch", phase: "down", x, y },
100
+ { kind: "touch", phase: "up", x, y },
101
+ ];
102
+ for (const ev of events) {
103
+ await engine.sendInput(ev);
104
+ }
105
+ return { content: [{ type: "text", text: `Tapped at (${x}, ${y})` }] };
106
+ });
107
+ server.tool("key", "Send a single Android keyevent", { keycode: z.number().describe("Android keycode") }, async ({ keycode }) => {
108
+ if (!engine) {
109
+ return {
110
+ content: [
111
+ { type: "text", text: "No active session. Call attach_device first." },
112
+ ],
113
+ };
114
+ }
115
+ await engine.sendInput({ kind: "key", phase: "down", keycode });
116
+ await engine.sendInput({ kind: "key", phase: "up", keycode });
117
+ return { content: [{ type: "text", text: `Key ${keycode} sent` }] };
118
+ });
119
+ server.tool("remote", "Press a TV remote button (D-pad, media, etc.)", {
120
+ button: z
121
+ .enum([
122
+ "dpad_up",
123
+ "dpad_down",
124
+ "dpad_left",
125
+ "dpad_right",
126
+ "select",
127
+ "back",
128
+ "home",
129
+ "menu",
130
+ "play_pause",
131
+ "rewind",
132
+ "fast_forward",
133
+ "volume_up",
134
+ "volume_down",
135
+ ])
136
+ .describe("Remote button name"),
137
+ }, async ({ button }) => {
138
+ if (!engine) {
139
+ return {
140
+ content: [
141
+ { type: "text", text: "No active session. Call attach_device first." },
142
+ ],
143
+ };
144
+ }
145
+ await engine.sendInput({ kind: "remote", button: button });
146
+ return { content: [{ type: "text", text: `Remote: ${button}` }] };
147
+ });
148
+ server.tool("type_text", "Type text on the device", { text: z.string().describe("Text to type") }, async ({ text }) => {
149
+ if (!engine) {
150
+ return {
151
+ content: [
152
+ { type: "text", text: "No active session. Call attach_device first." },
153
+ ],
154
+ };
155
+ }
156
+ await engine.sendInput({ kind: "text", text });
157
+ return { content: [{ type: "text", text: `Typed: "${text}"` }] };
158
+ });
159
+ server.tool("screenshot", "Take a screenshot of the current device screen", {}, async () => {
160
+ if (!engine) {
161
+ return {
162
+ content: [
163
+ { type: "text", text: "No active session. Call attach_device first." },
164
+ ],
165
+ };
166
+ }
167
+ const png = await engine.screenshot();
168
+ return {
169
+ content: [
170
+ {
171
+ type: "image",
172
+ data: Buffer.from(png).toString("base64"),
173
+ mimeType: "image/png",
174
+ },
175
+ ],
176
+ };
177
+ });
178
+ server.tool("install_apk", "Install an APK on the attached emulator", { path: z.string().describe("Local path to an APK file") }, async ({ path }) => {
179
+ if (!activeSerial) {
180
+ return {
181
+ content: [
182
+ { type: "text", text: "No active session. Call attach_device first." },
183
+ ],
184
+ };
185
+ }
186
+ const { execFile: execFileCb } = await import("node:child_process");
187
+ const { promisify } = await import("node:util");
188
+ const execFileP = promisify(execFileCb);
189
+ const { stdout, stderr } = await execFileP(adbBin(findAndroidSdk()), [
190
+ "-s",
191
+ activeSerial,
192
+ "install",
193
+ "-r",
194
+ path,
195
+ ]);
196
+ return {
197
+ content: [
198
+ {
199
+ type: "text",
200
+ text: JSON.stringify({ ok: true, serial: activeSerial, stdout, stderr }),
201
+ },
202
+ ],
203
+ };
204
+ });
205
+ server.tool("dump_ui", "Dump the Android UI hierarchy as JSON", { filter: z.string().optional().describe("Substring filter") }, async ({ filter }) => {
206
+ if (!activeSerial) {
207
+ return {
208
+ content: [
209
+ { type: "text", text: "No active session. Call attach_device first." },
210
+ ],
211
+ };
212
+ }
213
+ return {
214
+ content: [
215
+ {
216
+ type: "text",
217
+ text: JSON.stringify(await dumpUi(activeSerial, filter), null, 2),
218
+ },
219
+ ],
220
+ };
221
+ });
222
+ server.tool("get_focused", "Return the focused UI node, useful for Android TV focus testing", {}, async () => {
223
+ if (!activeSerial) {
224
+ return {
225
+ content: [
226
+ { type: "text", text: "No active session. Call attach_device first." },
227
+ ],
228
+ };
229
+ }
230
+ return {
231
+ content: [
232
+ {
233
+ type: "text",
234
+ text: JSON.stringify(await getFocusedNode(activeSerial), null, 2),
235
+ },
236
+ ],
237
+ };
238
+ });
239
+ server.tool("find_element", "Find a UI element by text or resource id and optionally tap it on phone profiles", {
240
+ text: z.string().optional(),
241
+ resourceId: z.string().optional(),
242
+ tap: z.boolean().optional(),
243
+ }, async ({ text, resourceId, tap }) => {
244
+ if (!activeSerial) {
245
+ return {
246
+ content: [
247
+ { type: "text", text: "No active session. Call attach_device first." },
248
+ ],
249
+ };
250
+ }
251
+ const node = await findElement(activeSerial, { text, resourceId });
252
+ if (node && tap && engine) {
253
+ if (!node.normalizedCenter) {
254
+ return {
255
+ content: [
256
+ {
257
+ type: "text",
258
+ text: "Found the element but could not determine display size to tap it.",
259
+ },
260
+ ],
261
+ };
262
+ }
263
+ await engine.sendInput({
264
+ kind: "touch",
265
+ phase: "down",
266
+ ...node.normalizedCenter,
267
+ });
268
+ await engine.sendInput({
269
+ kind: "touch",
270
+ phase: "up",
271
+ ...node.normalizedCenter,
272
+ });
273
+ }
274
+ return { content: [{ type: "text", text: JSON.stringify(node, null, 2) }] };
275
+ });
276
+ server.tool("wait_for", "Wait for text to appear in the UI hierarchy", {
277
+ text: z.string(),
278
+ timeoutMs: z.number().optional(),
279
+ }, async ({ text, timeoutMs }) => {
280
+ if (!activeSerial) {
281
+ return {
282
+ content: [
283
+ { type: "text", text: "No active session. Call attach_device first." },
284
+ ],
285
+ };
286
+ }
287
+ const node = await waitForUiText(activeSerial, text, timeoutMs ?? 10_000);
288
+ return { content: [{ type: "text", text: JSON.stringify(node, null, 2) }] };
289
+ });
290
+ server.tool("open_url", "Open a URL or Android deep link", { url: z.string() }, async ({ url }) => {
291
+ if (!activeSerial) {
292
+ return {
293
+ content: [
294
+ { type: "text", text: "No active session. Call attach_device first." },
295
+ ],
296
+ };
297
+ }
298
+ return {
299
+ content: [{ type: "text", text: await openUrl(activeSerial, url) }],
300
+ };
301
+ });
302
+ server.tool("stop_app", "Force-stop an app package", { packageName: z.string() }, async ({ packageName }) => {
303
+ if (!activeSerial) {
304
+ return {
305
+ content: [
306
+ { type: "text", text: "No active session. Call attach_device first." },
307
+ ],
308
+ };
309
+ }
310
+ await stopApp(activeSerial, packageName);
311
+ return { content: [{ type: "text", text: JSON.stringify({ ok: true }) }] };
312
+ });
313
+ server.tool("clear_app", "Clear app data for a package", { packageName: z.string() }, async ({ packageName }) => {
314
+ if (!activeSerial) {
315
+ return {
316
+ content: [
317
+ { type: "text", text: "No active session. Call attach_device first." },
318
+ ],
319
+ };
320
+ }
321
+ await clearApp(activeSerial, packageName);
322
+ return { content: [{ type: "text", text: JSON.stringify({ ok: true }) }] };
323
+ });
324
+ server.tool("read_logcat", "Read recent logcat output from the device", {
325
+ lines: z.number().optional().describe("Number of lines (default 50)"),
326
+ filter: z.string().optional().describe("Logcat filter expression"),
327
+ }, async ({ lines, filter }) => {
328
+ if (!engine) {
329
+ return {
330
+ content: [
331
+ { type: "text", text: "No active session. Call attach_device first." },
332
+ ],
333
+ };
334
+ }
335
+ const { execFile: execFileCb } = await import("node:child_process");
336
+ const { promisify } = await import("node:util");
337
+ const execFileP = promisify(execFileCb);
338
+ if (!activeSerial) {
339
+ return { content: [{ type: "text", text: "No device serial" }] };
340
+ }
341
+ const args = ["-s", activeSerial, "logcat", "-d", "-t", String(lines ?? 50)];
342
+ if (filter)
343
+ args.push(filter);
344
+ try {
345
+ const { stdout } = await execFileP("adb", args);
346
+ return { content: [{ type: "text", text: stdout }] };
347
+ }
348
+ catch (e) {
349
+ return { content: [{ type: "text", text: `logcat error: ${e}` }] };
350
+ }
351
+ });
352
+ server.tool("get_crashes", "Return recent crash and ANR records", {}, async () => {
353
+ if (!activeSerial) {
354
+ return {
355
+ content: [{ type: "text", text: "No active session. Call attach_device first." }],
356
+ };
357
+ }
358
+ const { execFile: execFileCb } = await import("node:child_process");
359
+ const { promisify } = await import("node:util");
360
+ const execFileP = promisify(execFileCb);
361
+ const { stdout } = await execFileP(adbBin(findAndroidSdk()), [
362
+ "-s",
363
+ activeSerial,
364
+ "logcat",
365
+ "-d",
366
+ "-t",
367
+ "1000",
368
+ ]);
369
+ return {
370
+ content: [{ type: "text", text: JSON.stringify(parseCrashes(stdout), null, 2) }],
371
+ };
372
+ });
373
+ const transport = new StdioServerTransport();
374
+ await server.connect(transport);
375
+ }
376
+ async function waitForBootCompleted(serial, timeoutMs) {
377
+ const { execFile: execFileCb } = await import("node:child_process");
378
+ const { promisify } = await import("node:util");
379
+ const execFileP = promisify(execFileCb);
380
+ const adb = adbBin(findAndroidSdk());
381
+ const start = Date.now();
382
+ while (Date.now() - start < timeoutMs) {
383
+ try {
384
+ const { stdout } = await execFileP(adb, [
385
+ "-s",
386
+ serial,
387
+ "shell",
388
+ "getprop",
389
+ "sys.boot_completed",
390
+ ]);
391
+ if (stdout.trim() === "1")
392
+ return;
393
+ }
394
+ catch {
395
+ // keep polling
396
+ }
397
+ await new Promise((resolve) => setTimeout(resolve, 1000));
398
+ }
399
+ throw new Error(`Timed out waiting for ${serial} to boot.`);
400
+ }
401
+ //# sourceMappingURL=server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../../src/mcp/server.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ import type { IncomingMessage, ServerResponse } from "node:http";
2
+ import type { Engine } from "./engine/types.js";
3
+ import type { DeviceInfo } from "./device-manager.js";
4
+ import { createWsServer } from "./server/ws.js";
5
+ export interface PortholeMiddlewareOptions {
6
+ getEngine: () => Engine | null;
7
+ getDevice?: () => DeviceInfo;
8
+ mountPath?: string;
9
+ }
10
+ export type PortholeMiddleware = (req: IncomingMessage, res: ServerResponse, next: () => void) => void;
11
+ export declare function createPortholeMiddleware(opts: PortholeMiddlewareOptions): PortholeMiddleware;
12
+ export declare function attachPortholeWs(httpServer: Parameters<typeof createWsServer>[0]["httpServer"], opts: PortholeMiddlewareOptions): void;
13
+ //# sourceMappingURL=middleware.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"middleware.d.ts","sourceRoot":"","sources":["../src/middleware.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AACjE,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAEtD,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAGhD,MAAM,WAAW,yBAAyB;IACxC,SAAS,EAAE,MAAM,MAAM,GAAG,IAAI,CAAC;IAC/B,SAAS,CAAC,EAAE,MAAM,UAAU,CAAC;IAC7B,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,MAAM,kBAAkB,GAAG,CAC/B,GAAG,EAAE,eAAe,EACpB,GAAG,EAAE,cAAc,EACnB,IAAI,EAAE,MAAM,IAAI,KACb,IAAI,CAAC;AAEV,wBAAgB,wBAAwB,CACtC,IAAI,EAAE,yBAAyB,GAC9B,kBAAkB,CAoBpB;AAED,wBAAgB,gBAAgB,CAC9B,UAAU,EAAE,UAAU,CAAC,OAAO,cAAc,CAAC,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC,EAC9D,IAAI,EAAE,yBAAyB,GAC9B,IAAI,CAMN"}
@@ -0,0 +1,31 @@
1
+ import { createHttpServer } from "./server/http.js";
2
+ import { createWsServer } from "./server/ws.js";
3
+ import { clientDistPath } from "./paths.js";
4
+ export function createPortholeMiddleware(opts) {
5
+ const mountPath = opts.mountPath ?? "/.porthole";
6
+ const clientDir = clientDistPath();
7
+ const { server } = createHttpServer({
8
+ port: 0,
9
+ host: "127.0.0.1",
10
+ clientDir,
11
+ getEngine: opts.getEngine,
12
+ getDevice: opts.getDevice,
13
+ });
14
+ return (req, res, next) => {
15
+ const url = req.url ?? "/";
16
+ if (!url.startsWith(mountPath)) {
17
+ next();
18
+ return;
19
+ }
20
+ req.url = url.slice(mountPath.length) || "/";
21
+ server.emit("request", req, res);
22
+ };
23
+ }
24
+ export function attachPortholeWs(httpServer, opts) {
25
+ createWsServer({
26
+ httpServer,
27
+ getEngine: opts.getEngine,
28
+ getDevice: opts.getDevice,
29
+ });
30
+ }
31
+ //# sourceMappingURL=middleware.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"middleware.js","sourceRoot":"","sources":["../src/middleware.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAChD,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAc5C,MAAM,UAAU,wBAAwB,CACtC,IAA+B;IAE/B,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,IAAI,YAAY,CAAC;IACjD,MAAM,SAAS,GAAG,cAAc,EAAE,CAAC;IACnC,MAAM,EAAE,MAAM,EAAE,GAAG,gBAAgB,CAAC;QAClC,IAAI,EAAE,CAAC;QACP,IAAI,EAAE,WAAW;QACjB,SAAS;QACT,SAAS,EAAE,IAAI,CAAC,SAAS;QACzB,SAAS,EAAE,IAAI,CAAC,SAAS;KAC1B,CAAC,CAAC;IAEH,OAAO,CAAC,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE,EAAE;QACxB,MAAM,GAAG,GAAG,GAAG,CAAC,GAAG,IAAI,GAAG,CAAC;QAC3B,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;YAC/B,IAAI,EAAE,CAAC;YACP,OAAO;QACT,CAAC;QACD,GAAG,CAAC,GAAG,GAAG,GAAG,CAAC,KAAK,CAAC,SAAS,CAAC,MAAM,CAAC,IAAI,GAAG,CAAC;QAC7C,MAAM,CAAC,IAAI,CAAC,SAAS,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC;IACnC,CAAC,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,gBAAgB,CAC9B,UAA8D,EAC9D,IAA+B;IAE/B,cAAc,CAAC;QACb,UAAU;QACV,SAAS,EAAE,IAAI,CAAC,SAAS;QACzB,SAAS,EAAE,IAAI,CAAC,SAAS;KAC1B,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,5 @@
1
+ export declare const SCRCPY_DOWNLOAD_SCRIPT: string;
2
+ export declare function scrcpyServerCandidatePath(): string;
3
+ export declare function scrcpyServerPath(): string;
4
+ export declare function clientDistPath(): string;
5
+ //# sourceMappingURL=paths.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"paths.d.ts","sourceRoot":"","sources":["../src/paths.ts"],"names":[],"mappings":"AAMA,eAAO,MAAM,sBAAsB,QAIlC,CAAC;AAEF,wBAAgB,yBAAyB,IAAI,MAAM,CAElD;AAED,wBAAgB,gBAAgB,IAAI,MAAM,CAUzC;AAED,wBAAgB,cAAc,IAAI,MAAM,CAKvC"}
package/dist/paths.js ADDED
@@ -0,0 +1,27 @@
1
+ import { existsSync } from "node:fs";
2
+ import { resolve } from "node:path";
3
+ const PACKAGE_ROOT = resolve(import.meta.dirname, "..");
4
+ const WORKSPACE_ROOT = resolve(PACKAGE_ROOT, "../..");
5
+ export const SCRCPY_DOWNLOAD_SCRIPT = resolve(PACKAGE_ROOT, "scripts", "download-scrcpy-server.mjs");
6
+ export function scrcpyServerCandidatePath() {
7
+ return resolve(PACKAGE_ROOT, "assets", "scrcpy-server");
8
+ }
9
+ export function scrcpyServerPath() {
10
+ const path = scrcpyServerCandidatePath();
11
+ if (!existsSync(path)) {
12
+ throw new Error(`scrcpy-server not found at ${path}. It is downloaded from the ` +
13
+ `Genymobile/scrcpy GitHub release at install time. Fetch it with:\n` +
14
+ ` node ${SCRCPY_DOWNLOAD_SCRIPT}`);
15
+ }
16
+ return path;
17
+ }
18
+ export function clientDistPath() {
19
+ return firstExisting([
20
+ resolve(PACKAGE_ROOT, "client-dist"),
21
+ resolve(WORKSPACE_ROOT, "packages", "porthole-client", "dist"),
22
+ ]);
23
+ }
24
+ function firstExisting(paths) {
25
+ return paths.find((path) => existsSync(path)) ?? paths[0] ?? "";
26
+ }
27
+ //# sourceMappingURL=paths.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"paths.js","sourceRoot":"","sources":["../src/paths.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEpC,MAAM,YAAY,GAAG,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;AACxD,MAAM,cAAc,GAAG,OAAO,CAAC,YAAY,EAAE,OAAO,CAAC,CAAC;AAEtD,MAAM,CAAC,MAAM,sBAAsB,GAAG,OAAO,CAC3C,YAAY,EACZ,SAAS,EACT,4BAA4B,CAC7B,CAAC;AAEF,MAAM,UAAU,yBAAyB;IACvC,OAAO,OAAO,CAAC,YAAY,EAAE,QAAQ,EAAE,eAAe,CAAC,CAAC;AAC1D,CAAC;AAED,MAAM,UAAU,gBAAgB;IAC9B,MAAM,IAAI,GAAG,yBAAyB,EAAE,CAAC;IACzC,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;QACtB,MAAM,IAAI,KAAK,CACb,8BAA8B,IAAI,8BAA8B;YAC9D,oEAAoE;YACpE,UAAU,sBAAsB,EAAE,CACrC,CAAC;IACJ,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,MAAM,UAAU,cAAc;IAC5B,OAAO,aAAa,CAAC;QACnB,OAAO,CAAC,YAAY,EAAE,aAAa,CAAC;QACpC,OAAO,CAAC,cAAc,EAAE,UAAU,EAAE,iBAAiB,EAAE,MAAM,CAAC;KAC/D,CAAC,CAAC;AACL,CAAC;AAED,SAAS,aAAa,CAAC,KAAe;IACpC,OAAO,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;AAClE,CAAC"}
@@ -0,0 +1,5 @@
1
+ import { type PortholeSessionRecord } from "./state.js";
2
+ export declare function isPortFree(port: number, host: string): Promise<boolean>;
3
+ export declare function portInUseMessage(port: number, session: PortholeSessionRecord | undefined): string;
4
+ export declare function ensurePortFree(port: number, host: string): Promise<string | null>;
5
+ //# sourceMappingURL=port-check.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"port-check.d.ts","sourceRoot":"","sources":["../src/port-check.ts"],"names":[],"mappings":"AACA,OAAO,EAAa,KAAK,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAEnE,wBAAsB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAQ7E;AAED,wBAAgB,gBAAgB,CAC9B,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,qBAAqB,GAAG,SAAS,GACzC,MAAM,CAUR;AAED,wBAAsB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAOvF"}
@@ -0,0 +1,27 @@
1
+ import { createServer } from "node:net";
2
+ import { readState } from "./state.js";
3
+ export async function isPortFree(port, host) {
4
+ return new Promise((resolve) => {
5
+ const probe = createServer();
6
+ probe.once("error", () => resolve(false));
7
+ probe.listen(port, host, () => {
8
+ probe.close(() => resolve(true));
9
+ });
10
+ });
11
+ }
12
+ export function portInUseMessage(port, session) {
13
+ if (session) {
14
+ return (`Port ${port} is already used by the Porthole session for ` +
15
+ `${session.avdName} (${session.url}, pid=${session.pid}).\n` +
16
+ `Open that preview, or start on another port with -p ${port + 1}, ` +
17
+ `or stop it with \`porthole kill\`.`);
18
+ }
19
+ return `Port ${port} is already in use. Try another port with -p ${port + 1}.`;
20
+ }
21
+ export async function ensurePortFree(port, host) {
22
+ if (await isPortFree(port, host))
23
+ return null;
24
+ const state = await readState();
25
+ return portInUseMessage(port, state.sessions.find((session) => session.port === port));
26
+ }
27
+ //# sourceMappingURL=port-check.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"port-check.js","sourceRoot":"","sources":["../src/port-check.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AACxC,OAAO,EAAE,SAAS,EAA8B,MAAM,YAAY,CAAC;AAEnE,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,IAAY,EAAE,IAAY;IACzD,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;QAC7B,MAAM,KAAK,GAAG,YAAY,EAAE,CAAC;QAC7B,KAAK,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC;QAC1C,KAAK,CAAC,MAAM,CAAC,IAAI,EAAE,IAAI,EAAE,GAAG,EAAE;YAC5B,KAAK,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;QACnC,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC;AAED,MAAM,UAAU,gBAAgB,CAC9B,IAAY,EACZ,OAA0C;IAE1C,IAAI,OAAO,EAAE,CAAC;QACZ,OAAO,CACL,QAAQ,IAAI,+CAA+C;YAC3D,GAAG,OAAO,CAAC,OAAO,KAAK,OAAO,CAAC,GAAG,SAAS,OAAO,CAAC,GAAG,MAAM;YAC5D,uDAAuD,IAAI,GAAG,CAAC,IAAI;YACnE,oCAAoC,CACrC,CAAC;IACJ,CAAC;IACD,OAAO,QAAQ,IAAI,gDAAgD,IAAI,GAAG,CAAC,GAAG,CAAC;AACjF,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,IAAY,EAAE,IAAY;IAC7D,IAAI,MAAM,UAAU,CAAC,IAAI,EAAE,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC;IAC9C,MAAM,KAAK,GAAG,MAAM,SAAS,EAAE,CAAC;IAChC,OAAO,gBAAgB,CACrB,IAAI,EACJ,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,KAAK,IAAI,CAAC,CACxD,CAAC;AACJ,CAAC"}
@@ -0,0 +1,4 @@
1
+ export type DeviceProfile = "phone" | "tv";
2
+ export declare function detectProfileFromConfig(configContent: string): DeviceProfile;
3
+ export declare function detectProfile(avdPath: string): Promise<DeviceProfile>;
4
+ //# sourceMappingURL=profiles.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"profiles.d.ts","sourceRoot":"","sources":["../src/profiles.ts"],"names":[],"mappings":"AAGA,MAAM,MAAM,aAAa,GAAG,OAAO,GAAG,IAAI,CAAC;AAE3C,wBAAgB,uBAAuB,CAAC,aAAa,EAAE,MAAM,GAAG,aAAa,CAW5E;AAED,wBAAsB,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC,CAQ3E"}
@@ -0,0 +1,25 @@
1
+ import { readFile } from "node:fs/promises";
2
+ import { join } from "node:path";
3
+ export function detectProfileFromConfig(configContent) {
4
+ for (const line of configContent.split("\n")) {
5
+ const trimmed = line.trim();
6
+ if (trimmed.startsWith("tag.id")) {
7
+ const value = trimmed.split("=")[1]?.trim();
8
+ if (value === "android-tv" || value === "google-tv") {
9
+ return "tv";
10
+ }
11
+ }
12
+ }
13
+ return "phone";
14
+ }
15
+ export async function detectProfile(avdPath) {
16
+ try {
17
+ const configPath = join(avdPath, "config.ini");
18
+ const content = await readFile(configPath, "utf-8");
19
+ return detectProfileFromConfig(content);
20
+ }
21
+ catch {
22
+ return "phone";
23
+ }
24
+ }
25
+ //# sourceMappingURL=profiles.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"profiles.js","sourceRoot":"","sources":["../src/profiles.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAIjC,MAAM,UAAU,uBAAuB,CAAC,aAAqB;IAC3D,KAAK,MAAM,IAAI,IAAI,aAAa,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;QAC7C,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;QAC5B,IAAI,OAAO,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;YACjC,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC;YAC5C,IAAI,KAAK,KAAK,YAAY,IAAI,KAAK,KAAK,WAAW,EAAE,CAAC;gBACpD,OAAO,IAAI,CAAC;YACd,CAAC;QACH,CAAC;IACH,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,OAAe;IACjD,IAAI,CAAC;QACH,MAAM,UAAU,GAAG,IAAI,CAAC,OAAO,EAAE,YAAY,CAAC,CAAC;QAC/C,MAAM,OAAO,GAAG,MAAM,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;QACpD,OAAO,uBAAuB,CAAC,OAAO,CAAC,CAAC;IAC1C,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,OAAO,CAAC;IACjB,CAAC;AACH,CAAC"}
@@ -0,0 +1,10 @@
1
+ import type { VideoChunk } from "./engine/types.js";
2
+ export type WsPacketType = "config" | "delta" | "key";
3
+ export interface DecodedWsPacket {
4
+ type: WsPacketType;
5
+ timestamp: number;
6
+ data: Uint8Array;
7
+ }
8
+ export declare function encodeVideoPacket(chunk: VideoChunk): Uint8Array;
9
+ export declare function decodeVideoPacket(data: ArrayBufferLike): DecodedWsPacket;
10
+ //# sourceMappingURL=protocol.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"protocol.d.ts","sourceRoot":"","sources":["../src/protocol.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAEpD,MAAM,MAAM,YAAY,GAAG,QAAQ,GAAG,OAAO,GAAG,KAAK,CAAC;AAEtD,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,YAAY,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,UAAU,CAAC;CAClB;AAID,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,UAAU,GAAG,UAAU,CAW/D;AAED,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,eAAe,GAAG,eAAe,CAYxE"}
@@ -0,0 +1,27 @@
1
+ const PACKET_HEADER_BYTES = 13;
2
+ export function encodeVideoPacket(chunk) {
3
+ const type = chunk.type === "config" ? 0 : chunk.keyframe ? 2 : 1;
4
+ const header = new Uint8Array(PACKET_HEADER_BYTES);
5
+ const view = new DataView(header.buffer);
6
+ view.setUint8(0, type);
7
+ view.setUint32(1, chunk.data.byteLength);
8
+ view.setFloat64(5, chunk.timestamp ?? 0);
9
+ const packet = new Uint8Array(PACKET_HEADER_BYTES + chunk.data.byteLength);
10
+ packet.set(header, 0);
11
+ packet.set(chunk.data, PACKET_HEADER_BYTES);
12
+ return packet;
13
+ }
14
+ export function decodeVideoPacket(data) {
15
+ const view = new DataView(data);
16
+ const rawType = view.getUint8(0);
17
+ const length = view.getUint32(1);
18
+ const timestamp = data.byteLength >= PACKET_HEADER_BYTES ? view.getFloat64(5) : 0;
19
+ const offset = data.byteLength >= PACKET_HEADER_BYTES ? PACKET_HEADER_BYTES : 5;
20
+ const type = rawType === 0 ? "config" : rawType === 2 ? "key" : "delta";
21
+ return {
22
+ type,
23
+ timestamp,
24
+ data: new Uint8Array(data, offset, length),
25
+ };
26
+ }
27
+ //# sourceMappingURL=protocol.js.map