Skip to content

Commit

Permalink
I2P minor fixes and outproxy setting implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
everoddandeven committed Feb 22, 2025
1 parent 79311a7 commit 4328d38
Show file tree
Hide file tree
Showing 19 changed files with 307 additions and 67 deletions.
14 changes: 11 additions & 3 deletions app/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -574,8 +574,8 @@ try {
win?.webContents.send(eventId, await detectInstallation(program));
});

ipcMain.handle('start-i2pd', async (event: IpcMainInvokeEvent, params: { eventId: string; path: string; port: number; rpcPort: number; }) => {
const { eventId, path, port, rpcPort } = params;
ipcMain.handle('start-i2pd', async (event: IpcMainInvokeEvent, params: { eventId: string; path: string; port: number; rpcPort: number; outproxy?: { host: string; port: number; } }) => {
const { eventId, path, port, rpcPort, outproxy } = params;

let error: string | undefined = undefined;

Expand All @@ -588,10 +588,18 @@ try {
else {
try {
//i2pdProcess = new I2pdProcess({ i2pdPath: path, flags, isExe: true });
i2pdProcess = MoneroI2pdProcess.createSimple(path, port, rpcPort);
i2pdProcess = MoneroI2pdProcess.createSimple(path, port, rpcPort, outproxy);
await i2pdProcess.start();
i2pdProcess.onStdOut((out: string) => win?.webContents.send('on-ip2d-stdout', out));
i2pdProcess.onStdErr((out: string) => win?.webContents.send('on-ip2d-stderr', out));
i2pdProcess.onClose((_code: number | null) => {
const code = _code != null ? _code : -Number.MAX_SAFE_INTEGER;
const msg = `Process i2pd ${i2pdProcess?.pid} exited with code: ${code}`;
console.log(msg);
win?.webContents.send('i2pd-stdout', msg);
win?.webContents.send('i2pd-close', code);
monerodProcess = null;
});
}
catch (err: any) {
error = `${err}`;
Expand Down
10 changes: 8 additions & 2 deletions app/preload.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,15 @@ contextBridge.exposeInMainWorld('electronAPI', {
ipcRenderer.once(eventId, handler);
ipcRenderer.invoke('check-valid-i2pd-path', { eventId, path });
},
startI2pd: (path, port, rpcPort, callback) => {
startI2pd: (options, callback) => {
const { path, port, rpcPort, outproxy } = options;
const eventId = `on-start-i2pd-${newId()}`;
const handler = (event, result) => {
callback(result);
};

ipcRenderer.once(eventId, handler);
ipcRenderer.invoke('start-i2pd', { eventId, path, port, rpcPort });
ipcRenderer.invoke('start-i2pd', { eventId, path, port, rpcPort, outproxy });
},
stopI2pd: (callback) => {
const eventId = `on-stop-i2pd-${newId()}`;
Expand Down Expand Up @@ -178,6 +179,11 @@ contextBridge.exposeInMainWorld('electronAPI', {
const handler = (event, result) => callback(result);
ipcRenderer.on('monero-close', handler);
},
onI2pdClose: (callback) => {
const handler = (event, result) => callback(result);
ipcRenderer.once('i2pd-close', handler);
},

unregisterOnMoneroStdout: () => {
ipcRenderer.removeAllListeners('monero-stdout');
},
Expand Down
25 changes: 16 additions & 9 deletions app/process/I2pdProcess.ts
Original file line number Diff line number Diff line change
Expand Up @@ -263,7 +263,11 @@ export abstract class MoneroI2pTunnelConfigCreator {
return 28089;
}

public static createSimple(networkType: 'mainnet' | 'stagenet' | 'testnet' = 'mainnet', port?: number, rpcPort?: number): [I2pTunnelConfig, I2pTunnelConfig] {
public static createSimple(
networkType: 'mainnet' | 'stagenet' | 'testnet' = 'mainnet',
port?: number,
rpcPort?: number
): [I2pTunnelConfig, I2pTunnelConfig] {
port = port || this.getDefaultNodePort(networkType);
rpcPort = rpcPort || this.getDefaultRpcPort(networkType);
return this.create({ host: '127.0.0.1', keys: `monero-${networkType}.dat`, port, rpcPort })
Expand Down Expand Up @@ -559,11 +563,15 @@ export class MoneroI2pdProcess extends I2pdProcess {
];
}

private static createDefaultConfigFile(): void {
private static createDefaultConfigFile(outproxy?: { host: string; port: number; }): void {
if (!fs.existsSync(path.join(this.userDataPath, 'i2pd'))) {
fs.mkdirSync(path.join(this.userDataPath, 'i2pd'));
}

const enabled = outproxy !== undefined ? 'true' : 'false';
const host = outproxy ? outproxy.host : '127.0.0.1';
const port = outproxy ? outproxy. port : 9050;

fs.writeFileSync(this.defaultConfigPath, `ipv4 = true
ipv6 = false
daemon = false
Expand All @@ -577,10 +585,9 @@ enabled = false
[socksproxy]
enabled = true
#outproxy.enabled = true
#outproxy = exit.stormycloud.i2p
#outproxy = 127.0.0.1
#outproxyport = 9050
outproxy.enabled = ${enabled}
outproxy = ${host}
outproxyport = ${port}
[reseed]
verify = true
Expand All @@ -591,7 +598,7 @@ verify = true
fs.writeFileSync(this.defaultTunnelsConfigPath, ``);
}

private static createDefaultTunnelsDir(port: number, rpcPort: number): void {
private static createDefaultTunnelsDir(port: number, rpcPort: number, outproxy?: { host: string; port: number; }): void {
const service = new MoneroI2pTunnelConfigService(path.join(this.defaultTunnelsConfigPath));
const result = MoneroI2pTunnelConfigCreator.createSimple('mainnet', port, rpcPort);
service.setConfig(result[0], result[1], true);
Expand All @@ -601,8 +608,8 @@ verify = true
fs.writeFileSync(this.defaultLogPath, ``);
}

public static createSimple(i2pdPath: string, port: number = 18080, rpcPort: number = 18081): MoneroI2pdProcess {
this.createDefaultConfigFile();
public static createSimple(i2pdPath: string, port: number = 18080, rpcPort: number = 18081, outproxy?: { host: string; port: number; }): MoneroI2pdProcess {
this.createDefaultConfigFile(outproxy);

if (!fs.existsSync(this.defaultTunnelsConfigPath)) {
this.createDefaultTunnelsConfigFile();
Expand Down
13 changes: 13 additions & 0 deletions src/app/core/services/daemon/daemon.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,14 @@ export class DaemonService {
public enablingSync: boolean = false;
public startedAt?: Date;

public get startingI2pService(): boolean {
return this.i2pService.starting;
}

public get stoppingI2pService(): boolean {
return this.i2pService.stopping;
}

public readonly onDaemonStatusChanged: EventEmitter<boolean> = new EventEmitter<boolean>();
public readonly onDaemonStopStart: EventEmitter<void> = new EventEmitter<void>();
public readonly onDaemonStopEnd: EventEmitter<void> = new EventEmitter<void>();
Expand Down Expand Up @@ -467,6 +475,11 @@ export class DaemonService {
}
catch (error: any) {
console.error(error);
window.electronAPI.showNotification({
title: 'Daemon error',
body: error instanceof Error ? error.message : `${error}`,
closeButtonText: 'Dismiss'
});
this.starting = false;
throw error;
}
Expand Down
136 changes: 103 additions & 33 deletions src/app/core/services/i2p/i2p-daemon.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ export class I2pDaemonService {
public readonly proxy: string = '127.0.0.1:4447';
public readonly txProxy: string = 'i2p,127.0.0.1:4447,disable_noise';

public tunnelsData: TunnelsData = new TunnelsData();

private _settings: I2pDaemonSettings = new I2pDaemonSettings();
private _running: boolean = false;
private _starting: boolean = false;
Expand Down Expand Up @@ -83,12 +85,38 @@ export class I2pDaemonService {
});
}

private async waitForWebConsole(tries: number = 10): Promise<void> {
for(let i = 0; i < tries; i++) {
try {
await this.getMainData();
return;
}
catch {
continue;
}
}

throw new Error("Failed connection to i2p webconsole");
}

private async checkI2PAlreadyRunningInSystem(): Promise<boolean> {
try {
await this.waitForWebConsole(1);
return true;
}
catch {
return false;
}
}

public async start(config?: I2pDaemonSettings): Promise<void> {
const _config = config ? config : this._settings;

if (this.running) throw new Error("Already running i2pd");
if (this.stopping) throw new Error("i2pd is stopping");
if (this.starting) throw new Error("Alrady starting i2pd");
if (await this.checkI2PAlreadyRunningInSystem()) throw new Error("Another i2p service is already running");

this._starting = true;
const promise = new Promise<void>((resolve, reject) => {

Expand All @@ -103,37 +131,100 @@ export class I2pDaemonService {
}
});

window.electronAPI.startI2pd(_config.path, _config.port, _config.rpcPort, (error?: any) => {
window.electronAPI.onI2pdClose((code: number) => {
console.log(code);
this._running = false;
this.onStop.emit();
});

window.electronAPI.startI2pd(_config, (error?: any) => {
this._starting = false;
if (error) reject(new Error(`${error}`));
else resolve();
});
});

await promise;

this.tunnelsData = new TunnelsData();
this._anonymousInbound = '';

try {
await this.waitForWebConsole();
this.tunnelsData = await this.getI2pTunnels();
this._anonymousInbound = await this.getAnonymousInbound();
}
catch (error: any) {
console.error(error);
}

this.setSettings(_config);
this._running = true;
this.onStart.emit();
}

private async shutdown(): Promise<void> {
if (this.starting) throw new Error("i2pd is starting");
if (!this.running) throw new Error("Already stopped i2pd");

await new Promise<void>((resolve, reject) => {
window.electronAPI.onI2pdClose((code: number) => {

Check failure on line 171 in src/app/core/services/i2p/i2p-daemon.service.ts

View workflow job for this annotation

GitHub Actions / build (20)

'code' is defined but never used
resolve();
});

this.forceShutdown().then((result) => {
if (!result.message.includes('SUCCESS')) reject(new Error(result.message));
}).catch((error: any) => reject(error instanceof Error ? error : new Error(`${error}`)));
});
}

private async stopGracefully(): Promise<void> {
if (this.starting) throw new Error("i2pd is starting");
if (!this.running) throw new Error("Already stopped i2pd");

await new Promise<number>((resolve, reject) => {
window.electronAPI.onI2pdClose((code: number) => {
resolve(code);
});

this.startGracefulShutdown().then((result) => {
if (!result.message.includes('SUCCESS')) reject(new Error(result.message));
}).catch((error: any) => reject(error instanceof Error ? error : new Error(`${error}`)));
});
}

public async stop(): Promise<void> {
public async stop(force: boolean = false): Promise<void> {
if (this.starting) throw new Error("i2pd is starting");
if (!this.running) throw new Error("Already stopped i2pd");
if (this.stopping) throw new Error("Alrady stopping i2pd");
this._stopping = true;

const promise = new Promise<void>((resolve, reject) => {
window.electronAPI.stopI2pd((error?: any) => {
this._stopping = false;
if (error) reject(new Error(`${error}`));
else resolve();
});
});
let err: any = null;

await promise;
try {
if (force) {
const promise = new Promise<void>((resolve, reject) => {
window.electronAPI.stopI2pd((error?: any) => {
this._stopping = false;
if (error) reject(new Error(`${error}`));
else resolve();
});
});

await promise;
}
else {
await this.stopGracefully();
}
}
catch (error: any) {
err = error;
this._stopping = false;
}

this._running = false;
if (!this.restarting) this.onStop.emit();

if (err) throw err;
}

public async restart(): Promise<void> {
Expand Down Expand Up @@ -251,29 +342,8 @@ export class I2pDaemonService {
return new Promise<HTMLDivElement>(resolveFunction);
}

async function wait(d: number = 5000): Promise<void> {
await new Promise<void>((resolve) => setTimeout(resolve, d));
}

const tries: number = 3;
let err: any = null;

for(let i = 0; i < tries; i++) {
try {
return await createPromise();
}
catch (error: any) {
err = error;

if (i != tries - 1) await wait();
}
}

if (err) {
throw err;
}
return await createPromise();

throw new Error("Unknown error");
}

public async getMainData(): Promise<MainData> {
Expand Down
12 changes: 12 additions & 0 deletions src/app/pages/detail/detail.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,19 @@ <h4><i class="bi bi-exclamation-triangle m-2"></i></h4>&nbsp;&nbsp;
</div>

<div [hidden]="!daemonRunning || stoppingDaemon" class="tab-content tab-grow" id="pills-tabContent">

<div class="tab-pane fade show active" id="pills-home" role="tabpanel" aria-labelledby="pills-home-tab" tabindex="0">
<div *ngIf="anonymousInbounds.length > 0" class="alert alert-info d-flex align-items-center justify-content-center text-center" role="alert">
<div>
<i class="bi bi-incognito m-2"></i> Anonymous inbounds:
<ul>
@for(inbound of anonymousInbounds; track inbound.uri) {
<li>{{ inbound.uri }} - {{ inbound.type }}</li>
}
</ul>
</div>
</div>

<div class="row d-flex justify-content-center">

<div class="card text-bg-dark m-3 text-center" style="max-width: 18rem;">
Expand Down
Loading

0 comments on commit 4328d38

Please sign in to comment.