dsh-ios-webview-app-control 0.0.0-stage → 0.2.2
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 +41 -0
- package/LICENSE +21 -0
- package/README.md +250 -2
- package/THIRD_PARTY_NOTICES.md +42 -0
- package/cordis.patch.yml +20 -0
- package/docs/installation.md +68 -0
- package/docs/new-session-prompt.md +29 -0
- package/docs/runtime-environment.md +40 -0
- package/docs/sdk-contract.md +79 -0
- package/docs/verification.md +64 -0
- package/icon.svg +8 -0
- package/lib/cli.mjs +51 -0
- package/lib/client.js +223 -0
- package/lib/core/dom.mjs +26 -0
- package/lib/core/leases.mjs +31 -0
- package/lib/core/protocol.mjs +101 -0
- package/lib/core/python.mjs +47 -0
- package/lib/core/service.mjs +122 -0
- package/lib/host/environment-service.mjs +175 -0
- package/lib/host/subprocess-runner.mjs +34 -0
- package/lib/plugin.mjs +303 -0
- package/lib/runtime/environment-manager.mjs +544 -0
- package/lib/runtime/errors.mjs +33 -0
- package/lib/runtime/runner.mjs +75 -0
- package/lib/runtime/safe-tar.mjs +179 -0
- package/lib/typert-environment.mjs +103 -0
- package/locale/en.json +6 -0
- package/locale/zh.json +6 -0
- package/package.json +72 -4
- package/requirements.txt +1 -0
- package/resources/notices/PBS-LICENSE.bdb.txt +126 -0
- package/resources/notices/PBS-LICENSE.bzip2.txt +37 -0
- package/resources/notices/PBS-LICENSE.cpython.txt +771 -0
- package/resources/notices/PBS-LICENSE.expat.txt +21 -0
- package/resources/notices/PBS-LICENSE.libX11.txt +942 -0
- package/resources/notices/PBS-LICENSE.libXau.txt +21 -0
- package/resources/notices/PBS-LICENSE.libedit.txt +29 -0
- package/resources/notices/PBS-LICENSE.libffi.txt +21 -0
- package/resources/notices/PBS-LICENSE.liblzma.txt +13 -0
- package/resources/notices/PBS-LICENSE.libuuid.txt +27 -0
- package/resources/notices/PBS-LICENSE.libxcb.txt +30 -0
- package/resources/notices/PBS-LICENSE.mpdecimal.txt +24 -0
- package/resources/notices/PBS-LICENSE.ncurses.txt +29 -0
- package/resources/notices/PBS-LICENSE.openssl-3.txt +177 -0
- package/resources/notices/PBS-LICENSE.sqlite.txt +23 -0
- package/resources/notices/PBS-LICENSE.tcl.txt +40 -0
- package/resources/notices/PBS-LICENSE.tix.txt +54 -0
- package/resources/notices/PBS-LICENSE.zlib-ng.txt +19 -0
- package/resources/notices/PBS-LICENSE.zlib.txt +21 -0
- package/resources/notices/PBS-LICENSE.zstd.txt +30 -0
- package/resources/notices/PYMOBILEDEVICE3-LICENSE.txt +674 -0
- package/resources/notices/PYTHON-LICENSE.txt +277 -0
- package/resources/notices/provenance.json +134 -0
- package/resources/runtime-lock.json +4252 -0
- package/scripts/manage-desktop.ps1 +56 -0
- package/scripts/package-validation.mjs +223 -0
- package/scripts/verify-package.mjs +13 -0
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { EnvironmentError, aborted, bounded } from './errors.mjs';
|
|
4
|
+
|
|
5
|
+
const OUTPUT_LIMIT = 1024 * 1024;
|
|
6
|
+
|
|
7
|
+
/** Default non-DSH runner. Inject the Host managed runner when a process-range service is available. */
|
|
8
|
+
export async function defaultRunner(executable, args = [], { signal, timeoutMs = 15000, env, cwd, onOutput } = {}) {
|
|
9
|
+
aborted(signal);
|
|
10
|
+
return new Promise((resolve, reject) => {
|
|
11
|
+
const child = spawn(executable, args, {
|
|
12
|
+
shell: false, windowsHide: true, cwd, env,
|
|
13
|
+
detached: process.platform !== 'win32',
|
|
14
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
15
|
+
});
|
|
16
|
+
let stdout = '';
|
|
17
|
+
let stderr = '';
|
|
18
|
+
let failure = null;
|
|
19
|
+
let terminating = null;
|
|
20
|
+
let escalation = null;
|
|
21
|
+
const terminate = () => {
|
|
22
|
+
if (terminating) return terminating;
|
|
23
|
+
if (!child.pid) return Promise.resolve();
|
|
24
|
+
if (process.platform === 'win32') {
|
|
25
|
+
const systemRoot = process.env.SystemRoot || process.env.WINDIR || 'C:\\Windows';
|
|
26
|
+
const taskkill = path.win32.join(systemRoot, 'System32', 'taskkill.exe');
|
|
27
|
+
terminating = new Promise(done => {
|
|
28
|
+
let killer;
|
|
29
|
+
try {
|
|
30
|
+
killer = spawn(taskkill, ['/PID', String(child.pid), '/T', '/F'], { shell: false, windowsHide: true, stdio: 'ignore' });
|
|
31
|
+
} catch { try { child.kill(); } catch {} done(); return; }
|
|
32
|
+
killer.once('error', () => { try { child.kill(); } catch {} });
|
|
33
|
+
killer.once('close', done);
|
|
34
|
+
});
|
|
35
|
+
} else {
|
|
36
|
+
try { process.kill(-child.pid, 'SIGTERM'); } catch {}
|
|
37
|
+
escalation = setTimeout(() => { try { process.kill(-child.pid, 'SIGKILL'); } catch {} }, 1000);
|
|
38
|
+
terminating = Promise.resolve();
|
|
39
|
+
}
|
|
40
|
+
return terminating;
|
|
41
|
+
};
|
|
42
|
+
const abort = () => {
|
|
43
|
+
failure ||= new EnvironmentError(signal?.reason?.code === 'runtime_timeout' ? 'runtime_timeout' : 'cancelled', 'Runtime subprocess was cancelled.');
|
|
44
|
+
void terminate();
|
|
45
|
+
};
|
|
46
|
+
const timer = setTimeout(() => {
|
|
47
|
+
failure ||= new EnvironmentError('runtime_process_timeout', 'Runtime subprocess exceeded its deadline.');
|
|
48
|
+
void terminate();
|
|
49
|
+
}, timeoutMs);
|
|
50
|
+
const output = (stream, chunk) => {
|
|
51
|
+
const text = chunk.toString('utf8');
|
|
52
|
+
const current = stream === 'stdout' ? stdout : stderr;
|
|
53
|
+
if (Buffer.byteLength(current) + chunk.length > OUTPUT_LIMIT) {
|
|
54
|
+
failure ||= new EnvironmentError('runtime_output_limit', 'Runtime subprocess output exceeded 1 MiB.');
|
|
55
|
+
void terminate();
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
if (stream === 'stdout') stdout += text; else stderr += text;
|
|
59
|
+
try { onOutput?.(stream, bounded(text, 4096)); } catch {}
|
|
60
|
+
};
|
|
61
|
+
signal?.addEventListener('abort', abort, { once: true });
|
|
62
|
+
child.stdout.on('data', chunk => output('stdout', chunk));
|
|
63
|
+
child.stderr.on('data', chunk => output('stderr', chunk));
|
|
64
|
+
child.once('error', error => { failure ||= new EnvironmentError('runtime_process_error', bounded(error.message)); });
|
|
65
|
+
child.once('close', async code => {
|
|
66
|
+
clearTimeout(timer);
|
|
67
|
+
if (escalation) clearTimeout(escalation);
|
|
68
|
+
signal?.removeEventListener('abort', abort);
|
|
69
|
+
await terminating;
|
|
70
|
+
if (failure) reject(failure);
|
|
71
|
+
else resolve({ stdout, stderr, exitCode: code ?? 1 });
|
|
72
|
+
});
|
|
73
|
+
if (signal?.aborted) abort();
|
|
74
|
+
});
|
|
75
|
+
}
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import { createGunzip } from 'node:zlib';
|
|
2
|
+
import { Readable } from 'node:stream';
|
|
3
|
+
import { lstat, mkdir, writeFile } from 'node:fs/promises';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { EnvironmentError, aborted } from './errors.mjs';
|
|
6
|
+
|
|
7
|
+
const BLOCK = 512;
|
|
8
|
+
const MAX_ENTRIES = 100_000;
|
|
9
|
+
const MAX_EXPANDED = 512 * 1024 * 1024;
|
|
10
|
+
const MAX_ENTRY = 256 * 1024 * 1024;
|
|
11
|
+
const MAX_METADATA = 1024 * 1024;
|
|
12
|
+
|
|
13
|
+
function reject(message) {
|
|
14
|
+
throw new EnvironmentError('runtime_archive_invalid', message);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function string(header, start, length) {
|
|
18
|
+
const bytes = header.subarray(start, start + length);
|
|
19
|
+
const zero = bytes.indexOf(0);
|
|
20
|
+
return bytes.subarray(0, zero < 0 ? bytes.length : zero).toString('utf8');
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function number(header, start, length) {
|
|
24
|
+
const raw = header.subarray(start, start + length);
|
|
25
|
+
if (raw[0] & 0x80) reject('Tar base-256 sizes are not supported.');
|
|
26
|
+
const text = string(header, start, length).trim();
|
|
27
|
+
if (text && !/^[0-7]+$/.test(text)) reject('Tar contains an invalid numeric field.');
|
|
28
|
+
const result = text ? parseInt(text, 8) : 0;
|
|
29
|
+
if (!Number.isSafeInteger(result)) reject('Tar numeric field exceeds the safe bound.');
|
|
30
|
+
return result;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function safeName(value) {
|
|
34
|
+
if (!value || value.length > 1024 || /[\\\x00-\x1f:\x7f]/.test(value) || value.startsWith('/')) reject('Tar contains an absolute, ADS, control-character or unsafe path.');
|
|
35
|
+
const name = value.replace(/\/+$/, '');
|
|
36
|
+
const parts = name.split('/');
|
|
37
|
+
if (parts.some(part => !part || part === '.' || part === '..' || /[. ]$/.test(part) || /^(?:CON|PRN|AUX|NUL|COM[0-9]|LPT[0-9])(?:\.|$)/i.test(part))) reject('Tar contains a traversal or reserved Windows path.');
|
|
38
|
+
return parts.join('/');
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function pax(bytes) {
|
|
42
|
+
const output = {};
|
|
43
|
+
let offset = 0;
|
|
44
|
+
while (offset < bytes.length) {
|
|
45
|
+
const space = bytes.indexOf(32, offset);
|
|
46
|
+
if (space < 0) reject('Tar contains malformed PAX metadata.');
|
|
47
|
+
const lengthText = bytes.toString('ascii', offset, space);
|
|
48
|
+
if (!/^[0-9]+$/.test(lengthText)) reject('Tar contains malformed PAX record lengths.');
|
|
49
|
+
const length = Number(lengthText);
|
|
50
|
+
if (!Number.isSafeInteger(length) || length <= space - offset + 1 || offset + length > bytes.length || bytes[offset + length - 1] !== 10) reject('Tar contains truncated PAX metadata.');
|
|
51
|
+
const text = bytes.toString('utf8', space + 1, offset + length - 1);
|
|
52
|
+
const equals = text.indexOf('=');
|
|
53
|
+
if (equals < 1) reject('Tar contains malformed PAX metadata.');
|
|
54
|
+
const key = text.slice(0, equals);
|
|
55
|
+
if (/linkpath|sparse/i.test(key)) reject('Tar PAX links and sparse files are forbidden.');
|
|
56
|
+
output[key] = text.slice(equals + 1);
|
|
57
|
+
offset += length;
|
|
58
|
+
}
|
|
59
|
+
return output;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
async function ensureDirectory(root, relative) {
|
|
63
|
+
let current = root;
|
|
64
|
+
for (const part of relative ? relative.split('/') : []) {
|
|
65
|
+
current = path.join(current, part);
|
|
66
|
+
try {
|
|
67
|
+
const entry = await lstat(current);
|
|
68
|
+
if (!entry.isDirectory() || entry.isSymbolicLink()) reject('Tar parent path is not a private real directory.');
|
|
69
|
+
} catch (error) {
|
|
70
|
+
if (error?.code !== 'ENOENT') throw error;
|
|
71
|
+
await mkdir(current, { mode: 0o700 });
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** Bounded tar.gz extraction. No symlinks, hardlinks, devices, sparse files or writes outside destination. */
|
|
77
|
+
export async function extractSafeTarGz(archive, destination, { signal, maxExpanded = MAX_EXPANDED, maxEntries = MAX_ENTRIES } = {}) {
|
|
78
|
+
aborted(signal);
|
|
79
|
+
await mkdir(destination, { recursive: true, mode: 0o700 });
|
|
80
|
+
if ((await lstat(destination)).isSymbolicLink()) reject('Archive destination must not be a symlink.');
|
|
81
|
+
const gunzip = createGunzip();
|
|
82
|
+
const source = Readable.from(archive);
|
|
83
|
+
const input = source.pipe(gunzip);
|
|
84
|
+
const iterator = input[Symbol.asyncIterator]();
|
|
85
|
+
let buffer = Buffer.alloc(0);
|
|
86
|
+
let expanded = 0;
|
|
87
|
+
let done = false;
|
|
88
|
+
const stop = () => { source.destroy(); input.destroy(new EnvironmentError('cancelled', 'Runtime extraction was cancelled.')); };
|
|
89
|
+
signal?.addEventListener('abort', stop, { once: true });
|
|
90
|
+
const take = async length => {
|
|
91
|
+
while (buffer.length < length && !done) {
|
|
92
|
+
aborted(signal);
|
|
93
|
+
const next = await iterator.next();
|
|
94
|
+
done = next.done === true;
|
|
95
|
+
if (!done) {
|
|
96
|
+
expanded += next.value.length;
|
|
97
|
+
if (expanded > maxExpanded) reject('Archive expands beyond the allowed size.');
|
|
98
|
+
buffer = Buffer.concat([buffer, next.value]);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
if (buffer.length < length) reject('Tar contains a truncated record.');
|
|
102
|
+
const value = buffer.subarray(0, length);
|
|
103
|
+
buffer = buffer.subarray(length);
|
|
104
|
+
return value;
|
|
105
|
+
};
|
|
106
|
+
const names = new Set();
|
|
107
|
+
let entries = 0;
|
|
108
|
+
let globalPax = {};
|
|
109
|
+
let localPax = {};
|
|
110
|
+
let longName = null;
|
|
111
|
+
try {
|
|
112
|
+
for (;;) {
|
|
113
|
+
const header = await take(BLOCK);
|
|
114
|
+
if (header.every(byte => byte === 0)) {
|
|
115
|
+
const second = await take(BLOCK);
|
|
116
|
+
if (!second.every(byte => byte === 0)) reject('Tar end-of-archive marker is invalid.');
|
|
117
|
+
while (!done) {
|
|
118
|
+
const next = await iterator.next();
|
|
119
|
+
done = next.done === true;
|
|
120
|
+
if (!done) {
|
|
121
|
+
expanded += next.value.length;
|
|
122
|
+
if (expanded > maxExpanded || next.value.some(byte => byte !== 0)) reject('Archive has excessive or nonzero trailing data.');
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
if (buffer.some(byte => byte !== 0)) reject('Archive has nonzero trailing data.');
|
|
126
|
+
break;
|
|
127
|
+
}
|
|
128
|
+
entries++;
|
|
129
|
+
if (entries > maxEntries) reject('Archive contains too many entries.');
|
|
130
|
+
const expectedChecksum = number(header, 148, 8);
|
|
131
|
+
let actualChecksum = 0;
|
|
132
|
+
for (let index = 0; index < BLOCK; index++) actualChecksum += index >= 148 && index < 156 ? 32 : header[index];
|
|
133
|
+
if (expectedChecksum !== actualChecksum) reject('Tar header checksum is invalid.');
|
|
134
|
+
const type = String.fromCharCode(header[156] || 48);
|
|
135
|
+
let size = number(header, 124, 12);
|
|
136
|
+
const metadata = { ...globalPax, ...localPax };
|
|
137
|
+
if (metadata.size !== undefined) {
|
|
138
|
+
if (!/^[0-9]+$/.test(metadata.size)) reject('PAX size is invalid.');
|
|
139
|
+
size = Number(metadata.size);
|
|
140
|
+
}
|
|
141
|
+
if (!Number.isSafeInteger(size) || size < 0 || size > MAX_ENTRY || size > maxExpanded) reject('Archive entry exceeds the allowed size.');
|
|
142
|
+
if (['x', 'g', 'L'].includes(type)) {
|
|
143
|
+
if (size > MAX_METADATA) reject('Tar metadata exceeds its bound.');
|
|
144
|
+
const data = await take(Math.ceil(size / BLOCK) * BLOCK);
|
|
145
|
+
if (type === 'L') longName = data.subarray(0, size).toString('utf8').replace(/\0.*$/s, '');
|
|
146
|
+
else if (type === 'g') globalPax = { ...globalPax, ...pax(data.subarray(0, size)) };
|
|
147
|
+
else localPax = pax(data.subarray(0, size));
|
|
148
|
+
continue;
|
|
149
|
+
}
|
|
150
|
+
if (type !== '0' && type !== '5') reject('Archive links and special file types are forbidden.');
|
|
151
|
+
const prefix = string(header, 345, 155);
|
|
152
|
+
const name = safeName(metadata.path || longName || [prefix, string(header, 0, 100)].filter(Boolean).join('/'));
|
|
153
|
+
localPax = {};
|
|
154
|
+
longName = null;
|
|
155
|
+
const canonical = name.toLowerCase();
|
|
156
|
+
if (names.has(canonical)) reject('Archive contains duplicate or case-colliding paths.');
|
|
157
|
+
names.add(canonical);
|
|
158
|
+
const target = path.resolve(destination, ...name.split('/'));
|
|
159
|
+
const relative = path.relative(destination, target);
|
|
160
|
+
if (relative.startsWith('..') || path.isAbsolute(relative)) reject('Archive path escapes its private directory.');
|
|
161
|
+
if (type === '5') {
|
|
162
|
+
if (size !== 0) reject('Tar directory has nonzero content.');
|
|
163
|
+
await ensureDirectory(destination, name);
|
|
164
|
+
} else {
|
|
165
|
+
await ensureDirectory(destination, name.includes('/') ? name.slice(0, name.lastIndexOf('/')) : '');
|
|
166
|
+
const data = await take(Math.ceil(size / BLOCK) * BLOCK);
|
|
167
|
+
await writeFile(target, data.subarray(0, size), { flag: 'wx', mode: 0o600 });
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
return { entries, expandedBytes: expanded };
|
|
171
|
+
} catch (error) {
|
|
172
|
+
if (error instanceof EnvironmentError) throw error;
|
|
173
|
+
throw new EnvironmentError('runtime_archive_invalid', `Cannot extract locked tar.gz: ${error?.message || error}`);
|
|
174
|
+
} finally {
|
|
175
|
+
signal?.removeEventListener('abort', stop);
|
|
176
|
+
source.destroy();
|
|
177
|
+
input.destroy();
|
|
178
|
+
}
|
|
179
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
|
|
3
|
+
// Hand-authored against DSH rc.5 InvocationDescriptor / TypertContribution.
|
|
4
|
+
// The Client embeds this package's own descriptor, never a Harness UI module.
|
|
5
|
+
export const IOS_ENVIRONMENT_PACKAGE = 'dsh-ios-webview-app-control';
|
|
6
|
+
export const IOS_ENVIRONMENT_SERVICE = 'iosEnvironment';
|
|
7
|
+
export const ENVIRONMENT_PHASES = Object.freeze([
|
|
8
|
+
'unprepared', 'checking', 'downloading', 'creating', 'installing', 'verifying',
|
|
9
|
+
'ready', 'failed', 'cancelled',
|
|
10
|
+
]);
|
|
11
|
+
|
|
12
|
+
export const EnvironmentSnapshotSchema = z.strictObject({
|
|
13
|
+
phase: z.enum(ENVIRONMENT_PHASES),
|
|
14
|
+
busy: z.boolean(),
|
|
15
|
+
step: z.string().max(256),
|
|
16
|
+
progress: z.strictObject({
|
|
17
|
+
completedBytes: z.number().int().nonnegative().max(Number.MAX_SAFE_INTEGER),
|
|
18
|
+
totalBytes: z.number().int().nonnegative().max(Number.MAX_SAFE_INTEGER).optional(),
|
|
19
|
+
}).nullable().optional(),
|
|
20
|
+
python: z.strictObject({
|
|
21
|
+
path: z.string().max(8192),
|
|
22
|
+
version: z.string().max(128),
|
|
23
|
+
source: z.enum(['host', 'private']),
|
|
24
|
+
abi: z.string().max(128).optional(),
|
|
25
|
+
}).nullable().optional(),
|
|
26
|
+
runtimeDir: z.string().max(8192).optional(),
|
|
27
|
+
pymobiledevice3: z.string().max(128).optional(),
|
|
28
|
+
error: z.strictObject({
|
|
29
|
+
code: z.string().max(128),
|
|
30
|
+
message: z.string().max(2048),
|
|
31
|
+
}).nullable().optional(),
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
/** Only named public environment fields cross the Remote boundary. */
|
|
35
|
+
export function projectEnvironmentSnapshot(raw) {
|
|
36
|
+
const snapshot = { phase: raw?.phase, busy: raw?.busy, step: raw?.step ?? raw?.phase };
|
|
37
|
+
if (raw?.progress === null) snapshot.progress = null;
|
|
38
|
+
else if (raw?.progress !== undefined) {
|
|
39
|
+
snapshot.progress = { completedBytes: raw.progress.completedBytes };
|
|
40
|
+
if (raw.progress.totalBytes !== undefined) snapshot.progress.totalBytes = raw.progress.totalBytes;
|
|
41
|
+
}
|
|
42
|
+
if (raw?.python === null) snapshot.python = null;
|
|
43
|
+
else if (raw?.python !== undefined) {
|
|
44
|
+
snapshot.python = { path: raw.python.path, version: raw.python.version, source: raw.python.source };
|
|
45
|
+
if (raw.python.abi !== undefined) snapshot.python.abi = raw.python.abi;
|
|
46
|
+
}
|
|
47
|
+
for (const key of ['runtimeDir', 'pymobiledevice3']) {
|
|
48
|
+
if (raw?.[key] !== undefined) snapshot[key] = raw[key];
|
|
49
|
+
}
|
|
50
|
+
if (raw?.error === null) snapshot.error = null;
|
|
51
|
+
else if (raw?.error !== undefined) snapshot.error = { code: raw.error.code, message: raw.error.message };
|
|
52
|
+
return EnvironmentSnapshotSchema.parse(snapshot);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export const environmentSnapshotCodec = Object.freeze({
|
|
56
|
+
mode: 'strict',
|
|
57
|
+
typeSymbol: `${IOS_ENVIRONMENT_PACKAGE}#EnvironmentSnapshot`,
|
|
58
|
+
create: () => EnvironmentSnapshotSchema,
|
|
59
|
+
// Gateway does not parse Host results itself; enforce the output boundary too.
|
|
60
|
+
encode: value => EnvironmentSnapshotSchema.parse(value),
|
|
61
|
+
decode: value => EnvironmentSnapshotSchema.parse(value),
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
export const environmentInvocations = Object.freeze(
|
|
65
|
+
['status', 'prepare', 'cancel', 'cleanup', 'follow'].map(method => Object.freeze({
|
|
66
|
+
id: `${IOS_ENVIRONMENT_PACKAGE}#${IOS_ENVIRONMENT_SERVICE}/${method}`,
|
|
67
|
+
service: IOS_ENVIRONMENT_SERVICE,
|
|
68
|
+
namespace: IOS_ENVIRONMENT_SERVICE,
|
|
69
|
+
method,
|
|
70
|
+
invocation: Object.freeze({ kind: 'direct' }),
|
|
71
|
+
parameters: Object.freeze([]),
|
|
72
|
+
cancellation: Object.freeze({ parameter: 'signal' }),
|
|
73
|
+
...(method === 'follow' ? { mode: 'stream' } : {}),
|
|
74
|
+
result: environmentSnapshotCodec,
|
|
75
|
+
})),
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
export const environmentHostContribution = Object.freeze({
|
|
79
|
+
package: IOS_ENVIRONMENT_PACKAGE,
|
|
80
|
+
face: 'host',
|
|
81
|
+
schemas: Object.freeze([{ name: 'EnvironmentSnapshot', create: () => EnvironmentSnapshotSchema }]),
|
|
82
|
+
model: Object.freeze({
|
|
83
|
+
services: [{
|
|
84
|
+
key: IOS_ENVIRONMENT_SERVICE,
|
|
85
|
+
exportName: 'IosEnvironmentService',
|
|
86
|
+
description: 'Host-owned Python environment preparation; never contacts a phone.',
|
|
87
|
+
tags: [],
|
|
88
|
+
members: environmentInvocations.map(({ method }) => ({
|
|
89
|
+
kind: 'method', name: method,
|
|
90
|
+
signature: method === 'follow'
|
|
91
|
+
? 'follow(signal: AbortSignal): AsyncIterable<EnvironmentSnapshot>'
|
|
92
|
+
: `${method}(signal: AbortSignal): Promise<EnvironmentSnapshot>`,
|
|
93
|
+
})),
|
|
94
|
+
types: [{
|
|
95
|
+
name: 'EnvironmentSnapshot',
|
|
96
|
+
declaration: 'export interface EnvironmentSnapshot { readonly phase: "unprepared" | "checking" | "downloading" | "creating" | "installing" | "verifying" | "ready" | "failed" | "cancelled"; readonly busy: boolean; readonly step: string; readonly progress?: { readonly completedBytes: number; readonly totalBytes?: number } | null; readonly python?: { readonly path: string; readonly version: string; readonly source: "host" | "private"; readonly abi?: string } | null; readonly runtimeDir?: string; readonly pymobiledevice3?: string; readonly error?: { readonly code: string; readonly message: string } | null; }',
|
|
97
|
+
}],
|
|
98
|
+
}],
|
|
99
|
+
objects: [],
|
|
100
|
+
events: [],
|
|
101
|
+
}),
|
|
102
|
+
invocations: environmentInvocations,
|
|
103
|
+
});
|
package/locale/en.json
ADDED
package/locale/zh.json
ADDED
package/package.json
CHANGED
|
@@ -1,6 +1,74 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-ios-webview-app-control",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.2.2",
|
|
4
|
+
"description": "Business-free DSH tools for inspectable iOS WebView/hybrid apps only.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"engines": {
|
|
8
|
+
"node": ">=22.18.0",
|
|
9
|
+
"dsh": ">=0.2.1-rc.5 <0.3.0"
|
|
10
|
+
},
|
|
11
|
+
"main": "./lib/plugin.mjs",
|
|
12
|
+
"exports": {
|
|
13
|
+
".": "./lib/plugin.mjs",
|
|
14
|
+
"./core": "./lib/core/service.mjs",
|
|
15
|
+
"./client": "./lib/client.js",
|
|
16
|
+
"./cordis.patch.yml": "./cordis.patch.yml",
|
|
17
|
+
"./locale/*.json": "./locale/*.json",
|
|
18
|
+
"./icon": "./icon.svg",
|
|
19
|
+
"./package.json": "./package.json"
|
|
20
|
+
},
|
|
21
|
+
"bin": {
|
|
22
|
+
"dsh-ios-webview-app-control": "./lib/cli.mjs"
|
|
23
|
+
},
|
|
24
|
+
"files": [
|
|
25
|
+
"lib/",
|
|
26
|
+
"resources/",
|
|
27
|
+
"THIRD_PARTY_NOTICES.md",
|
|
28
|
+
"cordis.patch.yml",
|
|
29
|
+
"README.md",
|
|
30
|
+
"docs/",
|
|
31
|
+
"requirements.txt",
|
|
32
|
+
"LICENSE",
|
|
33
|
+
"CHANGELOG.md",
|
|
34
|
+
"locale/*.json",
|
|
35
|
+
"icon.svg",
|
|
36
|
+
"scripts/manage-desktop.ps1",
|
|
37
|
+
"scripts/verify-package.mjs",
|
|
38
|
+
"scripts/package-validation.mjs"
|
|
39
|
+
],
|
|
40
|
+
"dsh": {
|
|
41
|
+
"bundle": {
|
|
42
|
+
"patch": "./cordis.patch.yml"
|
|
43
|
+
},
|
|
44
|
+
"client": {
|
|
45
|
+
"platform": "web",
|
|
46
|
+
"immediately": true,
|
|
47
|
+
"external": ["zod"],
|
|
48
|
+
"inject": ["@deepseek-ai/dsh-client-ui-plugin-manager", "@deepseek-ai/dsh-client-ui-settings-plugins"]
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
"scripts": {
|
|
52
|
+
"build": "node scripts/build.mjs",
|
|
53
|
+
"test": "node --test test/*.test.mjs",
|
|
54
|
+
"check": "node scripts/check.mjs",
|
|
55
|
+
"prepack": "npm run build && npm run check && npm test"
|
|
56
|
+
},
|
|
57
|
+
"dependencies": {
|
|
58
|
+
"@deepseek-ai/schemastery": "~3.18.5",
|
|
59
|
+
"zod": "4.4.3"
|
|
60
|
+
},
|
|
61
|
+
"devDependencies": {
|
|
62
|
+
"@deepseek-ai/dsh-api-gateway": "0.2.1-rc.5",
|
|
63
|
+
"@deepseek-ai/dsh-api-remote-access": "0.2.1-rc.5",
|
|
64
|
+
"@deepseek-ai/dsh-typert-registry": "0.2.1-rc.5"
|
|
65
|
+
},
|
|
66
|
+
"peerDependencies": {
|
|
67
|
+
"@deepseek-ai/cordis": "~4.0.5",
|
|
68
|
+
"@deepseek-ai/dsh-tools": "^0.2.1-rc.5",
|
|
69
|
+
"@deepseek-ai/dsh-system-prompt": "^0.2.1-rc.5",
|
|
70
|
+
"@deepseek-ai/dsh-user-approval": "^0.2.1-rc.5",
|
|
71
|
+
"@deepseek-ai/dsh-llm": "^0.2.1-rc.5",
|
|
72
|
+
"@deepseek-ai/dsh-typert-protocol": "^0.2.1-rc.5"
|
|
73
|
+
}
|
|
74
|
+
}
|
package/requirements.txt
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
pymobiledevice3==11.45.0
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
The following is the license that applies to this copy of the Berkeley DB
|
|
2
|
+
software. For a license to use the Berkeley DB software under conditions
|
|
3
|
+
other than those described here, or to purchase support for this software,
|
|
4
|
+
please contact Oracle at berkeleydb-info_us@oracle.com.
|
|
5
|
+
|
|
6
|
+
=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
|
|
7
|
+
/*
|
|
8
|
+
* Copyright (c) 1990, 2013 Oracle and/or its affiliates. All rights reserved.
|
|
9
|
+
*
|
|
10
|
+
* Redistribution and use in source and binary forms, with or without
|
|
11
|
+
* modification, are permitted provided that the following conditions
|
|
12
|
+
* are met:
|
|
13
|
+
* 1. Redistributions of source code must retain the above copyright
|
|
14
|
+
* notice, this list of conditions and the following disclaimer.
|
|
15
|
+
* 2. Redistributions in binary form must reproduce the above copyright
|
|
16
|
+
* notice, this list of conditions and the following disclaimer in the
|
|
17
|
+
* documentation and/or other materials provided with the distribution.
|
|
18
|
+
* 3. Redistributions in any form must be accompanied by information on
|
|
19
|
+
* how to obtain complete source code for the DB software and any
|
|
20
|
+
* accompanying software that uses the DB software. The source code
|
|
21
|
+
* must either be included in the distribution or be available for no
|
|
22
|
+
* more than the cost of distribution plus a nominal fee, and must be
|
|
23
|
+
* freely redistributable under reasonable conditions. For an
|
|
24
|
+
* executable file, complete source code means the source code for all
|
|
25
|
+
* modules it contains. It does not include source code for modules or
|
|
26
|
+
* files that typically accompany the major components of the operating
|
|
27
|
+
* system on which the executable file runs.
|
|
28
|
+
*
|
|
29
|
+
* THIS SOFTWARE IS PROVIDED BY ORACLE ``AS IS'' AND ANY EXPRESS OR
|
|
30
|
+
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
31
|
+
* WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR
|
|
32
|
+
* NON-INFRINGEMENT, ARE DISCLAIMED. IN NO EVENT SHALL ORACLE BE LIABLE
|
|
33
|
+
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
34
|
+
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
35
|
+
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
|
|
36
|
+
* BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
|
37
|
+
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
|
|
38
|
+
* OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
|
|
39
|
+
* IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
40
|
+
*/
|
|
41
|
+
/*
|
|
42
|
+
* Copyright (c) 1990, 1993, 1994, 1995
|
|
43
|
+
* The Regents of the University of California. All rights reserved.
|
|
44
|
+
*
|
|
45
|
+
* Redistribution and use in source and binary forms, with or without
|
|
46
|
+
* modification, are permitted provided that the following conditions
|
|
47
|
+
* are met:
|
|
48
|
+
* 1. Redistributions of source code must retain the above copyright
|
|
49
|
+
* notice, this list of conditions and the following disclaimer.
|
|
50
|
+
* 2. Redistributions in binary form must reproduce the above copyright
|
|
51
|
+
* notice, this list of conditions and the following disclaimer in the
|
|
52
|
+
* documentation and/or other materials provided with the distribution.
|
|
53
|
+
* 3. Neither the name of the University nor the names of its contributors
|
|
54
|
+
* may be used to endorse or promote products derived from this software
|
|
55
|
+
* without specific prior written permission.
|
|
56
|
+
*
|
|
57
|
+
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
|
|
58
|
+
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
59
|
+
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
60
|
+
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
|
|
61
|
+
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
62
|
+
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
|
63
|
+
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
|
64
|
+
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
|
65
|
+
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
|
66
|
+
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
|
67
|
+
* SUCH DAMAGE.
|
|
68
|
+
*/
|
|
69
|
+
/*
|
|
70
|
+
* Copyright (c) 1995, 1996
|
|
71
|
+
* The President and Fellows of Harvard University. All rights reserved.
|
|
72
|
+
*
|
|
73
|
+
* Redistribution and use in source and binary forms, with or without
|
|
74
|
+
* modification, are permitted provided that the following conditions
|
|
75
|
+
* are met:
|
|
76
|
+
* 1. Redistributions of source code must retain the above copyright
|
|
77
|
+
* notice, this list of conditions and the following disclaimer.
|
|
78
|
+
* 2. Redistributions in binary form must reproduce the above copyright
|
|
79
|
+
* notice, this list of conditions and the following disclaimer in the
|
|
80
|
+
* documentation and/or other materials provided with the distribution.
|
|
81
|
+
* 3. Neither the name of the University nor the names of its contributors
|
|
82
|
+
* may be used to endorse or promote products derived from this software
|
|
83
|
+
* without specific prior written permission.
|
|
84
|
+
*
|
|
85
|
+
* THIS SOFTWARE IS PROVIDED BY HARVARD AND ITS CONTRIBUTORS ``AS IS'' AND
|
|
86
|
+
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
87
|
+
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
88
|
+
* ARE DISCLAIMED. IN NO EVENT SHALL HARVARD OR ITS CONTRIBUTORS BE LIABLE
|
|
89
|
+
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
90
|
+
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
|
91
|
+
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
|
92
|
+
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
|
93
|
+
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
|
94
|
+
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
|
95
|
+
* SUCH DAMAGE.
|
|
96
|
+
*/
|
|
97
|
+
=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
|
|
98
|
+
/***
|
|
99
|
+
* ASM: a very small and fast Java bytecode manipulation framework
|
|
100
|
+
* Copyright (c) 2000-2005 INRIA, France Telecom
|
|
101
|
+
* All rights reserved.
|
|
102
|
+
*
|
|
103
|
+
* Redistribution and use in source and binary forms, with or without
|
|
104
|
+
* modification, are permitted provided that the following conditions
|
|
105
|
+
* are met:
|
|
106
|
+
* 1. Redistributions of source code must retain the above copyright
|
|
107
|
+
* notice, this list of conditions and the following disclaimer.
|
|
108
|
+
* 2. Redistributions in binary form must reproduce the above copyright
|
|
109
|
+
* notice, this list of conditions and the following disclaimer in the
|
|
110
|
+
* documentation and/or other materials provided with the distribution.
|
|
111
|
+
* 3. Neither the name of the copyright holders nor the names of its
|
|
112
|
+
* contributors may be used to endorse or promote products derived from
|
|
113
|
+
* this software without specific prior written permission.
|
|
114
|
+
*
|
|
115
|
+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
116
|
+
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
117
|
+
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
118
|
+
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
|
119
|
+
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
120
|
+
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
121
|
+
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
122
|
+
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
123
|
+
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
124
|
+
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
|
|
125
|
+
* THE POSSIBILITY OF SUCH DAMAGE.
|
|
126
|
+
*/
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
This program, "bzip2", the associated library "libbzip2", and all
|
|
2
|
+
documentation, are copyright (C) 1996-2010 Julian R Seward. All
|
|
3
|
+
rights reserved.
|
|
4
|
+
|
|
5
|
+
Redistribution and use in source and binary forms, with or without
|
|
6
|
+
modification, are permitted provided that the following conditions
|
|
7
|
+
are met:
|
|
8
|
+
|
|
9
|
+
1. Redistributions of source code must retain the above copyright
|
|
10
|
+
notice, this list of conditions and the following disclaimer.
|
|
11
|
+
|
|
12
|
+
2. The origin of this software must not be misrepresented; you must
|
|
13
|
+
not claim that you wrote the original software. If you use this
|
|
14
|
+
software in a product, an acknowledgment in the product
|
|
15
|
+
documentation would be appreciated but is not required.
|
|
16
|
+
|
|
17
|
+
3. Altered source versions must be plainly marked as such, and must
|
|
18
|
+
not be misrepresented as being the original software.
|
|
19
|
+
|
|
20
|
+
4. The name of the author may not be used to endorse or promote
|
|
21
|
+
products derived from this software without specific prior written
|
|
22
|
+
permission.
|
|
23
|
+
|
|
24
|
+
THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS
|
|
25
|
+
OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
26
|
+
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
27
|
+
ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
|
|
28
|
+
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
29
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
|
|
30
|
+
GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
31
|
+
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
|
32
|
+
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
|
33
|
+
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
34
|
+
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
35
|
+
|
|
36
|
+
Julian Seward, jseward@bzip.org
|
|
37
|
+
bzip2/libbzip2 version 1.0.6 of 6 September 2010
|