← NX8V Panduan
Server & Web

NX8V — Server & Web

Panduan lengkap membangun aplikasi web/server dengan NX8V.

Untuk aplikasi desktop (jendela WebView2, tray, dialog), lihat desktop.md. Untuk gambaran umum runtime, lihat README.md.

Daftar isi:

  1. Alur kerja
  2. Membuat proyek
  3. Struktur proyek
  4. HTTP server
  5. Router
  6. Static file server
  7. WebSocket server
  8. HTTP/HTTPS client + fetch
  9. Modul pendukung
  10. CLI server
  11. Deployment
  12. Contoh di repo
  13. Uji tiga server
  14. Catatan 24 Oktober 2026

1. Alur kerja

nx8v init            # buat proyek (project.json + server.js + public/)
nx8v dev             # development: simpan HTML memuat ulang browser
nx8v start           # production: jalankan dari project.json
nx8v logs            # lihat nx8v.log
nx8v stop            # hentikan server
nx8v package         # folder distribusi siap kirim

2. Membuat proyek

cd D:\proyek-saya
nx8v init
  name        [proyek-saya]: aplikasi-toko
  description [An NX8V application]: Aplikasi toko sederhana
  port        [3000]: 4000

Hasil:

D:\proyek-saya\
├── project.json          # "dependencies": {}
├── server.js
├── modules\              # folder modul, masih kosong
└── public\
    ├── index.html
    ├── about.html
    └── assets\
        ├── css\style.css
        ├── js\app.js
        ├── fonts\
        └── images\32x32.png    # ikon merek NX8V

Folder assets tetap ada. CSS, JS, font, dan gambar tinggal di dalamnya. Di HTML pathnya ditulis pendek: /css/style.css, /js/app.js, dan /images/32x32.png. NX8V mencari berkas itu di public/ dulu, lalu di public/assets/, sebelum server.js dijalankan.

Halaman juga memasang <link rel="icon" href="/images/32x32.png">. Permintaan otomatis /favicon.ico memakai ikon yang sama bila proyek belum punya favicon.ico sendiri.

project.json:

{
  "name": "aplikasi-toko",
  "version": "1.0.0",
  "description": "Aplikasi toko sederhana",
  "main": "server.js",
  "nx8v": {
    "port": 4000,
    "desktop": {
      "icon": "./images/32x32.png",
      "state": "normal",
      "width": 800,
      "height": 560,
      "minWidth": 640,
      "minHeight": 400,
      "center": true,
      "maximizable": true,
      "resizable": true,
      "titlebar": true,
      "devtools": false,
      "singleInstance": true,
      "productName": "aplikasi-toko",
      "shortcutName": "aplikasi-toko",
      "artifactName": "aplikasi-toko-Setup-${version}.exe"
    }
  },
  "dependencies": {},
  "repository": {
    "type": "git",
    "url": "https://github.com/nx8v/dependencies.git"
  },
  "scripts": {
    "start": "nx8v start",
    "dev": "nx8v dev",
    "stop": "nx8v stop"
  }
}

"icon": "./images/32x32.png" menunjuk berkas public/assets/images/32x32.png. Path panjang seperti ./public/assets/images/32x32.png tetap berlaku bila berkasnya ada di situ.

Port bisa dioverride tanpa mengubah file: nx8v start --port=5000, atau lewat environment NX8V_PORT / PORT (dibaca server.js).

nx8v init tidak menulis objek website dan tidak menaruh <nx8v-spa> di index.html. SPA, PWA, judul untuk tautan yang dibagikan, serta req.query, req.cookies, req.fields, dan req.files ada di runtime. Proyek baru memakainya setelah project.json dan HTML diubah seperti di bagian SPA dan PWA. server.js hasil init hanya mencatat permintaan (kecuali /.well-known/) dan menjawab /api/hello. Modul yang namanya ada di dependencies dan punya server.cjs menjawab lebih dulu, tanpa baris tambahan di server.js.


3. Struktur proyek

proyek/
├── project.json      # metadata, port, desktop, dependencies
├── server.js         # entry point (main)
├── modules/          # modul proyek; nama dan index.js wajib
│   └── contoh/
│       └── index.js
├── public/
│   ├── index.html
│   ├── about.html
│   └── assets/
│       ├── css/
│       ├── js/
│       ├── fonts/
│       └── images/
└── nx8v.log          # dibuat otomatis saat `nx8v start`

NX8V melayani berkas di public/ sebelum server.js. Menambah halaman cukup dengan menaruh file di folder itu:

URLFile yang dicari
/public/index.html
/aboutpublic/about.html lalu public/about/index.html
/about/data/upublic/about/data/u.html, about/data.html, about.html
/js/dll.jspublic/assets/js/dll.js. Path halaman diawali /js
/css/style.csspublic/assets/css/style.css
/images/32x32.pngpublic/assets/images/32x32.png
/favicon.icopublic/favicon.ico, lalu ikon public/assets/images/32x32.png
/contohmodules/contoh/index.js

Bila tidak ada berkas yang cocok, NX8V menanyakan modules/<nama>/server.cjs untuk setiap nama di dependencies. handle(req, res) yang mengembalikan true memiliki permintaan itu. Sisanya masuk ke server.js, misalnya GET /api/hello. server.js hasil init tetap pendek dan tidak memuat pemetaan folder.

SPA

Dua syarat sekaligus. project.json memuat objek website dengan "SPA": true, dan public/index.html memuat tag <nx8v-spa>. Tag itu boleh diletakkan di mana saja di halaman index. Bila keduanya ada, klik tautan tetap di dokumen index dan isi tautan masuk ke dalam tag. Bila salah satu tidak ada, URL kembali ke berkas HTML-nya sendiri. Objek website yang dipakai adalah yang sejajar dengan name di project.json.

"website": {
  "SPA": true,
  "PWA": false
}

Klik tautan pada origin yang sama mengisi slot dan mengganti URL. Yang dilewati: /api, berkas berekstensi, target selain _self, dan tautan unduhan. Formulir GET dan POST dengan syarat yang sama menaruh hasil di dalam tag, dan URL mengikuti action.

Halaman tujuan boleh membungkus potongan yang masuk slot dengan <nx8v-page>. Tanpa tag itu, isi <body> yang dipakai, termasuk <script> halaman itu, dan skrip tersebut dijalankan. Berkas yang sendiri berisi <nx8v-spa> tanpa <nx8v-page> tidak disalin ke dalam slot. Berkas tanpa <html>, misalnya teks polos, masuk utuh.

Nama halaman yang sama dengan nama modul, misalnya /form dan modul form, membuka public/form.html saat browser meminta HTML. import dari /form tetap menerima modules/form/index.js.

<nx8v-page>
  <h1>About</h1>
  <p>Isi yang masuk ke slot.</p>
</nx8v-page>

Membuka URL langsung, memuat ulang, atau melihat sumber halaman memakai cangkang index.html. <title> dan <meta property="og:title"> diambil dari halaman tujuan. Bila halaman tidak punya judul, segmen pertama dipakai, jadi help.html yang hanya berisi <h1> tetap berjudul Help. Deskripsi diambil dari <meta name="description">, atau dari paragraf pertama. og:description, og:image (dari meta atau <img> pertama), og:url, dan <link rel="canonical"> ikut ditulis. Isi <nx8v-page> sudah berada di dalam <nx8v-spa>. Tautan yang dibagikan membawa judul, deskripsi, dan isi halaman itu. Judul index disimpan di data-shell-title. Jalur penuh ada di data-path, ekor URL di data-rest. Kembali ke / mengosongkan slot dan memulihkan judul cangkang.

Klik menunggu respons sebelum URL diganti. Bila permintaan gagal dan tidak ada salinan offline, slot menampilkan Gagal memuat halaman. Hash ikut dibawa dan menggulir ke elemen dengan id itu. Halaman baru mulai dari atas. Tombol kembali memulihkan posisi gulir. Tautan yang path-nya sama dengan URL saat ini mendapat aria-current="page". Setiap <nx8v-spa> diisi. Setelah isi tiba, halaman mengirim peristiwa nx8v-route dengan detail.path, detail.rest, dan detail.hash. Salinan isi slot disimpan di cache nx8v-spa, jadi klik menu yang pernah dibuka tetap tampil saat jaringan putus.

Skrip di halaman meminta berkas asli dengan header X-Nx8v-Fragment: 1. Respons itu adalah about.html, bukan cangkang. Header X-Nx8v-Data berisi ekor URL, misalnya asasas untuk /about/asasas.

Selama permintaan masih berjalan, slot dapat menampilkan spinner dari modul spinner bila nama itu tertulis di dependencies. Halaman yang selesai sebelum modul siap langsung menampilkan isi, tanpa jeda tambahan. Atribut pada tag:

AtributNilaiBila tidak ditulis
sizesmall (20px), medium (40px), large (60px)medium
colorwarna CSS#007bff
messageteks di samping spinnerMemuat...
positioncenter, top, bottomcenter
typeinline, overlay, buttoninline

message="" meniadakan teks. type="overlay" menutup layar. Warna type="button" di modul tetap putih.

<nx8v-spa size="small" color="#EA580C" message="" position="center" type="inline"></nx8v-spa>

Log untuk /about/asasas:

route: /about/asasas -> index.html | spa | about.html | data: asasas

Ekor URL tercatat sebagai data. Berkas yang dilayani tetap about.html.

PWA

"PWA": true di objek website yang sama membuat aplikasi bisa dipasang dari browser. Halaman HTML mendapat manifes, warna tema, dan pendaftaran service worker.

