@qpjoy/electron-tunnel 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +53 -0
- package/dist/admin/AdminServer.d.ts +10 -0
- package/dist/admin/AdminServer.js +362 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +92 -0
- package/dist/config/renderRuntimeConfig.d.ts +3 -0
- package/dist/config/renderRuntimeConfig.js +151 -0
- package/dist/createElectronTunnel.d.ts +21 -0
- package/dist/createElectronTunnel.js +49 -0
- package/dist/db/TunnelDatabase.d.ts +23 -0
- package/dist/db/TunnelDatabase.js +296 -0
- package/dist/db/schema.d.ts +1 -0
- package/dist/db/schema.js +70 -0
- package/dist/defaults.d.ts +18 -0
- package/dist/defaults.js +68 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +25 -0
- package/dist/ipc/registerTunnelIpc.d.ts +7 -0
- package/dist/ipc/registerTunnelIpc.js +70 -0
- package/dist/mihomo/MihomoApi.d.ts +11 -0
- package/dist/mihomo/MihomoApi.js +56 -0
- package/dist/mihomo/MihomoManager.d.ts +75 -0
- package/dist/mihomo/MihomoManager.js +635 -0
- package/dist/security.d.ts +3 -0
- package/dist/security.js +24 -0
- package/dist/system/electronProxy.d.ts +4 -0
- package/dist/system/electronProxy.js +30 -0
- package/dist/types.d.ts +98 -0
- package/dist/types.js +2 -0
- package/package.json +52 -0
- package/resources/engine/darwin-arm64/mihomo.gz +0 -0
- package/resources/engine/darwin-x64/mihomo.gz +0 -0
- package/resources/engine/linux-arm64/mihomo.gz +0 -0
- package/resources/engine/linux-x64/mihomo.gz +0 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { App, IpcMain, Session } from 'electron';
|
|
2
|
+
import { AdminServer } from './admin/AdminServer';
|
|
3
|
+
import { MihomoManager } from './mihomo/MihomoManager';
|
|
4
|
+
import type { TunnelManagerOptions, TunnelStatus } from './types';
|
|
5
|
+
export interface CreateElectronTunnelHost {
|
|
6
|
+
app: App;
|
|
7
|
+
ipcMain: IpcMain;
|
|
8
|
+
session: Session;
|
|
9
|
+
}
|
|
10
|
+
export interface CreateElectronTunnelOptions extends Partial<Omit<TunnelManagerOptions, 'userDataPath'>> {
|
|
11
|
+
userDataPath?: string;
|
|
12
|
+
startAdminServer?: boolean;
|
|
13
|
+
}
|
|
14
|
+
export interface ElectronTunnelHandle {
|
|
15
|
+
manager: MihomoManager;
|
|
16
|
+
admin: AdminServer;
|
|
17
|
+
applyProxy: () => Promise<void>;
|
|
18
|
+
status: () => TunnelStatus;
|
|
19
|
+
close: () => void;
|
|
20
|
+
}
|
|
21
|
+
export declare function createElectronTunnel(host: CreateElectronTunnelHost, options?: CreateElectronTunnelOptions): ElectronTunnelHandle;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createElectronTunnel = createElectronTunnel;
|
|
4
|
+
const fs_1 = require("fs");
|
|
5
|
+
const path_1 = require("path");
|
|
6
|
+
const AdminServer_1 = require("./admin/AdminServer");
|
|
7
|
+
const registerTunnelIpc_1 = require("./ipc/registerTunnelIpc");
|
|
8
|
+
const MihomoManager_1 = require("./mihomo/MihomoManager");
|
|
9
|
+
const electronProxy_1 = require("./system/electronProxy");
|
|
10
|
+
function defaultBundledEngineDir() {
|
|
11
|
+
const resourcesPath = process.resourcesPath ?? process.cwd();
|
|
12
|
+
const packageDir = typeof __dirname === 'undefined' ? process.cwd() : __dirname;
|
|
13
|
+
const candidates = [
|
|
14
|
+
(0, path_1.join)(resourcesPath, 'qpjoy-tunnel-engine'),
|
|
15
|
+
(0, path_1.join)(resourcesPath, 'mihomo'),
|
|
16
|
+
(0, path_1.resolve)(packageDir, '../resources/engine'),
|
|
17
|
+
(0, path_1.resolve)(process.cwd(), 'resources/qpjoy-tunnel-engine'),
|
|
18
|
+
(0, path_1.resolve)(process.cwd(), 'resources/mihomo')
|
|
19
|
+
];
|
|
20
|
+
return candidates.find((candidate) => (0, fs_1.existsSync)(candidate)) ?? candidates[0];
|
|
21
|
+
}
|
|
22
|
+
function createElectronTunnel(host, options = {}) {
|
|
23
|
+
const manager = new MihomoManager_1.MihomoManager({
|
|
24
|
+
...options,
|
|
25
|
+
userDataPath: options.userDataPath ?? host.app.getPath('userData'),
|
|
26
|
+
bundledEngineDir: options.bundledEngineDir ?? defaultBundledEngineDir()
|
|
27
|
+
});
|
|
28
|
+
const admin = new AdminServer_1.AdminServer(manager);
|
|
29
|
+
async function applyProxy() {
|
|
30
|
+
const status = manager.status();
|
|
31
|
+
await (0, electronProxy_1.applyElectronProxy)(host.session, status.mode, status.ports);
|
|
32
|
+
}
|
|
33
|
+
if (options.startAdminServer !== false) {
|
|
34
|
+
admin.start();
|
|
35
|
+
}
|
|
36
|
+
(0, registerTunnelIpc_1.registerTunnelIpc)(host.ipcMain, manager, {
|
|
37
|
+
afterSettingsChange: applyProxy
|
|
38
|
+
});
|
|
39
|
+
return {
|
|
40
|
+
manager,
|
|
41
|
+
admin,
|
|
42
|
+
applyProxy,
|
|
43
|
+
status: () => manager.status(),
|
|
44
|
+
close: () => {
|
|
45
|
+
admin.stop();
|
|
46
|
+
manager.close();
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { DomainRule, DomainRuleKind, EventRecord, RuntimeSettings, SubscriptionInput, SubscriptionRecord, TunnelPorts } from '../types';
|
|
2
|
+
export declare class TunnelDatabase {
|
|
3
|
+
private readonly db;
|
|
4
|
+
constructor(dbPath: string, ports?: Partial<TunnelPorts>);
|
|
5
|
+
close(): void;
|
|
6
|
+
getSettings(): RuntimeSettings;
|
|
7
|
+
updateSettings(patch: Partial<Pick<RuntimeSettings, 'mode' | 'corePath' | 'tunInstalled' | 'activeSubscriptionId'>>): RuntimeSettings;
|
|
8
|
+
updatePorts(patch: Partial<Pick<TunnelPorts, 'mixed' | 'dns'>>): RuntimeSettings;
|
|
9
|
+
updateAdminPassword(username: string, password: string): RuntimeSettings;
|
|
10
|
+
listSubscriptions(): SubscriptionRecord[];
|
|
11
|
+
getSubscription(id: number): SubscriptionRecord | null;
|
|
12
|
+
getActiveSubscription(): SubscriptionRecord | null;
|
|
13
|
+
createSubscription(input: SubscriptionInput): SubscriptionRecord;
|
|
14
|
+
deleteSubscription(id: number): void;
|
|
15
|
+
setActiveSubscription(id: number): SubscriptionRecord;
|
|
16
|
+
updateSubscriptionContent(id: number, content: string, localPath: string): SubscriptionRecord;
|
|
17
|
+
listRules(): DomainRule[];
|
|
18
|
+
upsertRule(kind: DomainRuleKind, domain: string, source?: string): DomainRule;
|
|
19
|
+
removeRule(id: number): void;
|
|
20
|
+
addEvent(level: EventRecord['level'], message: string): void;
|
|
21
|
+
listEvents(limit?: number): EventRecord[];
|
|
22
|
+
private ensureDefaultSettings;
|
|
23
|
+
}
|
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.TunnelDatabase = void 0;
|
|
7
|
+
const better_sqlite3_1 = __importDefault(require("better-sqlite3"));
|
|
8
|
+
const fs_1 = require("fs");
|
|
9
|
+
const path_1 = require("path");
|
|
10
|
+
const defaults_1 = require("../defaults");
|
|
11
|
+
const security_1 = require("../security");
|
|
12
|
+
const schema_1 = require("./schema");
|
|
13
|
+
function nowIso() {
|
|
14
|
+
return new Date().toISOString();
|
|
15
|
+
}
|
|
16
|
+
function toBool(value) {
|
|
17
|
+
return Number(value) === 1;
|
|
18
|
+
}
|
|
19
|
+
function mergePorts(ports) {
|
|
20
|
+
return {
|
|
21
|
+
admin: ports.admin ?? defaults_1.DEFAULT_PORTS.admin,
|
|
22
|
+
controller: ports.controller ?? defaults_1.DEFAULT_PORTS.controller,
|
|
23
|
+
mixed: ports.mixed ?? defaults_1.DEFAULT_PORTS.mixed,
|
|
24
|
+
dns: ports.dns ?? defaults_1.DEFAULT_PORTS.dns
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
function mapSettings(row) {
|
|
28
|
+
return {
|
|
29
|
+
id: Number(row.id),
|
|
30
|
+
mode: String(row.mode),
|
|
31
|
+
ports: {
|
|
32
|
+
admin: Number(row.admin_port),
|
|
33
|
+
controller: Number(row.controller_port),
|
|
34
|
+
mixed: Number(row.mixed_port),
|
|
35
|
+
dns: Number(row.dns_port)
|
|
36
|
+
},
|
|
37
|
+
adminUser: String(row.admin_user),
|
|
38
|
+
adminPasswordHash: String(row.admin_password_hash),
|
|
39
|
+
controllerSecret: String(row.controller_secret),
|
|
40
|
+
corePath: row.core_path ? String(row.core_path) : null,
|
|
41
|
+
tunInstalled: toBool(row.tun_installed),
|
|
42
|
+
activeSubscriptionId: row.active_subscription_id ? Number(row.active_subscription_id) : null,
|
|
43
|
+
updatedAt: String(row.updated_at)
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
function mapSubscription(row) {
|
|
47
|
+
return {
|
|
48
|
+
id: Number(row.id),
|
|
49
|
+
name: String(row.name),
|
|
50
|
+
url: String(row.url),
|
|
51
|
+
username: String(row.username ?? ''),
|
|
52
|
+
password: String(row.password ?? ''),
|
|
53
|
+
localPath: row.local_path ? String(row.local_path) : null,
|
|
54
|
+
content: row.content ? String(row.content) : null,
|
|
55
|
+
active: toBool(row.active),
|
|
56
|
+
lastUpdatedAt: row.last_updated_at ? String(row.last_updated_at) : null,
|
|
57
|
+
createdAt: String(row.created_at),
|
|
58
|
+
updatedAt: String(row.updated_at)
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
function mapRule(row) {
|
|
62
|
+
return {
|
|
63
|
+
id: Number(row.id),
|
|
64
|
+
kind: String(row.kind),
|
|
65
|
+
domain: String(row.domain),
|
|
66
|
+
source: String(row.source),
|
|
67
|
+
enabled: toBool(row.enabled),
|
|
68
|
+
createdAt: String(row.created_at)
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
class TunnelDatabase {
|
|
72
|
+
db;
|
|
73
|
+
constructor(dbPath, ports = {}) {
|
|
74
|
+
(0, fs_1.mkdirSync)((0, path_1.dirname)(dbPath), { recursive: true });
|
|
75
|
+
this.db = new better_sqlite3_1.default(dbPath);
|
|
76
|
+
this.db.exec(schema_1.SCHEMA_SQL);
|
|
77
|
+
this.ensureDefaultSettings(mergePorts(ports));
|
|
78
|
+
}
|
|
79
|
+
close() {
|
|
80
|
+
this.db.close();
|
|
81
|
+
}
|
|
82
|
+
getSettings() {
|
|
83
|
+
const row = this.db.prepare('SELECT * FROM runtime_settings WHERE id = 1').get();
|
|
84
|
+
if (!row) {
|
|
85
|
+
throw new Error('runtime settings row is missing');
|
|
86
|
+
}
|
|
87
|
+
return mapSettings(row);
|
|
88
|
+
}
|
|
89
|
+
updateSettings(patch) {
|
|
90
|
+
const current = this.getSettings();
|
|
91
|
+
const next = {
|
|
92
|
+
mode: patch.mode ?? current.mode,
|
|
93
|
+
corePath: patch.corePath === undefined ? current.corePath : patch.corePath,
|
|
94
|
+
tunInstalled: patch.tunInstalled === undefined ? current.tunInstalled : patch.tunInstalled,
|
|
95
|
+
activeSubscriptionId: patch.activeSubscriptionId === undefined ? current.activeSubscriptionId : patch.activeSubscriptionId,
|
|
96
|
+
updatedAt: nowIso()
|
|
97
|
+
};
|
|
98
|
+
this.db.prepare(`
|
|
99
|
+
UPDATE runtime_settings
|
|
100
|
+
SET mode = @mode,
|
|
101
|
+
core_path = @corePath,
|
|
102
|
+
tun_installed = @tunInstalled,
|
|
103
|
+
active_subscription_id = @activeSubscriptionId,
|
|
104
|
+
updated_at = @updatedAt
|
|
105
|
+
WHERE id = 1
|
|
106
|
+
`).run({
|
|
107
|
+
...next,
|
|
108
|
+
tunInstalled: next.tunInstalled ? 1 : 0
|
|
109
|
+
});
|
|
110
|
+
return this.getSettings();
|
|
111
|
+
}
|
|
112
|
+
updatePorts(patch) {
|
|
113
|
+
const current = this.getSettings();
|
|
114
|
+
const mixedPort = patch.mixed ?? current.ports.mixed;
|
|
115
|
+
const dnsPort = patch.dns ?? current.ports.dns;
|
|
116
|
+
this.db.prepare(`
|
|
117
|
+
UPDATE runtime_settings
|
|
118
|
+
SET mixed_port = @mixedPort,
|
|
119
|
+
dns_port = @dnsPort,
|
|
120
|
+
updated_at = @updatedAt
|
|
121
|
+
WHERE id = 1
|
|
122
|
+
`).run({
|
|
123
|
+
mixedPort,
|
|
124
|
+
dnsPort,
|
|
125
|
+
updatedAt: nowIso()
|
|
126
|
+
});
|
|
127
|
+
return this.getSettings();
|
|
128
|
+
}
|
|
129
|
+
updateAdminPassword(username, password) {
|
|
130
|
+
this.db.prepare(`
|
|
131
|
+
UPDATE runtime_settings
|
|
132
|
+
SET admin_user = @username,
|
|
133
|
+
admin_password_hash = @passwordHash,
|
|
134
|
+
updated_at = @updatedAt
|
|
135
|
+
WHERE id = 1
|
|
136
|
+
`).run({
|
|
137
|
+
username,
|
|
138
|
+
passwordHash: (0, security_1.hashPassword)(password),
|
|
139
|
+
updatedAt: nowIso()
|
|
140
|
+
});
|
|
141
|
+
return this.getSettings();
|
|
142
|
+
}
|
|
143
|
+
listSubscriptions() {
|
|
144
|
+
const rows = this.db.prepare('SELECT * FROM subscriptions ORDER BY active DESC, updated_at DESC').all();
|
|
145
|
+
return rows.map(mapSubscription);
|
|
146
|
+
}
|
|
147
|
+
getSubscription(id) {
|
|
148
|
+
const row = this.db.prepare('SELECT * FROM subscriptions WHERE id = ?').get(id);
|
|
149
|
+
return row ? mapSubscription(row) : null;
|
|
150
|
+
}
|
|
151
|
+
getActiveSubscription() {
|
|
152
|
+
const row = this.db.prepare('SELECT * FROM subscriptions WHERE active = 1 ORDER BY updated_at DESC LIMIT 1').get();
|
|
153
|
+
return row ? mapSubscription(row) : null;
|
|
154
|
+
}
|
|
155
|
+
createSubscription(input) {
|
|
156
|
+
const stamp = nowIso();
|
|
157
|
+
const result = this.db.prepare(`
|
|
158
|
+
INSERT INTO subscriptions (name, url, username, password, created_at, updated_at)
|
|
159
|
+
VALUES (@name, @url, @username, @password, @createdAt, @updatedAt)
|
|
160
|
+
`).run({
|
|
161
|
+
name: input.name,
|
|
162
|
+
url: input.url,
|
|
163
|
+
username: input.username ?? '',
|
|
164
|
+
password: input.password ?? '',
|
|
165
|
+
createdAt: stamp,
|
|
166
|
+
updatedAt: stamp
|
|
167
|
+
});
|
|
168
|
+
const created = this.getSubscription(Number(result.lastInsertRowid));
|
|
169
|
+
if (!created) {
|
|
170
|
+
throw new Error('failed to create subscription');
|
|
171
|
+
}
|
|
172
|
+
if (this.listSubscriptions().length === 1) {
|
|
173
|
+
this.setActiveSubscription(created.id);
|
|
174
|
+
}
|
|
175
|
+
return this.getSubscription(created.id) ?? created;
|
|
176
|
+
}
|
|
177
|
+
deleteSubscription(id) {
|
|
178
|
+
this.db.prepare('DELETE FROM subscriptions WHERE id = ?').run(id);
|
|
179
|
+
const active = this.getActiveSubscription();
|
|
180
|
+
if (!active) {
|
|
181
|
+
this.updateSettings({ activeSubscriptionId: null });
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
setActiveSubscription(id) {
|
|
185
|
+
const subscription = this.getSubscription(id);
|
|
186
|
+
if (!subscription) {
|
|
187
|
+
throw new Error(`subscription not found: ${id}`);
|
|
188
|
+
}
|
|
189
|
+
const tx = this.db.transaction(() => {
|
|
190
|
+
this.db.prepare('UPDATE subscriptions SET active = 0').run();
|
|
191
|
+
this.db.prepare('UPDATE subscriptions SET active = 1, updated_at = ? WHERE id = ?').run(nowIso(), id);
|
|
192
|
+
this.updateSettings({ activeSubscriptionId: id });
|
|
193
|
+
});
|
|
194
|
+
tx();
|
|
195
|
+
const active = this.getSubscription(id);
|
|
196
|
+
if (!active) {
|
|
197
|
+
throw new Error(`subscription not found after activation: ${id}`);
|
|
198
|
+
}
|
|
199
|
+
return active;
|
|
200
|
+
}
|
|
201
|
+
updateSubscriptionContent(id, content, localPath) {
|
|
202
|
+
this.db.prepare(`
|
|
203
|
+
UPDATE subscriptions
|
|
204
|
+
SET content = @content,
|
|
205
|
+
local_path = @localPath,
|
|
206
|
+
last_updated_at = @lastUpdatedAt,
|
|
207
|
+
updated_at = @updatedAt
|
|
208
|
+
WHERE id = @id
|
|
209
|
+
`).run({
|
|
210
|
+
id,
|
|
211
|
+
content,
|
|
212
|
+
localPath,
|
|
213
|
+
lastUpdatedAt: nowIso(),
|
|
214
|
+
updatedAt: nowIso()
|
|
215
|
+
});
|
|
216
|
+
const subscription = this.getSubscription(id);
|
|
217
|
+
if (!subscription) {
|
|
218
|
+
throw new Error(`subscription not found: ${id}`);
|
|
219
|
+
}
|
|
220
|
+
return subscription;
|
|
221
|
+
}
|
|
222
|
+
listRules() {
|
|
223
|
+
const rows = this.db.prepare('SELECT * FROM domain_rules ORDER BY kind ASC, source ASC, domain ASC').all();
|
|
224
|
+
return rows.map(mapRule);
|
|
225
|
+
}
|
|
226
|
+
upsertRule(kind, domain, source = 'manual') {
|
|
227
|
+
const normalized = domain.trim().toLowerCase().replace(/^\.+/, '');
|
|
228
|
+
if (!normalized) {
|
|
229
|
+
throw new Error('domain is required');
|
|
230
|
+
}
|
|
231
|
+
const createdAt = nowIso();
|
|
232
|
+
this.db.prepare(`
|
|
233
|
+
INSERT INTO domain_rules (kind, domain, source, enabled, created_at)
|
|
234
|
+
VALUES (@kind, @domain, @source, 1, @createdAt)
|
|
235
|
+
ON CONFLICT(kind, domain) DO UPDATE SET
|
|
236
|
+
source = excluded.source,
|
|
237
|
+
enabled = 1
|
|
238
|
+
`).run({ kind, domain: normalized, source, createdAt });
|
|
239
|
+
const row = this.db.prepare('SELECT * FROM domain_rules WHERE kind = ? AND domain = ?').get(kind, normalized);
|
|
240
|
+
if (!row) {
|
|
241
|
+
throw new Error(`failed to upsert rule: ${normalized}`);
|
|
242
|
+
}
|
|
243
|
+
return mapRule(row);
|
|
244
|
+
}
|
|
245
|
+
removeRule(id) {
|
|
246
|
+
this.db.prepare('DELETE FROM domain_rules WHERE id = ?').run(id);
|
|
247
|
+
}
|
|
248
|
+
addEvent(level, message) {
|
|
249
|
+
this.db.prepare(`
|
|
250
|
+
INSERT INTO events (level, message, created_at)
|
|
251
|
+
VALUES (?, ?, ?)
|
|
252
|
+
`).run(level, message, nowIso());
|
|
253
|
+
}
|
|
254
|
+
listEvents(limit = 200) {
|
|
255
|
+
const rows = this.db.prepare(`
|
|
256
|
+
SELECT * FROM events
|
|
257
|
+
ORDER BY id DESC
|
|
258
|
+
LIMIT ?
|
|
259
|
+
`).all(limit);
|
|
260
|
+
return rows.map((row) => ({
|
|
261
|
+
id: Number(row.id),
|
|
262
|
+
level: String(row.level),
|
|
263
|
+
message: String(row.message),
|
|
264
|
+
createdAt: String(row.created_at)
|
|
265
|
+
}));
|
|
266
|
+
}
|
|
267
|
+
ensureDefaultSettings(ports) {
|
|
268
|
+
const row = this.db.prepare('SELECT id FROM runtime_settings WHERE id = 1').get();
|
|
269
|
+
if (row) {
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
this.db.prepare(`
|
|
273
|
+
INSERT INTO runtime_settings (
|
|
274
|
+
id, mode, admin_port, controller_port, mixed_port, dns_port,
|
|
275
|
+
admin_user, admin_password_hash, controller_secret, tun_installed,
|
|
276
|
+
updated_at
|
|
277
|
+
)
|
|
278
|
+
VALUES (
|
|
279
|
+
1, @mode, @adminPort, @controllerPort, @mixedPort, @dnsPort,
|
|
280
|
+
@adminUser, @adminPasswordHash, @controllerSecret, 0,
|
|
281
|
+
@updatedAt
|
|
282
|
+
)
|
|
283
|
+
`).run({
|
|
284
|
+
mode: defaults_1.DEFAULT_MODE,
|
|
285
|
+
adminPort: ports.admin,
|
|
286
|
+
controllerPort: ports.controller,
|
|
287
|
+
mixedPort: ports.mixed,
|
|
288
|
+
dnsPort: ports.dns,
|
|
289
|
+
adminUser: defaults_1.DEFAULT_ADMIN_USER,
|
|
290
|
+
adminPasswordHash: (0, security_1.hashPassword)(defaults_1.DEFAULT_ADMIN_PASSWORD),
|
|
291
|
+
controllerSecret: (0, defaults_1.createControllerSecret)(),
|
|
292
|
+
updatedAt: nowIso()
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
exports.TunnelDatabase = TunnelDatabase;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const SCHEMA_SQL = "\nPRAGMA journal_mode = WAL;\n\nCREATE TABLE IF NOT EXISTS runtime_settings (\n id INTEGER PRIMARY KEY CHECK (id = 1),\n mode TEXT NOT NULL,\n admin_port INTEGER NOT NULL,\n controller_port INTEGER NOT NULL,\n mixed_port INTEGER NOT NULL,\n dns_port INTEGER NOT NULL,\n admin_user TEXT NOT NULL,\n admin_password_hash TEXT NOT NULL,\n controller_secret TEXT NOT NULL,\n core_path TEXT,\n tun_installed INTEGER NOT NULL DEFAULT 0,\n active_subscription_id INTEGER,\n updated_at TEXT NOT NULL\n);\n\nCREATE TABLE IF NOT EXISTS subscriptions (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n name TEXT NOT NULL,\n url TEXT NOT NULL,\n username TEXT NOT NULL DEFAULT '',\n password TEXT NOT NULL DEFAULT '',\n local_path TEXT,\n content TEXT,\n active INTEGER NOT NULL DEFAULT 0,\n last_updated_at TEXT,\n created_at TEXT NOT NULL,\n updated_at TEXT NOT NULL\n);\n\nCREATE TABLE IF NOT EXISTS domain_rules (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n kind TEXT NOT NULL CHECK (kind IN ('allow', 'block')),\n domain TEXT NOT NULL,\n source TEXT NOT NULL DEFAULT 'manual',\n enabled INTEGER NOT NULL DEFAULT 1,\n created_at TEXT NOT NULL,\n UNIQUE(kind, domain)\n);\n\nCREATE TABLE IF NOT EXISTS core_versions (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n version TEXT NOT NULL,\n platform TEXT NOT NULL,\n arch TEXT NOT NULL,\n path TEXT NOT NULL,\n active INTEGER NOT NULL DEFAULT 0,\n created_at TEXT NOT NULL\n);\n\nCREATE TABLE IF NOT EXISTS traffic_snapshots (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n upload_bytes INTEGER NOT NULL DEFAULT 0,\n download_bytes INTEGER NOT NULL DEFAULT 0,\n created_at TEXT NOT NULL\n);\n\nCREATE TABLE IF NOT EXISTS events (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n level TEXT NOT NULL CHECK (level IN ('info', 'warn', 'error')),\n message TEXT NOT NULL,\n created_at TEXT NOT NULL\n);\n";
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SCHEMA_SQL = void 0;
|
|
4
|
+
exports.SCHEMA_SQL = `
|
|
5
|
+
PRAGMA journal_mode = WAL;
|
|
6
|
+
|
|
7
|
+
CREATE TABLE IF NOT EXISTS runtime_settings (
|
|
8
|
+
id INTEGER PRIMARY KEY CHECK (id = 1),
|
|
9
|
+
mode TEXT NOT NULL,
|
|
10
|
+
admin_port INTEGER NOT NULL,
|
|
11
|
+
controller_port INTEGER NOT NULL,
|
|
12
|
+
mixed_port INTEGER NOT NULL,
|
|
13
|
+
dns_port INTEGER NOT NULL,
|
|
14
|
+
admin_user TEXT NOT NULL,
|
|
15
|
+
admin_password_hash TEXT NOT NULL,
|
|
16
|
+
controller_secret TEXT NOT NULL,
|
|
17
|
+
core_path TEXT,
|
|
18
|
+
tun_installed INTEGER NOT NULL DEFAULT 0,
|
|
19
|
+
active_subscription_id INTEGER,
|
|
20
|
+
updated_at TEXT NOT NULL
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
CREATE TABLE IF NOT EXISTS subscriptions (
|
|
24
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
25
|
+
name TEXT NOT NULL,
|
|
26
|
+
url TEXT NOT NULL,
|
|
27
|
+
username TEXT NOT NULL DEFAULT '',
|
|
28
|
+
password TEXT NOT NULL DEFAULT '',
|
|
29
|
+
local_path TEXT,
|
|
30
|
+
content TEXT,
|
|
31
|
+
active INTEGER NOT NULL DEFAULT 0,
|
|
32
|
+
last_updated_at TEXT,
|
|
33
|
+
created_at TEXT NOT NULL,
|
|
34
|
+
updated_at TEXT NOT NULL
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
CREATE TABLE IF NOT EXISTS domain_rules (
|
|
38
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
39
|
+
kind TEXT NOT NULL CHECK (kind IN ('allow', 'block')),
|
|
40
|
+
domain TEXT NOT NULL,
|
|
41
|
+
source TEXT NOT NULL DEFAULT 'manual',
|
|
42
|
+
enabled INTEGER NOT NULL DEFAULT 1,
|
|
43
|
+
created_at TEXT NOT NULL,
|
|
44
|
+
UNIQUE(kind, domain)
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
CREATE TABLE IF NOT EXISTS core_versions (
|
|
48
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
49
|
+
version TEXT NOT NULL,
|
|
50
|
+
platform TEXT NOT NULL,
|
|
51
|
+
arch TEXT NOT NULL,
|
|
52
|
+
path TEXT NOT NULL,
|
|
53
|
+
active INTEGER NOT NULL DEFAULT 0,
|
|
54
|
+
created_at TEXT NOT NULL
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
CREATE TABLE IF NOT EXISTS traffic_snapshots (
|
|
58
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
59
|
+
upload_bytes INTEGER NOT NULL DEFAULT 0,
|
|
60
|
+
download_bytes INTEGER NOT NULL DEFAULT 0,
|
|
61
|
+
created_at TEXT NOT NULL
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
CREATE TABLE IF NOT EXISTS events (
|
|
65
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
66
|
+
level TEXT NOT NULL CHECK (level IN ('info', 'warn', 'error')),
|
|
67
|
+
message TEXT NOT NULL,
|
|
68
|
+
created_at TEXT NOT NULL
|
|
69
|
+
);
|
|
70
|
+
`;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { RuntimeMode, TunnelPorts } from './types';
|
|
2
|
+
export declare const DEFAULT_PORTS: TunnelPorts;
|
|
3
|
+
export declare const DEFAULT_MODE: RuntimeMode;
|
|
4
|
+
export declare const DEFAULT_ADMIN_USER = "admin";
|
|
5
|
+
export declare const DEFAULT_ADMIN_PASSWORD = "admin";
|
|
6
|
+
export declare function createControllerSecret(): string;
|
|
7
|
+
export declare const GEOX_URL: {
|
|
8
|
+
geoip: string;
|
|
9
|
+
geosite: string;
|
|
10
|
+
mmdb: string;
|
|
11
|
+
};
|
|
12
|
+
export declare const DOMAIN_PRESETS: {
|
|
13
|
+
readonly google: readonly ["google.com", "googleapis.com", "gstatic.com", "googleusercontent.com", "googlevideo.com", "ggpht.com", "gmail.com", "googlemail.com", "google-analytics.com", "googletagmanager.com", "googlesyndication.com", "doubleclick.net", "blogger.com", "chrome.com", "chromium.org"];
|
|
14
|
+
readonly youtube: readonly ["youtube.com", "youtu.be", "ytimg.com", "youtubei.googleapis.com", "youtube-nocookie.com", "googlevideo.com", "yt3.ggpht.com"];
|
|
15
|
+
readonly x: readonly ["x.com", "twitter.com", "t.co", "twimg.com", "tweetdeck.com", "ads-twitter.com", "pscp.tv", "periscope.tv"];
|
|
16
|
+
readonly telegram: readonly ["telegram.org", "telegram.me", "t.me", "tdesktop.com", "telegra.ph", "tdlib.org"];
|
|
17
|
+
};
|
|
18
|
+
export type DomainPresetId = keyof typeof DOMAIN_PRESETS;
|
package/dist/defaults.js
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DOMAIN_PRESETS = exports.GEOX_URL = exports.DEFAULT_ADMIN_PASSWORD = exports.DEFAULT_ADMIN_USER = exports.DEFAULT_MODE = exports.DEFAULT_PORTS = void 0;
|
|
4
|
+
exports.createControllerSecret = createControllerSecret;
|
|
5
|
+
const crypto_1 = require("crypto");
|
|
6
|
+
exports.DEFAULT_PORTS = {
|
|
7
|
+
admin: 23456,
|
|
8
|
+
controller: 23457,
|
|
9
|
+
mixed: 23458,
|
|
10
|
+
dns: 23459
|
|
11
|
+
};
|
|
12
|
+
exports.DEFAULT_MODE = 'app-rule';
|
|
13
|
+
exports.DEFAULT_ADMIN_USER = 'admin';
|
|
14
|
+
exports.DEFAULT_ADMIN_PASSWORD = 'admin';
|
|
15
|
+
function createControllerSecret() {
|
|
16
|
+
return (0, crypto_1.randomBytes)(24).toString('hex');
|
|
17
|
+
}
|
|
18
|
+
exports.GEOX_URL = {
|
|
19
|
+
geoip: 'https://testingcf.jsdelivr.net/gh/MetaCubeX/meta-rules-dat@release/geoip.dat',
|
|
20
|
+
geosite: 'https://testingcf.jsdelivr.net/gh/MetaCubeX/meta-rules-dat@release/geosite.dat',
|
|
21
|
+
mmdb: 'https://testingcf.jsdelivr.net/gh/MetaCubeX/meta-rules-dat@release/country.mmdb'
|
|
22
|
+
};
|
|
23
|
+
exports.DOMAIN_PRESETS = {
|
|
24
|
+
google: [
|
|
25
|
+
'google.com',
|
|
26
|
+
'googleapis.com',
|
|
27
|
+
'gstatic.com',
|
|
28
|
+
'googleusercontent.com',
|
|
29
|
+
'googlevideo.com',
|
|
30
|
+
'ggpht.com',
|
|
31
|
+
'gmail.com',
|
|
32
|
+
'googlemail.com',
|
|
33
|
+
'google-analytics.com',
|
|
34
|
+
'googletagmanager.com',
|
|
35
|
+
'googlesyndication.com',
|
|
36
|
+
'doubleclick.net',
|
|
37
|
+
'blogger.com',
|
|
38
|
+
'chrome.com',
|
|
39
|
+
'chromium.org'
|
|
40
|
+
],
|
|
41
|
+
youtube: [
|
|
42
|
+
'youtube.com',
|
|
43
|
+
'youtu.be',
|
|
44
|
+
'ytimg.com',
|
|
45
|
+
'youtubei.googleapis.com',
|
|
46
|
+
'youtube-nocookie.com',
|
|
47
|
+
'googlevideo.com',
|
|
48
|
+
'yt3.ggpht.com'
|
|
49
|
+
],
|
|
50
|
+
x: [
|
|
51
|
+
'x.com',
|
|
52
|
+
'twitter.com',
|
|
53
|
+
't.co',
|
|
54
|
+
'twimg.com',
|
|
55
|
+
'tweetdeck.com',
|
|
56
|
+
'ads-twitter.com',
|
|
57
|
+
'pscp.tv',
|
|
58
|
+
'periscope.tv'
|
|
59
|
+
],
|
|
60
|
+
telegram: [
|
|
61
|
+
'telegram.org',
|
|
62
|
+
'telegram.me',
|
|
63
|
+
't.me',
|
|
64
|
+
'tdesktop.com',
|
|
65
|
+
'telegra.ph',
|
|
66
|
+
'tdlib.org'
|
|
67
|
+
]
|
|
68
|
+
};
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export { AdminServer } from './admin/AdminServer';
|
|
2
|
+
export { createElectronTunnel } from './createElectronTunnel';
|
|
3
|
+
export { renderRuntimeConfig } from './config/renderRuntimeConfig';
|
|
4
|
+
export { TunnelDatabase } from './db/TunnelDatabase';
|
|
5
|
+
export { registerTunnelIpc } from './ipc/registerTunnelIpc';
|
|
6
|
+
export { MihomoApi, MihomoApi as TunnelApi } from './mihomo/MihomoApi';
|
|
7
|
+
export { MihomoManager, MihomoManager as TunnelManager } from './mihomo/MihomoManager';
|
|
8
|
+
export { applyElectronProxy, proxyEnv } from './system/electronProxy';
|
|
9
|
+
export { DEFAULT_PORTS, DOMAIN_PRESETS } from './defaults';
|
|
10
|
+
export type { CreateElectronTunnelHost, CreateElectronTunnelOptions, ElectronTunnelHandle } from './createElectronTunnel';
|
|
11
|
+
export type * from './types';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DOMAIN_PRESETS = exports.DEFAULT_PORTS = exports.proxyEnv = exports.applyElectronProxy = exports.TunnelManager = exports.MihomoManager = exports.TunnelApi = exports.MihomoApi = exports.registerTunnelIpc = exports.TunnelDatabase = exports.renderRuntimeConfig = exports.createElectronTunnel = exports.AdminServer = void 0;
|
|
4
|
+
var AdminServer_1 = require("./admin/AdminServer");
|
|
5
|
+
Object.defineProperty(exports, "AdminServer", { enumerable: true, get: function () { return AdminServer_1.AdminServer; } });
|
|
6
|
+
var createElectronTunnel_1 = require("./createElectronTunnel");
|
|
7
|
+
Object.defineProperty(exports, "createElectronTunnel", { enumerable: true, get: function () { return createElectronTunnel_1.createElectronTunnel; } });
|
|
8
|
+
var renderRuntimeConfig_1 = require("./config/renderRuntimeConfig");
|
|
9
|
+
Object.defineProperty(exports, "renderRuntimeConfig", { enumerable: true, get: function () { return renderRuntimeConfig_1.renderRuntimeConfig; } });
|
|
10
|
+
var TunnelDatabase_1 = require("./db/TunnelDatabase");
|
|
11
|
+
Object.defineProperty(exports, "TunnelDatabase", { enumerable: true, get: function () { return TunnelDatabase_1.TunnelDatabase; } });
|
|
12
|
+
var registerTunnelIpc_1 = require("./ipc/registerTunnelIpc");
|
|
13
|
+
Object.defineProperty(exports, "registerTunnelIpc", { enumerable: true, get: function () { return registerTunnelIpc_1.registerTunnelIpc; } });
|
|
14
|
+
var MihomoApi_1 = require("./mihomo/MihomoApi");
|
|
15
|
+
Object.defineProperty(exports, "MihomoApi", { enumerable: true, get: function () { return MihomoApi_1.MihomoApi; } });
|
|
16
|
+
Object.defineProperty(exports, "TunnelApi", { enumerable: true, get: function () { return MihomoApi_1.MihomoApi; } });
|
|
17
|
+
var MihomoManager_1 = require("./mihomo/MihomoManager");
|
|
18
|
+
Object.defineProperty(exports, "MihomoManager", { enumerable: true, get: function () { return MihomoManager_1.MihomoManager; } });
|
|
19
|
+
Object.defineProperty(exports, "TunnelManager", { enumerable: true, get: function () { return MihomoManager_1.MihomoManager; } });
|
|
20
|
+
var electronProxy_1 = require("./system/electronProxy");
|
|
21
|
+
Object.defineProperty(exports, "applyElectronProxy", { enumerable: true, get: function () { return electronProxy_1.applyElectronProxy; } });
|
|
22
|
+
Object.defineProperty(exports, "proxyEnv", { enumerable: true, get: function () { return electronProxy_1.proxyEnv; } });
|
|
23
|
+
var defaults_1 = require("./defaults");
|
|
24
|
+
Object.defineProperty(exports, "DEFAULT_PORTS", { enumerable: true, get: function () { return defaults_1.DEFAULT_PORTS; } });
|
|
25
|
+
Object.defineProperty(exports, "DOMAIN_PRESETS", { enumerable: true, get: function () { return defaults_1.DOMAIN_PRESETS; } });
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { IpcMain } from 'electron';
|
|
2
|
+
import type { MihomoManager } from '../mihomo/MihomoManager';
|
|
3
|
+
interface RegisterTunnelIpcOptions {
|
|
4
|
+
afterSettingsChange?: () => Promise<void> | void;
|
|
5
|
+
}
|
|
6
|
+
export declare function registerTunnelIpc(ipcMain: IpcMain, manager: MihomoManager, options?: RegisterTunnelIpcOptions): void;
|
|
7
|
+
export {};
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.registerTunnelIpc = registerTunnelIpc;
|
|
4
|
+
async function changed(options) {
|
|
5
|
+
await options?.afterSettingsChange?.();
|
|
6
|
+
}
|
|
7
|
+
function registerTunnelIpc(ipcMain, manager, options) {
|
|
8
|
+
ipcMain.handle('tunnel:snapshot', () => manager.snapshot());
|
|
9
|
+
ipcMain.handle('tunnel:create-subscription', (_event, input) => manager.createSubscription(input));
|
|
10
|
+
ipcMain.handle('tunnel:delete-subscription', async (_event, id) => {
|
|
11
|
+
manager.deleteSubscription(id);
|
|
12
|
+
await manager.applyRuntimeConfigChange();
|
|
13
|
+
});
|
|
14
|
+
ipcMain.handle('tunnel:set-active-subscription', async (_event, id) => {
|
|
15
|
+
const subscription = manager.setActiveSubscription(id);
|
|
16
|
+
await manager.applyRuntimeConfigChange();
|
|
17
|
+
return subscription;
|
|
18
|
+
});
|
|
19
|
+
ipcMain.handle('tunnel:update-subscription', async (_event, id) => {
|
|
20
|
+
const subscription = await manager.updateSubscription(id);
|
|
21
|
+
if (subscription.active) {
|
|
22
|
+
await manager.applyRuntimeConfigChange();
|
|
23
|
+
}
|
|
24
|
+
return subscription;
|
|
25
|
+
});
|
|
26
|
+
ipcMain.handle('tunnel:update-active-subscription', async () => {
|
|
27
|
+
const subscription = await manager.updateActiveSubscription();
|
|
28
|
+
await manager.applyRuntimeConfigChange();
|
|
29
|
+
return subscription;
|
|
30
|
+
});
|
|
31
|
+
ipcMain.handle('tunnel:set-mode', async (_event, mode) => {
|
|
32
|
+
const changedMode = manager.setMode(mode);
|
|
33
|
+
if (changedMode) {
|
|
34
|
+
await manager.applyRuntimeConfigChange();
|
|
35
|
+
}
|
|
36
|
+
await changed(options);
|
|
37
|
+
});
|
|
38
|
+
ipcMain.handle('tunnel:set-core-path', (_event, corePath) => manager.setCorePath(corePath));
|
|
39
|
+
ipcMain.handle('tunnel:set-local-ports', async (_event, ports) => {
|
|
40
|
+
await manager.setLocalPorts(ports);
|
|
41
|
+
await changed(options);
|
|
42
|
+
});
|
|
43
|
+
ipcMain.handle('tunnel:install-tun', async () => {
|
|
44
|
+
manager.installTunFeature();
|
|
45
|
+
await manager.applyRuntimeConfigChange();
|
|
46
|
+
await changed(options);
|
|
47
|
+
});
|
|
48
|
+
ipcMain.handle('tunnel:uninstall-tun', async () => {
|
|
49
|
+
manager.uninstallTunFeature();
|
|
50
|
+
await manager.applyRuntimeConfigChange();
|
|
51
|
+
await changed(options);
|
|
52
|
+
});
|
|
53
|
+
ipcMain.handle('tunnel:start', () => manager.start());
|
|
54
|
+
ipcMain.handle('tunnel:stop', () => manager.stop());
|
|
55
|
+
ipcMain.handle('tunnel:restart', () => manager.restart());
|
|
56
|
+
ipcMain.handle('tunnel:add-rule', async (_event, input) => {
|
|
57
|
+
const rule = manager.addDomainRule(input.kind, input.domain);
|
|
58
|
+
await manager.applyRuntimeConfigChange();
|
|
59
|
+
return rule;
|
|
60
|
+
});
|
|
61
|
+
ipcMain.handle('tunnel:remove-rule', async (_event, id) => {
|
|
62
|
+
manager.removeDomainRule(id);
|
|
63
|
+
await manager.applyRuntimeConfigChange();
|
|
64
|
+
});
|
|
65
|
+
ipcMain.handle('tunnel:add-preset', async (_event, preset) => {
|
|
66
|
+
const rules = manager.addPreset(preset);
|
|
67
|
+
await manager.applyRuntimeConfigChange();
|
|
68
|
+
return rules;
|
|
69
|
+
});
|
|
70
|
+
}
|