@walkhi/code-relax 0.1.0-beta.30 → 0.1.0-beta.31
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/README.md +95 -89
- package/dist/bin/cleanup-accounts.mjs +63 -63
- package/dist/bin/codex-remote.mjs +374 -367
- package/dist/bin/manage-relay-admin.mjs +20 -20
- package/dist/bin/migrate-relay-db.mjs +109 -109
- package/dist/bin/publish-android-release.mjs +62 -58
- package/dist/bin/self-relay-admin.mjs +8 -8
- package/dist/bin/self-relay-demo.mjs +33 -33
- package/dist/bin/self-relay-server.mjs +66 -66
- package/dist/build-platform.json +1 -1
- package/dist/get-lan-networks.ps1 +40 -40
- package/dist/get-shared-service-url.ps1 +6 -6
- package/dist/shared/app-server-events.cjs +422 -412
- package/dist/shared/code-browser.cjs +159 -159
- package/dist/shared/file-changes.cjs +31 -31
- package/dist/shared/file-citations.cjs +117 -117
- package/dist/shared/file-mentions.cjs +92 -92
- package/dist/shared/message-delivery.cjs +54 -54
- package/dist/shared/p2p-data-channel.cjs +72 -72
- package/dist/shared/p2p-diagnostics.cjs +28 -28
- package/dist/shared/p2p-file-channel.cjs +74 -74
- package/dist/shared/plan-pricing.cjs +32 -32
- package/dist/shared/question-attention.cjs +40 -0
- package/dist/shared/server-requests.cjs +219 -219
- package/dist/shared/timeline-sync.cjs +173 -173
- package/dist/skills/code-relax/SKILL.md +22 -22
- package/dist/src/archived-threads.mjs +22 -0
- package/dist/src/background-process.mjs +18 -18
- package/dist/src/bounded-process-log.mjs +63 -63
- package/dist/src/celsius-client.mjs +107 -107
- package/dist/src/code-browser.mjs +100 -100
- package/dist/src/desktop-launcher.mjs +46 -46
- package/dist/src/dots-approval-timeline.mjs +99 -99
- package/dist/src/dots-avatar.mjs +42 -42
- package/dist/src/dots-chat-projection.mjs +50 -50
- package/dist/src/dots-chat-server.mjs +461 -461
- package/dist/src/dots-message-images.mjs +319 -319
- package/dist/src/dots-profile.mjs +77 -77
- package/dist/src/dots-room-activity.mjs +94 -94
- package/dist/src/dots-room-client.mjs +135 -135
- package/dist/src/dots-room-projection.mjs +94 -94
- package/dist/src/dots-schedules.mjs +66 -66
- package/dist/src/dots-summary-tasks.mjs +32 -32
- package/dist/src/dots-task-catalog.mjs +430 -430
- package/dist/src/dots-task-files.mjs +52 -52
- package/dist/src/execution-health.mjs +26 -26
- package/dist/src/file-transfer-range.mjs +22 -22
- package/dist/src/file-upload-session.mjs +130 -130
- package/dist/src/hosted-history-app-server.mjs +103 -103
- package/dist/src/hosted-history-cache.mjs +113 -113
- package/dist/src/lan-socket-server.mjs +265 -263
- package/dist/src/managed-app-server.mjs +244 -244
- package/dist/src/message-images.mjs +9 -9
- package/dist/src/message-upload-files.mjs +74 -74
- package/dist/src/onboarding.mjs +12 -12
- package/dist/src/plan-pricing.mjs +47 -47
- package/dist/src/platform/README.md +12 -12
- package/dist/src/platform/codex-protocol.mjs +46 -46
- package/dist/src/platform/codex-runtime.mjs +3 -3
- package/dist/src/platform/credential-store.mjs +3 -3
- package/dist/src/platform/desktop-host.mjs +3 -3
- package/dist/src/platform/desktop-project-registration.mjs +3 -3
- package/dist/src/platform/desktop-project-store.mjs +208 -149
- package/dist/src/platform/file-opener.mjs +9 -0
- package/dist/src/platform/linux/README.md +3 -3
- package/dist/src/platform/linux/codex-runtime.mjs +56 -0
- package/dist/src/platform/linux/credential-store.mjs +28 -0
- package/dist/src/platform/linux/desktop-host.mjs +83 -0
- package/dist/src/platform/linux/desktop-installation.mjs +36 -0
- package/dist/src/platform/linux/desktop-project-registration.mjs +11 -0
- package/dist/src/platform/linux/network-info.mjs +11 -0
- package/dist/src/platform/linux/process.mjs +42 -0
- package/dist/src/platform/linux/project-locations.mjs +17 -0
- package/dist/src/platform/linux/resident-startup.mjs +52 -0
- package/dist/src/platform/linux/retained-runtime.mjs +76 -0
- package/dist/src/platform/linux/service-host.mjs +18 -0
- package/dist/src/platform/linux/system.mjs +49 -0
- package/dist/src/platform/linux/user-environment.mjs +7 -0
- package/dist/src/platform/macos/README.md +3 -3
- package/dist/src/platform/macos/codex-runtime.mjs +125 -125
- package/dist/src/platform/macos/credential-store.mjs +10 -10
- package/dist/src/platform/macos/desktop-host.mjs +82 -82
- package/dist/src/platform/macos/desktop-project-registration.mjs +11 -10
- package/dist/src/platform/macos/network-info.mjs +8 -28
- package/dist/src/platform/macos/project-locations.mjs +8 -8
- package/dist/src/platform/macos/resident-startup.mjs +84 -84
- package/dist/src/platform/macos/retained-runtime.mjs +127 -127
- package/dist/src/platform/macos/service-host.mjs +18 -18
- package/dist/src/platform/macos/system.mjs +29 -29
- package/dist/src/platform/macos/user-environment.mjs +7 -7
- package/dist/src/platform/native-package.mjs +58 -46
- package/dist/src/platform/network-info.mjs +3 -3
- package/dist/src/platform/path-identity.mjs +5 -5
- package/dist/src/platform/posix/network-candidates.mjs +23 -0
- package/dist/src/platform/posix/runtime-paths.mjs +25 -25
- package/dist/src/platform/project-locations.mjs +3 -3
- package/dist/src/platform/runtime-paths.mjs +8 -8
- package/dist/src/platform/select.mjs +5 -5
- package/dist/src/platform/service-host.mjs +3 -3
- package/dist/src/platform/user-environment.mjs +3 -3
- package/dist/src/platform/windows/IsolatedProcess.cs +79 -79
- package/dist/src/platform/windows/activate-desktop.ps1 +48 -48
- package/dist/src/platform/windows/background-process.mjs +30 -30
- package/dist/src/platform/windows/codex-runtime.mjs +215 -215
- package/dist/src/platform/windows/credential-store.mjs +27 -27
- package/dist/src/platform/windows/credential-store.ps1 +49 -49
- package/dist/src/platform/windows/desktop-helper.mjs +179 -179
- package/dist/src/platform/windows/desktop-host.mjs +58 -58
- package/dist/src/platform/windows/desktop-info.ps1 +14 -14
- package/dist/src/platform/windows/desktop-project-registration.mjs +4 -4
- package/dist/src/platform/windows/desktop-runtime.ps1 +10 -10
- package/dist/src/platform/windows/desktop-shortcuts.ps1 +21 -21
- package/dist/src/platform/windows/dots-profile.mjs +49 -49
- package/dist/src/platform/windows/launch-worker.mjs +41 -41
- package/dist/src/platform/windows/network-info.mjs +34 -34
- package/dist/src/platform/windows/project-locations.mjs +16 -16
- package/dist/src/platform/windows/resident-start.js +20 -20
- package/dist/src/platform/windows/resident-start.ps1 +7 -7
- package/dist/src/platform/windows/resident-startup.ps1 +83 -83
- package/dist/src/platform/windows/resolve-state-root.ps1 +14 -14
- package/dist/src/platform/windows/runtime-paths.mjs +20 -20
- package/dist/src/platform/windows/rustdesk-video-probe.mjs +167 -167
- package/dist/src/platform/windows/service-host.mjs +31 -31
- package/dist/src/platform/windows/start-hidden-console.ps1 +49 -49
- package/dist/src/platform/windows/stop-desktop.ps1 +22 -22
- package/dist/src/platform/windows/user-environment.mjs +50 -50
- package/dist/src/platform/windows/user-environment.ps1 +41 -41
- package/dist/src/project-archive.mjs +30 -0
- package/dist/src/project-create.mjs +121 -119
- package/dist/src/project-management.mjs +59 -69
- package/dist/src/project-remove.mjs +55 -0
- package/dist/src/project-update.mjs +56 -0
- package/dist/src/runtime-discovery.mjs +47 -47
- package/dist/src/self-relay/account-cleanup.mjs +30 -30
- package/dist/src/self-relay/account-connections.mjs +52 -52
- package/dist/src/self-relay/admin-cloud.mjs +101 -101
- package/dist/src/self-relay/admin-page.mjs +80 -80
- package/dist/src/self-relay/admin-server.mjs +74 -74
- package/dist/src/self-relay/admin-state.mjs +127 -127
- package/dist/src/self-relay/admin-style.mjs +14 -14
- package/dist/src/self-relay/android-releases.mjs +61 -61
- package/dist/src/self-relay/client.mjs +61 -61
- package/dist/src/self-relay/connector.mjs +439 -438
- package/dist/src/self-relay/debug-allowlist.mjs +33 -33
- package/dist/src/self-relay/demo.mjs +37 -37
- package/dist/src/self-relay/device-info.mjs +27 -0
- package/dist/src/self-relay/device-store.mjs +92 -84
- package/dist/src/self-relay/diagnostics.mjs +54 -54
- package/dist/src/self-relay/email-accounts.mjs +183 -183
- package/dist/src/self-relay/file-wire.mjs +13 -13
- package/dist/src/self-relay/lan-authorizations.mjs +71 -42
- package/dist/src/self-relay/lifecycle.mjs +264 -250
- package/dist/src/self-relay/long-pairing.mjs +40 -40
- package/dist/src/self-relay/mysql-state.mjs +118 -105
- package/dist/src/self-relay/p2p-probe.mjs +159 -159
- package/dist/src/self-relay/privacy-page.mjs +26 -26
- package/dist/src/self-relay/public-releases.mjs +34 -0
- package/dist/src/self-relay/rtc-file-bridge.mjs +41 -41
- package/dist/src/self-relay/server.mjs +1006 -932
- package/dist/src/self-relay/usage-daily.mjs +55 -55
- package/dist/src/self-relay/usage-meter.mjs +141 -141
- package/dist/src/self-relay/windows-route-helper.mjs +75 -75
- package/dist/src/self-relay/wire.mjs +36 -36
- package/dist/src/server.mjs +1861 -1778
- package/dist/src/service-control.mjs +82 -82
- package/dist/src/service-doctor.mjs +110 -110
- package/dist/src/service-health.mjs +33 -33
- package/dist/src/service-install.mjs +91 -91
- package/dist/src/service-lifecycle.mjs +138 -138
- package/dist/src/service-restart.mjs +81 -81
- package/dist/src/shared-app-server.mjs +615 -611
- package/dist/src/shared-catalog.mjs +132 -132
- package/dist/src/shared-recovery.mjs +146 -146
- package/dist/src/shared-thread-stream.mjs +164 -164
- package/dist/src/skill-install.mjs +51 -51
- package/dist/src/thread-title-generation.mjs +150 -150
- package/dist/src/turn-model-history.mjs +47 -47
- package/dist/src/usage-reset.mjs +17 -17
- package/dist/src/workstation-downloads.mjs +120 -120
- package/dist/tools/rtc-route-helper.ps1 +99 -99
- package/dist/tools/start-rtc-route-helper.ps1 +15 -15
- package/dist/web/vendor/markdown-it.LICENSE.txt +22 -22
- package/dist/web/vendor/markdown-it.umd.min.js +13 -13
- package/package.json +7 -6
- package/tools/postinstall.mjs +127 -127
|
@@ -1,120 +1,120 @@
|
|
|
1
|
-
import fs from 'node:fs/promises';
|
|
2
|
-
import { constants } from 'node:fs';
|
|
3
|
-
import path from 'node:path';
|
|
4
|
-
import crypto from 'node:crypto';
|
|
5
|
-
import { fileRevision, FILE_PART_BYTES, FILE_READ_LIMIT } from './file-transfer-range.mjs';
|
|
6
|
-
import { readRecord, writeRecord } from './service-lifecycle.mjs';
|
|
7
|
-
|
|
8
|
-
const fail = (message, statusCode = 400) => { throw Object.assign(new Error(message), { statusCode }); };
|
|
9
|
-
const pathKey = value => process.platform === 'win32' ? value.toLowerCase() : value;
|
|
10
|
-
const validName = value => typeof value === 'string' && value.length > 0
|
|
11
|
-
&& !/[<>:"/\\|?*\x00-\x1f]/.test(value) && !/[ .]$/.test(value) && !['.', '..'].includes(value)
|
|
12
|
-
&& !/^(?:con|prn|aux|nul|com[1-9¹²³]|lpt[1-9¹²³])(?:\.|$)/i.test(value);
|
|
13
|
-
|
|
14
|
-
// A saved copy belongs to the authorized external asset, rather than its current chat page.
|
|
15
|
-
export class WorkstationDownloads {
|
|
16
|
-
constructor(stateRoot) {
|
|
17
|
-
this.recordPath = path.join(stateRoot, 'external-file-copies.json');
|
|
18
|
-
this.copies = readRecord(this.recordPath) || {};
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
async copy(sourceId) {
|
|
22
|
-
const record = this.copies[sourceId];
|
|
23
|
-
if (!record) return null;
|
|
24
|
-
try {
|
|
25
|
-
const stat = await fs.stat(record.path);
|
|
26
|
-
return stat.isFile() && fileRevision(stat) === record.revision && stat.size === record.size ? record : null;
|
|
27
|
-
} catch (error) { if (['ENOENT', 'ENOTDIR'].includes(error.code)) return null; throw error; }
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
async folder(rpc, locations, selection) {
|
|
31
|
-
let root;
|
|
32
|
-
if (selection.target === 'downloads') root = locations.downloads;
|
|
33
|
-
else if (selection.target === 'project' && typeof selection.projectId === 'string' && selection.projectId) {
|
|
34
|
-
const { project } = await rpc('project/read', { projectId: selection.projectId });
|
|
35
|
-
const selected = selection.root || project.roots[0]?.path;
|
|
36
|
-
root = project.roots.find(item => pathKey(item.path) === pathKey(selected))?.path;
|
|
37
|
-
}
|
|
38
|
-
if (!root || !path.isAbsolute(root)) fail('请选择电脑项目或下载目录。');
|
|
39
|
-
const relative = selection.path || '';
|
|
40
|
-
if (typeof relative !== 'string' || relative.split('/').some(part => part && !validName(part))) fail('子文件夹路径无效。');
|
|
41
|
-
const realRoot = await fs.realpath(root), target = path.join(realRoot, ...relative.split('/'));
|
|
42
|
-
const realTarget = await fs.realpath(target), difference = path.relative(realRoot, realTarget);
|
|
43
|
-
if (difference === '..' || difference.startsWith('..' + path.sep) || path.isAbsolute(difference)) fail('只能选择当前目录内的子文件夹。');
|
|
44
|
-
if (!(await fs.stat(realTarget)).isDirectory()) fail('所选路径不是文件夹。');
|
|
45
|
-
return { root, path: relative, absolutePath: target, realPath: realTarget,
|
|
46
|
-
parent: relative ? relative.split('/').slice(0, -1).join('/') : null };
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
async browse(rpc, locations, selection) {
|
|
50
|
-
const folder = await this.folder(rpc, locations, selection);
|
|
51
|
-
const entries = (await fs.readdir(folder.realPath, { withFileTypes: true })).filter(entry => entry.isDirectory())
|
|
52
|
-
.map(entry => ({ name: entry.name, path: [folder.path, entry.name].filter(Boolean).join('/') }))
|
|
53
|
-
.sort((a, b) => a.name.localeCompare(b.name, 'zh-CN', { numeric: true }));
|
|
54
|
-
const { realPath, ...visible } = folder;
|
|
55
|
-
return { ...visible, entries };
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
async save(rpc, locations, selection, asset, readPart, signal) {
|
|
59
|
-
const folder = await this.folder(rpc, locations, selection);
|
|
60
|
-
const partial = path.join(folder.realPath, `.code-relax-${crypto.randomUUID()}.download.part`);
|
|
61
|
-
let handle, result, created = false;
|
|
62
|
-
try {
|
|
63
|
-
signal?.throwIfAborted();
|
|
64
|
-
handle = await fs.open(partial, 'wx', 0o600);
|
|
65
|
-
created = true;
|
|
66
|
-
let offset = 0, size, revision, name, mimeType;
|
|
67
|
-
do {
|
|
68
|
-
signal?.throwIfAborted();
|
|
69
|
-
const query = new URLSearchParams({ offset: String(offset), length: String(FILE_PART_BYTES) });
|
|
70
|
-
if (revision) query.set('revision', revision);
|
|
71
|
-
const part = await readPart(asset, query);
|
|
72
|
-
signal?.throwIfAborted();
|
|
73
|
-
if (offset === 0) {
|
|
74
|
-
size = part.size; revision = part.revision; name = part.name || asset.name; mimeType = part.mimeType || asset.mimeType;
|
|
75
|
-
if (!validName(name)) fail('文件名无效,请修正来源文件的名称。');
|
|
76
|
-
if (!Number.isSafeInteger(size) || size < 1 || size > FILE_READ_LIMIT || !revision) fail('文件大小无效或超过 20 MiB 上限。');
|
|
77
|
-
}
|
|
78
|
-
if (part.size !== size || part.revision !== revision || part.offset !== offset
|
|
79
|
-
|| !part.bytes?.length || part.bytes.length > Math.min(FILE_PART_BYTES, size - offset)) fail('文件内容发生变化或分片不完整。', 409);
|
|
80
|
-
let written = 0;
|
|
81
|
-
while (written < part.bytes.length) {
|
|
82
|
-
const { bytesWritten } = await handle.write(part.bytes, written, part.bytes.length - written, offset + written);
|
|
83
|
-
if (!bytesWritten) fail('电脑文件写入不完整。', 502);
|
|
84
|
-
written += bytesWritten;
|
|
85
|
-
}
|
|
86
|
-
offset += part.bytes.length;
|
|
87
|
-
} while (offset < size);
|
|
88
|
-
await handle.close(); handle = null;
|
|
89
|
-
signal?.throwIfAborted();
|
|
90
|
-
// Recheck the destination after the remote read, before creating the final copy.
|
|
91
|
-
const current = await this.folder(rpc, locations, selection);
|
|
92
|
-
if (pathKey(current.realPath) !== pathKey(folder.realPath)) fail('目标目录发生变化,请重新选择。', 409);
|
|
93
|
-
const extension = path.extname(name), body = name.slice(0, name.length - extension.length);
|
|
94
|
-
for (let number = 0; ; number++) {
|
|
95
|
-
const filename = number ? `${body} (${number})${extension}` : name;
|
|
96
|
-
const destination = path.join(folder.realPath, filename);
|
|
97
|
-
try {
|
|
98
|
-
await fs.copyFile(partial, destination, constants.COPYFILE_EXCL);
|
|
99
|
-
const stat = await fs.stat(destination);
|
|
100
|
-
if (stat.size !== size) { await fs.unlink(destination); fail('电脑副本大小核对失败。', 502); }
|
|
101
|
-
result = { path: destination, name: filename, size, revision: fileRevision(stat), mimeType };
|
|
102
|
-
break;
|
|
103
|
-
} catch (error) { if (error.code !== 'EEXIST') throw error; }
|
|
104
|
-
}
|
|
105
|
-
this.copies[asset.id] = result;
|
|
106
|
-
try { writeRecord(this.recordPath, this.copies); }
|
|
107
|
-
catch (error) { result.warning = '文件已保存,副本记录未能持久保存;重启后请从原文件重新选择。'; console.error('电脑副本记录保存失败', error); }
|
|
108
|
-
return result;
|
|
109
|
-
} finally {
|
|
110
|
-
await handle?.close();
|
|
111
|
-
if (created) {
|
|
112
|
-
try { await fs.rm(partial, { force: true }); }
|
|
113
|
-
catch (error) {
|
|
114
|
-
if (!result) throw error;
|
|
115
|
-
result.warning = '文件已保存,下载临时文件未能清理。'; console.error('电脑下载临时文件清理失败', error);
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
}
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import { constants } from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import crypto from 'node:crypto';
|
|
5
|
+
import { fileRevision, FILE_PART_BYTES, FILE_READ_LIMIT } from './file-transfer-range.mjs';
|
|
6
|
+
import { readRecord, writeRecord } from './service-lifecycle.mjs';
|
|
7
|
+
|
|
8
|
+
const fail = (message, statusCode = 400) => { throw Object.assign(new Error(message), { statusCode }); };
|
|
9
|
+
const pathKey = value => process.platform === 'win32' ? value.toLowerCase() : value;
|
|
10
|
+
const validName = value => typeof value === 'string' && value.length > 0
|
|
11
|
+
&& !/[<>:"/\\|?*\x00-\x1f]/.test(value) && !/[ .]$/.test(value) && !['.', '..'].includes(value)
|
|
12
|
+
&& !/^(?:con|prn|aux|nul|com[1-9¹²³]|lpt[1-9¹²³])(?:\.|$)/i.test(value);
|
|
13
|
+
|
|
14
|
+
// A saved copy belongs to the authorized external asset, rather than its current chat page.
|
|
15
|
+
export class WorkstationDownloads {
|
|
16
|
+
constructor(stateRoot) {
|
|
17
|
+
this.recordPath = path.join(stateRoot, 'external-file-copies.json');
|
|
18
|
+
this.copies = readRecord(this.recordPath) || {};
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async copy(sourceId) {
|
|
22
|
+
const record = this.copies[sourceId];
|
|
23
|
+
if (!record) return null;
|
|
24
|
+
try {
|
|
25
|
+
const stat = await fs.stat(record.path);
|
|
26
|
+
return stat.isFile() && fileRevision(stat) === record.revision && stat.size === record.size ? record : null;
|
|
27
|
+
} catch (error) { if (['ENOENT', 'ENOTDIR'].includes(error.code)) return null; throw error; }
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async folder(rpc, locations, selection) {
|
|
31
|
+
let root;
|
|
32
|
+
if (selection.target === 'downloads') root = locations.downloads;
|
|
33
|
+
else if (selection.target === 'project' && typeof selection.projectId === 'string' && selection.projectId) {
|
|
34
|
+
const { project } = await rpc('project/read', { projectId: selection.projectId });
|
|
35
|
+
const selected = selection.root || project.roots[0]?.path;
|
|
36
|
+
root = project.roots.find(item => pathKey(item.path) === pathKey(selected))?.path;
|
|
37
|
+
}
|
|
38
|
+
if (!root || !path.isAbsolute(root)) fail('请选择电脑项目或下载目录。');
|
|
39
|
+
const relative = selection.path || '';
|
|
40
|
+
if (typeof relative !== 'string' || relative.split('/').some(part => part && !validName(part))) fail('子文件夹路径无效。');
|
|
41
|
+
const realRoot = await fs.realpath(root), target = path.join(realRoot, ...relative.split('/'));
|
|
42
|
+
const realTarget = await fs.realpath(target), difference = path.relative(realRoot, realTarget);
|
|
43
|
+
if (difference === '..' || difference.startsWith('..' + path.sep) || path.isAbsolute(difference)) fail('只能选择当前目录内的子文件夹。');
|
|
44
|
+
if (!(await fs.stat(realTarget)).isDirectory()) fail('所选路径不是文件夹。');
|
|
45
|
+
return { root, path: relative, absolutePath: target, realPath: realTarget,
|
|
46
|
+
parent: relative ? relative.split('/').slice(0, -1).join('/') : null };
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async browse(rpc, locations, selection) {
|
|
50
|
+
const folder = await this.folder(rpc, locations, selection);
|
|
51
|
+
const entries = (await fs.readdir(folder.realPath, { withFileTypes: true })).filter(entry => entry.isDirectory())
|
|
52
|
+
.map(entry => ({ name: entry.name, path: [folder.path, entry.name].filter(Boolean).join('/') }))
|
|
53
|
+
.sort((a, b) => a.name.localeCompare(b.name, 'zh-CN', { numeric: true }));
|
|
54
|
+
const { realPath, ...visible } = folder;
|
|
55
|
+
return { ...visible, entries };
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async save(rpc, locations, selection, asset, readPart, signal) {
|
|
59
|
+
const folder = await this.folder(rpc, locations, selection);
|
|
60
|
+
const partial = path.join(folder.realPath, `.code-relax-${crypto.randomUUID()}.download.part`);
|
|
61
|
+
let handle, result, created = false;
|
|
62
|
+
try {
|
|
63
|
+
signal?.throwIfAborted();
|
|
64
|
+
handle = await fs.open(partial, 'wx', 0o600);
|
|
65
|
+
created = true;
|
|
66
|
+
let offset = 0, size, revision, name, mimeType;
|
|
67
|
+
do {
|
|
68
|
+
signal?.throwIfAborted();
|
|
69
|
+
const query = new URLSearchParams({ offset: String(offset), length: String(FILE_PART_BYTES) });
|
|
70
|
+
if (revision) query.set('revision', revision);
|
|
71
|
+
const part = await readPart(asset, query);
|
|
72
|
+
signal?.throwIfAborted();
|
|
73
|
+
if (offset === 0) {
|
|
74
|
+
size = part.size; revision = part.revision; name = part.name || asset.name; mimeType = part.mimeType || asset.mimeType;
|
|
75
|
+
if (!validName(name)) fail('文件名无效,请修正来源文件的名称。');
|
|
76
|
+
if (!Number.isSafeInteger(size) || size < 1 || size > FILE_READ_LIMIT || !revision) fail('文件大小无效或超过 20 MiB 上限。');
|
|
77
|
+
}
|
|
78
|
+
if (part.size !== size || part.revision !== revision || part.offset !== offset
|
|
79
|
+
|| !part.bytes?.length || part.bytes.length > Math.min(FILE_PART_BYTES, size - offset)) fail('文件内容发生变化或分片不完整。', 409);
|
|
80
|
+
let written = 0;
|
|
81
|
+
while (written < part.bytes.length) {
|
|
82
|
+
const { bytesWritten } = await handle.write(part.bytes, written, part.bytes.length - written, offset + written);
|
|
83
|
+
if (!bytesWritten) fail('电脑文件写入不完整。', 502);
|
|
84
|
+
written += bytesWritten;
|
|
85
|
+
}
|
|
86
|
+
offset += part.bytes.length;
|
|
87
|
+
} while (offset < size);
|
|
88
|
+
await handle.close(); handle = null;
|
|
89
|
+
signal?.throwIfAborted();
|
|
90
|
+
// Recheck the destination after the remote read, before creating the final copy.
|
|
91
|
+
const current = await this.folder(rpc, locations, selection);
|
|
92
|
+
if (pathKey(current.realPath) !== pathKey(folder.realPath)) fail('目标目录发生变化,请重新选择。', 409);
|
|
93
|
+
const extension = path.extname(name), body = name.slice(0, name.length - extension.length);
|
|
94
|
+
for (let number = 0; ; number++) {
|
|
95
|
+
const filename = number ? `${body} (${number})${extension}` : name;
|
|
96
|
+
const destination = path.join(folder.realPath, filename);
|
|
97
|
+
try {
|
|
98
|
+
await fs.copyFile(partial, destination, constants.COPYFILE_EXCL);
|
|
99
|
+
const stat = await fs.stat(destination);
|
|
100
|
+
if (stat.size !== size) { await fs.unlink(destination); fail('电脑副本大小核对失败。', 502); }
|
|
101
|
+
result = { path: destination, name: filename, size, revision: fileRevision(stat), mimeType };
|
|
102
|
+
break;
|
|
103
|
+
} catch (error) { if (error.code !== 'EEXIST') throw error; }
|
|
104
|
+
}
|
|
105
|
+
this.copies[asset.id] = result;
|
|
106
|
+
try { writeRecord(this.recordPath, this.copies); }
|
|
107
|
+
catch (error) { result.warning = '文件已保存,副本记录未能持久保存;重启后请从原文件重新选择。'; console.error('电脑副本记录保存失败', error); }
|
|
108
|
+
return result;
|
|
109
|
+
} finally {
|
|
110
|
+
await handle?.close();
|
|
111
|
+
if (created) {
|
|
112
|
+
try { await fs.rm(partial, { force: true }); }
|
|
113
|
+
catch (error) {
|
|
114
|
+
if (!result) throw error;
|
|
115
|
+
result.warning = '文件已保存,下载临时文件未能清理。'; console.error('电脑下载临时文件清理失败', error);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
@@ -1,99 +1,99 @@
|
|
|
1
|
-
param(
|
|
2
|
-
[Parameter(Mandatory = $true)][string]$Token,
|
|
3
|
-
[int]$IdleSeconds = 1800
|
|
4
|
-
)
|
|
5
|
-
|
|
6
|
-
$ErrorActionPreference = 'Stop'
|
|
7
|
-
$pipeName = 'CodeRelaxRtcRouteHelper'
|
|
8
|
-
$leases = @{}
|
|
9
|
-
$pipeSecurity = [System.IO.Pipes.PipeSecurity]::new()
|
|
10
|
-
$currentUser = [System.Security.Principal.WindowsIdentity]::GetCurrent().User
|
|
11
|
-
$pipeSecurity.AddAccessRule([System.IO.Pipes.PipeAccessRule]::new($currentUser, [System.IO.Pipes.PipeAccessRights]::ReadWrite,
|
|
12
|
-
[System.Security.AccessControl.AccessControlType]::Allow))
|
|
13
|
-
$pipeSecurity.AddAccessRule([System.IO.Pipes.PipeAccessRule]::new([System.Security.Principal.SecurityIdentifier]::new('S-1-5-18'),
|
|
14
|
-
[System.IO.Pipes.PipeAccessRights]::FullControl, [System.Security.AccessControl.AccessControlType]::Allow))
|
|
15
|
-
$pipeSecurity.AddAccessRule([System.IO.Pipes.PipeAccessRule]::new([System.Security.Principal.SecurityIdentifier]::new('S-1-5-32-544'),
|
|
16
|
-
[System.IO.Pipes.PipeAccessRights]::FullControl, [System.Security.AccessControl.AccessControlType]::Allow))
|
|
17
|
-
|
|
18
|
-
function Test-PublicIpv4([string]$Address) {
|
|
19
|
-
$parts = $Address.Split('.')
|
|
20
|
-
if ($parts.Count -ne 4) { return $false }
|
|
21
|
-
try { $bytes = $parts | ForEach-Object { [byte]$_ } } catch { return $false }
|
|
22
|
-
$a = $bytes[0]; $b = $bytes[1]
|
|
23
|
-
return $a -ne 0 -and $a -ne 10 -and $a -ne 127 -and $a -lt 224 -and -not ($a -eq 169 -and $b -eq 254) -and
|
|
24
|
-
-not ($a -eq 172 -and $b -ge 16 -and $b -le 31) -and -not ($a -eq 192 -and $b -eq 168) -and
|
|
25
|
-
-not ($a -eq 100 -and $b -ge 64 -and $b -le 127) -and -not ($a -eq 198 -and ($b -eq 18 -or $b -eq 19))
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
function Get-PhysicalDefaultRoute {
|
|
29
|
-
$routes = Get-NetRoute -DestinationPrefix '0.0.0.0/0' -AddressFamily IPv4 | Where-Object {
|
|
30
|
-
$adapter = Get-NetAdapter -InterfaceIndex $_.InterfaceIndex -ErrorAction SilentlyContinue
|
|
31
|
-
$_.NextHop -and $_.NextHop -ne '0.0.0.0' -and $adapter.Status -eq 'Up' -and $adapter.HardwareInterface -eq $true -and $adapter.Virtual -ne $true
|
|
32
|
-
} | Sort-Object @{ Expression = { $_.RouteMetric + $_.InterfaceMetric } }
|
|
33
|
-
foreach ($route in $routes) {
|
|
34
|
-
$adapter = Get-NetAdapter -InterfaceIndex $route.InterfaceIndex -ErrorAction SilentlyContinue
|
|
35
|
-
if ($adapter.Name -notmatch 'Mihomo|Meta|Tunnel|VPN|TAP|Wintun') { return $route }
|
|
36
|
-
}
|
|
37
|
-
throw '找不到活动物理 IPv4 默认路由'
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
function Clear-ExpiredRoutes {
|
|
41
|
-
$now = Get-Date
|
|
42
|
-
foreach ($address in @($leases.Keys)) {
|
|
43
|
-
if ($leases[$address].expires -gt $now) { continue }
|
|
44
|
-
$lease = $leases[$address]
|
|
45
|
-
if ($lease.created) {
|
|
46
|
-
Get-NetRoute -DestinationPrefix "$address/32" -InterfaceIndex $lease.ifIndex -NextHop $lease.nextHop -ErrorAction SilentlyContinue |
|
|
47
|
-
Remove-NetRoute -Confirm:$false -ErrorAction SilentlyContinue
|
|
48
|
-
}
|
|
49
|
-
$leases.Remove($address)
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
function Clear-AllRoutes {
|
|
54
|
-
foreach ($address in @($leases.Keys)) {
|
|
55
|
-
$lease = $leases[$address]
|
|
56
|
-
if ($lease.created) {
|
|
57
|
-
Get-NetRoute -DestinationPrefix "$address/32" -InterfaceIndex $lease.ifIndex -NextHop $lease.nextHop -ErrorAction SilentlyContinue |
|
|
58
|
-
Remove-NetRoute -Confirm:$false -ErrorAction SilentlyContinue
|
|
59
|
-
}
|
|
60
|
-
$leases.Remove($address)
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
try {
|
|
65
|
-
$deadline = (Get-Date).AddSeconds($IdleSeconds)
|
|
66
|
-
while ((Get-Date) -lt $deadline) {
|
|
67
|
-
Clear-ExpiredRoutes
|
|
68
|
-
$server = [System.IO.Pipes.NamedPipeServerStream]::new($pipeName, [System.IO.Pipes.PipeDirection]::InOut, 1,
|
|
69
|
-
[System.IO.Pipes.PipeTransmissionMode]::Byte, [System.IO.Pipes.PipeOptions]::Asynchronous, 0, 0, $pipeSecurity)
|
|
70
|
-
$writer = $null
|
|
71
|
-
try {
|
|
72
|
-
$wait = $server.WaitForConnectionAsync()
|
|
73
|
-
if (-not $wait.Wait(1000)) { continue }
|
|
74
|
-
$reader = [System.IO.StreamReader]::new($server)
|
|
75
|
-
$writer = [System.IO.StreamWriter]::new($server); $writer.AutoFlush = $true
|
|
76
|
-
$request = $reader.ReadLine() | ConvertFrom-Json
|
|
77
|
-
if ($request.token -cne $Token -or $request.action -ne 'add') { throw '请求未授权' }
|
|
78
|
-
$ttl = [Math]::Min([Math]::Max([int]$request.ttlSeconds, 15), 180)
|
|
79
|
-
$route = Get-PhysicalDefaultRoute
|
|
80
|
-
$rawAddresses = @($request.addresses)
|
|
81
|
-
$addresses = @($rawAddresses | Where-Object { Test-PublicIpv4 $_ } | Select-Object -Unique)
|
|
82
|
-
if ($addresses.Count -ne $rawAddresses.Count -or $addresses.Count -gt 8) { throw '路由目标无效' }
|
|
83
|
-
foreach ($address in $addresses) {
|
|
84
|
-
$created = $leases[$address].created -eq $true
|
|
85
|
-
if (-not $leases.ContainsKey($address) -and -not (Get-NetRoute -DestinationPrefix "$address/32" -InterfaceIndex $route.InterfaceIndex -NextHop $route.NextHop -ErrorAction SilentlyContinue)) {
|
|
86
|
-
New-NetRoute -DestinationPrefix "$address/32" -InterfaceIndex $route.InterfaceIndex -NextHop $route.NextHop -RouteMetric 5 -PolicyStore ActiveStore | Out-Null
|
|
87
|
-
$created = $true
|
|
88
|
-
}
|
|
89
|
-
$leases[$address] = @{ expires = (Get-Date).AddSeconds($ttl); ifIndex = $route.InterfaceIndex; nextHop = $route.NextHop; created = $created }
|
|
90
|
-
}
|
|
91
|
-
$writer.WriteLine((@{ ok = $true; count = $addresses.Count } | ConvertTo-Json -Compress))
|
|
92
|
-
}
|
|
93
|
-
catch {
|
|
94
|
-
if ($writer) { $writer.WriteLine((@{ ok = $false; error = $_.Exception.Message } | ConvertTo-Json -Compress)) }
|
|
95
|
-
}
|
|
96
|
-
finally { $server.Dispose() }
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
finally { Clear-AllRoutes }
|
|
1
|
+
param(
|
|
2
|
+
[Parameter(Mandatory = $true)][string]$Token,
|
|
3
|
+
[int]$IdleSeconds = 1800
|
|
4
|
+
)
|
|
5
|
+
|
|
6
|
+
$ErrorActionPreference = 'Stop'
|
|
7
|
+
$pipeName = 'CodeRelaxRtcRouteHelper'
|
|
8
|
+
$leases = @{}
|
|
9
|
+
$pipeSecurity = [System.IO.Pipes.PipeSecurity]::new()
|
|
10
|
+
$currentUser = [System.Security.Principal.WindowsIdentity]::GetCurrent().User
|
|
11
|
+
$pipeSecurity.AddAccessRule([System.IO.Pipes.PipeAccessRule]::new($currentUser, [System.IO.Pipes.PipeAccessRights]::ReadWrite,
|
|
12
|
+
[System.Security.AccessControl.AccessControlType]::Allow))
|
|
13
|
+
$pipeSecurity.AddAccessRule([System.IO.Pipes.PipeAccessRule]::new([System.Security.Principal.SecurityIdentifier]::new('S-1-5-18'),
|
|
14
|
+
[System.IO.Pipes.PipeAccessRights]::FullControl, [System.Security.AccessControl.AccessControlType]::Allow))
|
|
15
|
+
$pipeSecurity.AddAccessRule([System.IO.Pipes.PipeAccessRule]::new([System.Security.Principal.SecurityIdentifier]::new('S-1-5-32-544'),
|
|
16
|
+
[System.IO.Pipes.PipeAccessRights]::FullControl, [System.Security.AccessControl.AccessControlType]::Allow))
|
|
17
|
+
|
|
18
|
+
function Test-PublicIpv4([string]$Address) {
|
|
19
|
+
$parts = $Address.Split('.')
|
|
20
|
+
if ($parts.Count -ne 4) { return $false }
|
|
21
|
+
try { $bytes = $parts | ForEach-Object { [byte]$_ } } catch { return $false }
|
|
22
|
+
$a = $bytes[0]; $b = $bytes[1]
|
|
23
|
+
return $a -ne 0 -and $a -ne 10 -and $a -ne 127 -and $a -lt 224 -and -not ($a -eq 169 -and $b -eq 254) -and
|
|
24
|
+
-not ($a -eq 172 -and $b -ge 16 -and $b -le 31) -and -not ($a -eq 192 -and $b -eq 168) -and
|
|
25
|
+
-not ($a -eq 100 -and $b -ge 64 -and $b -le 127) -and -not ($a -eq 198 -and ($b -eq 18 -or $b -eq 19))
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function Get-PhysicalDefaultRoute {
|
|
29
|
+
$routes = Get-NetRoute -DestinationPrefix '0.0.0.0/0' -AddressFamily IPv4 | Where-Object {
|
|
30
|
+
$adapter = Get-NetAdapter -InterfaceIndex $_.InterfaceIndex -ErrorAction SilentlyContinue
|
|
31
|
+
$_.NextHop -and $_.NextHop -ne '0.0.0.0' -and $adapter.Status -eq 'Up' -and $adapter.HardwareInterface -eq $true -and $adapter.Virtual -ne $true
|
|
32
|
+
} | Sort-Object @{ Expression = { $_.RouteMetric + $_.InterfaceMetric } }
|
|
33
|
+
foreach ($route in $routes) {
|
|
34
|
+
$adapter = Get-NetAdapter -InterfaceIndex $route.InterfaceIndex -ErrorAction SilentlyContinue
|
|
35
|
+
if ($adapter.Name -notmatch 'Mihomo|Meta|Tunnel|VPN|TAP|Wintun') { return $route }
|
|
36
|
+
}
|
|
37
|
+
throw '找不到活动物理 IPv4 默认路由'
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function Clear-ExpiredRoutes {
|
|
41
|
+
$now = Get-Date
|
|
42
|
+
foreach ($address in @($leases.Keys)) {
|
|
43
|
+
if ($leases[$address].expires -gt $now) { continue }
|
|
44
|
+
$lease = $leases[$address]
|
|
45
|
+
if ($lease.created) {
|
|
46
|
+
Get-NetRoute -DestinationPrefix "$address/32" -InterfaceIndex $lease.ifIndex -NextHop $lease.nextHop -ErrorAction SilentlyContinue |
|
|
47
|
+
Remove-NetRoute -Confirm:$false -ErrorAction SilentlyContinue
|
|
48
|
+
}
|
|
49
|
+
$leases.Remove($address)
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function Clear-AllRoutes {
|
|
54
|
+
foreach ($address in @($leases.Keys)) {
|
|
55
|
+
$lease = $leases[$address]
|
|
56
|
+
if ($lease.created) {
|
|
57
|
+
Get-NetRoute -DestinationPrefix "$address/32" -InterfaceIndex $lease.ifIndex -NextHop $lease.nextHop -ErrorAction SilentlyContinue |
|
|
58
|
+
Remove-NetRoute -Confirm:$false -ErrorAction SilentlyContinue
|
|
59
|
+
}
|
|
60
|
+
$leases.Remove($address)
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
try {
|
|
65
|
+
$deadline = (Get-Date).AddSeconds($IdleSeconds)
|
|
66
|
+
while ((Get-Date) -lt $deadline) {
|
|
67
|
+
Clear-ExpiredRoutes
|
|
68
|
+
$server = [System.IO.Pipes.NamedPipeServerStream]::new($pipeName, [System.IO.Pipes.PipeDirection]::InOut, 1,
|
|
69
|
+
[System.IO.Pipes.PipeTransmissionMode]::Byte, [System.IO.Pipes.PipeOptions]::Asynchronous, 0, 0, $pipeSecurity)
|
|
70
|
+
$writer = $null
|
|
71
|
+
try {
|
|
72
|
+
$wait = $server.WaitForConnectionAsync()
|
|
73
|
+
if (-not $wait.Wait(1000)) { continue }
|
|
74
|
+
$reader = [System.IO.StreamReader]::new($server)
|
|
75
|
+
$writer = [System.IO.StreamWriter]::new($server); $writer.AutoFlush = $true
|
|
76
|
+
$request = $reader.ReadLine() | ConvertFrom-Json
|
|
77
|
+
if ($request.token -cne $Token -or $request.action -ne 'add') { throw '请求未授权' }
|
|
78
|
+
$ttl = [Math]::Min([Math]::Max([int]$request.ttlSeconds, 15), 180)
|
|
79
|
+
$route = Get-PhysicalDefaultRoute
|
|
80
|
+
$rawAddresses = @($request.addresses)
|
|
81
|
+
$addresses = @($rawAddresses | Where-Object { Test-PublicIpv4 $_ } | Select-Object -Unique)
|
|
82
|
+
if ($addresses.Count -ne $rawAddresses.Count -or $addresses.Count -gt 8) { throw '路由目标无效' }
|
|
83
|
+
foreach ($address in $addresses) {
|
|
84
|
+
$created = $leases[$address].created -eq $true
|
|
85
|
+
if (-not $leases.ContainsKey($address) -and -not (Get-NetRoute -DestinationPrefix "$address/32" -InterfaceIndex $route.InterfaceIndex -NextHop $route.NextHop -ErrorAction SilentlyContinue)) {
|
|
86
|
+
New-NetRoute -DestinationPrefix "$address/32" -InterfaceIndex $route.InterfaceIndex -NextHop $route.NextHop -RouteMetric 5 -PolicyStore ActiveStore | Out-Null
|
|
87
|
+
$created = $true
|
|
88
|
+
}
|
|
89
|
+
$leases[$address] = @{ expires = (Get-Date).AddSeconds($ttl); ifIndex = $route.InterfaceIndex; nextHop = $route.NextHop; created = $created }
|
|
90
|
+
}
|
|
91
|
+
$writer.WriteLine((@{ ok = $true; count = $addresses.Count } | ConvertTo-Json -Compress))
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
94
|
+
if ($writer) { $writer.WriteLine((@{ ok = $false; error = $_.Exception.Message } | ConvertTo-Json -Compress)) }
|
|
95
|
+
}
|
|
96
|
+
finally { $server.Dispose() }
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
finally { Clear-AllRoutes }
|
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
$ErrorActionPreference = 'Stop'
|
|
2
|
-
$roots = @(
|
|
3
|
-
(Join-Path $env:LOCALAPPDATA 'CodexRemote'),
|
|
4
|
-
(Join-Path $env:LOCALAPPDATA 'Packages\OpenAI.Codex_2p2nqsd0c76g0\LocalCache\Local\CodexRemote')
|
|
5
|
-
)
|
|
6
|
-
$config = $null
|
|
7
|
-
foreach ($root in $roots) {
|
|
8
|
-
$path = Join-Path $root 'self-relay-config.json'
|
|
9
|
-
if (-not (Test-Path -LiteralPath $path)) { continue }
|
|
10
|
-
$value = Get-Content -LiteralPath $path -Raw -Encoding UTF8 | ConvertFrom-Json
|
|
11
|
-
if ($value.p2pWindowsRouteHelper -eq $true -and $value.p2pWindowsRouteHelperToken) { $config = $value; break }
|
|
12
|
-
}
|
|
13
|
-
if (-not $config) { throw '未找到已启用的 RTC 路由辅助配置' }
|
|
14
|
-
$helper = Join-Path $PSScriptRoot 'rtc-route-helper.ps1'
|
|
15
|
-
& $helper -Token $config.p2pWindowsRouteHelperToken -IdleSeconds 1800
|
|
1
|
+
$ErrorActionPreference = 'Stop'
|
|
2
|
+
$roots = @(
|
|
3
|
+
(Join-Path $env:LOCALAPPDATA 'CodexRemote'),
|
|
4
|
+
(Join-Path $env:LOCALAPPDATA 'Packages\OpenAI.Codex_2p2nqsd0c76g0\LocalCache\Local\CodexRemote')
|
|
5
|
+
)
|
|
6
|
+
$config = $null
|
|
7
|
+
foreach ($root in $roots) {
|
|
8
|
+
$path = Join-Path $root 'self-relay-config.json'
|
|
9
|
+
if (-not (Test-Path -LiteralPath $path)) { continue }
|
|
10
|
+
$value = Get-Content -LiteralPath $path -Raw -Encoding UTF8 | ConvertFrom-Json
|
|
11
|
+
if ($value.p2pWindowsRouteHelper -eq $true -and $value.p2pWindowsRouteHelperToken) { $config = $value; break }
|
|
12
|
+
}
|
|
13
|
+
if (-not $config) { throw '未找到已启用的 RTC 路由辅助配置' }
|
|
14
|
+
$helper = Join-Path $PSScriptRoot 'rtc-route-helper.ps1'
|
|
15
|
+
& $helper -Token $config.p2pWindowsRouteHelperToken -IdleSeconds 1800
|
|
@@ -1,22 +1,22 @@
|
|
|
1
|
-
Copyright (c) 2014 Vitaly Puzrin, Alex Kocharin.
|
|
2
|
-
|
|
3
|
-
Permission is hereby granted, free of charge, to any person
|
|
4
|
-
obtaining a copy of this software and associated documentation
|
|
5
|
-
files (the "Software"), to deal in the Software without
|
|
6
|
-
restriction, including without limitation the rights to use,
|
|
7
|
-
copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
8
|
-
copies of the Software, and to permit persons to whom the
|
|
9
|
-
Software is furnished to do so, subject to the following
|
|
10
|
-
conditions:
|
|
11
|
-
|
|
12
|
-
The above copyright notice and this permission notice shall be
|
|
13
|
-
included in all copies or substantial portions of the Software.
|
|
14
|
-
|
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
16
|
-
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
|
17
|
-
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
18
|
-
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
|
19
|
-
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
|
20
|
-
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
21
|
-
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
|
22
|
-
OTHER DEALINGS IN THE SOFTWARE.
|
|
1
|
+
Copyright (c) 2014 Vitaly Puzrin, Alex Kocharin.
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person
|
|
4
|
+
obtaining a copy of this software and associated documentation
|
|
5
|
+
files (the "Software"), to deal in the Software without
|
|
6
|
+
restriction, including without limitation the rights to use,
|
|
7
|
+
copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
8
|
+
copies of the Software, and to permit persons to whom the
|
|
9
|
+
Software is furnished to do so, subject to the following
|
|
10
|
+
conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be
|
|
13
|
+
included in all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
16
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
|
17
|
+
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
18
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
|
19
|
+
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
|
20
|
+
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
21
|
+
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
|
22
|
+
OTHER DEALINGS IN THE SOFTWARE.
|