| … | … | @@ -1,15 +1,16 @@ |
| 1 | 1 | #!/usr/bin/env node |
| 2 | | /** @typedef {import('pear-interface')} */ /* global Pear */ |
| 3 | | |
| 4 | | /** |
| 5 | | * Wirecore Pear App - P2P Social Network Terminal Client |
| 6 | | * |
| 7 | | * Architecture: |
| 8 | | * - Each user has their own writable Hypercore feed |
| 9 | | * - Following = joining their Hyperswarm topic and replicating read-only |
| 10 | | * - Timeline = merge all followed feeds by timestamp |
| 11 | | * - Sparse replication = only followed users, not entire network |
| 12 | | */ |
| 2 | // Wirecore - P2P Social Network Terminal Client |
| 3 | // |
| 4 | // Architecture: |
| 5 | // - Each user has their own writable Hypercore feed |
| 6 | // - Following = joining their Hyperswarm topic and replicating read-only |
| 7 | // - Timeline = merge all followed feeds by timestamp |
| 8 | // - Sparse replication = only followed users, not entire network |
| 9 | // |
| 10 | // pear-runtime provides P2P OTA updates when running from a deployed install. |
| 11 | // |
| 12 | // Usage: bare index.js <command> [options] |
| 13 | // pear run pear://KEY <command> [options] |
| 13 | 14 | |
| 14 | 15 | import Corestore from 'corestore' |
| 15 | 16 | import Hyperswarm from 'hyperswarm' |
| … | … | @@ -25,6 +26,7 @@ import bareWs from 'bare-ws' |
| 25 | 26 | import fs from 'bare-fs' |
| 26 | 27 | import path from 'bare-path' |
| 27 | 28 | import process from 'bare-process' |
| 29 | import os from 'bare-os' |
| 28 | 30 | import { ANProto } from './lib/anproto.js' |
| 29 | 31 | import { YAMLHelper } from './lib/yaml-helper.js' |
| 30 | 32 | |
| … | … | @@ -33,21 +35,20 @@ const { Server: WebSocketServer } = bareWs |
| 33 | 35 | // Check if running in Pear or Node |
| 34 | 36 | const isPear = typeof Pear !== 'undefined' |
| 35 | 37 | |
| 36 | | // Access the app bundle using pear-appdrive (read-only) |
| 37 | | let appDrive = null |
| 38 | | if (isPear) { |
| 39 | | try { |
| 40 | | const { default: AppDrive } = await import('pear-appdrive') |
| 41 | | appDrive = new AppDrive() |
| 42 | | await appDrive.ready() |
| 43 | | console.log('[DEBUG] pear-appdrive ready - can read app bundle files') |
| 44 | | } catch (err) { |
| 45 | | console.log('[DEBUG] pear-appdrive failed:', err.message) |
| 46 | | } |
| 38 | function getAppDir () { |
| 39 | return path.dirname(new URL(import.meta.url).pathname) |
| 40 | } |
| 41 | |
| 42 | function getStorageDir () { |
| 43 | const p = os.platform() |
| 44 | const name = 'wirecore' |
| 45 | if (p === 'darwin') return path.join(os.homedir(), 'Library', 'Application Support', name) |
| 46 | if (p === 'win32') return path.join(os.homedir(), 'AppData', 'Roaming', name) |
| 47 | return path.join(os.homedir(), '.config', name) |
| 47 | 48 | } |
| 48 | 49 | |
| 49 | 50 | // Configuration |
| 50 | | const STORAGE_PATH = isPear && Pear.config.storage ? Pear.config.storage : './wirecore-storage' |
| 51 | const STORAGE_PATH = isPear && Pear.config.storage ? Pear.config.storage : getStorageDir() |
| 51 | 52 | |
| 52 | 53 | // Global state |
| 53 | 54 | let store = null |
| … | … | @@ -1490,30 +1491,14 @@ function readBody(req) { |
| 1490 | 1491 | }) |
| 1491 | 1492 | } |
| 1492 | 1493 | |
| 1493 | | // Serve static files |
| 1494 | // Serve static files from local filesystem |
| 1494 | 1495 | async function serveStatic(req, res, url) { |
| 1495 | 1496 | // Remove leading slash and default to index.html |
| 1496 | 1497 | let fileName = url.pathname === '/' ? 'index.html' : url.pathname.substring(1) |
| 1497 | 1498 | |
| 1498 | | console.log(`[STATIC] Request: ${url.pathname} → ${fileName}`) |
| 1499 | | |
| 1500 | 1499 | try { |
| 1501 | | let data |
| 1502 | | |
| 1503 | | if (appDrive) { |
| 1504 | | // Running from pear:// link - read from app bundle using pear-appdrive |
| 1505 | | const filePath = '/public/' + fileName |
| 1506 | | console.log(`[STATIC] Reading from app bundle: ${filePath}`) |
| 1507 | | data = await appDrive.get(filePath) |
| 1508 | | if (!data) throw new Error('File not found in app bundle') |
| 1509 | | } else { |
| 1510 | | // Local development - read from filesystem |
| 1511 | | const localPath = path.join('public', fileName) |
| 1512 | | console.log(`[STATIC] Reading from filesystem: ${localPath}`) |
| 1513 | | data = fs.readFileSync(localPath) |
| 1514 | | } |
| 1515 | | |
| 1516 | | console.log(`[STATIC] ✅ Success: ${fileName} (${data.length} bytes)`) |
| 1500 | const localPath = path.join(getAppDir(), 'public', fileName) |
| 1501 | const data = fs.readFileSync(localPath) |
| 1517 | 1502 | |
| 1518 | 1503 | const ext = path.extname(fileName) |
| 1519 | 1504 | const contentType = { |
| … | … | @@ -1527,8 +1512,6 @@ async function serveStatic(req, res, url) { |
| 1527 | 1512 | res.writeHead(200, { 'Content-Type': contentType }) |
| 1528 | 1513 | res.end(data) |
| 1529 | 1514 | } catch (err) { |
| 1530 | | console.error(`[STATIC] ❌ Failed: ${fileName}`) |
| 1531 | | console.error(`[STATIC] Error: ${err.message}`) |
| 1532 | 1515 | res.writeHead(404, { 'Content-Type': 'text/plain' }) |
| 1533 | 1516 | res.end('Not found') |
| 1534 | 1517 | } |
| … | … | @@ -1873,7 +1856,7 @@ function showUsage() { |
| 1873 | 1856 | * Main CLI handler |
| 1874 | 1857 | */ |
| 1875 | 1858 | async function main() { |
| 1876 | | const args = isPear ? (Pear.config.args || []) : process.argv.slice(2) |
| 1859 | const args = isPear ? (Pear.config.args || []) : (typeof Bare !== 'undefined' ? Bare.argv.slice(2) : process.argv.slice(2)) |
| 1877 | 1860 | |
| 1878 | 1861 | if (args.length === 0) { |
| 1879 | 1862 | showUsage() |
| … | … | @@ -2009,10 +1992,6 @@ async function main() { |
| 2009 | 1992 | |
| 2010 | 1993 | case 'storage-path': |
| 2011 | 1994 | console.log('Storage path:', STORAGE_PATH) |
| 2012 | | console.log('Is Pear:', isPear) |
| 2013 | | if (isPear) { |
| 2014 | | console.log('Pear.config.storage:', Pear.config.storage) |
| 2015 | | } |
| 2016 | 1995 | break |
| 2017 | 1996 | |
| 2018 | 1997 | case 'migrate-storage': |
| … | … | @@ -2073,8 +2052,33 @@ async function main() { |
| 2073 | 2052 | } |
| 2074 | 2053 | } |
| 2075 | 2054 | |
| 2055 | // Initialize pear-runtime for P2P OTA updates (non-blocking) |
| 2056 | async function initPearRuntime () { |
| 2057 | try { |
| 2058 | const pkg = JSON.parse(fs.readFileSync(path.join(getAppDir(), 'package.json'), 'utf-8')) |
| 2059 | if (!pkg.upgrade) return null |
| 2060 | |
| 2061 | const { default: PearRuntime } = await import('pear-runtime') |
| 2062 | const dir = getStorageDir() |
| 2063 | |
| 2064 | const runtime = new PearRuntime({ dir, app: getAppDir(), version: pkg.version, upgrade: pkg.upgrade }) |
| 2065 | runtime.on('updated', () => { |
| 2066 | runtime.applyUpdate().catch(() => {}) |
| 2067 | }) |
| 2068 | return runtime |
| 2069 | } catch { |
| 2070 | return null |
| 2071 | } |
| 2072 | } |
| 2073 | |
| 2076 | 2074 | // Run main |
| 2075 | let runtimeRef = null |
| 2076 | if (!isPear) { |
| 2077 | runtimeRef = initPearRuntime() |
| 2078 | } |
| 2079 | |
| 2077 | 2080 | main().catch(err => { |
| 2078 | 2081 | console.error('Fatal error:', err) |
| 2079 | | process.exit(1) |
| 2082 | if (typeof Bare !== 'undefined') Bare.exit(1) |
| 2083 | else process.exit(1) |
| 2080 | 2084 | }) |