diff --git a/electron/after-all-artifact-build.js b/electron/after-all-artifact-build.cjs
similarity index 60%
rename from electron/after-all-artifact-build.js
rename to electron/after-all-artifact-build.cjs
index 6cbe6039..7b684bc2 100644
--- a/electron/after-all-artifact-build.js
+++ b/electron/after-all-artifact-build.cjs
@@ -1,14 +1,11 @@
-// hook that runs after electron-build
-
-import fs from 'fs-extra';
-import path from 'path';
-import { execSync } from 'child_process';
-import packageJson from '../package.json' assert { type: 'json' };
-import { fileURLToPath } from 'url';
-const rootPath = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
+const fs = require('fs-extra');
+const path = require('path');
+const { execSync } = require('child_process');
+const packageJson = require('../package.json');
+const rootPath = path.resolve(__dirname, '..');
const distFolderPath = path.resolve(rootPath, 'dist');
-const addPortableToPortableExecutableFileName = () => {
+function addPortableToPortableExecutableFileName() {
const files = fs.readdirSync(distFolderPath);
for (const file of files) {
if (file.endsWith('.exe') && !file.match('Setup')) {
@@ -17,9 +14,9 @@ const addPortableToPortableExecutableFileName = () => {
fs.moveSync(filePath, renamedFilePath);
}
}
-};
+}
-const createHtmlArchive = () => {
+function createHtmlArchive() {
if (process.platform !== 'linux') {
return;
}
@@ -28,17 +25,14 @@ const createHtmlArchive = () => {
const outputFile = path.resolve(distFolderPath, `${plebchanHtmlFolderName}.zip`);
const inputFolder = path.resolve(rootPath, 'build');
try {
- // will break if node_modules/7zip-bin changes
execSync(`${zipBinPath} a ${outputFile} ${inputFolder}`);
- // rename 'build' folder to 'plebchan-html-version' inside the archive
execSync(`${zipBinPath} rn -r ${outputFile} build ${plebchanHtmlFolderName}`);
} catch (e) {
- e.message = 'electron build createHtmlArchive error: ' + e.message;
- console.log(e);
+ console.error('electron build createHtmlArchive error:', e);
}
-};
+}
-export default async (buildResult) => {
+module.exports = async function afterAllArtifactBuild(buildResult) {
addPortableToPortableExecutableFileName();
createHtmlArchive();
-};
+};
\ No newline at end of file
diff --git a/electron/before-pack.js b/electron/before-pack.js
index 135ff946..1ceef74a 100644
--- a/electron/before-pack.js
+++ b/electron/before-pack.js
@@ -24,12 +24,20 @@ const ipfsClientMacUrl = `https://dist.ipfs.io/kubo/v${ipfsClientVersion}/kubo_v
const ipfsClientLinuxUrl = `https://dist.ipfs.io/kubo/v${ipfsClientVersion}/kubo_v${ipfsClientVersion}_linux-amd64.tar.gz`;
const downloadWithProgress = (url) =>
- new Promise((resolve) => {
+ new Promise((resolve, reject) => {
const split = url.split('/');
const fileName = split[split.length - 1];
const chunks = [];
const req = https.request(url);
+ req.on('error', (err) => {
+ console.error(`Error making request for ${url}:`, err);
+ reject(err);
+ });
req.on('response', (res) => {
+ res.on('error', (err) => {
+ console.error(`Error in response for ${url}:`, err);
+ reject(err);
+ });
// handle redirects
if (res.statusCode == 301 || res.statusCode === 302) {
resolve(downloadWithProgress(res.headers.location));
@@ -43,6 +51,7 @@ const downloadWithProgress = (url) =>
incomplete: ' ',
width: 20,
total: len,
+ stream: process.stdout,
});
res.on('data', (chunk) => {
chunks.push(chunk);
@@ -56,6 +65,20 @@ const downloadWithProgress = (url) =>
req.end();
});
+// add retry wrapper around downloadWithProgress to handle transient network errors
+const downloadWithRetry = async (url, retries = 3) => {
+ for (let attempt = 1; attempt <= retries; attempt++) {
+ try {
+ return await downloadWithProgress(url);
+ } catch (err) {
+ console.warn(`Download attempt ${attempt} for ${url} failed:`, err);
+ if (attempt === retries) throw err;
+ // wait before retrying
+ await new Promise((res) => setTimeout(res, attempt * 1000));
+ }
+ }
+};
+
// plebbit kubo downloads dont need to be extracted
const download = async (url, destinationPath) => {
let binName = 'ipfs';
@@ -69,8 +92,8 @@ const download = async (url, destinationPath) => {
}
const split = url.split('/');
const fileName = split[split.length - 1];
- const dowloadPath = path.join(destinationPath, fileName);
- const file = await downloadWithProgress(url);
+ const downloadPath = path.join(destinationPath, fileName);
+ const file = await downloadWithRetry(url);
fs.ensureDirSync(destinationPath);
await fs.writeFile(binPath, file);
};
@@ -85,24 +108,51 @@ const downloadAndExtract = async (url, destinationPath) => {
if (fs.pathExistsSync(binPath)) {
return;
}
+ console.log(`Downloading IPFS client from ${url} to ${destinationPath}`);
const split = url.split('/');
const fileName = split[split.length - 1];
- const dowloadPath = path.join(destinationPath, fileName);
- const file = await downloadWithProgress(url);
+ const downloadPath = path.join(destinationPath, fileName);
+ const file = await downloadWithRetry(url);
fs.ensureDirSync(destinationPath);
- await fs.writeFile(dowloadPath, file);
- await decompress(dowloadPath, destinationPath);
+ await fs.writeFile(downloadPath, file);
+ console.log(`Downloaded archive to ${downloadPath}`);
+ console.log(`Extracting ${downloadPath} to ${destinationPath}`);
+ try {
+ await decompress(downloadPath, destinationPath);
+ console.log('Decompression complete');
+ } catch (err) {
+ console.error('Error during decompression:', err);
+ throw err;
+ }
const extractedPath = path.join(destinationPath, 'kubo');
const extractedBinPath = path.join(extractedPath, binName);
+ console.log(`Moving binary from ${extractedBinPath} to ${binPath}`);
fs.moveSync(extractedBinPath, binPath);
- fs.removeSync(extractedPath);
- fs.removeSync(dowloadPath);
+ console.log('Binary moved');
+ console.log('Cleaning up temporary files');
+ fs.removeSync(downloadPath);
+ console.log('Cleanup complete');
};
export const downloadIpfsClients = async () => {
- await downloadAndExtract(ipfsClientWindowsUrl, ipfsClientWindowsPath);
- await downloadAndExtract(ipfsClientMacUrl, ipfsClientMacPath);
- await downloadAndExtract(ipfsClientLinuxUrl, ipfsClientLinuxPath);
+ const platform = process.platform;
+ console.log(`Starting IPFS client download for platform: ${platform}`);
+ switch (platform) {
+ case 'win32':
+ await downloadAndExtract(ipfsClientWindowsUrl, ipfsClientWindowsPath);
+ break;
+ case 'darwin':
+ await downloadAndExtract(ipfsClientMacUrl, ipfsClientMacPath);
+ break;
+ case 'linux':
+ await downloadAndExtract(ipfsClientLinuxUrl, ipfsClientLinuxPath);
+ break;
+ default:
+ console.warn(`Unknown platform: ${platform}, downloading all IPFS clients`);
+ await downloadAndExtract(ipfsClientWindowsUrl, ipfsClientWindowsPath);
+ await downloadAndExtract(ipfsClientMacUrl, ipfsClientMacPath);
+ await downloadAndExtract(ipfsClientLinuxUrl, ipfsClientLinuxPath);
+ }
};
export default async (context) => {
diff --git a/electron/download-ipfs.js b/electron/download-ipfs.js
index 710e8b30..02812e84 100644
--- a/electron/download-ipfs.js
+++ b/electron/download-ipfs.js
@@ -1,2 +1,13 @@
import { downloadIpfsClients } from './before-pack.js';
-downloadIpfsClients();
+
+// Wrap the download in an async IIFE and exit when done
+(async () => {
+ try {
+ await downloadIpfsClients();
+ console.log('IPFS clients downloaded successfully.');
+ process.exit(0);
+ } catch (err) {
+ console.error('IPFS clients download error:', err);
+ process.exit(1);
+ }
+})();
diff --git a/electron/main.js b/electron/main.js
index a90c5d34..cc9b3775 100644
--- a/electron/main.js
+++ b/electron/main.js
@@ -1,5 +1,5 @@
import './log.js';
-import { app, BrowserWindow, Menu, MenuItem, Tray, screen as electronScreen, shell, dialog, nativeTheme, ipcMain } from 'electron';
+import { app, BrowserWindow, Menu, MenuItem, Tray, shell, dialog, nativeTheme, ipcMain } from 'electron';
import isDev from 'electron-is-dev';
import fs from 'fs';
import path from 'path';
@@ -8,8 +8,13 @@ import startIpfs from './start-ipfs.js';
import './start-plebbit-rpc.js';
import { URL, fileURLToPath } from 'node:url';
import contextMenu from 'electron-context-menu';
-import packageJson from '../package.json' with { type: 'json' };
-const dirname = path.join(path.dirname(fileURLToPath(import.meta.url)));
+
+// Determine __filename and dirname for ESM
+const __filename = fileURLToPath(import.meta.url);
+const dirname = path.dirname(__filename);
+
+// Load package.json dynamically
+const packageJson = JSON.parse(fs.readFileSync(path.join(dirname, '../package.json'), 'utf-8'));
let startIpfsError;
startIpfs.onError = (error) => {
@@ -78,7 +83,7 @@ const createMainWindow = () => {
nodeIntegration: false,
contextIsolation: true,
devTools: true, // TODO: change to isDev when no bugs left
- preload: path.join(dirname, 'preload.js'),
+ preload: path.join(dirname, '../build/electron/preload.cjs'),
},
});
diff --git a/electron/preload.js b/electron/preload.mjs
similarity index 77%
rename from electron/preload.js
rename to electron/preload.mjs
index 40238671..c542bd61 100644
--- a/electron/preload.js
+++ b/electron/preload.mjs
@@ -1,10 +1,11 @@
-const { contextBridge, ipcRenderer } = require('electron');
+import { contextBridge, ipcRenderer } from 'electron';
// dev uses http://localhost, prod uses file://...index.html
const isDev = window.location.protocol === 'http:';
const defaultPlebbitOptions = {
plebbitRpcClientsOptions: ['ws://localhost:9138'],
+ httpRoutersOptions: ['https://peers.pleb.bot', 'https://routing.lol', 'https://peers.forumindex.com', 'https://peers.plebpubsub.xyz'],
};
contextBridge.exposeInMainWorld('isElectron', true);
@@ -14,6 +15,3 @@ contextBridge.exposeInMainWorld('defaultMediaIpfsGatewayUrl', 'http://localhost:
// receive plebbit rpc auth key from main
ipcRenderer.on('plebbit-rpc-auth-key', (event, plebbitRpcAuthKey) => contextBridge.exposeInMainWorld('plebbitRpcAuthKey', plebbitRpcAuthKey));
ipcRenderer.send('get-plebbit-rpc-auth-key');
-
-// uncomment for logs
-// localStorage.debug = 'plebbit-js:*,plebbit-react-hooks:*,plebchan:*'
diff --git a/electron/vite.preload.config.js b/electron/vite.preload.config.js
new file mode 100644
index 00000000..fcfd9462
--- /dev/null
+++ b/electron/vite.preload.config.js
@@ -0,0 +1,27 @@
+import { defineConfig } from 'vite';
+import { resolve, dirname } from 'path';
+import { fileURLToPath } from 'url';
+
+const __filename = fileURLToPath(import.meta.url);
+const __dirname = dirname(__filename);
+
+export default defineConfig({
+ resolve: {
+ conditions: ['node'],
+ },
+ build: {
+ lib: {
+ entry: resolve(__dirname, 'preload.mjs'),
+ name: 'Preload',
+ fileName: () => 'preload.cjs',
+ formats: ['cjs'],
+ },
+ outDir: resolve(__dirname, '../build/electron'),
+ emptyOutDir: true,
+ rollupOptions: {
+ external: ['electron'],
+ },
+ target: 'node20',
+ minify: false,
+ },
+});
diff --git a/package.json b/package.json
index 53d15612..9ff4daf0 100644
--- a/package.json
+++ b/package.json
@@ -49,17 +49,18 @@
"scripts": {
"start": "vite",
"build": "cross-env PUBLIC_URL=./ GENERATE_SOURCEMAP=false vite build",
+ "build:preload": "vite build --config electron/vite.preload.config.js",
"build-netlify": "cross-env NODE_OPTIONS=\"--max_old_space_size=4096\" PUBLIC_URL=./ GENERATE_SOURCEMAP=true VITE_COMMIT_REF=$COMMIT_REF CI='' vite build",
"test": "vitest",
"preview": "vite preview",
- "analyze-bundle": "cross-env PUBLIC_URL=./ GENERATE_SOURCEMAP=true vite build && source-map-explorer 'build/assets/*.js'",
- "electron": "yarn electron:before && electron .",
+ "analyze-bundle": "cross-env PUBLIC_URL=./ GENERATE_SOURCEMAP=true vite build && npx source-map-explorer 'build/assets/*.js'",
+ "electron": "yarn build:preload && cross-env ELECTRON_IS_DEV=1 yarn electron:before && cross-env ELECTRON_IS_DEV=1 electron .",
"electron:no-delete-data": "yarn electron:before:download-ipfs && electron .",
"electron:start": "concurrently \"cross-env BROWSER=none yarn start\" \"wait-on http://localhost:3000 && yarn electron\"",
"electron:start:no-delete-data": "concurrently \"cross-env BROWSER=none yarn start\" \"wait-on http://localhost:3000 && yarn electron:no-delete-data\"",
- "electron:build:linux": "yarn build && electron-rebuild && electron-builder build --publish never -l",
- "electron:build:windows": "yarn build && yarn electron-rebuild && electron-builder build --publish never -w",
- "electron:build:mac": "yarn build && yarn electron-rebuild && electron-builder build --publish never -m",
+ "electron:build:linux": "yarn build && yarn build:preload && electron-rebuild && electron-builder build --publish never -l",
+ "electron:build:windows": "yarn build && yarn build:preload && yarn electron-rebuild && electron-builder build --publish never -w",
+ "electron:build:mac": "yarn build && yarn build:preload && yarn electron-rebuild && electron-builder build --publish never -m",
"electron:before": "yarn electron-rebuild && yarn electron:before:download-ipfs && yarn electron:before:delete-data",
"electron:before:download-ipfs": "node electron/download-ipfs",
"electron:before:delete-data": "rimraf .plebbit",
@@ -83,9 +84,9 @@
]
},
"devDependencies": {
- "@capacitor/android": "5.0.0",
- "@capacitor/cli": "5.0.0",
- "@capacitor/core": "5.0.0",
+ "@capacitor/android": "7.2.0",
+ "@capacitor/cli": "7.2.0",
+ "@capacitor/core": "7.2.0",
"@electron/rebuild": "4.0.0",
"@types/memoizee": "0.4.9",
"@typescript-eslint/eslint-plugin": "8.29.0",
@@ -112,12 +113,15 @@
"isomorphic-fetch": "3.0.0",
"lint-staged": "12.3.8",
"stream-browserify": "3.0.0",
- "vite": "6.2.0",
+ "vite": "6.2.1",
"vite-plugin-eslint": "1.8.1",
"vite-plugin-node-polyfills": "0.23.0",
"vite-plugin-pwa": "0.21.1",
"wait-on": "7.0.1"
},
+ "resolutions": {
+ "@bonfida/spl-name-service": "3.0.0"
+ },
"main": "electron/main.js",
"build": {
"appId": "plebchan.desktop",
diff --git a/src/components/catalog-row/catalog-row.tsx b/src/components/catalog-row/catalog-row.tsx
index 5cc50d22..a381021f 100644
--- a/src/components/catalog-row/catalog-row.tsx
+++ b/src/components/catalog-row/catalog-row.tsx
@@ -242,7 +242,7 @@ const CatalogPost = ({ post }: { post: Comment }) => {
) : hasThumbnail ? (
<>
- {shouldShowSnow() && hasThumbnail &&
}
+ {shouldShowSnow() && hasThumbnail &&
}
}
+ {shouldShowSnow() && hasThumbnail &&
}
{link && !isHidden && !(deleted || removed) && isValidURL(link) && (
}
+ {shouldShowSnow() &&
}