Oasis 0.8.7 mobile (nodejs-project baseline)

This commit is contained in:
s1to 2026-07-09 09:56:30 +02:00
commit e563b86ede
226 changed files with 141968 additions and 0 deletions

191
src/server/SSB_server.js Normal file
View file

@ -0,0 +1,191 @@
#!/usr/bin/env node
const moduleAlias = require('module-alias');
moduleAlias.addAlias('punycode', 'punycode/');
const fs = require('fs');
const path = require('path');
const SecretStack = require('secret-stack');
const caps = require('ssb-caps');
const SSB = require('ssb-db');
const config = require('./ssb_config');
const { printMetadata } = require('./ssb_metadata');
(() => {
const realErr = console.error;
const SHS_NOISE = /shs\.server:|they dailed a wrong number|client hello invalid|invalid challenge|wrong application cap/i;
const EBT_NOISE = /stream ended with:\s*\d+\s+but wanted:\s*\d+/i;
const EBT_HANDSHAKE_NOISE = /does not support RPC ebt\./i;
const isEbtReplicateException = (args) =>
args.length >= 2 &&
typeof args[0] === 'string' &&
/rpc\.ebt\.replicate exception/i.test(args[0]) &&
args[1] && typeof args[1].message === 'string' && EBT_NOISE.test(args[1].message);
const parsePeer = (addr) => {
if (typeof addr !== 'string') return 'unknown';
const m = /net:(.+?):(\d+)(?:~|$)/.exec(addr);
if (!m) return addr;
return `${m[1].replace(/^::ffff:/, '')}:${m[2]}`;
};
const logRejection = (peer) => {
const ts = new Date().toISOString().replace('T', ' ').slice(0, 19);
realErr.call(console, `[${ts}] REJECTED ${peer} (wrong SHS cap)`);
};
console.error = function (...args) {
if (args.length >= 2 && args[0] === 'server error, from' && typeof args[1] === 'string' && args[1].includes('~shs:')) {
logRejection(parsePeer(args[1]));
return;
}
if (args.length >= 1 && args[0] && typeof args[0].message === 'string' && SHS_NOISE.test(args[0].message)) {
logRejection(parsePeer(args[0].address));
return;
}
if (args.length >= 1 && args[0] && typeof args[0].message === 'string' && EBT_NOISE.test(args[0].message)) return;
if (isEbtReplicateException(args)) return;
if (args.length >= 1 && typeof args[0] === 'string' && /rpc\.ebt\.replicate exception:.*stream ended with/i.test(args[0])) return;
return realErr.apply(console, args);
};
const realWarn = console.warn;
console.warn = function (...args) {
if (args.length >= 1 && typeof args[0] === 'string' && EBT_HANDSHAKE_NOISE.test(args[0])) return;
return realWarn.apply(console, args);
};
})();
require('ssb-plugins').loadUserPlugins(SecretStack({ caps }), config);
const Server = SecretStack({ caps })
.use(SSB)
.use(require('ssb-master'))
.use(require('ssb-gossip'))
.use(require('ssb-ebt'))
.use(require('ssb-friends'))
.use(require('ssb-blobs'))
.use(require('ssb-meme'))
.use(require('ssb-plugins'))
.use(require('ssb-conn'))
.use(require('ssb-box'))
.use(require('ssb-search'))
.use(require('ssb-private'))
.use(require('ssb-friend-pub'))
.use(config.pub ? require('ssb-invite') : require('ssb-invite-client'))
.use(require('ssb-logging'))
.use(require('ssb-replication-scheduler'))
.use(require('ssb-partial-replication'))
.use(require('ssb-about'))
.use(require('ssb-onion'))
.use(require('ssb-unix-socket'))
.use(require('ssb-no-auth'))
.use(require('ssb-backlinks'))
.use(require('ssb-links'))
.use(require('ssb-tangle'))
.use(require('ssb-query'));
if (config.autofollow && typeof config.autofollow === 'object' && !Array.isArray(config.autofollow)) {
if (config.autofollow.enabled === false) {
config.autofollow = null;
} else {
const feeds = Array.isArray(config.autofollow.feeds) ? config.autofollow.feeds : (Array.isArray(config.autofollow.suggestions) ? config.autofollow.suggestions : []);
config.autofollow = feeds.filter(f => typeof f === 'string' && f.length > 0);
}
}
if (config.autofollow && (Array.isArray(config.autofollow) ? config.autofollow.length > 0 : true)) {
Server.use(require('ssb-autofollow'));
}
const manifestFile = path.join(config.path, 'manifest.json');
let server;
const argv = process.argv.slice(2);
const isLockError = (err) => {
if (!err) return false;
if (err.name === 'OpenError') return true;
const msg = String(err.message || '');
return /Resource temporarily unavailable/i.test(msg) && /\.ssb\/.*LOCK/i.test(msg);
};
const handleFatal = (err) => {
if (isLockError(err)) {
console.log('');
console.log('Another Oasis instance is already running on this device. Close the other instance (or kill the process) and try again.');
console.log('');
process.exit(1);
}
throw err;
};
process.on('uncaughtException', handleFatal);
if (argv[0] === 'start') {
try {
server = Server(config);
} catch (err) {
handleFatal(err);
}
fs.writeFileSync(manifestFile, JSON.stringify(server.getManifest(), null, 2));
const { cmdAliases } = require('../client/cli-cmd-aliases');
const manifest = server.getManifest();
for (const k in cmdAliases) {
server[k] = server[cmdAliases[k]];
manifest[k] = manifest[cmdAliases[k]];
}
manifest.config = 'sync';
server.config = cb => {
console.log(JSON.stringify(config, null, 2));
cb();
};
if (process.stdout.isTTY && config.logging?.level !== 'info') {
const showProgress = () => {
let prog = -1;
const bar = r => '\r' + '*'.repeat(Math.floor(r * 50)) + '.'.repeat(50 - Math.floor(r * 50));
const percent = r => (Math.round(r * 10000) / 100).toFixed(2) + '%';
const rate = prog => prog.target === prog.current ? 1 : (prog.current - prog.start) / (prog.target - prog.start);
const interval = setInterval(() => {
const p = server.progress();
let r = 1;
const tasks = [];
for (const k in p) {
const pr = rate(p[k]);
if (pr < 1) tasks.push(`${k}:${percent(pr)}`);
r = Math.min(r, pr);
}
if (r !== prog) {
prog = r;
process.stdout.write(bar(r) + ` (${tasks.join(', ')})\x1b[K\r`);
}
}, 333);
interval.unref?.();
};
showProgress();
}
const { printMetadata, colors } = require('./ssb_metadata');
printMetadata('OASIS Server Only', colors.cyan, null);
setTimeout(() => {
try {
const pull = require('pull-stream');
const stream = server.conn && server.conn.hub && server.conn.hub().listen && server.conn.hub().listen();
if (!stream) return;
pull(stream, pull.drain((ev) => {
if (!ev || !ev.type) return;
const ts = new Date().toISOString().replace('T', ' ').slice(0, 19);
if (ev.type === 'connected') {
console.log(`[${ts}] CONNECTED ${ev.address || ''}`);
} else if (ev.type === 'disconnected') {
console.log(`[${ts}] DISCONNECTED ${ev.address || ''}`);
}
}, () => {}));
} catch (_) {}
}, 1000);
}
module.exports = {
config,
server: server || Server(config),
open: async () => server || Server(config)
};