"website": {
  "SPA": true,
  "PWA": true
}
URLIsi
/manifest.webmanifestnama dan deskripsi dari project.json, start_url /, display standalone
/nx8v-sw.jsservice worker
/nx8v-icon-192.pngikon 192, diperbesar dari public/assets/images/32x32.png
/nx8v-icon-512.pngikon 512 dari berkas yang sama

"PWA": true memasang manifes, ikon, dan service worker pada nx8v dev maupun nx8v start. Warna, tampilan, dan URL awal dibaca dari objek website yang sama. Bila tidak diisi, tema #3b82f6, latar #ffffff, display standalone, dan start_url /.

"website": {
  "SPA": true,
  "PWA": true,
  "theme_color": "#EA580C",
  "background_color": "#ffffff",
  "display": "standalone",
  "start_url": "/"
}

Nama cache mengikuti version di project.json, misalnya nx8v-pwa-1.0.0. Saat dipasang, worker menyimpan /, manifes, dan kedua ikon. Halaman juga mendapat <link rel="apple-touch-icon">. Pemasangan memakai ikon di bilah alamat browser.

Service worker mengambil jaringan lebih dulu dengan cache: 'no-store', lalu menyimpan salinan di cache-nya sendiri. Saat jaringan putus, halaman dan berkas yang pernah dibuka dilayani dari simpanan itu. Permintaan /api, pemeriksa nx8v dev, dan isi slot SPA tidak melewati service worker, jadi klik antar menu tetap bisa dibatalkan. Isi slot yang sudah pernah tampil dibaca lagi dari cache nx8v-spa.

Bila PWA mati, keempat URL itu tidak dibuat. Halaman melepas service worker yang sudah terpasang.

Modul

Modul proyek ditaruh di modules/. Dua hal wajib:

  1. Nama di dependencies pada project.json.
  2. Berkas masuk index.js di folder yang ditunjuk nama itu.
"dependencies": {
  "contoh": "1.0.0",
  "photos": "1.0.0"
}

Kuncinya nama modul. Nilainya versi. Folder selalu modules/ ditambah nama itu, jadi "contoh": "1.0.0" berarti modules/contoh/index.js.

Yang dibuatBentuk
Namakunci contoh
Versi1.0.0
Foldermodules/contoh
Berkas masukmodules/contoh/index.js

Di HTML cukup nama itu:

<script type="module" src="contoh"></script>

Browser meminta /contoh. NX8V mengembalikan modules/contoh/index.js. /contoh/index.js adalah berkas yang sama. Nama yang tidak tertulis di dependencies tidak dilayani.

require('./modules/photos') di server.js juga membuka index.js folder itu. photos untuk server. contoh untuk halaman. nx8v init membuat folder modules/, "dependencies": {}, dan repository yang menunjuk ke mega-repo internal NX8V.

Modul yang juga melayani HTTP menaruh server.cjs di folder yang sama:

function handle(req, res) {
  const clean = req.url.split("?")[0];
  if (req.method !== "POST" || clean !== "/api/contoh") return false;
  res.writeHead(200, { "Content-Type": "application/json" });
  res.end(JSON.stringify({ ok: true }));
  return true;
}

module.exports = { handle };

NX8V memuat berkas itu saat permintaan pertama, hanya bila nama foldernya tertulis di dependencies dan berkasnya ada. handle menerima req dan res yang sama dengan listener server.js. Balasan true berarti modul itu yang menjawab, dan listener server.js tidak dipanggil. Balasan false meneruskan permintaan ke modul berikutnya, lalu ke server.js. Body tetap sampai lewat req.on('data') dan req.on('end').

server.js tidak perlu require modul itu. Menambah atau menghapus nama di dependencies sudah cukup. Setelah server.cjs atau project.json berubah, jalankan ulang nx8v.

Dependensi

repository di project.json adalah tempat modul diambil dan diterbitkan. Bawaannya repo internal NX8V. Saat materi dependensi dipublikasikan dari repo milik user, url diganti.

"repository": {
  "type": "git",
  "url": "https://github.com/nx8v/dependencies.git"
}

Satu versi sama dengan satu GitHub Release. Nama dan versi pada perintah menjadi tag <nama>-<versi>. Aset rilis disalin ke modules/<nama>/. Berkas masuknya index.js. Berkas lain di folder yang sama, misalnya app.js, ikut diunduh dan ikut diterbitkan.

Perintah dijalankan di folder proyek:

PerintahFungsi
nx8v i contoh@1.0.2pasang. Tag yang dicari: contoh-1.0.2
nx8v u contoh@1.0.2ganti versi modul yang sudah ada di dependencies. Angka versinya yang diganti
nx8v p contohnamalain@1.0.0terbitkan isi modules/contohnamalain/ ke repository
nx8v d contohhapus contoh dari dependencies, nx8v.lock, dan modules/contoh
nx8v r contohhapus rilis, tag, dan folder contoh dari repository

Pasang menulis tiga tempat:

"dependencies": {
  "contoh": "1.0.2"
}
modules/contoh/index.js
{
  "dependencies": {
    "contoh": {
      "version": "1.0.2",
      "resolved": "https://github.com/nx8v/dependencies/releases/tag/contoh-1.0.2"
    }
  }
}

resolved di nx8v.lock adalah halaman rilis yang dipakai saat pemasangan. Nama yang tidak ada tag-nya tidak ditulis ke dependencies maupun modules. Bila tag tidak ketemu, perintah menampilkan daftar rilis yang ada.

Perbarui hanya untuk nama yang sudah terpasang. nx8v u contoh@1.0.2 mengganti berkas di modules/contoh/, versi di dependencies, dan catatan di nx8v.lock. Nama yang belum ada ditolak; pasang lebih dulu dengan nx8v i.

Terbitkan membaca modules/contohnamalain/. Folder itu wajib berisi index.js. Setiap berkas di folder itu dilampirkan sebagai aset rilis contohnamalain-1.0.0. Perintah ini memakai GitHub CLI dan akun yang sudah masuk:

gh auth login
nx8v p contohnamalain@1.0.0

Setelah terbit, dependencies dan nx8v.lock di proyek itu ikut mencatat versi tersebut. Rilis yang tag-nya sudah ada tidak ditimpa.

Hapus mengeluarkan nama dari daftar proyek. nx8v d contoh menghapus kunci contoh di project.json dan nx8v.lock, lalu menghapus folder modules/contoh.

Hapus dari repository dipakai pemilik repo. nx8v r contoh menghapus setiap rilis bertag contoh-<versi>, tag itu, dan folder contoh di cabang utama. Perintah ini memakai GitHub CLI yang sudah masuk, sama seperti nx8v p. Daftar di proyek yang sedang dibuka tidak diubah.


4. HTTP server

const http = require('http');

const server = http.createServer((req, res) => {
  console.log(req.method, req.url);

  res.writeHead(200, { 'Content-Type': 'application/json' });
  res.end(JSON.stringify({ ok: true }));
});

server.listen(3000, () => console.log('http://localhost:3000'));

HTTPS memakai modul https. Berkas PFX dibaca sebagai Buffer (tanpa encoding). Kunci PEM dibaca sebagai teks.

const https = require('https');
const fs = require('fs');

https.createServer({
  pfx: fs.readFileSync('server.pfx'),
  passphrase: 'rahasia'
}, (req, res) => {
  res.end(req.httpVersion);
}).listen(8443);

https.createServer({
  cert: fs.readFileSync('server.crt', 'utf8'),
  key: fs.readFileSync('server.key', 'utf8')
}, (req, res) => {
  res.end(req.httpVersion);
}).listen(8444);

