@qpjoy/electron-tunnel 0.1.3 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/createElectronTunnel.js +76 -0
- package/dist/plugin.d.ts +44 -0
- package/dist/plugin.js +52 -0
- package/dist/plugin.manifest.json +22 -0
- package/package.json +14 -2
|
@@ -7,6 +7,7 @@ const AdminServer_1 = require("./admin/AdminServer");
|
|
|
7
7
|
const registerTunnelIpc_1 = require("./ipc/registerTunnelIpc");
|
|
8
8
|
const MihomoManager_1 = require("./mihomo/MihomoManager");
|
|
9
9
|
const electronProxy_1 = require("./system/electronProxy");
|
|
10
|
+
const TUNNEL_PLUGIN_ID = 'qpjoy.electron-tunnel';
|
|
10
11
|
function defaultBundledEngineDir() {
|
|
11
12
|
const resourcesPath = process.resourcesPath ?? process.cwd();
|
|
12
13
|
const packageDir = typeof __dirname === 'undefined' ? process.cwd() : __dirname;
|
|
@@ -38,6 +39,13 @@ function createElectronTunnel(host, options = {}) {
|
|
|
38
39
|
(0, registerTunnelIpc_1.registerTunnelIpc)(host.ipcMain, manager, {
|
|
39
40
|
afterSettingsChange: applyProxy
|
|
40
41
|
});
|
|
42
|
+
// Self-register in the shared marketplace.db so the panel (when it shows
|
|
43
|
+
// up later) knows the tunnel is here. Best-effort; failure is silent —
|
|
44
|
+
// tunnel must keep working even if marketplace-db is missing.
|
|
45
|
+
registerSelfInMarketplaceDb(host.app, options).catch((err) => {
|
|
46
|
+
// eslint-disable-next-line no-console
|
|
47
|
+
console.warn('[electron-tunnel] marketplace-db self-register failed:', err);
|
|
48
|
+
});
|
|
41
49
|
return {
|
|
42
50
|
manager,
|
|
43
51
|
admin,
|
|
@@ -49,3 +57,71 @@ function createElectronTunnel(host, options = {}) {
|
|
|
49
57
|
}
|
|
50
58
|
};
|
|
51
59
|
}
|
|
60
|
+
async function registerSelfInMarketplaceDb(app, options) {
|
|
61
|
+
// Late require so a missing peer dep doesn't break standalone use. Use a
|
|
62
|
+
// computed specifier + cast so tsc never tries to resolve types for it.
|
|
63
|
+
const specifier = '@qpjoy/marketplace-db';
|
|
64
|
+
let mod;
|
|
65
|
+
try {
|
|
66
|
+
// eslint-disable-next-line @typescript-eslint/no-implied-eval, @typescript-eslint/no-var-requires
|
|
67
|
+
mod = require(specifier);
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
return; // package not installed alongside tunnel — fine.
|
|
71
|
+
}
|
|
72
|
+
const userDataPath = options.userDataPath ?? app.getPath('userData');
|
|
73
|
+
const dbPath = mod.resolveMarketplaceDbPath(userDataPath);
|
|
74
|
+
const packageJson = readNearbyJson('package.json');
|
|
75
|
+
const manifest = readNearbyJson('plugin.manifest.json', 'dist');
|
|
76
|
+
const db = mod.MarketplaceDB.open(dbPath);
|
|
77
|
+
try {
|
|
78
|
+
if (db.getInstalled(TUNNEL_PLUGIN_ID))
|
|
79
|
+
return; // host already registered us
|
|
80
|
+
const version = packageJson?.version ?? '0.0.0';
|
|
81
|
+
db.upsertInstalled({
|
|
82
|
+
id: TUNNEL_PLUGIN_ID,
|
|
83
|
+
npm: '@qpjoy/electron-tunnel',
|
|
84
|
+
version,
|
|
85
|
+
installPath: resolveTunnelPackageRoot(),
|
|
86
|
+
installSource: 'standalone',
|
|
87
|
+
manifest: {
|
|
88
|
+
id: TUNNEL_PLUGIN_ID,
|
|
89
|
+
name: 'QPJoy Tunnel',
|
|
90
|
+
version,
|
|
91
|
+
engines: { electronPlugin: '>=0.1.0', electron: '>=28' },
|
|
92
|
+
permissions: manifest?.permissions ?? [],
|
|
93
|
+
activationEvents: ['onStartup'],
|
|
94
|
+
contributes: { adminPanel: { url: 'http://127.0.0.1:23456', label: 'Tunnel' } }
|
|
95
|
+
},
|
|
96
|
+
// Standalone tunnel granted itself everything; the user implicitly
|
|
97
|
+
// accepted by choosing to install it directly (not via marketplace).
|
|
98
|
+
grantedPermissions: manifest?.permissions ?? [],
|
|
99
|
+
state: 'active',
|
|
100
|
+
errorMessage: null,
|
|
101
|
+
marketplaceEntryId: TUNNEL_PLUGIN_ID
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
finally {
|
|
105
|
+
db.close();
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
function readNearbyJson(name, sub) {
|
|
109
|
+
const packageDir = typeof __dirname === 'undefined' ? process.cwd() : __dirname;
|
|
110
|
+
const candidates = sub
|
|
111
|
+
? [(0, path_1.resolve)(packageDir, sub, name), (0, path_1.resolve)(packageDir, '..', sub, name)]
|
|
112
|
+
: [(0, path_1.resolve)(packageDir, name), (0, path_1.resolve)(packageDir, '..', name)];
|
|
113
|
+
for (const p of candidates) {
|
|
114
|
+
try {
|
|
115
|
+
if ((0, fs_1.existsSync)(p))
|
|
116
|
+
return JSON.parse((0, fs_1.readFileSync)(p, 'utf8'));
|
|
117
|
+
}
|
|
118
|
+
catch {
|
|
119
|
+
// try next
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
return null;
|
|
123
|
+
}
|
|
124
|
+
function resolveTunnelPackageRoot() {
|
|
125
|
+
const packageDir = typeof __dirname === 'undefined' ? process.cwd() : __dirname;
|
|
126
|
+
return (0, path_1.resolve)(packageDir, '..');
|
|
127
|
+
}
|
package/dist/plugin.d.ts
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plugin adapter for @qpjoy/electron-plugin.
|
|
3
|
+
*
|
|
4
|
+
* The same package keeps working as a standalone npm dependency (use
|
|
5
|
+
* `createElectronTunnel(...)` directly). When loaded through the plugin
|
|
6
|
+
* host, this default export is what gets invoked instead — wrapping the
|
|
7
|
+
* exact same runtime behind the standard plugin contract.
|
|
8
|
+
*
|
|
9
|
+
* No new state is introduced here: the underlying MihomoManager owns its
|
|
10
|
+
* own SQLite + admin server, so this adapter is a ~30-line shim.
|
|
11
|
+
*/
|
|
12
|
+
import type { App, IpcMain, Session } from 'electron';
|
|
13
|
+
/**
|
|
14
|
+
* Subset of the SDK types — re-declared structurally so this file does not
|
|
15
|
+
* take a build-time dependency on `@qpjoy/plugin-sdk`. (Tunnel still ships
|
|
16
|
+
* fine when installed without the plugin host present.)
|
|
17
|
+
*/
|
|
18
|
+
interface PluginHostBridge {
|
|
19
|
+
app: App;
|
|
20
|
+
ipcMain: IpcMain;
|
|
21
|
+
session: Session;
|
|
22
|
+
}
|
|
23
|
+
type ExposedApi = Record<string, (...args: any[]) => any>;
|
|
24
|
+
interface PluginContextLike<S> {
|
|
25
|
+
host: PluginHostBridge;
|
|
26
|
+
settings: {
|
|
27
|
+
get(): S;
|
|
28
|
+
};
|
|
29
|
+
log: {
|
|
30
|
+
info(m: string, meta?: Record<string, unknown>): void;
|
|
31
|
+
};
|
|
32
|
+
expose(api: ExposedApi): void;
|
|
33
|
+
}
|
|
34
|
+
export interface TunnelPluginSettings {
|
|
35
|
+
adminPort?: number;
|
|
36
|
+
controllerPort?: number;
|
|
37
|
+
mixedPort?: number;
|
|
38
|
+
dnsPort?: number;
|
|
39
|
+
bundledEngineDir?: string;
|
|
40
|
+
}
|
|
41
|
+
declare const tunnelPlugin: {
|
|
42
|
+
activate(ctx: PluginContextLike<TunnelPluginSettings>): Promise<() => void>;
|
|
43
|
+
};
|
|
44
|
+
export default tunnelPlugin;
|
package/dist/plugin.js
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const createElectronTunnel_1 = require("./createElectronTunnel");
|
|
4
|
+
const tunnelPlugin = {
|
|
5
|
+
async activate(ctx) {
|
|
6
|
+
const settings = ctx.settings.get() ?? {};
|
|
7
|
+
const handle = (0, createElectronTunnel_1.createElectronTunnel)({ app: ctx.host.app, ipcMain: ctx.host.ipcMain, session: ctx.host.session }, {
|
|
8
|
+
adminPort: settings.adminPort ?? 23456,
|
|
9
|
+
controllerPort: settings.controllerPort ?? 23457,
|
|
10
|
+
mixedPort: settings.mixedPort ?? 23458,
|
|
11
|
+
dnsPort: settings.dnsPort ?? 23459,
|
|
12
|
+
bundledEngineDir: settings.bundledEngineDir
|
|
13
|
+
});
|
|
14
|
+
// Expose an RPC surface so the host (and other plugins) can drive the
|
|
15
|
+
// tunnel without poking at internals. Anything not on this list is
|
|
16
|
+
// intentionally private — bump the surface explicitly when needed.
|
|
17
|
+
ctx.expose({
|
|
18
|
+
status: () => handle.status(),
|
|
19
|
+
snapshot: () => handle.manager.snapshot(),
|
|
20
|
+
applyProxy: () => handle.applyProxy(),
|
|
21
|
+
addDomainRule: async (kind, domain) => {
|
|
22
|
+
const rule = handle.manager.addDomainRule(kind, domain);
|
|
23
|
+
await handle.manager.applyRuntimeConfigChange();
|
|
24
|
+
return rule;
|
|
25
|
+
},
|
|
26
|
+
removeDomainRule: async (id) => {
|
|
27
|
+
handle.manager.removeDomainRule(id);
|
|
28
|
+
await handle.manager.applyRuntimeConfigChange();
|
|
29
|
+
},
|
|
30
|
+
addPreset: async (preset) => {
|
|
31
|
+
const rules = handle.manager.addPreset(preset);
|
|
32
|
+
await handle.manager.applyRuntimeConfigChange();
|
|
33
|
+
return rules;
|
|
34
|
+
},
|
|
35
|
+
removePreset: async (preset) => {
|
|
36
|
+
const count = handle.manager.removePreset(preset);
|
|
37
|
+
await handle.manager.applyRuntimeConfigChange();
|
|
38
|
+
return count;
|
|
39
|
+
},
|
|
40
|
+
onEvent: (listener) => {
|
|
41
|
+
handle.manager.on('event', listener);
|
|
42
|
+
return () => handle.manager.off('event', listener);
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
ctx.log.info('tunnel activated', {
|
|
46
|
+
ports: handle.status().ports,
|
|
47
|
+
mode: handle.status().mode
|
|
48
|
+
});
|
|
49
|
+
return () => handle.close();
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
exports.default = tunnelPlugin;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "qpjoy.electron-tunnel",
|
|
3
|
+
"name": "QPJoy Tunnel",
|
|
4
|
+
"version": "0.1.3",
|
|
5
|
+
"author": "qpjoy",
|
|
6
|
+
"description": "Mihomo-based tunnel runtime with TUN / system-proxy / rule modes. Provides outbound networking for other plugins.",
|
|
7
|
+
"engines": { "electronPlugin": ">=0.1.0", "electron": ">=28" },
|
|
8
|
+
"permissions": [
|
|
9
|
+
"fs:userData",
|
|
10
|
+
"net:listen:23456",
|
|
11
|
+
"net:listen:23457",
|
|
12
|
+
"net:listen:23458",
|
|
13
|
+
"net:listen:23459",
|
|
14
|
+
"system:proxy",
|
|
15
|
+
"system:exec:mihomo",
|
|
16
|
+
"ui:adminPanel"
|
|
17
|
+
],
|
|
18
|
+
"activationEvents": ["onStartup"],
|
|
19
|
+
"contributes": {
|
|
20
|
+
"adminPanel": { "url": "http://127.0.0.1:23456", "label": "Tunnel" }
|
|
21
|
+
}
|
|
22
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@qpjoy/electron-tunnel",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Reusable QPJoy tunnel runtime and CLI for Electron apps on macOS and Linux.",
|
|
6
6
|
"license": "UNLICENSED",
|
|
@@ -12,6 +12,10 @@
|
|
|
12
12
|
"types": "./dist/index.d.ts",
|
|
13
13
|
"default": "./dist/index.js"
|
|
14
14
|
},
|
|
15
|
+
"./plugin": {
|
|
16
|
+
"types": "./dist/plugin.d.ts",
|
|
17
|
+
"default": "./dist/plugin.js"
|
|
18
|
+
},
|
|
15
19
|
"./package.json": "./package.json"
|
|
16
20
|
},
|
|
17
21
|
"bin": {
|
|
@@ -23,11 +27,16 @@
|
|
|
23
27
|
"dist",
|
|
24
28
|
"resources/engine"
|
|
25
29
|
],
|
|
30
|
+
"qpjoyPlugin": {
|
|
31
|
+
"specVersion": 1,
|
|
32
|
+
"entry": "dist/plugin.js",
|
|
33
|
+
"manifest": "dist/plugin.manifest.json"
|
|
34
|
+
},
|
|
26
35
|
"publishConfig": {
|
|
27
36
|
"access": "public"
|
|
28
37
|
},
|
|
29
38
|
"scripts": {
|
|
30
|
-
"build": "tsc -p tsconfig.json",
|
|
39
|
+
"build": "tsc -p tsconfig.json && node -e \"require('fs').copyFileSync('src/plugin.manifest.json','dist/plugin.manifest.json')\"",
|
|
31
40
|
"prepack": "pnpm build",
|
|
32
41
|
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
33
42
|
"lint": "tsc -p tsconfig.json --noEmit"
|
|
@@ -43,6 +52,9 @@
|
|
|
43
52
|
"better-sqlite3": "^11.8.1",
|
|
44
53
|
"yaml": "^2.7.0"
|
|
45
54
|
},
|
|
55
|
+
"optionalDependencies": {
|
|
56
|
+
"@qpjoy/marketplace-db": ">=0.1.0"
|
|
57
|
+
},
|
|
46
58
|
"peerDependencies": {
|
|
47
59
|
"electron": ">=28"
|
|
48
60
|
},
|