Files
multiverse/jumpa-chat/lib/xcu-telepathy-matrix.ts

342 lines
12 KiB
TypeScript

/* eslint-disable */
// [TSM.ID].[11031972] -- All Rights Reserved. Proprietary & Confidential.
import CryptoJS from 'crypto-js';
export interface EncryptedMessage {
id: string;
sender: string;
ciphertext: string;
timestamp: number;
}
export interface DecryptedMessage {
id: string;
sender: string;
content: string;
timestamp: number;
status: string;
}
/**
* XCUTelepathyMatrix FASE 2:
* Telah berevolusi meninggalkan NodeJS, Yjs, dan WebSockets usang.
* Menggunakan arsitektur WebTransport Datagrams yang menembak langsung ke Rust Engine (Port 8443).
*/
export class XCUTelepathyMatrix {
private roomName: string;
private secretKey: string;
private transport: any = null; // WebTransport instance
private isActive: boolean = false;
private participantId: number;
private username: string = "";
public onMessagesUpdate: ((messages: DecryptedMessage[]) => void) | null = null;
public onTypingUpdate: ((typingUsers: Record<string, number>) => void) | null = null;
public onQuantumResonance: ((senderId: number, type: string) => void) | null = null;
public onSovereignSignal: ((command: string, targetId?: number) => void) | null = null;
// Local state untuk dirender
private messages: DecryptedMessage[] = [];
private typingState: Record<string, number> = {};
constructor(roomName: string, secretKey: string = 'QUANTUM-X-SECRET-256') {
this.roomName = roomName;
this.secretKey = secretKey;
this.participantId = Math.floor(Math.random() * 65534) + 1;
}
public async ignite(serverUrl: string, username: string) {
this.username = username;
this.isActive = true;
try {
// 1. Ekstrak host dari serverUrl
let host = window.location.hostname;
if (serverUrl && serverUrl !== "/") {
const urlObj = new URL(serverUrl);
host = urlObj.hostname;
}
const secureProto = window.location.protocol;
const wtUrl = `${secureProto}//${host}:8443/neural-link/${this.roomName}`;
console.log("[XTM] Menginisialisasi WebTransport ke:", wtUrl);
// 2. Setup WebTransport
// Gunakan any cast karena WebTransport mungkin belum diakui di semua tsconfig
const WT = (window as any).WebTransport;
if (!WT) {
throw new Error("WebTransport tidak didukung di browser ini!");
}
this.transport = new WT(wtUrl);
await this.transport.ready;
console.log("[XTM] Pipa WebTransport Kuantum TERHUBUNG!");
// 3. Mulai Membaca Datagrams
this.readDatagrams();
} catch (e) {
console.error("[XTM] Ignition Failed:", e);
// Fallback: Jika WebTransport diblokir firewall, XTM akan mengaktifkan SSE Relay (TBD)
}
}
private async readDatagrams() {
if (!this.transport || !this.transport.datagrams) return;
try {
const reader = this.transport.datagrams.readable.getReader();
while (this.isActive) {
const { value, done } = await reader.read();
if (done) break;
if (value && value.length >= 8) {
const type = value[0];
// byte 2-3 = participantId pengirim
const senderId = new DataView(value.buffer).getUint16(2, true);
if (senderId === this.participantId) continue;
const payload = value.slice(8);
if (type === 7) { // 7 = Chat Text
this.decryptAndPush(payload);
} else if (type === 8) { // 8 = Telepathic Resonance (Typing)
// Mendekripsi siapa yang mengetik
this.handleTypingResonance(payload);
} else if (type === 9) { // 9 = PKEPX Resonance (Emoji)
this.handleQuantumResonance(payload, senderId);
} else if (type === 10) { // 10 = PKEPX Sovereign Command
this.handleSovereignCommand(payload);
}
}
}
} catch (e) {
if (this.isActive) console.error("[XTM] Datagram Reader terputus:", e);
}
}
private async decryptAndPush(payload: Uint8Array) {
try {
// Convert Uint8Array to WordArray
const wordArr = CryptoJS.lib.WordArray.create(payload as any);
const ciphertext = CryptoJS.enc.Base64.stringify(wordArr);
const bytes = CryptoJS.AES.decrypt(ciphertext, this.secretKey);
const originalText = bytes.toString(CryptoJS.enc.Utf8);
if (originalText) {
const parsed = JSON.parse(originalText);
this.messages.push({
id: Math.random().toString(),
sender: parsed.sender,
content: parsed.text,
timestamp: parsed.timestamp,
status: 'delivered'
});
if (this.onMessagesUpdate) {
this.onMessagesUpdate([...this.messages]);
}
}
} catch (e) {
console.error("Gagal dekripsi pesan XTM", e);
}
}
private async handleTypingResonance(payload: Uint8Array) {
try {
const wordArr = CryptoJS.lib.WordArray.create(payload as any);
const ciphertext = CryptoJS.enc.Base64.stringify(wordArr);
const bytes = CryptoJS.AES.decrypt(ciphertext, this.secretKey);
const name = bytes.toString(CryptoJS.enc.Utf8);
if (name) {
this.typingState[name] = Date.now();
if (this.onTypingUpdate) this.onTypingUpdate({...this.typingState});
setTimeout(() => {
delete this.typingState[name];
if (this.onTypingUpdate) this.onTypingUpdate({...this.typingState});
}, 3000);
}
} catch(e) {}
}
// =====================================
// PKEPX ZOOM-KILLER: JUMPA CHAT PORT
// =====================================
private async handleQuantumResonance(payload: Uint8Array, senderId: number) {
try {
const wordArr = CryptoJS.lib.WordArray.create(payload as any);
const ciphertext = CryptoJS.enc.Base64.stringify(wordArr);
const bytes = CryptoJS.AES.decrypt(ciphertext, this.secretKey);
const reactionType = bytes.toString(CryptoJS.enc.Utf8);
if (reactionType && this.onQuantumResonance) {
this.onQuantumResonance(senderId, reactionType);
}
} catch(e) {}
}
private async handleSovereignCommand(payload: Uint8Array) {
try {
const wordArr = CryptoJS.lib.WordArray.create(payload as any);
const ciphertext = CryptoJS.enc.Base64.stringify(wordArr);
const bytes = CryptoJS.AES.decrypt(ciphertext, this.secretKey);
const cmdStr = bytes.toString(CryptoJS.enc.Utf8);
if (cmdStr && this.onSovereignSignal) {
const cmd = JSON.parse(cmdStr);
this.onSovereignSignal(cmd.command, cmd.targetId);
}
} catch(e) {}
}
public async emitResonance(reactionType: string) {
if (!this.transport || !this.transport.datagrams) return;
const ciphertext = CryptoJS.AES.encrypt(reactionType, this.secretKey).toString();
const encWordArr = CryptoJS.enc.Base64.parse(ciphertext);
const encPayload = new Uint8Array(encWordArr.sigBytes);
for (let i = 0; i < encWordArr.sigBytes; i++) {
encPayload[i] = (encWordArr.words[i >>> 2] >>> (24 - (i % 4) * 8)) & 0xff;
}
const header = new Uint8Array(8);
header[0] = 9; // Tipe 9 = Resonance
new DataView(header.buffer).setUint16(2, this.participantId, true);
const fullPacket = new Uint8Array(8 + encPayload.length);
fullPacket.set(header, 0);
fullPacket.set(encPayload, 8);
let writer: any = null;
try {
writer = this.transport.datagrams.writable.getWriter();
await writer.write(fullPacket);
if (this.onQuantumResonance) this.onQuantumResonance(this.participantId, reactionType);
} catch (e) {} finally {
if (writer) writer.releaseLock();
}
}
public async broadcastSovereignSignal(command: string, targetId?: number) {
if (!this.transport || !this.transport.datagrams) return;
const payloadStr = JSON.stringify({ command, targetId });
const ciphertext = CryptoJS.AES.encrypt(payloadStr, this.secretKey).toString();
const encWordArr = CryptoJS.enc.Base64.parse(ciphertext);
const encPayload = new Uint8Array(encWordArr.sigBytes);
for (let i = 0; i < encWordArr.sigBytes; i++) {
encPayload[i] = (encWordArr.words[i >>> 2] >>> (24 - (i % 4) * 8)) & 0xff;
}
const header = new Uint8Array(8);
header[0] = 10; // Tipe 10 = Sovereign
new DataView(header.buffer).setUint16(2, this.participantId, true);
const fullPacket = new Uint8Array(8 + encPayload.length);
fullPacket.set(header, 0);
fullPacket.set(encPayload, 8);
let writer: any = null;
try {
writer = this.transport.datagrams.writable.getWriter();
await writer.write(fullPacket);
} catch (e) {} finally {
if (writer) writer.releaseLock();
}
}
// Encrypt and send
public async sendMessage(sender: string, content: string) {
if (!this.transport || !this.transport.datagrams) {
// Fallback simpan ke local buffer jika belum konek
this.messages.push({
id: Math.random().toString(),
sender,
content,
timestamp: Date.now(),
status: 'failed_offline'
});
if (this.onMessagesUpdate) this.onMessagesUpdate([...this.messages]);
return;
}
const payloadStr = JSON.stringify({ sender, text: content, timestamp: Date.now() });
// E2EE: AES Encryption
const ciphertext = CryptoJS.AES.encrypt(payloadStr, this.secretKey).toString();
const encWordArr = CryptoJS.enc.Base64.parse(ciphertext);
// Convert WordArray to Uint8Array
const encPayload = new Uint8Array(encWordArr.sigBytes);
for (let i = 0; i < encWordArr.sigBytes; i++) {
encPayload[i] = (encWordArr.words[i >>> 2] >>> (24 - (i % 4) * 8)) & 0xff;
}
// Header
const header = new Uint8Array(8);
header[0] = 7; // Tipe 7 = Text Message
header[1] = 0;
new DataView(header.buffer).setUint16(2, this.participantId, true);
const fullPacket = new Uint8Array(8 + encPayload.length);
fullPacket.set(header, 0);
fullPacket.set(encPayload, 8);
let writer: any = null;
try {
writer = this.transport.datagrams.writable.getWriter();
await writer.write(fullPacket);
// Optimistic UI Update
this.messages.push({
id: Math.random().toString(),
sender,
content,
timestamp: Date.now(),
status: 'delivered'
});
if (this.onMessagesUpdate) this.onMessagesUpdate([...this.messages]);
} catch (e) {
console.error("Gagal mengirim pesan Kuantum:", e);
} finally {
if (writer) writer.releaseLock();
}
}
public async setTyping(username: string, charCount: number) {
if (!this.transport || !this.transport.datagrams) return;
const ciphertext = CryptoJS.AES.encrypt(username, this.secretKey).toString();
const encWordArr = CryptoJS.enc.Base64.parse(ciphertext);
const encPayload = new Uint8Array(encWordArr.sigBytes);
for (let i = 0; i < encWordArr.sigBytes; i++) {
encPayload[i] = (encWordArr.words[i >>> 2] >>> (24 - (i % 4) * 8)) & 0xff;
}
const header = new Uint8Array(8);
header[0] = 8; // Tipe 8 = Typing
new DataView(header.buffer).setUint16(2, this.participantId, true);
const fullPacket = new Uint8Array(8 + encPayload.length);
fullPacket.set(header, 0);
fullPacket.set(encPayload, 8);
let writer: any = null;
try {
writer = this.transport.datagrams.writable.getWriter();
await writer.write(fullPacket);
} catch (e) {} finally {
if (writer) writer.releaseLock();
}
}
public shutdown() {
this.isActive = false;
if (this.transport) {
try { this.transport.close(); } catch(e){}
}
}
}