📦 Add Linux packaging (AppImage/Flatpak/DEB)

🧹 Minor Codebase cleanup
 Implement automatic starting of the relay
This commit is contained in:
Danny Morabito 2025-02-24 21:48:11 +01:00
parent 89fcaa9aa6
commit f402ff04ab
Signed by: dannym
GPG key ID: 7CC8056A5A04557E
19 changed files with 519 additions and 119 deletions

2
.gitignore vendored
View file

@ -4,6 +4,8 @@ logs
node_modules node_modules
dist dist
out
extras
# Editor directories and files # Editor directories and files
.vscode/* .vscode/*

29
electron-builder.yaml Normal file
View 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
View 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,
// },
},
});

View file

@ -1,16 +1,31 @@
{ {
"name": "eve", "name": "eve",
"private": true, "description": "Closed Community Networks",
"version": "0.0.0", "version": "0.0.1",
"type": "module", "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": { "scripts": {
"dev": "vite", "build": "tsc && electron-vite build",
"build": "tsc && vite build", "build:linux": "bun run build && electron-builder --linux",
"preview": "vite preview" "start": "electron-vite preview",
"dev": "electron-vite dev",
"prebuild": "electron-vite build"
}, },
"devDependencies": { "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", "@tsconfig/node22": "^22.0.0",
"@types/markdown-it": "^14.1.2", "@types/markdown-it": "^14.1.2",
"@types/node": "^22.10.2", "@types/node": "^22.10.2",
@ -21,11 +36,13 @@
}, },
"dependencies": { "dependencies": {
"@lit-labs/motion": "^1.0.8", "@lit-labs/motion": "^1.0.8",
"@noble/ciphers": "^1.2.1",
"@nostr-dev-kit/ndk": "^2.10.7", "@nostr-dev-kit/ndk": "^2.10.7",
"@nostr/tools": "npm:@jsr/nostr__tools",
"@open-wc/lit-helpers": "^0.7.0", "@open-wc/lit-helpers": "^0.7.0",
"@std/encoding": "npm:@jsr/std__encoding",
"iconify-icon": "^2.2.0", "iconify-icon": "^2.2.0",
"lit": "^3.2.1", "lit": "^3.2.1",
"markdown-it": "^14.1.0", "markdown-it": "^14.1.0"
"nostr-tools": "^2.10.4"
} }
} }

BIN
public/icon512x512.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

View file

@ -1,12 +1,14 @@
import { LitElement, html, css } from "lit"; 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 { animate } from "@lit-labs/motion";
import * as nostrTools from "nostr-tools/pure"; import * as nostrTools from "@nostr/tools/pure";
import * as nip06 from "nostr-tools/nip06"; import * as nip06 from "@nostr/tools/nip06";
import * as nip19 from "nostr-tools/nip19"; import * as nip19 from "@nostr/tools/nip19";
import * as nip49 from "nostr-tools/nip49"; import * as nip49 from "@nostr/tools/nip49";
import { ndk, setSigner } from "@/ndk"; import { ndk, setSigner } from "@/ndk";
import { NDKEvent, NDKKind, NDKPrivateKeySigner } from "@nostr-dev-kit/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") @customElement("arx-initial-setup")
export class InitialSetup extends LitElement { export class InitialSetup extends LitElement {
@ -17,6 +19,15 @@ export class InitialSetup extends LitElement {
@state() private profileImage = ""; @state() private profileImage = "";
@state() private lightningAddress = ""; @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` static override styles = css`
:host { :host {
display: block; display: block;
@ -409,32 +420,9 @@ export class InitialSetup extends LitElement {
`; `;
} }
private getSetupCode() { private async startRelay() {
const userAgent = navigator.userAgent.toLowerCase(); await window.relay.writeSeed(this.seedPhrase);
if (userAgent.includes("mac")) { await window.relay.start(this.encryptionPassphrase);
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 renderPageThree() { private renderPageThree() {
@ -446,11 +434,10 @@ export class InitialSetup extends LitElement {
During this alpha phase, manual relay configuration is required. During this alpha phase, manual relay configuration is required.
This process will be automated in future releases. This process will be automated in future releases.
</p> </p>
<p>Open your terminal and run following commands:</p> <p>Please press the button below to start the relay.</p>
<pre> <button @click=${() => this.startRelay()} class="button primary">
<code>${this.getSetupCode()}</code> Start Relay
</pre </button>
>
<p> <p>
Having trouble? Our team is here to help if you encounter any Having trouble? Our team is here to help if you encounter any
issues. issues.
@ -586,15 +573,11 @@ export class InitialSetup extends LitElement {
} }
private async goToFinalStep() { 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 randomPrivateKey = nostrTools.generateSecretKey();
const encryptedNsec = nip49.encrypt(randomPrivateKey, encryptionPassphrase); const encryptedNsec = nip49.encrypt(
randomPrivateKey,
this.encryptionPassphrase
);
const npub = nip19.npubEncode(nostrTools.getPublicKey(randomPrivateKey)); const npub = nip19.npubEncode(nostrTools.getPublicKey(randomPrivateKey));
if (!this.lightningAddress) this.lightningAddress = `${npub}@npub.cash`; if (!this.lightningAddress) this.lightningAddress = `${npub}@npub.cash`;
@ -602,6 +585,7 @@ export class InitialSetup extends LitElement {
localStorage.setItem("ncryptsec", encryptedNsec); localStorage.setItem("ncryptsec", encryptedNsec);
setSigner(new NDKPrivateKeySigner(randomPrivateKey)); setSigner(new NDKPrivateKeySigner(randomPrivateKey));
await ndk.connect(5000);
const event = new NDKEvent(ndk); const event = new NDKEvent(ndk);
event.kind = NDKKind.Metadata; event.kind = NDKKind.Metadata;

View file

@ -1,18 +1,18 @@
import { css, LitElement } from 'lit'; import { css, LitElement } from "lit";
import { customElement, property } from 'lit/decorators.js'; import { customElement, property } from "lit/decorators.js";
import { unsafeHTML } from 'lit/directives/unsafe-html.js'; import { unsafeHTML } from "lit/directives/unsafe-html.js";
import MarkdownIt, { type StateCore, type Token } from 'markdown-it'; import MarkdownIt, { type StateCore, type Token } from "markdown-it";
function nostrPlugin(md: MarkdownIt): void { function nostrPlugin(md: MarkdownIt): void {
const npubRegex = /(npub[0-9a-zA-Z]{59})/g; 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) { 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++) { for (let i = 0; i < token.children.length; i++) {
const child = token.children[i]; const child = token.children[i];
if (child.type === 'text') { if (child.type === "text") {
const matches = child.content.match(npubRegex); const matches = child.content.match(npubRegex);
if (!matches) continue; if (!matches) continue;
@ -21,28 +21,29 @@ function nostrPlugin(md: MarkdownIt): void {
child.content.replace( child.content.replace(
npubRegex, npubRegex,
// @ts-ignore this is an issue with the types
(match: string, npub: string, offset: number) => { (match: string, npub: string, offset: number) => {
if (offset > lastIndex) { if (offset > lastIndex) {
const textToken = new state.Token('text', '', 0); const textToken = new state.Token("text", "", 0);
textToken.content = child.content.slice(lastIndex, offset); textToken.content = child.content.slice(lastIndex, offset);
newTokens.push(textToken); newTokens.push(textToken);
} }
const linkOpen = new state.Token('link_open', 'a', 1); const linkOpen = new state.Token("link_open", "a", 1);
linkOpen.attrs = [['href', `nostr:${npub}`]]; linkOpen.attrs = [["href", `nostr:${npub}`]];
const text = new state.Token('text', '', 0); const text = new state.Token("text", "", 0);
text.content = npub; 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); newTokens.push(linkOpen, text, linkClose);
lastIndex = offset + match.length; lastIndex = offset + match.length;
}, }
); );
if (lastIndex < child.content.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); textToken.content = child.content.slice(lastIndex);
newTokens.push(textToken); 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 { export class MarkdownContent extends LitElement {
private md = new MarkdownIt({ private md = new MarkdownIt({
html: false, html: false,
@ -68,7 +69,7 @@ export class MarkdownContent extends LitElement {
}); });
@property({ type: String }) @property({ type: String })
content = ''; content = "";
static override styles = [ static override styles = [
css` css`
@ -94,7 +95,16 @@ export class MarkdownContent extends LitElement {
display: contents; display: contents;
} }
h1, h2, h3, h4, h5, h6, code, ul, ol, blockquote { h1,
h2,
h3,
h4,
h5,
h6,
code,
ul,
ol,
blockquote {
width: 100%; width: 100%;
margin: 0; margin: 0;
display: block; display: block;

77
src/electron/main.ts Normal file
View 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
View 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;
}

View 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];
}
}

View file

@ -7,6 +7,6 @@
</head> </head>
<body> <body>
<script src="/src/main.ts" type="module"></script> <script src="main.ts" type="module"></script>
</body> </body>
</html> </html>

View file

@ -7,8 +7,22 @@ import "@components/NostrProfile";
import "@components/Breadcrumbs"; import "@components/Breadcrumbs";
import "@components/Header"; import "@components/Header";
import "@routes/router"; import "@routes/router";
import "@components/LoadingView";
import type EveRouter from "@routes/router"; import type EveRouter from "@routes/router";
import { sleep } from "./utils/sleep";
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; const router = document.createElement("arx-eve-router") as EveRouter;
router.ccnSetup = localStorage.getItem("ncryptsec"); router.ccnSetup = !!localStorage.getItem("ncryptsec");
document.body.appendChild(router); document.body.appendChild(router);
});

View file

@ -1,5 +1,5 @@
import NDK, { NDKPrivateKeySigner } from "@nostr-dev-kit/ndk"; 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({ export const ndk = new NDK({
explicitRelayUrls: ["ws://localhost:6942"], explicitRelayUrls: ["ws://localhost:6942"],
@ -13,8 +13,9 @@ export async function getSigner() {
await ndk.connect(); await ndk.connect();
if (ndk.signer) return; if (ndk.signer) return;
const encryptionPassphrase = localStorage.getItem("encryption_key"); const encryptionPassphrase = localStorage.getItem("encryption_key");
if (!encryptionPassphrase) throw new Error("Encryption passphrase not found");
const signer = new NDKPrivateKeySigner( const signer = new NDKPrivateKeySigner(
nip49.decrypt(localStorage.getItem("ncryptsec"), encryptionPassphrase) nip49.decrypt(localStorage.getItem("ncryptsec")!, encryptionPassphrase)
); );
setSigner(signer); setSigner(signer);
} }

21
src/relayManager.d.ts vendored Normal file
View 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 {};

View file

@ -1,7 +1,6 @@
import { LitElement, html, css } from "lit"; import { LitElement, html, css } from "lit";
import { customElement, property, state } from "lit/decorators.js"; import { customElement, property, state } from "lit/decorators.js";
import { when } from "lit/directives/when.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 type { NDKUserProfile } from "@nostr-dev-kit/ndk";
import { getUserProfile } from "../ndk"; import { getUserProfile } from "../ndk";

View file

@ -78,7 +78,7 @@ export default class EveRouter extends LitElement {
private currentIndex = -1; private currentIndex = -1;
@property() @property()
private ccnSetup = false; public ccnSetup = false;
private beforeEachGuards: ((to: Route, from: Route | null) => boolean)[] = []; private beforeEachGuards: ((to: Route, from: Route | null) => boolean)[] = [];
private afterEachHooks: ((to: Route, from: Route | null) => void)[] = []; private afterEachHooks: ((to: Route, from: Route | null) => void)[] = [];
@ -116,6 +116,8 @@ export default class EveRouter extends LitElement {
constructor() { constructor() {
super(); super();
this.initializeRouter(); this.initializeRouter();
if (this.ccnSetup)
window.relay.start(localStorage.getItem("encryption_key")!);
} }
override connectedCallback(): void { override connectedCallback(): void {
@ -276,7 +278,7 @@ export default class EveRouter extends LitElement {
?canGoBack=${this.currentIndex > 0} ?canGoBack=${this.currentIndex > 0}
?canGoForward=${this.currentIndex < this.history.length - 1} ?canGoForward=${this.currentIndex < this.history.length - 1}
url="eve://${this.currentPath}" url="eve://${this.currentPath}"
@navigate=${(e) => this.navigate(e.detail)} @navigate=${(e: CustomEvent) => this.navigate(e.detail)}
@go-back=${this.goBack} @go-back=${this.goBack}
@go-forward=${this.goForward} @go-forward=${this.goForward}
title="Eve" title="Eve"

3
src/utils/sleep.ts Normal file
View file

@ -0,0 +1,3 @@
export function sleep(ms: number) {
return new Promise((resolve) => setTimeout(resolve, ms));
}

View file

@ -31,5 +31,6 @@
"@components/*": ["./src/components/*"], "@components/*": ["./src/components/*"],
"@widgets/*": ["./src/components/Widgets/*"] "@widgets/*": ["./src/components/Widgets/*"]
} }
} },
"include": ["./src/**/*"]
} }

View file

@ -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",
},
},
});