Sertifikat PFX berunding TLS 1.2 dan 1.3. Pasangan cert dan key menerima BEGIN CERTIFICATE bersama BEGIN PRIVATE KEY (PKCS#8) atau BEGIN RSA PRIVATE KEY. Di Windows, PEM berunding TLS 1.2. Di Linux, PEM berunding TLS 1.2 dan 1.3. Kunci bertipe BEGIN ENCRYPTED PRIVATE KEY belum didukung; bungkus kunci itu ke dalam PFX. ALPN menawarkan h2 lalu http/1.1. req.httpVersion berisi "2.0" saat klien memakai HTTP/2, dan "HTTP/1.1" saat klien memakai HTTP/1.1. HTTP/2 tanpa TLS (preface PRI * HTTP/2.0) juga diterima oleh http.createServer. Build Linux menautkan OpenSSL lewat NX8V_USE_OPENSSL, untuk klien https:// dan https.createServer. Rilis v0.2.2 memuat server TLS ini. Ketiga panel di port 6002 menjalankan biner yang sama.

Request (req):

Properti/MethodKeterangan
req.methodGET, POST, …
req.urlpath + query, apa adanya
req.httpVersion"HTTP/1.1" pada HTTP/1.1, "2.0" pada HTTP/2
req.headersobjek header (nama asli dari klien)
req.queryobjek query string, ?a=1 menjadi { a: "1" }
req.cookiesobjek dari header Cookie
req.fieldsbidang formulir application/x-www-form-urlencoded atau multipart/form-data
req.filesberkas unggahan. Setiap nama berisi { filename, type, data } dan data adalah Buffer
req.on('data', fn)body (bila ada)
req.on('end', fn)body selesai

Response (res):

MethodKeterangan
res.writeHead(status[, message][, headers])kirim status + header. Tanpa message, kalimat status mengikuti kode: 404 menjadi Not Found
res.setHeader(name, value)set satu header
res.cookie(name, value[, opsi])tambah Set-Cookie. Opsi: path, maxAge, httpOnly, secure, sameSite
res.getHeader(name)baca header
res.write(chunk)tulis body (di-buffer). chunk boleh string atau Buffer
res.end([chunk][, cb])kirim respons, tutup koneksi. Byte 0x00 di dalam PNG ikut terkirim
res.statusCodestatus default 200. Mengubah angka ini juga mengganti kalimat status

fs.readFile dan fs.readFileSync tanpa encoding mengembalikan Buffer, jadi gambar tidak rusak saat dikirim lewat res.end. Encoding 'utf8' tetap mengembalikan string. data.toString() memakai teks UTF-8.

Event server: listening, request, connection, close, error, websocket.

Catatan: server membalas Connection: close (satu permintaan per koneksi). Pipelining belum didukung. Body di atas 8 MB dibalas 413 Payload Too Large.

HTML, termasuk cangkang SPA, dikirim dengan Cache-Control: no-store dan tetap bisa di-gzip. JavaScript, CSS, dan berkas modul tanpa ekstensi dikirim dengan ETag dan Cache-Control: no-cache, jadi browser menanyakan ulang ke server. Gambar, font, dan berkas lain dikirim dengan ETag, Cache-Control: public, max-age=3600, Range (206), dan gzip bila klien meminta Accept-Encoding: gzip. If-None-Match yang cocok pada kelompok itu membalas 304.


5. Router

const app = require('router')();

app.use((req, res) => {
  console.log(req.method, req.url);       // middleware global
});

app.get('/user/:id', (req, res) => {
  res.json({ id: req.params.id });        // param dinamis
});

app.post('/api/login', (req, res) => {
  res.status(201).json({ ok: true, token: 'abc123' });
});

app.get('/lama', (req, res) => res.redirect('/baru'));

app.notFound((req, res) => {
  res.status(404).json({ error: 'tidak ada rute', path: req.url });
});

app.listen(5400, () => console.log('http://localhost:5400'));
APIKeterangan
app.get/post/put/delete/patch/all(path, handler)daftar rute
app.use([path,] fn)middleware (global atau ber-scope)
app.notFound(fn)handler 404 kustom
app.listen(port[, cb])jalankan server, mengembalikan http.Server
req.paramshasil parse param rute
res.status(code)set status (chainable)
res.json(obj)kirim JSON + header otomatis
`res.send(str\Buffer)`kirim teks/biner
res.redirect(url[, code])redirect (default 302)

Pola path yang didukung:

/user/:id          → req.params.id
/file/:name?       → opsional
/static/*          → req.params[0] berisi sisa path

Middleware bisa menghentikan rantai dengan return false.


6. Static file server

Versi produksi dari static handler (ETag, Range, HEAD, cache):

const http  = require('http');
const serve = require('static');

const handler = serve('public', { maxAge: 3600, index: 'index.html' });

http.createServer((req, res) => handler(req, res)).listen(3000);
// atau: app.use(handler)  (di router)
OpsiDefaultKeterangan
maxAge0detik untuk Cache-Control: public, max-age=N
indexindex.htmlfile untuk direktori
etagtruekirim ETag + dukung If-None-Match (304)
rangestruedukung Range (206 Partial Content)

Perilaku:

  • GET/HEAD saja; method lain → 405 + header Allow
  • If-None-Match / If-Modified-Since cocok → 304 (tanpa body)
  • Range: bytes=0-99 → 206 + Content-Range
  • Range tidak valid → 416
  • .. pada path (termasuk %2e%2e) → 403
  • File tidak ada → 404

7. WebSocket server

const http = require('http');
const server = http.createServer((req, res) => res.end('ws server'));

server.on('websocket', (ws) => {
  console.log('client tersambung');

  ws.send('selamat datang');

  ws.on('message', (msg) => {
    console.log('terima:', msg);
    ws.send('echo: ' + msg);
  });

  ws.on('close', () => console.log('client putus'));
});

server.listen(5300);

Upgrade ditangani otomatis: klien yang mengirim header Upgrade: websocket ke port yang sama akan diubah menjadi koneksi WebSocket (RFC 6455).

APIKeterangan
server.on('websocket', fn)koneksi WebSocket baru
`ws.send(text \Buffer)`kirim frame
ws.on('message', fn)pesan masuk (string)
ws.on('close', fn)koneksi ditutup
ws.close()tutup dari server

Ping/pong dijawab otomatis. Klien (browser) bisa memakai API WebSocket biasa:

const ws = new WebSocket('ws://localhost:5300/');
ws.onmessage = (e) => console.log(e.data);

8. HTTP/HTTPS client + fetch

http.get / http.request

const http = require('http');

http.get('http://127.0.0.1:3000/api/hello', (res) => {
  console.log('status:', res.statusCode, res.statusMessage);
  let body = '';
  res.on('data', (c) => { body += c; });
  res.on('end', () => console.log(body));
});
const https = require('https');           // sama, untuk https://

const req = https.request({
  hostname: 'jsonplaceholder.typicode.com',
  path: '/photos?_limit=5',
  method: 'GET',
  headers: { 'Accept': 'application/json' },
}, (res) => {
  let body = '';
  res.on('data', (c) => { body += c; });
  res.on('end', () => console.log(JSON.parse(body).length, 'item'));
});

fetch (global)

fetch('https://jsonplaceholder.typicode.com/photos?_limit=5')
  .then((r) => {
    if (!r.ok) throw new Error('HTTP ' + r.status);
    return r.json();
  })
  .then((photos) => console.log(photos.length, 'foto'))
  .catch((e) => console.error(e.message));
Objek responsKeterangan
res.oktrue bila status 200–299
res.statuskode status
res.statusTextteks status
res.headersobjek header (lowercase)
res.text()Promise<string>
res.json()Promise<object>

Catatan HTTPS: di Windows memakai WinHTTP (TLS 1.2/1.3, sertifikat dari cert store Windows, redirect dan de-chunk otomatis). Di Linux aktif bila dikompilasi dengan -DNX8V_USE_OPENSSL.

Chunked transfer (Transfer-Encoding: chunked) ditangani otomatis pada respons HTTP biasa.


9. Modul pendukung

Modul yang paling sering dipakai server:

ModulKegunaan
fsbaca/tulis file. readFile tanpa encoding mengembalikan Buffer
pathjoin, resolve, dirname, basename, extname, parse, format
querystringparse, stringify, escape, unescape, URLSearchParams
urlparse URL (href, pathname, search, host, …)
zlibgzipSync / gunzipSync / deflateSync / inflateSync
cryptocreateHash (md5/sha1/sha256), randomBytes, randomUUID
Bufferdata biner (index access, toString('hex'/'base64'), slice, concat)
streamReadable/Writable/PassThrough + pipe
eventsEventEmitter
netTCP mentah (createServer, connect)
dnslookup, resolve, resolve4
child_processexec, spawn
osplatform, cpus, networkInterfaces, homedir, …
dbopen — SQLite, PostgreSQL, dan MySQL

require('db')

Pemanggilan memblokir sampai database menjawab. Kegagalan melempar Error. Placeholder di SQL adalah ?. Pada PostgreSQL tanda itu dikirim sebagai $1, $2, dan seterusnya.

const db = require('db');

const sql = db.open({ driver: 'sqlite', file: 'data.db' });
sql.exec('CREATE TABLE IF NOT EXISTS t (id INTEGER PRIMARY KEY, nama TEXT)');
const tulis = sql.run('INSERT INTO t (nama) VALUES (?)', ['Budi']);
// tulis.changes, tulis.lastInsertRowid
const satu = sql.get('SELECT * FROM t WHERE nama = ?', ['Budi']); // objek, atau null
const semua = sql.all('SELECT * FROM t');                          // array
sql.close();
driverBawaan
sqlitefile = data.db. :memory: membuka basis di memori. timeout milidetik, bawaan 10000
postgres atau postgresqlhost 127.0.0.1, port 5432, user postgres. password boleh ditulis pass. database kosong memakai nama user
mysql atau mariadbhost 127.0.0.1, port 3306, user root. database kosong memakai nama user
MetodeHasil
exec(sql[, params])menjalankan SQL, tanpa nilai kembali
run(sql[, params]){ changes, lastInsertRowid }
get(sql[, params])satu objek, atau null
all(sql[, params])array objek
close()menutup koneksi

PostgreSQL menerima sandi cleartext, md5, dan SCRAM-SHA-256. MySQL menerima mysql_native_password dan jalur cepat caching_sha2_password. Pertukaran autentikasi penuh caching_sha2 (penanda 0x01 0x04) belum dikerjakan dan mengembalikan pesan kesalahan. SQLite menyalakan foreign_keys dan tidak memuat ekstensi.

Contoh querystring:

const qs = require('querystring');
qs.parse('a=1&b=dua&c=x%20y');       // { a: '1', b: 'dua', c: 'x y' }
qs.stringify({ x: 1, y: 'a b' });    // 'x=1&y=a+b'

10. CLI server

PerintahFungsi
nx8v initwizard pembuatan proyek
nx8v i <nama>@<versi>pasang modul dari repository di project.json
nx8v u <nama>@<versi>ganti versi modul yang sudah terpasang
nx8v p <nama>@<versi>terbitkan modules/<nama> ke repository
nx8v d <nama>hapus modul dari dependencies
nx8v r <nama>hapus rilis, tag, dan folder modul dari repository
nx8v dev [--port=N]development: simpan HTML memuat ulang browser; simpan JS me-restart server
nx8v start [--port=N]jalankan dari project.json
nx8v stop [--port=N]hentikan server proyek
nx8v listdaftar server berjalan (PID, port, proyek, direktori)
nx8v logs [--lines=N]ekor nx8v.log proyek ini
nx8v test [path]jalankan *.test.js
nx8v packagefolder distribusi (dist/<nama>/)

nx8v dev

Memantau folder proyek, termasuk file di root seperti server.js. Dilewati: node_modules, .git, build, dist, .nx8v.

Simpan .html, .css, atau .svg memuat ulang halaman yang sedang terbuka. Server tetap jalan:

[watch] change detected: D:\proyek\public\index.html
[dev] browser reload

Simpan .js, .mjs, atau .json me-restart server, lalu halaman yang sama ikut memuat ulang setelah server hidup lagi:

[watch] change detected: D:\proyek\server.js
[dev] stopping server for restart...
[dev] restarting D:\proyek\server.js
[dev] server started (pid 1250)

Setelah nx8v diperbarui, refresh halaman sekali supaya skrip pemantau masuk. Simpan berikutnya memuat ulang sendiri. Bila server crash karena error sintaks, supervisor tetap hidup — perbaiki file dan server otomatis menyala lagi.

Komponen HTML (partial)

Tag apa pun bisa memuat file HTML lain. nx8v menggabungkannya saat halaman dikirim, tanpa JavaScript di browser:

<header partial="./header.html"></header>

./header.html dibaca dari folder yang sama dengan halaman (public/). Isi file masuk ke dalam tag, dan atribut partial dihapus. Simpan file komponen saat nx8v dev berjalan: halaman yang terbuka memuat ulang sendiri.

Path di luar folder public ditolak.

nx8v list

PID      PORT    PROJECT          DIR
1360     4000    Aplikasi demo    D:\NX8V\example
13204    5202    toko-demo        D:\NX8V\build\demotest

nx8v test

test('penjumlahan', () => {
  assert.equal(1 + 1, 2);
  assert.deepEqual([1, 2], [1, 2]);
  assert.throws(() => { throw new Error('x'); });
});

Output ✓/✗ per test + ringkasan; exit code 1 bila ada kegagalan. Assertion: equal, strictEqual, notEqual, deepEqual, deepStrictEqual, ok, truthy, notOk, throws, doesNotThrow.


11. Deployment

Windows

# jalankan di background lalu cek
nx8v start
nx8v list
curl http://127.0.0.1:4000/

# hentikan
nx8v stop

Linux (systemd)

sudo ./install.sh --service /path/to/project

Menghasilkan unit dari packaging/nx8v.service:

[Unit]
Description=NX8V application
After=network.target

[Service]
Type=simple
WorkingDirectory=@PROJECT@
ExecStart=/usr/bin/env nx8v start
Restart=on-failure
RestartSec=2

Distribusi

nx8v package                # dist/<nama>/ berisi exe + proyek + start.cmd
nx8v package --name=toko --out=release

Folder hasil bisa di-zip dan dikirim; penerima cukup menjalankan start.cmd.


12. Contoh di repo

FileIsi
samples/httpserver.jsHTTP server + JSON endpoint
samples/router.jsrouter: param, middleware, redirect, notFound
samples/static_server.jsstatic handler (ETag/Range)
samples/websocket.jsWebSocket server (echo)
samples/wsclient.jsWebSocket client
samples/fs.jsoperasi file
samples/timers.jssetTimeout/setInterval
samples/basic.test.jscontoh test
example/proyek lengkap: galeri foto dari API (fetch HTTPS)

13. Uji tiga server

Uji 23 Oktober 2026 di tiga server Docker pada WSL Ubuntu-22.04. Susunan server ada di D:\server\README.md. NX8V berjalan sebagai proses sendiri. aaPanel, CyberPanel, dan Apache hanya meneruskan jalur /nx8v/. Situs yang sudah ada tetap di akarnya.

Server kedua adalah CyberPanel penuh versi 1.9, container cyberpanel, image cyberpanel-ssh:latest, CentOS 7.6. Narasinya ada di D:\server\CyberPanel\README.md. Panel itu memakai stack hosting umum: lscpd (panel, port 8090), OpenLiteSpeed (web :80/:443 dan admin :7080), MariaDB, Postfix/Dovecot, PowerDNS, dan Pure-FTPd. OpenLiteSpeed di sini adalah web server di dalam CyberPanel. Layanan di container ini dinyalakan entrypoint docker/cyberpanel/entrypoint.sh, karena systemd di dalam container WSL tidak lengkap.

Akses dari Windows memakai IP WSL. Di mesin uji, localhost tidak meneruskan port container. IP saat uji: 192.168.144.198. Cek ulang dengan wsl -d Ubuntu-22.04 -u root hostname -I.

ServerURLHalaman/nx8v/api/where
aaPanelhttp://192.168.144.198:8080/nx8v/HTTP 200{"runtime":"nx8v","host":"437e50fe88dd"}
CyberPanelhttp://192.168.144.198:8082/nx8v/HTTP 200{"runtime":"nx8v","host":"DANTRIK"}
Apachehttp://192.168.144.198:8083/nx8v/HTTP 200{"runtime":"nx8v","host":"acaf18fee8e1"}

Halaman menampilkan judul NX8V Server & web, lalu mengisi nama host dari fetch('api/where'). Situs NxPHP di CyberPanel (Host: nxd-cyber.local pada port 8082) tetap HTTP 200 setelah jalur /nx8v/ dipasang.

Proyek uji: D:\server\nx8v-app (project.json, server.js, public/index.html). Port dengar 6002. Binary Linux yang dipakai build/linux/nx8v (glibc 2.34).

ServerTempat prosesProxy
aaPanel, container aapanel (glibc 2.35)/opt/nx8v, 127.0.0.1:6002D:\server\docker\aapanel\nx8v-proxy.conf
Apache, container apache (glibc 2.41)/opt/nx8v, 127.0.0.1:6002D:\server\docker\apache\nx8v-proxy.conf
CyberPanel 1.9, container cyberpanel, CentOS 7.6 (glibc 2.17)proses di WSL, gateway 172.18.0.1:6002context /nx8v/ pada vhost nxlocal, D:\server\nx8v-app\ols-nx8v.snippet

CentOS 7.6 di CyberPanel memakai glibc 2.17, jadi binary NX8V (glibc 2.34) dijalankan di WSL. OpenLiteSpeed di dalam CyberPanel meneruskan /nx8v/ ke gateway Docker 172.18.0.1:6002. aaPanel dan Apache menjalankan binary yang sama di dalam masing-masing container.

Menyalakan ulang setelah WSL atau container hidup kembali:

wsl -d Ubuntu-22.04 -u root bash /mnt/d/server/nx8v-app/install-servers.sh
wsl -d Ubuntu-22.04 -u root bash /mnt/d/server/nx8v-app/enable-proxy.sh

14. Catatan 24 Oktober 2026

Yang sudah ada di runtime, dan apakah nx8v init ikut menuliskannya:

BagianRuntimenx8v init
SPA (website.SPA dan <nx8v-spa>)adatidak
PWA (website.PWA, manifes, service worker)adatidak
Judul, deskripsi, dan og:* saat tautan dibagikanada, ikut SPAtidak
req.query, req.cookies, req.fields, req.files, res.cookieada pada setiap permintaantidak. Contoh init hanya /api/hello
ETag, Range, dan gzip berkas publik selain HTMLadatidak perlu di template
Log /.well-known/ tidak dicetakadaada, satu syarat di server.js
nx8v --fuzz (korpus + 20.000 mutasi, benih --fuzz=1:20000)adabukan berkas proyek
crypto.randomBytes / crypto.randomUUID lewat BCryptGenRandom (Windows) dan getrandom (Linux)adabukan berkas proyek
Hosting aaPanel, CyberPanel, dan Apachehosting.mdbukan berkas proyek
require('db'): SQLite, PostgreSQL, MySQLadabukan berkas proyek
modules/<nama>/server.cjs untuk nama di dependenciesada. handle yang mengembalikan true menjawab sebelum server.jstidak. server.js tetap contoh /api/hello
JavaScript, CSS, dan modul tanpa ekstensi: Cache-Control: no-cacheadatidak perlu di template
Service worker fetch memakai cache: 'no-store'ada, bila PWA hiduptidak

Parser chunked menolak panjang yang lebih besar dari data yang benar-benar ada, jadi klaim ukuran tidak membuat buffer sebesar angka itu.

Pemasangan lewat SSH, dicatat di hosting.md:

ServerHasil
aaPanelnx8v/0.2.2 terpasang di /home/admin/.local/bin/nx8v
Apachenx8v/0.2.2 terpasang di /home/admin/.local/bin/nx8v
CyberPanelberkas terpasang di jalur yang sama, tetapi tidak jalan: libssl.so.3 tidak ada

Troubleshooting

GejalaPenyebab & solusi
ERR_UNSAFE_PORT di browserPort diblokir Chrome (mis. 6000). Pakai 3000/4000/8080.
Browser reload terusSudah diperbaiki; pastikan exe terbaru.
Cannot find module 'X'Modul bukan built-in → cek path relatif, atau daftar modul di §9.
EADDRINUSE / address already in usePort dipakai proses lain → nx8v list, lalu nx8v stop --port=N.
Gambar rusak atau hanya beberapa byteres.end harus menerima Buffer dari readFile. Pakai exe terbaru.
404 pada /favicon.icoLetakkan public/favicon.ico, atau biarkan server memakai public/assets/images/32x32.png.
invalid URL pada fetchSkema harus http:// atau https://.
Halaman tidak berubahHTML memakai no-store. JavaScript, CSS, dan modul tanpa ekstensi memakai no-cache. Muat ulang halaman. Perubahan server.cjs butuh nx8v dijalankan ulang.
Hosting

Hosting NX8V

Satu aplikasi NX8V. Tiga web server di depannya: aaPanel, CyberPanel, dan Apache.

Situs yang sudah ada tetap di /. Aplikasi NX8V tampil di /nx8v/. Web server hanya meneruskan jalur itu ke proses NX8V. NX8V sendiri yang menulis halaman dan API.

Yang tampil di ketiga server sama.

Halamanhttps://domain/nx8v/
JudulNX8V Server & web
APIhttps://domain/nx8v/api/where
Isi API{"runtime":"nx8v","host":"nama-mesin"}

Nama host di halaman diisi dari API itu.


1. Aplikasi

Simpan di /opt/nx8v.

project.json

{
  "name": "nx8v-web",
  "version": "1.0.0",
  "description": "NX8V Server and web",
  "main": "server.js",
  "nx8v": { "port": 6002 }
}

public/index.html

<!DOCTYPE html>
<html lang="id">
<head>
  <meta charset="utf-8">
  <title>NX8V Server &amp; web</title>
  <style>
    body { font-family: system-ui, sans-serif; margin: 2rem; }
    h1 { color: #1d4ed8; }
  </style>
</head>
<body>
  <h1>NX8V Server &amp; web</h1>
  <p>Halaman ini dilayani oleh runtime NX8V.</p>
  <p>Host: <strong id="host">memuat</strong></p>
  <script>
    fetch('api/where').then(function (r) { return r.json(); }).then(function (j) {
      document.getElementById('host').textContent = j.host;
    }).catch(function () {
      document.getElementById('host').textContent = 'tidak terbaca';
    });
  </script>
</body>
</html>

server.js mendengar di 127.0.0.1:6002. Awalan /nx8v dibuang sebelum rute dipilih, karena halaman memakai fetch('api/where') dan browser mengirim /nx8v/api/where.

const http = require('http');
const fs = require('fs');
const os = require('os');

const PORT = 6002;
const page = process.cwd().replace(/\/$/, '') + '/public/index.html';

function route(url) {
  let p = (url || '/').split('?')[0];
  if (p === '/nx8v' || p === '/nx8v/') return '/';
  if (p.indexOf('/nx8v/') === 0) return p.slice('/nx8v'.length);
  return p;
}

const server = http.createServer((req, res) => {
  const clean = route(req.url);
  if (clean === '/api/where') {
    res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' });
    res.end(JSON.stringify({ runtime: 'nx8v', host: os.hostname() }));
    return;
  }
  if (clean === '/' || clean === '/index.html') {
    res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
    res.end(fs.readFileSync(page, 'utf8'));
    return;
  }
  res.writeHead(404, { 'Content-Type': 'text/plain; charset=utf-8' });
  res.end('404');
});

server.listen(PORT, '127.0.0.1', () => {
  console.log('nx8v web http://127.0.0.1:' + PORT);
});

Port 6002 hanya untuk mesin itu. Dari internet yang terbuka tetap port web server, 80 atau 443.


2. Dari mana NX8V dipasang

Runtime-nya tidak ikut panel. Di aaPanel, CyberPanel, dan Apache, sumbernya sama: rilis Linux di github.com/iyanrsaleh/nx8v/releases/latest.

Tiap server punya SSH sendiri. Perintah di bawah diketik di terminal SSH server itu, bukan dari mesin lain.

Mesin membutuhkan glibc 2.34 atau lebih baru. install.sh mengunduh nx8v-linux-x64.tar.gz, mencocokkan hash di SHA256SUMS, lalu memasang biner ke ~/.local/bin/nx8v.

curl -fsSL -o install.sh https://raw.githubusercontent.com/iyanrsaleh/nx8v/main/install.sh
sed -i 's/\r$//' install.sh
sh install.sh
sudo install -m 755 "$HOME/.local/bin/nx8v" /usr/local/bin/nx8v
nx8v --version

sh install.sh memasang biner untuk user SSH admin. Hasilnya di ketiga server:

ServerHasil
aaPanelnx8v/0.2.2 terpasang di /home/admin/.local/bin/nx8v
Apachenx8v/0.2.2 terpasang di /home/admin/.local/bin/nx8v
CyberPanelbiner tidak bisa jalan sendiri (glibc 2.17). Ditambal dengan glibc 2.36 dari Ubuntu 24.04 (patchelf) agar bisa digunakan

/usr/local/bin membuat nx8v ketemu oleh layanan sistem. Di CyberPanel langkah sudo install tidak dijalankan, karena biner itu tidak bisa dieksekusi di mesin tersebut. Aplikasi di bagian 1 tetap berkas Anda sendiri. Salin project.json, server.js, dan public/index.html ke /opt/nx8v, lalu nyalakan.

cd /opt/nx8v
nx8v start
curl -sS http://127.0.0.1:6002/api/where

Jawaban yang benar:

{"runtime":"nx8v","host":"nama-mesin"}

Agar hidup lagi setelah mesin menyala, pasang unit dari skrip yang sama. Direktori kerja unit itu /opt/nx8v, perintahnya nx8v start.

sudo sh install.sh --service /opt/nx8v
sudo systemctl enable --now nx8v

3. aaPanel

aaPanel di sini memakai Apache. Situs lama tetap di konfigurasi situsnya. Tambahkan penerusan ini pada vhost situs, lalu muat ulang Apache dari panel.

RedirectMatch ^/nx8v$ /nx8v/
<Location /nx8v/>
    ProxyPass http://127.0.0.1:6002/
    ProxyPassReverse http://127.0.0.1:6002/
</Location>

/nx8v tanpa garis miring diarahkan ke /nx8v/. Modul proxy dan proxy_http harus aktif. httpd -t bersih sebelum Apache dimuat ulang.

Buka https://domain/nx8v/. Judulnya NX8V Server & web. Baris host terisi nama mesin.


4. CyberPanel

Web server CyberPanel adalah OpenLiteSpeed. Container berbasis CentOS 7.6 (glibc 2.17) — terlalu lama untuk biner NX8V (butuh glibc 2.34+, OpenSSL 3). Solusinya: salin glibc 2.36 + OpenSSL 3 dari Ubuntu 24.04, lalu patch biner dengan patchelf agar memakai library baru itu. Tidak ada yang ditimpa di sistem; semuanya di /opt/glibc236.

4a. Pasang library dari Ubuntu 24.04

Jalankan di host Windows (bukan di dalam container):

# Ambil library dari image Ubuntu 24.04 (sekali saja)
docker pull ubuntu:24.04 --quiet
docker create --name tmp-ub24 ubuntu:24.04 true
docker cp tmp-ub24:/lib/x86_64-linux-gnu/libssl.so.3   <dir>\libssl.so.3
docker cp tmp-ub24:/lib/x86_64-linux-gnu/libcrypto.so.3 <dir>\libcrypto.so.3
docker cp tmp-ub24:/lib/x86_64-linux-gnu/libm.so.6       <dir>\libm.so.6
docker cp tmp-ub24:/lib/x86_64-linux-gnu/libgcc_s.so.1   <dir>\libgcc_s.so.1
docker cp tmp-ub24:/lib/x86_64-linux-gnu/libc.so.6       <dir>\libc.so.6
docker cp tmp-ub24:/lib64/ld-linux-x86-64.so.2           <dir>\ld-linux-x86-64.so.2
docker rm tmp-ub24

<dir> adalah direktori sementara, mis. D:\tmp-libs. Semua file berikutnya diletakkan di /opt/glibc236 di dalam container:

# Dari Windows
docker cp <dir>\libssl.so.3   cyberpanel:/opt/glibc236/libssl.so.3
docker cp <dir>\libcrypto.so.3 cyberpanel:/opt/glibc236/libcrypto.so.3
docker cp <dir>\libm.so.6       cyberpanel:/opt/glibc236/libm.so.6
docker cp <dir>\libgcc_s.so.1   cyberpanel:/opt/glibc236/libgcc_s.so.1
docker cp <dir>\libc.so.6       cyberpanel:/opt/glibc236/libc.so.6
docker cp <dir>\ld-linux-x86-64.so.2 cyberpanel:/opt/glibc236/ld-linux-x86-64.so.2

4b. Patch biner nx8v

Pasang patchelf (sekali saja):

curl -fsSL https://github.com/NixOS/patchelf/releases/download/0.18.0/patchelf-0.18.0-x86_64.tar.gz -o /tmp/p.tar.gz
tar -xzf /tmp/p.tar.gz -C /tmp
cp /tmp/patchelf-0.18.0-x86_64/bin/patchelf /usr/local/bin/

Patch interpreter dan RPATH:

NX8V=/home/admin/.local/bin/nx8v
patchelf --set-interpreter /opt/glibc236/ld-linux-x86-64.so.2 $NX8V
patchelf --set-rpath /opt/glibc236 $NX8V
$NX8V --version   # nx8v/0.2.2

4c. Jalankan

cd /opt/nx8v
nohup env LD_LIBRARY_PATH=/opt/glibc236 /home/admin/.local/bin/nx8v start >/var/log/nx8v.log 2>&1 &
curl -sS http://127.0.0.1:6002/api/where

Jawaban yang benar:

{"runtime":"nx8v","host":"nama-mesin"}

Auto-start di /opt/entrypoint-cyberpanel.sh (host: docker/cyberpanel/entrypoint.sh):

NX8V=/home/admin/.local/bin/nx8v
if [ -x "$NX8V" ] && [ -f /opt/nx8v/project.json ] && ! pgrep -f "$NX8V start" >/dev/null 2>&1; then
    echo "[cyberpanel] mulai nx8v (glibc 2.36 patched)..."
    cd /opt/nx8v && nohup env LD_LIBRARY_PATH=/opt/glibc236 $NX8V start >/var/log/nx8v.log 2>&1 &
fi

4d. OpenLiteSpeed

Sama dengan aaPanel, tetapi perlu stop + start penuh (restart SIGUSR1 tidak cukup untuk config baru). extProcessor harus di level server (httpd_config.conf), bukan di vhconf:

extProcessor nx8v{
    type                            proxy
    address                         127.0.0.1:6002
    maxConns                        100
    initTimeout                     60
    retryTimeout                    0
    respBuffer                      0
}

context pada vhconf situs:

context /nx8v/ {
  type                    proxy
  handler                 nx8v
  addDefaultCharset       off
}
/usr/local/lsws/bin/lswsctrl stop
/usr/local/lsws/bin/lswsctrl start

Halaman dan API yang tampil sama dengan aaPanel.


5. Apache

Aktifkan modul, simpan cuplikan yang sama dengan aaPanel, lalu muat ulang.

a2enmod proxy proxy_http

Berkas /etc/apache2/conf-available/nx8v.conf:

RedirectMatch ^/nx8v$ /nx8v/
<Location /nx8v/>
    ProxyPass http://127.0.0.1:6002/
    ProxyPassReverse http://127.0.0.1:6002/
</Location>
a2enconf nx8v
apache2ctl configtest
apache2ctl graceful

configtest harus menjawab Syntax OK sebelum graceful.


6. Cek

Di tiap server, dua alamat ini harus menjawab.

AlamatHasil
/nx8v/halaman NX8V Server & web, host terisi
/nx8v/api/where{"runtime":"nx8v","host":"..."}
/situs lama, tidak berubah
curl -sS -o /dev/null -w "%{http_code}\n" https://domain/nx8v/
curl -sS https://domain/nx8v/api/where
curl -sS -o /dev/null -w "%{http_code}\n" https://domain/

7. Bila gagal

GejalaYang dicek
/nx8v/ menjawab 502 atau 503curl http://127.0.0.1:6002/api/where di mesin yang menjalankan nx8v start
Halaman tampil, host tetap "memuat" atau "tidak terbaca"/nx8v/api/where dari browser. Awalan /nx8v di aplikasi harus terbuang
Akar situs ikut pindahPenerusan hanya pada /nx8v/, bukan pada /
curl berhenti dengan kesalahan sertifikatport 443 yang keluar dari server ikut dialihkan ke panel, jadi sertifikat yang tampil milik panel. Batasi pengalihan itu pada alamat server sendiri, lalu ulangi install.sh
nx8v: cannot execute atau libssl.so.3 tidak ketemu (aaPanel/Apache)glibc di bawah 2.34, atau OpenSSL 3 belum ada. sh install.sh ulang, atau periksa ldd /home/admin/.local/bin/nx8v
nx8v: cannot execute di CyberPanelglibc 2.17 terlalu lama. Patch dengan patchelf --set-interpreter /opt/glibc236/ld-linux-x86-64.so.2 --set-rpath /opt/glibc236 /home/admin/.local/bin/nx8v, lalu jalankan dengan LD_LIBRARY_PATH=/opt/glibc236
Desktop

NX8V — Desktop (WebView2)

Panduan lengkap membangun aplikasi desktop dengan NX8V: jendela native, bridge JS ↔ native, dialog, tray, notifikasi, dan packaging.

Untuk aplikasi web/server, lihat server.md. Untuk gambaran umum runtime, lihat README.md.

Daftar isi:

  1. Cara kerja
  2. Menjalankan
  3. Bridge window.nx8v
  4. Dialog native
  5. Tray & menu
  6. Notifikasi
  7. Multi-window
  8. Packaging
  9. Detail teknis
  10. Prasyarat & troubleshooting

1. Cara kerja

nx8v window
   │
   ├─▶ jalankan script (biasanya HTTP server) di event loop libuv
   │
   └─▶ buka jendela Win32 + WebView2
          └─▶ navigate ke http://127.0.0.1:<port>/
                 └─▶ halaman HTML/CSS/JS biasa
                        └─▶ window.nx8v  (bridge ke native)

Frontend tetap HTML/CSS/JS normal yang dilayani server lokal; backend adalah runtime NX8V sendiri. Tidak ada Electron, satu exe Windows sekitar 34 MB.

Di Linux, nx8v window membuka jendela GTK dengan WebKitGTK dan menuju alamat yang sama. window.nx8v untuk judul, ukuran, dialog, dan jendela tambahan berlaku di keduanya. Tray tetap di Windows.


2. Menjalankan

nx8v window                                   # pakai project.json (main + port)
nx8v window server.js                         # script tertentu
nx8v window --url=http://localhost:3000       # URL eksplisit
nx8v window --title="Toko" --width=1200 --height=800
OpsiDefaultKeterangan
--url=http://127.0.0.1:<port>/URL yang dibuka
--title=nx8vjudul jendela
--width=desktop.width, atau 1100lebar jendela
--height=desktop.height, atau 760tinggi jendela
--state=desktop.state, atau normalnormal, maximized, atau minimized
--maximizedsama dengan --state=maximized
--minimizedsama dengan --state=minimized
--no-maximizedesktop.maximizablesembunyikan tombol maximize
--min-width=desktop.minWidthlebar terkecil saat tepi ditarik
--min-height=desktop.minHeighttinggi terkecil saat tepi ditarik
--x= --y=desktop.x, desktop.ypojok kiri atas jendela
--centerdesktop.centerletakkan di tengah area kerja
--framelessdesktop.titlebarsama dengan "titlebar": false
--no-resizedesktop.resizablekunci ukuran; tepi jendela tidak bisa ditarik
--multi-instancedesktop.singleInstanceizinkan lebih dari satu jendela untuk proyek yang sama
--icon=nx8v.desktop.icon, atau icon.icoikon jendela (png, jpg, webp, bmp, ico)

Tanpa argumen, NX8V membaca project.json (main dan nx8v.port).

server.listen selesai sebelum jendela membuka URL, jadi halaman index tampil saat unduhan data pertama masih berjalan.

Menyimpan .html, .css, atau .svg memuat ulang halaman di jendela. Menyimpan .js, .mjs, atau .json menutup jendela lalu membukanya lagi supaya kode server yang baru ikut jalan.

Menutup jendela lewat tombol X menghentikan proses dan mengembalikan prompt terminal (kecuali tray aktif — lihat §5). Ctrl+C di terminal tempat nx8v window dijalankan menutup jendela yang sama. Ctrl+C di dalam <nx8v-terminal> tidak menutup jendela; tombol itu milik program di halaman.

Komponen HTML berlaku di dalam jendela, karena halamannya tetap dari server:

<header partial="./header.html"></header>

3. Bridge window.nx8v

Tersedia otomatis di semua halaman (di-inject sebelum skrip halaman berjalan).

// Jendela
nx8v.setTitle('Judul Baru');
nx8v.setSize(1200, 800);
nx8v.minimize();
nx8v.maximize();          // toggle maximize/restore
nx8v.openDevTools();
nx8v.close();
MethodKeterangan
setTitle(text)ubah judul jendela
setSize(w, h)ubah ukuran jendela
minimize()minimize
maximize()maximize / restore
openDevTools()buka DevTools WebView2. Tidak melakukan apa pun bila desktop.devtools adalah false
close()tutup aplikasi

Title bar sendiri

"titlebar": false menyembunyikan title bar Windows. Halaman menggambar bilahnya sendiri. Elemen data-nx8v-drag bisa diseret untuk memindahkan jendela. Tombol di dalamnya memakai data-nx8v-nodrag supaya tetap bisa diklik. "titlebar": true menampilkan title bar Windows.

<header data-nx8v-drag>
  <span>Aplikasi demo</span>
  <button data-nx8v-nodrag onclick="nx8v.minimize()">—</button>
  <button data-nx8v-nodrag onclick="nx8v.maximize()">□</button>
  <button data-nx8v-nodrag onclick="nx8v.close()">×</button>
</header>

nx8v.setTitle(...) tetap mengubah nama di taskbar. Tulisan di bilah halaman diatur oleh HTML. Contohnya ada di example/public/titlebar.html.

Terminal di halaman

Satu tag menempelkan shell interaktif di halaman. Di Windows sesinya ConPTY, jadi prompt, warna, dan tombol sampai ke program yang dijalankan di shell itu, termasuk agent CLI. Terminal ini hanya ada di Windows.

<nx8v-terminal></nx8v-terminal>
<nx8v-terminal height="30%"></nx8v-terminal>

Cukup tag itu. Jangan menambah <script src="terminal">. Runtime menyisipkan skrip elemen saat halaman HTML dikirim. Terminal bukan entri dependencies dan bukan modul.

Satu elemen memakai satu shell. Shell yang dicari, berurutan: pwsh.exe, PowerShell 7, Windows PowerShell, lalu cmd. Folder awalnya folder pengguna, sama seperti jendela PowerShell baru, bukan folder proyek.

Lebarselalu 100% dari induknya
Tinggi bawaan360px
Tinggi minimum144px, supaya prompt tetap kelihatan
Atribut heightnilai CSS: 30%, 30vh, 240px

30% dihitung dari tinggi induk. Kalau induk tidak punya tinggi, kotak tetap tampil setinggi minimum. 30vh dihitung dari tinggi jendela.

Warna 16, 256, dan truecolor digambar seperti yang dikirim program. NX8V tidak mengganti palet agent. Layar alternatif (OpenCode, nxc, dan TUI lain) mengisi terminal sendiri; riwayat shell tidak digabung ke gambar itu.

TombolPerilaku
Ctrl+C, ada teks terpilihsalin teks. Sinyal berhenti tidak dikirim
Ctrl+C, tidak ada pilihansatu tombol berhenti ke program yang sedang berjalan
Ctrl+Shift+Csalin
Ctrl+V, Ctrl+Shift+V, Shift+Inserttempel isi papan klip
Shift+PageUp, Shift+PageDowngulir riwayat shell, selama program tidak memakai layar alternatif
Ctrl+panah, Alt+Enter, F1–F12diteruskan ke program

Kalau program meminta tempelan berkurung, Ctrl+V dibungkus sehingga agent menerimanya sebagai satu tempelan, bukan sebagai deretan perintah. TERM adalah xterm-256color dan COLORTERM adalah truecolor bila lingkungan belum mengisinya.

Pemantau ukuran halaman (ResizeObserver) menunggu elemen <html> ada. Skrip jembatan jendela berjalan sebelum tag itu terbentuk, jadi pemantauan tidak dipasang pada nilai kosong.

Semua method jendela bersifat fire-and-forget (tidak mengembalikan Promise). Dialog dan tray mengembalikan Promise.


4. Dialog native

// Buka file -> Promise<string|null>
const file = await nx8v.dialog.openFile({
  title: 'Pilih file',
  filters: [
    { name: 'JavaScript', extensions: ['js', 'mjs'] },
    { name: 'Semua file', extensions: ['*'] },
  ],
});

// Simpan file -> Promise<string|null>
const target = await nx8v.dialog.saveFile({
  title: 'Simpan sebagai',
  defaultName: 'hasil.txt',
  filters: [{ name: 'Teks', extensions: ['txt'] }],
});

// Pilih folder -> Promise<string|null>
const folder = await nx8v.dialog.openFolder();

// Pesan modal. confirm mengembalikan true bila pengguna memilih Ya.
await nx8v.dialog.alert('Data tersimpan', 'nx8v');
const ya = await nx8v.dialog.confirm('Hapus data ini?', 'nx8v');
OpsiKeterangan
titlejudul dialog
filtersarray { name, extensions: ['js', 'txt', '*'] }
defaultNamenama file awal (khusus saveFile)

alert(pesan, judul) menampilkan kotak OK. confirm(pesan, judul) menampilkan Ya/Tidak dan mengembalikan true bila Ya dipilih.

Nilai null berarti pengguna membatalkan.

Contoh memakai hasilnya:

const path = await nx8v.dialog.openFile({ filters: [{ name: 'Teks', extensions: ['txt'] }] });
if (path) {
  const res = await fetch('/api/read?path=' + encodeURIComponent(path));
  console.log(await res.text());
}
Catatan: dialog berjalan modal di atas jendela; Promise resolve setelah pengguna memilih atau membatalkan.

5. Tray & menu

const ok = await nx8v.tray.show({
  tooltip: 'Aplikasi saya',
  menu: [
    { label: 'Buka jendela', id: 101 },
    { label: 'Tentang', id: 102 },
    '-',                        // separator
    { label: 'Keluar', id: 103 },
  ],
});

nx8v.onMenu(101, () => console.log('buka jendela'));
nx8v.onMenu(102, () => nx8v.notify('nx8v', 'Desktop + WebView2'));
nx8v.onMenu(103, () => nx8v.close());

nx8v.tray.setTooltip('tooltip baru');
nx8v.tray.hide();
APIKeterangan
tray.show({ tooltip, menu })pasang ikon tray, Promise<boolean>
tray.setTooltip(text)ubah tooltip
tray.hide()hapus ikon tray
onMenu(id, fn)callback saat item menu diklik

Perilaku penting:

  • Klik kiri ikon tray → jendela dimunculkan kembali
  • Klik kanan → menu konteks
  • Saat tray aktif, tombol close jendela menyembunyikan ke tray (bukan keluar)
  • exit: false meniadakan entri Keluar. exitLabel mengganti tulisannya. Tanpa itu, Keluar tetap ditambahkan.
  • Item menu: { label, id, enabled, checked, children } atau '-'
  • Klik kanan di halaman diganti dengan nx8v.contextMenu.set([...]). clear() mengembalikan menu browser.

6. Notifikasi

nx8v.notify('Judul', 'Isi pesan');

Balloon notification lewat ikon tray (tray dipasang otomatis bila belum ada). Muncul di pojok kanan bawah Windows.


7. Multi-window

const satu = await nx8v.window.create({
  id: 'satu',
  url: '/detail',
  title: 'Detail',
  width: 800,
  height: 600,
  center: true,
});
const dua = await nx8v.window.create({ id: 'dua', url: '/lain' });

// Tutup satu jendela. Jendela "dua" dan jendela utama tetap terbuka.
nx8v.window.close('satu');
nx8v.window.close(dua);

create mengembalikan id jendela. Tiap jendela butuh id sendiri. Bila id tidak diisi, nx8v mengisi 1, 2, 3, dan seterusnya.

OpsiDefault
urllocation.href (halaman saat ini)
idid untuk window.close. Otomatis 1, 2, … bila kosong
titlenx8v
width900
height640
centertrue, jendela di tengah area kerja. false menyerahkan posisi ke Windows
x, ypojok kiri atas. Bila keduanya diisi, center diabaikan
maximizabletrue. false menghilangkan tombol maximize. Bila tidak diisi, mengikuti project.json

8. Packaging

nx8v package                       # dist/<nama>/
nx8v package --name=toko --out=release

Isi folder hasil:

dist/<nama>/
├── <nama>.exe          # aplikasi ada di dalam exe
├── WebView2Loader.dll  # otomatis bila proyek memakai bridge
├── start.cmd
└── README.txt
dist/<artifactName>     # pemasang Windows, misalnya Aplikasi-demo-Setup-1.0.0.exe
dist/<nama>-<versi>-linux-x64.tar.gz

Satu perintah nx8v package menulis dua berkas unduhan, seperti folder rilis: pemasang Windows Nama-Setup-versi.exe dan arsip Linux Nama-versi-linux-x64.tar.gz. Di Linux: tar -xzf arsip itu, masuk ke foldernya, lalu ./start.sh. Runtime Linux yang dibungkus adalah berkas nx8v-linux di folder yang sama dengan nx8v.exe. Jendela desktop Linux belum ada, jadi paket Linux menjalankan server.

server.js, project.json, dan public/ tidak ikut sebagai berkas terpisah. Saat exe dijalankan, nx8v membuka isi itu dari dalam exe ke cache pribadi. Pemasang dibuat oleh nx8v sendiri. Klik dua kali berkas setup: aplikasi dipasang di %LOCALAPPDATA%\<nama>, dengan pintasan desktop, Start Menu, dan entri hapus pemasangan. Skrip .nsi tetap ditulis. makensis hanya dipakai bila sudah terpasang.

Deteksi otomatis: bila ada file .js/.html yang memuat window.nx8v, nx8v.dialog, atau nx8v.setTitle, paket dianggap aplikasi desktop — WebView2Loader.dll disalin dan start.cmd menjalankan mode window.

Dilewati saat menyalin: node_modules, .git, build, dist, deps, out, .nx8v.


9. Detail teknis

BagianImplementasi
JendelaCreateWindowExW + WNDPROC (src/bindings/window.h)
BrowserWebView2 ICoreWebView2Controller (deps/webview2/)
Message loopdua jalur pump: uv_prepare + uv_timer 4 ms
Bridge masukAddScriptToExecuteOnDocumentCreated + postMessage → WebMessageReceived
Bridge keluarPostWebMessageAsJson (host → halaman)
DialogIFileOpenDialog / IFileSaveDialog (shobjidl.h)
TrayShell_NotifyIconW (NOTIFYICONDATAW)
MenuCreatePopupMenu + TrackPopupMenu
ResizeWM_SIZE → put_Bounds

Kenapa dua jalur pump? uv_prepare hanya jalan saat loop berputar; libuv bisa memblokir lama menunggu I/O (IOCP). Timer 4 ms memastikan pesan Win32 tetap diproses → jendela tidak freeze.

Arah pesan: window.chrome.webview.postMessage() adalah halaman → host. Untuk host → halaman wajib PostWebMessageAsJson (memakai ExecuteScript + postMessage akan mengirim ke arah yang salah).


10. Prasyarat & troubleshooting

Prasyarat:

  • Windows 10/11
  • WebView2 Runtime — sudah terpasang di Windows 11; Windows 10 bisa unduh

dari situs Microsoft (Evergreen Runtime)

  • SDK saat build: NuGet Microsoft.Web.WebView2 → deps/webview2/

(include/, lib/WebView2LoaderStatic.lib, bin/WebView2Loader.dll)

GejalaPenyebab & solusi
Jendela tidak munculWebView2 Runtime tidak ada → cek log error gagal membuat WebView2 environment
ERR_UNSAFE_PORT di http://127.0.0.1:6000/WebView2 menolak port itu. Jendela memakai port aman berikutnya, misalnya 6001. Paket lama perlu nx8v package ulang
Jendela freeze / "not responding"message loop tidak dipompa → pastikan build terbaru (timer pump)
window.nx8v undefinedhalaman dibuka di browser biasa, bukan nx8v window
Dialog tidak munculnx8v window harus mode desktop; cek bridge ada
tray.show() mengembalikan falseikon gagal dipasang (Explorer belum siap) — panggil ulang
WebView2Loader.dll hilang di paketjalankan nx8v package dari exe yang punya DLL di sebelahnya
Tray tidak terlihatWindows 11 menyembunyikan ikon baru di overflow (panah ^)

Contoh di repo

FileIsi
example/public/bridge.htmluji setTitle, dialog open/save/folder
example/public/tray.htmluji tray, menu, notifikasi
example/public/dialogtest.htmldialog otomatis (untuk uji cepat)
example/project.jsondependencies: photos dan contoh
example/server.jsmemanggil modul photos. Halaman dilayani NX8V
example/modules/photos/API foto: /api/photos, /api/status, /api/refresh. Masuknya index.js
example/modules/contoh/modul halaman. Masuknya index.js, dipanggil <script type="module" src="contoh"></script>
example/public/index.htmlhalaman utama galeri, memuat modul contoh
example/public/about.htmlhalaman About, memakai partial yang sama
example/public/header.htmlkomponen header yang dipakai berulang

Pembungkusan

nx8v package membuat folder dist/<nama>/ dan berkas pemasang dist/<artifactName>.

project.json

"nx8v": {
  "port": 4000,
  "desktop": {
    "icon": "./public/assets/images/32x32.png",
    "state": "normal",
    "width": 1100,
    "height": 560,
    "minWidth": 640,
    "minHeight": 400,
    "center": true,
    "maximizable": true,
    "resizable": true,
    "titlebar": true,
    "devtools": false,
    "singleInstance": true,
    "productName": "Aplikasi demo",
    "shortcutName": "Aplikasi demo",
    "artifactName": "Aplikasi-demo-Setup-${version}.exe"
  }
}
KunciPakai
iconpng, jpg, webp, bmp, atau ico untuk jendela, exe, dan pintasan. nx8v init menulis ./images/32x32.png; berkasnya ada di public/assets/images/32x32.png. Path penuh dari folder proyek, misalnya ./public/assets/images/32x32.png, tetap dibaca bila berkasnya ada
statetampilan saat aplikasi diklik: normal, maximized, atau minimized
width, heightukuran jendela. Pada normal ini ukuran yang tampil. Pada maximized atau minimized ini ukuran setelah jendela dikembalikan
minWidth, minHeightbatas terkecil saat pengguna menarik tepi jendela
x, yposisi pojok kiri atas. Bila ada, center diabaikan
centertrue menaruh jendela di tengah layar
maximizablefalse meniadakan tombol maximize. state yang maximized ikut dibuka sebagai normal
resizablefalse mengunci ukuran. Tombol maximize ikut hilang
titlebartrue menampilkan title bar Windows. false menyembunyikannya; halaman memasang title bar sendiri
devtoolstrue menampilkan menu Inspect dan mengizinkan nx8v.openDevTools(). false menyembunyikan menu itu. Produksi memakai false
singleInstancetrue membuat klik kedua memunculkan jendela yang sudah ada
productNamenama yang terlihat pengguna
shortcutNamenama pintasan Start Menu dan desktop
artifactNamenama berkas pemasang; ${version} dan ${name} diganti dari project.json

Tahap 1 — folder yang bisa diklik dua kali

  • Baca desktop.icon. Tanam ikon itu ke dalam exe hasil salinan, supaya Explorer menampilkannya.
  • Exe di samping project.json, bila dijalankan tanpa argumen, membuka nx8v window. start.cmd tidak wajib.
  • Gambar ikon di desktop.icon ikut disalin. PNG tidak perlu diubah menjadi ico oleh pengguna.

Tahap 2 — pemasang

  • nx8v package selalu menulis berkas sesuai artifactName, misalnya dist/Aplikasi-demo-Setup-1.0.0.exe.
  • Klik dua kali berkas itu. Jendela pemasangan terbuka dalam bahasa Inggris: folder, pintasan, dan tombol Install. Tidak ada jendela terminal.
  • Aplikasi dipasang di folder yang dipilih. Bawaan %LOCALAPPDATA%\<nama>.
  • Pemasang membuat pintasan Start Menu dan desktop dengan shortcutName bila kotak pintasan dicentang. Start Menu tidak berisi pintasan hapus.
  • Di folder pemasangan ada Uninstall.exe. Klik dua kali berkas itu untuk membuka jendela hapus. Pengaturan Windows > Aplikasi memakai berkas yang sama.
  • Folder tahap 1 tetap ada untuk dicoba tanpa memasang.
  • Skrip NSIS ikut ditulis. makensis hanya dijalankan bila ada di komputer.

Tahap 3 — menu yang mengikuti aplikasi

  • Item menu: label, id, enabled, checked, pemisah, dan submenu.
  • exit: false meniadakan Keluar bawaan. exitLabel mengganti tulisannya.
  • Klik kanan di halaman bisa diganti lewat nx8v.contextMenu.set([...]), bukan menu browser yang tetap.
Keamanan

SECURITY — Catatan Keamanan NX8V (referensi internal)

Dokumen bacaan: model ancaman, pemetaan kerentanan ekosistem, dan pengerasan terencana. Tidak dimasukkan ke git — sifatnya internal.

1. Permukaan serang (apa yang terbuka ke luar)

PermukaanBerkasCatatan
HTTP server & clienthttp.hparser/keep-alive/chunked ditulis sendiri
HTTPS clienttls.hWinHTTP (Windows) — TLS & validasi sertifikat diserahkan ke Windows; OpenSSL hanya via -DNX8V_USE_OPENSSL (Linux)
WebSocket client/serverws.h, http.hRFC 6455 implementasi sendiri (server) + WinHttpWebSocket (client)
Static serverstatic.hETag/Range/HEAD — anti path traversal sudah ada
Bridge jendelawindow.hpostMessage halaman↔host; desktop.devtools bisa dimatikan produksi
Terminal di halamandesktop.md §TerminalConPTY — input keyboard halaman masuk ke shell
CLIinit.h, dev.h, list.h, logs.hnx8v i/u/p/d/r menyentuh GitHub (dibaca dari gh CLI)

2. Lapisan bersama yang wajib dantau

  • V8 — CVE-nya mengalir kemari (contoh: hash seed / HashDoS, 2026). Respons:

build ulang tinju (increment ~90 detik) segera setelah advisory.

  • libuv — sama; uv_getaddrinfo dipakai dns.
  • zlib — compress.h (gzip/deflate) — pantau advisory zlib.
  • OpenSSL — hanya build Linux (NX8V_USE_OPENSSL).
  • ca-cert Linux — build Linux harus ikut pembaruan trust store; di Windows

validasi sertifikat via WinHTTP + Windows Update (otomatis).

3. Pemetaan batch CVE Node.js 21 Okt 2026 (10 CVE)

CVE kelasPermukaan di nx8vStatus
c-ares — dns.resolve NUL (high)❌ — dns.h pakai uv_getaddrinfotidak berlaku
TLS hostname bypass (fullwidth/loose-IP, high)Windows: delegasi ke WinHTTPtidak berlaku (Win); cek saat build Linux
Brotli OOB-write RCE (high)❌ — zlib module tanpa Brotlitidak berlaku
Undici / SQLite / zipfile❌ — fetch ditulis sendiri; tanpa modul itutidak berlaku
Keep-alive pra-request (medium)kelas serupa ada di http.h sendiri🔶 jaga: selalu Connection: close + fuzzing
APA entropi randomUUID di snapshot (medium)❌ tidak ada snapshot — crypto.h pakai std::random_device per panggilantidak berlaku (lihat §4-i)
Permission Model ×2 (rendah)❌ belum ada permission modeltidak berlaku — 🔶 trade-off: proteksinya juga belum ada
ca-cert (eMudhra, medium)Windows tidak terdampak; Linux ikut OpenSSL🔶 cek pada build Linux

4. Pengerasan terencana (urut prioritas)

  1. Fuzzing http.h / ws.h — ✅ berlaku sejak v0.2.2: perintah

nx8v --fuzz (korpus + 20.000 mutasi, benih --fuzz=1:20000). Jawaban untuk kelas "CVE RCE tanpa autentikasi": parser C++ milik sendiri.

  1. crypto.h — ✅ berlaku sejak v0.2.2: randomBytes/randomUUID kini

lewat BCryptGenRandom (Windows) dan getrandom (Linux).

  1. Rilis — ✅ berlaku sejak v0.2.2: aset SHA256SUMS dilampirkan dan

install.ps1 memverifikasi hash sebelum memasang.

  1. Linux — audit versi OpenSSL terbundel + pembaruan ca-cert path sebelum

pemasangan dipublikasikan luas.

  1. Terminal bridge — uji batas pesan halaman↔host; halaman tak boleh bisa

meminta operasi di luar set (setTitle/setSize/...) tanpa hardening.

  1. Batas HTTPS client — req.httpVersion berhenti di HTTP/2 via ALPN;

pantau spec upgrade berikutnya.

5. Disklaimer

  • Batch CVE-2026-10-21 Node.js tidak menular langsung ke nx8v karena

lapisan yang bermasalahnya (c-ares/Brotli/Undici/SQLite/permission/ca-cert) tidak diimplementasi ulang di sini — namun kelas kerentanannya (keep-alive, entropi, path, parser) tetap relevan dan masuk §4.

  • PHP RCE (2 CVE, 21 Okt 2026) — stack berbeda; pelajaran: parser

tanpa autentikasi harus selalu diasumsikan diserang.