mirror of
https://github.com/permissionlesstech/bitchat.git
synced 2026-07-25 19:05:20 +00:00
122 lines
4.8 KiB
Swift
122 lines
4.8 KiB
Swift
import Foundation
|
||
|
||
// MARK: - BitChat-over-Nostr Adapter
|
||
|
||
struct NostrEmbeddedBitChat {
|
||
/// Build a `bitchat1:` base64url-encoded BitChat packet carrying a private message for Nostr DMs.
|
||
static func encodePMForNostr(content: String, messageID: String, recipientPeerID: PeerID, senderPeerID: PeerID) -> String? {
|
||
// TLV-encode the private message
|
||
let pm = PrivateMessagePacket(messageID: messageID, content: content)
|
||
guard let tlv = pm.encode() else { return nil }
|
||
|
||
// Prefix with NoisePayloadType
|
||
var payload = Data([NoisePayloadType.privateMessage.rawValue])
|
||
payload.append(tlv)
|
||
|
||
// Determine 8-byte recipient ID to embed
|
||
let recipientID = normalizeRecipientPeerID(recipientPeerID)
|
||
|
||
let packet = BitchatPacket(
|
||
type: MessageType.noiseEncrypted.rawValue,
|
||
senderID: Data(hexString: senderPeerID.id) ?? Data(),
|
||
recipientID: Data(hexString: recipientID.id),
|
||
timestamp: UInt64(Date().timeIntervalSince1970 * 1000),
|
||
payload: payload,
|
||
signature: nil,
|
||
ttl: 7
|
||
)
|
||
|
||
guard let data = packet.toBinaryData() else { return nil }
|
||
return "bitchat1:" + base64URLEncode(data)
|
||
}
|
||
|
||
/// Build a `bitchat1:` base64url-encoded BitChat packet carrying a delivery/read ack for Nostr DMs.
|
||
static func encodeAckForNostr(type: NoisePayloadType, messageID: String, recipientPeerID: PeerID, senderPeerID: PeerID) -> String? {
|
||
guard type == .delivered || type == .readReceipt else { return nil }
|
||
|
||
var payload = Data([type.rawValue])
|
||
payload.append(Data(messageID.utf8))
|
||
|
||
let recipientID = normalizeRecipientPeerID(recipientPeerID)
|
||
|
||
let packet = BitchatPacket(
|
||
type: MessageType.noiseEncrypted.rawValue,
|
||
senderID: Data(hexString: senderPeerID.id) ?? Data(),
|
||
recipientID: Data(hexString: recipientID.id),
|
||
timestamp: UInt64(Date().timeIntervalSince1970 * 1000),
|
||
payload: payload,
|
||
signature: nil,
|
||
ttl: 7
|
||
)
|
||
|
||
guard let data = packet.toBinaryData() else { return nil }
|
||
return "bitchat1:" + base64URLEncode(data)
|
||
}
|
||
|
||
/// Build a `bitchat1:` ACK (delivered/read) without an embedded recipient peer ID (geohash DMs).
|
||
static func encodeAckForNostrNoRecipient(type: NoisePayloadType, messageID: String, senderPeerID: PeerID) -> String? {
|
||
guard type == .delivered || type == .readReceipt else { return nil }
|
||
|
||
var payload = Data([type.rawValue])
|
||
payload.append(Data(messageID.utf8))
|
||
|
||
let packet = BitchatPacket(
|
||
type: MessageType.noiseEncrypted.rawValue,
|
||
senderID: Data(hexString: senderPeerID.id) ?? Data(),
|
||
recipientID: nil,
|
||
timestamp: UInt64(Date().timeIntervalSince1970 * 1000),
|
||
payload: payload,
|
||
signature: nil,
|
||
ttl: 7
|
||
)
|
||
|
||
guard let data = packet.toBinaryData() else { return nil }
|
||
return "bitchat1:" + base64URLEncode(data)
|
||
}
|
||
|
||
/// Build a `bitchat1:` payload without an embedded recipient peer ID (used for geohash DMs).
|
||
static func encodePMForNostrNoRecipient(content: String, messageID: String, senderPeerID: PeerID) -> String? {
|
||
let pm = PrivateMessagePacket(messageID: messageID, content: content)
|
||
guard let tlv = pm.encode() else { return nil }
|
||
|
||
var payload = Data([NoisePayloadType.privateMessage.rawValue])
|
||
payload.append(tlv)
|
||
|
||
let packet = BitchatPacket(
|
||
type: MessageType.noiseEncrypted.rawValue,
|
||
senderID: Data(hexString: senderPeerID.id) ?? Data(),
|
||
recipientID: nil,
|
||
timestamp: UInt64(Date().timeIntervalSince1970 * 1000),
|
||
payload: payload,
|
||
signature: nil,
|
||
ttl: 7
|
||
)
|
||
|
||
guard let data = packet.toBinaryData() else { return nil }
|
||
return "bitchat1:" + base64URLEncode(data)
|
||
}
|
||
|
||
private static func normalizeRecipientPeerID(_ recipientPeerID: PeerID) -> PeerID {
|
||
if let maybeData = Data(hexString: recipientPeerID.id) {
|
||
if maybeData.count == 32 {
|
||
// Treat as Noise static public key; derive peerID from fingerprint
|
||
return PeerID(publicKey: maybeData)
|
||
} else if maybeData.count == 8 {
|
||
// Already an 8-byte peer ID
|
||
return recipientPeerID
|
||
}
|
||
}
|
||
// Fallback: return as-is (expecting 16 hex chars) – caller should pass a valid peer ID
|
||
return recipientPeerID
|
||
}
|
||
|
||
/// Base64url encode without padding
|
||
private static func base64URLEncode(_ data: Data) -> String {
|
||
let b64 = data.base64EncodedString()
|
||
return b64
|
||
.replacingOccurrences(of: "+", with: "-")
|
||
.replacingOccurrences(of: "/", with: "_")
|
||
.replacingOccurrences(of: "=", with: "")
|
||
}
|
||
}
|