Skip to content

Commit ddb620c

Browse files
Merge pull request #180 from p2plabsxyz/fix/p2p-app-update-packaged-builds
fix: P2P app updates for packaged builds
2 parents 411b695 + a001400 commit ddb620c

6 files changed

Lines changed: 133 additions & 6 deletions

File tree

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
"files": [
3535
"node_modules/**/*",
3636
"package.json",
37+
".gitmodules",
3738
"pages/**/*",
3839
"public/*",
3940
"src/**/*",

src/p2p-app-registry.js

Lines changed: 117 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { app, ipcMain, dialog } from "electron";
44
import { promises as fs } from "fs";
55
import { exec } from "child_process";
66
import { promisify } from "util";
7+
import unzipper from "unzipper";
78

89
const execAsync = promisify(exec);
910
const __filename = fileURLToPath(import.meta.url);
@@ -363,9 +364,124 @@ class P2PAppRegistry {
363364
await this.saveRegistry();
364365
}
365366

367+
parseGitModules(content) {
368+
const submodules = [];
369+
let current = null;
370+
for (const line of content.split('\n')) {
371+
const trimmed = line.trim();
372+
if (trimmed.startsWith('[submodule')) {
373+
current = {};
374+
submodules.push(current);
375+
} else if (current && trimmed.startsWith('path =')) {
376+
current.subPath = trimmed.slice('path ='.length).trim();
377+
} else if (current && trimmed.startsWith('url =')) {
378+
current.url = trimmed.slice('url ='.length).trim();
379+
}
380+
}
381+
return submodules
382+
.filter(s => s.subPath && s.url && s.url.includes('github.com'))
383+
.map(s => ({
384+
repo: s.url.replace(/^https?:\/\/github\.com\//, '').replace(/\.git$/, ''),
385+
dir: path.basename(s.subPath)
386+
}));
387+
}
388+
389+
async updateSubmodulesFromGitHub() {
390+
let SUBMODULE_APPS;
391+
try {
392+
const gitmodulesPath = path.join(app.getAppPath(), '.gitmodules');
393+
const content = await fs.readFile(gitmodulesPath, 'utf8');
394+
SUBMODULE_APPS = this.parseGitModules(content);
395+
if (!SUBMODULE_APPS.length) throw new Error('No GitHub submodules found in .gitmodules');
396+
} catch (err) {
397+
console.error('Could not parse .gitmodules:', err.message);
398+
return { success: false, error: `Could not read .gitmodules: ${err.message}` };
399+
}
400+
401+
// In packaged builds, __dirname points inside app.asar (read-only)
402+
// We need to write to app.asar.unpacked instead
403+
let p2pDir;
404+
if (app.isPackaged) {
405+
const appPath = app.getAppPath();
406+
const unpackedPath = appPath.replace(/\.asar$/, '.asar.unpacked');
407+
p2pDir = path.join(unpackedPath, 'src', 'pages', 'p2p');
408+
} else {
409+
p2pDir = path.join(__dirname, 'pages', 'p2p');
410+
}
411+
const errors = [];
412+
413+
for (const mod of SUBMODULE_APPS) {
414+
try {
415+
const zipUrl = `https://github.com/${mod.repo}/archive/HEAD.zip`;
416+
const controller = new AbortController();
417+
const timer = setTimeout(() => controller.abort(), 60000);
418+
419+
let response;
420+
try {
421+
response = await fetch(zipUrl, { signal: controller.signal });
422+
} finally {
423+
clearTimeout(timer);
424+
}
425+
426+
if (!response.ok) {
427+
throw new Error(`HTTP ${response.status}`);
428+
}
429+
430+
const buffer = Buffer.from(await response.arrayBuffer());
431+
const directory = await unzipper.Open.buffer(buffer);
432+
const targetDir = path.join(p2pDir, mod.dir);
433+
434+
// Clear existing contents before extracting fresh copy
435+
try {
436+
const existing = await fs.readdir(targetDir);
437+
await Promise.all(
438+
existing.map(e => fs.rm(path.join(targetDir, e), { recursive: true, force: true }))
439+
);
440+
} catch {
441+
await fs.mkdir(targetDir, { recursive: true });
442+
}
443+
444+
// Extract all files, stripping the top-level GitHub zip folder (e.g. "peerchat-main/")
445+
for (const file of directory.files) {
446+
const relativePath = file.path.replace(/^[^/]+\//, '');
447+
if (!relativePath) continue;
448+
449+
const destPath = path.join(targetDir, relativePath);
450+
if (file.type === 'Directory' || relativePath.endsWith('/')) {
451+
await fs.mkdir(destPath, { recursive: true });
452+
} else {
453+
await fs.mkdir(path.dirname(destPath), { recursive: true });
454+
const content = await file.buffer();
455+
await fs.writeFile(destPath, content);
456+
}
457+
}
458+
} catch (error) {
459+
console.error(`Failed to update ${mod.dir}:`, error);
460+
errors.push(`${mod.dir}: ${error.message}`);
461+
}
462+
}
463+
464+
if (errors.length > 0) {
465+
return {
466+
success: false,
467+
error: `Failed to update some apps: ${errors.join('; ')}`
468+
};
469+
}
470+
471+
return {
472+
success: true,
473+
message: 'P2P apps updated to latest versions. Refresh to see changes.'
474+
};
475+
}
476+
366477
async updateSubmodules() {
478+
// Packaged builds have no .git and no git binary — use GitHub zip downloads instead
479+
if (app.isPackaged) {
480+
return await this.updateSubmodulesFromGitHub();
481+
}
482+
483+
// Development: use git submodule update
367484
try {
368-
// Get the project root directory (where .git is located)
369485
const projectRoot = app.getAppPath();
370486

371487
// Update all submodules to their latest commits

src/pages/p2p/p2p-list.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
const builtInP2PApps = [
22
{ id: "ai-chat", name: "AI Chat", icon: "robot.svg", url: "peersky://p2p/ai-chat/" },
3-
{ id: "chat", name: "Chat", icon: "chat.svg", url: "peersky://p2p/peerchat/" },
4-
{ id: "editor", name: "Editor", icon: "file-code.svg", url: "peersky://p2p/peerpad/" },
3+
{ id: "chat", name: "PeerChat", icon: "chat.svg", url: "peersky://p2p/peerchat/" },
4+
{ id: "editor", name: "PeerPad", icon: "file-code.svg", url: "peersky://p2p/peerpad/" },
55
{ id: "p2pmd", name: "P2P Markdown", icon: "markdown.svg", url: "peersky://p2p/p2pmd/" },
66
{ id: "reader", name: "Social Reader", icon: "people.svg", url: "https://reader.distributed.press/" },
77
{ id: "upload", name: "Upload", icon: "file-upload.svg", url: "peersky://p2p/upload/" },

src/pages/p2p/peerchat

src/pages/p2p/upload

src/protocols/peersky-protocol.js

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,17 @@ import extensionManager from '../extensions/index.js';
1111
const log = createLogger('protocols:peersky');
1212

1313
const __dirname = fileURLToPath(new URL('./', import.meta.url));
14-
const pagesPath = path.join(__dirname, '../pages');
14+
15+
// In packaged builds, serve from app.asar.unpacked so we can read updated P2P apps
16+
let pagesPath;
17+
if (app.isPackaged) {
18+
const appPath = app.getAppPath();
19+
const unpackedPath = appPath.replace(/\.asar$/, '.asar.unpacked');
20+
pagesPath = path.join(unpackedPath, 'src', 'pages');
21+
} else {
22+
pagesPath = path.join(__dirname, '../pages');
23+
}
24+
1525
const fs = new ScopedFS(pagesPath);
1626

1727
const CHECK_PATHS = [

0 commit comments

Comments
 (0)