📦 Add Linux packaging (AppImage/Flatpak/DEB)
🧹 Minor Codebase cleanup ⚡ Implement automatic starting of the relay
This commit is contained in:
parent
89fcaa9aa6
commit
f402ff04ab
19 changed files with 519 additions and 119 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -4,6 +4,8 @@ logs
|
|||
|
||||
node_modules
|
||||
dist
|
||||
out
|
||||
extras
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
|
|
29
electron-builder.yaml
Normal file
29
electron-builder.yaml
Normal file
|
@ -0,0 +1,29 @@
|
|||
appId: com.arx-ccn.eve
|
||||
productName: Eve
|
||||
executableName: Eve
|
||||
icon: public/icon512x512.png
|
||||
linux:
|
||||
category: Network
|
||||
target:
|
||||
- AppImage
|
||||
- flatpak
|
||||
- deb
|
||||
desktop:
|
||||
desktopActions: {}
|
||||
extraFiles:
|
||||
- from: extras/linux/relay
|
||||
to: usr/bin/eve-relay
|
||||
flatpak:
|
||||
runtimeVersion: "24.08"
|
||||
license: "LICENSE"
|
||||
directories:
|
||||
buildResources: build
|
||||
files:
|
||||
- '!**/.vscode/*'
|
||||
- '!src/*'
|
||||
- '!electron.vite.config.{js,ts,mjs,cjs}'
|
||||
- '!{.eslintignore,.eslintrc.cjs,.prettierignore,.prettierrc.yaml,dev-app-update.yml,CHANGELOG.md,README.md}'
|
||||
- '!{.env,.env.*,.npmrc,pnpm-lock.yaml}'
|
||||
- '!{tsconfig.json,tsconfig.node.json,tsconfig.web.json}'
|
||||
asarUnpack:
|
||||
- resources/**
|
51
electron.vite.config.ts
Normal file
51
electron.vite.config.ts
Normal file
|
@ -0,0 +1,51 @@
|
|||
import { fileURLToPath, URL } from "node:url";
|
||||
import { defineConfig, externalizeDepsPlugin } from "electron-vite";
|
||||
import { resolve } from "path";
|
||||
|
||||
export default defineConfig({
|
||||
main: {
|
||||
plugins: [externalizeDepsPlugin()],
|
||||
build: {
|
||||
lib: {
|
||||
entry: resolve(__dirname, "src/electron/main.ts"),
|
||||
},
|
||||
},
|
||||
},
|
||||
preload: {
|
||||
plugins: [externalizeDepsPlugin()],
|
||||
build: {
|
||||
lib: {
|
||||
entry: resolve(__dirname, "src/electron/preload.ts"),
|
||||
},
|
||||
},
|
||||
},
|
||||
renderer: {
|
||||
root: resolve(__dirname, "src"),
|
||||
build: {
|
||||
target: "es2024",
|
||||
rollupOptions: {
|
||||
input: {
|
||||
index: resolve(__dirname, "src/index.html"),
|
||||
},
|
||||
},
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
"@utils": fileURLToPath(new URL("./src/utils", import.meta.url)),
|
||||
"@routes": fileURLToPath(new URL("./src/routes", import.meta.url)),
|
||||
"@styles": fileURLToPath(new URL("./src/styles", import.meta.url)),
|
||||
"@widgets": fileURLToPath(
|
||||
new URL("./src/components/Widgets", import.meta.url)
|
||||
),
|
||||
"@components": fileURLToPath(
|
||||
new URL("./src/components", import.meta.url)
|
||||
),
|
||||
"@": fileURLToPath(new URL("./src", import.meta.url)),
|
||||
},
|
||||
},
|
||||
// server: {
|
||||
// port: 5173,
|
||||
// open: true,
|
||||
// },
|
||||
},
|
||||
});
|
35
package.json
35
package.json
|
@ -1,16 +1,31 @@
|
|||
{
|
||||
"name": "eve",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"description": "Closed Community Networks",
|
||||
"version": "0.0.1",
|
||||
"type": "module",
|
||||
"browserslist": ["not dead"],
|
||||
"license": "AGPL-3.0-only",
|
||||
"browserslist": [
|
||||
"electron >= 22.0.0"
|
||||
],
|
||||
"main": "./out/main/main.js",
|
||||
"homepage": "https://arx-ccn.com/eve",
|
||||
"author": {
|
||||
"name": "arx-ccn",
|
||||
"email": "developers@arx-ccn.com"
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "tsc && vite build",
|
||||
"preview": "vite preview"
|
||||
"build": "tsc && electron-vite build",
|
||||
"build:linux": "bun run build && electron-builder --linux",
|
||||
"start": "electron-vite preview",
|
||||
"dev": "electron-vite dev",
|
||||
"prebuild": "electron-vite build"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@biomejs/biome": "1.9.4",
|
||||
"@electron-toolkit/preload": "^3.0.1",
|
||||
"@electron-toolkit/utils": "^4.0.0",
|
||||
"electron-builder": "^25.1.8",
|
||||
"electron-vite": "^3.0.0",
|
||||
"electron": "^34.2.0",
|
||||
"@tsconfig/node22": "^22.0.0",
|
||||
"@types/markdown-it": "^14.1.2",
|
||||
"@types/node": "^22.10.2",
|
||||
|
@ -21,11 +36,13 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"@lit-labs/motion": "^1.0.8",
|
||||
"@noble/ciphers": "^1.2.1",
|
||||
"@nostr-dev-kit/ndk": "^2.10.7",
|
||||
"@nostr/tools": "npm:@jsr/nostr__tools",
|
||||
"@open-wc/lit-helpers": "^0.7.0",
|
||||
"@std/encoding": "npm:@jsr/std__encoding",
|
||||
"iconify-icon": "^2.2.0",
|
||||
"lit": "^3.2.1",
|
||||
"markdown-it": "^14.1.0",
|
||||
"nostr-tools": "^2.10.4"
|
||||
"markdown-it": "^14.1.0"
|
||||
}
|
||||
}
|
||||
|
|
BIN
public/icon512x512.png
Normal file
BIN
public/icon512x512.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 80 KiB |
|
@ -1,12 +1,14 @@
|
|||
import { LitElement, html, css } from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators.js";
|
||||
import { customElement, state } from "lit/decorators.js";
|
||||
import { animate } from "@lit-labs/motion";
|
||||
import * as nostrTools from "nostr-tools/pure";
|
||||
import * as nip06 from "nostr-tools/nip06";
|
||||
import * as nip19 from "nostr-tools/nip19";
|
||||
import * as nip49 from "nostr-tools/nip49";
|
||||
import * as nostrTools from "@nostr/tools/pure";
|
||||
import * as nip06 from "@nostr/tools/nip06";
|
||||
import * as nip19 from "@nostr/tools/nip19";
|
||||
import * as nip49 from "@nostr/tools/nip49";
|
||||
import { ndk, setSigner } from "@/ndk";
|
||||
import { NDKEvent, NDKKind, NDKPrivateKeySigner } from "@nostr-dev-kit/ndk";
|
||||
import { encodeBase64 } from "@std/encoding/base64";
|
||||
import { randomBytes } from "@noble/ciphers/webcrypto";
|
||||
|
||||
@customElement("arx-initial-setup")
|
||||
export class InitialSetup extends LitElement {
|
||||
|
@ -17,6 +19,15 @@ export class InitialSetup extends LitElement {
|
|||
@state() private profileImage = "";
|
||||
@state() private lightningAddress = "";
|
||||
|
||||
get encryptionPassphrase() {
|
||||
let encryptionPassphrase = localStorage.getItem("encryption_key");
|
||||
if (!encryptionPassphrase) {
|
||||
encryptionPassphrase = encodeBase64(randomBytes(32));
|
||||
localStorage.setItem("encryption_key", encryptionPassphrase);
|
||||
}
|
||||
return encryptionPassphrase;
|
||||
}
|
||||
|
||||
static override styles = css`
|
||||
:host {
|
||||
display: block;
|
||||
|
@ -409,32 +420,9 @@ export class InitialSetup extends LitElement {
|
|||
`;
|
||||
}
|
||||
|
||||
private getSetupCode() {
|
||||
const userAgent = navigator.userAgent.toLowerCase();
|
||||
if (userAgent.includes("mac")) {
|
||||
return `
|
||||
mkdir -p ~/.config/arx/eve && cd ~/.config/arx/eve
|
||||
echo "${this.seedPhrase}" > ccn.seed
|
||||
launchctl load ~/Library/LaunchAgents/com.user.eve-relay.plist
|
||||
launchctl start com.user.eve-relay
|
||||
`
|
||||
.split("\n")
|
||||
.map((x) => x.trim())
|
||||
.join("\n");
|
||||
}
|
||||
|
||||
if (userAgent.includes("linux")) {
|
||||
return `
|
||||
mkdir -p ~/.config/arx/eve && cd ~/.config/arx/eve
|
||||
echo "${this.seedPhrase}" > ccn.seed
|
||||
systemctl --user enable eve-relay.service
|
||||
systemctl --user start eve-relay.service
|
||||
`
|
||||
.split("\n")
|
||||
.map((x) => x.trim())
|
||||
.join("\n");
|
||||
}
|
||||
return "Unsupported OS";
|
||||
private async startRelay() {
|
||||
await window.relay.writeSeed(this.seedPhrase);
|
||||
await window.relay.start(this.encryptionPassphrase);
|
||||
}
|
||||
|
||||
private renderPageThree() {
|
||||
|
@ -446,11 +434,10 @@ export class InitialSetup extends LitElement {
|
|||
During this alpha phase, manual relay configuration is required.
|
||||
This process will be automated in future releases.
|
||||
</p>
|
||||
<p>Open your terminal and run following commands:</p>
|
||||
<pre>
|
||||
<code>${this.getSetupCode()}</code>
|
||||
</pre
|
||||
>
|
||||
<p>Please press the button below to start the relay.</p>
|
||||
<button @click=${() => this.startRelay()} class="button primary">
|
||||
Start Relay
|
||||
</button>
|
||||
<p>
|
||||
Having trouble? Our team is here to help if you encounter any
|
||||
issues.
|
||||
|
@ -586,15 +573,11 @@ export class InitialSetup extends LitElement {
|
|||
}
|
||||
|
||||
private async goToFinalStep() {
|
||||
let encryptionPassphrase = localStorage.getItem("encryption_key");
|
||||
if (!encryptionPassphrase) {
|
||||
encryptionPassphrase =
|
||||
Math.random().toString(36).substring(2, 15) +
|
||||
Math.random().toString(36).substring(2, 15);
|
||||
localStorage.setItem("encryption_key", encryptionPassphrase);
|
||||
}
|
||||
const randomPrivateKey = nostrTools.generateSecretKey();
|
||||
const encryptedNsec = nip49.encrypt(randomPrivateKey, encryptionPassphrase);
|
||||
const encryptedNsec = nip49.encrypt(
|
||||
randomPrivateKey,
|
||||
this.encryptionPassphrase
|
||||
);
|
||||
const npub = nip19.npubEncode(nostrTools.getPublicKey(randomPrivateKey));
|
||||
|
||||
if (!this.lightningAddress) this.lightningAddress = `${npub}@npub.cash`;
|
||||
|
@ -602,6 +585,7 @@ export class InitialSetup extends LitElement {
|
|||
localStorage.setItem("ncryptsec", encryptedNsec);
|
||||
|
||||
setSigner(new NDKPrivateKeySigner(randomPrivateKey));
|
||||
await ndk.connect(5000);
|
||||
|
||||
const event = new NDKEvent(ndk);
|
||||
event.kind = NDKKind.Metadata;
|
||||
|
|
|
@ -1,18 +1,18 @@
|
|||
import { css, LitElement } from 'lit';
|
||||
import { customElement, property } from 'lit/decorators.js';
|
||||
import { unsafeHTML } from 'lit/directives/unsafe-html.js';
|
||||
import MarkdownIt, { type StateCore, type Token } from 'markdown-it';
|
||||
import { css, LitElement } from "lit";
|
||||
import { customElement, property } from "lit/decorators.js";
|
||||
import { unsafeHTML } from "lit/directives/unsafe-html.js";
|
||||
import MarkdownIt, { type StateCore, type Token } from "markdown-it";
|
||||
|
||||
function nostrPlugin(md: MarkdownIt): void {
|
||||
const npubRegex = /(npub[0-9a-zA-Z]{59})/g;
|
||||
|
||||
md.core.ruler.after('inline', 'nostr_npub', (state: StateCore): boolean => {
|
||||
md.core.ruler.after("inline", "nostr_npub", (state: StateCore): boolean => {
|
||||
for (const token of state.tokens) {
|
||||
if (token.type === 'inline' && token.children) {
|
||||
if (token.type === "inline" && token.children) {
|
||||
for (let i = 0; i < token.children.length; i++) {
|
||||
const child = token.children[i];
|
||||
|
||||
if (child.type === 'text') {
|
||||
if (child.type === "text") {
|
||||
const matches = child.content.match(npubRegex);
|
||||
if (!matches) continue;
|
||||
|
||||
|
@ -21,28 +21,29 @@ function nostrPlugin(md: MarkdownIt): void {
|
|||
|
||||
child.content.replace(
|
||||
npubRegex,
|
||||
// @ts-ignore this is an issue with the types
|
||||
(match: string, npub: string, offset: number) => {
|
||||
if (offset > lastIndex) {
|
||||
const textToken = new state.Token('text', '', 0);
|
||||
const textToken = new state.Token("text", "", 0);
|
||||
textToken.content = child.content.slice(lastIndex, offset);
|
||||
newTokens.push(textToken);
|
||||
}
|
||||
|
||||
const linkOpen = new state.Token('link_open', 'a', 1);
|
||||
linkOpen.attrs = [['href', `nostr:${npub}`]];
|
||||
const linkOpen = new state.Token("link_open", "a", 1);
|
||||
linkOpen.attrs = [["href", `nostr:${npub}`]];
|
||||
|
||||
const text = new state.Token('text', '', 0);
|
||||
const text = new state.Token("text", "", 0);
|
||||
text.content = npub;
|
||||
|
||||
const linkClose = new state.Token('link_close', 'a', -1);
|
||||
const linkClose = new state.Token("link_close", "a", -1);
|
||||
|
||||
newTokens.push(linkOpen, text, linkClose);
|
||||
lastIndex = offset + match.length;
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
if (lastIndex < child.content.length) {
|
||||
const textToken = new state.Token('text', '', 0);
|
||||
const textToken = new state.Token("text", "", 0);
|
||||
textToken.content = child.content.slice(lastIndex);
|
||||
newTokens.push(textToken);
|
||||
}
|
||||
|
@ -58,7 +59,7 @@ function nostrPlugin(md: MarkdownIt): void {
|
|||
});
|
||||
}
|
||||
|
||||
@customElement('arx-markdown-content')
|
||||
@customElement("arx-markdown-content")
|
||||
export class MarkdownContent extends LitElement {
|
||||
private md = new MarkdownIt({
|
||||
html: false,
|
||||
|
@ -68,7 +69,7 @@ export class MarkdownContent extends LitElement {
|
|||
});
|
||||
|
||||
@property({ type: String })
|
||||
content = '';
|
||||
content = "";
|
||||
|
||||
static override styles = [
|
||||
css`
|
||||
|
@ -94,7 +95,16 @@ export class MarkdownContent extends LitElement {
|
|||
display: contents;
|
||||
}
|
||||
|
||||
h1, h2, h3, h4, h5, h6, code, ul, ol, blockquote {
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6,
|
||||
code,
|
||||
ul,
|
||||
ol,
|
||||
blockquote {
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
display: block;
|
||||
|
|
77
src/electron/main.ts
Normal file
77
src/electron/main.ts
Normal file
|
@ -0,0 +1,77 @@
|
|||
import { app, shell, BrowserWindow, ipcMain } from "electron";
|
||||
import { optimizer, is } from "@electron-toolkit/utils";
|
||||
import { RelayManager } from "./relayManager";
|
||||
import path from "node:path";
|
||||
import fs from "node:fs";
|
||||
import os from "node:os";
|
||||
|
||||
const relay = new RelayManager();
|
||||
|
||||
ipcMain.handle("relay:writeSeed", (_, ...args: any) => {
|
||||
if (!args[0]) throw new Error("No seed provided");
|
||||
const seed = args[0] as string;
|
||||
const configPath = path.join(os.homedir(), ".config", "arx", "Eve");
|
||||
const seedPath = path.join(configPath, "ccn.seed");
|
||||
fs.mkdirSync(configPath, { recursive: true });
|
||||
fs.writeFileSync(seedPath, seed);
|
||||
});
|
||||
|
||||
ipcMain.handle("relay:start", (_, ...args: any) => {
|
||||
if (!args[0]) throw new Error("No encryption key provided");
|
||||
const encryptionKey = args[0] as string;
|
||||
return relay.start(encryptionKey);
|
||||
});
|
||||
|
||||
ipcMain.handle("relay:stop", () => {
|
||||
return relay.stop();
|
||||
});
|
||||
|
||||
ipcMain.handle("relay:status", () => {
|
||||
return {
|
||||
running: relay.isRunning,
|
||||
pid: relay.pid,
|
||||
logs: relay.getLogs(),
|
||||
};
|
||||
});
|
||||
|
||||
ipcMain.handle("relay:getLogs", () => {
|
||||
return relay.getLogs();
|
||||
});
|
||||
|
||||
function createWindow(): void {
|
||||
const mainWindow = new BrowserWindow({
|
||||
width: 1024,
|
||||
height: 768,
|
||||
show: false,
|
||||
autoHideMenuBar: true,
|
||||
webPreferences: {
|
||||
preload: path.join(__dirname, "../preload/preload.mjs"),
|
||||
sandbox: false,
|
||||
},
|
||||
});
|
||||
|
||||
mainWindow.on("ready-to-show", () => {
|
||||
mainWindow.show();
|
||||
});
|
||||
|
||||
mainWindow.webContents.setWindowOpenHandler((details) => {
|
||||
shell.openExternal(details.url);
|
||||
return { action: "deny" };
|
||||
});
|
||||
|
||||
if (is.dev && process.env["ELECTRON_RENDERER_URL"])
|
||||
mainWindow.loadURL(process.env["ELECTRON_RENDERER_URL"]);
|
||||
else mainWindow.loadFile(path.join(__dirname, "../renderer/index.html"));
|
||||
}
|
||||
|
||||
app.whenReady().then(() => {
|
||||
app.on("browser-window-created", (_, window) => {
|
||||
optimizer.watchWindowShortcuts(window);
|
||||
});
|
||||
|
||||
createWindow();
|
||||
});
|
||||
|
||||
app.on("window-all-closed", () => {
|
||||
app.quit();
|
||||
});
|
21
src/electron/preload.ts
Normal file
21
src/electron/preload.ts
Normal file
|
@ -0,0 +1,21 @@
|
|||
import { contextBridge, ipcRenderer } from "electron";
|
||||
import { electronAPI } from "@electron-toolkit/preload";
|
||||
|
||||
if (process.contextIsolated) {
|
||||
try {
|
||||
contextBridge.exposeInMainWorld("electron", electronAPI);
|
||||
contextBridge.exposeInMainWorld("relay", {
|
||||
writeSeed: (seed: string) => ipcRenderer.invoke("relay:writeSeed", seed),
|
||||
start: (encryptionKey: string) =>
|
||||
ipcRenderer.invoke("relay:start", encryptionKey),
|
||||
stop: () => ipcRenderer.invoke("relay:stop"),
|
||||
getStatus: () => ipcRenderer.invoke("relay:status"),
|
||||
getLogs: () => ipcRenderer.invoke("relay:logs"),
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
} else {
|
||||
// @ts-ignore (define in dts)
|
||||
window.electron = electronAPI;
|
||||
}
|
207
src/electron/relayManager.ts
Normal file
207
src/electron/relayManager.ts
Normal file
|
@ -0,0 +1,207 @@
|
|||
import { spawn, ChildProcess } from "child_process";
|
||||
import { join } from "path";
|
||||
import { is } from "@electron-toolkit/utils";
|
||||
|
||||
type PackageEnvironment = "flatpak" | "appimage" | "system" | "dev";
|
||||
|
||||
export class RelayManager {
|
||||
private process: ChildProcess | null;
|
||||
private readonly relayPath: string;
|
||||
private isShuttingDown: boolean;
|
||||
private restartAttempts: number;
|
||||
private readonly maxRestartAttempts: number;
|
||||
private readonly restartDelay: number;
|
||||
private restartTimeout: NodeJS.Timeout | null;
|
||||
private relayLogs: string[];
|
||||
private readonly maxLogs: number;
|
||||
private encryptionKey: string | null;
|
||||
|
||||
/**
|
||||
* Checks if the relay is currently running.
|
||||
*
|
||||
* @returns {boolean} True if the relay is running, otherwise false.
|
||||
*/
|
||||
get isRunning(): boolean {
|
||||
return !!this.process;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the process identifier (PID) of the running relay process.
|
||||
*
|
||||
* @returns {number | undefined} The PID of the relay process if it is running, otherwise undefined.
|
||||
*/
|
||||
|
||||
get pid(): number | undefined {
|
||||
return this.process?.pid;
|
||||
}
|
||||
|
||||
constructor(maxRestartAttempts = 5, restartDelay = 1000, maxLogs = 100) {
|
||||
this.process = null;
|
||||
this.relayPath = this.getRelayPath();
|
||||
this.isShuttingDown = false;
|
||||
this.restartAttempts = 0;
|
||||
this.maxRestartAttempts = maxRestartAttempts;
|
||||
this.restartDelay = restartDelay;
|
||||
this.restartTimeout = null;
|
||||
this.relayLogs = [];
|
||||
this.maxLogs = maxLogs;
|
||||
this.encryptionKey = null;
|
||||
}
|
||||
|
||||
private detectEnvironment(): PackageEnvironment {
|
||||
if (is.dev) return "dev";
|
||||
if (process.env.FLATPAK_ID) return "flatpak";
|
||||
if (process.env.APPIMAGE) return "appimage";
|
||||
return "system";
|
||||
}
|
||||
|
||||
private getRelayPath(): string {
|
||||
const environment = this.detectEnvironment();
|
||||
|
||||
switch (environment) {
|
||||
case "dev":
|
||||
return join(__dirname, "../../extras/linux/relay");
|
||||
case "flatpak":
|
||||
return "/app/lib/com.arx_ccn.eve/usr/bin/eve-relay";
|
||||
case "appimage":
|
||||
return join(process.env.APPDIR || "", "usr/bin/eve-relay");
|
||||
case "system":
|
||||
return "/usr/bin/eve-relay";
|
||||
}
|
||||
}
|
||||
|
||||
private handleProcessExit(code: number | null): void {
|
||||
this.process = null;
|
||||
console.log(`Relay exited with code ${code}`);
|
||||
|
||||
if (!this.isShuttingDown) this.restartProcess();
|
||||
}
|
||||
|
||||
private restartProcess(): void {
|
||||
if (this.restartAttempts >= this.maxRestartAttempts) {
|
||||
console.error(
|
||||
`Failed to restart relay after ${this.maxRestartAttempts} attempts`
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
this.restartAttempts++;
|
||||
console.log(
|
||||
`Attempting restart #${this.restartAttempts} in ${this.restartDelay}ms...`
|
||||
);
|
||||
|
||||
if (this.restartTimeout) clearTimeout(this.restartTimeout);
|
||||
|
||||
this.restartTimeout = setTimeout(() => {
|
||||
this.start(this.encryptionKey!);
|
||||
}, this.restartDelay);
|
||||
}
|
||||
|
||||
private addLog(data: string): void {
|
||||
this.relayLogs.push(data);
|
||||
if (this.relayLogs.length > this.maxLogs) {
|
||||
this.relayLogs = this.relayLogs.slice(-this.maxLogs);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Start the Eve Relay.
|
||||
*
|
||||
* If the process is already running, do nothing.
|
||||
*
|
||||
* Logs from the Relay process are captured and can be retrieved with the
|
||||
* `getLogs()` method.
|
||||
*
|
||||
* If the process exits unexpectedly, it will be restarted according to the
|
||||
* configured restart policy.
|
||||
*
|
||||
* @param {string} encryptionKey - The key to use for encrypting data saved in
|
||||
* the Relay.
|
||||
*/
|
||||
public start(encryptionKey: string): void {
|
||||
if (this.process) return;
|
||||
|
||||
this.encryptionKey = encryptionKey;
|
||||
|
||||
try {
|
||||
this.process = spawn(this.relayPath, [], {
|
||||
env: {
|
||||
...process.env,
|
||||
LD_LIBRARY_PATH: process.env.LD_LIBRARY_PATH,
|
||||
PATH: process.env.PATH,
|
||||
ENCRYPTION_KEY: encryptionKey,
|
||||
},
|
||||
});
|
||||
|
||||
if (this.process.stdout) {
|
||||
this.process.stdout.on("data", (data: Buffer) => {
|
||||
const logLine = data.toString().trim();
|
||||
this.addLog(logLine);
|
||||
console.log(logLine);
|
||||
});
|
||||
}
|
||||
|
||||
if (this.process.stderr) {
|
||||
this.process.stderr.on("data", (data: Buffer) => {
|
||||
const logLine = data.toString().trim();
|
||||
this.addLog(logLine);
|
||||
console.error(logLine);
|
||||
});
|
||||
}
|
||||
|
||||
this.process.on("error", (err: Error) => {
|
||||
console.error(`Failed to start Relay: ${err.message}`);
|
||||
this.process = null;
|
||||
this.restartProcess();
|
||||
});
|
||||
|
||||
this.process.on("exit", this.handleProcessExit.bind(this));
|
||||
|
||||
if (this.process.pid) {
|
||||
this.restartAttempts = 0;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(
|
||||
`Error starting Relay: ${
|
||||
error instanceof Error ? error.message : "Unknown error"
|
||||
}`
|
||||
);
|
||||
this.restartProcess();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop the Eve Relay.
|
||||
*
|
||||
* If the Relay process is currently running, it will be terminated.
|
||||
*
|
||||
* If the process is not running, do nothing.
|
||||
*
|
||||
* All pending restarts will be cancelled.
|
||||
*/
|
||||
public stop(): void {
|
||||
this.isShuttingDown = true;
|
||||
|
||||
if (this.restartTimeout) {
|
||||
clearTimeout(this.restartTimeout);
|
||||
this.restartTimeout = null;
|
||||
}
|
||||
|
||||
if (this.process) {
|
||||
this.process.kill();
|
||||
this.process = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a copy of all the log lines the Relay has written to date.
|
||||
*
|
||||
* The returned array is a copy of the internal log state, and will not be
|
||||
* modified by future log activity.
|
||||
*
|
||||
* @returns A copy of all log lines from the Relay since it was last started.
|
||||
*/
|
||||
public getLogs(): string[] {
|
||||
return [...this.relayLogs];
|
||||
}
|
||||
}
|
|
@ -7,6 +7,6 @@
|
|||
</head>
|
||||
|
||||
<body>
|
||||
<script src="/src/main.ts" type="module"></script>
|
||||
<script src="main.ts" type="module"></script>
|
||||
</body>
|
||||
</html>
|
20
src/main.ts
20
src/main.ts
|
@ -7,8 +7,22 @@ import "@components/NostrProfile";
|
|||
import "@components/Breadcrumbs";
|
||||
import "@components/Header";
|
||||
import "@routes/router";
|
||||
import "@components/LoadingView";
|
||||
import type EveRouter from "@routes/router";
|
||||
import { sleep } from "./utils/sleep";
|
||||
|
||||
const router = document.createElement("arx-eve-router") as EveRouter;
|
||||
router.ccnSetup = localStorage.getItem("ncryptsec");
|
||||
document.body.appendChild(router);
|
||||
async function startRelay() {
|
||||
if (localStorage.getItem("ncryptsec")) {
|
||||
const loadingIndicator = document.createElement("arx-loading-view");
|
||||
document.body.appendChild(loadingIndicator);
|
||||
await window.relay.start(localStorage.getItem("encryption_key")!);
|
||||
await sleep(5000);
|
||||
loadingIndicator.remove();
|
||||
}
|
||||
}
|
||||
|
||||
startRelay().then(() => {
|
||||
const router = document.createElement("arx-eve-router") as EveRouter;
|
||||
router.ccnSetup = !!localStorage.getItem("ncryptsec");
|
||||
document.body.appendChild(router);
|
||||
});
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import NDK, { NDKPrivateKeySigner } from "@nostr-dev-kit/ndk";
|
||||
import * as nip49 from "nostr-tools/nip49";
|
||||
import * as nip49 from "@nostr/tools/nip49";
|
||||
|
||||
export const ndk = new NDK({
|
||||
explicitRelayUrls: ["ws://localhost:6942"],
|
||||
|
@ -13,8 +13,9 @@ export async function getSigner() {
|
|||
await ndk.connect();
|
||||
if (ndk.signer) return;
|
||||
const encryptionPassphrase = localStorage.getItem("encryption_key");
|
||||
if (!encryptionPassphrase) throw new Error("Encryption passphrase not found");
|
||||
const signer = new NDKPrivateKeySigner(
|
||||
nip49.decrypt(localStorage.getItem("ncryptsec"), encryptionPassphrase)
|
||||
nip49.decrypt(localStorage.getItem("ncryptsec")!, encryptionPassphrase)
|
||||
);
|
||||
setSigner(signer);
|
||||
}
|
||||
|
|
21
src/relayManager.d.ts
vendored
Normal file
21
src/relayManager.d.ts
vendored
Normal file
|
@ -0,0 +1,21 @@
|
|||
interface RelayStatus {
|
||||
running: boolean;
|
||||
pid: number | null;
|
||||
logs: string[];
|
||||
}
|
||||
|
||||
interface RelayBridge {
|
||||
writeSeed: (seed: string) => Promise<void>;
|
||||
start: (encryptionKey: string) => Promise<void>;
|
||||
stop: () => Promise<void>;
|
||||
getStatus: () => Promise<RelayStatus>;
|
||||
getLogs: () => Promise<string[]>;
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
relay: RelayBridge;
|
||||
}
|
||||
}
|
||||
|
||||
export {};
|
|
@ -1,7 +1,6 @@
|
|||
import { LitElement, html, css } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators.js";
|
||||
import { when } from "lit/directives/when.js";
|
||||
import { styleMap } from "lit/directives/style-map.js";
|
||||
import type { NDKUserProfile } from "@nostr-dev-kit/ndk";
|
||||
import { getUserProfile } from "../ndk";
|
||||
|
||||
|
|
|
@ -78,7 +78,7 @@ export default class EveRouter extends LitElement {
|
|||
private currentIndex = -1;
|
||||
|
||||
@property()
|
||||
private ccnSetup = false;
|
||||
public ccnSetup = false;
|
||||
|
||||
private beforeEachGuards: ((to: Route, from: Route | null) => boolean)[] = [];
|
||||
private afterEachHooks: ((to: Route, from: Route | null) => void)[] = [];
|
||||
|
@ -116,6 +116,8 @@ export default class EveRouter extends LitElement {
|
|||
constructor() {
|
||||
super();
|
||||
this.initializeRouter();
|
||||
if (this.ccnSetup)
|
||||
window.relay.start(localStorage.getItem("encryption_key")!);
|
||||
}
|
||||
|
||||
override connectedCallback(): void {
|
||||
|
@ -276,7 +278,7 @@ export default class EveRouter extends LitElement {
|
|||
?canGoBack=${this.currentIndex > 0}
|
||||
?canGoForward=${this.currentIndex < this.history.length - 1}
|
||||
url="eve://${this.currentPath}"
|
||||
@navigate=${(e) => this.navigate(e.detail)}
|
||||
@navigate=${(e: CustomEvent) => this.navigate(e.detail)}
|
||||
@go-back=${this.goBack}
|
||||
@go-forward=${this.goForward}
|
||||
title="Eve"
|
||||
|
|
3
src/utils/sleep.ts
Normal file
3
src/utils/sleep.ts
Normal file
|
@ -0,0 +1,3 @@
|
|||
export function sleep(ms: number) {
|
||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||
}
|
|
@ -31,5 +31,6 @@
|
|||
"@components/*": ["./src/components/*"],
|
||||
"@widgets/*": ["./src/components/Widgets/*"]
|
||||
}
|
||||
}
|
||||
},
|
||||
"include": ["./src/**/*"]
|
||||
}
|
||||
|
|
|
@ -1,39 +0,0 @@
|
|||
import { fileURLToPath, URL } from "node:url";
|
||||
import { defineConfig } from "vite";
|
||||
import { resolve } from "path";
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [],
|
||||
build: {
|
||||
target: "es2024",
|
||||
outDir: "dist",
|
||||
rollupOptions: {
|
||||
input: {
|
||||
main: resolve(__dirname, "index.html"),
|
||||
},
|
||||
},
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
"@utils": fileURLToPath(new URL("./src/utils", import.meta.url)),
|
||||
"@routes": fileURLToPath(new URL("./src/routes", import.meta.url)),
|
||||
"@styles": fileURLToPath(new URL("./src/styles", import.meta.url)),
|
||||
"@widgets": fileURLToPath(
|
||||
new URL("./src/components/Widgets", import.meta.url)
|
||||
),
|
||||
"@components": fileURLToPath(
|
||||
new URL("./src/components", import.meta.url)
|
||||
),
|
||||
"@": fileURLToPath(new URL("./src", import.meta.url)),
|
||||
},
|
||||
},
|
||||
server: {
|
||||
port: 5173,
|
||||
open: true,
|
||||
},
|
||||
optimizeDeps: {
|
||||
esbuildOptions: {
|
||||
target: "es2024",
|
||||
},
|
||||
},
|
||||
});
|
Loading…
Add table
Reference in a new issue