/**
* Hyperswarm Content Transport
*
* Multiplexes content-by-hash requests over Hyperswarm connections.
* Uses Protomux to multiplex alongside Hypercore replication.
*
* Protocol:
* - Request: { type: 'content-request', hash, requestId }
* - Response: { type: 'content-response', hash, content, requestId, found }
*/
import Protomux from 'protomux'
import c from 'compact-encoding'
// JSON encoding for messages
const jsonEncoding = {
preencode (state, m) {
c.string.preencode(state, JSON.stringify(m))
},
encode (state, m) {
c.string.encode(state, JSON.stringify(m))
},
decode (state) {
return JSON.parse(c.string.decode(state))
}
}
export class HyperswarmContentTransport {
constructor(swarm, contentSyncManager) {
this.swarm = swarm
this.contentSync = contentSyncManager
this.channels = new Map() // conn -> channel
// Register this transport with content sync manager
this.contentSync.registerTransport(
'hyperswarm',
this.sendRequest.bind(this),
this.hasActiveConnections.bind(this)
)
console.log('HyperswarmContentTransport: initialized')
}
/**
* Attach to a Hyperswarm connection
* Sets up Protomux channel for content sync protocol
*/
attachToConnection(conn) {
console.log('HyperswarmContentTransport: attaching to connection')
try {
// Get or create Protomux for this connection
const mux = Protomux.from(conn)
console.log('HyperswarmContentTransport: got Protomux instance')
// Open a channel for our content sync protocol
const channel = mux.createChannel({
protocol: 'wirecore-content-sync',
id: null,
handshake: c.raw,
onopen: (handshake) => {
console.log('HyperswarmContentTransport: channel opened successfully!')
},
onclose: () => {
console.log('HyperswarmContentTransport: channel closed')
this.channels.delete(conn)
},
ondestroy: () => {
console.log('HyperswarmContentTransport: channel destroyed')
this.channels.delete(conn)
}
})
if (!channel) {
console.error('HyperswarmContentTransport: failed to create channel')
return
}
console.log('HyperswarmContentTransport: channel created, adding message handler')
// Add message handlers
channel.addMessage({
encoding: jsonEncoding,
onmessage: (msg) => {
console.log('HyperswarmContentTransport: received message:', msg.type)
this.handleMessage(msg, conn)
}
})
// Store channel for sending messages
this.channels.set(conn, channel)
// Open the channel with empty handshake
console.log('HyperswarmContentTransport: opening channel...')
channel.open(Buffer.alloc(0))
// Cleanup on connection close
conn.once('close', () => {
console.log('HyperswarmContentTransport: connection closed')
this.channels.delete(conn)
})
conn.once('error', (err) => {
console.error('HyperswarmContentTransport: connection error:', err.message)
this.channels.delete(conn)
})
} catch (err) {
console.error('HyperswarmContentTransport: error in attachToConnection:', err)
}
}
/**
* Handle incoming message
*/
async handleMessage(msg, conn) {
if (!msg || !msg.type) {
console.error('HyperswarmContentTransport: invalid message format')
return
}
try {
if (msg.type === 'content-request') {
await this.handleContentRequest(msg, conn)
} else if (msg.type === 'content-response') {
await this.handleContentResponse(msg)
} else {
console.warn('HyperswarmContentTransport: unknown message type:', msg.type)
}
} catch (err) {
console.error('HyperswarmContentTransport: message handling error:', err.message)
}
}
/**
* Handle incoming content request
*/
async handleContentRequest(msg, conn) {
const { hash, requestId } = msg
if (!hash || !requestId) {
console.error('HyperswarmContentTransport: invalid content-request')
return
}
// Ask content sync manager to handle the request
await this.contentSync.handleContentRequest(
hash,
requestId,
(hash, content, requestId) => {
this.sendResponse(conn, hash, content, requestId)
}
)
}
/**
* Handle incoming content response
*/
async handleContentResponse(msg) {
const { hash, content, requestId, found } = msg
if (!hash || !requestId) {
console.error('HyperswarmContentTransport: invalid content-response')
return
}
// Forward to content sync manager
await this.contentSync.handleContentResponse(hash, content, requestId)
}
/**
* Send content request to all active connections
*/
async sendRequest(hash, requestId) {
const message = {
type: 'content-request',
hash,
requestId
}
// Send to all active channels
for (const [conn, channel] of this.channels) {
try {
if (channel.opened) {
channel.messages[0].send(message)
}
} catch (err) {
console.error('HyperswarmContentTransport: failed to send request:', err.message)
}
}
}
/**
* Send content response to a specific connection
*/
async sendResponse(conn, hash, content, requestId) {
const message = {
type: 'content-response',
hash,
content: content || null,
found: !!content,
requestId
}
try {
const channel = this.channels.get(conn)
if (channel && channel.opened) {
channel.messages[0].send(message)
}
} catch (err) {
console.error('HyperswarmContentTransport: failed to send response:', err.message)
}
}
/**
* Check if we have any active connections
*/
hasActiveConnections() {
return this.channels.size > 0
}
/**
* Get connection count
*/
getConnectionCount() {
return this.channels.size
}
}
export default HyperswarmContentTransport