15
src/server/nodemon.json Normal file
View file

@ -0,0 +1,15 @@
{
"watch": [
"../backend",
"../models",
"../views",
"../client"
],
"exec": "node ../backend/backend.js",
"ext": "js,json",
"ignore": [
"../client/assets/"
],
"signal": "SIGTERM",
"delay": 500
}

17813
src/server/package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

159
src/server/package.json Normal file
View file

@ -0,0 +1,159 @@
{
"name": "@krakenslab/oasis",
"version": "0.8.7",
"description": "Oasis - Social Networking Utopia",
"repository": {
"type": "git",
"url": "git+ssh://git@code.03c8.net/krakenlabs/oasis.git"
},
"license": "AGPL-3.0",
"author": "psy <epsylon@riseup.net>",
"main": "src/index.js",
"bin": {
"oasis": "npm run start"
},
"scripts": {
"start": "npm run start:ssb && sleep 10 && npm run start:backend",
"start:backend": "node ../backend/backend.js",
"start:ssb": "node SSB_server.js start &",
"dev": "npm run dev:backend",
"dev:backend": "nodemon",
"postinstall": "node ../../scripts/patch-node-modules.js"
},
"dependencies": {
"@koa/router": "^13.1.0",
"@open-rpc/client-js": "^1.8.1",
"@xenova/transformers": "^2.17.2",
"abstract-level": "^2.0.1",
"archiver": "^7.0.1",
"axios": "^1.10.0",
"cors": "^2.8.5",
"debug": "^4.3.1",
"dompurify": "^3.3.1",
"env-paths": "^2.2.1",
"epidemic-broadcast-trees": "^9.0.4",
"express": "^5.1.0",
"file-type": "^16.5.4",
"highlight.js": "11.0.0",
"hyperaxe": "^2.0.1",
"ip": "https://registry.npmjs.org/neoip/-/neoip-3.0.0.tgz",
"is-svg": "^4.4.0",
"jsdom": "^28.0.0",
"koa": "^2.7.0",
"koa-body": "^6.0.1",
"koa-mount": "^4.0.0",
"koa-static": "^5.0.0",
"lodash": "^4.17.21",
"minimist": "^1.2.8",
"module-alias": "^2.2.3",
"moment": "^2.30.1",
"multiblob": "^1.13.0",
"multiserver": "^3.3.1",
"multiserver-address": "^1.0.1",
"muxrpc": "^8.0.0",
"muxrpc-validation": "^3.0.2",
"muxrpcli": "^3.1.2",
"node-llama-cpp": "^3.10.0",
"non-private-ip": "^2.2.0",
"open": "^8.4.2",
"openpgp": "^6.3.0",
"packet-stream": "^2.0.6",
"packet-stream-codec": "^1.2.0",
"pdfjs-dist": "^5.2.133",
"pretty-ms": "^7.0.1",
"pull-abortable": "^4.1.1",
"pull-cat": "~1.1.5",
"pull-file": "^1.0.0",
"pull-many": "~1.0.6",
"pull-paramap": "^1.2.2",
"pull-pushable": "^2.2.0",
"pull-sort": "^1.0.2",
"pull-stream": "^3.7.0",
"punycode.js": "^2.3.1",
"qrcode": "^1.5.4",
"secret-stack": "^6.3.1",
"ssb-about": "^2.0.1",
"ssb-autofollow": "^1.1.0",
"ssb-backlinks": "^2.1.1",
"ssb-blobs": "^2.0.1",
"ssb-box": "^1.0.1",
"ssb-caps": "^1.0.1",
"ssb-client": "^4.9.0",
"ssb-config": "^3.4.4",
"ssb-conn": "6.0.3",
"ssb-conn-db": "^1.0.5",
"ssb-conn-hub": "^1.2.0",
"ssb-conn-staging": "^1.0.0",
"ssb-db": "^20.4.1",
"ssb-device-address": "^1.1.6",
"ssb-ebt": "^9.1.2",
"ssb-friend-pub": "^1.0.7",
"ssb-friends": "^5.0.0",
"ssb-gossip": "^1.1.1",
"ssb-invite": "^3.0.3",
"ssb-invite-client": "^1.3.3",
"ssb-keys": "^8.0.0",
"ssb-lan": "^1.0.0",
"ssb-legacy-conn": "^1.0.17",
"ssb-links": "^3.0.10",
"ssb-logging": "^1.0.0",
"ssb-markdown": "^3.6.0",
"ssb-master": "^1.0.3",
"ssb-meme": "^1.1.0",
"ssb-mentions": "^0.5.2",
"ssb-msgs": "^5.2.0",
"ssb-no-auth": "^1.0.0",
"ssb-onion": "^1.0.0",
"ssb-partial-replication": "^3.0.1",
"ssb-plugins": "^1.0.2",
"ssb-private": "^1.1.0",
"ssb-query": "^2.4.5",
"ssb-ref": "^2.16.0",
"ssb-replication-scheduler": "^3.0.0",
"ssb-search": "^1.3.0",
"ssb-server": "file:packages/ssb-server",
"ssb-tangle": "^1.0.1",
"ssb-thread-schema": "^1.1.1",
"ssb-threads": "^10.0.4",
"ssb-unix-socket": "^1.0.0",
"ssb-ws": "^6.2.3",
"util": "^0.12.5",
"yargs": "^17.7.2"
},
"overrides": {
"caller-path": "^4.0.0",
"highlight.js": "11.0.0",
"@babel/traverse": "7.23.2",
"trim": "0.0.3",
"json5": "2.2.2",
"debug": "^4.3.1",
"postcss": "8.4.31",
"punycode": "2.3.1",
"ejs": "3.1.10",
"babel-traverse": "7.0.0-alpha.1",
"ssb-conn": "6.0.3",
"ssb-ref": "^2.16.0",
"secret-stack": "^6.3.1",
"ip": "https://registry.npmjs.org/neoip/-/neoip-3.0.0.tgz",
"lodash.set": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz"
},
"optionalDependencies": {
"fsevents": "^2.3.2",
"sharp": "^0.33.5"
},
"devDependencies": {
"nodemon": "^3.1.0"
},
"bugs": {
"url": "https://github.com/epsylon/oasis/issues"
},
"homepage": "https://github.com/epsylon/oasis",
"directories": {
"doc": "docs"
},
"keywords": [],
"engines": {
"node": "^10.0.0 || >=12.0.0"
},
"packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
}

