@skylandnpm/octopus-proxy 0.1.50 → 0.1.52
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/{chunk-6NV726RL.js → chunk-3KIA6HF6.js} +87 -18
- package/dist/{chunk-PPH4OB7Y.js → chunk-XIJKEY4L.js} +13 -9
- package/dist/cli.js +2 -2
- package/dist/main.js +2 -2
- package/dist/remote-worker.js +1 -1
- package/native/desktop-macos.m +3 -1
- package/native/remote-macos.m +31 -6
- package/package.json +1 -1
- package/proxy.example.json +0 -1
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
probeRemote,
|
|
13
13
|
remoteSessionBudget,
|
|
14
14
|
z
|
|
15
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-XIJKEY4L.js";
|
|
16
16
|
|
|
17
17
|
// ../../node_modules/.pnpm/ajv@8.17.1/node_modules/ajv/dist/compile/codegen/code.js
|
|
18
18
|
var require_code = __commonJS({
|
|
@@ -3321,8 +3321,8 @@ var require_utils = __commonJS({
|
|
|
3321
3321
|
var HOST_DELIMS = { "@": "%40", "/": "%2F", "?": "%3F", "#": "%23", ":": "%3A" };
|
|
3322
3322
|
var HOST_DELIM_RE = /[@/?#:]/g;
|
|
3323
3323
|
var HOST_DELIM_NO_COLON_RE = /[@/?#]/g;
|
|
3324
|
-
function reescapeHostDelimiters(host,
|
|
3325
|
-
const re =
|
|
3324
|
+
function reescapeHostDelimiters(host, isIP2) {
|
|
3325
|
+
const re = isIP2 ? HOST_DELIM_NO_COLON_RE : HOST_DELIM_RE;
|
|
3326
3326
|
re.lastIndex = 0;
|
|
3327
3327
|
return host.replace(re, (ch) => HOST_DELIMS[ch]);
|
|
3328
3328
|
}
|
|
@@ -4011,8 +4011,8 @@ var require_fast_uri = __commonJS({
|
|
|
4011
4011
|
const host = matches[4];
|
|
4012
4012
|
return hasMalformedPercentEncoding(matches[3]) || host !== void 0 && !isIPLiteral(host) && hasMalformedPercentEncoding(host) || hasMalformedPercentEncoding(matches[6]) || hasMalformedPercentEncoding(matches[7]) || hasMalformedPercentEncoding(matches[8]);
|
|
4013
4013
|
}
|
|
4014
|
-
function canonicalizeHost(parsed, options, schemeHandler,
|
|
4015
|
-
if (!options.unicodeSupport && (!schemeHandler || !schemeHandler.unicodeSupport) && parsed.host && !isIPLiteral(parsed.host) && (options.domainHost || schemeHandler && schemeHandler.domainHost) &&
|
|
4014
|
+
function canonicalizeHost(parsed, options, schemeHandler, isIP2) {
|
|
4015
|
+
if (!options.unicodeSupport && (!schemeHandler || !schemeHandler.unicodeSupport) && parsed.host && !isIPLiteral(parsed.host) && (options.domainHost || schemeHandler && schemeHandler.domainHost) && isIP2 === false && nonSimpleDomain(parsed.host)) {
|
|
4016
4016
|
try {
|
|
4017
4017
|
parsed.host = new URL("http://" + parsed.host).hostname;
|
|
4018
4018
|
} catch (e) {
|
|
@@ -4039,7 +4039,7 @@ var require_fast_uri = __commonJS({
|
|
|
4039
4039
|
let malformedHost = false;
|
|
4040
4040
|
let malformedIPLiteral = false;
|
|
4041
4041
|
let malformedScheme = false;
|
|
4042
|
-
let
|
|
4042
|
+
let isIP2 = false;
|
|
4043
4043
|
if (options.reference === "suffix") {
|
|
4044
4044
|
if (options.scheme) {
|
|
4045
4045
|
uri = options.scheme + ":" + uri;
|
|
@@ -4102,15 +4102,15 @@ var require_fast_uri = __commonJS({
|
|
|
4102
4102
|
const bracketedIPLiteral = isIPLiteral(parsed.host);
|
|
4103
4103
|
const hasIPLiteralBracket = parsed.host.indexOf("[") !== -1 || parsed.host.indexOf("]") !== -1;
|
|
4104
4104
|
const ipv6result = normalizeIPv6(parsed.host);
|
|
4105
|
-
|
|
4105
|
+
isIP2 = ipv6result.isIPV6 || ipv6result.isIPVFuture === true;
|
|
4106
4106
|
malformedIPLiteral = hasIPLiteralBracket && (!bracketedIPLiteral || ipv6result.error === true);
|
|
4107
|
-
parsed.host =
|
|
4107
|
+
parsed.host = isIP2 ? ipv6result.host : ipv6result.host.toLowerCase();
|
|
4108
4108
|
if (malformedIPLiteral) {
|
|
4109
4109
|
parsed.error = parsed.error || "URI host is malformed.";
|
|
4110
4110
|
malformedAuthorityOrPort = true;
|
|
4111
4111
|
}
|
|
4112
4112
|
} else {
|
|
4113
|
-
|
|
4113
|
+
isIP2 = true;
|
|
4114
4114
|
}
|
|
4115
4115
|
}
|
|
4116
4116
|
if (parsed.scheme === void 0 && parsed.userinfo === void 0 && parsed.host === void 0 && parsed.port === void 0 && parsed.query === void 0 && !parsed.path) {
|
|
@@ -4127,14 +4127,14 @@ var require_fast_uri = __commonJS({
|
|
|
4127
4127
|
}
|
|
4128
4128
|
const schemeHandler = getSchemeHandler(options.scheme || parsed.scheme);
|
|
4129
4129
|
if (!malformedIPLiteral) {
|
|
4130
|
-
malformedHost = canonicalizeHost(parsed, options, schemeHandler,
|
|
4130
|
+
malformedHost = canonicalizeHost(parsed, options, schemeHandler, isIP2);
|
|
4131
4131
|
}
|
|
4132
4132
|
if (uri.indexOf("%") !== -1 && parsed.host !== void 0 && !malformedIPLiteral) {
|
|
4133
|
-
let host =
|
|
4134
|
-
if (!
|
|
4133
|
+
let host = isIP2 ? parsed.host : normalizePercentEncoding(parsed.host, true);
|
|
4134
|
+
if (!isIP2) {
|
|
4135
4135
|
host = normalizePercentEncoding(host.toLowerCase());
|
|
4136
4136
|
}
|
|
4137
|
-
parsed.host = reescapeHostDelimiters(host,
|
|
4137
|
+
parsed.host = reescapeHostDelimiters(host, isIP2);
|
|
4138
4138
|
}
|
|
4139
4139
|
if (!schemeHandler || schemeHandler && !schemeHandler.skipNormalize) {
|
|
4140
4140
|
if (parsed.path) {
|
|
@@ -13335,6 +13335,71 @@ var PlatformDesktopSession = class {
|
|
|
13335
13335
|
import { fork } from "node:child_process";
|
|
13336
13336
|
import { randomBytes as randomBytes2, randomUUID as randomUUID16 } from "node:crypto";
|
|
13337
13337
|
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
13338
|
+
|
|
13339
|
+
// src/remote-stun.ts
|
|
13340
|
+
import { Resolver } from "node:dns/promises";
|
|
13341
|
+
import { isIP } from "node:net";
|
|
13342
|
+
var publicStunHost = "stun.cloudflare.com";
|
|
13343
|
+
var dnsJsonEndpoints = ["https://dns.alidns.com/resolve", "https://cloudflare-dns.com/dns-query"];
|
|
13344
|
+
var STUN_DNS_TIMEOUT = 1500;
|
|
13345
|
+
function defaultRemoteStunUrls(hubUrl) {
|
|
13346
|
+
return [.../* @__PURE__ */ new Set([`stun:${new URL(hubUrl).hostname}:3478`, `stun:${publicStunHost}:3478`])];
|
|
13347
|
+
}
|
|
13348
|
+
function publicAddress(address, family) {
|
|
13349
|
+
if (isIP(address) !== family) return false;
|
|
13350
|
+
if (family === 6) return /^[23][\da-f]{0,3}:/i.test(address);
|
|
13351
|
+
const [a, b] = address.split(".").map(Number);
|
|
13352
|
+
return a !== 0 && a !== 10 && a !== 127 && a < 224 && !(a === 100 && b >= 64 && b <= 127) && !(a === 169 && b === 254) && !(a === 172 && b >= 16 && b <= 31) && !(a === 192 && b === 168) && !(a === 198 && (b === 18 || b === 19));
|
|
13353
|
+
}
|
|
13354
|
+
async function systemLookup(family) {
|
|
13355
|
+
const resolver = new Resolver({ timeout: 500, tries: 1 });
|
|
13356
|
+
try {
|
|
13357
|
+
return await (family === 6 ? resolver.resolve6(publicStunHost) : resolver.resolve4(publicStunHost));
|
|
13358
|
+
} finally {
|
|
13359
|
+
resolver.cancel();
|
|
13360
|
+
}
|
|
13361
|
+
}
|
|
13362
|
+
async function queryDnsJson(endpoint, family, signal) {
|
|
13363
|
+
const type = family === 6 ? 28 : 1;
|
|
13364
|
+
const url = new URL(endpoint);
|
|
13365
|
+
url.searchParams.set("name", publicStunHost);
|
|
13366
|
+
url.searchParams.set("type", String(type));
|
|
13367
|
+
const response = await fetch(url, { headers: { accept: "application/dns-json" }, signal, redirect: "error" });
|
|
13368
|
+
if (!response.ok) throw new Error("STUN DNS request failed");
|
|
13369
|
+
const text = await response.text();
|
|
13370
|
+
if (text.length > 16384) throw new Error("STUN DNS response too large");
|
|
13371
|
+
const body = JSON.parse(text);
|
|
13372
|
+
if (body.Status !== 0 || !Array.isArray(body.Answer)) throw new Error("STUN DNS answer unavailable");
|
|
13373
|
+
return body.Answer.filter((record2) => record2.type === type && typeof record2.data === "string").map((record2) => record2.data);
|
|
13374
|
+
}
|
|
13375
|
+
async function resolvePublicStunAddresses(lookup = systemLookup, query = queryDnsJson) {
|
|
13376
|
+
const families = await Promise.all([6, 4].map(async (family) => {
|
|
13377
|
+
const valid = (addresses) => addresses.filter((address) => publicAddress(address, family)).slice(0, 1);
|
|
13378
|
+
const native = valid(await lookup(family).catch(() => []));
|
|
13379
|
+
if (native.length) return native;
|
|
13380
|
+
const controller = new AbortController();
|
|
13381
|
+
const timer = setTimeout(() => controller.abort(), STUN_DNS_TIMEOUT);
|
|
13382
|
+
try {
|
|
13383
|
+
return await Promise.any(dnsJsonEndpoints.map(async (endpoint) => {
|
|
13384
|
+
const addresses = valid(await query(endpoint, family, controller.signal));
|
|
13385
|
+
if (!addresses.length) throw new Error("No public STUN address");
|
|
13386
|
+
return addresses;
|
|
13387
|
+
})).catch(() => []);
|
|
13388
|
+
} finally {
|
|
13389
|
+
clearTimeout(timer);
|
|
13390
|
+
controller.abort();
|
|
13391
|
+
}
|
|
13392
|
+
}));
|
|
13393
|
+
return families.flat();
|
|
13394
|
+
}
|
|
13395
|
+
async function discoverRemoteStunUrls(hubUrl, configured, resolve5 = resolvePublicStunAddresses) {
|
|
13396
|
+
if (configured) return [...configured];
|
|
13397
|
+
const addresses = await resolve5().catch(() => []);
|
|
13398
|
+
const numeric = addresses.map((address) => `stun:${isIP(address) === 6 ? `[${address}]` : address}:3478`);
|
|
13399
|
+
return [.../* @__PURE__ */ new Set([...numeric, ...defaultRemoteStunUrls(hubUrl)])].slice(0, 4);
|
|
13400
|
+
}
|
|
13401
|
+
|
|
13402
|
+
// src/remote-manager.ts
|
|
13338
13403
|
function spawnWorker(message) {
|
|
13339
13404
|
const worker = fileURLToPath4(new URL(import.meta.url.endsWith(".ts") ? "./remote-worker.ts" : "./remote-worker.js", import.meta.url));
|
|
13340
13405
|
const child = fork(worker, [], { stdio: ["ignore", "ignore", "ignore", "ipc"], execArgv: import.meta.url.endsWith(".ts") ? ["--import", "tsx"] : [] });
|
|
@@ -13342,7 +13407,7 @@ function spawnWorker(message) {
|
|
|
13342
13407
|
return child;
|
|
13343
13408
|
}
|
|
13344
13409
|
var RemoteManager = class {
|
|
13345
|
-
constructor(host, desktop, desktopSession, send, touch, workerFactory = spawnWorker, probe = probeRemote) {
|
|
13410
|
+
constructor(host, desktop, desktopSession, send, touch, workerFactory = spawnWorker, probe = probeRemote, discoverStun = discoverRemoteStunUrls) {
|
|
13346
13411
|
this.host = host;
|
|
13347
13412
|
this.desktop = desktop;
|
|
13348
13413
|
this.desktopSession = desktopSession;
|
|
@@ -13350,13 +13415,14 @@ var RemoteManager = class {
|
|
|
13350
13415
|
this.touch = touch;
|
|
13351
13416
|
this.workerFactory = workerFactory;
|
|
13352
13417
|
this.probe = probe;
|
|
13418
|
+
this.discoverStun = discoverStun;
|
|
13353
13419
|
this.config = RemoteConfigSchema.parse(host.desktopRemote ?? {});
|
|
13354
13420
|
}
|
|
13355
13421
|
current;
|
|
13356
13422
|
stopping = false;
|
|
13357
13423
|
config;
|
|
13358
13424
|
stunUrls() {
|
|
13359
|
-
return this.config.stunUrls ??
|
|
13425
|
+
return this.config.stunUrls ?? defaultRemoteStunUrls(this.host.hubUrl);
|
|
13360
13426
|
}
|
|
13361
13427
|
async status() {
|
|
13362
13428
|
const base = { qualityModes: RemoteQualitySchema.options, platform: process.platform, passwordInput: process.platform === "darwin", enabled: this.config.enabled, busy: !!this.current, stunUrls: this.stunUrls(), budget: { ...REMOTE_DEFAULTS, totalBitrate: this.config.totalBitrate, videoMaxBitrate: this.config.videoMaxBitrate } };
|
|
@@ -13404,7 +13470,10 @@ var RemoteManager = class {
|
|
|
13404
13470
|
const session = { remoteId: randomUUID16(), owner, closed: false, signaling: Promise.resolve(), signals: 0, signalWindow: Date.now(), candidates: 0 };
|
|
13405
13471
|
this.current = session;
|
|
13406
13472
|
try {
|
|
13407
|
-
const permissions = await
|
|
13473
|
+
const [permissions, stunUrls] = await Promise.all([
|
|
13474
|
+
this.probe(this.host.stateDirectory, this.config),
|
|
13475
|
+
this.discoverStun(this.host.hubUrl, this.config.stunUrls)
|
|
13476
|
+
]);
|
|
13408
13477
|
if (!permissions.session || !permissions.capture || !permissions.input) throw new Error("\u8FDC\u63A7\u9700\u8981\u4E3B\u673A\u56FE\u5F62\u4F1A\u8BDD\u3001\u5C4F\u5E55\u5F55\u5236\u53CA\u8F85\u52A9\u529F\u80FD\u6743\u9650");
|
|
13409
13478
|
if (permissions.locked === true && permissions.lockCapture === false) throw new Error("\u9501\u5C4F\u8FDC\u63A7\u9700\u8981 macOS 14 \u53CA\u4EE5\u4E0A\u53CA\u65B0\u7248 Xcode Command Line Tools\uFF1B\u5F53\u524D\u4E3B\u673A\u8BF7\u5148\u672C\u5730\u89E3\u9501");
|
|
13410
13479
|
const identity = await this.desktopSession.sessionIdentity();
|
|
@@ -13413,7 +13482,7 @@ var RemoteManager = class {
|
|
|
13413
13482
|
const layout = await this.desktop.layoutIdentity?.(display.id);
|
|
13414
13483
|
if (session.closed || this.current !== session) throw new Error("\u5DE5\u4F5C\u533A\u8FDE\u63A5\u5DF2\u5173\u95ED\uFF0C\u8FDC\u63A7\u5DF2\u53D6\u6D88");
|
|
13415
13484
|
const bindToken = randomBytes2(32).toString("base64url"), expiresAt = Date.now() + 2e4;
|
|
13416
|
-
const child = this.workerFactory({ remoteId: session.remoteId, bindToken, expiresAt, display, helper: permissions.helper, quality, legacy: payload.quality === void 0, config: this.config, stunUrls
|
|
13485
|
+
const child = this.workerFactory({ remoteId: session.remoteId, bindToken, expiresAt, display, helper: permissions.helper, quality, legacy: payload.quality === void 0, config: this.config, stunUrls });
|
|
13417
13486
|
session.child = child;
|
|
13418
13487
|
child.on("message", (message) => {
|
|
13419
13488
|
if (this.current !== session || session.closed || !message || typeof message !== "object") return;
|
|
@@ -13447,7 +13516,7 @@ var RemoteManager = class {
|
|
|
13447
13516
|
}
|
|
13448
13517
|
}, 1e3);
|
|
13449
13518
|
session.monitor.unref();
|
|
13450
|
-
return { remoteId: session.remoteId, bindToken, expiresAt: new Date(expiresAt).toISOString(), display, revision: 1, stunUrls
|
|
13519
|
+
return { remoteId: session.remoteId, bindToken, expiresAt: new Date(expiresAt).toISOString(), display, revision: 1, stunUrls, budget, quality, qualityModes: RemoteQualitySchema.options, protocolVersion: 1, heartbeatAck: true };
|
|
13451
13520
|
} catch (error) {
|
|
13452
13521
|
this.finish(session);
|
|
13453
13522
|
throw error;
|
|
@@ -4187,7 +4187,7 @@ function assertLgplFFmpeg(output) {
|
|
|
4187
4187
|
import { execFile } from "node:child_process";
|
|
4188
4188
|
import { promisify } from "node:util";
|
|
4189
4189
|
import { createHash, randomUUID } from "node:crypto";
|
|
4190
|
-
import { chmod, mkdir, mkdtemp, readFile, rename, rm, stat } from "node:fs/promises";
|
|
4190
|
+
import { chmod, mkdir, mkdtemp, readFile, rename, rm, stat, writeFile } from "node:fs/promises";
|
|
4191
4191
|
import { join } from "node:path";
|
|
4192
4192
|
import { fileURLToPath } from "node:url";
|
|
4193
4193
|
var DESKTOP_IMAGE_LIMIT = 20 * 1024 * 1024;
|
|
@@ -4257,16 +4257,20 @@ var PlatformDesktop = class {
|
|
|
4257
4257
|
await mkdir(this.stateDirectory, { recursive: true, mode: 448 });
|
|
4258
4258
|
const target = join(this.stateDirectory, "octopus-desktop-capture");
|
|
4259
4259
|
const source = fileURLToPath(new URL("../native/desktop-macos.m", import.meta.url));
|
|
4260
|
-
const
|
|
4261
|
-
|
|
4262
|
-
const
|
|
4260
|
+
const flags = ["-O2", "-fobjc-arc", "-mmacosx-version-min=12.3", "-framework", "Foundation", "-framework", "AppKit", "-framework", "ScreenCaptureKit", "-framework", "CoreMedia", "-framework", "CoreVideo", "-framework", "VideoToolbox", "-framework", "ImageIO"];
|
|
4261
|
+
const fingerprint = createHash("sha256").update(await readFile(source)).update(JSON.stringify([process.arch, flags])).digest("hex");
|
|
4262
|
+
const stamp = `${target}.sha256`, existing = await stat(target).catch(() => null);
|
|
4263
|
+
if (existing?.isFile() && (existing.mode & 63) === 0 && await readFile(stamp, "utf8").catch(() => "") === fingerprint) return target;
|
|
4264
|
+
const temporary = `${target}.${randomUUID()}`, temporaryStamp = `${temporary}.sha256`;
|
|
4263
4265
|
try {
|
|
4264
|
-
await this.execute("/usr/bin/clang", [
|
|
4266
|
+
await this.execute("/usr/bin/clang", [...flags, source, "-o", temporary]);
|
|
4265
4267
|
await chmod(temporary, 448);
|
|
4268
|
+
await writeFile(temporaryStamp, fingerprint, { mode: 384 });
|
|
4266
4269
|
await rename(temporary, target);
|
|
4270
|
+
await rename(temporaryStamp, stamp);
|
|
4267
4271
|
return target;
|
|
4268
4272
|
} finally {
|
|
4269
|
-
await rm(temporary, { force: true });
|
|
4273
|
+
await Promise.all([rm(temporary, { force: true }), rm(temporaryStamp, { force: true })]);
|
|
4270
4274
|
}
|
|
4271
4275
|
})();
|
|
4272
4276
|
this.helper.catch(() => {
|
|
@@ -4447,7 +4451,7 @@ var DesktopManager = class {
|
|
|
4447
4451
|
// src/remote-native.ts
|
|
4448
4452
|
import { execFile as execFile2, spawn } from "node:child_process";
|
|
4449
4453
|
import { promisify as promisify2 } from "node:util";
|
|
4450
|
-
import { chmod as chmod2, mkdir as mkdir2, readFile as readFile2, rename as rename2, rm as rm2, stat as stat2, writeFile } from "node:fs/promises";
|
|
4454
|
+
import { chmod as chmod2, mkdir as mkdir2, readFile as readFile2, rename as rename2, rm as rm2, stat as stat2, writeFile as writeFile2 } from "node:fs/promises";
|
|
4451
4455
|
import { join as join2 } from "node:path";
|
|
4452
4456
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
4453
4457
|
import { createHash as createHash2, randomUUID as randomUUID2 } from "node:crypto";
|
|
@@ -4461,7 +4465,7 @@ async function ensureRemoteHelper(stateDirectory) {
|
|
|
4461
4465
|
const source = fileURLToPath2(new URL(mac ? "../native/remote-macos.m" : "../native/remote-x11.c", import.meta.url));
|
|
4462
4466
|
const target = join2(stateDirectory, "octopus-remote-desktop");
|
|
4463
4467
|
const stamp = `${target}.sha256`;
|
|
4464
|
-
const flags = mac ? ["-O2", "-fobjc-arc", "-mmacosx-version-min=12.3", "-framework", "Foundation", "-framework", "AppKit", "-framework", "ScreenCaptureKit", "-framework", "CoreMedia", "-framework", "CoreVideo", "-framework", "ApplicationServices", "-framework", "Carbon"] : ["-O2", "-lX11", "-lXtst"];
|
|
4468
|
+
const flags = mac ? ["-O2", "-fobjc-arc", "-mmacosx-version-min=12.3", "-framework", "Foundation", "-framework", "AppKit", "-framework", "ScreenCaptureKit", "-framework", "CoreMedia", "-framework", "CoreVideo", "-framework", "ApplicationServices", "-framework", "Carbon", "-framework", "IOKit"] : ["-O2", "-lX11", "-lXtst"];
|
|
4465
4469
|
const fingerprint = createHash2("sha256").update(await readFile2(source)).update(JSON.stringify([process.platform, process.arch, flags])).digest("hex");
|
|
4466
4470
|
const existing = await stat2(target).catch(() => void 0);
|
|
4467
4471
|
if (existing?.isFile() && (existing.mode & 63) === 0 && await readFile2(stamp, "utf8").catch(() => "") === fingerprint) return target;
|
|
@@ -4470,7 +4474,7 @@ async function ensureRemoteHelper(stateDirectory) {
|
|
|
4470
4474
|
try {
|
|
4471
4475
|
await exec(mac ? "/usr/bin/clang" : "cc", args, { timeout: 6e4, maxBuffer: 16384 });
|
|
4472
4476
|
await chmod2(temporary, 448);
|
|
4473
|
-
await
|
|
4477
|
+
await writeFile2(temporaryStamp, fingerprint, { mode: 384 });
|
|
4474
4478
|
await rename2(temporary, target);
|
|
4475
4479
|
await rename2(temporaryStamp, stamp);
|
|
4476
4480
|
return target;
|
package/dist/cli.js
CHANGED
|
@@ -31,13 +31,13 @@ import {
|
|
|
31
31
|
supportedPlatform,
|
|
32
32
|
systemdUnitPath,
|
|
33
33
|
toolSearchDirs
|
|
34
|
-
} from "./chunk-
|
|
34
|
+
} from "./chunk-3KIA6HF6.js";
|
|
35
35
|
import {
|
|
36
36
|
DESKTOP_PERMISSION_GUIDANCE,
|
|
37
37
|
PlatformDesktop,
|
|
38
38
|
checkDesktop,
|
|
39
39
|
requestDesktopPermission
|
|
40
|
-
} from "./chunk-
|
|
40
|
+
} from "./chunk-XIJKEY4L.js";
|
|
41
41
|
|
|
42
42
|
// src/cli.ts
|
|
43
43
|
import { spawn as spawn2 } from "node:child_process";
|
package/dist/main.js
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
import {
|
|
3
3
|
ConfigError,
|
|
4
4
|
runDaemon
|
|
5
|
-
} from "./chunk-
|
|
6
|
-
import "./chunk-
|
|
5
|
+
} from "./chunk-3KIA6HF6.js";
|
|
6
|
+
import "./chunk-XIJKEY4L.js";
|
|
7
7
|
|
|
8
8
|
// src/main.ts
|
|
9
9
|
runDaemon(process.env.OCTOPUS_PROXY_CONFIG ?? "proxy.json").catch((error) => {
|
package/dist/remote-worker.js
CHANGED
package/native/desktop-macos.m
CHANGED
|
@@ -106,7 +106,9 @@ int main(int argc, const char *argv[]) {
|
|
|
106
106
|
if (argc == 2 && strcmp(argv[1], "list") == 0) {
|
|
107
107
|
CGDirectDisplayID ids[64];
|
|
108
108
|
uint32_t count = 0;
|
|
109
|
-
|
|
109
|
+
// Sleeping displays are still connected and must remain selectable so
|
|
110
|
+
// an authenticated remote connection can wake them with user input.
|
|
111
|
+
if (CGGetOnlineDisplayList(64, ids, &count) != kCGErrorSuccess) {
|
|
110
112
|
fail(@"无法读取活动显示器,请检查 Proxy 是否运行在已登录的图形桌面会话中。" );
|
|
111
113
|
return 1;
|
|
112
114
|
}
|
package/native/remote-macos.m
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
#import <ApplicationServices/ApplicationServices.h>
|
|
4
4
|
#import <ScreenCaptureKit/ScreenCaptureKit.h>
|
|
5
5
|
#import <Carbon/Carbon.h>
|
|
6
|
+
#import <IOKit/pwr_mgt/IOPMLib.h>
|
|
6
7
|
#include <arpa/inet.h>
|
|
7
8
|
#include <fcntl.h>
|
|
8
9
|
#include <signal.h>
|
|
@@ -17,6 +18,8 @@ static NSMutableSet<NSNumber *> *heldKeys;
|
|
|
17
18
|
static BOOL heldButtons[3];
|
|
18
19
|
static double lastPing;
|
|
19
20
|
static pid_t parentPID;
|
|
21
|
+
static IOPMAssertionID activityAssertion = kIOPMNullAssertionID;
|
|
22
|
+
static double lastActivity;
|
|
20
23
|
static void signalStop(int signal) { stopping = 1; }
|
|
21
24
|
static BOOL sessionPresent(void) {
|
|
22
25
|
NSDictionary *s = CFBridgingRelease(CGSessionCopyCurrentDictionary());
|
|
@@ -29,6 +32,19 @@ static BOOL screenLocked(void) {
|
|
|
29
32
|
return [s[@"CGSSessionScreenIsLocked"] boolValue];
|
|
30
33
|
}
|
|
31
34
|
static BOOL inputOK(void) { return AXIsProcessTrusted() && CGPreflightPostEventAccess() && sessionPresent(); }
|
|
35
|
+
// Quartz events can fill the password field without marking the console user
|
|
36
|
+
// active. loginwindow then times out its activity check and reports a bad
|
|
37
|
+
// password. Declare activity on connection and real console input, never on
|
|
38
|
+
// capture/pings. This also wakes a sleeping connected display.
|
|
39
|
+
static void userActivity(void) {
|
|
40
|
+
double now=NSProcessInfo.processInfo.systemUptime;
|
|
41
|
+
if(activityAssertion!=kIOPMNullAssertionID && now-lastActivity<1) return;
|
|
42
|
+
if(IOPMAssertionDeclareUserActivity(CFSTR("Octopus remote console input"),kIOPMUserActiveLocal,&activityAssertion)==kIOReturnSuccess) lastActivity=now;
|
|
43
|
+
}
|
|
44
|
+
static void releaseActivity(void) {
|
|
45
|
+
if(activityAssertion!=kIOPMNullAssertionID) IOPMAssertionRelease(activityAssertion);
|
|
46
|
+
activityAssertion=kIOPMNullAssertionID; lastActivity=0;
|
|
47
|
+
}
|
|
32
48
|
static void emit(NSDictionary *value) {
|
|
33
49
|
NSData *data = [NSJSONSerialization dataWithJSONObject:value options:0 error:NULL];
|
|
34
50
|
if (!data) return;
|
|
@@ -205,15 +221,18 @@ static void command(NSString *line,RemoteStream *capture) {
|
|
|
205
221
|
}
|
|
206
222
|
if (([op isEqual:@"pointer"] && parts.count==3) || ([op isEqual:@"button"] && parts.count==5) || ([op isEqual:@"wheel"] && parts.count==5)) {
|
|
207
223
|
position(parts[1].doubleValue,parts[2].doubleValue);
|
|
208
|
-
if ([op isEqual:@"button"]
|
|
224
|
+
if ([op isEqual:@"button"] && (parts[3].intValue<0 || parts[3].intValue>2)) { stopping=1; return; }
|
|
225
|
+
userActivity();
|
|
226
|
+
if ([op isEqual:@"button"]) { int button=parts[3].intValue; heldButtons[button]=parts[4].boolValue; mouseEvent(mouseType(button,heldButtons[button]),mouseButton(button)); }
|
|
209
227
|
else if ([op isEqual:@"wheel"]) { mouseEvent(kCGEventMouseMoved,kCGMouseButtonLeft); CGEventRef e=CGEventCreateScrollWheelEvent(NULL,kCGScrollEventUnitPixel,2,-parts[4].intValue,-parts[3].intValue); if(e) { CGEventSetFlags(e,flags()); CGEventPost(kCGHIDEventTap,e); CFRelease(e); } }
|
|
210
228
|
else { CGEventType type=heldButtons[0]?kCGEventLeftMouseDragged:heldButtons[2]?kCGEventRightMouseDragged:heldButtons[1]?kCGEventOtherMouseDragged:kCGEventMouseMoved; mouseEvent(type,heldButtons[2]?kCGMouseButtonRight:heldButtons[1]?kCGMouseButtonCenter:kCGMouseButtonLeft); }
|
|
211
|
-
} else if ([op isEqual:@"key"] && parts.count==3) { NSNumber *key=keyCodes()[parts[1]]; if(key) keyEvent(key.unsignedShortValue,parts[2].boolValue); }
|
|
229
|
+
} else if ([op isEqual:@"key"] && parts.count==3) { NSNumber *key=keyCodes()[parts[1]]; if(key) { userActivity(); keyEvent(key.unsignedShortValue,parts[2].boolValue); } }
|
|
212
230
|
else if (([op isEqual:@"text"] && parts.count==3) || ([op isEqual:@"password"] && parts.count==2)) {
|
|
213
231
|
BOOL password=[op isEqual:@"password"];
|
|
214
232
|
NSData *data=[[NSData alloc] initWithBase64EncodedString:parts[password ? 1 : 2] options:0]; NSString *text=[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
|
|
215
233
|
if(!text || data.length>(password ? 1024 : 16384) || (password && (!data.length || [text rangeOfCharacterFromSet:NSCharacterSet.controlCharacterSet].location!=NSNotFound))) { stopping=1; return; }
|
|
216
234
|
if(password && !screenLocked()) { emit(@{@"kind":@"input-error"}); return; }
|
|
235
|
+
if(text.length) userActivity();
|
|
217
236
|
releaseAll();
|
|
218
237
|
for(NSUInteger i=0;i<text.length;) {
|
|
219
238
|
if(!inputOK()) { stopping=1; return; }
|
|
@@ -252,9 +271,15 @@ int main(int argc,const char *argv[]) {
|
|
|
252
271
|
emit(@{@"capture":@(CGPreflightScreenCaptureAccess()),@"input":@((BOOL)(AXIsProcessTrusted() && CGPreflightPostEventAccess())),@"session":@(sessionPresent()),@"locked":@(screenLocked()),@"lockCapture":@(lockCapture)}); return 0;
|
|
253
272
|
}
|
|
254
273
|
if(argc!=6 || strcmp(argv[1],"serve")!=0 || !inputOK() || !CGPreflightScreenCaptureAccess()) return 1;
|
|
255
|
-
displayID=(CGDirectDisplayID)strtoul(argv[2],NULL,10);
|
|
274
|
+
displayID=(CGDirectDisplayID)strtoul(argv[2],NULL,10);
|
|
256
275
|
int w=atoi(argv[3]),h=atoi(argv[4]),fps=atoi(argv[5]);
|
|
257
|
-
if(!
|
|
276
|
+
if(!CGDisplayIsOnline(displayID) || w<2 || h<2 || w>3840 || h>2160 || w%2 || h%2 || fps<1 || fps>30) return 1;
|
|
277
|
+
userActivity();
|
|
278
|
+
NSDate *wakeDeadline=[NSDate dateWithTimeIntervalSinceNow:2];
|
|
279
|
+
while(!CGDisplayIsActive(displayID) && CGDisplayIsOnline(displayID) && inputOK() && wakeDeadline.timeIntervalSinceNow>0)
|
|
280
|
+
[NSRunLoop.currentRunLoop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:.02]];
|
|
281
|
+
if(!CGDisplayIsActive(displayID) || !inputOK()) { releaseActivity(); return 1; }
|
|
282
|
+
bounds=CGDisplayBounds(displayID);
|
|
258
283
|
heldKeys=[NSMutableSet set]; cursor=bounds.origin; parentPID=getppid(); lastPing=NSDate.date.timeIntervalSince1970;
|
|
259
284
|
signal(SIGTERM,signalStop); signal(SIGINT,signalStop); signal(SIGPIPE,signalStop);
|
|
260
285
|
fcntl(STDIN_FILENO,F_SETFL,O_NONBLOCK);
|
|
@@ -279,7 +304,7 @@ int main(int argc,const char *argv[]) {
|
|
|
279
304
|
NSMutableData *incoming=[NSMutableData data];
|
|
280
305
|
while(!stopping) {
|
|
281
306
|
@autoreleasepool {
|
|
282
|
-
if(getppid()!=parentPID || NSDate.date.timeIntervalSince1970-lastPing>15 || !inputOK() || !CGPreflightScreenCaptureAccess() || !
|
|
307
|
+
if(getppid()!=parentPID || NSDate.date.timeIntervalSince1970-lastPing>15 || !inputOK() || !CGPreflightScreenCaptureAccess() || !CGDisplayIsOnline(displayID) || !CGRectEqualToRect(bounds,CGDisplayBounds(displayID))) break;
|
|
283
308
|
if(screenLocked()) [capture captureLockedFrame];
|
|
284
309
|
else if(capture.streamStopped && capture.stream) {
|
|
285
310
|
capture.streamStopped=NO;
|
|
@@ -297,7 +322,7 @@ int main(int argc,const char *argv[]) {
|
|
|
297
322
|
[NSRunLoop.currentRunLoop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:.005]];
|
|
298
323
|
}
|
|
299
324
|
}
|
|
300
|
-
stopping=1; releaseAll(); [capture.stream stopCaptureWithCompletionHandler:nil]; return 0;
|
|
325
|
+
stopping=1; releaseAll(); releaseActivity(); [capture.stream stopCaptureWithCompletionHandler:nil]; return 0;
|
|
301
326
|
}
|
|
302
327
|
return 1;
|
|
303
328
|
}
|
package/package.json
CHANGED
package/proxy.example.json
CHANGED
|
@@ -7,7 +7,6 @@
|
|
|
7
7
|
"tool": { "kind": "opencode", "command": "/Users/luhuili/.opencode/bin/opencode", "baseUrl": "http://127.0.0.1:4096", "mode": "external", "password": "replace-with-a-random-secret-at-least-20-characters" },
|
|
8
8
|
"desktopRemote": {
|
|
9
9
|
"enabled": true,
|
|
10
|
-
"stunUrls": ["stun:hub.example.com:3478"],
|
|
11
10
|
"totalBitrate": 22000000,
|
|
12
11
|
"videoMaxBitrate": 16000000
|
|
13
12
|
}
|