P1
This commit is contained in:
@@ -8,11 +8,14 @@ const { SquidManager } = require('./lib/squid-manager')
|
||||
const { createWorld, listWorlds, resolveWorldPaths } = require('./lib/worlds')
|
||||
const { worldsRoot } = require('./lib/paths')
|
||||
const { ensureDir } = require('./lib/worlds')
|
||||
const { WorldTunnelHost, WorldTunnelClient } = require('./lib/world-tunnel')
|
||||
const { loadOrCreateTunnelKeys } = require('./lib/tunnel-keys')
|
||||
const { encodeInvite, decodeInvite, decodeKey } = require('./lib/invite')
|
||||
|
||||
/**
|
||||
* Flying Jib Bare application shell.
|
||||
* - Optional pear-runtime worker for OTA (hello-pear-bare pattern)
|
||||
* - Squid runs in this process under Bare (ADR-0013)
|
||||
* - Squid + HyperDHT tunnels run in this process under Bare (ADR-0013)
|
||||
*/
|
||||
module.exports = class App extends ReadyResource {
|
||||
constructor({ dir, appPath, updates, version, upgrade, name }) {
|
||||
@@ -26,14 +29,16 @@ module.exports = class App extends ReadyResource {
|
||||
this.IPC = null
|
||||
this.pipe = null
|
||||
this.squid = null
|
||||
this.tunnelHost = null
|
||||
this.tunnelClient = null
|
||||
this.activeWorld = null
|
||||
this._shuttingDown = false
|
||||
}
|
||||
|
||||
_open() {
|
||||
ensureDir(worldsRoot(this.dir))
|
||||
ensureDir(path.join(this.dir, 'corestore'))
|
||||
|
||||
// Pear OTA worker (optional — disabled when --no-updates and no upgrade key)
|
||||
const enableWorker =
|
||||
this.updates !== false && this.upgrade && !String(this.upgrade).includes('<YOUR_KEY')
|
||||
|
||||
@@ -58,10 +63,19 @@ module.exports = class App extends ReadyResource {
|
||||
}
|
||||
|
||||
async _close() {
|
||||
if (this.tunnelClient) {
|
||||
await this.tunnelClient.close().catch(() => {})
|
||||
this.tunnelClient = null
|
||||
}
|
||||
if (this.tunnelHost) {
|
||||
await this.tunnelHost.close().catch(() => {})
|
||||
this.tunnelHost = null
|
||||
}
|
||||
if (this.squid) {
|
||||
await this.squid.close().catch(() => {})
|
||||
this.squid = null
|
||||
}
|
||||
this.activeWorld = null
|
||||
const pipe = this.pipe
|
||||
const IPC = this.IPC
|
||||
this.pipe = null
|
||||
@@ -117,7 +131,7 @@ module.exports = class App extends ReadyResource {
|
||||
port,
|
||||
version,
|
||||
motd: meta.motd || `Flying Jib — ${meta.name}`,
|
||||
logging: true
|
||||
logging: false
|
||||
})
|
||||
|
||||
this.squid.on('error', (err) => this.emit('error', err))
|
||||
@@ -130,24 +144,107 @@ module.exports = class App extends ReadyResource {
|
||||
return this.squid.status
|
||||
}
|
||||
|
||||
/**
|
||||
* Start Squid + HyperDHT host tunnel; return status + invite string.
|
||||
* @param {{ world: string, port?: number, version?: string }} opts
|
||||
*/
|
||||
async hostWorld(opts) {
|
||||
const squidStatus = await this.startWorld(opts)
|
||||
const keys = loadOrCreateTunnelKeys(this.dir, this.activeWorld.id)
|
||||
|
||||
this.tunnelHost = new WorldTunnelHost({
|
||||
localPort: squidStatus.port,
|
||||
seed: keys.seed,
|
||||
cap: keys.cap
|
||||
})
|
||||
this.tunnelHost.on('connection', () => {
|
||||
this.emit('message', 'Remote peer connected via tunnel')
|
||||
})
|
||||
this.tunnelHost.on('error', (err) => this.emit('error', err))
|
||||
await this.tunnelHost.ready()
|
||||
|
||||
const invite = encodeInvite({
|
||||
type: 'private-world',
|
||||
worldKey: keys.publicKeyZ32,
|
||||
cap: keys.capZ32,
|
||||
name: this.activeWorld.name,
|
||||
mcVersion: this.activeWorld.version,
|
||||
portHint: squidStatus.port
|
||||
})
|
||||
|
||||
return {
|
||||
squid: squidStatus,
|
||||
tunnel: this.tunnelHost.status,
|
||||
invite
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Join a private world via fj1. invite (client tunnel only; no local Squid).
|
||||
* @param {{ invite: string, localPort?: number }} opts
|
||||
*/
|
||||
async joinWorld(opts) {
|
||||
if (this.tunnelClient) {
|
||||
throw new Error('Already joined a remote world; stop first')
|
||||
}
|
||||
const inv = decodeInvite(opts.invite)
|
||||
const publicKey = decodeKey(inv.worldKey)
|
||||
const cap = decodeKey(inv.cap)
|
||||
|
||||
this.tunnelClient = new WorldTunnelClient({
|
||||
publicKey,
|
||||
cap,
|
||||
localPort: opts.localPort != null ? Number(opts.localPort) : 0
|
||||
})
|
||||
this.tunnelClient.on('error', (err) => this.emit('error', err))
|
||||
await this.tunnelClient.ready()
|
||||
|
||||
return {
|
||||
invite: inv,
|
||||
tunnel: this.tunnelClient.status
|
||||
}
|
||||
}
|
||||
|
||||
async stopWorld() {
|
||||
if (!this.squid) return false
|
||||
await this.squid.close()
|
||||
this.squid = null
|
||||
let stopped = false
|
||||
if (this.tunnelClient) {
|
||||
await this.tunnelClient.close().catch(() => {})
|
||||
this.tunnelClient = null
|
||||
stopped = true
|
||||
}
|
||||
if (this.tunnelHost) {
|
||||
await this.tunnelHost.close().catch(() => {})
|
||||
this.tunnelHost = null
|
||||
stopped = true
|
||||
}
|
||||
if (this.squid) {
|
||||
await this.squid.close().catch(() => {})
|
||||
this.squid = null
|
||||
stopped = true
|
||||
}
|
||||
this.activeWorld = null
|
||||
return true
|
||||
return stopped
|
||||
}
|
||||
|
||||
getStatus() {
|
||||
return {
|
||||
storage: this.dir,
|
||||
world: this.activeWorld,
|
||||
squid: this.squid ? this.squid.status : { running: false }
|
||||
squid: this.squid ? this.squid.status : { running: false },
|
||||
tunnelHost: this.tunnelHost ? this.tunnelHost.status : null,
|
||||
tunnelClient: this.tunnelClient ? this.tunnelClient.status : null
|
||||
}
|
||||
}
|
||||
|
||||
async exit(code = 0) {
|
||||
if (this._shuttingDown) return
|
||||
this._shuttingDown = true
|
||||
if (typeof Bare !== 'undefined') Bare.exitCode = code
|
||||
try {
|
||||
await this.stopWorld()
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
await this.close()
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user