[TSM.ID].[11031972] PXE : Platform X Ecosystem I [118 Module -LIVE-]
This commit is contained in:
@@ -0,0 +1,14 @@
|
||||
export type Currency = 'Rp' | 'USD' | 'Crypto';
|
||||
|
||||
export function formatCurrency(amountUSD: number, currency: Currency): string {
|
||||
if (currency === 'USD') {
|
||||
return new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(amountUSD);
|
||||
} else if (currency === 'Rp') {
|
||||
// 1 USD = 16000 Rp (Simulation)
|
||||
return new Intl.NumberFormat('id-ID', { style: 'currency', currency: 'IDR' }).format(amountUSD * 16000);
|
||||
} else if (currency === 'Crypto') {
|
||||
// 1 USD = 1 USDT
|
||||
return `${amountUSD.toFixed(2)} USDT`;
|
||||
}
|
||||
return amountUSD.toString();
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
import { useOmni } from "@/components/OmniSyncProvider";
|
||||
|
||||
export const translations = {
|
||||
en: {
|
||||
Index: {
|
||||
title: "Verified Access",
|
||||
subtitle: "JUMPA Global B2B Real-time Infrastructure",
|
||||
credentials: "Credentials",
|
||||
quantum_qr: "Quantum QR",
|
||||
email_placeholder: "Email Address",
|
||||
password_placeholder: "Password",
|
||||
persistent_session: "Persistent Session",
|
||||
reset_key: "Reset Key?",
|
||||
enter_multiverse: "Enter Multiverse",
|
||||
initialize_account: "Initialize Account",
|
||||
instant_sync: "Instant Sync Auth",
|
||||
scan_instructions: "Scan this quantum hash with your JUMPA Mobile App for zero-password entry.",
|
||||
new_to_multiverse: "New to the Multiverse?",
|
||||
access_now: "Access Now — It's Free",
|
||||
known_identity: "Known Identity?",
|
||||
sign_in: "Access Now",
|
||||
quantum_vault_sync: "Quantum Vault Synchronized"
|
||||
},
|
||||
Dashboard: {
|
||||
welcome: "Welcome to the Multiverse",
|
||||
mesh_sync: "Mesh Sync",
|
||||
active_nodes: "Active Nodes",
|
||||
launch_app: "Launch Application",
|
||||
engine_status: "Engine Status",
|
||||
realtime_telemetry: "Real-time Telemetry"
|
||||
},
|
||||
Billing: {
|
||||
title: "Billing Matrix",
|
||||
package: "Package",
|
||||
price: "Price",
|
||||
features: "Features",
|
||||
checkout: "Pay Now",
|
||||
currency: "Currency",
|
||||
payment_gateway: "Payment Gateway",
|
||||
subscription: "Subscription",
|
||||
upgrade: "Upgrade Plan"
|
||||
}
|
||||
},
|
||||
id: {
|
||||
Index: {
|
||||
title: "Akses Terverifikasi",
|
||||
subtitle: "Infrastruktur Real-time Global B2B JUMPA",
|
||||
credentials: "Kredensial",
|
||||
quantum_qr: "Quantum QR",
|
||||
email_placeholder: "Alamat Email",
|
||||
password_placeholder: "Kata Sandi",
|
||||
persistent_session: "Sesi Persisten",
|
||||
reset_key: "Reset Kunci?",
|
||||
enter_multiverse: "Masuk ke Multiverse",
|
||||
initialize_account: "Inisialisasi Akun",
|
||||
instant_sync: "Autentikasi Sinkron Instan",
|
||||
scan_instructions: "Pindai hash quantum ini dengan Aplikasi Mobile JUMPA Anda untuk akses tanpa kata sandi.",
|
||||
new_to_multiverse: "Baru di Multiverse?",
|
||||
access_now: "Akses Sekarang — Gratis",
|
||||
known_identity: "Identitas Dikenal?",
|
||||
sign_in: "Masuk Sekarang",
|
||||
quantum_vault_sync: "Vault Quantum Tersinkronisasi"
|
||||
},
|
||||
Dashboard: {
|
||||
welcome: "Selamat Datang di Multiverse",
|
||||
mesh_sync: "Sinkronisasi Mesh",
|
||||
active_nodes: "Node Aktif",
|
||||
launch_app: "Luncurkan Aplikasi",
|
||||
engine_status: "Status Mesin",
|
||||
realtime_telemetry: "Telemetri Real-time"
|
||||
},
|
||||
Billing: {
|
||||
title: "Matriks Penagihan",
|
||||
package: "Paket",
|
||||
price: "Harga",
|
||||
features: "Fitur",
|
||||
checkout: "Bayar Sekarang",
|
||||
currency: "Mata Uang",
|
||||
payment_gateway: "Gerbang Pembayaran",
|
||||
subscription: "Langganan",
|
||||
upgrade: "Tingkatkan Paket"
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export function useDictionary() {
|
||||
const { locale } = useOmni();
|
||||
|
||||
const t = (path: string) => {
|
||||
const keys = path.split(".");
|
||||
let current: any = translations[locale];
|
||||
|
||||
// Fallback logic
|
||||
if (!current && locale !== 'id') {
|
||||
current = translations['id'];
|
||||
}
|
||||
|
||||
for (const key of keys) {
|
||||
if (current[key] !== undefined) {
|
||||
current = current[key];
|
||||
} else {
|
||||
return path; // Return key string if missing
|
||||
}
|
||||
}
|
||||
return current;
|
||||
};
|
||||
|
||||
return { t };
|
||||
}
|
||||
@@ -0,0 +1,172 @@
|
||||
/**
|
||||
* QUANTUM CAPABILITY ORCHESTRATOR v2.0 (STRENGTHENED)
|
||||
*
|
||||
* Logic Tier tertinggi untuk orkestrasi JUMPA.ID Multiverse.
|
||||
* Menangani resolusi konflik, grace period, dan pemetaan 101 modul secara granular.
|
||||
*/
|
||||
|
||||
export type LicenseState = 'GRANTED' | 'UPSELL' | 'HIDDEN';
|
||||
|
||||
export interface QuantumCapabilities {
|
||||
status: 'ACTIVE' | 'SUSPENDED' | 'GRACE_PERIOD';
|
||||
|
||||
// VIDEO ENGINE (XCU)
|
||||
video: {
|
||||
codec: 'AV1' | 'HEVC' | 'VP9' | 'VP8';
|
||||
transport: 'MOQ' | 'WEBTRANSPORT' | 'WEBRTC';
|
||||
maxResolution: '4K' | '2K' | '1080p' | '720p';
|
||||
fps: 30 | 60 | 120;
|
||||
features: {
|
||||
svc: boolean;
|
||||
onPremGateway: boolean;
|
||||
ebpfBypass: boolean;
|
||||
autopilot: boolean;
|
||||
multicam: boolean;
|
||||
recording: boolean;
|
||||
};
|
||||
};
|
||||
|
||||
// CHAT ENGINE (XTM)
|
||||
chat: {
|
||||
encryption: 'E2EE_QUANTUM' | 'E2EE_STANDARD' | 'NONE';
|
||||
vaultStorageGB: number;
|
||||
retentionDays: number;
|
||||
features: {
|
||||
omniBrainAI: boolean;
|
||||
selfDestruct: boolean;
|
||||
biometric: boolean;
|
||||
fileLarge: boolean;
|
||||
};
|
||||
};
|
||||
|
||||
// IDENTITY & ACCESS (IAM)
|
||||
iam: {
|
||||
branding: {
|
||||
enabled: boolean;
|
||||
customDomain: boolean;
|
||||
};
|
||||
security: {
|
||||
sso: boolean;
|
||||
ipWhitelist: boolean;
|
||||
auditLog: boolean;
|
||||
killswitch: boolean;
|
||||
};
|
||||
};
|
||||
|
||||
// UI SOVEREIGNTY MATRIX (JVC/JC)
|
||||
// Dynamic Record: Mendukung tak terbatas injeksi DNA UI dari database tanpa hardcoding
|
||||
ui: Record<string, boolean>;
|
||||
}
|
||||
|
||||
export class QuantumOrchestrator {
|
||||
/**
|
||||
* Memperkuat Logika Resolusi Lisensi.
|
||||
* Menggabungkan Tenant-Level Policy dengan User-Level Overrides.
|
||||
*/
|
||||
static resolve(
|
||||
tenantLicenses: Record<string, string>,
|
||||
userLicenses: Record<string, string> = {},
|
||||
tenantIsActive: boolean = true
|
||||
): QuantumCapabilities {
|
||||
|
||||
// 1. Hard Override: Jika Tenant Suspended, semua fitur mati kecuali Killswitch
|
||||
if (!tenantIsActive) {
|
||||
return this.generateSuspendedState(tenantLicenses);
|
||||
}
|
||||
|
||||
// 2. Merge Logic: User license overrides tenant license unless tenant blocks it
|
||||
const merged = { ...tenantLicenses };
|
||||
for (const key in userLicenses) {
|
||||
if (tenantLicenses[key] !== 'HIDDEN') {
|
||||
merged[key] = userLicenses[key];
|
||||
}
|
||||
}
|
||||
|
||||
const isGranted = (key: string) => merged[key] === 'GRANTED';
|
||||
|
||||
return {
|
||||
status: 'ACTIVE',
|
||||
|
||||
video: {
|
||||
codec: isGranted('xcu.codec.av1') ? 'AV1' :
|
||||
isGranted('xcu.codec.hevc') ? 'HEVC' :
|
||||
isGranted('xcu.codec.vp9') ? 'VP9' : 'VP8',
|
||||
|
||||
transport: isGranted('xcu.transport.moq') ? 'MOQ' :
|
||||
isGranted('xcu.transport.quic') ? 'WEBTRANSPORT' : 'WEBRTC',
|
||||
|
||||
maxResolution: isGranted('xcu.codec.av1') ? '4K' :
|
||||
isGranted('xcu.codec.hevc') ? '2K' : '1080p',
|
||||
|
||||
fps: isGranted('xcu.feature.screenshare_high') ? 60 : 30,
|
||||
|
||||
features: {
|
||||
svc: isGranted('xcu.feature.svc'),
|
||||
onPremGateway: isGranted('xcu.feature.onprem'),
|
||||
ebpfBypass: isGranted('xcu.feature.ebpf'),
|
||||
autopilot: isGranted('xcu.feature.autopilot'),
|
||||
multicam: isGranted('xcu.feature.multicam'),
|
||||
recording: isGranted('xcu.feature.recording'),
|
||||
}
|
||||
},
|
||||
|
||||
chat: {
|
||||
encryption: isGranted('xtm.security.quantum') ? 'E2EE_QUANTUM' :
|
||||
isGranted('xtm.security.e2ee') ? 'E2EE_STANDARD' : 'NONE',
|
||||
|
||||
vaultStorageGB: isGranted('xtm.storage.vault') ? 100 : 5,
|
||||
retentionDays: isGranted('xtm.feature.persistence') ? 365 : 30,
|
||||
|
||||
features: {
|
||||
omniBrainAI: isGranted('xtm.ai.interceptor'),
|
||||
selfDestruct: isGranted('xtm.feature.burn'),
|
||||
biometric: isGranted('xtm.security.biometric'),
|
||||
fileLarge: isGranted('xtm.feature.file_large'),
|
||||
}
|
||||
},
|
||||
|
||||
iam: {
|
||||
branding: {
|
||||
enabled: isGranted('iam.feature.branding'),
|
||||
customDomain: isGranted('iam.feature.custom_domain'),
|
||||
},
|
||||
security: {
|
||||
sso: isGranted('iam.auth.sso'),
|
||||
ipWhitelist: isGranted('iam.security.ip_whitelist'),
|
||||
auditLog: isGranted('iam.security.audit_log'),
|
||||
killswitch: isGranted('iam.security.killswitch'),
|
||||
}
|
||||
},
|
||||
|
||||
ui: Object.keys(merged).filter(k => k.startsWith('jvc.ui.') || k.startsWith('ui.')).reduce((acc, key) => {
|
||||
// Expose dynamic UI features by removing the 'jvc.ui.' prefix for simpler frontend access if desired,
|
||||
// or just keep the full key. Using full key to avoid conflicts.
|
||||
acc[key] = isGranted(key);
|
||||
return acc;
|
||||
}, {} as Record<string, boolean>)
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* State Khusus untuk Tenant yang disuspensi (Kill-Switch Active)
|
||||
*/
|
||||
private static generateSuspendedState(licenses: Record<string, string>): QuantumCapabilities {
|
||||
return {
|
||||
status: 'SUSPENDED',
|
||||
video: { codec: 'VP8', transport: 'WEBRTC', maxResolution: '720p', fps: 30,
|
||||
features: { svc: false, onPremGateway: false, ebpfBypass: false, autopilot: false, multicam: false, recording: false }
|
||||
},
|
||||
chat: { encryption: 'NONE', vaultStorageGB: 0, retentionDays: 0,
|
||||
features: { omniBrainAI: false, selfDestruct: false, biometric: false, fileLarge: false }
|
||||
},
|
||||
iam: {
|
||||
branding: { enabled: false, customDomain: false },
|
||||
security: { sso: false, ipWhitelist: false, auditLog: false, killswitch: licenses['iam.security.killswitch'] === 'GRANTED' }
|
||||
},
|
||||
ui: Object.keys(licenses).filter(k => k.startsWith('jvc.ui.') || k.startsWith('ui.')).reduce((acc, key) => {
|
||||
acc[key] = false; // Always false in suspended mode
|
||||
return acc;
|
||||
}, {} as Record<string, boolean>)
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import Redis from 'ioredis';
|
||||
export const redis = new Redis(process.env.REDIS_URL || 'redis://localhost:6379');
|
||||
export async function getExchangeRate() {
|
||||
const cache = await redis.get('usd_idr_rate');
|
||||
if (cache) return parseFloat(cache);
|
||||
const rate = 15500;
|
||||
await redis.set('usd_idr_rate', rate, 'EX', 43200);
|
||||
return rate;
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
/**
|
||||
* XENDIT INDUSTRIAL BILLING ENGINE (X-IBE)
|
||||
*
|
||||
* Engine pembayaran murni tanpa dependensi pihak ketiga.
|
||||
* Menggunakan standar REST API Xendit v2 untuk Invoice & Webhooks.
|
||||
*/
|
||||
|
||||
const XENDIT_SECRET_KEY = process.env.XENDIT_SECRET_KEY || "";
|
||||
const XENDIT_ENDPOINT = "https://api.xendit.co/v2/invoices";
|
||||
|
||||
export interface XenditInvoiceRequest {
|
||||
external_id: string;
|
||||
amount: number;
|
||||
payer_email: string;
|
||||
description: string;
|
||||
success_redirect_url: string;
|
||||
failure_redirect_url: string;
|
||||
currency: string;
|
||||
items?: Array<{
|
||||
name: string;
|
||||
quantity: number;
|
||||
price: number;
|
||||
category?: string;
|
||||
}>;
|
||||
}
|
||||
|
||||
export class XenditEngine {
|
||||
private static getAuthHeader() {
|
||||
// Xendit requires Basic Auth with secret key as username and empty password
|
||||
const auth = Buffer.from(`${XENDIT_SECRET_KEY}:`).toString('base64');
|
||||
return `Basic ${auth}`;
|
||||
}
|
||||
|
||||
static async createInvoice(req: XenditInvoiceRequest) {
|
||||
if (!XENDIT_SECRET_KEY) {
|
||||
console.warn("[XENDIT] Secret Key missing. Using Sandbox Mode (Simulated URL).");
|
||||
return {
|
||||
id: "sim_inv_" + Date.now(),
|
||||
invoice_url: `${req.success_redirect_url}?sim_status=PAID&external_id=${req.external_id}`
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(XENDIT_ENDPOINT, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Authorization': this.getAuthHeader(),
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
...req,
|
||||
should_send_email: true,
|
||||
payment_methods: ["BNI", "BRI", "MANDIRI", "BCA", "PERMATA", "OVO", "DANA", "QRIS"]
|
||||
})
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
if (!response.ok) {
|
||||
throw new Error(data.message || "Xendit API Error");
|
||||
}
|
||||
|
||||
return data; // returns { id, invoice_url, status, etc }
|
||||
} catch (error) {
|
||||
console.error("[XENDIT ERROR]", error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
static async getInvoice(invoiceId: string) {
|
||||
try {
|
||||
const response = await fetch(`${XENDIT_ENDPOINT}/${invoiceId}`, {
|
||||
headers: { 'Authorization': this.getAuthHeader() }
|
||||
});
|
||||
return await response.json();
|
||||
} catch (error) {
|
||||
console.error("[XENDIT GET ERROR]", error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
/**
|
||||
* THE ZERO-SOCKET ENGINE (FASE 2)
|
||||
* Pengganti murni untuk socket.io-client tanpa dependensi pihak ketiga.
|
||||
* Ini mem-bypass NodeJS WebSockets dan menggunakan Next.js SSE (Server-Sent Events) + Fetch API,
|
||||
* memastikan performa mentah HTTP/2 murni tanpa overhead WebSocket ping/pong.
|
||||
*/
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
type EventCallback = (data: any) => void;
|
||||
|
||||
export class ZeroSocket {
|
||||
private url: string;
|
||||
private listeners: Record<string, EventCallback[]> = {};
|
||||
private eventSource: EventSource | null = null;
|
||||
private channel: string = "global_lobby";
|
||||
public id: string;
|
||||
|
||||
constructor(url: string) {
|
||||
this.url = url;
|
||||
// Generate random id simulating socket.id
|
||||
this.id = Math.random().toString(36).substring(2, 15);
|
||||
}
|
||||
|
||||
// Connect is called explicitly or implicitly
|
||||
connect() {
|
||||
if (this.eventSource) return;
|
||||
this.initSSE(this.channel);
|
||||
}
|
||||
|
||||
private initSSE(channelName: string) {
|
||||
if (this.eventSource) {
|
||||
this.eventSource.close();
|
||||
}
|
||||
|
||||
this.eventSource = new EventSource(`/api/omnibrain/sse?channel=${channelName}`);
|
||||
|
||||
this.eventSource.onmessage = (e) => {
|
||||
try {
|
||||
const data = JSON.parse(e.data);
|
||||
if (data.event && this.listeners[data.event]) {
|
||||
this.listeners[data.event].forEach(fn => fn(data.payload));
|
||||
}
|
||||
} catch (_) {
|
||||
// Abaikan parse error (mungkin keep-alive)
|
||||
}
|
||||
};
|
||||
|
||||
this.eventSource.onerror = () => {
|
||||
console.warn("[ZeroSocket] SSE Koneksi terputus, mencoba memulihkan otomatis...");
|
||||
};
|
||||
}
|
||||
|
||||
on(event: string, callback: EventCallback) {
|
||||
if (!this.listeners[event]) this.listeners[event] = [];
|
||||
this.listeners[event].push(callback);
|
||||
return this; // for chaining
|
||||
}
|
||||
|
||||
once(event: string, callback: EventCallback) {
|
||||
const onceWrapper: EventCallback = (data: unknown) => {
|
||||
callback(data);
|
||||
this.off(event, onceWrapper);
|
||||
};
|
||||
this.on(event, onceWrapper);
|
||||
return this;
|
||||
}
|
||||
|
||||
off(event: string, callback?: EventCallback) {
|
||||
if (!this.listeners[event]) return this;
|
||||
if (callback) {
|
||||
this.listeners[event] = this.listeners[event].filter(cb => cb !== callback);
|
||||
} else {
|
||||
delete this.listeners[event];
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
emit(event: string, payload: unknown = {}) {
|
||||
// Simulasi `socket.join(room)`
|
||||
// Di backend socket.io asli, `join` memasukkan client ke room. Di ZeroSocket, kita mengubah parameter channel SSE.
|
||||
if (event === "qr_auth_init") {
|
||||
const p = payload as { sessionId: string };
|
||||
this.channel = `qr_session_${p.sessionId}`;
|
||||
this.initSSE(this.channel);
|
||||
} else if (event === "guest_knock") {
|
||||
// Kita ganti channel menjadi lobby_ROOM agar bisa mendengarkan approval
|
||||
this.channel = `guest_${this.id}`;
|
||||
this.initSSE(this.channel);
|
||||
(payload as Record<string, unknown>).guestSocketId = this.id; // Sisipkan ID agar host tahu harus membalas kemana
|
||||
} else if (event === "register_user") {
|
||||
this.channel = `USER_${payload}`;
|
||||
this.initSSE(this.channel);
|
||||
}
|
||||
|
||||
// Fire & Forget HTTP POST ke Redis PubSub OmniBrain
|
||||
fetch('/api/omnibrain/emit', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
channel: this.channel,
|
||||
event,
|
||||
payload
|
||||
})
|
||||
}).catch(e => console.error("[ZeroSocket] Gagal memancarkan sinyal:", e));
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
disconnect() {
|
||||
if (this.eventSource) {
|
||||
this.eventSource.close();
|
||||
this.eventSource = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Named export 'io' untuk meniru "import { io } from 'socket.io-client'"
|
||||
export const io = (url?: string): ZeroSocket => {
|
||||
const socket = new ZeroSocket(url || "");
|
||||
socket.connect();
|
||||
return socket;
|
||||
};
|
||||
|
||||
// Export tipe
|
||||
export type Socket = ZeroSocket;
|
||||
|
||||
Reference in New Issue
Block a user