dsh-mobile 0.8.5 → 0.8.7
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/CHANGELOG.md +37 -1
- package/CONTRIBUTORS.md +16 -4
- package/README.en.md +19 -13
- package/README.md +15 -11
- package/THIRD_PARTY_NOTICES.md +2 -2
- package/apps/mobile/README.md +15 -7
- package/apps/mobile/README.zh-CN.md +15 -7
- package/apps/mobile/release.json +3 -3
- package/assets/brand/plugin-icon.png +0 -0
- package/docs/ATTACH_EXISTING_FRPS.en.md +2 -2
- package/docs/CLIENT_MODULES.en.md +1 -1
- package/docs/CLIENT_MODULES.md +1 -1
- package/docs/CPOLAR_SUBDOMAIN.en.md +1 -1
- package/docs/CPOLAR_SUBDOMAIN.md +1 -1
- package/docs/IROH.en.md +5 -5
- package/docs/IROH.md +6 -6
- package/docs/MOBILE_COMPOSER_TOOLBAR.en.md +2 -2
- package/docs/MOBILE_COMPOSER_TOOLBAR.md +2 -2
- package/docs/MOBILE_PLUGIN_ADAPTATION.en.md +9 -0
- package/docs/MOBILE_PLUGIN_ADAPTATION.md +9 -0
- package/docs/MOBILE_PLUGIN_BASELINE.en.md +1 -1
- package/docs/MOBILE_PLUGIN_BASELINE.md +1 -1
- package/docs/README.md +1 -1
- package/docs/WEB_PUSH.en.md +21 -5
- package/docs/WEB_PUSH.md +21 -5
- package/index.mjs +6 -0
- package/lib/cli.js +104 -9
- package/lib/cli.js.map +1 -1
- package/lib/client.js +406 -16
- package/lib/client.js.map +1 -1
- package/lib/index.d.mts +40 -1
- package/lib/index.mjs +404 -35
- package/lib/index.mjs.map +1 -1
- package/lib/mobile-layout.js +177 -0
- package/lib/mobile-layout.js.map +1 -1
- package/native/iroh-host/README.md +10 -4
- package/package.json +8 -4
- package/packages/question-fixes/package.json +1 -1
package/lib/index.mjs
CHANGED
|
@@ -1210,6 +1210,39 @@ function parseGatewayConfig(raw) {
|
|
|
1210
1210
|
});
|
|
1211
1211
|
}
|
|
1212
1212
|
// src/exec-file.ts
|
|
1213
|
+
/**
|
|
1214
|
+
* Longest stream text attached to a failed subprocess result.
|
|
1215
|
+
*
|
|
1216
|
+
* The text exists for diagnostics only: a caller decides which part is safe to
|
|
1217
|
+
* show. It is bounded here so a chatty child cannot turn one failure into an
|
|
1218
|
+
* unbounded buffer in every future consumer of that rejection.
|
|
1219
|
+
*/
|
|
1220
|
+
const MAX_SUBPROCESS_ERROR_STREAM_CHARS = 8192;
|
|
1221
|
+
function boundedStream(value) {
|
|
1222
|
+
if (typeof value !== "string" || value === "") return void 0;
|
|
1223
|
+
return value.length <= 8192 ? value : value.slice(0, MAX_SUBPROCESS_ERROR_STREAM_CHARS);
|
|
1224
|
+
}
|
|
1225
|
+
/**
|
|
1226
|
+
* Attach the captured streams to a subprocess failure, best effort.
|
|
1227
|
+
*
|
|
1228
|
+
* `execFile` reports a non-zero exit as an Error whose only text is the command
|
|
1229
|
+
* line with stderr merged into `message`; the raw stream strings are handed to
|
|
1230
|
+
* the callback and otherwise discarded, so a caller cannot read them from the
|
|
1231
|
+
* rejection. They are copied onto it when it accepts properties. A frozen,
|
|
1232
|
+
* exotic or primitive rejection value must keep `message` as its diagnostic
|
|
1233
|
+
* rather than turn one failure into a different error, so every write is
|
|
1234
|
+
* guarded and an existing property is never overwritten.
|
|
1235
|
+
*/
|
|
1236
|
+
function attachCapturedStreams(error, stdout, stderr) {
|
|
1237
|
+
if (typeof error !== "object" || error === null) return;
|
|
1238
|
+
const target = error;
|
|
1239
|
+
try {
|
|
1240
|
+
const capturedStderr = boundedStream(stderr);
|
|
1241
|
+
const capturedStdout = boundedStream(stdout);
|
|
1242
|
+
if (target.stderr === void 0 && capturedStderr !== void 0) target.stderr = capturedStderr;
|
|
1243
|
+
if (target.stdout === void 0 && capturedStdout !== void 0) target.stdout = capturedStdout;
|
|
1244
|
+
} catch {}
|
|
1245
|
+
}
|
|
1213
1246
|
/** Capture both output streams even when a desktop host wraps execFile without Node's promisify metadata. */
|
|
1214
1247
|
function execFileText(file, args, options = {}) {
|
|
1215
1248
|
return new Promise((resolve, reject) => {
|
|
@@ -1219,6 +1252,7 @@ function execFileText(file, args, options = {}) {
|
|
|
1219
1252
|
encoding: "utf8"
|
|
1220
1253
|
}, (error, stdout, stderr) => {
|
|
1221
1254
|
if (error !== null) {
|
|
1255
|
+
attachCapturedStreams(error, stdout, stderr);
|
|
1222
1256
|
reject(error);
|
|
1223
1257
|
return;
|
|
1224
1258
|
}
|
|
@@ -2122,6 +2156,8 @@ function requestPathCategory(path) {
|
|
|
2122
2156
|
case "/mobile-access/events": return path;
|
|
2123
2157
|
default:
|
|
2124
2158
|
if (path.startsWith("/api/")) return "/api/*";
|
|
2159
|
+
if (path.startsWith("/plugins/")) return "/plugins/*";
|
|
2160
|
+
if (path.startsWith("/assets/")) return "/assets/*";
|
|
2125
2161
|
if (path.startsWith("/mobile-access/extensions/")) return "/mobile-access/extensions/*";
|
|
2126
2162
|
if (/^\/mobile-access\/compat-[a-f\d]{64}\.js$/u.test(path)) return "/mobile-access/compat.js";
|
|
2127
2163
|
if (path.startsWith("/mobile-access/")) return "/mobile-access/*";
|
|
@@ -3244,7 +3280,7 @@ function forwardableClose(code) {
|
|
|
3244
3280
|
* @param upstreamCookie - gateway's signed upstream cookie; browser cookies are discarded.
|
|
3245
3281
|
* @param options - resolved message, queue, and compression limits.
|
|
3246
3282
|
* @param handshakeTimeoutMs - maximum handshake and close-handshake duration.
|
|
3247
|
-
* @returns
|
|
3283
|
+
* @returns the close handle after both HTTP upgrades are accepted, or rejection before acceptance.
|
|
3248
3284
|
*/
|
|
3249
3285
|
function bridgeCompressedWebSocket(request, client, upstream, head, target, upstreamCookie, options, handshakeTimeoutMs) {
|
|
3250
3286
|
return new Promise((resolve, reject) => {
|
|
@@ -3433,7 +3469,7 @@ function bridgeCompressedWebSocket(request, client, upstream, head, target, upst
|
|
|
3433
3469
|
accepted = true;
|
|
3434
3470
|
clearTimeout(handshakeTimer);
|
|
3435
3471
|
incoming.resume();
|
|
3436
|
-
resolve();
|
|
3472
|
+
resolve({ close: beginClose });
|
|
3437
3473
|
});
|
|
3438
3474
|
} catch (error) {
|
|
3439
3475
|
stop(error instanceof Error ? error : /* @__PURE__ */ new Error("Client WebSocket upgrade failed"));
|
|
@@ -5703,6 +5739,7 @@ const MOBILE_PUSH_PREFERENCES_PATH = `${AUTH_PREFIX}/push/preferences`;
|
|
|
5703
5739
|
const MOBILE_PUSH_DISABLE_PATH = `${AUTH_PREFIX}/push/disable`;
|
|
5704
5740
|
const MOBILE_PUSH_TEST_PATH = `${AUTH_PREFIX}/push/test`;
|
|
5705
5741
|
const MOBILE_PUSH_RESOLVE_PATH = `${AUTH_PREFIX}/push/resolve`;
|
|
5742
|
+
const BACKGROUND_EVENT_PATH = `${AUTH_PREFIX}/background-events`;
|
|
5706
5743
|
const MOBILE_PUSH_PREFERENCE_KEYS = [
|
|
5707
5744
|
"question",
|
|
5708
5745
|
"plan",
|
|
@@ -6448,6 +6485,12 @@ function sanitizeRequestHeaders(request, upstream) {
|
|
|
6448
6485
|
const value = request.headers[name];
|
|
6449
6486
|
if (value !== void 0) headers[name] = value;
|
|
6450
6487
|
}
|
|
6488
|
+
for (const name of Object.keys(request.headers)) {
|
|
6489
|
+
const lower = name.toLowerCase();
|
|
6490
|
+
if (!lower.startsWith("x-dsh-") || lower.startsWith("x-dsh-mobile-") || lower === "x-dsh-auth-token") continue;
|
|
6491
|
+
const value = request.headers[name];
|
|
6492
|
+
if (value !== void 0) headers[name] = Array.isArray(value) ? value[0] : value;
|
|
6493
|
+
}
|
|
6451
6494
|
return headers;
|
|
6452
6495
|
}
|
|
6453
6496
|
const BLOCKED_RESPONSE_HEADERS = /* @__PURE__ */ new Set([
|
|
@@ -7601,6 +7644,7 @@ var MobileAccessGateway = class {
|
|
|
7601
7644
|
const computerImage = request.method === "GET" && target.decodedPathname === `/mobile-access/computer-image`;
|
|
7602
7645
|
const workspaceDirectories = (request.method === "GET" || request.method === "POST") && target.decodedPathname === `/mobile-access/workspace-directories`;
|
|
7603
7646
|
const clientModules = target.decodedPathname === `${AUTH_PREFIX}/client-modules`;
|
|
7647
|
+
const backgroundEvents = target.decodedPathname === BACKGROUND_EVENT_PATH;
|
|
7604
7648
|
const mobilePushRoute = [
|
|
7605
7649
|
MOBILE_PUSH_CONFIG_PATH,
|
|
7606
7650
|
MOBILE_PUSH_SUBSCRIPTION_PATH,
|
|
@@ -7634,7 +7678,7 @@ var MobileAccessGateway = class {
|
|
|
7634
7678
|
fallback: void 0,
|
|
7635
7679
|
digest: requestedCompatRevision
|
|
7636
7680
|
} : void 0;
|
|
7637
|
-
if (customAsset === void 0 && requestedMobileBootBatch === void 0 && !computerImages && !computerImage && !workspaceDirectories && !clientModules && !mobilePushRoute && extensionTarget(target.decodedPathname) === void 0 && (target.decodedPathname === "/mobile-access" || target.decodedPathname.startsWith(`/mobile-access/`))) throw new HttpError(404, "not_found");
|
|
7681
|
+
if (customAsset === void 0 && requestedMobileBootBatch === void 0 && !computerImages && !computerImage && !workspaceDirectories && !clientModules && !backgroundEvents && !mobilePushRoute && extensionTarget(target.decodedPathname) === void 0 && (target.decodedPathname === "/mobile-access" || target.decodedPathname.startsWith(`/mobile-access/`))) throw new HttpError(404, "not_found");
|
|
7638
7682
|
if (![
|
|
7639
7683
|
"GET",
|
|
7640
7684
|
"HEAD",
|
|
@@ -7675,6 +7719,11 @@ var MobileAccessGateway = class {
|
|
|
7675
7719
|
await this.handleMobilePushRequest(target, request, response, authorization);
|
|
7676
7720
|
return;
|
|
7677
7721
|
}
|
|
7722
|
+
if (backgroundEvents) {
|
|
7723
|
+
if (request.method !== "GET" || target.search !== "") throw new HttpError(request.method === "GET" ? 400 : 405, request.method === "GET" ? "bad_request" : "method_not_allowed");
|
|
7724
|
+
this.openBackgroundEventStream(request, response, authorization);
|
|
7725
|
+
return;
|
|
7726
|
+
}
|
|
7678
7727
|
if (clientModules) {
|
|
7679
7728
|
if (target.search !== "") throw new HttpError(400, "bad_request");
|
|
7680
7729
|
if (request.method !== "GET" && request.method !== "POST") throw new HttpError(405, "method_not_allowed");
|
|
@@ -9415,6 +9464,54 @@ var MobileAccessGateway = class {
|
|
|
9415
9464
|
request.once("aborted", close);
|
|
9416
9465
|
response.once("close", close);
|
|
9417
9466
|
}
|
|
9467
|
+
/**
|
|
9468
|
+
* Native background notifications need only Host task and revocation events.
|
|
9469
|
+
* They use the paired session cookie but no mobile document view, because the
|
|
9470
|
+
* App has no live WebView while this stream is serving a suspended page.
|
|
9471
|
+
*/
|
|
9472
|
+
openBackgroundEventStream(request, response, authorization) {
|
|
9473
|
+
const operation = this.allocateRequest(authorization, response, {});
|
|
9474
|
+
let closed = false;
|
|
9475
|
+
let heartbeat;
|
|
9476
|
+
const close = () => {
|
|
9477
|
+
if (closed) return;
|
|
9478
|
+
closed = true;
|
|
9479
|
+
if (heartbeat !== void 0) clearInterval(heartbeat);
|
|
9480
|
+
this.taskEventListeners.delete(sendTask);
|
|
9481
|
+
const deviceListeners = this.deviceEventListeners.get(authorization.deviceId);
|
|
9482
|
+
deviceListeners?.delete(sendDevice);
|
|
9483
|
+
if (deviceListeners?.size === 0) this.deviceEventListeners.delete(authorization.deviceId);
|
|
9484
|
+
request.removeListener("aborted", close);
|
|
9485
|
+
response.removeListener("close", close);
|
|
9486
|
+
operation.release();
|
|
9487
|
+
};
|
|
9488
|
+
const sendTask = (payload) => {
|
|
9489
|
+
if (closed || response.destroyed || response.writableEnded) return;
|
|
9490
|
+
response.write(`event: task-notify\ndata: ${payload}\n\n`);
|
|
9491
|
+
};
|
|
9492
|
+
const sendDevice = (payload) => {
|
|
9493
|
+
if (closed || response.destroyed || response.writableEnded) return;
|
|
9494
|
+
response.write(`event: device-revoked\ndata: ${payload}\n\n`);
|
|
9495
|
+
};
|
|
9496
|
+
setSecurityHeaders(response, this.tlsEnabled);
|
|
9497
|
+
response.writeHead(200, {
|
|
9498
|
+
"Content-Type": "text/event-stream; charset=utf-8",
|
|
9499
|
+
"Cache-Control": "no-store",
|
|
9500
|
+
Connection: "keep-alive",
|
|
9501
|
+
"X-Accel-Buffering": "no"
|
|
9502
|
+
});
|
|
9503
|
+
response.write("retry: 2000\n: ready\n\n");
|
|
9504
|
+
this.taskEventListeners.add(sendTask);
|
|
9505
|
+
const deviceListeners = this.deviceEventListeners.get(authorization.deviceId) ?? /* @__PURE__ */ new Set();
|
|
9506
|
+
deviceListeners.add(sendDevice);
|
|
9507
|
+
this.deviceEventListeners.set(authorization.deviceId, deviceListeners);
|
|
9508
|
+
heartbeat = setInterval(() => {
|
|
9509
|
+
if (!closed && !response.destroyed && !response.writableEnded) response.write(": heartbeat\n\n");
|
|
9510
|
+
}, EXTENSION_EVENT_HEARTBEAT_MS);
|
|
9511
|
+
heartbeat.unref();
|
|
9512
|
+
request.once("aborted", close);
|
|
9513
|
+
response.once("close", close);
|
|
9514
|
+
}
|
|
9418
9515
|
async readUpgradeResponse(upstream, expectedAccept) {
|
|
9419
9516
|
return new Promise((resolve, reject) => {
|
|
9420
9517
|
let buffer = Buffer.alloc(0);
|
|
@@ -9528,9 +9625,31 @@ var MobileAccessGateway = class {
|
|
|
9528
9625
|
client.destroy();
|
|
9529
9626
|
upstream.destroy();
|
|
9530
9627
|
};
|
|
9628
|
+
let closeCompressedBridge;
|
|
9629
|
+
const finishGracefully = () => {
|
|
9630
|
+
if (closeCompressedBridge !== void 0) closeCompressedBridge(1e3, "session expired");
|
|
9631
|
+
else {
|
|
9632
|
+
upstream.unpipe(client);
|
|
9633
|
+
client.unpipe(upstream);
|
|
9634
|
+
upstream.pause();
|
|
9635
|
+
const sendCloseFrame = (socket) => {
|
|
9636
|
+
if (socket.destroyed || !socket.writable) return;
|
|
9637
|
+
try {
|
|
9638
|
+
socket.write(Buffer.from([
|
|
9639
|
+
136,
|
|
9640
|
+
2,
|
|
9641
|
+
3,
|
|
9642
|
+
232
|
|
9643
|
+
]));
|
|
9644
|
+
} catch {}
|
|
9645
|
+
};
|
|
9646
|
+
sendCloseFrame(client);
|
|
9647
|
+
}
|
|
9648
|
+
setTimeout(closeBoth, 50).unref();
|
|
9649
|
+
};
|
|
9531
9650
|
client.on("error", closeBoth);
|
|
9532
9651
|
upstream.on("error", closeBoth);
|
|
9533
|
-
const timer = setTimeout(
|
|
9652
|
+
const timer = setTimeout(finishGracefully, Math.max(1, authorization.expiresAt - Date.now()));
|
|
9534
9653
|
timer.unref();
|
|
9535
9654
|
const record = Object.freeze({
|
|
9536
9655
|
...authorization,
|
|
@@ -9559,7 +9678,10 @@ var MobileAccessGateway = class {
|
|
|
9559
9678
|
const compressionTarget = new URL(this.config.upstreamOrigin.href);
|
|
9560
9679
|
compressionTarget.pathname = target.pathname;
|
|
9561
9680
|
compressionTarget.search = target.search;
|
|
9562
|
-
await bridgeCompressedWebSocket(request, client, upstream, head, compressionTarget, upstreamCookie, this.config.websocketCompression, this.config.upstreamTimeoutMs);
|
|
9681
|
+
const bridge = await bridgeCompressedWebSocket(request, client, upstream, head, compressionTarget, upstreamCookie, this.config.websocketCompression, this.config.upstreamTimeoutMs);
|
|
9682
|
+
closeCompressedBridge = (code, reason) => {
|
|
9683
|
+
bridge.close(code, reason);
|
|
9684
|
+
};
|
|
9563
9685
|
return;
|
|
9564
9686
|
}
|
|
9565
9687
|
await new Promise((resolve, reject) => {
|
|
@@ -9903,6 +10025,26 @@ var MemoryDeviceStore = class {
|
|
|
9903
10025
|
return structuredClone(this.snapshot);
|
|
9904
10026
|
}
|
|
9905
10027
|
};
|
|
10028
|
+
// src/mobile-push/service.ts
|
|
10029
|
+
/**
|
|
10030
|
+
* Small Cordis service exposed to trusted Host plugins. The provider owns the
|
|
10031
|
+
* runtime lifetime and all recipient, category, session, and expiry checks.
|
|
10032
|
+
*/
|
|
10033
|
+
var MobilePushService = class extends Service {
|
|
10034
|
+
send;
|
|
10035
|
+
constructor(ctx, send) {
|
|
10036
|
+
super(ctx, "mobilePush");
|
|
10037
|
+
this.send = send;
|
|
10038
|
+
}
|
|
10039
|
+
/**
|
|
10040
|
+
* Queue a notification for enabled web-push recipients.
|
|
10041
|
+
* @param input - Session, text, optional category and optional expiry.
|
|
10042
|
+
* @returns The number of newly queued recipient deliveries and the applied expiry.
|
|
10043
|
+
*/
|
|
10044
|
+
async notify(input) {
|
|
10045
|
+
return await this.send(input);
|
|
10046
|
+
}
|
|
10047
|
+
};
|
|
9906
10048
|
// src/managed-files.ts
|
|
9907
10049
|
/** Refuse link-shaped ancestors inside an owned state root before accessing a child. */
|
|
9908
10050
|
async function assertManagedParents(root, target, prefix) {
|
|
@@ -15961,6 +16103,79 @@ var IrohIdentityProbeError = class extends Error {
|
|
|
15961
16103
|
function publicStatus$2(status) {
|
|
15962
16104
|
return Object.freeze({ ...status });
|
|
15963
16105
|
}
|
|
16106
|
+
function diagnosticText(value, dataDir) {
|
|
16107
|
+
const text = value.toString("utf8").replaceAll(dataDir, "<data-dir>").trim().replace(/\s+/gu, " ");
|
|
16108
|
+
return text === "" ? void 0 : text.slice(0, IDENTITY_DIAGNOSTIC_MAX_BYTES);
|
|
16109
|
+
}
|
|
16110
|
+
/** The wrapper `tls-prepare` prints when it discards the underlying native cause. */
|
|
16111
|
+
const HOST_TLS_PREPARATION_FAILURE = "Error: host TLS preparation failed";
|
|
16112
|
+
/**
|
|
16113
|
+
* Whether the wrapper is the only explanation stderr carries.
|
|
16114
|
+
*
|
|
16115
|
+
* Compare lines, never the whole buffer: the probe collapses whitespace for
|
|
16116
|
+
* reporting, and a preceding warning or a CRLF terminator must not hide the
|
|
16117
|
+
* fact that no cause chain was printed. A `Caused by:` section means the
|
|
16118
|
+
* native explanation already reached us, so the extra host run cannot add
|
|
16119
|
+
* anything the caller does not already have.
|
|
16120
|
+
*/
|
|
16121
|
+
function isBareHostTlsPreparationFailure(value, dataDir) {
|
|
16122
|
+
const lines = value.toString("utf8").replaceAll(dataDir, "<data-dir>").split(/\r?\n/gu).map((line) => line.trim()).filter((line) => line !== "");
|
|
16123
|
+
return lines.includes(HOST_TLS_PREPARATION_FAILURE) && !lines.some((line) => /^Caused by:/u.test(line));
|
|
16124
|
+
}
|
|
16125
|
+
/**
|
|
16126
|
+
* Ask the host entry point for the cause chain when `tls-prepare` reports only
|
|
16127
|
+
* its top-level wrapper. The command is local-only and bounded; it never
|
|
16128
|
+
* downloads a component or enables relay/discovery.
|
|
16129
|
+
*/
|
|
16130
|
+
async function hostIdentityDiagnostic(executable, dataDir, launch, onSpawn) {
|
|
16131
|
+
const args = [
|
|
16132
|
+
"--lang",
|
|
16133
|
+
"en",
|
|
16134
|
+
"host",
|
|
16135
|
+
"--data-dir",
|
|
16136
|
+
dataDir,
|
|
16137
|
+
"--local-only"
|
|
16138
|
+
];
|
|
16139
|
+
const child = launch === void 0 ? spawn(executable, args, {
|
|
16140
|
+
stdio: [
|
|
16141
|
+
"pipe",
|
|
16142
|
+
"pipe",
|
|
16143
|
+
"pipe"
|
|
16144
|
+
],
|
|
16145
|
+
windowsHide: true
|
|
16146
|
+
}) : launch(executable, args);
|
|
16147
|
+
onSpawn?.(child);
|
|
16148
|
+
let stderrOutput = Buffer.alloc(0);
|
|
16149
|
+
let settled = false;
|
|
16150
|
+
let deadline;
|
|
16151
|
+
return await new Promise((resolve) => {
|
|
16152
|
+
const finish = (value) => {
|
|
16153
|
+
if (settled) return;
|
|
16154
|
+
settled = true;
|
|
16155
|
+
if (deadline !== void 0) clearTimeout(deadline);
|
|
16156
|
+
resolve(value);
|
|
16157
|
+
};
|
|
16158
|
+
deadline = setTimeout(() => {
|
|
16159
|
+
terminateRemoteProcess(child).then(() => {
|
|
16160
|
+
finish(diagnosticText(stderrOutput, dataDir));
|
|
16161
|
+
}, () => {
|
|
16162
|
+
finish(diagnosticText(stderrOutput, dataDir));
|
|
16163
|
+
});
|
|
16164
|
+
}, 5e3);
|
|
16165
|
+
deadline.unref?.();
|
|
16166
|
+
child.stderr?.on("data", (chunk) => {
|
|
16167
|
+
const remaining = IDENTITY_DIAGNOSTIC_MAX_BYTES - stderrOutput.length;
|
|
16168
|
+
if (remaining > 0) stderrOutput = Buffer.concat([stderrOutput, chunk.subarray(0, remaining)]);
|
|
16169
|
+
});
|
|
16170
|
+
child.once("error", () => {
|
|
16171
|
+
finish(diagnosticText(stderrOutput, dataDir));
|
|
16172
|
+
});
|
|
16173
|
+
child.once("close", () => {
|
|
16174
|
+
finish(diagnosticText(stderrOutput, dataDir));
|
|
16175
|
+
});
|
|
16176
|
+
child.stdin?.end();
|
|
16177
|
+
});
|
|
16178
|
+
}
|
|
15964
16179
|
/**
|
|
15965
16180
|
* Prepare the sidecar's private identity/TLS state and resolve its endpoint.
|
|
15966
16181
|
*
|
|
@@ -16030,6 +16245,15 @@ async function defaultResolveIdentity(executable, dataDir, options = {}) {
|
|
|
16030
16245
|
if (settled) return;
|
|
16031
16246
|
settled = true;
|
|
16032
16247
|
const probeDiagnostic = diagnostic();
|
|
16248
|
+
if (code !== 0 && probeDiagnostic !== void 0 && isBareHostTlsPreparationFailure(stderrOutput, dataDir)) {
|
|
16249
|
+
hostIdentityDiagnostic(executable, dataDir, options.launch, options.onSpawn).then((detail) => {
|
|
16250
|
+
const resolvedDiagnostic = detail ?? probeDiagnostic;
|
|
16251
|
+
reject(new IrohIdentityProbeError(/private (?:Windows )?data directory is not secure|private path ACL grants/iu.test(resolvedDiagnostic) ? "iroh_data_directory_insecure" : "iroh_identity_invalid", resolvedDiagnostic));
|
|
16252
|
+
}, () => {
|
|
16253
|
+
reject(new IrohIdentityProbeError("iroh_identity_invalid", probeDiagnostic));
|
|
16254
|
+
});
|
|
16255
|
+
return;
|
|
16256
|
+
}
|
|
16033
16257
|
if (code !== 0 && probeDiagnostic !== void 0 && /private (?:Windows )?data directory is not secure|private path ACL grants/iu.test(probeDiagnostic)) {
|
|
16034
16258
|
reject(new IrohIdentityProbeError("iroh_data_directory_insecure", probeDiagnostic));
|
|
16035
16259
|
return;
|
|
@@ -16939,23 +17163,36 @@ function remoteSuffix(origin) {
|
|
|
16939
17163
|
return "地址格式无效";
|
|
16940
17164
|
}
|
|
16941
17165
|
}
|
|
17166
|
+
/**
|
|
17167
|
+
* Read-only PowerShell that reports whether both managed LAN rules are usable.
|
|
17168
|
+
*
|
|
17169
|
+
* The verdict mirrors `windowsFirewallSetupScript`'s post-condition: enabled,
|
|
17170
|
+
* inbound, allow, and `Profile` exactly `Any`. A Domain/Private-scoped rule is
|
|
17171
|
+
* active only on those networks, so reporting it as ready would tell a user on
|
|
17172
|
+
* a Public network that nothing needs fixing. The script only reads: it never
|
|
17173
|
+
* enables, creates or deletes a rule, and the caller keeps its 3s budget.
|
|
17174
|
+
* @param port - managed LAN listener port the rules must cover.
|
|
17175
|
+
*/
|
|
17176
|
+
function windowsFirewallProbeScript(port) {
|
|
17177
|
+
return [
|
|
17178
|
+
"$specs = @(@{ Name = 'DSH Mobile HTTPS'; Protocol = 'TCP' }, @{ Name = 'DSH Mobile Discovery'; Protocol = 'UDP' })",
|
|
17179
|
+
"$ready = $true",
|
|
17180
|
+
"$specs | ForEach-Object {",
|
|
17181
|
+
" $spec = $_",
|
|
17182
|
+
" $rule = Get-NetFirewallRule -DisplayName $spec.Name -ErrorAction SilentlyContinue | Where-Object { $_.Enabled -eq 'True' -and $_.Direction -eq 'Inbound' -and $_.Action -eq 'Allow' -and $_.Profile -eq 'Any' } | Select-Object -First 1",
|
|
17183
|
+
" if ($null -eq $rule) { $ready = $false; return }",
|
|
17184
|
+
" $filters = @($rule | Get-NetFirewallPortFilter -ErrorAction SilentlyContinue)",
|
|
17185
|
+
` $matching = @($filters | Where-Object { $_.Protocol -eq $spec.Protocol -and ($_.LocalPort -eq 'Any' -or $_.LocalPort -eq '${String(port)}') })`,
|
|
17186
|
+
" if ($matching.Count -eq 0) { $ready = $false }",
|
|
17187
|
+
"}",
|
|
17188
|
+
"if ($ready) { 'ready' } else { 'missing' }"
|
|
17189
|
+
].join("; ");
|
|
17190
|
+
}
|
|
16942
17191
|
function defaultFirewallProbe(platform = process.platform) {
|
|
16943
17192
|
return async (port) => {
|
|
16944
17193
|
if (platform !== "win32") return { state: "not-applicable" };
|
|
16945
17194
|
if (port === void 0) return { state: "unknown" };
|
|
16946
|
-
const script =
|
|
16947
|
-
"$specs = @(@{ Name = 'DSH Mobile HTTPS'; Protocol = 'TCP' }, @{ Name = 'DSH Mobile Discovery'; Protocol = 'UDP' })",
|
|
16948
|
-
"$ready = $true",
|
|
16949
|
-
"$specs | ForEach-Object {",
|
|
16950
|
-
" $spec = $_",
|
|
16951
|
-
" $rule = Get-NetFirewallRule -DisplayName $spec.Name -ErrorAction SilentlyContinue | Where-Object { $_.Enabled -eq 'True' -and $_.Direction -eq 'Inbound' -and $_.Action -eq 'Allow' } | Select-Object -First 1",
|
|
16952
|
-
" if ($null -eq $rule) { $ready = $false; return }",
|
|
16953
|
-
" $filters = @($rule | Get-NetFirewallPortFilter -ErrorAction SilentlyContinue)",
|
|
16954
|
-
` $matching = @($filters | Where-Object { $_.Protocol -eq $spec.Protocol -and ($_.LocalPort -eq 'Any' -or $_.LocalPort -eq '${String(port)}') })`,
|
|
16955
|
-
" if ($matching.Count -eq 0) { $ready = $false }",
|
|
16956
|
-
"}",
|
|
16957
|
-
"if ($ready) { 'ready' } else { 'missing' }"
|
|
16958
|
-
].join("; ");
|
|
17195
|
+
const script = windowsFirewallProbeScript(port);
|
|
16959
17196
|
try {
|
|
16960
17197
|
return { state: (await execFileText("powershell.exe", [
|
|
16961
17198
|
"-NoProfile",
|
|
@@ -22109,7 +22346,8 @@ function validSource(value) {
|
|
|
22109
22346
|
"lifetime",
|
|
22110
22347
|
"turn",
|
|
22111
22348
|
"callId",
|
|
22112
|
-
"goal"
|
|
22349
|
+
"goal",
|
|
22350
|
+
"custom"
|
|
22113
22351
|
])) return false;
|
|
22114
22352
|
return [
|
|
22115
22353
|
value.sourceId,
|
|
@@ -22126,7 +22364,7 @@ function validSource(value) {
|
|
|
22126
22364
|
"live",
|
|
22127
22365
|
"projected",
|
|
22128
22366
|
"committed"
|
|
22129
|
-
].includes(String(value.lifetime)) && (value.turn === void 0 || Number.isSafeInteger(value.turn)) && (value.callId === void 0 || typeof value.callId === "string") && (value.goal === void 0 || object(value.goal) && only(value.goal, ["id", "revision"]) && typeof value.goal.id === "string" && Number.isSafeInteger(value.goal.revision));
|
|
22367
|
+
].includes(String(value.lifetime)) && (value.turn === void 0 || Number.isSafeInteger(value.turn)) && (value.callId === void 0 || typeof value.callId === "string") && (value.goal === void 0 || object(value.goal) && only(value.goal, ["id", "revision"]) && typeof value.goal.id === "string" && Number.isSafeInteger(value.goal.revision)) && (value.custom === void 0 || object(value.custom) && only(value.custom, ["title", "body"]) && typeof value.custom.title === "string" && value.custom.title.length > 0 && value.custom.title.length <= 80 && !/[\u0000-\u001f\u007f]/u.test(value.custom.title) && typeof value.custom.body === "string" && value.custom.body.length > 0 && value.custom.body.length <= 200 && !/[\u0000-\u001f\u007f]/u.test(value.custom.body));
|
|
22130
22368
|
}
|
|
22131
22369
|
function scopedCounts(rows, scope, maximum = 1024) {
|
|
22132
22370
|
const counts = /* @__PURE__ */ new Map();
|
|
@@ -22742,10 +22980,11 @@ function validateNavigate(navigate, category) {
|
|
|
22742
22980
|
if (typeof navigate !== "string" || !/^\/\?mobile-push=[A-Za-z0-9_-]{1,256}$/u.test(navigate)) throw new TypeError("push_navigation_invalid");
|
|
22743
22981
|
}
|
|
22744
22982
|
/** Build Apple's declarative Web Push schema with generic, locale-owned text. */
|
|
22745
|
-
function buildDeclarativePayload(category, locale, navigate, tag) {
|
|
22983
|
+
function buildDeclarativePayload(category, locale, navigate, tag, custom) {
|
|
22746
22984
|
validateNavigate(navigate, category);
|
|
22747
22985
|
if (!(locale in messages) || !(category in messages[locale]) || typeof tag !== "string" || !/^[A-Za-z0-9._:-]{1,128}$/u.test(tag)) throw new TypeError("push_payload_invalid");
|
|
22748
|
-
|
|
22986
|
+
if (custom !== void 0 && (typeof custom.title !== "string" || custom.title.length === 0 || custom.title.length > 80 || typeof custom.body !== "string" || custom.body.length === 0 || custom.body.length > 200)) throw new TypeError("push_payload_invalid");
|
|
22987
|
+
const [title, body] = custom === void 0 ? messages[locale][category] : [custom.title, custom.body];
|
|
22749
22988
|
const payload = JSON.stringify({
|
|
22750
22989
|
web_push: 8030,
|
|
22751
22990
|
notification: {
|
|
@@ -23085,6 +23324,12 @@ function sameKey(a, b) {
|
|
|
23085
23324
|
function isPreferenceEnabled(preferences, category) {
|
|
23086
23325
|
return preferences[category] === true;
|
|
23087
23326
|
}
|
|
23327
|
+
function notificationText(value, maximum, label) {
|
|
23328
|
+
if (typeof value !== "string") throw new TypeError(`push_${label}_invalid`);
|
|
23329
|
+
const text = value.trim().replace(/[\u0000-\u001f\u007f]+/gu, " ");
|
|
23330
|
+
if (text.length === 0 || text.length > maximum) throw new TypeError(`push_${label}_invalid`);
|
|
23331
|
+
return text;
|
|
23332
|
+
}
|
|
23088
23333
|
function pendingHas(snapshot, intent) {
|
|
23089
23334
|
return snapshot.pending.some((item) => item.id === intent.id && sameKey(item.key, intent.key));
|
|
23090
23335
|
}
|
|
@@ -23198,6 +23443,51 @@ var PushDelivery = class {
|
|
|
23198
23443
|
this.#kick();
|
|
23199
23444
|
});
|
|
23200
23445
|
}
|
|
23446
|
+
/**
|
|
23447
|
+
* Queue trusted Host-plugin text for every enabled web-push recipient that
|
|
23448
|
+
* opted into the selected notification category. The source keeps its own
|
|
23449
|
+
* short expiry; a restart may still retry it only inside that window.
|
|
23450
|
+
*/
|
|
23451
|
+
async notifyCustom(input) {
|
|
23452
|
+
if (!this.#started || this.#closed || !this.#admitting) throw new Error("push_unavailable");
|
|
23453
|
+
const sessionId = typeof input.sessionId === "string" ? input.sessionId.trim() : "";
|
|
23454
|
+
if (sessionId === "" || sessionId.length > 4096) throw new TypeError("push_session_id_invalid");
|
|
23455
|
+
const title = notificationText(input.title, 80, "title");
|
|
23456
|
+
const body = notificationText(input.body, 200, "body");
|
|
23457
|
+
const category = input.category ?? "response";
|
|
23458
|
+
if (![
|
|
23459
|
+
"question",
|
|
23460
|
+
"plan",
|
|
23461
|
+
"approval",
|
|
23462
|
+
"response",
|
|
23463
|
+
"goal",
|
|
23464
|
+
"failure"
|
|
23465
|
+
].includes(category)) throw new TypeError("push_category_invalid");
|
|
23466
|
+
const now = this.#now();
|
|
23467
|
+
const maximumExpiry = now + OUTCOME_TTL_MS;
|
|
23468
|
+
if (input.expiresAt !== void 0 && (!Number.isSafeInteger(input.expiresAt) || input.expiresAt <= now)) throw new TypeError("push_expiry_invalid");
|
|
23469
|
+
const expiresAt = Math.min(input.expiresAt ?? maximumExpiry, maximumExpiry);
|
|
23470
|
+
if (!await this.#host.sessionExists(sessionId)) throw new Error("push_session_unavailable");
|
|
23471
|
+
const source = {
|
|
23472
|
+
sourceId: `custom:${randomUUID()}`,
|
|
23473
|
+
sessionId,
|
|
23474
|
+
targetSessionId: sessionId,
|
|
23475
|
+
category,
|
|
23476
|
+
createdAt: now,
|
|
23477
|
+
expiresAt,
|
|
23478
|
+
lifetime: "committed",
|
|
23479
|
+
custom: {
|
|
23480
|
+
title,
|
|
23481
|
+
body
|
|
23482
|
+
}
|
|
23483
|
+
};
|
|
23484
|
+
const queued = await this.#admit(source);
|
|
23485
|
+
this.#kick();
|
|
23486
|
+
return {
|
|
23487
|
+
queued,
|
|
23488
|
+
expiresAt
|
|
23489
|
+
};
|
|
23490
|
+
}
|
|
23201
23491
|
async resolve(owner, reference) {
|
|
23202
23492
|
if (this.#closed || !reference) return void 0;
|
|
23203
23493
|
let snapshot;
|
|
@@ -23237,14 +23527,15 @@ var PushDelivery = class {
|
|
|
23237
23527
|
this.#origins.clear();
|
|
23238
23528
|
}
|
|
23239
23529
|
async #admit(source) {
|
|
23240
|
-
if (!this.#admitting || source.expiresAt <= this.#now()) return;
|
|
23530
|
+
if (!this.#admitting || source.expiresAt <= this.#now()) return 0;
|
|
23241
23531
|
let eligible = false;
|
|
23242
23532
|
try {
|
|
23243
|
-
eligible = this.#host.available && await this.#host.eligible(source);
|
|
23533
|
+
eligible = source.custom === void 0 ? this.#host.available && await this.#host.eligible(source) : this.#host.available && await this.#host.sessionExists(source.sessionId);
|
|
23244
23534
|
} catch {
|
|
23245
23535
|
eligible = false;
|
|
23246
23536
|
}
|
|
23247
|
-
if (!eligible || source.expiresAt <= this.#now()) return;
|
|
23537
|
+
if (!eligible || source.expiresAt <= this.#now()) return 0;
|
|
23538
|
+
let queued = 0;
|
|
23248
23539
|
for (const origin of this.#origins.values()) {
|
|
23249
23540
|
if (!origin.active || this.#closed) continue;
|
|
23250
23541
|
const devices = await this.#readAuthorized(origin);
|
|
@@ -23253,7 +23544,7 @@ var PushDelivery = class {
|
|
|
23253
23544
|
try {
|
|
23254
23545
|
snapshot = this.#registry.snapshot();
|
|
23255
23546
|
} catch {
|
|
23256
|
-
return;
|
|
23547
|
+
return queued;
|
|
23257
23548
|
}
|
|
23258
23549
|
const registrations = snapshot.registrations.filter((reg) => reg.scopeId === origin.scopeId && reg.origin === origin.origin && devices.has(reg.deviceId) && reg.enabled && reg.health === "ready" && isPreferenceEnabled(reg.preferences, source.category) && snapshot.keys?.fingerprint === reg.keyFingerprint);
|
|
23259
23550
|
if (registrations.length === 0) continue;
|
|
@@ -23282,6 +23573,7 @@ var PushDelivery = class {
|
|
|
23282
23573
|
attempts: 0,
|
|
23283
23574
|
nextAttemptAt: this.#now()
|
|
23284
23575
|
});
|
|
23576
|
+
queued += 1;
|
|
23285
23577
|
}
|
|
23286
23578
|
return {
|
|
23287
23579
|
...clean,
|
|
@@ -23290,6 +23582,7 @@ var PushDelivery = class {
|
|
|
23290
23582
|
});
|
|
23291
23583
|
}
|
|
23292
23584
|
this.#kick();
|
|
23585
|
+
return queued;
|
|
23293
23586
|
}
|
|
23294
23587
|
async #reconcileKnownScopes() {
|
|
23295
23588
|
let snapshot;
|
|
@@ -23474,7 +23767,7 @@ var PushDelivery = class {
|
|
|
23474
23767
|
await this.#removeIntent(intent);
|
|
23475
23768
|
return;
|
|
23476
23769
|
}
|
|
23477
|
-
const payload = buildDeclarativePayload(intent.source.category, registration.locale, `/?mobile-push=${reference}`, intent.source.sourceId.replace(/[^A-Za-z0-9._:-]/gu, "_").slice(0, 128) || "push");
|
|
23770
|
+
const payload = buildDeclarativePayload(intent.source.category, registration.locale, `/?mobile-push=${reference}`, intent.source.sourceId.replace(/[^A-Za-z0-9._:-]/gu, "_").slice(0, 128) || "push", intent.source.custom);
|
|
23478
23771
|
let result;
|
|
23479
23772
|
try {
|
|
23480
23773
|
result = await this.#sender.send({
|
|
@@ -23584,6 +23877,11 @@ var PushDelivery = class {
|
|
|
23584
23877
|
/** Maps the Host tri-state for durable-work decisions; boolean eligible() would collapse transient uncertainty into denial. */
|
|
23585
23878
|
async #sourceGuard(source) {
|
|
23586
23879
|
if (!this.#host.available) return "unverifiable";
|
|
23880
|
+
if (source.custom !== void 0) try {
|
|
23881
|
+
return await this.#host.sessionExists(source.sessionId) ? "allowed" : "denied";
|
|
23882
|
+
} catch {
|
|
23883
|
+
return "unverifiable";
|
|
23884
|
+
}
|
|
23587
23885
|
let result;
|
|
23588
23886
|
try {
|
|
23589
23887
|
result = await this.#host.reconcile(source);
|
|
@@ -23937,6 +24235,10 @@ var PushGatewayCoordinator = class {
|
|
|
23937
24235
|
installationId
|
|
23938
24236
|
});
|
|
23939
24237
|
}
|
|
24238
|
+
async notifyCustom(input) {
|
|
24239
|
+
if (!this.host.available) throw new Error("push_unavailable");
|
|
24240
|
+
return await this.delivery.notifyCustom(input);
|
|
24241
|
+
}
|
|
23940
24242
|
async resolve(owner, reference) {
|
|
23941
24243
|
if (!this.host.available || !await this.authorize(owner, true)) return void 0;
|
|
23942
24244
|
return this.delivery.resolve(owner, reference);
|
|
@@ -24059,16 +24361,71 @@ const MAX_MANAGED_PORT = 65535;
|
|
|
24059
24361
|
function assertManagedPort(port, name) {
|
|
24060
24362
|
if (!Number.isSafeInteger(port) || port < MIN_MANAGED_PORT || port > MAX_MANAGED_PORT) throw new Error(`${name} must be from ${String(MIN_MANAGED_PORT)} through ${String(MAX_MANAGED_PORT)}`);
|
|
24061
24363
|
}
|
|
24062
|
-
|
|
24364
|
+
/** Longest failure detail carried in the thrown message. */
|
|
24365
|
+
const MAX_FAILURE_DETAIL_CHARS = 512;
|
|
24366
|
+
/**
|
|
24367
|
+
* Strip the elevated invocation and filesystem locations from PowerShell text.
|
|
24368
|
+
*
|
|
24369
|
+
* The launcher's stderr quotes the command it ran, including the base64
|
|
24370
|
+
* `-EncodedCommand` payload, and the elevated script quotes its own scratch
|
|
24371
|
+
* path. Neither helps an operator, and both can push the one useful line past
|
|
24372
|
+
* the length bound, so they are replaced before the detail is bounded.
|
|
24373
|
+
*/
|
|
24374
|
+
function redactPowerShellDetail(value) {
|
|
24375
|
+
return value.replace(/EncodedCommand\s+\S+/giu, "EncodedCommand <redacted>").replace(/\b[A-Za-z0-9+/]{64,}={0,2}\b/gu, "<redacted>").replace(/(?<![A-Za-z])[A-Za-z]:[\\/][^\s'"]*/gu, "<path>");
|
|
24376
|
+
}
|
|
24377
|
+
/** Bounded, redacted detail for a failed elevated Windows operation. */
|
|
24378
|
+
function powerShellFailureDetail(error, innerError = "") {
|
|
24063
24379
|
if (!(error instanceof Error)) return "unknown failure";
|
|
24064
|
-
const code = "code" in error && typeof error.code === "string" ? ` (${error.code})` : "";
|
|
24065
|
-
const
|
|
24066
|
-
return `${code}${
|
|
24380
|
+
const code = "code" in error && (typeof error.code === "string" || typeof error.code === "number") ? ` (${String(error.code)})` : "";
|
|
24381
|
+
const detail = redactPowerShellDetail(innerError.trim() === "" && "stderr" in error && typeof error.stderr === "string" ? error.stderr : innerError).trim().replace(/\s+/gu, " ").slice(0, MAX_FAILURE_DETAIL_CHARS);
|
|
24382
|
+
return `${code}${detail === "" ? "" : `: ${detail}`}`;
|
|
24383
|
+
}
|
|
24384
|
+
/** Default sink for a scratch directory that could not be removed. */
|
|
24385
|
+
function warnFirewallCleanupFailure(detail) {
|
|
24386
|
+
process.emitWarning(`Windows firewall temporary cleanup failed (${detail})`, { code: "DSH_MOBILE_FIREWALL_CLEANUP_FAILED" });
|
|
24387
|
+
}
|
|
24388
|
+
/**
|
|
24389
|
+
* Remove the elevated helper's scratch directory without touching the run's outcome.
|
|
24390
|
+
*
|
|
24391
|
+
* The directory is created before elevation and removed in a `finally`, so a
|
|
24392
|
+
* cleanup failure must not replace the firewall error that explains the
|
|
24393
|
+
* failure, and must not fail a run that already succeeded. It stays observable
|
|
24394
|
+
* instead: the removal error's code is reported to the warning sink, whose own
|
|
24395
|
+
* failure is ignored for the same reason.
|
|
24396
|
+
* @param directory - scratch path created for this run.
|
|
24397
|
+
* @param options - injectable removal and warning sink for tests.
|
|
24398
|
+
*/
|
|
24399
|
+
async function discardFirewallScratch(directory, options = {}) {
|
|
24400
|
+
const remove = options.remove ?? ((path) => rm(path, {
|
|
24401
|
+
recursive: true,
|
|
24402
|
+
force: true
|
|
24403
|
+
}));
|
|
24404
|
+
try {
|
|
24405
|
+
await remove(directory);
|
|
24406
|
+
} catch (error) {
|
|
24407
|
+
const code = error !== null && typeof error === "object" && "code" in error && typeof error.code === "string" ? error.code : "unknown";
|
|
24408
|
+
const warn = options.warn ?? warnFirewallCleanupFailure;
|
|
24409
|
+
try {
|
|
24410
|
+
warn(code);
|
|
24411
|
+
} catch {}
|
|
24412
|
+
}
|
|
24067
24413
|
}
|
|
24068
24414
|
async function runElevatedPowerShell(script) {
|
|
24415
|
+
const temporary = await mkdtemp(join(tmpdir(), "dsh-mobile-firewall-"));
|
|
24416
|
+
const errorFile = join(temporary, "error.log");
|
|
24417
|
+
const wrapped = [
|
|
24418
|
+
`$__dshMobileErrorFile = '${errorFile.replaceAll("'", "''")}'`,
|
|
24419
|
+
"try {",
|
|
24420
|
+
script,
|
|
24421
|
+
"} catch {",
|
|
24422
|
+
" try { [IO.File]::WriteAllText($__dshMobileErrorFile, ($_ | Out-String)) } catch {}",
|
|
24423
|
+
" throw",
|
|
24424
|
+
"}"
|
|
24425
|
+
].join("\n");
|
|
24069
24426
|
const launch = [
|
|
24070
24427
|
"$ErrorActionPreference = 'Stop'; $process = Start-Process -FilePath 'powershell.exe' -Verb RunAs -WindowStyle Hidden -Wait -PassThru",
|
|
24071
|
-
` -ArgumentList @('-NoProfile','-NonInteractive','-EncodedCommand','${Buffer.from(
|
|
24428
|
+
` -ArgumentList @('-NoProfile','-NonInteractive','-EncodedCommand','${Buffer.from(wrapped, "utf16le").toString("base64")}')`,
|
|
24072
24429
|
"; exit $process.ExitCode"
|
|
24073
24430
|
].join(" ");
|
|
24074
24431
|
try {
|
|
@@ -24079,7 +24436,13 @@ async function runElevatedPowerShell(script) {
|
|
|
24079
24436
|
launch
|
|
24080
24437
|
], { windowsHide: true });
|
|
24081
24438
|
} catch (error) {
|
|
24082
|
-
|
|
24439
|
+
let innerError = "";
|
|
24440
|
+
try {
|
|
24441
|
+
innerError = (await readFile(errorFile, "utf8")).replace(/^\uFEFF/u, "");
|
|
24442
|
+
} catch {}
|
|
24443
|
+
throw new Error(`Windows firewall operation failed${powerShellFailureDetail(error, innerError)}`, { cause: error });
|
|
24444
|
+
} finally {
|
|
24445
|
+
await discardFirewallScratch(temporary);
|
|
24083
24446
|
}
|
|
24084
24447
|
}
|
|
24085
24448
|
function firewallRuleScript(name, protocol, port) {
|
|
@@ -24094,7 +24457,7 @@ function firewallRuleScript(name, protocol, port) {
|
|
|
24094
24457
|
" $rule | Get-NetFirewallAddressFilter | Set-NetFirewallAddressFilter -RemoteAddress LocalSubnet",
|
|
24095
24458
|
" }",
|
|
24096
24459
|
"}",
|
|
24097
|
-
`$verified = @(Get-NetFirewallRule -DisplayName '${name}' -Enabled True -Direction Inbound -Action Allow -Profile Any
|
|
24460
|
+
`$verified = @(Get-NetFirewallRule -DisplayName '${name}' -ErrorAction Stop | Where-Object { $_.Enabled -eq 'True' -and $_.Direction -eq 'Inbound' -and $_.Action -eq 'Allow' -and $_.Profile -eq 'Any' })`,
|
|
24098
24461
|
`$matchingPort = @($verified | Get-NetFirewallPortFilter | Where-Object { $_.Protocol -eq '${protocol}' -and $_.LocalPort -contains '${String(port)}' })`,
|
|
24099
24462
|
`$matchingAddress = @($verified | Get-NetFirewallAddressFilter | Where-Object { $_.RemoteAddress -contains 'LocalSubnet' })`,
|
|
24100
24463
|
`if ($verified.Count -eq 0 -or $matchingPort.Count -eq 0 -or $matchingAddress.Count -eq 0) { throw "DSH Mobile firewall rule '${name}' is not active with the expected scope" }`
|
|
@@ -24590,6 +24953,7 @@ async function apply(ctx, config) {
|
|
|
24590
24953
|
if (service === void 0) throw new Error("push_unavailable");
|
|
24591
24954
|
return service;
|
|
24592
24955
|
};
|
|
24956
|
+
new MobilePushService(ctx, async (input) => (await requiredPushGatewayService()).notifyCustom(input));
|
|
24593
24957
|
const queueDeviceInvalidations = (scopeId, deviceIds) => {
|
|
24594
24958
|
let queued = pendingDeviceInvalidations.get(scopeId);
|
|
24595
24959
|
if (queued === void 0) pendingDeviceInvalidations.set(scopeId, queued = /* @__PURE__ */ new Set());
|
|
@@ -24664,6 +25028,11 @@ async function apply(ctx, config) {
|
|
|
24664
25028
|
async test(owner, installationId) {
|
|
24665
25029
|
await (await requiredPushGatewayService()).test(owner, installationId);
|
|
24666
25030
|
},
|
|
25031
|
+
async notifyCustom(input) {
|
|
25032
|
+
const service = await requiredPushGatewayService();
|
|
25033
|
+
if (service.notifyCustom === void 0) throw new Error("push_unavailable");
|
|
25034
|
+
return await service.notifyCustom(input);
|
|
25035
|
+
},
|
|
24667
25036
|
async resolve(owner, reference) {
|
|
24668
25037
|
return (await requiredPushGatewayService()).resolve(owner, reference);
|
|
24669
25038
|
}
|
|
@@ -25999,6 +26368,6 @@ async function apply(ctx, config) {
|
|
|
25999
26368
|
};
|
|
26000
26369
|
}, "dsh-mobile: independent LAN and selectable remote providers with /mobile command");
|
|
26001
26370
|
}
|
|
26002
|
-
export { AUTH_PREFIX, AccessController, AccessError, BlockedUpgradePathLog, BoundedRateLimiter, CERT_EXPIRING_DAYS, CLOUDFLARED_COMPONENT_RELEASE, CLOUDFLARED_COMPONENT_RELEASES, CPOLAR_COMPONENT_RELEASE, CPOLAR_COMPONENT_RELEASES, CSRF_COOKIE, CSRF_HEADER, ClientModuleConflictError, ClientModulePreferenceStore, CloudflaredComponentManager, CloudflaredController, CloudflaredTunnelStore, Config, CpolarComponentManager, DEFAULT_IROH_LOCAL_PORT, DEFAULT_ORIGIN_LISTEN_PORT, FRP_VHOST_HTTP_PORT as DEFAULT_VHOST_HTTP_PORT, FRP_VHOST_HTTP_PORT, DEVICE_COOKIE, EXTENSION_LIMITS, FRP_ATTACH_DISCOVERY_PATH, FRP_ATTACH_LOCAL_DECLARATION, FRP_ATTACH_SELF_SIGNED_DECLARATION, FRP_ATTACH_VPS_DECLARATION, FRP_CADDY_IMPORT_LINE, FRP_CADDY_SNIPPET_MARKER, FRP_CADDY_SNIPPET_PATH, FRP_COMPONENT_RELEASES, FRP_DEFAULT_PROXY_NAME, FRP_DEFAULT_PUBLIC_PORT, FRP_RESERVED_PORTS, FrpComponentManager, FrpConfigStore, FrpController, IROH_COMPONENT_RELEASES, IROH_LEAF_RENEW_BEFORE_DAYS, IROH_LEAF_VALIDITY_DAYS, IROH_VERSION, IrohComponentManager, IrohController, JsonDeviceStore, JsonMobileAccessControlStore, JsonRemoteProviderStore, LOCAL_ADMIN_PREFIX, MAX_BLOCKED_UPGRADE_PATHS, MAX_EXTRA_WEBSOCKET_PATHS, MAX_PAIRING_DESCRIPTOR_BYTES, MAX_PAIRING_LIFETIME_MS, MAX_WEBSOCKET_PATH_LENGTH, MemoryDeviceStore, MobileAccessGateway, MobileAccessGatewayController, MobileAccessService, MobileExtensionError, OriginConfigStore, OriginController, PAIRING_DESCRIPTOR_LOCAL_PORT, PAIRING_DESCRIPTOR_TYPE, PAIRING_DESCRIPTOR_VERSION, REMOTE_PROVIDERS, RequestTrustPolicy, SESSION_COOKIE, TASK_EVENT_DEBOUNCE_MS, TaskEventHub, WS_PATHS, WebSocketPathStore, addressAllowed, apply, assertExtensionId, configuredRemoteProvider, createCaddySite, createFrpAttachPlan, createFrpAttachTemplate, createFrpAttachTemplateParts, createFrpServerTemplate, createFrpcToml, createMobileAccessService, createRestrictedFrpServerTemplate, decodePairingDescriptor, encodePairingDescriptor, ensureFrpIngressCertificate, ensureIrohTls, evaluateCertificateLifetime, frpAttachVpsParts, frpIngressPaths, frpIngressSelfCheck, frpProxyName, inject, irohGatewayConfig, irohHostname, irohOrigin, irohTlsPaths, isCloudflaredRegistration, isFrpSelfSignedIngress, isGloballyRoutableIpv4, isLoopbackAddress, issueServerCertificate, mergeSavedCloudflaredTunnelSettings, mergeSavedFrpSettings, mergeSavedFrpTarget, name, normalizeWebSocketPaths, originGatewayConfig, parseAuthority, parseCidr, parseCloudflaredOrigin, parseCloudflaredTunnelSettings, parseControlFile, parseDeviceSnapshot, parseExtensionManifest, parseFrpSettings, parseGatewayConfig, parseMobileAccessControlState, parseOriginSettings, parseRemoteProviderState, probeOriginCertificate, readCertificateRenewal, resolveAuthority, resolveFrpEntryTls, resolveFrpMode, resolveFrpPublicPort, resolveFrpVhostHttpPort, resolveHostExecution, rewriteMobileIndex, validateAttachSettings, validateCloudflaredTunnelHostname, validateCloudflaredTunnelPort, validateCloudflaredTunnelToken, validateFrpEntryTls, validateFrpMode, validateFrpProxyName, validateFrpPublicOrigin, validateFrpPublicPort, validateFrpServerAddress, validateFrpServerPort, validateFrpToken, validateFrpVhostHttpPort, validateIrohOrigin, validateOriginAllowedCidrs, validateOriginListenHost, validateOriginListenPort, validateOriginPublicOrigin, validatePairingDescriptor, validateWebSocketPath, watchTaskCompletions };
|
|
26371
|
+
export { AUTH_PREFIX, AccessController, AccessError, BlockedUpgradePathLog, BoundedRateLimiter, CERT_EXPIRING_DAYS, CLOUDFLARED_COMPONENT_RELEASE, CLOUDFLARED_COMPONENT_RELEASES, CPOLAR_COMPONENT_RELEASE, CPOLAR_COMPONENT_RELEASES, CSRF_COOKIE, CSRF_HEADER, ClientModuleConflictError, ClientModulePreferenceStore, CloudflaredComponentManager, CloudflaredController, CloudflaredTunnelStore, Config, CpolarComponentManager, DEFAULT_IROH_LOCAL_PORT, DEFAULT_ORIGIN_LISTEN_PORT, FRP_VHOST_HTTP_PORT as DEFAULT_VHOST_HTTP_PORT, FRP_VHOST_HTTP_PORT, DEVICE_COOKIE, EXTENSION_LIMITS, FRP_ATTACH_DISCOVERY_PATH, FRP_ATTACH_LOCAL_DECLARATION, FRP_ATTACH_SELF_SIGNED_DECLARATION, FRP_ATTACH_VPS_DECLARATION, FRP_CADDY_IMPORT_LINE, FRP_CADDY_SNIPPET_MARKER, FRP_CADDY_SNIPPET_PATH, FRP_COMPONENT_RELEASES, FRP_DEFAULT_PROXY_NAME, FRP_DEFAULT_PUBLIC_PORT, FRP_RESERVED_PORTS, FrpComponentManager, FrpConfigStore, FrpController, IROH_COMPONENT_RELEASES, IROH_LEAF_RENEW_BEFORE_DAYS, IROH_LEAF_VALIDITY_DAYS, IROH_VERSION, IrohComponentManager, IrohController, JsonDeviceStore, JsonMobileAccessControlStore, JsonRemoteProviderStore, LOCAL_ADMIN_PREFIX, MAX_BLOCKED_UPGRADE_PATHS, MAX_EXTRA_WEBSOCKET_PATHS, MAX_PAIRING_DESCRIPTOR_BYTES, MAX_PAIRING_LIFETIME_MS, MAX_WEBSOCKET_PATH_LENGTH, MemoryDeviceStore, MobileAccessGateway, MobileAccessGatewayController, MobileAccessService, MobileExtensionError, MobilePushService, OriginConfigStore, OriginController, PAIRING_DESCRIPTOR_LOCAL_PORT, PAIRING_DESCRIPTOR_TYPE, PAIRING_DESCRIPTOR_VERSION, REMOTE_PROVIDERS, RequestTrustPolicy, SESSION_COOKIE, TASK_EVENT_DEBOUNCE_MS, TaskEventHub, WS_PATHS, WebSocketPathStore, addressAllowed, apply, assertExtensionId, configuredRemoteProvider, createCaddySite, createFrpAttachPlan, createFrpAttachTemplate, createFrpAttachTemplateParts, createFrpServerTemplate, createFrpcToml, createMobileAccessService, createRestrictedFrpServerTemplate, decodePairingDescriptor, encodePairingDescriptor, ensureFrpIngressCertificate, ensureIrohTls, evaluateCertificateLifetime, frpAttachVpsParts, frpIngressPaths, frpIngressSelfCheck, frpProxyName, inject, irohGatewayConfig, irohHostname, irohOrigin, irohTlsPaths, isCloudflaredRegistration, isFrpSelfSignedIngress, isGloballyRoutableIpv4, isLoopbackAddress, issueServerCertificate, mergeSavedCloudflaredTunnelSettings, mergeSavedFrpSettings, mergeSavedFrpTarget, name, normalizeWebSocketPaths, originGatewayConfig, parseAuthority, parseCidr, parseCloudflaredOrigin, parseCloudflaredTunnelSettings, parseControlFile, parseDeviceSnapshot, parseExtensionManifest, parseFrpSettings, parseGatewayConfig, parseMobileAccessControlState, parseOriginSettings, parseRemoteProviderState, probeOriginCertificate, readCertificateRenewal, resolveAuthority, resolveFrpEntryTls, resolveFrpMode, resolveFrpPublicPort, resolveFrpVhostHttpPort, resolveHostExecution, rewriteMobileIndex, validateAttachSettings, validateCloudflaredTunnelHostname, validateCloudflaredTunnelPort, validateCloudflaredTunnelToken, validateFrpEntryTls, validateFrpMode, validateFrpProxyName, validateFrpPublicOrigin, validateFrpPublicPort, validateFrpServerAddress, validateFrpServerPort, validateFrpToken, validateFrpVhostHttpPort, validateIrohOrigin, validateOriginAllowedCidrs, validateOriginListenHost, validateOriginListenPort, validateOriginPublicOrigin, validatePairingDescriptor, validateWebSocketPath, watchTaskCompletions };
|
|
26003
26372
|
|
|
26004
26373
|
//# sourceMappingURL=index.mjs.map
|