import { ImapClient } from './imap-client.js'
import { SmtpClient } from './smtp-client.js'
import { KeetClient } from './keet-client.js'
import { Router } from './router.js'
import { emailToEnvelope, keetToEmail, emailToKeetText } from './adapter.js'
class EmailBridge {
constructor (opts = {}) {
this.imap = new ImapClient({
host: opts.imapHost,
port: opts.imapPort,
user: opts.user,
pass: opts.pass
})
this.smtp = new SmtpClient({
host: opts.smtpHost,
port: opts.smtpPort,
user: opts.user,
pass: opts.pass,
from: opts.from || opts.user
})
this.keet = new KeetClient({
host: opts.keetHost,
port: opts.keetPort
})
this.roomId = opts.roomId
this.forwardTo = opts.forwardTo // email address to relay Keet messages to
this.pollInterval = opts.pollInterval || 30000 // 30s
this.bridgeIdentity = opts.bridgeIdentity || null
this.onLog = opts.onLog || (() => {})
this._polling = false
this._pollTimer = null
this.router = new Router({
onKeetMessage: (author, text) => {
this.keet.send(this.roomId, text).catch(err => {
this.onLog(`Keet send error: ${err.message}`)
})
},
onExternalMessage: (author, text) => {
if (!this.forwardTo) return
this._sendEmail(author, text).catch(err => {
this.onLog(`Email send error: ${err.message}`)
})
}
})
}
async _sendEmail (author, text) {
const smtp = new SmtpClient({
host: this.smtp.host,
port: this.smtp.port,
user: this.smtp.user,
pass: this.smtp.pass,
from: this.smtp.from
})
await smtp.connect()
await smtp.ehlo()
await smtp.auth()
await smtp.sendMail(
this.forwardTo,
`[Keet] Message from ${author}`,
`${author} wrote:\n\n${text}\n\n---\nRelayed via bridge-email from Keet P2P chat`
)
await smtp.quit()
}
async start () {
// Get bridge identity
if (!this.bridgeIdentity) {
try {
const identity = await this.keet.identity()
this.bridgeIdentity = identity.memberId || identity.id || ''
this.onLog(`Bridge identity: ${this.bridgeIdentity}`)
} catch (err) {
this.onLog(`Warning: Could not get identity: ${err.message}`)
}
}
// Connect IMAP
await this.imap.connect()
await this.imap.login()
await this.imap.select('INBOX')
this.onLog(`IMAP: connected to ${this.imap.host}`)
// Start polling IMAP for new messages
this._polling = true
this._poll()
// Watch Keet → Email
this.keet.watch(this.roomId, (msg) => {
const result = keetToEmail(msg, this.bridgeIdentity)
if (!result) return
this.router.routeToExternal(result.author, result.text)
})
this.onLog(`Bridge started: Email (${this.imap.host}) ↔ Keet`)
}
async _poll () {
if (!this._polling) return
try {
const unseen = await this.imap.searchUnseen()
for (const id of unseen) {
try {
const email = await this.imap.fetch(id)
const envelope = emailToEnvelope(email)
if (envelope) {
const text = emailToKeetText(envelope)
this.router.routeToKeet(envelope.author, text)
await this.imap.markSeen(id)
}
} catch (err) {
this.onLog(`Fetch error for message ${id}: ${err.message}`)
}
}
} catch (err) {
this.onLog(`Poll error: ${err.message}`)
}
this._pollTimer = setTimeout(() => this._poll(), this.pollInterval)
}
stop () {
this._polling = false
if (this._pollTimer) {
clearTimeout(this._pollTimer)
this._pollTimer = null
}
this.keet.stopWatch()
this.imap.close()
this.onLog('Bridge stopped')
}
}
export { EmailBridge }