summaryrefslogtreecommitdiff
path: root/client/client.ts
diff options
context:
space:
mode:
authorGravatar moon <moon@coffin.ir> 2025-09-14 13:47:32 +0330
committerGravatar moon <moon@coffin.ir> 2025-09-14 13:47:32 +0330
commitf7485f452360beae96280fd212f52c7904871784 (patch)
treec44e61fbcdc5c0b856fd07959f5440ea6b249bb9 /client/client.ts
parentts + py clients (diff)
moved to 3rd party
Diffstat (limited to 'client/client.ts')
-rw-r--r--client/client.ts674
1 files changed, 0 insertions, 674 deletions
diff --git a/client/client.ts b/client/client.ts
deleted file mode 100644
index 4f6031a..0000000
--- a/client/client.ts
+++ /dev/null
@@ -1,674 +0,0 @@
-// moonpaste-client.ts
-import { dlopen, FFIType, JSCallback, suffix } from 'bun:ffi'
-import * as path from 'path'
-import * as fs from 'fs'
-
-// Type definitions
-export interface RelayNeighbor {
- active: boolean
- hasIpv4: boolean
- hasIpv6: boolean
- v4Addr: Uint8Array // 4 bytes
- v6Addr: Uint8Array // 16 bytes
-}
-
-export interface RelayCapabilities {
- retentionHours: number
- canStore: boolean
- leadingZeros: number
- acceptsPlain: boolean
-}
-
-export interface RelayStatistics {
- neighbors: number
- heldPackets: bigint
- quickConnect: RelayNeighbor[] // 4 elements
-}
-
-export interface RelayInfo {
- majorVersion: number
- minorVersion: number
- motd: string // 128 bytes max
- capabilities: RelayCapabilities
- statistics: RelayStatistics
-}
-
-export interface DataPacketContents {
- encrypted: boolean
- createdTimeMs: bigint
- author: Uint8Array // 32 bytes
- authorSigningPub: Uint8Array // 32 bytes
- recipientCount: number
- recipients: Uint8Array[] // 32 arrays of 32 bytes each
- data: Uint8Array // 65536 bytes
- signature: Uint8Array // 64 bytes
-}
-
-export interface DataPacket {
- nonce: number
- hash: Uint8Array // 32 bytes
- contents: DataPacketContents
-}
-
-export interface KeyPair {
- encryptionPub: Uint8Array // 32 bytes
- encryptionPriv: Uint8Array // 32 bytes
- signingPub: Uint8Array // 32 bytes
- signingPriv: Uint8Array // 64 bytes
-}
-
-export interface DataPacketList {
- packets: DataPacket[]
- count: number
- newOffset: bigint
-}
-
-export enum ClientError {
- Success = 0,
- NetworkFail = 1,
- InvalidAddress = 2,
- ConnectionFailed = 3,
- SendFailed = 4,
- RecvFailed = 5,
- ServerBusy = 6,
- InvalidResponse = 7,
- DeserializeFailed = 8,
- AllocFailed = 9,
- FileNotFound = 10,
- FileReadFailed = 11,
- BadArgument = 12
-}
-
-function getRuntime(): 'bun' | 'deno' | 'node' {
- if (typeof Bun !== 'undefined') return 'bun'
- if (typeof Deno !== 'undefined') return 'deno'
- return 'node'
-}
-
-function findLibrary(): string {
- const runtime = getRuntime()
- const libNames = ['libmpclient.so', 'libmpclient.dylib', 'libmpclient.dll']
- const searchPaths = [
- './',
- './lib/',
- '../lib/',
- '/usr/local/lib/',
- '/usr/lib/',
- process.env.MOONPASTE_LIB_PATH || ''
- ].filter(p => p.length > 0)
-
- for (const searchPath of searchPaths) {
- for (const libName of libNames) {
- const fullPath = path.join(searchPath, libName)
- if (fs.existsSync(fullPath)) {
- return fullPath
- }
- }
- }
-
- throw new Error('Could not find moonpaste client library. Searched for: ' + libNames.join(', '))
-}
-
-class MoonpasteFFI {
- private lib: any
- private runtime: string
-
- constructor() {
- this.runtime = getRuntime()
- const libPath = findLibrary()
-
- switch (this.runtime) {
- case 'bun':
- this.lib = dlopen(libPath, {
- mp_get_server_info: {
- args: [FFIType.cstring, FFIType.i32, FFIType.ptr],
- returns: FFIType.i32
- },
- mp_get_server_neighbors: {
- args: [FFIType.cstring, FFIType.i32, FFIType.u8, FFIType.ptr, FFIType.ptr],
- returns: FFIType.i32
- },
- mp_push_packet: {
- args: [FFIType.cstring, FFIType.i32, FFIType.ptr, FFIType.ptr],
- returns: FFIType.i32
- },
- mp_get_packet: {
- args: [FFIType.cstring, FFIType.i32, FFIType.ptr, FFIType.ptr],
- returns: FFIType.i32
- },
- mp_get_firehose_packets: {
- args: [FFIType.cstring, FFIType.i32, FFIType.u64, FFIType.u16, FFIType.ptr],
- returns: FFIType.i32
- },
- mp_create_packet_from_bytes: {
- args: [FFIType.ptr, FFIType.usize, FFIType.u8, FFIType.ptr, FFIType.ptr],
- returns: FFIType.ptr
- },
- mp_create_packet_from_file: {
- args: [FFIType.cstring, FFIType.u8, FFIType.ptr, FFIType.ptr, FFIType.ptr],
- returns: FFIType.i32
- },
- mp_run_pow: {
- args: [FFIType.ptr, FFIType.u8],
- returns: FFIType.ptr
- },
- mp_generate_keypair: {
- args: [],
- returns: FFIType.ptr
- }
- })
- break
-
- case 'deno':
- this.lib = Deno.dlopen(libPath, {
- mp_get_server_info: {
- parameters: ['buffer', 'i32', 'pointer'],
- result: 'i32'
- },
- mp_get_server_neighbors: {
- parameters: ['buffer', 'i32', 'u8', 'pointer', 'pointer'],
- result: 'i32'
- },
- mp_push_packet: {
- parameters: ['buffer', 'i32', 'pointer', 'pointer'],
- result: 'i32'
- },
- mp_get_packet: {
- parameters: ['buffer', 'i32', 'pointer', 'pointer'],
- result: 'i32'
- },
- mp_get_firehose_packets: {
- parameters: ['buffer', 'i32', 'u64', 'u16', 'pointer'],
- result: 'i32'
- },
- mp_create_packet_from_bytes: {
- parameters: ['pointer', 'usize', 'u8', 'pointer', 'pointer'],
- result: 'pointer'
- },
- mp_create_packet_from_file: {
- parameters: ['buffer', 'u8', 'pointer', 'pointer', 'pointer'],
- result: 'i32'
- },
- mp_run_pow: {
- parameters: ['pointer', 'u8'],
- result: 'pointer'
- },
- mp_generate_keypair: {
- parameters: [],
- result: 'pointer'
- }
- })
- break
-
- case 'node':
- try {
- const ffi = require('ffi-napi') // dependency
- const ref = require('ref-napi') // dependency
-
- this.lib = ffi.Library(libPath, {
- mp_get_server_info: ['int', ['string', 'int', 'pointer']],
- mp_get_server_neighbors: ['int', ['string', 'int', 'uint8', 'pointer', 'pointer']],
- mp_push_packet: ['int', ['string', 'int', 'pointer', 'pointer']],
- mp_get_packet: ['int', ['string', 'int', 'pointer', 'pointer']],
- mp_get_firehose_packets: ['int', ['string', 'int', 'uint64', 'uint16', 'pointer']],
- mp_create_packet_from_bytes: ['pointer', ['pointer', 'size_t', 'uint8', 'pointer', 'pointer']],
- mp_create_packet_from_file: ['int', ['string', 'uint8', 'pointer', 'pointer', 'pointer']],
- mp_run_pow: ['pointer', ['pointer', 'uint8']],
- mp_generate_keypair: ['pointer', []]
- })
- } catch (e) {
- throw new Error('Node.js FFI requires ffi-napi and ref-napi packages: npm install ffi-napi ref-napi')
- }
- break
- }
- }
-
- private allocateBuffer(size: number): any {
- switch (this.runtime) {
- case 'bun':
- return new ArrayBuffer(size)
- case 'deno':
- return new ArrayBuffer(size)
- case 'node':
- const ref = require('ref-napi')
- return ref.alloc(size)
- }
- }
-
- private stringToBuffer(str: string): any {
- switch (this.runtime) {
- case 'bun':
- case 'deno':
- return new TextEncoder().encode(str + '\0')
- case 'node':
- return Buffer.from(str + '\0', 'utf8')
- }
- }
-
- async getServerInfo(ip: string, port: number): Promise<RelayInfo> {
- const infoBuffer = this.allocateBuffer(1024)
- const ipBuffer = this.stringToBuffer(ip)
-
- const result = this.lib.symbols.mp_get_server_info(ipBuffer, port, infoBuffer)
-
- if (result !== ClientError.Success) {
- throw new Error(`Failed to get server info: ${result}`)
- }
-
- return this.parseRelayInfo(infoBuffer)
- }
-
- async getServerNeighbors(ip: string, port: number, count: number): Promise<RelayNeighbor[]> {
- const neighborsPtr = this.allocateBuffer(8) // pointer
- const countPtr = this.allocateBuffer(1) // uint8
- const ipBuffer = this.stringToBuffer(ip)
-
- const result = this.lib.symbols.mp_get_server_neighbors(ipBuffer, port, count, neighborsPtr, countPtr)
-
- if (result !== ClientError.Success) {
- throw new Error(`Failed to get neighbors: ${result}`)
- }
-
- return this.parseNeighbors(neighborsPtr, countPtr)
- }
-
- async pushPacket(ip: string, port: number, packet: DataPacket): Promise<boolean> {
- const successPtr = this.allocateBuffer(1)
- const ipBuffer = this.stringToBuffer(ip)
- const packetBuffer = this.serializePacket(packet)
-
- const result = this.lib.symbols.mp_push_packet(ipBuffer, port, packetBuffer, successPtr)
-
- if (result !== ClientError.Success) {
- throw new Error(`Failed to push packet: ${result}`)
- }
-
- return this.readBool(successPtr)
- }
-
- async getPacket(ip: string, port: number, hash: Uint8Array): Promise<DataPacket> {
- const packetBuffer = this.allocateBuffer(70000) // approximation
- const ipBuffer = this.stringToBuffer(ip)
-
- const result = this.lib.symbols.mp_get_packet(ipBuffer, port, hash, packetBuffer)
-
- if (result !== ClientError.Success) {
- throw new Error(`Failed to get packet: ${result}`)
- }
-
- return this.parseDataPacket(packetBuffer)
- }
-
- async getFirehosePackets(ip: string, port: number, offset: bigint, count: number): Promise<DataPacketList> {
- const listBuffer = this.allocateBuffer(1024)
- const ipBuffer = this.stringToBuffer(ip)
-
- const result = this.lib.symbols.mp_get_firehose_packets(ipBuffer, port, offset, count, listBuffer)
-
- if (result !== ClientError.Success) {
- throw new Error(`Failed to get firehose packets: ${result}`)
- }
-
- return this.parseDataPacketList(listBuffer)
- }
-
- createPacketFromBytes(input: Uint8Array, recipientCount: number, recipients: Uint8Array[] | null, authorKeys: KeyPair): DataPacket {
- const recipientsBuffer = recipients ? this.serializeRecipients(recipients) : null
- const keysBuffer = this.serializeKeyPair(authorKeys)
-
- const result = this.lib.symbols.mp_create_packet_from_bytes(
- input,
- input.length,
- recipientCount,
- recipientsBuffer,
- keysBuffer
- )
-
- return this.parseDataPacket(result)
- }
-
- async createPacketFromFile(filename: string, recipientCount: number, recipients: Uint8Array[] | null, authorKeys: KeyPair): Promise<DataPacket> {
- const packetBuffer = this.allocateBuffer(4096)
- const filenameBuffer = this.stringToBuffer(filename)
- const recipientsBuffer = recipients ? this.serializeRecipients(recipients) : null
- const keysBuffer = this.serializeKeyPair(authorKeys)
-
- const result = this.lib.symbols.mp_create_packet_from_file(
- filenameBuffer,
- recipientCount,
- recipientsBuffer,
- keysBuffer,
- packetBuffer
- )
-
- if (result !== ClientError.Success) {
- throw new Error(`Failed to create packet from file: ${result}`)
- }
-
- return this.parseDataPacket(packetBuffer)
- }
-
- runProofOfWork(packet: DataPacket, leadingZeros: number): DataPacket {
- const packetBuffer = this.serializePacket(packet)
- const result = this.lib.symbols.mp_run_pow(packetBuffer, leadingZeros)
- return this.parseDataPacket(result)
- }
-
- generateKeyPair(): KeyPair {
- const result = this.lib.symbols.mp_generate_keypair()
- return this.parseKeyPair(result)
- }
-
- private parseRelayInfo(buffer: any): RelayInfo {
- const view = new DataView(buffer instanceof ArrayBuffer ? buffer : buffer.buffer)
- let offset = 0
-
- const majorVersion = view.getUint8(offset++)
- const minorVersion = view.getUint8(offset++)
-
- const motdBytes = new Uint8Array(buffer, offset, 128)
- const motd = new TextDecoder().decode(motdBytes).replace(/\0.*$/, '')
- offset += 128
-
- const retentionHours = view.getUint16(offset, true) // little endian
- offset += 2
- const canStore = view.getUint8(offset++) !== 0
- const leadingZeros = view.getUint8(offset++)
- const acceptsPlain = view.getUint8(offset++) !== 0
-
- const neighbors = view.getUint16(offset, true)
- offset += 2
- const heldPackets = view.getBigUint64(offset, true)
- offset += 8
-
- const quickConnect: RelayNeighbor[] = []
- for (let i = 0; i < 4; i++) {
- quickConnect.push(this.parseRelayNeighborAt(buffer, offset))
- offset += 23 // 1 + 1 + 1 + 4 + 16
- }
-
- return {
- majorVersion,
- minorVersion,
- motd,
- capabilities: {
- retentionHours,
- canStore,
- leadingZeros,
- acceptsPlain
- },
- statistics: {
- neighbors,
- heldPackets,
- quickConnect
- }
- }
- }
-
- private parseRelayNeighborAt(buffer: any, offset: number): RelayNeighbor {
- const view = new DataView(buffer instanceof ArrayBuffer ? buffer : buffer.buffer)
-
- const active = view.getUint8(offset) !== 0
- const hasIpv4 = view.getUint8(offset + 1) !== 0
- const hasIpv6 = view.getUint8(offset + 2) !== 0
-
- const v4Addr = new Uint8Array(buffer, offset + 3, 4)
- const v6Addr = new Uint8Array(buffer, offset + 7, 16)
-
- return { active, hasIpv4, hasIpv6, v4Addr, v6Addr }
- }
-
- private parseNeighbors(neighborsPtr: any, countPtr: any): RelayNeighbor[] {
- const count = this.readUint8(countPtr)
- const neighbors: RelayNeighbor[] = []
-
- if (count > 0 && neighborsPtr) {
- for (let i = 0; i < count; i++) {
- const neighborOffset = i * 23 // RELAY_NEIGHBOR_WIRE_LEN
- neighbors.push(this.parseRelayNeighborAt(neighborsPtr, neighborOffset))
- }
- }
-
- return neighbors
- }
-
- private parseDataPacket(buffer: any): DataPacket {
- const view = new DataView(buffer instanceof ArrayBuffer ? buffer : buffer.buffer)
- let offset = 0
-
- const nonce = view.getUint32(offset, true)
- offset += 4
-
- const hash = new Uint8Array(buffer, offset, 32)
- offset += 32
-
- const encrypted = view.getUint8(offset++) !== 0
- const createdTimeMs = view.getBigUint64(offset, true)
- offset += 8
-
- const author = new Uint8Array(buffer, offset, 32)
- offset += 32
- const authorSigningPub = new Uint8Array(buffer, offset, 32)
- offset += 32
-
- const recipientCount = view.getUint8(offset++)
- const recipients: Uint8Array[] = []
- for (let i = 0; i < 32; i++) {
- recipients.push(new Uint8Array(buffer, offset, 32))
- offset += 32
- }
-
- const data = new Uint8Array(buffer, offset, 65536)
- offset += 65536
-
- const signature = new Uint8Array(buffer, offset, 64)
-
- return {
- nonce,
- hash,
- contents: {
- encrypted,
- createdTimeMs,
- author,
- authorSigningPub,
- recipientCount,
- recipients,
- data,
- signature
- }
- }
- }
-
- private parseDataPacketList(buffer: any): DataPacketList {
- const view = new DataView(buffer instanceof ArrayBuffer ? buffer : buffer.buffer)
-
- const newOffset = view.getBigUint64(0, true)
- const availableBytes = buffer.byteLength - 8
- const packetSize = 4 + 32 + (1 + 8 + 32 + 32 + 1 + (32 * 32) + 65536 + 64) // PACKET_SIZE
- const packetCount = Math.floor(availableBytes / packetSize)
-
- const packets: DataPacket[] = []
- for (let i = 0; i < packetCount; i++) {
- const packetOffset = 8 + (i * packetSize)
- const packetBuffer = buffer.slice(packetOffset, packetOffset + packetSize)
- packets.push(this.parseDataPacket(packetBuffer))
- }
-
- return {
- packets,
- count: packetCount,
- newOffset
- }
- }
-
- private parseKeyPair(buffer: any): KeyPair {
- const encryptionPub = new Uint8Array(buffer, 0, 32)
- const encryptionPriv = new Uint8Array(buffer, 32, 32)
- const signingPub = new Uint8Array(buffer, 64, 32)
- const signingPriv = new Uint8Array(buffer, 96, 64)
-
- return {
- encryptionPub,
- encryptionPriv,
- signingPub,
- signingPriv
- }
- }
-
- private serializePacket(packet: DataPacket): any {
- const packetSize = 4 + 32 + (1 + 8 + 32 + 32 + 1 + (32 * 32) + 65536 + 64)
- const buffer = new ArrayBuffer(packetSize)
- const view = new DataView(buffer)
- let offset = 0
-
- view.setUint32(offset, packet.nonce, true)
- offset += 4
-
- new Uint8Array(buffer, offset, 32).set(packet.hash)
- offset += 32
-
- view.setUint8(offset++, packet.contents.encrypted ? 1 : 0)
- view.setBigUint64(offset, packet.contents.createdTimeMs, true)
- offset += 8
-
- new Uint8Array(buffer, offset, 32).set(packet.contents.author)
- offset += 32
- new Uint8Array(buffer, offset, 32).set(packet.contents.authorSigningPub)
- offset += 32
-
- view.setUint8(offset++, packet.contents.recipientCount)
- for (let i = 0; i < 32; i++) {
- if (i < packet.contents.recipients.length) {
- new Uint8Array(buffer, offset, 32).set(packet.contents.recipients[i])
- }
- offset += 32
- }
-
- new Uint8Array(buffer, offset, 65536).set(packet.contents.data)
- offset += 65536
-
- new Uint8Array(buffer, offset, 64).set(packet.contents.signature)
-
- return buffer
- }
-
- private serializeRecipients(recipients: Uint8Array[]): any {
- const buffer = new ArrayBuffer(32 * 32) // MAX_RECIPIENTS * 32 bytes each
- for (let i = 0; i < Math.min(recipients.length, 32); i++) {
- new Uint8Array(buffer, i * 32, 32).set(recipients[i])
- }
- return buffer
- }
-
- private serializeKeyPair(keyPair: KeyPair): any {
- const buffer = new ArrayBuffer(32 + 32 + 32 + 64) // total keypair size
- let offset = 0
-
- new Uint8Array(buffer, offset, 32).set(keyPair.encryptionPub)
- offset += 32
- new Uint8Array(buffer, offset, 32).set(keyPair.encryptionPriv)
- offset += 32
- new Uint8Array(buffer, offset, 32).set(keyPair.signingPub)
- offset += 32
- new Uint8Array(buffer, offset, 64).set(keyPair.signingPriv)
-
- return buffer
- }
-
- private readBool(ptr: any): boolean {
- return this.readUint8(ptr) !== 0
- }
-
- private readUint8(ptr: any): number {
- switch (this.runtime) {
- case 'bun':
- case 'deno':
- return new Uint8Array(ptr)[0]
- case 'node':
- return ptr.readUInt8(0)
- default:
- return 0
- }
- }
-}
-
-// Main client class
-export class MoonpasteClient {
- private ffi: MoonpasteFFI
-
- constructor() {
- this.ffi = new MoonpasteFFI()
- }
-
- async getServerInfo(ip: string, port: number): Promise<RelayInfo> {
- return this.ffi.getServerInfo(ip, port)
- }
-
- async getNeighbors(ip: string, port: number, count: number = 5): Promise<RelayNeighbor[]> {
- return this.ffi.getServerNeighbors(ip, port, count)
- }
-
- async pushPacket(ip: string, port: number, packet: DataPacket): Promise<boolean> {
- return this.ffi.pushPacket(ip, port, packet)
- }
-
- async getPacket(ip: string, port: number, hash: Uint8Array): Promise<DataPacket> {
- return this.ffi.getPacket(ip, port, hash)
- }
-
- async getFirehosePackets(ip: string, port: number, offset: bigint = 0n, count: number = 10): Promise<DataPacketList> {
- return this.ffi.getFirehosePackets(ip, port, offset, count)
- }
-
- createPacket(message: string | Uint8Array, recipients?: Uint8Array[], authorKeys?: KeyPair): DataPacket {
- const data = typeof message === 'string' ? new TextEncoder().encode(message) : message
- const keys = authorKeys || this.generateKeyPair()
- const recipientCount = recipients ? recipients.length : 0
-
- return this.ffi.createPacketFromBytes(data, recipientCount, recipients || null, keys)
- }
-
- async createPacketFromFile(filename: string, recipients?: Uint8Array[], authorKeys?: KeyPair): Promise<DataPacket> {
- const keys = authorKeys || this.generateKeyPair()
- const recipientCount = recipients ? recipients.length : 0
-
- return this.ffi.createPacketFromFile(filename, recipientCount, recipients || null, keys)
- }
-
- runProofOfWork(packet: DataPacket, leadingZeros: number): DataPacket {
- return this.ffi.runProofOfWork(packet, leadingZeros)
- }
-
- generateKeyPair(): KeyPair {
- return this.ffi.generateKeyPair()
- }
-
- // Utility methods
- static hashToHex(hash: Uint8Array): string {
- return Array.from(hash).map(b => b.toString(16).padStart(2, '0')).join('')
- }
-
- static hexToHash(hex: string): Uint8Array {
- const result = new Uint8Array(32)
- for (let i = 0; i < 32; i++) {
- result[i] = parseInt(hex.substr(i * 2, 2), 16)
- }
- return result
- }
-
- static formatIpAddress(neighbor: RelayNeighbor): string {
- if (neighbor.hasIpv4) {
- return Array.from(neighbor.v4Addr).join('.')
- } else if (neighbor.hasIpv6) {
- const parts: string[] = []
- for (let i = 0; i < 8; i++) {
- const word = (neighbor.v6Addr[i * 2] << 8) | neighbor.v6Addr[i * 2 + 1]
- parts.push(word.toString(16))
- }
- return parts.join(':')
- }
- return 'N/A'
- }
-}
-
-export default MoonpasteClient \ No newline at end of file