26
src/server/ssb_config.js Normal file
View file

@ -0,0 +1,26 @@
const fs = require('fs');
const path = require('path');
const Config = require('ssb-config/inject');
const minimist = require('minimist');
const configPath = path.resolve(__dirname, '../configs', 'server-config.json');
const configData = JSON.parse(fs.readFileSync(configPath, 'utf8'));
const argv = process.argv.slice(2);
const i = argv.indexOf('--');
const conf = argv.slice(i + 1);
const cliArgs = ~i ? argv.slice(0, i) : argv;
let config = Config('ssb', minimist(conf));
config = { ...config, ...configData };
const debug = process.argv.includes('--debug') || process.env.OASIS_DEBUG === '1' || process.env.OASIS_DEBUG === 'true';
if (debug) {
config.logging = { ...(config.logging || {}), level: 'debug' };
}
const megabyte = Math.pow(2, 20);
config.blobs = config.blobs || {};
config.blobs.max = 50 * megabyte;
module.exports = config;

141
src/server/ssb_metadata.js Normal file
View file

@ -0,0 +1,141 @@
const fs = require('fs');
const os = require('os');
const path = require('path');
const pkg = require('./package.json');
const config = require('./ssb_config');
const updater = require('../backend/updater.js');
let printed = false;
let checkedForUpdate = false;
let pendingClearnetModules = null;
let clearnetReady = false;
let pendingWarmup = null;
let warmupReady = false;
function setClearnetModules(modules) {
pendingClearnetModules = Array.isArray(modules) ? modules : [];
clearnetReady = true;
}
function setWarmupTime(str) {
pendingWarmup = str;
warmupReady = true;
}
function waitForWarmup(timeoutMs = 120000) {
return new Promise((resolve) => {
if (warmupReady) return resolve(pendingWarmup);
const started = Date.now();
const tick = () => {
if (warmupReady) return resolve(pendingWarmup);
if (Date.now() - started >= timeoutMs) return resolve(null);
setTimeout(tick, 250);
};
tick();
});
}
function getModules() {
const nodeModulesPath = path.resolve(__dirname, 'node_modules');
try {
return fs.readdirSync(nodeModulesPath)
.filter(m => fs.existsSync(path.join(nodeModulesPath, m, 'package.json')));
} catch {
return [];
}
}
const colors = {
blue: '\x1b[38;5;33m',
yellow: '\x1b[38;5;226m',
orange: '\x1b[38;5;214m',
cyan: '\x1b[36m',
reset: '\x1b[0m'
};
async function checkForUpdate() {
if (checkedForUpdate) return;
checkedForUpdate = true;
const updateFlagPath = path.join(__dirname, '../server/.update_required');
if (fs.existsSync(updateFlagPath)) {
fs.unlinkSync(updateFlagPath);
}
await updater.getRemoteVersion();
}
function waitForClearnet(timeoutMs = 25000) {
return new Promise((resolve) => {
if (clearnetReady) return resolve(pendingClearnetModules || []);
const started = Date.now();
const tick = () => {
if (clearnetReady) return resolve(pendingClearnetModules || []);
if (Date.now() - started >= timeoutMs) return resolve(null);
setTimeout(tick, 250);
};
tick();
});
}
async function printMetadata(mode, modeColor = colors.cyan, httpPort = 3000, httpHost = 'localhost', offline = false, isPublic = false) {
if (printed) return;
printed = true;
const modules = getModules();
const version = pkg.version;
const name = pkg.name;
const logLevel = config.logging?.level || 'info';
const publicKey = config.keys?.public || '';
const hasHttp = httpPort !== null && httpPort !== false;
const httpUrl = hasHttp ? `http://${httpHost}:${httpPort}` : '';
const oscLink = hasHttp ? `\x1b]8;;${httpUrl}\x07${httpUrl}\x1b]8;;\x07` : '';
const ssbPort = config.connections?.incoming?.net?.[0]?.port || config.port || 8008;
const localDiscovery = config.local === true;
const hops = config.conn?.hops ?? config.friends?.hops ?? 2;
console.log("=========================");
console.log(`Running mode: ${modeColor}${mode}${colors.reset}`);
console.log("=========================");
console.log(`- Package: ${colors.blue}${name} ${colors.yellow}[Version: ${version}]${colors.reset}`);
if (hasHttp) console.log(`- URL: ${colors.cyan}${oscLink}${colors.reset}`);
console.log(`- Oasis ID: [ ${colors.orange}@${publicKey}${colors.reset} ]`);
console.log("- Logging Level:", logLevel);
const ifaces = os.networkInterfaces();
const isOnline = Object.values(ifaces).some(list =>
list && list.some(i => !i.internal && i.family === 'IPv4')
);
console.log(`- Protocol (port): ${ssbPort}`);
console.log(`- Mode: ${isOnline ? 'online' : 'offline'}`);
console.log(`- Replication (hops): ${hops}`);
console.log(`- LAN Broadcasting (UDP): ${localDiscovery ? 'enabled' : 'disabled'}`);
const clearnetModules = await waitForClearnet();
const clearnetStatus = (clearnetModules && clearnetModules.length > 0) ? clearnetModules.join(', ') : 'disabled';
let fediverseConnected = false;
try {
const acc = JSON.parse(fs.readFileSync(path.join(__dirname, '../configs/fediverse-accounts.json'), 'utf8'));
fediverseConnected = !!(acc && acc.mastodon);
} catch (_) {}
console.log(`- Internet Broadcasting:`);
console.log(` - Clearnet: ${clearnetStatus}`);
console.log(` - Fediverse: ${fediverseConnected ? 'enabled' : 'disabled'}`);
console.log("");
console.log("=========================");
console.log("Modules loaded: [", modules.length, "]");
console.log("=========================");
// Check for updates
await checkForUpdate();
console.log("=========================");
if (/gui/i.test(mode)) {
const warmup = await waitForWarmup();
if (warmup) console.log(`- Warmup-time: ${warmup}`);
}
}
module.exports = {
printMetadata,
setClearnetModules,
setWarmupTime,
colors
};