@arenahito/droid-webscr 0.0.0 → 0.2.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/dist/bin.js CHANGED
@@ -5,80 +5,80 @@ var __export = (target, all) => {
5
5
  __defProp(target, name, { get: all[name], enumerable: true });
6
6
  };
7
7
 
8
- // package.json
9
- var package_default = {
10
- name: "@arenahito/droid-webscr",
11
- version: "0.0.0",
12
- private: false,
13
- license: "MIT",
14
- repository: {
15
- type: "git",
16
- url: "git+https://github.com/arenahito/droid-webscr.git",
17
- directory: "apps/agent"
18
- },
19
- bin: {
20
- "droid-webscr": "dist/bin.js"
21
- },
22
- files: [
23
- "dist",
24
- "android",
25
- "README.md",
26
- "LICENSE"
27
- ],
28
- type: "module",
29
- exports: {
30
- ".": {
31
- types: "./dist/index.d.ts",
32
- default: "./dist/index.js"
8
+ // src/bin.ts
9
+ import { dirname as dirname2 } from "path";
10
+ import { fileURLToPath as fileURLToPath2 } from "url";
11
+
12
+ // ../agent/src/device-server/artifact.ts
13
+ import { access } from "fs/promises";
14
+ import { constants } from "fs";
15
+ import { dirname, join } from "path";
16
+ import { fileURLToPath } from "url";
17
+ var deviceServerArtifactFileName = "droid-webscr-server-android.jar";
18
+ var deviceServerArtifactRemotePath = "/data/local/tmp/droid-webscr-server.jar";
19
+ var defaultDeviceServerArtifact = {
20
+ localPath: `android/server/build/${deviceServerArtifactFileName}`,
21
+ remotePath: deviceServerArtifactRemotePath
22
+ };
23
+ async function resolveDeviceServerArtifact(moduleUrl = import.meta.url, startDirectory = dirname(fileURLToPath(moduleUrl))) {
24
+ const candidates = ancestorDirectories(startDirectory).flatMap((directory) => [
25
+ join(directory, "android", deviceServerArtifactFileName),
26
+ join(directory, "android", "server", "build", deviceServerArtifactFileName)
27
+ ]);
28
+ const checks = await Promise.all(
29
+ candidates.map(async (candidate) => ({
30
+ candidate,
31
+ exists: await isReadableFile(candidate)
32
+ }))
33
+ );
34
+ for (const check2 of checks) {
35
+ if (check2.exists) {
36
+ return {
37
+ localPath: check2.candidate,
38
+ remotePath: deviceServerArtifactRemotePath
39
+ };
33
40
  }
34
- },
35
- publishConfig: {
36
- access: "public"
37
- },
38
- scripts: {
39
- build: "tsup && node ../../tools/package-agent.mjs",
40
- dev: "tsc -p tsconfig.build.json --watch",
41
- lint: "oxlint --config ../../oxlint.json src",
42
- test: "vitest run",
43
- "type-check": "tsc -p tsconfig.json --noEmit"
44
- },
45
- dependencies: {
46
- "@fastify/websocket": "11.2.0",
47
- fastify: "5.8.5",
48
- pino: "10.3.1"
49
- },
50
- devDependencies: {
51
- "@types/ws": "8.18.1"
52
41
  }
53
- };
42
+ throw new Error(
43
+ `Android server artifact was not found. Run pnpm android:build before starting droid-webscr.`
44
+ );
45
+ }
46
+ function ancestorDirectories(startDirectory) {
47
+ const directories = [];
48
+ let current = startDirectory;
49
+ while (true) {
50
+ directories.push(current);
51
+ const parent = dirname(current);
52
+ if (parent === current) {
53
+ return directories;
54
+ }
55
+ current = parent;
56
+ }
57
+ }
58
+ async function isReadableFile(path) {
59
+ try {
60
+ await access(path, constants.R_OK);
61
+ return true;
62
+ } catch {
63
+ return false;
64
+ }
65
+ }
54
66
 
55
- // ../../packages/adb/dist/provider.js
67
+ // ../../packages/adb/src/provider.ts
56
68
  import { Readable } from "stream";
57
- var AdbAuthorizationState;
58
- (function(AdbAuthorizationState2) {
59
- AdbAuthorizationState2["Authorized"] = "authorized";
60
- AdbAuthorizationState2["Offline"] = "offline";
61
- AdbAuthorizationState2["Unauthorized"] = "unauthorized";
62
- })(AdbAuthorizationState || (AdbAuthorizationState = {}));
63
- var AdbTransportKind;
64
- (function(AdbTransportKind2) {
65
- AdbTransportKind2["Emulator"] = "emulator";
66
- AdbTransportKind2["Network"] = "network";
67
- AdbTransportKind2["Usb"] = "usb";
68
- })(AdbTransportKind || (AdbTransportKind = {}));
69
69
  function isUsableDevice(device) {
70
- return device.authorizationState === AdbAuthorizationState.Authorized && device.serial.length > 0;
70
+ return device.authorizationState === "authorized" /* Authorized */ && device.serial.length > 0;
71
71
  }
72
72
 
73
- // ../../packages/adb/dist/system-adb-provider.js
73
+ // ../../packages/adb/src/system-adb-provider.ts
74
74
  import { spawn } from "child_process";
75
75
  import { createConnection, Server } from "net";
76
76
  import { Readable as Readable2 } from "stream";
77
77
  var SystemAdbProvider = class _SystemAdbProvider {
78
- adbPath;
79
78
  constructor(adbPath = "adb") {
80
79
  this.adbPath = adbPath;
81
80
  }
81
+ adbPath;
82
82
  /* v8 ignore next 4 -- external adb process boundary; deterministic parsing is unit-tested */
83
83
  async listDevices() {
84
84
  const output = await runAndCollect(this.adbPath, ["devices", "-l"]);
@@ -117,16 +117,16 @@ var SystemAdbProvider = class _SystemAdbProvider {
117
117
  }
118
118
  };
119
119
  var SystemAdbDeviceSession = class {
120
+ constructor(adbPath, serial) {
121
+ this.adbPath = adbPath;
122
+ this.serial = serial;
123
+ }
120
124
  adbPath;
121
125
  serial;
122
126
  forwards = /* @__PURE__ */ new Set();
123
127
  sockets = /* @__PURE__ */ new Set();
124
128
  shellChildren = /* @__PURE__ */ new Set();
125
129
  shellCloses = /* @__PURE__ */ new Map();
126
- constructor(adbPath, serial) {
127
- this.adbPath = adbPath;
128
- this.serial = serial;
129
- }
130
130
  async push(localPath, remotePath) {
131
131
  await runAndCollect(this.adbPath, ["-s", this.serial, "push", localPath, remotePath]);
132
132
  }
@@ -172,10 +172,16 @@ var SystemAdbDeviceSession = class {
172
172
  this.forwards.add(forward.local);
173
173
  try {
174
174
  const socket = await connectLocalPortWithRetry(localPort);
175
- const forwardedSocket = new ForwardedAdbSocket(this.adbPath, this.serial, forward.local, socket, () => {
176
- this.forwards.delete(forward.local);
177
- this.sockets.delete(forwardedSocket);
178
- });
175
+ const forwardedSocket = new ForwardedAdbSocket(
176
+ this.adbPath,
177
+ this.serial,
178
+ forward.local,
179
+ socket,
180
+ () => {
181
+ this.forwards.delete(forward.local);
182
+ this.sockets.delete(forwardedSocket);
183
+ }
184
+ );
179
185
  this.sockets.add(forwardedSocket);
180
186
  return forwardedSocket;
181
187
  } catch (error51) {
@@ -191,7 +197,9 @@ var SystemAdbDeviceSession = class {
191
197
  await Promise.allSettled([
192
198
  ...this.shellCloses.values(),
193
199
  ...[...this.sockets].map((socket) => socket.close()),
194
- ...[...this.forwards].map((forward) => runAndCollect(this.adbPath, ["-s", this.serial, "forward", "--remove", forward]))
200
+ ...[...this.forwards].map(
201
+ (forward) => runAndCollect(this.adbPath, ["-s", this.serial, "forward", "--remove", forward])
202
+ )
195
203
  ]);
196
204
  this.forwards.clear();
197
205
  }
@@ -220,24 +228,24 @@ function parseDeviceLine(line) {
220
228
  }
221
229
  function parseAuthorizationState(state) {
222
230
  if (state === "device") {
223
- return AdbAuthorizationState.Authorized;
231
+ return "authorized" /* Authorized */;
224
232
  }
225
233
  if (state === "unauthorized") {
226
- return AdbAuthorizationState.Unauthorized;
234
+ return "unauthorized" /* Unauthorized */;
227
235
  }
228
- return AdbAuthorizationState.Offline;
236
+ return "offline" /* Offline */;
229
237
  }
230
238
  function inferTransportKind(serial, fields) {
231
239
  if (serial.startsWith("emulator-")) {
232
- return AdbTransportKind.Emulator;
240
+ return "emulator" /* Emulator */;
233
241
  }
234
242
  if (serial.includes(":")) {
235
- return AdbTransportKind.Network;
243
+ return "network" /* Network */;
236
244
  }
237
245
  if (fields.some((field) => field.startsWith("usb:"))) {
238
- return AdbTransportKind.Usb;
246
+ return "usb" /* Usb */;
239
247
  }
240
- return AdbTransportKind.Usb;
248
+ return "usb" /* Usb */;
241
249
  }
242
250
  function parseField(fields, key) {
243
251
  const prefix = `${key}:`;
@@ -260,7 +268,9 @@ async function runAndCollect(command, args) {
260
268
  return stdout;
261
269
  }
262
270
  async function startLogcatTail(command, serial) {
263
- const epoch = Number((await runAndCollect(command, ["-s", serial, "shell", "date", "+%s"])).trim());
271
+ const epoch = Number(
272
+ (await runAndCollect(command, ["-s", serial, "shell", "date", "+%s"])).trim()
273
+ );
264
274
  if (!Number.isFinite(epoch)) {
265
275
  throw new Error("Failed to read device logcat start time.");
266
276
  }
@@ -369,12 +379,6 @@ function delay(ms) {
369
379
  return new Promise((resolve) => setTimeout(resolve, ms));
370
380
  }
371
381
  var ForwardedAdbSocket = class {
372
- adbPath;
373
- serial;
374
- localForward;
375
- socket;
376
- onClose;
377
- chunks;
378
382
  constructor(adbPath, serial, localForward, socket, onClose = () => {
379
383
  }) {
380
384
  this.adbPath = adbPath;
@@ -384,6 +388,12 @@ var ForwardedAdbSocket = class {
384
388
  this.onClose = onClose;
385
389
  this.chunks = socket;
386
390
  }
391
+ adbPath;
392
+ serial;
393
+ localForward;
394
+ socket;
395
+ onClose;
396
+ chunks;
387
397
  async write(chunk) {
388
398
  await new Promise((resolve, reject) => {
389
399
  this.socket.write(chunk, (error51) => {
@@ -411,7 +421,7 @@ var ForwardedAdbSocket = class {
411
421
  }
412
422
  };
413
423
 
414
- // ../../packages/shared/dist/index.js
424
+ // ../../packages/shared/src/index.ts
415
425
  var AppError = class extends Error {
416
426
  code;
417
427
  constructor(code, message) {
@@ -14941,7 +14951,7 @@ function date4(params) {
14941
14951
  // ../../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/classic/external.js
14942
14952
  config(en_default());
14943
14953
 
14944
- // ../../packages/config/dist/schema.js
14954
+ // ../../packages/config/src/schema.ts
14945
14955
  var defaultAgentConfig = {
14946
14956
  authToken: void 0,
14947
14957
  bindHost: "127.0.0.1",
@@ -14964,7 +14974,9 @@ function validateAgentConfig(input) {
14964
14974
  return err(new ConfigError("CONFIG_INVALID", parsed.error.message));
14965
14975
  }
14966
14976
  if (!isLocalBind(parsed.data.bindHost) && !parsed.data.authToken) {
14967
- return err(new ConfigError("CONFIG_UNSAFE_BIND", "Non-local bind addresses require authToken."));
14977
+ return err(
14978
+ new ConfigError("CONFIG_UNSAFE_BIND", "Non-local bind addresses require authToken.")
14979
+ );
14968
14980
  }
14969
14981
  return ok(parsed.data);
14970
14982
  }
@@ -14972,70 +14984,15 @@ function isLocalBind(host) {
14972
14984
  return host === "127.0.0.1" || host === "localhost" || host === "::1";
14973
14985
  }
14974
14986
 
14975
- // src/runtime.ts
14987
+ // ../agent/src/runtime.ts
14976
14988
  import { pathToFileURL } from "url";
14977
14989
 
14978
- // src/device-server/artifact.ts
14979
- import { access } from "fs/promises";
14980
- import { constants } from "fs";
14981
- import { dirname, join } from "path";
14982
- import { fileURLToPath } from "url";
14983
- var deviceServerArtifactFileName = "droid-webscr-server-android.jar";
14984
- var deviceServerArtifactRemotePath = "/data/local/tmp/droid-webscr-server.jar";
14985
- var defaultDeviceServerArtifact = {
14986
- localPath: `android/server/build/${deviceServerArtifactFileName}`,
14987
- remotePath: deviceServerArtifactRemotePath
14988
- };
14989
- async function resolveDeviceServerArtifact(moduleUrl = import.meta.url, startDirectory = dirname(fileURLToPath(moduleUrl))) {
14990
- const candidates = ancestorDirectories(startDirectory).flatMap((directory) => [
14991
- join(directory, "android", deviceServerArtifactFileName),
14992
- join(directory, "android", "server", "build", deviceServerArtifactFileName)
14993
- ]);
14994
- const checks = await Promise.all(
14995
- candidates.map(async (candidate) => ({
14996
- candidate,
14997
- exists: await isReadableFile(candidate)
14998
- }))
14999
- );
15000
- for (const check2 of checks) {
15001
- if (check2.exists) {
15002
- return {
15003
- localPath: check2.candidate,
15004
- remotePath: deviceServerArtifactRemotePath
15005
- };
15006
- }
15007
- }
15008
- throw new Error(
15009
- `Android server artifact was not found. Run pnpm android:build before starting droid-webscr.`
15010
- );
15011
- }
15012
- function ancestorDirectories(startDirectory) {
15013
- const directories = [];
15014
- let current = startDirectory;
15015
- while (true) {
15016
- directories.push(current);
15017
- const parent = dirname(current);
15018
- if (parent === current) {
15019
- return directories;
15020
- }
15021
- current = parent;
15022
- }
15023
- }
15024
- async function isReadableFile(path) {
15025
- try {
15026
- await access(path, constants.R_OK);
15027
- return true;
15028
- } catch {
15029
- return false;
15030
- }
15031
- }
15032
-
15033
- // src/device-server/deploy.ts
14990
+ // ../agent/src/device-server/deploy.ts
15034
14991
  async function deployDeviceServer(session, artifact) {
15035
14992
  await session.push(artifact.localPath, artifact.remotePath);
15036
14993
  }
15037
14994
 
15038
- // src/device-server/start.ts
14995
+ // ../agent/src/device-server/start.ts
15039
14996
  var AdbDeviceServer = class {
15040
14997
  constructor(adbProvider, artifact = defaultDeviceServerArtifact) {
15041
14998
  this.adbProvider = adbProvider;
@@ -15193,36 +15150,30 @@ async function collectTextUntil(chunks, expected) {
15193
15150
  return output + decoder.decode();
15194
15151
  }
15195
15152
 
15196
- // src/server/create-fastify-app.ts
15153
+ // ../agent/src/server/create-fastify-app.ts
15197
15154
  import websocket from "@fastify/websocket";
15155
+ import middie from "@fastify/middie";
15198
15156
 
15199
- // ../../packages/protocol/dist/streams.js
15200
- var StreamId;
15201
- (function(StreamId2) {
15157
+ // ../../packages/protocol/src/streams.ts
15158
+ var StreamId = /* @__PURE__ */ ((StreamId2) => {
15202
15159
  StreamId2[StreamId2["Session"] = 1] = "Session";
15203
15160
  StreamId2[StreamId2["Device"] = 2] = "Device";
15204
15161
  StreamId2[StreamId2["Video"] = 3] = "Video";
15205
15162
  StreamId2[StreamId2["Control"] = 4] = "Control";
15206
15163
  StreamId2[StreamId2["Log"] = 5] = "Log";
15207
- })(StreamId || (StreamId = {}));
15208
- var knownStreams = new Set(Object.values(StreamId).filter((value) => typeof value === "number"));
15164
+ return StreamId2;
15165
+ })(StreamId || {});
15166
+ var knownStreams = new Set(
15167
+ Object.values(StreamId).filter((value) => typeof value === "number")
15168
+ );
15209
15169
 
15210
- // ../../packages/protocol/dist/frame.js
15170
+ // ../../packages/protocol/src/frame.ts
15211
15171
  var FRAME_MAGIC = 1146573635;
15212
15172
  var FRAME_HEADER_LENGTH = 40;
15213
15173
  var WIRE_VERSION = 1;
15214
15174
  var DEFAULT_MAX_PAYLOAD_LENGTH = 16 * 1024 * 1024;
15215
15175
 
15216
- // ../../packages/protocol/dist/errors.js
15217
- var ProtocolErrorCode;
15218
- (function(ProtocolErrorCode2) {
15219
- ProtocolErrorCode2["FrameTooShort"] = "FRAME_TOO_SHORT";
15220
- ProtocolErrorCode2["InvalidMagic"] = "INVALID_MAGIC";
15221
- ProtocolErrorCode2["UnsupportedVersion"] = "UNSUPPORTED_VERSION";
15222
- ProtocolErrorCode2["UnsupportedHeaderLength"] = "UNSUPPORTED_HEADER_LENGTH";
15223
- ProtocolErrorCode2["PayloadLengthMismatch"] = "PAYLOAD_LENGTH_MISMATCH";
15224
- ProtocolErrorCode2["PayloadTooLarge"] = "PAYLOAD_TOO_LARGE";
15225
- })(ProtocolErrorCode || (ProtocolErrorCode = {}));
15176
+ // ../../packages/protocol/src/errors.ts
15226
15177
  var ProtocolError = class extends Error {
15227
15178
  code;
15228
15179
  constructor(code, message) {
@@ -15232,7 +15183,7 @@ var ProtocolError = class extends Error {
15232
15183
  }
15233
15184
  };
15234
15185
 
15235
- // ../../packages/protocol/dist/codec.js
15186
+ // ../../packages/protocol/src/codec.ts
15236
15187
  function encodeFrame(frame) {
15237
15188
  const payloadLength = frame.payload.byteLength;
15238
15189
  const output = new Uint8Array(FRAME_HEADER_LENGTH + payloadLength);
@@ -15253,28 +15204,37 @@ function encodeFrame(frame) {
15253
15204
  function decodeFrame(bytes, options = {}) {
15254
15205
  const maxPayloadLength = options.maxPayloadLength ?? DEFAULT_MAX_PAYLOAD_LENGTH;
15255
15206
  if (bytes.byteLength < FRAME_HEADER_LENGTH) {
15256
- return failure(ProtocolErrorCode.FrameTooShort, "Frame is shorter than the protocol header.");
15207
+ return failure("FRAME_TOO_SHORT" /* FrameTooShort */, "Frame is shorter than the protocol header.");
15257
15208
  }
15258
15209
  const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
15259
15210
  const magic = view.getUint32(0, false);
15260
15211
  if (magic !== FRAME_MAGIC) {
15261
- return failure(ProtocolErrorCode.InvalidMagic, "Frame magic does not match DWSC.");
15212
+ return failure("INVALID_MAGIC" /* InvalidMagic */, "Frame magic does not match DWSC.");
15262
15213
  }
15263
15214
  const version2 = view.getUint16(4, false);
15264
15215
  if (version2 !== WIRE_VERSION) {
15265
- return failure(ProtocolErrorCode.UnsupportedVersion, `Unsupported wire version: ${version2}.`);
15216
+ return failure("UNSUPPORTED_VERSION" /* UnsupportedVersion */, `Unsupported wire version: ${version2}.`);
15266
15217
  }
15267
15218
  const headerLength = view.getUint16(6, false);
15268
15219
  if (headerLength !== FRAME_HEADER_LENGTH) {
15269
- return failure(ProtocolErrorCode.UnsupportedHeaderLength, `Unsupported header length: ${headerLength}.`);
15220
+ return failure(
15221
+ "UNSUPPORTED_HEADER_LENGTH" /* UnsupportedHeaderLength */,
15222
+ `Unsupported header length: ${headerLength}.`
15223
+ );
15270
15224
  }
15271
15225
  const payloadLength = view.getUint32(16, false);
15272
15226
  if (payloadLength > maxPayloadLength) {
15273
- return failure(ProtocolErrorCode.PayloadTooLarge, `Payload length ${payloadLength} exceeds limit ${maxPayloadLength}.`);
15227
+ return failure(
15228
+ "PAYLOAD_TOO_LARGE" /* PayloadTooLarge */,
15229
+ `Payload length ${payloadLength} exceeds limit ${maxPayloadLength}.`
15230
+ );
15274
15231
  }
15275
15232
  const expectedLength = FRAME_HEADER_LENGTH + payloadLength;
15276
15233
  if (bytes.byteLength !== expectedLength) {
15277
- return failure(ProtocolErrorCode.PayloadLengthMismatch, `Frame length ${bytes.byteLength} does not match declared length ${expectedLength}.`);
15234
+ return failure(
15235
+ "PAYLOAD_LENGTH_MISMATCH" /* PayloadLengthMismatch */,
15236
+ `Frame length ${bytes.byteLength} does not match declared length ${expectedLength}.`
15237
+ );
15278
15238
  }
15279
15239
  return {
15280
15240
  ok: true,
@@ -15302,9 +15262,8 @@ function failure(code, message) {
15302
15262
  };
15303
15263
  }
15304
15264
 
15305
- // ../../packages/protocol/dist/messages.js
15306
- var MessageType;
15307
- (function(MessageType2) {
15265
+ // ../../packages/protocol/src/messages.ts
15266
+ var MessageType = /* @__PURE__ */ ((MessageType2) => {
15308
15267
  MessageType2[MessageType2["SessionHello"] = 1] = "SessionHello";
15309
15268
  MessageType2[MessageType2["SessionHelloAck"] = 2] = "SessionHelloAck";
15310
15269
  MessageType2[MessageType2["SessionStart"] = 3] = "SessionStart";
@@ -15321,10 +15280,13 @@ var MessageType;
15321
15280
  MessageType2[MessageType2["ControlSystem"] = 772] = "ControlSystem";
15322
15281
  MessageType2[MessageType2["ControlClipboard"] = 773] = "ControlClipboard";
15323
15282
  MessageType2[MessageType2["LogRecord"] = 1025] = "LogRecord";
15324
- })(MessageType || (MessageType = {}));
15325
- var knownMessageTypes = new Set(Object.values(MessageType).filter((value) => typeof value === "number"));
15283
+ return MessageType2;
15284
+ })(MessageType || {});
15285
+ var knownMessageTypes = new Set(
15286
+ Object.values(MessageType).filter((value) => typeof value === "number")
15287
+ );
15326
15288
 
15327
- // ../../packages/transport/dist/stream.js
15289
+ // ../../packages/transport/src/stream.ts
15328
15290
  var DEFAULT_MAX_BUFFERED_BYTES = 16 * 1024 * 1024;
15329
15291
  var FrameAssembler = class {
15330
15292
  buffer = new Uint8Array();
@@ -15393,13 +15355,16 @@ function readPayloadLength(bytes) {
15393
15355
  return view.getUint32(16, false);
15394
15356
  }
15395
15357
 
15396
- // ../../packages/transport/dist/backpressure.js
15358
+ // ../../packages/transport/src/backpressure.ts
15397
15359
  var VIDEO_KEYFRAME_FLAG = 1 << 0;
15398
15360
 
15399
- // src/server/create-fastify-app.ts
15361
+ // ../agent/src/server/create-fastify-app.ts
15400
15362
  import Fastify from "fastify";
15363
+ import { constants as constants2 } from "fs";
15364
+ import { access as access2, readFile } from "fs/promises";
15365
+ import { extname, join as join2, normalize, relative, sep } from "path";
15401
15366
 
15402
- // src/security/origin.ts
15367
+ // ../agent/src/security/origin.ts
15403
15368
  function isAllowedOrigin(origin, config2, requestHost) {
15404
15369
  if (!origin) {
15405
15370
  return true;
@@ -15409,9 +15374,6 @@ function isAllowedOrigin(origin, config2, requestHost) {
15409
15374
  if (url2.protocol !== "http:") {
15410
15375
  return false;
15411
15376
  }
15412
- if (requestHost !== void 0 && isLocalDevUiOrigin(url2) && isAllowedHost(requestHost, config2)) {
15413
- return true;
15414
- }
15415
15377
  if (!isAllowedHost(url2.host, config2)) {
15416
15378
  return false;
15417
15379
  }
@@ -15454,9 +15416,6 @@ function isLocalHost(host) {
15454
15416
  const normalized = normalizeHost(host);
15455
15417
  return normalized === "127.0.0.1" || normalized === "localhost" || normalized === "::1";
15456
15418
  }
15457
- function isLocalDevUiOrigin(url2) {
15458
- return url2.port === "5173" && isLocalHost(url2.host);
15459
- }
15460
15419
  function samePort(url2, host) {
15461
15420
  return (url2.port || defaultPort(url2.protocol)) === (extractPort(host) || defaultPort(url2.protocol));
15462
15421
  }
@@ -15475,7 +15434,7 @@ function extractPort(host) {
15475
15434
  return parts.length === 2 ? parts[1] ?? "" : "";
15476
15435
  }
15477
15436
 
15478
- // src/security/session-token.ts
15437
+ // ../agent/src/security/session-token.ts
15479
15438
  import { randomBytes } from "crypto";
15480
15439
  function createSessionToken(sessionId, deviceSerial, nowMs, ttlMs, video) {
15481
15440
  return {
@@ -15487,7 +15446,7 @@ function createSessionToken(sessionId, deviceSerial, nowMs, ttlMs, video) {
15487
15446
  };
15488
15447
  }
15489
15448
 
15490
- // src/session/session-manager.ts
15449
+ // ../agent/src/session/session-manager.ts
15491
15450
  var SessionManager = class {
15492
15451
  constructor(adbProvider, now = () => Date.now(), ttlMs = 6e4) {
15493
15452
  this.adbProvider = adbProvider;
@@ -15573,7 +15532,7 @@ var SessionManager = class {
15573
15532
  }
15574
15533
  };
15575
15534
 
15576
- // src/security/auth.ts
15535
+ // ../agent/src/security/auth.ts
15577
15536
  function validateAgentAuthHeader(authorization, config2) {
15578
15537
  if (!config2.authToken) {
15579
15538
  return true;
@@ -15582,7 +15541,7 @@ function validateAgentAuthHeader(authorization, config2) {
15582
15541
  return values.some((value) => value === `Bearer ${config2.authToken}`);
15583
15542
  }
15584
15543
 
15585
- // src/server/routes.ts
15544
+ // ../agent/src/server/routes.ts
15586
15545
  var noopUpdateRuntimeConfig = () => {
15587
15546
  return;
15588
15547
  };
@@ -15910,10 +15869,10 @@ function createShareUrl(bindHost, port) {
15910
15869
  return `http://${host}:${port}`;
15911
15870
  }
15912
15871
 
15913
- // src/server/websocket.ts
15872
+ // ../agent/src/server/websocket.ts
15914
15873
  var binaryWebSocketProtocol = "droid-webscr.v1";
15915
15874
 
15916
- // src/server/create-fastify-app.ts
15875
+ // ../agent/src/server/create-fastify-app.ts
15917
15876
  function createLoggerOptions(enabled) {
15918
15877
  return enabled === false ? false : {
15919
15878
  level: "info",
@@ -15928,6 +15887,12 @@ async function createFastifyApp(context) {
15928
15887
  const app = Fastify({
15929
15888
  logger: createLoggerOptions(context.logger)
15930
15889
  });
15890
+ if (context.webUi?.devMiddleware) {
15891
+ await app.register(middie);
15892
+ app.use(
15893
+ context.webUi.devMiddleware
15894
+ );
15895
+ }
15931
15896
  await app.register(websocket, {
15932
15897
  options: {
15933
15898
  handleProtocols: selectBinaryWebSocketProtocol
@@ -15970,6 +15935,7 @@ async function createFastifyApp(context) {
15970
15935
  app.closeActiveDeviceSessions = closeActiveSessions;
15971
15936
  app.addHook("preClose", async () => {
15972
15937
  await closeActiveSessions({ waitForStartup: true });
15938
+ await context.webUi?.close?.();
15973
15939
  });
15974
15940
  app.addHook("onRequest", async (request, reply) => {
15975
15941
  const origin = request.headers.origin;
@@ -16117,6 +16083,7 @@ async function createFastifyApp(context) {
16117
16083
  });
16118
16084
  }
16119
16085
  );
16086
+ registerWebUiRoutes(app, context.webUi);
16120
16087
  return app;
16121
16088
  }
16122
16089
  function selectBinaryWebSocketProtocol(protocols) {
@@ -16132,8 +16099,85 @@ function ignoreAsyncError2() {
16132
16099
  function closeBrowserSocket(socket, code, reason) {
16133
16100
  socket.close?.(code, reason);
16134
16101
  }
16102
+ function registerWebUiRoutes(app, webUi) {
16103
+ if (!webUi) {
16104
+ return;
16105
+ }
16106
+ app.get("/*", async (request, reply) => {
16107
+ const path = request.url.split("?")[0] ?? "/";
16108
+ if (path.startsWith("/api/") || path === "/api" || path.startsWith("/ws/") || path === "/ws") {
16109
+ return reply.code(404).send({ error: "Not found" });
16110
+ }
16111
+ const file2 = await readWebUiFile(webUi, path);
16112
+ if (file2) {
16113
+ return reply.type(file2.contentType).send(file2.content);
16114
+ }
16115
+ if (webUi.renderIndex) {
16116
+ return reply.type("text/html; charset=utf-8").send(await webUi.renderIndex(request.url));
16117
+ }
16118
+ const index = await readWebUiFile(webUi, "/");
16119
+ if (index) {
16120
+ return reply.type(index.contentType).send(index.content);
16121
+ }
16122
+ return reply.code(404).send({ error: "Not found" });
16123
+ });
16124
+ }
16125
+ async function readWebUiFile(webUi, path) {
16126
+ const staticFile = webUi.staticFiles?.[path] ?? (path === "/" ? webUi.staticFiles?.["/"] : void 0);
16127
+ if (staticFile) {
16128
+ return staticFile;
16129
+ }
16130
+ if (!webUi.staticRoot) {
16131
+ return void 0;
16132
+ }
16133
+ const filePath = resolveStaticFilePath(webUi.staticRoot, path);
16134
+ if (!filePath || !await isReadableFile2(filePath)) {
16135
+ return void 0;
16136
+ }
16137
+ return {
16138
+ content: await readFile(filePath),
16139
+ contentType: contentTypeForPath(filePath)
16140
+ };
16141
+ }
16142
+ function resolveStaticFilePath(root, path) {
16143
+ const normalizedPath = path === "/" ? "/index.html" : path;
16144
+ const decodedPath = decodeURIComponent(normalizedPath);
16145
+ const relativePath = decodedPath.replace(/^\/+/, "");
16146
+ const resolvedPath = normalize(join2(root, relativePath));
16147
+ const relativeToRoot = relative(root, resolvedPath);
16148
+ if (relativeToRoot.startsWith("..") || relativeToRoot.includes(`..${sep}`)) {
16149
+ return void 0;
16150
+ }
16151
+ return resolvedPath;
16152
+ }
16153
+ async function isReadableFile2(path) {
16154
+ try {
16155
+ await access2(path, constants2.R_OK);
16156
+ return true;
16157
+ } catch {
16158
+ return false;
16159
+ }
16160
+ }
16161
+ function contentTypeForPath(path) {
16162
+ switch (extname(path)) {
16163
+ case ".css":
16164
+ return "text/css; charset=utf-8";
16165
+ case ".html":
16166
+ return "text/html; charset=utf-8";
16167
+ case ".js":
16168
+ return "text/javascript; charset=utf-8";
16169
+ case ".json":
16170
+ return "application/json; charset=utf-8";
16171
+ case ".svg":
16172
+ return "image/svg+xml";
16173
+ case ".wasm":
16174
+ return "application/wasm";
16175
+ default:
16176
+ return "application/octet-stream";
16177
+ }
16178
+ }
16135
16179
 
16136
- // src/runtime.ts
16180
+ // ../agent/src/runtime.ts
16137
16181
  async function startAgent(options = {}) {
16138
16182
  const adbProvider = options.adbProvider ?? new SystemAdbProvider();
16139
16183
  let runtimeConfig = options.config ?? defaultAgentConfig;
@@ -16149,7 +16193,8 @@ async function startAgent(options = {}) {
16149
16193
  rebindRuntime,
16150
16194
  updateRuntimeConfig: (nextConfig) => {
16151
16195
  runtimeConfig = nextConfig;
16152
- }
16196
+ },
16197
+ webUi: options.webUi
16153
16198
  });
16154
16199
  const applyRuntimeRebind = async (bindHost, port) => {
16155
16200
  const previousApp = currentApp;
@@ -16194,9 +16239,17 @@ async function startAgent(options = {}) {
16194
16239
  await currentApp?.close();
16195
16240
  await Promise.all(closingPorts.values());
16196
16241
  currentApp = void 0;
16242
+ },
16243
+ get url() {
16244
+ return createRuntimeUrl(runtimeConfig.bindHost, runtimeConfig.port);
16197
16245
  }
16198
16246
  };
16199
16247
  }
16248
+ function createRuntimeUrl(bindHost, port) {
16249
+ const host = bindHost === "0.0.0.0" || bindHost === "::" ? "127.0.0.1" : bindHost;
16250
+ const formattedHost = host.includes(":") && !host.startsWith("[") ? `[${host}]` : host;
16251
+ return `http://${formattedHost}:${port}`;
16252
+ }
16200
16253
  function isDirectRun(moduleUrl, argv) {
16201
16254
  const entrypoint = argv[1];
16202
16255
  return entrypoint !== void 0 && pathToFileURL(entrypoint).href === moduleUrl;
@@ -16238,41 +16291,89 @@ function ignoreAsyncError3() {
16238
16291
  return void 0;
16239
16292
  }
16240
16293
 
16241
- // src/bin.ts
16294
+ // package.json
16295
+ var package_default = {
16296
+ name: "@arenahito/droid-webscr",
16297
+ version: "0.2.0",
16298
+ private: false,
16299
+ license: "MIT",
16300
+ repository: {
16301
+ type: "git",
16302
+ url: "git+https://github.com/arenahito/droid-webscr.git",
16303
+ directory: "apps/cli"
16304
+ },
16305
+ bin: {
16306
+ "droid-webscr": "dist/bin.js"
16307
+ },
16308
+ files: [
16309
+ "dist",
16310
+ "web",
16311
+ "android",
16312
+ "README.md",
16313
+ "LICENSE"
16314
+ ],
16315
+ type: "module",
16316
+ publishConfig: {
16317
+ access: "public"
16318
+ },
16319
+ scripts: {
16320
+ build: "tsup && node ../../tools/package-cli.mjs",
16321
+ "build:dev": "tsup --config tsup.dev.config.ts",
16322
+ lint: "oxlint --config ../../oxlint.json src",
16323
+ test: "vitest run",
16324
+ "type-check": "tsc -p tsconfig.json --noEmit"
16325
+ },
16326
+ dependencies: {
16327
+ "@droid-webscr/agent": "workspace:*",
16328
+ "@fastify/middie": "9.3.2",
16329
+ "@fastify/websocket": "11.2.0",
16330
+ fastify: "5.8.5",
16331
+ pino: "10.3.1"
16332
+ },
16333
+ devDependencies: {
16334
+ vite: "^8.0.16"
16335
+ }
16336
+ };
16337
+
16338
+ // src/cli.ts
16242
16339
  async function runCli(argv, runtime = {}) {
16243
16340
  const args = argv.slice(2);
16244
16341
  const io = createCliIo(runtime);
16245
- const packageVersion = runtime.packageVersion ?? package_default.version;
16246
- const start = runtime.startAgent ?? startAgent;
16247
- if (args.length === 0) {
16248
- await start();
16249
- return 0;
16250
- }
16251
16342
  if (args.length === 1 && (args[0] === "--help" || args[0] === "-h")) {
16252
16343
  io.stdout(createCliHelp());
16253
16344
  return 0;
16254
16345
  }
16255
16346
  if (args.length === 1 && (args[0] === "--version" || args[0] === "-v")) {
16256
- io.stdout(`${packageVersion}
16347
+ io.stdout(`${runtime.packageVersion ?? "0.0.0"}
16257
16348
  `);
16258
16349
  return 0;
16259
16350
  }
16260
- io.stderr(`Unknown option: ${args.join(" ")}
16351
+ if (args.length > 0) {
16352
+ io.stderr(`Unknown option: ${args.join(" ")}
16261
16353
 
16262
16354
  `);
16263
- io.stderr(createCliHelp());
16264
- return 1;
16355
+ io.stderr(createCliHelp());
16356
+ return 1;
16357
+ }
16358
+ const startRuntime = runtime.startRuntime;
16359
+ if (!startRuntime) {
16360
+ throw new Error("No droid-webscr runtime was configured.");
16361
+ }
16362
+ const started = await startRuntime();
16363
+ io.stdout(`droid-webscr is running at ${started.url}
16364
+ `);
16365
+ return 0;
16265
16366
  }
16266
16367
  function createCliHelp() {
16267
16368
  return `Usage: droid-webscr [options]
16268
16369
 
16269
- Starts the local droid-webscr agent.
16370
+ Starts the integrated droid-webscr local server and web UI.
16270
16371
 
16271
16372
  Options:
16272
16373
  -h, --help Show this help text
16273
16374
  -v, --version Show the package version
16274
16375
 
16275
- The agent listens on http://127.0.0.1:7391 by default.
16376
+ The web UI and agent API share http://127.0.0.1:7391 by default.
16276
16377
  `;
16277
16378
  }
16278
16379
  function createCliIo(runtime) {
@@ -16281,8 +16382,35 @@ function createCliIo(runtime) {
16281
16382
  stdout: runtime.stdout ?? ((value) => process.stdout.write(value))
16282
16383
  };
16283
16384
  }
16385
+
16386
+ // src/package-paths.ts
16387
+ import { join as join3 } from "path";
16388
+ var packagedAndroidArtifactFileName = "droid-webscr-server-android.jar";
16389
+ function resolvePackagedWebRoot(packageRoot2) {
16390
+ return join3(packageRoot2, "web");
16391
+ }
16392
+ function resolvePackagedAndroidArtifact(packageRoot2) {
16393
+ return {
16394
+ localPath: join3(packageRoot2, "android", packagedAndroidArtifactFileName),
16395
+ remotePath: deviceServerArtifactRemotePath
16396
+ };
16397
+ }
16398
+
16399
+ // src/bin.ts
16400
+ var packageRoot = dirname2(dirname2(fileURLToPath2(import.meta.url)));
16401
+ async function startPackagedRuntime() {
16402
+ return startAgent({
16403
+ deviceServerArtifact: resolvePackagedAndroidArtifact(packageRoot),
16404
+ webUi: {
16405
+ staticRoot: resolvePackagedWebRoot(packageRoot)
16406
+ }
16407
+ });
16408
+ }
16284
16409
  if (isDirectRun(import.meta.url, process.argv)) {
16285
- runCli(process.argv).then(
16410
+ runCli(process.argv, {
16411
+ packageVersion: package_default.version,
16412
+ startRuntime: startPackagedRuntime
16413
+ }).then(
16286
16414
  (exitCode) => {
16287
16415
  process.exitCode = exitCode;
16288
16416
  },
@@ -16293,7 +16421,6 @@ if (isDirectRun(import.meta.url, process.argv)) {
16293
16421
  );
16294
16422
  }
16295
16423
  export {
16296
- createCliHelp,
16297
- runCli
16424
+ startPackagedRuntime
16298
16425
  };
16299
16426
  //# sourceMappingURL=bin.js.map