p2-3
This commit is contained in:
@@ -5,6 +5,9 @@ node_modules/
|
|||||||
out/
|
out/
|
||||||
dist/
|
dist/
|
||||||
deploy/
|
deploy/
|
||||||
|
# vendored at postinstall
|
||||||
|
build/shims/events-polyfill.cjs
|
||||||
|
build/squid-imports.json
|
||||||
*.AppImage
|
*.AppImage
|
||||||
*.dmg
|
*.dmg
|
||||||
*.snap
|
*.snap
|
||||||
|
|||||||
@@ -59,7 +59,10 @@ $BARE bin.mjs host demo --port 25565 # print fj1. invite; local Squid
|
|||||||
$BARE bin.mjs join 'fj1.…' --port 25566 # guest tunnel
|
$BARE bin.mjs join 'fj1.…' --port 25566 # guest tunnel
|
||||||
|
|
||||||
# Minecraft Java → 127.0.0.1:<port shown>
|
# Minecraft Java → 127.0.0.1:<port shown>
|
||||||
|
# Chat: type lines while host/join is running; /peers
|
||||||
|
|
||||||
npm test
|
npm test
|
||||||
|
npm run make:standalone # single Bare executable → out/flying-jib-<host>/
|
||||||
```
|
```
|
||||||
|
|
||||||
See [developer_docs/SETUP.md](./developer_docs/SETUP.md) and [living_docs/CURRENT_STATUS.md](./living_docs/CURRENT_STATUS.md).
|
See [developer_docs/SETUP.md](./developer_docs/SETUP.md) and [living_docs/CURRENT_STATUS.md](./living_docs/CURRENT_STATUS.md).
|
||||||
|
|||||||
@@ -11,14 +11,15 @@ const { ensureDir } = require('./lib/worlds')
|
|||||||
const { WorldTunnelHost, WorldTunnelClient } = require('./lib/world-tunnel')
|
const { WorldTunnelHost, WorldTunnelClient } = require('./lib/world-tunnel')
|
||||||
const { loadOrCreateTunnelKeys } = require('./lib/tunnel-keys')
|
const { loadOrCreateTunnelKeys } = require('./lib/tunnel-keys')
|
||||||
const { encodeInvite, decodeInvite, decodeKey } = require('./lib/invite')
|
const { encodeInvite, decodeInvite, decodeKey } = require('./lib/invite')
|
||||||
|
const { PeerSession } = require('./lib/peer-session')
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Flying Jib Bare application shell.
|
* Flying Jib Bare application shell.
|
||||||
* - Optional pear-runtime worker for OTA (hello-pear-bare pattern)
|
* - Optional pear-runtime worker for OTA (hello-pear-bare pattern)
|
||||||
* - Squid + HyperDHT tunnels run in this process under Bare (ADR-0013)
|
* - Squid + HyperDHT tunnels + Protomux peer session in this process (ADR-0013)
|
||||||
*/
|
*/
|
||||||
module.exports = class App extends ReadyResource {
|
module.exports = class App extends ReadyResource {
|
||||||
constructor({ dir, appPath, updates, version, upgrade, name }) {
|
constructor({ dir, appPath, updates, version, upgrade, name, displayName }) {
|
||||||
super()
|
super()
|
||||||
this.dir = dir
|
this.dir = dir
|
||||||
this.appPath = appPath
|
this.appPath = appPath
|
||||||
@@ -26,11 +27,13 @@ module.exports = class App extends ReadyResource {
|
|||||||
this.version = version
|
this.version = version
|
||||||
this.upgrade = upgrade
|
this.upgrade = upgrade
|
||||||
this.name = name
|
this.name = name
|
||||||
|
this.displayName = displayName || 'player'
|
||||||
this.IPC = null
|
this.IPC = null
|
||||||
this.pipe = null
|
this.pipe = null
|
||||||
this.squid = null
|
this.squid = null
|
||||||
this.tunnelHost = null
|
this.tunnelHost = null
|
||||||
this.tunnelClient = null
|
this.tunnelClient = null
|
||||||
|
this.peerSession = null
|
||||||
this.activeWorld = null
|
this.activeWorld = null
|
||||||
this._shuttingDown = false
|
this._shuttingDown = false
|
||||||
}
|
}
|
||||||
@@ -63,6 +66,20 @@ module.exports = class App extends ReadyResource {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async _close() {
|
async _close() {
|
||||||
|
await this._teardownSession()
|
||||||
|
const pipe = this.pipe
|
||||||
|
const IPC = this.IPC
|
||||||
|
this.pipe = null
|
||||||
|
this.IPC = null
|
||||||
|
pipe?.destroy()
|
||||||
|
IPC?.destroy()
|
||||||
|
}
|
||||||
|
|
||||||
|
async _teardownSession() {
|
||||||
|
if (this.peerSession) {
|
||||||
|
await this.peerSession.close().catch(() => {})
|
||||||
|
this.peerSession = null
|
||||||
|
}
|
||||||
if (this.tunnelClient) {
|
if (this.tunnelClient) {
|
||||||
await this.tunnelClient.close().catch(() => {})
|
await this.tunnelClient.close().catch(() => {})
|
||||||
this.tunnelClient = null
|
this.tunnelClient = null
|
||||||
@@ -76,12 +93,6 @@ module.exports = class App extends ReadyResource {
|
|||||||
this.squid = null
|
this.squid = null
|
||||||
}
|
}
|
||||||
this.activeWorld = null
|
this.activeWorld = null
|
||||||
const pipe = this.pipe
|
|
||||||
const IPC = this.IPC
|
|
||||||
this.pipe = null
|
|
||||||
this.IPC = null
|
|
||||||
pipe?.destroy()
|
|
||||||
IPC?.destroy()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_onWorkerMessage(data) {
|
_onWorkerMessage(data) {
|
||||||
@@ -106,6 +117,25 @@ module.exports = class App extends ReadyResource {
|
|||||||
if (this.pipe) this.pipe.write(message)
|
if (this.pipe) this.pipe.write(message)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async _startPeerSession(worldPublicKey) {
|
||||||
|
if (this.peerSession) {
|
||||||
|
await this.peerSession.close().catch(() => {})
|
||||||
|
this.peerSession = null
|
||||||
|
}
|
||||||
|
this.peerSession = new PeerSession({
|
||||||
|
worldPublicKey,
|
||||||
|
displayName: this.displayName
|
||||||
|
})
|
||||||
|
this.peerSession.on('chat', (m) => this.emit('chat', m))
|
||||||
|
this.peerSession.on('presence', (m) => this.emit('presence', m))
|
||||||
|
this.peerSession.on('peers', (list) => this.emit('peers', list))
|
||||||
|
this.peerSession.on('peer-join', (m) => this.emit('peer-join', m))
|
||||||
|
this.peerSession.on('peer-leave', (m) => this.emit('peer-leave', m))
|
||||||
|
this.peerSession.on('error', (err) => this.emit('error', err))
|
||||||
|
await this.peerSession.ready()
|
||||||
|
this.emit('message', `Peer session ready (id ${this.peerSession.peerId})`)
|
||||||
|
}
|
||||||
|
|
||||||
listWorlds() {
|
listWorlds() {
|
||||||
return listWorlds(this.dir)
|
return listWorlds(this.dir)
|
||||||
}
|
}
|
||||||
@@ -145,10 +175,11 @@ module.exports = class App extends ReadyResource {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Start Squid + HyperDHT host tunnel; return status + invite string.
|
* Start Squid + HyperDHT host tunnel + peer chat/presence.
|
||||||
* @param {{ world: string, port?: number, version?: string }} opts
|
* @param {{ world: string, port?: number, version?: string, displayName?: string }} opts
|
||||||
*/
|
*/
|
||||||
async hostWorld(opts) {
|
async hostWorld(opts) {
|
||||||
|
if (opts.displayName) this.displayName = opts.displayName
|
||||||
const squidStatus = await this.startWorld(opts)
|
const squidStatus = await this.startWorld(opts)
|
||||||
const keys = loadOrCreateTunnelKeys(this.dir, this.activeWorld.id)
|
const keys = loadOrCreateTunnelKeys(this.dir, this.activeWorld.id)
|
||||||
|
|
||||||
@@ -158,11 +189,13 @@ module.exports = class App extends ReadyResource {
|
|||||||
cap: keys.cap
|
cap: keys.cap
|
||||||
})
|
})
|
||||||
this.tunnelHost.on('connection', () => {
|
this.tunnelHost.on('connection', () => {
|
||||||
this.emit('message', 'Remote peer connected via tunnel')
|
this.emit('message', 'Remote peer connected via MC tunnel')
|
||||||
})
|
})
|
||||||
this.tunnelHost.on('error', (err) => this.emit('error', err))
|
this.tunnelHost.on('error', (err) => this.emit('error', err))
|
||||||
await this.tunnelHost.ready()
|
await this.tunnelHost.ready()
|
||||||
|
|
||||||
|
await this._startPeerSession(keys.publicKey)
|
||||||
|
|
||||||
const invite = encodeInvite({
|
const invite = encodeInvite({
|
||||||
type: 'private-world',
|
type: 'private-world',
|
||||||
worldKey: keys.publicKeyZ32,
|
worldKey: keys.publicKeyZ32,
|
||||||
@@ -175,18 +208,20 @@ module.exports = class App extends ReadyResource {
|
|||||||
return {
|
return {
|
||||||
squid: squidStatus,
|
squid: squidStatus,
|
||||||
tunnel: this.tunnelHost.status,
|
tunnel: this.tunnelHost.status,
|
||||||
|
peers: this.peerSession ? this.peerSession.peerList : [],
|
||||||
invite
|
invite
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Join a private world via fj1. invite (client tunnel only; no local Squid).
|
* Join a private world via fj1. invite (tunnel + peer session).
|
||||||
* @param {{ invite: string, localPort?: number }} opts
|
* @param {{ invite: string, localPort?: number, displayName?: string }} opts
|
||||||
*/
|
*/
|
||||||
async joinWorld(opts) {
|
async joinWorld(opts) {
|
||||||
if (this.tunnelClient) {
|
if (this.tunnelClient) {
|
||||||
throw new Error('Already joined a remote world; stop first')
|
throw new Error('Already joined a remote world; stop first')
|
||||||
}
|
}
|
||||||
|
if (opts.displayName) this.displayName = opts.displayName
|
||||||
const inv = decodeInvite(opts.invite)
|
const inv = decodeInvite(opts.invite)
|
||||||
const publicKey = decodeKey(inv.worldKey)
|
const publicKey = decodeKey(inv.worldKey)
|
||||||
const cap = decodeKey(inv.cap)
|
const cap = decodeKey(inv.cap)
|
||||||
@@ -199,40 +234,35 @@ module.exports = class App extends ReadyResource {
|
|||||||
this.tunnelClient.on('error', (err) => this.emit('error', err))
|
this.tunnelClient.on('error', (err) => this.emit('error', err))
|
||||||
await this.tunnelClient.ready()
|
await this.tunnelClient.ready()
|
||||||
|
|
||||||
|
await this._startPeerSession(publicKey)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
invite: inv,
|
invite: inv,
|
||||||
tunnel: this.tunnelClient.status
|
tunnel: this.tunnelClient.status,
|
||||||
|
peers: this.peerSession ? this.peerSession.peerList : []
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sendChat(text) {
|
||||||
|
if (!this.peerSession) throw new Error('No peer session (host or join first)')
|
||||||
|
this.peerSession.sendChat(text)
|
||||||
|
}
|
||||||
|
|
||||||
async stopWorld() {
|
async stopWorld() {
|
||||||
let stopped = false
|
await this._teardownSession()
|
||||||
if (this.tunnelClient) {
|
return true
|
||||||
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 stopped
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getStatus() {
|
getStatus() {
|
||||||
return {
|
return {
|
||||||
storage: this.dir,
|
storage: this.dir,
|
||||||
world: this.activeWorld,
|
world: this.activeWorld,
|
||||||
|
displayName: this.displayName,
|
||||||
squid: this.squid ? this.squid.status : { running: false },
|
squid: this.squid ? this.squid.status : { running: false },
|
||||||
tunnelHost: this.tunnelHost ? this.tunnelHost.status : null,
|
tunnelHost: this.tunnelHost ? this.tunnelHost.status : null,
|
||||||
tunnelClient: this.tunnelClient ? this.tunnelClient.status : null
|
tunnelClient: this.tunnelClient ? this.tunnelClient.status : null,
|
||||||
|
peers: this.peerSession ? this.peerSession.peerList : [],
|
||||||
|
peerId: this.peerSession ? this.peerSession.peerId : null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,13 +14,17 @@ import { createRequire } from 'module'
|
|||||||
import { command, flag, arg, summary, header, footer } from 'paparam'
|
import { command, flag, arg, summary, header, footer } from 'paparam'
|
||||||
import path from 'path'
|
import path from 'path'
|
||||||
import process from 'process'
|
import process from 'process'
|
||||||
import { fileURLToPath } from 'url'
|
|
||||||
import pkg from './package.json' with { type: 'json' }
|
import pkg from './package.json' with { type: 'json' }
|
||||||
import App from './app.js'
|
import App from './app.js'
|
||||||
|
|
||||||
const require = createRequire(import.meta.url)
|
// createRequire needs a file: URL in Node; under bare-pack standalone, import.meta.url
|
||||||
const __filename = fileURLToPath(import.meta.url)
|
// may be app.bundle — fall back to a dummy path for require() of CJS shims.
|
||||||
const __dirname = path.dirname(__filename)
|
let require
|
||||||
|
try {
|
||||||
|
require = createRequire(import.meta.url)
|
||||||
|
} catch {
|
||||||
|
require = createRequire('file:///flying-jib/bin.mjs')
|
||||||
|
}
|
||||||
|
|
||||||
const appName = pkg.productName || pkg.name
|
const appName = pkg.productName || pkg.name
|
||||||
const isBare = typeof Bare !== 'undefined'
|
const isBare = typeof Bare !== 'undefined'
|
||||||
@@ -48,17 +52,72 @@ function exit(code) {
|
|||||||
else process.exit(code)
|
else process.exit(code)
|
||||||
}
|
}
|
||||||
|
|
||||||
function makeApp(storage, updates = false) {
|
function makeApp(storage, updates = false, displayName) {
|
||||||
return new App({
|
return new App({
|
||||||
dir: storage,
|
dir: storage,
|
||||||
appPath: isDev ? null : argv0,
|
appPath: isDev ? null : argv0,
|
||||||
updates,
|
updates,
|
||||||
version: pkg.version,
|
version: pkg.version,
|
||||||
upgrade: pkg.upgrade,
|
upgrade: pkg.upgrade,
|
||||||
name: appName
|
name: appName,
|
||||||
|
displayName: displayName || 'player'
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Wire chat/presence logging + optional stdin chat lines for host/join */
|
||||||
|
function attachPeerUi(app) {
|
||||||
|
app.on('chat', (m) => {
|
||||||
|
const tag = m.local ? 'you' : m.from
|
||||||
|
console.log(`[chat] <${tag}> ${m.text}`)
|
||||||
|
})
|
||||||
|
app.on('peer-join', (m) => {
|
||||||
|
console.log(`[peers] joined ${m.peerId.slice(0, 12)}…`)
|
||||||
|
})
|
||||||
|
app.on('peer-leave', (m) => {
|
||||||
|
console.log(`[peers] left ${m.peerId.slice(0, 12)}…`)
|
||||||
|
})
|
||||||
|
app.on('peers', (list) => {
|
||||||
|
if (list.length) {
|
||||||
|
console.log(`[peers] online: ${list.map((p) => p.name).join(', ')}`)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Type lines to chat (host/join sessions)
|
||||||
|
if (process.stdin && process.stdin.isTTY && typeof process.stdin.on === 'function') {
|
||||||
|
try {
|
||||||
|
process.stdin.setEncoding('utf8')
|
||||||
|
process.stdin.resume()
|
||||||
|
let buf = ''
|
||||||
|
process.stdin.on('data', (chunk) => {
|
||||||
|
buf += chunk
|
||||||
|
let i
|
||||||
|
while ((i = buf.indexOf('\n')) >= 0) {
|
||||||
|
const line = buf.slice(0, i).replace(/\r$/, '').trim()
|
||||||
|
buf = buf.slice(i + 1)
|
||||||
|
if (!line) continue
|
||||||
|
if (line === '/peers') {
|
||||||
|
const st = app.getStatus()
|
||||||
|
console.log(
|
||||||
|
'[peers]',
|
||||||
|
st.peers.length
|
||||||
|
? st.peers.map((p) => `${p.name}(${p.id.slice(0, 8)})`).join(', ')
|
||||||
|
: '(none)'
|
||||||
|
)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
app.sendChat(line)
|
||||||
|
} catch (err) {
|
||||||
|
console.error('[chat]', err.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} catch {
|
||||||
|
// non-interactive
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/** Attach signal handlers once; re-entrant safe via app.exit */
|
/** Attach signal handlers once; re-entrant safe via app.exit */
|
||||||
function attachShutdown(app) {
|
function attachShutdown(app) {
|
||||||
let stopping = false
|
let stopping = false
|
||||||
@@ -182,25 +241,29 @@ const hostCmd = command(
|
|||||||
arg('<name>', 'world name / id'),
|
arg('<name>', 'world name / id'),
|
||||||
flag('--port|-p [port]', 'local Minecraft port (default 25565)'),
|
flag('--port|-p [port]', 'local Minecraft port (default 25565)'),
|
||||||
flag('--version [ver]', 'override Minecraft version'),
|
flag('--version [ver]', 'override Minecraft version'),
|
||||||
|
flag('--name|-n [display]', 'chat display name'),
|
||||||
async (cmd) => {
|
async (cmd) => {
|
||||||
const storage = resolveStorage(root.flags.storage)
|
const storage = resolveStorage(root.flags.storage)
|
||||||
const app = makeApp(storage, false)
|
const app = makeApp(storage, false, cmd.flags.name)
|
||||||
app.on('message', (m) => console.log(m))
|
app.on('message', (m) => console.log(m))
|
||||||
app.on('error', (err) => console.error('[error]', err))
|
app.on('error', (err) => console.error('[error]', err))
|
||||||
attachShutdown(app)
|
attachShutdown(app)
|
||||||
|
attachPeerUi(app)
|
||||||
|
|
||||||
await app.ready()
|
await app.ready()
|
||||||
try {
|
try {
|
||||||
const result = await app.hostWorld({
|
const result = await app.hostWorld({
|
||||||
world: cmd.args.name,
|
world: cmd.args.name,
|
||||||
port: cmd.flags.port ? Number(cmd.flags.port) : 25565,
|
port: cmd.flags.port ? Number(cmd.flags.port) : 25565,
|
||||||
version: cmd.flags.version
|
version: cmd.flags.version,
|
||||||
|
displayName: cmd.flags.name
|
||||||
})
|
})
|
||||||
console.log('')
|
console.log('')
|
||||||
console.log(`${appName} hosting private world (P2P)`)
|
console.log(`${appName} hosting private world (P2P)`)
|
||||||
console.log(` world: ${cmd.args.name}`)
|
console.log(` world: ${cmd.args.name}`)
|
||||||
console.log(` local: ${result.squid.host}:${result.squid.port}`)
|
console.log(` local: ${result.squid.host}:${result.squid.port}`)
|
||||||
console.log(` tunnel: HyperDHT host`)
|
console.log(` tunnel: HyperDHT host`)
|
||||||
|
console.log(` chat: type lines + Enter; /peers for list`)
|
||||||
console.log('')
|
console.log('')
|
||||||
console.log('Invite (treat as secret — fj1. capability):')
|
console.log('Invite (treat as secret — fj1. capability):')
|
||||||
console.log(result.invite)
|
console.log(result.invite)
|
||||||
@@ -226,18 +289,21 @@ const joinCmd = command(
|
|||||||
summary('Join a private world from an fj1. invite'),
|
summary('Join a private world from an fj1. invite'),
|
||||||
arg('<invite>', 'fj1. invite string'),
|
arg('<invite>', 'fj1. invite string'),
|
||||||
flag('--port|-p [port]', 'local bind port (default: ephemeral)'),
|
flag('--port|-p [port]', 'local bind port (default: ephemeral)'),
|
||||||
|
flag('--name|-n [display]', 'chat display name'),
|
||||||
async (cmd) => {
|
async (cmd) => {
|
||||||
const storage = resolveStorage(root.flags.storage)
|
const storage = resolveStorage(root.flags.storage)
|
||||||
const app = makeApp(storage, false)
|
const app = makeApp(storage, false, cmd.flags.name)
|
||||||
app.on('message', (m) => console.log(m))
|
app.on('message', (m) => console.log(m))
|
||||||
app.on('error', (err) => console.error('[error]', err))
|
app.on('error', (err) => console.error('[error]', err))
|
||||||
attachShutdown(app)
|
attachShutdown(app)
|
||||||
|
attachPeerUi(app)
|
||||||
|
|
||||||
await app.ready()
|
await app.ready()
|
||||||
try {
|
try {
|
||||||
const result = await app.joinWorld({
|
const result = await app.joinWorld({
|
||||||
invite: cmd.args.invite,
|
invite: cmd.args.invite,
|
||||||
localPort: cmd.flags.port ? Number(cmd.flags.port) : 0
|
localPort: cmd.flags.port ? Number(cmd.flags.port) : 0,
|
||||||
|
displayName: cmd.flags.name
|
||||||
})
|
})
|
||||||
const port = result.tunnel.localPort
|
const port = result.tunnel.localPort
|
||||||
console.log('')
|
console.log('')
|
||||||
@@ -245,6 +311,7 @@ const joinCmd = command(
|
|||||||
console.log(` name: ${result.invite.name || '(unnamed)'}`)
|
console.log(` name: ${result.invite.name || '(unnamed)'}`)
|
||||||
console.log(` version: ${result.invite.mcVersion || 'unknown'}`)
|
console.log(` version: ${result.invite.mcVersion || 'unknown'}`)
|
||||||
console.log(` local: 127.0.0.1:${port}`)
|
console.log(` local: 127.0.0.1:${port}`)
|
||||||
|
console.log(` chat: type lines + Enter; /peers for list`)
|
||||||
console.log('')
|
console.log('')
|
||||||
console.log('Connect Java Edition to:')
|
console.log('Connect Java Edition to:')
|
||||||
console.log(` 127.0.0.1:${port}`)
|
console.log(` 127.0.0.1:${port}`)
|
||||||
@@ -297,6 +364,3 @@ if (root.flags.version) {
|
|||||||
console.log(`${appName} v${pkg.version}`)
|
console.log(`${appName} v${pkg.version}`)
|
||||||
exit(0)
|
exit(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
void __dirname
|
|
||||||
void __filename
|
|
||||||
|
|||||||
@@ -1,12 +1,7 @@
|
|||||||
'use strict'
|
'use strict'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Flying Squid / readable-stream use `EventEmitter.call(this)` (util.inherits).
|
* Callable EventEmitter for Flying Squid / readable-stream (util.inherits).
|
||||||
* bare-events is an ES class (throws without `new`).
|
* Vendored polyfill is inlined so bare-pack standalone does not need nested requires.
|
||||||
*
|
|
||||||
* Load the classic npm `events` package by absolute path so we do not recurse
|
|
||||||
* through package.json / bare-node-runtime "events" import maps.
|
|
||||||
*/
|
*/
|
||||||
const path = require('path')
|
module.exports = require('./events-polyfill.cjs')
|
||||||
const eventsJs = path.join(__dirname, '..', '..', 'node_modules', 'events', 'events.js')
|
|
||||||
module.exports = require(eventsJs)
|
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
'use strict'; module.exports = {}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
'use strict'; module.exports = {}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
'use strict'; module.exports = {}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
'use strict'; module.exports = {}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
'use strict'; module.exports = {}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
'use strict'; module.exports = {}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
'use strict'
|
||||||
|
module.exports = {}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
'use strict'
|
||||||
|
module.exports = false
|
||||||
@@ -54,9 +54,20 @@ Optional packaged forms (`.AppImage`, `.pkg`, `.msix`) via bare-build package mo
|
|||||||
|
|
||||||
## Flying Squid in the bundle
|
## Flying Squid in the bundle
|
||||||
|
|
||||||
- Include Prismarine graph with **`bare-node-runtime`** + project **`build/squid-imports.json`** (generated postinstall)
|
Use the peardock-style packer (not plain `bare-build` CLI):
|
||||||
- **Note:** plain `bare-build` currently fails resolving flying-squid’s browser-only `esbuild-import-glob(...)` specifier. Prefer peardock-style **`bare-pack` with imports** then embed into bare-runtime (see peardock `scripts/bare-standalone.cjs`). Stub at `build/stubs/esbuild-import-glob.cjs` is prepared for that path.
|
|
||||||
- Natives: only target host prebuilds (prune multi-arch bloat)
|
```sh
|
||||||
|
npm run make:standalone # current host
|
||||||
|
npm run make:standalone:all # all 64-bit hosts
|
||||||
|
# → out/flying-jib-<host>/flying-jib[.exe]
|
||||||
|
```
|
||||||
|
|
||||||
|
Script: `scripts/bare-standalone.cjs`
|
||||||
|
- `bare-pack` + `build/squid-imports.json` + stubs
|
||||||
|
- Postinstall patches: static Squid plugins, log.js, engines, events polyfill
|
||||||
|
|
||||||
|
Verified: standalone binary starts Squid on `127.0.0.1` with empty PATH (no system Node).
|
||||||
|
Size today ~500MB — optimize later (prune natives / strip).
|
||||||
|
|
||||||
|
|
||||||
## Pear deployment layers
|
## Pear deployment layers
|
||||||
|
|||||||
@@ -99,12 +99,14 @@ Stream framing: Hyperswarm/HyperDHT secretstream (already message-oriented for P
|
|||||||
|
|
||||||
| Protocol name | Phase | Purpose |
|
| Protocol name | Phase | Purpose |
|
||||||
|---------------|-------|---------|
|
|---------------|-------|---------|
|
||||||
| `flying-jib/presence` | 3 | Peer id, display name, world/region, heartbeat |
|
| `flying-jib/presence` | 3 | Peer id, display name, heartbeat (**implemented**) |
|
||||||
| `flying-jib/chat` | 3 | Side-channel chat messages |
|
| `flying-jib/chat` | 3 | Side-channel chat messages (**implemented**) |
|
||||||
| `flying-jib/control` | 2–3 | Session hello, errors, version negotiate |
|
| `flying-jib/control` | 3 | Control messages (channel open; payload TBD) |
|
||||||
| `flying-jib/migrate` | 4 | Border handoff state |
|
| `flying-jib/migrate` | 4 | Border handoff state |
|
||||||
| `flying-jib/registry` | 4 | Optional live region gossip (Autobase is source of truth) |
|
| `flying-jib/registry` | 4 | Optional live region gossip (Autobase is source of truth) |
|
||||||
|
|
||||||
|
**Topic:** `hash(worldPublicKey || 'flying-jib/control/v1')` via Hyperswarm (separate from MC TCP tunnel).
|
||||||
|
|
||||||
### Presence message (logical)
|
### Presence message (logical)
|
||||||
|
|
||||||
```json
|
```json
|
||||||
|
|||||||
@@ -0,0 +1,238 @@
|
|||||||
|
'use strict'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Side-channel peer session for a private world (Phase 3).
|
||||||
|
* Hyperswarm topic derived from world public key; Protomux channels:
|
||||||
|
* flying-jib/presence
|
||||||
|
* flying-jib/chat
|
||||||
|
* flying-jib/control
|
||||||
|
*
|
||||||
|
* Separate from the MC TCP tunnel (ADR-0006 data plane).
|
||||||
|
*/
|
||||||
|
|
||||||
|
const ReadyResource = require('ready-resource')
|
||||||
|
const Hyperswarm = require('hyperswarm')
|
||||||
|
const Protomux = require('protomux')
|
||||||
|
const c = require('compact-encoding')
|
||||||
|
const b4a = require('b4a')
|
||||||
|
const crypto = require('hypercore-crypto')
|
||||||
|
|
||||||
|
const PROTO_PRESENCE = 'flying-jib/presence'
|
||||||
|
const PROTO_CHAT = 'flying-jib/chat'
|
||||||
|
const PROTO_CONTROL = 'flying-jib/control'
|
||||||
|
|
||||||
|
const presenceEncoding = c.json
|
||||||
|
const chatEncoding = c.json
|
||||||
|
const controlEncoding = c.json
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 32-byte topic for private-world control plane.
|
||||||
|
* @param {Buffer} worldPublicKey
|
||||||
|
*/
|
||||||
|
function controlTopic(worldPublicKey) {
|
||||||
|
return crypto.hash(b4a.concat([b4a.from(worldPublicKey), b4a.from('flying-jib/control/v1')]))
|
||||||
|
}
|
||||||
|
|
||||||
|
class PeerSession extends ReadyResource {
|
||||||
|
/**
|
||||||
|
* @param {object} opts
|
||||||
|
* @param {Buffer} opts.worldPublicKey
|
||||||
|
* @param {string} [opts.displayName]
|
||||||
|
* @param {Buffer} [opts.keyPairSeed] optional stable Noise key seed
|
||||||
|
*/
|
||||||
|
constructor(opts) {
|
||||||
|
super()
|
||||||
|
if (!opts || !opts.worldPublicKey) throw new Error('PeerSession requires worldPublicKey')
|
||||||
|
this.worldPublicKey = b4a.from(opts.worldPublicKey)
|
||||||
|
this.displayName = opts.displayName || 'player'
|
||||||
|
this.keyPairSeed = opts.keyPairSeed || null
|
||||||
|
this.swarm = null
|
||||||
|
this.topic = controlTopic(this.worldPublicKey)
|
||||||
|
this.peers = new Map() // remotePublicKey hex -> { name, lastSeen, mux }
|
||||||
|
this._heartbeat = null
|
||||||
|
this._peerId = null
|
||||||
|
}
|
||||||
|
|
||||||
|
get peerId() {
|
||||||
|
return this._peerId
|
||||||
|
}
|
||||||
|
|
||||||
|
get peerList() {
|
||||||
|
const list = []
|
||||||
|
for (const [id, p] of this.peers) {
|
||||||
|
list.push({ id, name: p.name, lastSeen: p.lastSeen })
|
||||||
|
}
|
||||||
|
return list
|
||||||
|
}
|
||||||
|
|
||||||
|
async _open() {
|
||||||
|
const keyPair = this.keyPairSeed
|
||||||
|
? require('hyperdht').keyPair(this.keyPairSeed)
|
||||||
|
: require('hyperdht').keyPair()
|
||||||
|
this._peerId = b4a.toString(keyPair.publicKey, 'hex').slice(0, 16)
|
||||||
|
|
||||||
|
this.swarm = new Hyperswarm({ keyPair })
|
||||||
|
this.swarm.on('connection', (conn, info) => this._onConnection(conn, info))
|
||||||
|
|
||||||
|
this.swarm.join(this.topic, { server: true, client: true })
|
||||||
|
await this.swarm.flush()
|
||||||
|
|
||||||
|
this._heartbeat = setInterval(() => this._broadcastPresence(), 5000)
|
||||||
|
if (this._heartbeat.unref) this._heartbeat.unref()
|
||||||
|
this._broadcastPresence()
|
||||||
|
}
|
||||||
|
|
||||||
|
_onConnection(conn, info) {
|
||||||
|
const remoteId = b4a.toString(conn.remotePublicKey, 'hex')
|
||||||
|
const mux = new Protomux(conn)
|
||||||
|
|
||||||
|
const presence = mux.createChannel({
|
||||||
|
protocol: PROTO_PRESENCE,
|
||||||
|
onopen: () => {},
|
||||||
|
onclose: () => {}
|
||||||
|
})
|
||||||
|
const chat = mux.createChannel({
|
||||||
|
protocol: PROTO_CHAT,
|
||||||
|
onopen: () => {},
|
||||||
|
onclose: () => {}
|
||||||
|
})
|
||||||
|
const control = mux.createChannel({
|
||||||
|
protocol: PROTO_CONTROL,
|
||||||
|
onopen: () => {},
|
||||||
|
onclose: () => {}
|
||||||
|
})
|
||||||
|
|
||||||
|
const entry = {
|
||||||
|
name: remoteId.slice(0, 8),
|
||||||
|
lastSeen: Date.now(),
|
||||||
|
mux,
|
||||||
|
_sendPresence: null,
|
||||||
|
_sendChat: null
|
||||||
|
}
|
||||||
|
this.peers.set(remoteId, entry)
|
||||||
|
|
||||||
|
const presenceMsg = presence.addMessage({
|
||||||
|
encoding: presenceEncoding,
|
||||||
|
onmessage: (m) => {
|
||||||
|
const p = this.peers.get(remoteId) || entry
|
||||||
|
p.name = m.name || p.name
|
||||||
|
p.lastSeen = Date.now()
|
||||||
|
this.peers.set(remoteId, p)
|
||||||
|
this.emit('presence', { peerId: remoteId, ...m })
|
||||||
|
this.emit('peers', this.peerList)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const chatMsg = chat.addMessage({
|
||||||
|
encoding: chatEncoding,
|
||||||
|
onmessage: (m) => {
|
||||||
|
this.emit('chat', {
|
||||||
|
from: m.from || remoteId.slice(0, 8),
|
||||||
|
peerId: remoteId,
|
||||||
|
text: String(m.text || ''),
|
||||||
|
ts: m.ts || Date.now()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
control.addMessage({
|
||||||
|
encoding: controlEncoding,
|
||||||
|
onmessage: (m) => {
|
||||||
|
this.emit('control', { peerId: remoteId, ...m })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
presence.open()
|
||||||
|
chat.open()
|
||||||
|
control.open()
|
||||||
|
|
||||||
|
entry._sendPresence = (payload) => {
|
||||||
|
try {
|
||||||
|
presenceMsg.send(payload)
|
||||||
|
} catch {
|
||||||
|
/* ignore */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
entry._sendChat = (payload) => {
|
||||||
|
try {
|
||||||
|
chatMsg.send(payload)
|
||||||
|
} catch {
|
||||||
|
/* ignore */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Introduce ourselves
|
||||||
|
entry._sendPresence({
|
||||||
|
peerId: this._peerId,
|
||||||
|
name: this.displayName,
|
||||||
|
ts: Date.now()
|
||||||
|
})
|
||||||
|
|
||||||
|
conn.on('close', () => {
|
||||||
|
this.peers.delete(remoteId)
|
||||||
|
this.emit('peer-leave', { peerId: remoteId })
|
||||||
|
this.emit('peers', this.peerList)
|
||||||
|
})
|
||||||
|
conn.on('error', () => {
|
||||||
|
/* closed via close */
|
||||||
|
})
|
||||||
|
|
||||||
|
this.emit('peer-join', { peerId: remoteId })
|
||||||
|
}
|
||||||
|
|
||||||
|
_broadcastPresence() {
|
||||||
|
const payload = {
|
||||||
|
peerId: this._peerId,
|
||||||
|
name: this.displayName,
|
||||||
|
ts: Date.now()
|
||||||
|
}
|
||||||
|
for (const p of this.peers.values()) {
|
||||||
|
if (p._sendPresence) p._sendPresence(payload)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} text
|
||||||
|
*/
|
||||||
|
sendChat(text) {
|
||||||
|
const payload = {
|
||||||
|
from: this.displayName,
|
||||||
|
text: String(text).slice(0, 2000),
|
||||||
|
ts: Date.now()
|
||||||
|
}
|
||||||
|
for (const p of this.peers.values()) {
|
||||||
|
if (p._sendChat) p._sendChat(payload)
|
||||||
|
}
|
||||||
|
// Echo locally so UI/CLI sees own messages consistently
|
||||||
|
this.emit('chat', { ...payload, peerId: this._peerId, local: true })
|
||||||
|
}
|
||||||
|
|
||||||
|
setDisplayName(name) {
|
||||||
|
this.displayName = String(name || 'player').slice(0, 32)
|
||||||
|
this._broadcastPresence()
|
||||||
|
}
|
||||||
|
|
||||||
|
async _close() {
|
||||||
|
if (this._heartbeat) {
|
||||||
|
clearInterval(this._heartbeat)
|
||||||
|
this._heartbeat = null
|
||||||
|
}
|
||||||
|
if (this.swarm) {
|
||||||
|
try {
|
||||||
|
await this.swarm.destroy()
|
||||||
|
} catch {
|
||||||
|
/* ignore */
|
||||||
|
}
|
||||||
|
this.swarm = null
|
||||||
|
}
|
||||||
|
this.peers.clear()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
PeerSession,
|
||||||
|
controlTopic,
|
||||||
|
PROTO_PRESENCE,
|
||||||
|
PROTO_CHAT,
|
||||||
|
PROTO_CONTROL
|
||||||
|
}
|
||||||
@@ -11,6 +11,12 @@ and this project aims to follow [Semantic Versioning](https://semver.org/).
|
|||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
|
- Phase 3 peer communication (CLI):
|
||||||
|
- Hyperswarm/Protomux presence + chat for private worlds
|
||||||
|
- Stdin chat on `host`/`join`; `/peers` command
|
||||||
|
- Standalone packaging:
|
||||||
|
- `npm run make:standalone` → single Bare executable (no system Node)
|
||||||
|
- Flying Squid postinstall patches for static plugin load
|
||||||
- Phase 2 private world sharing (core):
|
- Phase 2 private world sharing (core):
|
||||||
- CLI: `host` (share), `join` (client tunnel)
|
- CLI: `host` (share), `join` (client tunnel)
|
||||||
- HyperDHT world tunnel with capability header
|
- HyperDHT world tunnel with capability header
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
# Flying Jib — Current Status
|
# Flying Jib — Current Status
|
||||||
|
|
||||||
**Snapshot date:** 2026-07-30
|
**Snapshot date:** 2026-07-30
|
||||||
**Phase:** 1 largely done · **Phase 2 core working** (host/join tunnel)
|
**Phase:** 1–3 core working · standalone binary ships Squid without system Node
|
||||||
**Product:** Flying Jib — Bare/Pear P2P Minecraft mesh
|
**Product:** Flying Jib — Bare/Pear P2P Minecraft mesh
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## One-line summary
|
## One-line summary
|
||||||
|
|
||||||
**Bare CLI hosts Flying Squid on loopback and can share private worlds via HyperDHT + `fj1.` invites.** Guests join with a local tunnel port; Java Edition always uses localhost.
|
**Single Bare executable** runs Flying Squid on loopback, shares worlds via HyperDHT + `fj1.` invites, and side-channel chat/presence over Hyperswarm/Protomux — **no system Node.js**.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -16,13 +16,12 @@
|
|||||||
|
|
||||||
| Area | Status |
|
| Area | Status |
|
||||||
|------|--------|
|
|------|--------|
|
||||||
| Bare app CLI (`create` / `list` / `start` / `host` / `join` / `status`) | Working |
|
| CLI (`create` / `list` / `start` / `host` / `join` / `status`) | Working |
|
||||||
| Flying Squid on Bare + Node (dev) | Loopback listen verified |
|
| Flying Squid on Bare + standalone binary | Loopback listen verified **without Node on PATH** |
|
||||||
| `fj-bind-guard` | OK |
|
| HyperDHT world tunnel + `fj1.` invites | Working |
|
||||||
| Unit tests (bind-guard, worlds, invite, tunnel) | **10/10 pass** |
|
| Peer chat + presence (Protomux) | Working (tests + CLI stdin chat) |
|
||||||
| HyperDHT world tunnel host↔client | Integration test + CLI smoke |
|
| `npm run make:standalone` | Produces `out/flying-jib-<host>/flying-jib` (~500MB) |
|
||||||
| `fj1.` invite mint/parse | Working |
|
| Unit/integration tests | **12/12 pass** |
|
||||||
| Graceful shutdown (SIGINT re-entrancy guard) | Improved |
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -30,32 +29,34 @@
|
|||||||
|
|
||||||
| Area | Status |
|
| Area | Status |
|
||||||
|------|--------|
|
|------|--------|
|
||||||
| `bare-build --standalone` release artifact | In progress / unproven |
|
| Binary size optimization | ~500MB — prune later |
|
||||||
| Presence / chat Protomux channels | Phase 3 |
|
|
||||||
| Mesh enrollment / borders | Phase 4 |
|
| Mesh enrollment / borders | Phase 4 |
|
||||||
| GUI | Not started |
|
| GUI | Not started |
|
||||||
| Production Pear OTA key | Placeholder |
|
| Production Pear OTA key | Placeholder |
|
||||||
| Full Java multiplayer playtest on two machines | Manual |
|
| Two-machine Java playtest | Manual |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## How to try (two terminals)
|
## How to try
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
|
# Dev
|
||||||
BARE=./node_modules/bare-runtime/bin/bare
|
BARE=./node_modules/bare-runtime/bin/bare
|
||||||
$BARE bin.mjs --storage /tmp/fj-host create demo
|
$BARE bin.mjs host demo --port 25565 --name Alice
|
||||||
$BARE bin.mjs --storage /tmp/fj-host host demo --port 25565
|
$BARE bin.mjs join 'fj1.…' --port 25566 --name Bob
|
||||||
# copy fj1.… invite
|
# type chat lines; /peers
|
||||||
|
|
||||||
$BARE bin.mjs --storage /tmp/fj-guest join 'fj1.…' --port 25566
|
# Standalone (no Node)
|
||||||
# Java → 127.0.0.1:25565 (host) or :25566 (guest)
|
npm run make:standalone
|
||||||
|
./out/flying-jib-darwin-arm64/flying-jib create demo
|
||||||
|
./out/flying-jib-darwin-arm64/flying-jib start demo --port 25565
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Next actions
|
## Next actions
|
||||||
|
|
||||||
1. Manual Java Edition multiplayer through tunnel
|
1. Two-machine Java Edition playtest
|
||||||
2. Finish bare-build standalone CI path
|
2. Shrink standalone binary / CI matrix for all hosts
|
||||||
3. Phase 3: presence + chat over Protomux
|
3. Phase 4 mesh registry + border migration
|
||||||
4. Hardening: tunnel connection limits UX, invite rotate
|
|
||||||
|
|||||||
@@ -5,6 +5,29 @@
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## 2026-07-30 — Phase 3 chat/presence + standalone binary
|
||||||
|
|
||||||
|
### Wins
|
||||||
|
|
||||||
|
- `lib/peer-session.js` — Hyperswarm + Protomux (`flying-jib/presence|chat|control`)
|
||||||
|
- CLI host/join: stdin chat, `/peers`, `--name` display name
|
||||||
|
- Peer chat integration test
|
||||||
|
- **`scripts/bare-standalone.cjs`** peardock-style pack; **standalone binary runs Squid on 127.0.0.1 with PATH without Node**
|
||||||
|
- Postinstall patches: static Squid plugins, log.js bare-pack fixes, events polyfill vendor
|
||||||
|
|
||||||
|
### Notes
|
||||||
|
|
||||||
|
- Standalone ~500MB; size optimization later
|
||||||
|
- Dev path `bare-runtime/bin/bare bin.mjs` remains preferred for iteration
|
||||||
|
|
||||||
|
### Next
|
||||||
|
|
||||||
|
- Multi-host make:standalone:all in CI
|
||||||
|
- Phase 4 mesh
|
||||||
|
- Java multiplayer playtest
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## 2026-07-30 — Phase 2 core: HyperDHT tunnel + fj1. invites
|
## 2026-07-30 — Phase 2 core: HyperDHT tunnel + fj1. invites
|
||||||
|
|
||||||
### Wins
|
### Wins
|
||||||
|
|||||||
@@ -120,7 +120,7 @@
|
|||||||
|
|
||||||
| Field | Value |
|
| Field | Value |
|
||||||
|-------|-------|
|
|-------|-------|
|
||||||
| **Status** | Not Started |
|
| **Status** | In Progress (CLI core done) |
|
||||||
| **Owner** | TBD |
|
| **Owner** | TBD |
|
||||||
| **Target** | TBD |
|
| **Target** | TBD |
|
||||||
| **ADRs** | 0003 (channels) |
|
| **ADRs** | 0003 (channels) |
|
||||||
@@ -133,9 +133,11 @@
|
|||||||
|
|
||||||
### Acceptance
|
### Acceptance
|
||||||
|
|
||||||
- [ ] Live peer list for private world session
|
- [x] Live peer list for private world session (`/peers`, presence events)
|
||||||
- [ ] Side-channel chat works when peers are connected
|
- [x] Side-channel chat works when peers are connected (stdin on host/join)
|
||||||
- [ ] Reconnect / heartbeat documented and implemented
|
- [x] Heartbeat presence every 5s
|
||||||
|
- [ ] GUI peer list
|
||||||
|
- [ ] Bridge to in-game Squid chat (optional)
|
||||||
|
|
||||||
### Documentation required to complete
|
### Documentation required to complete
|
||||||
|
|
||||||
|
|||||||
Generated
+155
-5
@@ -10,6 +10,7 @@
|
|||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"autobase": "^7.28.1",
|
||||||
"b4a": "^1.6.7",
|
"b4a": "^1.6.7",
|
||||||
"bare-assert": "^1.1.0",
|
"bare-assert": "^1.1.0",
|
||||||
"bare-buffer": "^3.3.1",
|
"bare-buffer": "^3.3.1",
|
||||||
@@ -36,16 +37,19 @@
|
|||||||
"bare-url": "^2.1.0",
|
"bare-url": "^2.1.0",
|
||||||
"bare-utils": "^1.0.0",
|
"bare-utils": "^1.0.0",
|
||||||
"bare-zlib": "^1.3.0",
|
"bare-zlib": "^1.3.0",
|
||||||
|
"compact-encoding": "^3.3.0",
|
||||||
"corestore": "^7.9.2",
|
"corestore": "^7.9.2",
|
||||||
"events": "^3.3.0",
|
"events": "^3.3.0",
|
||||||
"flying-squid": "^1.12.0",
|
"flying-squid": "^1.12.0",
|
||||||
"framed-stream": "^1.0.1",
|
"framed-stream": "^1.0.1",
|
||||||
"graceful-goodbye": "^1.3.3",
|
"graceful-goodbye": "^1.3.3",
|
||||||
|
"hyperbee": "^2.27.3",
|
||||||
"hypercore-crypto": "^3.4.2",
|
"hypercore-crypto": "^3.4.2",
|
||||||
"hyperdht": "^6.20.0",
|
"hyperdht": "^6.20.0",
|
||||||
"hyperswarm": "^4.17.0",
|
"hyperswarm": "^4.17.0",
|
||||||
"paparam": "^1.10.1",
|
"paparam": "^1.10.1",
|
||||||
"pear-runtime": "^1.2.0",
|
"pear-runtime": "^1.2.0",
|
||||||
|
"protomux": "^3.11.0",
|
||||||
"ready-resource": "^1.2.0",
|
"ready-resource": "^1.2.0",
|
||||||
"which-runtime": "^1.4.0",
|
"which-runtime": "^1.4.0",
|
||||||
"z32": "^1.1.0"
|
"z32": "^1.1.0"
|
||||||
@@ -55,6 +59,9 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"bare-build": "^1.0.2",
|
"bare-build": "^1.0.2",
|
||||||
|
"bare-bundle-id": "^1.0.0",
|
||||||
|
"bare-module-traverse": "^1.0.0",
|
||||||
|
"bare-pack": "^2.2.1",
|
||||||
"bare-runtime": "1.29.4",
|
"bare-runtime": "1.29.4",
|
||||||
"brittle": "^3.19.0",
|
"brittle": "^3.19.0",
|
||||||
"prettier": "^3.6.2",
|
"prettier": "^3.6.2",
|
||||||
@@ -248,6 +255,36 @@
|
|||||||
"integrity": "sha512-6i37w/+EhlWlGUJff3T/Q8u1RGmP5wgbiwYnOnbOqvtrPxT63/sYFyP9RcpxtxGymtfA075IvmOnL7ycNOWl3w==",
|
"integrity": "sha512-6i37w/+EhlWlGUJff3T/Q8u1RGmP5wgbiwYnOnbOqvtrPxT63/sYFyP9RcpxtxGymtfA075IvmOnL7ycNOWl3w==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/autobase": {
|
||||||
|
"version": "7.28.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/autobase/-/autobase-7.28.1.tgz",
|
||||||
|
"integrity": "sha512-I8SHcJE/ru5Nun6NpqwRjenB8aK01b9z1qNeyx4XuG+BxeFUpXk0ZT2u3/nG+GZnObUTFMCKOo2fbAv7ipcaLw==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"b4a": "^1.6.1",
|
||||||
|
"bare-events": "^2.2.0",
|
||||||
|
"compact-encoding": "^3.0.0",
|
||||||
|
"core-coupler": "^2.0.0",
|
||||||
|
"debounceify": "^1.0.0",
|
||||||
|
"encryption-encoding": "^1.0.3",
|
||||||
|
"hyperbee": "^2.22.0",
|
||||||
|
"hypercore": "^11.27.12",
|
||||||
|
"hypercore-crypto": "^3.4.0",
|
||||||
|
"hypercore-id-encoding": "^1.2.0",
|
||||||
|
"hyperschema": "^1.12.1",
|
||||||
|
"index-encoder": "^3.3.2",
|
||||||
|
"nanoassert": "^2.0.0",
|
||||||
|
"protomux-wakeup": "^2.0.0",
|
||||||
|
"ready-resource": "^1.0.0",
|
||||||
|
"resolve-reject-promise": "^1.1.0",
|
||||||
|
"safety-catch": "^1.0.2",
|
||||||
|
"scope-lock": "^1.2.4",
|
||||||
|
"signal-promise": "^1.0.3",
|
||||||
|
"sodium-universal": "^5.0.1",
|
||||||
|
"sub-encoder": "^2.1.1",
|
||||||
|
"tiny-buffer-map": "^1.1.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/b4a": {
|
"node_modules/b4a": {
|
||||||
"version": "1.8.1",
|
"version": "1.8.1",
|
||||||
"resolved": "https://registry.npmjs.org/b4a/-/b4a-1.8.1.tgz",
|
"resolved": "https://registry.npmjs.org/b4a/-/b4a-1.8.1.tgz",
|
||||||
@@ -614,6 +651,31 @@
|
|||||||
"require-asset": "^1.0.2"
|
"require-asset": "^1.0.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/bare-build/node_modules/bare-module-traverse": {
|
||||||
|
"version": "2.4.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/bare-module-traverse/-/bare-module-traverse-2.4.4.tgz",
|
||||||
|
"integrity": "sha512-zK4bDxqeD15Tvu/C5B1Mi/epl9PavjIhU3SVh5RhDb4Hpet6applPVX8Q9rinu7nT80yzgSSlBn0exzJaNXv0A==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"bare-addon-resolve": "^1.5.0",
|
||||||
|
"bare-mime": "^1.0.0",
|
||||||
|
"bare-module-lexer": "^1.6.0",
|
||||||
|
"bare-module-resolve": "^1.7.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"bare-buffer": "*",
|
||||||
|
"bare-url": "*"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"bare-buffer": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"bare-url": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/bare-bundle": {
|
"node_modules/bare-bundle": {
|
||||||
"version": "1.10.0",
|
"version": "1.10.0",
|
||||||
"resolved": "https://registry.npmjs.org/bare-bundle/-/bare-bundle-1.10.0.tgz",
|
"resolved": "https://registry.npmjs.org/bare-bundle/-/bare-bundle-1.10.0.tgz",
|
||||||
@@ -1108,14 +1170,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/bare-module-traverse": {
|
"node_modules/bare-module-traverse": {
|
||||||
"version": "2.4.4",
|
"version": "1.8.3",
|
||||||
"resolved": "https://registry.npmjs.org/bare-module-traverse/-/bare-module-traverse-2.4.4.tgz",
|
"resolved": "https://registry.npmjs.org/bare-module-traverse/-/bare-module-traverse-1.8.3.tgz",
|
||||||
"integrity": "sha512-zK4bDxqeD15Tvu/C5B1Mi/epl9PavjIhU3SVh5RhDb4Hpet6applPVX8Q9rinu7nT80yzgSSlBn0exzJaNXv0A==",
|
"integrity": "sha512-Iykt1899FOo0lpejEwAUK50fYIzQ18uiJFqybgZvIIqnZSV0/0GhJp+3w3Vx5hTVeTIROF8UmTUB5jHNs65PAg==",
|
||||||
|
"dev": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"bare-addon-resolve": "^1.5.0",
|
"bare-addon-resolve": "^1.5.0",
|
||||||
"bare-mime": "^1.0.0",
|
"bare-module-lexer": "^1.4.0",
|
||||||
"bare-module-lexer": "^1.6.0",
|
|
||||||
"bare-module-resolve": "^1.7.0"
|
"bare-module-resolve": "^1.7.0"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
@@ -1297,6 +1359,31 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/bare-pack/node_modules/bare-module-traverse": {
|
||||||
|
"version": "2.4.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/bare-module-traverse/-/bare-module-traverse-2.4.4.tgz",
|
||||||
|
"integrity": "sha512-zK4bDxqeD15Tvu/C5B1Mi/epl9PavjIhU3SVh5RhDb4Hpet6applPVX8Q9rinu7nT80yzgSSlBn0exzJaNXv0A==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"bare-addon-resolve": "^1.5.0",
|
||||||
|
"bare-mime": "^1.0.0",
|
||||||
|
"bare-module-lexer": "^1.6.0",
|
||||||
|
"bare-module-resolve": "^1.7.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"bare-buffer": "*",
|
||||||
|
"bare-url": "*"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"bare-buffer": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"bare-url": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/bare-path": {
|
"node_modules/bare-path": {
|
||||||
"version": "3.1.1",
|
"version": "3.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/bare-path/-/bare-path-3.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/bare-path/-/bare-path-3.1.1.tgz",
|
||||||
@@ -1956,6 +2043,30 @@
|
|||||||
"bare-url": "^2.4.2"
|
"bare-url": "^2.4.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/bare-thread/node_modules/bare-module-traverse": {
|
||||||
|
"version": "2.4.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/bare-module-traverse/-/bare-module-traverse-2.4.4.tgz",
|
||||||
|
"integrity": "sha512-zK4bDxqeD15Tvu/C5B1Mi/epl9PavjIhU3SVh5RhDb4Hpet6applPVX8Q9rinu7nT80yzgSSlBn0exzJaNXv0A==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"bare-addon-resolve": "^1.5.0",
|
||||||
|
"bare-mime": "^1.0.0",
|
||||||
|
"bare-module-lexer": "^1.6.0",
|
||||||
|
"bare-module-resolve": "^1.7.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"bare-buffer": "*",
|
||||||
|
"bare-url": "*"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"bare-buffer": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"bare-url": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/bare-timers": {
|
"node_modules/bare-timers": {
|
||||||
"version": "1.5.2",
|
"version": "1.5.2",
|
||||||
"resolved": "https://registry.npmjs.org/bare-timers/-/bare-timers-1.5.2.tgz",
|
"resolved": "https://registry.npmjs.org/bare-timers/-/bare-timers-1.5.2.tgz",
|
||||||
@@ -2462,6 +2573,15 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/core-coupler": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/core-coupler/-/core-coupler-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-FJuEvsdCMwx0Wu+gFQ49rGCi8LCXh8kizHsCQwkdgPZFEFiF0z2HDvyIs+fPt5wMIfU2UVFDuN+dtpfbIxJE6g==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"safety-catch": "^1.0.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/corestore": {
|
"node_modules/corestore": {
|
||||||
"version": "7.12.0",
|
"version": "7.12.0",
|
||||||
"resolved": "https://registry.npmjs.org/corestore/-/corestore-7.12.0.tgz",
|
"resolved": "https://registry.npmjs.org/corestore/-/corestore-7.12.0.tgz",
|
||||||
@@ -2580,6 +2700,15 @@
|
|||||||
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
|
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/encryption-encoding": {
|
||||||
|
"version": "1.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/encryption-encoding/-/encryption-encoding-1.0.3.tgz",
|
||||||
|
"integrity": "sha512-+SlKCeULnNnwBF2rGrJlSLYjwITwfjO8PLSHzue4yt+2DsVJUU8GakRkH4+mTaa3FzpxVjpTy1kKR2gYGctNWg==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"hyperschema": "^1.19.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/endian-toggle": {
|
"node_modules/endian-toggle": {
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/endian-toggle/-/endian-toggle-0.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/endian-toggle/-/endian-toggle-0.0.0.tgz",
|
||||||
@@ -3804,6 +3933,18 @@
|
|||||||
"unslab": "^1.3.0"
|
"unslab": "^1.3.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/protomux-wakeup": {
|
||||||
|
"version": "2.9.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/protomux-wakeup/-/protomux-wakeup-2.9.0.tgz",
|
||||||
|
"integrity": "sha512-K93WhS9qIRL8WAQPU2V3PxdpQ1oRlQCFyCyIYInoq/iRkcuO1BegRERWo7SS1Tcme14VKGWOr/IivjjYnHan3Q==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"b4a": "^1.6.7",
|
||||||
|
"hypercore-crypto": "^3.5.0",
|
||||||
|
"hyperschema": "^1.10.4",
|
||||||
|
"protomux": "^3.10.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/punycode": {
|
"node_modules/punycode": {
|
||||||
"version": "2.3.1",
|
"version": "2.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
|
||||||
@@ -4356,6 +4497,15 @@
|
|||||||
"integrity": "sha512-IaM+/bh71URhhzlH1qNKNylamK4nxaby2q0qxolWL4Typ3+sIcZHxLe/Mj35b7GOHat/RKpp2vbRepjV6sD+Rw==",
|
"integrity": "sha512-IaM+/bh71URhhzlH1qNKNylamK4nxaby2q0qxolWL4Typ3+sIcZHxLe/Mj35b7GOHat/RKpp2vbRepjV6sD+Rw==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/tiny-buffer-map": {
|
||||||
|
"version": "1.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/tiny-buffer-map/-/tiny-buffer-map-1.1.1.tgz",
|
||||||
|
"integrity": "sha512-C1eDw6ks9CmkDbWVCPHobuixPTkxGa7IDERlaVk98dv4tOUdz42o3haHBr0uhNxbj0gczBTVIyS2uQsu+1vc2Q==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"b4a": "^1.6.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/tmatch": {
|
"node_modules/tmatch": {
|
||||||
"version": "5.0.0",
|
"version": "5.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/tmatch/-/tmatch-5.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/tmatch/-/tmatch-5.0.0.tgz",
|
||||||
|
|||||||
+11
-2
@@ -8,13 +8,15 @@
|
|||||||
"bin": "bin.mjs",
|
"bin": "bin.mjs",
|
||||||
"upgrade": "pear://<YOUR_KEY_HERE>",
|
"upgrade": "pear://<YOUR_KEY_HERE>",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"postinstall": "node scripts/patch-engines-for-bare.js && node scripts/generate-squid-imports.js",
|
"postinstall": "node scripts/patch-engines-for-bare.js && node scripts/patch-flying-squid-plugins.js && node scripts/patch-flying-squid-log.js && node scripts/generate-squid-imports.js",
|
||||||
"start": "node_modules/bare-runtime/bin/bare bin.mjs",
|
"start": "node_modules/bare-runtime/bin/bare bin.mjs",
|
||||||
"start:node": "node bin.mjs",
|
"start:node": "node bin.mjs",
|
||||||
"test": "brittle-node test/*.test.js",
|
"test": "brittle-node test/*.test.js",
|
||||||
"test:bare": "brittle-bare test/*.test.js",
|
"test:bare": "brittle-bare test/*.test.js",
|
||||||
"docs:check": "node -e \"const fs=require('fs');const paths=['living_docs/ROADMAP.md','living_docs/CURRENT_STATUS.md','agent/RULES.md','agent/SECURITY.md','developer_docs/ARCHITECTURE.md','user_docs/README.md','lib/squid-manager.js','bin.mjs'];for (const p of paths){if(!fs.existsSync(p)){console.error('missing',p);process.exit(1)}};console.log('docs+app scaffold ok')\"",
|
"docs:check": "node -e \"const fs=require('fs');const paths=['living_docs/ROADMAP.md','living_docs/CURRENT_STATUS.md','agent/RULES.md','agent/SECURITY.md','developer_docs/ARCHITECTURE.md','user_docs/README.md','lib/squid-manager.js','bin.mjs'];for (const p of paths){if(!fs.existsSync(p)){console.error('missing',p);process.exit(1)}};console.log('docs+app scaffold ok')\"",
|
||||||
"make": "node scripts/make.js",
|
"make": "node scripts/make.js",
|
||||||
|
"make:standalone": "node scripts/bare-standalone.cjs",
|
||||||
|
"make:standalone:all": "node scripts/bare-standalone.cjs --host all",
|
||||||
"make:darwin-arm64": "bare-build --name flying-jib --standalone --host darwin-arm64 --out ./out/darwin-arm64 bin.mjs",
|
"make:darwin-arm64": "bare-build --name flying-jib --standalone --host darwin-arm64 --out ./out/darwin-arm64 bin.mjs",
|
||||||
"make:darwin-x64": "bare-build --name flying-jib --standalone --host darwin-x64 --out ./out/darwin-x64 bin.mjs",
|
"make:darwin-x64": "bare-build --name flying-jib --standalone --host darwin-x64 --out ./out/darwin-x64 bin.mjs",
|
||||||
"make:linux-arm64": "bare-build --name flying-jib --standalone --host linux-arm64 --out ./out/linux-arm64 bin.mjs",
|
"make:linux-arm64": "bare-build --name flying-jib --standalone --host linux-arm64 --out ./out/linux-arm64 bin.mjs",
|
||||||
@@ -142,6 +144,8 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"autobase": "^7.28.1",
|
||||||
|
"b4a": "^1.6.7",
|
||||||
"bare-assert": "^1.1.0",
|
"bare-assert": "^1.1.0",
|
||||||
"bare-buffer": "^3.3.1",
|
"bare-buffer": "^3.3.1",
|
||||||
"bare-console": "^6.0.1",
|
"bare-console": "^6.0.1",
|
||||||
@@ -167,23 +171,28 @@
|
|||||||
"bare-url": "^2.1.0",
|
"bare-url": "^2.1.0",
|
||||||
"bare-utils": "^1.0.0",
|
"bare-utils": "^1.0.0",
|
||||||
"bare-zlib": "^1.3.0",
|
"bare-zlib": "^1.3.0",
|
||||||
|
"compact-encoding": "^3.3.0",
|
||||||
"corestore": "^7.9.2",
|
"corestore": "^7.9.2",
|
||||||
"events": "^3.3.0",
|
"events": "^3.3.0",
|
||||||
"b4a": "^1.6.7",
|
|
||||||
"flying-squid": "^1.12.0",
|
"flying-squid": "^1.12.0",
|
||||||
"framed-stream": "^1.0.1",
|
"framed-stream": "^1.0.1",
|
||||||
"graceful-goodbye": "^1.3.3",
|
"graceful-goodbye": "^1.3.3",
|
||||||
|
"hyperbee": "^2.27.3",
|
||||||
"hypercore-crypto": "^3.4.2",
|
"hypercore-crypto": "^3.4.2",
|
||||||
"hyperdht": "^6.20.0",
|
"hyperdht": "^6.20.0",
|
||||||
"hyperswarm": "^4.17.0",
|
"hyperswarm": "^4.17.0",
|
||||||
"paparam": "^1.10.1",
|
"paparam": "^1.10.1",
|
||||||
"pear-runtime": "^1.2.0",
|
"pear-runtime": "^1.2.0",
|
||||||
|
"protomux": "^3.11.0",
|
||||||
"ready-resource": "^1.2.0",
|
"ready-resource": "^1.2.0",
|
||||||
"which-runtime": "^1.4.0",
|
"which-runtime": "^1.4.0",
|
||||||
"z32": "^1.1.0"
|
"z32": "^1.1.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"bare-build": "^1.0.2",
|
"bare-build": "^1.0.2",
|
||||||
|
"bare-bundle-id": "^1.0.0",
|
||||||
|
"bare-module-traverse": "^1.0.0",
|
||||||
|
"bare-pack": "^2.2.1",
|
||||||
"bare-runtime": "1.29.4",
|
"bare-runtime": "1.29.4",
|
||||||
"brittle": "^3.19.0",
|
"brittle": "^3.19.0",
|
||||||
"prettier": "^3.6.2",
|
"prettier": "^3.6.2",
|
||||||
|
|||||||
@@ -0,0 +1,267 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
/**
|
||||||
|
* Pack Flying Jib as a Bare standalone executable (peardock pattern).
|
||||||
|
*
|
||||||
|
* bare-build CLI does not accept --imports; this script uses bare-pack with
|
||||||
|
* build/squid-imports.json + package.json imports, then embeds into bare-runtime.
|
||||||
|
*
|
||||||
|
* Usage:
|
||||||
|
* node scripts/bare-standalone.cjs
|
||||||
|
* node scripts/bare-standalone.cjs --host darwin-arm64
|
||||||
|
* node scripts/bare-standalone.cjs --host all
|
||||||
|
*/
|
||||||
|
'use strict'
|
||||||
|
|
||||||
|
const path = require('path')
|
||||||
|
const fs = require('fs')
|
||||||
|
const { pathToFileURL } = require('url')
|
||||||
|
const pack = require('bare-pack')
|
||||||
|
const { readModule, listPrefix } = require('bare-pack/fs')
|
||||||
|
const traverse = require('bare-module-traverse')
|
||||||
|
const id = require('bare-bundle-id')
|
||||||
|
|
||||||
|
const root = path.resolve(__dirname, '..')
|
||||||
|
const pkg = require(path.join(root, 'package.json'))
|
||||||
|
|
||||||
|
const ALL_HOSTS = [
|
||||||
|
'darwin-arm64',
|
||||||
|
'darwin-x64',
|
||||||
|
'linux-arm64',
|
||||||
|
'linux-x64',
|
||||||
|
'win32-arm64',
|
||||||
|
'win32-x64'
|
||||||
|
]
|
||||||
|
|
||||||
|
function parseArgs(argv) {
|
||||||
|
const out = { hosts: [], outRoot: path.join(root, 'out') }
|
||||||
|
for (let i = 0; i < argv.length; i++) {
|
||||||
|
const a = argv[i]
|
||||||
|
if (a === '--host') {
|
||||||
|
const h = argv[++i]
|
||||||
|
if (h === 'all') out.hosts.push(...ALL_HOSTS)
|
||||||
|
else out.hosts.push(h)
|
||||||
|
} else if (a === '--out') out.outRoot = path.resolve(argv[++i])
|
||||||
|
else if (a === '--help' || a === '-h') out.help = true
|
||||||
|
}
|
||||||
|
if (!out.hosts.length) out.hosts.push(`${process.platform}-${process.arch}`)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
function fileURL(rel) {
|
||||||
|
return pathToFileURL(path.join(root, rel)).href
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildImportsMap() {
|
||||||
|
// Prefer generated squid-imports (bnr + shims)
|
||||||
|
const squidImportsPath = path.join(root, 'build', 'squid-imports.json')
|
||||||
|
let map = {}
|
||||||
|
if (fs.existsSync(squidImportsPath)) {
|
||||||
|
map = JSON.parse(fs.readFileSync(squidImportsPath, 'utf8'))
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
map = { ...require('bare-node-runtime/imports') }
|
||||||
|
} catch {
|
||||||
|
map = {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Merge package.json imports (relative bare paths need absolutizing)
|
||||||
|
const pkgImports = pkg.imports || {}
|
||||||
|
for (const [spec, target] of Object.entries(pkgImports)) {
|
||||||
|
if (typeof target === 'string') {
|
||||||
|
map[spec] = target
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (target && typeof target === 'object') {
|
||||||
|
map[spec] = { ...target }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const [spec, target] of Object.entries(map)) {
|
||||||
|
if (!target || typeof target !== 'object') continue
|
||||||
|
if (typeof target.bare === 'string' && target.bare.startsWith('./')) {
|
||||||
|
map[spec] = {
|
||||||
|
...target,
|
||||||
|
bare: fileURL(target.bare.replace(/^\.\//, ''))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Explicit stubs for optional / browser-only / missing optional deps
|
||||||
|
const stubs = {
|
||||||
|
'esbuild-import-glob(path:.,skipFiles:index.js,external.js)':
|
||||||
|
'build/stubs/esbuild-import-glob.cjs',
|
||||||
|
'supports-color': 'build/stubs/supports-color.cjs',
|
||||||
|
'cpu-features': 'build/stubs/supports-color.cjs',
|
||||||
|
'encoding': 'build/stubs/auto-encoding.cjs',
|
||||||
|
'bufferutil': 'build/stubs/auto-bufferutil.cjs',
|
||||||
|
'utf-8-validate': 'build/stubs/auto-utf-8-validate.cjs',
|
||||||
|
'canvas': 'build/stubs/auto-canvas.cjs',
|
||||||
|
'sqlite3': 'build/stubs/auto-sqlite3.cjs',
|
||||||
|
'better-sqlite3': 'build/stubs/auto-better-sqlite3.cjs'
|
||||||
|
}
|
||||||
|
for (const [spec, rel] of Object.entries(stubs)) {
|
||||||
|
map[spec] = { bare: fileURL(rel), default: fileURL(rel) }
|
||||||
|
}
|
||||||
|
|
||||||
|
return map
|
||||||
|
}
|
||||||
|
|
||||||
|
function platformForHost(host) {
|
||||||
|
const bareBuildRoot = path.dirname(require.resolve('bare-build/package'))
|
||||||
|
const load = (name) => require(path.join(bareBuildRoot, 'lib', 'platform', name))
|
||||||
|
if (host.startsWith('darwin') || host.startsWith('ios')) return load('apple')
|
||||||
|
if (host.startsWith('linux')) return load('linux')
|
||||||
|
if (host.startsWith('win32')) return load('windows')
|
||||||
|
if (host.startsWith('android')) return load('android')
|
||||||
|
throw new Error(`Unknown host '${host}'`)
|
||||||
|
}
|
||||||
|
|
||||||
|
function walkFind(dir, pred) {
|
||||||
|
const stack = [dir]
|
||||||
|
while (stack.length) {
|
||||||
|
const d = stack.pop()
|
||||||
|
let entries
|
||||||
|
try {
|
||||||
|
entries = fs.readdirSync(d, { withFileTypes: true })
|
||||||
|
} catch {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for (const ent of entries) {
|
||||||
|
const p = path.join(d, ent.name)
|
||||||
|
if (ent.isDirectory()) stack.push(p)
|
||||||
|
else if (pred(p)) return p
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
async function buildOne(host, outRoot) {
|
||||||
|
const name = pkg.productName ? 'flying-jib' : pkg.name
|
||||||
|
const outDir = path.join(outRoot, `${name}-${host}`)
|
||||||
|
fs.rmSync(outDir, { recursive: true, force: true })
|
||||||
|
fs.mkdirSync(outDir, { recursive: true })
|
||||||
|
|
||||||
|
// Ensure postinstall artifacts
|
||||||
|
require('child_process').execSync('node scripts/generate-squid-imports.js', {
|
||||||
|
cwd: root,
|
||||||
|
stdio: 'inherit'
|
||||||
|
})
|
||||||
|
require('child_process').execSync('node scripts/patch-engines-for-bare.js', {
|
||||||
|
cwd: root,
|
||||||
|
stdio: 'inherit'
|
||||||
|
})
|
||||||
|
|
||||||
|
const entryPath = path.join(root, 'bin.mjs')
|
||||||
|
const imports = buildImportsMap()
|
||||||
|
console.log(`[bare-standalone] packing ${name} for ${host}…`)
|
||||||
|
|
||||||
|
let entry = await pack(
|
||||||
|
pathToFileURL(entryPath),
|
||||||
|
{
|
||||||
|
hosts: [host],
|
||||||
|
linked: false,
|
||||||
|
resolve: traverse.resolve.bare,
|
||||||
|
imports
|
||||||
|
},
|
||||||
|
readModule,
|
||||||
|
listPrefix
|
||||||
|
)
|
||||||
|
|
||||||
|
const baseURL = pathToFileURL(root + path.sep)
|
||||||
|
entry = entry.unmount(baseURL)
|
||||||
|
entry.id = id(entry).toString('hex')
|
||||||
|
|
||||||
|
const platform = platformForHost(host)
|
||||||
|
const opts = {
|
||||||
|
name,
|
||||||
|
version: pkg.version || '0.0.0',
|
||||||
|
description: pkg.description || 'Flying Jib',
|
||||||
|
author: pkg.author || '',
|
||||||
|
identifier: 'dev.flyingjib.app',
|
||||||
|
hosts: [host],
|
||||||
|
out: outDir,
|
||||||
|
standalone: true,
|
||||||
|
package: false,
|
||||||
|
base: root
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`[bare-standalone] embedding bare-runtime for ${host}…`)
|
||||||
|
for await (const resource of platform(root, entry, null, opts)) {
|
||||||
|
if (resource && resource.path) {
|
||||||
|
console.log(`[bare-standalone] resource ${resource.path}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const binName = host.startsWith('win32') ? `${name}.exe` : name
|
||||||
|
let binary = path.join(outDir, binName)
|
||||||
|
if (!fs.existsSync(binary)) {
|
||||||
|
const found = walkFind(outDir, (f) => {
|
||||||
|
const base = path.basename(f)
|
||||||
|
return base === name || base === `${name}.exe` || base === 'flying-jib'
|
||||||
|
})
|
||||||
|
if (found) binary = found
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fs.existsSync(binary)) {
|
||||||
|
try {
|
||||||
|
fs.chmodSync(binary, 0o755)
|
||||||
|
} catch {
|
||||||
|
/* win */
|
||||||
|
}
|
||||||
|
const flat = path.join(outDir, path.basename(binary))
|
||||||
|
if (path.resolve(binary) !== path.resolve(flat)) {
|
||||||
|
fs.copyFileSync(binary, flat)
|
||||||
|
binary = flat
|
||||||
|
}
|
||||||
|
console.log(`[bare-standalone] wrote ${binary}`)
|
||||||
|
} else {
|
||||||
|
console.warn(`[bare-standalone] WARN: binary not found under ${outDir}`)
|
||||||
|
try {
|
||||||
|
console.warn(
|
||||||
|
' contents:',
|
||||||
|
fs.readdirSync(outDir, { recursive: true }).slice(0, 40).join(', ')
|
||||||
|
)
|
||||||
|
} catch {
|
||||||
|
/* ignore */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fs.writeFileSync(
|
||||||
|
path.join(outDir, 'build-info.json'),
|
||||||
|
JSON.stringify(
|
||||||
|
{
|
||||||
|
host,
|
||||||
|
name,
|
||||||
|
version: pkg.version,
|
||||||
|
builtAt: new Date().toISOString(),
|
||||||
|
entry: 'bin.mjs',
|
||||||
|
bundleId: entry.id
|
||||||
|
},
|
||||||
|
null,
|
||||||
|
2
|
||||||
|
) + '\n'
|
||||||
|
)
|
||||||
|
|
||||||
|
return outDir
|
||||||
|
}
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
const opts = parseArgs(process.argv.slice(2))
|
||||||
|
if (opts.help) {
|
||||||
|
console.log(`Usage: node scripts/bare-standalone.cjs [--host <host>|all] [--out dir]
|
||||||
|
Hosts: ${ALL_HOSTS.join(', ')}`)
|
||||||
|
process.exit(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
const results = []
|
||||||
|
for (const host of opts.hosts) {
|
||||||
|
results.push(await buildOne(host, opts.outRoot))
|
||||||
|
}
|
||||||
|
console.log('[bare-standalone] done:', results.join(', '))
|
||||||
|
}
|
||||||
|
|
||||||
|
main().catch((err) => {
|
||||||
|
console.error(err)
|
||||||
|
process.exit(1)
|
||||||
|
})
|
||||||
@@ -12,6 +12,15 @@ const { pathToFileURL } = require('url')
|
|||||||
|
|
||||||
const root = path.resolve(__dirname, '..')
|
const root = path.resolve(__dirname, '..')
|
||||||
const bnr = require('bare-node-runtime/imports')
|
const bnr = require('bare-node-runtime/imports')
|
||||||
|
|
||||||
|
// Keep events polyfill vendored for bare-pack (no nested node_modules require)
|
||||||
|
const eventsSrc = path.join(root, 'node_modules', 'events', 'events.js')
|
||||||
|
const eventsPoly = path.join(root, 'build', 'shims', 'events-polyfill.cjs')
|
||||||
|
if (fs.existsSync(eventsSrc)) {
|
||||||
|
fs.mkdirSync(path.dirname(eventsPoly), { recursive: true })
|
||||||
|
fs.copyFileSync(eventsSrc, eventsPoly)
|
||||||
|
}
|
||||||
|
|
||||||
const eventsShim = pathToFileURL(path.join(root, 'build/shims/node-events.cjs')).href
|
const eventsShim = pathToFileURL(path.join(root, 'build/shims/node-events.cjs')).href
|
||||||
const readlineShim = pathToFileURL(path.join(root, 'build/shims/node-readline.cjs')).href
|
const readlineShim = pathToFileURL(path.join(root, 'build/shims/node-readline.cjs')).href
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,67 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
'use strict'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Neutralize flying-squid log plugin dynamic import('exit-hook') which bare-pack
|
||||||
|
* does not follow, and soft-fail readline TTY helpers on Bare.
|
||||||
|
*/
|
||||||
|
|
||||||
|
const fs = require('fs')
|
||||||
|
const path = require('path')
|
||||||
|
|
||||||
|
const logPath = path.join(
|
||||||
|
__dirname,
|
||||||
|
'..',
|
||||||
|
'node_modules',
|
||||||
|
'flying-squid',
|
||||||
|
'src',
|
||||||
|
'lib',
|
||||||
|
'plugins',
|
||||||
|
'log.js'
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!fs.existsSync(logPath)) {
|
||||||
|
console.warn('[patch-flying-squid-log] skip (not installed)')
|
||||||
|
process.exit(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
let src = fs.readFileSync(logPath, 'utf8')
|
||||||
|
if (src.includes('FLYING_JIB_PATCH: exit-hook removed')) {
|
||||||
|
console.log('[patch-flying-squid-log] already patched')
|
||||||
|
process.exit(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Replace whole isInNode block's exit-hook import
|
||||||
|
src = src.replace(
|
||||||
|
/if \(isInNode\) \{[\s\S]*?readline = require\('readline'\)/,
|
||||||
|
`if (isInNode) {
|
||||||
|
/* FLYING_JIB_PATCH: exit-hook removed for bare-pack */
|
||||||
|
readline = require('readline')`
|
||||||
|
)
|
||||||
|
|
||||||
|
// Soften createInterface / prompt
|
||||||
|
if (!src.includes('FLYING_JIB_PATCH: readline soft')) {
|
||||||
|
src = src.replace(
|
||||||
|
/rl = readline\.createInterface\(\{[\s\S]*?\}\)\s*\n\s*rl\.setPrompt\('> '\)\s*\n\s*rl\.prompt\(true\)/,
|
||||||
|
`try { /* FLYING_JIB_PATCH: readline soft */
|
||||||
|
rl = readline.createInterface({
|
||||||
|
input: process.stdin,
|
||||||
|
output: process.stdout
|
||||||
|
})
|
||||||
|
if (rl && typeof rl.setPrompt === 'function') {
|
||||||
|
rl.setPrompt('> ')
|
||||||
|
rl.prompt(true)
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
rl = undefined
|
||||||
|
}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
src = src.replace(
|
||||||
|
'readline?.cursorTo(process.stdout, 0)',
|
||||||
|
'(readline && typeof readline.cursorTo === "function" ? readline.cursorTo(process.stdout, 0) : undefined)'
|
||||||
|
)
|
||||||
|
|
||||||
|
fs.writeFileSync(logPath, src)
|
||||||
|
console.log('[patch-flying-squid-log] patched')
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
'use strict'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Replace flying-squid dynamic requireindex plugin loading with static requires
|
||||||
|
* so bare-pack can include the full plugin graph in standalone binaries.
|
||||||
|
*/
|
||||||
|
|
||||||
|
const fs = require('fs')
|
||||||
|
const path = require('path')
|
||||||
|
|
||||||
|
const root = path.resolve(__dirname, '..')
|
||||||
|
const pluginsDir = path.join(root, 'node_modules', 'flying-squid', 'src', 'lib', 'plugins')
|
||||||
|
const indexPath = path.join(pluginsDir, 'index.js')
|
||||||
|
|
||||||
|
if (!fs.existsSync(indexPath)) {
|
||||||
|
console.warn('[patch-flying-squid-plugins] flying-squid not installed; skip')
|
||||||
|
process.exit(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
const files = fs
|
||||||
|
.readdirSync(pluginsDir)
|
||||||
|
.filter((f) => f.endsWith('.js') && f !== 'index.js')
|
||||||
|
.sort()
|
||||||
|
|
||||||
|
const requires = files
|
||||||
|
.map((f) => {
|
||||||
|
const key = './' + f
|
||||||
|
return ` require(${JSON.stringify(key)})`
|
||||||
|
})
|
||||||
|
.join(',\n')
|
||||||
|
|
||||||
|
const out = `module.exports.builtinPlugins = null
|
||||||
|
|
||||||
|
// Flying Jib postinstall: static plugin list for bare-pack (no readdir at runtime).
|
||||||
|
module.exports.initPlugins = () => {
|
||||||
|
if (module.exports.builtinPlugins) return
|
||||||
|
module.exports.builtinPlugins = [
|
||||||
|
${requires}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
fs.writeFileSync(indexPath, out)
|
||||||
|
console.log(`[patch-flying-squid-plugins] wrote static list (${files.length} plugins)`)
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
'use strict'
|
||||||
|
|
||||||
|
const test = require('brittle')
|
||||||
|
const crypto = require('hypercore-crypto')
|
||||||
|
const { PeerSession, controlTopic } = require('../lib/peer-session')
|
||||||
|
|
||||||
|
test('controlTopic is stable 32 bytes', (t) => {
|
||||||
|
const k = crypto.randomBytes(32)
|
||||||
|
const a = controlTopic(k)
|
||||||
|
const b = controlTopic(k)
|
||||||
|
t.is(a.byteLength, 32)
|
||||||
|
t.alike(a, b)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('two PeerSessions exchange chat', { timeout: 45000 }, async (t) => {
|
||||||
|
const worldKey = crypto.randomBytes(32)
|
||||||
|
|
||||||
|
const a = new PeerSession({
|
||||||
|
worldPublicKey: worldKey,
|
||||||
|
displayName: 'Alice',
|
||||||
|
keyPairSeed: crypto.randomBytes(32)
|
||||||
|
})
|
||||||
|
const b = new PeerSession({
|
||||||
|
worldPublicKey: worldKey,
|
||||||
|
displayName: 'Bob',
|
||||||
|
keyPairSeed: crypto.randomBytes(32)
|
||||||
|
})
|
||||||
|
|
||||||
|
await a.ready()
|
||||||
|
await b.ready()
|
||||||
|
|
||||||
|
const got = new Promise((resolve, reject) => {
|
||||||
|
const timer = setTimeout(() => reject(new Error('chat timeout')), 30000)
|
||||||
|
b.on('chat', (msg) => {
|
||||||
|
if (msg.local) return
|
||||||
|
if (msg.text === 'hello-from-alice') {
|
||||||
|
clearTimeout(timer)
|
||||||
|
resolve(msg)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
// Wait for swarm to link
|
||||||
|
await new Promise((r) => setTimeout(r, 3000))
|
||||||
|
a.sendChat('hello-from-alice')
|
||||||
|
|
||||||
|
const msg = await got
|
||||||
|
t.is(msg.from, 'Alice')
|
||||||
|
t.is(msg.text, 'hello-from-alice')
|
||||||
|
|
||||||
|
await a.close()
|
||||||
|
await b.close()
|
||||||
|
})
|
||||||
+5
-1
@@ -30,7 +30,11 @@ Not in the current design. Flying Squid targets **Java Edition** protocol.
|
|||||||
|
|
||||||
## Is chat end-to-end encrypted?
|
## Is chat end-to-end encrypted?
|
||||||
|
|
||||||
Peer links use Noise encryption (Hyperswarm/HyperDHT). Side-channel chat rides those links. In-game Minecraft chat is whatever Squid implements on the host world.
|
Peer links use Noise encryption (Hyperswarm/HyperDHT). Side-channel chat (CLI: type while hosting/joined) rides a separate Hyperswarm topic for that world. In-game Minecraft chat is local to the host Squid instance.
|
||||||
|
|
||||||
|
## Do I need Node.js for the released app?
|
||||||
|
|
||||||
|
**No.** Use the standalone binary from `make:standalone`, or install via Pear when published.
|
||||||
|
|
||||||
## Will this work on school/work Wi‑Fi?
|
## Will this work on school/work Wi‑Fi?
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user