diff options
| author | 2025-09-14 13:47:32 +0330 | |
|---|---|---|
| committer | 2025-09-14 13:47:32 +0330 | |
| commit | f7485f452360beae96280fd212f52c7904871784 (patch) | |
| tree | c44e61fbcdc5c0b856fd07959f5440ea6b249bb9 /client/client.ts | |
| parent | ts + py clients (diff) | |
moved to 3rd party
Diffstat (limited to 'client/client.ts')
| -rw-r--r-- | client/client.ts | 674 |
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 |
