@mmo69/vmcode 0.0.0-stage → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (4) hide show
  1. package/README.md +196 -2
  2. package/README_VI.md +194 -0
  3. package/index.mjs +104 -0
  4. package/package.json +25 -4
package/README.md CHANGED
@@ -1,3 +1,197 @@
1
- # Temporary Holding Version
1
+ # vmcode
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ > [🇻🇳 Tiếng Việt](README_VI.md) • 🇬🇧 English
4
+
5
+ NodeJS SDK for the **JsVM.MMO69.Com** VMCode protection API — compile plain
6
+ JavaScript into server-side TNVM **VM bytecode** without shipping the engine
7
+ to the browser.
8
+
9
+ - **Async task API** (captcha-solver style): submit → `taskId` → poll — made
10
+ for large files and slow presets
11
+ - **Zero dependencies** (Node ≥ 18, built-in `fetch`)
12
+ - **gzip uploads** for large payloads
13
+ - Needs an **API key** (`tnvm_…`, Pro plan or higher) — create one at
14
+ <https://JsVM.MMO69.Com/client/apikeys.html>
15
+
16
+ ## Install
17
+
18
+ ```bash
19
+ npm i @mmo69/vmcode
20
+ # or just copy index.mjs — the whole library is a single file
21
+ ```
22
+
23
+ ## Quick start
24
+
25
+ ```js
26
+ import { VmCode } from '@mmo69/vmcode';
27
+ import fs from 'node:fs';
28
+
29
+ const vm = new VmCode({ apiKey: 'tnvm_YOUR_KEY' });
30
+
31
+ const out = await vm.protect(
32
+ fs.readFileSync('src/app.js', 'utf8'),
33
+ { preset: 'medium', target: 'node' },
34
+ );
35
+ fs.writeFileSync('app.protected.js', out.result);
36
+ console.log(`Done in ${out.elapsed}ms — quota used: ${out.usage?.bytes}/${out.usage?.quotaMB}MB`);
37
+ ```
38
+
39
+ Or protect a single function inline:
40
+
41
+ ```js
42
+ const { result } = await vm.protect('function f(x){ return x*3+1; }', { preset: 'low' });
43
+ console.log(result); // protected code — runs exactly like the original
44
+ ```
45
+
46
+ ## Presets
47
+
48
+ | Option (order matches the tables below) | `low` | `medium` | `max` |
49
+ |---|:---:|:---:|:---:|
50
+ | `targetMode` | `root` | `root` | `root` |
51
+ | `preprocessIdentifiers` | – | ✓ | ✓ |
52
+ | `encryptBytecode` | – | ✓ | ✓ |
53
+ | `debugProtection` | – | – | ✓ |
54
+ | `dynamicOpcodes` | ✓ | ✓ | ✓ |
55
+ | `decoyOpcodes` | – | ✓ | ✓ |
56
+ | `deadCodeInjection` | – | – | ✓ |
57
+ | `stackEncoding` | – | – | ✓ |
58
+ | `rollingCipher` | – | ✓ | ✓ |
59
+ | `integrityBinding` | – | – | ✓ |
60
+ | `vmShielding` | – | – | ✓ |
61
+ | `mixedBooleanArithmetic` | – | – | ✓ |
62
+ | `handlerFragmentation` | – | – | ✓ |
63
+ | `stringAtomization` | – | ✓ | ✓ |
64
+ | `polymorphicDecoder` | – | ✓ | ✓ |
65
+ | `scatteredKeys` | – | ✓ | ✓ |
66
+ | `blockPermutation` | – | – | ✓ |
67
+ | `opcodeMutation` | – | – | ✓ |
68
+ | `bytecodeScattering` | – | ✓ | ✓ |
69
+ | `incrementalCipher` | – | – | ✓ |
70
+ | `semanticOpacity` | – | – | ✓ |
71
+ | `observationResistance` | – | – | ✓ |
72
+
73
+ - **`low`** *(default)* — fastest, only `dynamicOpcodes`: plain VM bytecode,
74
+ compact output. A 122KB file takes ~a few hundred ms.
75
+ - **`medium`** — balanced: 9/21 options on. A 122KB file takes ~3s.
76
+ - **`max`** — all 21 options. Strongest but slow — **cost scales with
77
+ FUNCTION COUNT**; a many-function file (800+) can take minutes — raise
78
+ `timeoutMs` when using it.
79
+
80
+ **Auto-enable** (the engine turns these on by itself, no need to declare):
81
+ enabling `integrityBinding` / `vmShielding` / `opcodeMutation` /
82
+ `incrementalCipher` / `observationResistance` → auto-enables `rollingCipher`;
83
+ enabling `stringAtomization` → auto-enables `polymorphicDecoder`.
84
+
85
+ ## Engine options
86
+
87
+ Pass as the second argument of `submit()` / `protect()`. Booleans only work
88
+ as **opt-IN additions** to the preset (the server only accepts `true` — you
89
+ cannot turn off an option the preset already enables):
90
+
91
+ ```js
92
+ await vm.protect(code, {
93
+ preset: 'medium', // low | medium | max (default low)
94
+ targetMode: 'comment', // root (default) | comment — Pro+
95
+ target: 'node', // browser (default) | node — Pro+
96
+ rollingCipher: true, // turn an extra option ON top of the preset
97
+ vmShielding: true, // → rollingCipher comes along automatically
98
+ });
99
+ ```
100
+
101
+ ### `targetMode`
102
+
103
+ - **`root`** *(default)* — protects ALL top-level functions of the file
104
+ - **`comment`** *(Pro+)* — only protects functions marked by a leading comment:
105
+ ```js
106
+ // tnvm:vm
107
+ function secret(a, b) { /* only this function gets VM-compiled */ }
108
+ ```
109
+
110
+ ### `target`
111
+
112
+ - **`browser`** *(default)* — output runs in the browser
113
+ - **`node`** *(Pro+)* — output runs in NodeJS
114
+
115
+ ### Security
116
+
117
+ | Option | Description |
118
+ |---|---|
119
+ | `rollingCipher` | Position-dependent XOR encryption on every instruction |
120
+ | `integrityBinding` | Bind bytecode decryption to interpreter source integrity |
121
+ | `debugProtection` | Multi-layered anti-debugger with escalating response |
122
+ | `vmShielding` | Per-function micro-interpreters with independent opcode shuffle |
123
+ | `incrementalCipher` | Move instruction decryption into the VM dispatch loop |
124
+ | `semanticOpacity` | Opaque predicates, handler aliasing, and encoding diversity |
125
+ | `observationResistance` | Silent computation corruption when instrumentation detected |
126
+ | `encryptBytecode` | RC4 encryption using an environment fingerprint key |
127
+
128
+ ### Obfuscation
129
+
130
+ | Option | Description |
131
+ |---|---|
132
+ | `mixedBooleanArithmetic` | Replace arithmetic/bitwise ops with MBA expressions |
133
+ | `stackEncoding` | XOR-encode VM stack values during execution |
134
+ | `deadCodeInjection` | Insert unreachable bytecode sequences after RETURN |
135
+ | `handlerFragmentation` | Split handlers into interleaved fragments |
136
+ | `stringAtomization` | Replace string literals with encoded table lookups |
137
+ | `blockPermutation` | Shuffle bytecode basic block order |
138
+ | `opcodeMutation` | Runtime handler table mutations via MUTATE opcodes |
139
+ | `bytecodeScattering` | Split bytecode into mixed-type fragments scattered through output |
140
+
141
+ ### Optimization
142
+
143
+ | Option | Description |
144
+ |---|---|
145
+ | `preprocessIdentifiers` | Rename identifiers before compilation |
146
+ | `dynamicOpcodes` | Filter unused opcode handlers from interpreter |
147
+ | `decoyOpcodes` | Inject realistic fake opcode handlers |
148
+ | `polymorphicDecoder` | Per-build random chain of reversible byte operations |
149
+ | `scatteredKeys` | Fragment key materials across closure tiers |
150
+
151
+ ## Plan gating
152
+
153
+ API keys are only issued from the **Pro** plan up, so any valid `tnvm_…` key
154
+ unlocks every preset and all custom options:
155
+
156
+ | Plan | Presets | Custom options | Max request size |
157
+ |---|---|:---:|---|
158
+ | Anonymous (web) | low, medium | ✗ | 128KB |
159
+ | Free | low, medium | ✗ | 268KB |
160
+ | **Pro** | low, medium, max | ✓ | 1MB |
161
+ | Team / Business | low, medium, max | ✓ + priority queue | 3MB |
162
+
163
+ ## API
164
+
165
+ ### `new VmCode({ apiKey, baseUrl, pollMs, timeoutMs, gzip })`
166
+
167
+ | Option | Default | Note |
168
+ |---|---|---|
169
+ | `apiKey` | — | **required**, `tnvm_…` (Pro+) |
170
+ | `baseUrl` | `https://JsVM.MMO69.Com` | change for a local server |
171
+ | `pollMs` | `1000` | result polling interval |
172
+ | `timeoutMs` | `600000` | max wait of `protect()` (preset `max` on many-function files can take minutes) |
173
+ | `gzip` | `true` | gzip requests > 1KB |
174
+
175
+ ### Methods
176
+
177
+ | Method | Returns |
178
+ |---|---|
179
+ | `vm.protect(code, options?, callOpts?)` | `{ result, elapsed, usage? }` — submits and polls until done |
180
+ | `vm.submit(code, options?)` | `{ taskId, status: 'pending' }` — creates the task, keep the `taskId` |
181
+ | `vm.poll(taskId)` | `{status:'pending'\|'running'}` \| `done{result,elapsed,usage}` \| `error{error}` \| `gone` |
182
+ | `vm.meta()` | `{ options, presets, plans, … }` from `/api/meta` |
183
+
184
+ `callOpts` = `{ pollMs, timeoutMs }` overrides the constructor config for a
185
+ single call.
186
+
187
+ **The result can be fetched only ONCE** — after a `done` poll the task is
188
+ deleted; unclaimed tasks expire after 2 hours. Queue-friendly: after
189
+ `submit()` you can do other work and `poll()` whenever you're ready.
190
+
191
+ ### `VmError`
192
+
193
+ Every failure throws a `VmError` — `err.code` matches the API error codes:
194
+ `size` `options_locked` `quota` `rate` `engine` `task` `timeout` `network` …
195
+ (`err.status` = HTTP status, `0` = network/timeout error on the SDK side).
196
+ On `protect()` timeout the message already includes the `taskId` so you can
197
+ resume polling manually.
package/README_VI.md ADDED
@@ -0,0 +1,194 @@
1
+ # vmcode
2
+
3
+ > 🇻🇳 Tiếng Việt • [🇬🇧 English](README.md)
4
+
5
+ SDK NodeJS cho API bảo vệ mã **JsVM.MMO69.Com** (TNVM VMCode) — biên dịch
6
+ JavaScript thường thành **VM bytecode** xử lý bên server, không ship engine ra
7
+ trình duyệt.
8
+
9
+ - **API bất đồng bộ** kiểu captcha-solver: submit → `taskId` → poll — sinh ra
10
+ cho file lớn và preset chậm
11
+ - **Zero dependencies** (Node ≥ 18, dùng `fetch` có sẵn)
12
+ - **gzip upload** cho payload lớn
13
+ - Cần **API key** (`tnvm_…`, gói Pro trở lên) — tạo tại
14
+ [https://JsVM.MMO69.Com/client/apikeys.html](https://JsVM.MMO69.Com/client/apikeys.html)
15
+
16
+ ## Cài đặt
17
+
18
+ ```bash
19
+ npm i @mmo69/vmcode
20
+ # hoặc chỉ copy index.mjs — cả thư viện gói gọn trong 1 file
21
+ ```
22
+
23
+ ## Dùng nhanh
24
+
25
+ ```js
26
+ import { VmCode } from '@mmo69/vmcode';
27
+ import fs from 'node:fs';
28
+
29
+ const vm = new VmCode({ apiKey: 'tnvm_CUA_BAN' });
30
+
31
+ const out = await vm.protect(
32
+ fs.readFileSync('src/app.js', 'utf8'),
33
+ { preset: 'medium', target: 'node' },
34
+ );
35
+ fs.writeFileSync('app.protected.js', out.result);
36
+ console.log(`Xong sau ${out.elapsed}ms — đã dùng ${out.usage?.bytes}/${out.usage?.quotaMB}MB`);
37
+ ```
38
+
39
+ Hoặc inline 1 hàm:
40
+
41
+ ```js
42
+ const { result } = await vm.protect('function f(x){ return x*3+1; }', { preset: 'low' });
43
+ console.log(result); // mã đã bảo vệ — chạy y hệt bản gốc
44
+ ```
45
+
46
+ ## Preset
47
+
48
+ | Option (thứ tự trong bảng ở dưới) | `low` | `medium` | `max` |
49
+ | --------------------------------------- | :------: | :--------: | :------: |
50
+ | `targetMode` | `root` | `root` | `root` |
51
+ | `preprocessIdentifiers` | – | ✓ | ✓ |
52
+ | `encryptBytecode` | – | ✓ | ✓ |
53
+ | `debugProtection` | – | – | ✓ |
54
+ | `dynamicOpcodes` | ✓ | ✓ | ✓ |
55
+ | `decoyOpcodes` | – | ✓ | ✓ |
56
+ | `deadCodeInjection` | – | – | ✓ |
57
+ | `stackEncoding` | – | – | ✓ |
58
+ | `rollingCipher` | – | ✓ | ✓ |
59
+ | `integrityBinding` | – | – | ✓ |
60
+ | `vmShielding` | – | – | ✓ |
61
+ | `mixedBooleanArithmetic` | – | – | ✓ |
62
+ | `handlerFragmentation` | – | – | ✓ |
63
+ | `stringAtomization` | – | ✓ | ✓ |
64
+ | `polymorphicDecoder` | – | ✓ | ✓ |
65
+ | `scatteredKeys` | – | ✓ | ✓ |
66
+ | `blockPermutation` | – | – | ✓ |
67
+ | `opcodeMutation` | – | – | ✓ |
68
+ | `bytecodeScattering` | – | ✓ | ✓ |
69
+ | `incrementalCipher` | – | – | ✓ |
70
+ | `semanticOpacity` | – | – | ✓ |
71
+ | `observationResistance` | – | – | ✓ |
72
+
73
+ - **`low`** *(mặc định)* — nhanh nhất, chỉ `dynamicOpcodes`: bytecode VM thuần,
74
+ output gọn. File 122KB ~vài trăm ms.
75
+ - **`medium`** — cân bằng: 9/21 option bật. File 122KB ~3s.
76
+ - **`max`** — tất cả 21 option. Mạnh nhất nhưng chậm — **thời gian tăng theo
77
+ SỐ HÀM**, file nhiều hàm (800+) có thể mất vài phút; tăng `timeoutMs` khi dùng.
78
+
79
+ **Auto-enable** (engine tự bật kèm, không cần khai báo):
80
+ bật `integrityBinding` / `vmShielding` / `opcodeMutation` / `incrementalCipher` /
81
+ `observationResistance` → tự bật `rollingCipher`; bật `stringAtomization` → tự
82
+ bật `polymorphicDecoder`.
83
+
84
+ ## Tuỳ chọn engine (options)
85
+
86
+ Truyền vào tham số thứ 2 của `submit()` / `protect()`. **Boolean chỉ có tác
87
+ dụng BẬT THÊM** ngoài preset (server chỉ nhận `true` — không thể tắt option mà
88
+ preset đã bật):
89
+
90
+ ```js
91
+ await vm.protect(code, {
92
+ preset: 'medium', // low | medium | max (mặc định low)
93
+ targetMode: 'comment', // root (mặc định) | comment — Pro+
94
+ target: 'node', // browser (mặc định) | node — Pro+
95
+ rollingCipher: true, // bật THÊM option ngoài preset
96
+ vmShielding: true, // → tự bật rollingCipher kèm theo
97
+ });
98
+ ```
99
+
100
+ ### `targetMode`
101
+
102
+ - **`root`** *(mặc định)* — bảo vệ TOÀN BỘ hàm top-level của file
103
+ - **`comment`** *(Pro+)* — chỉ bảo vệ hàm có comment đánh dấu ngay phía trên:
104
+ ```js
105
+ // tnvm:vm
106
+ function secret(a, b) { /* chỉ hàm này được VM hoá */ }
107
+ ```
108
+
109
+ ### `target`
110
+
111
+ - **`browser`** *(mặc định)* — output chạy trong trình duyệt
112
+ - **`node`** *(Pro+)* — output chạy trong NodeJS
113
+
114
+ ### Bảo mật (Security)
115
+
116
+ | Option | Mô tả |
117
+ | ------------------------- | ---------------------------------------------------------------------------- |
118
+ | `rollingCipher` | Mã hoá XOR phụ thuộc vị trí trên từng instruction |
119
+ | `integrityBinding` | Ràng buộc giải mã bytecode vào tính toàn vẹn source của interpreter |
120
+ | `debugProtection` | Chống debugger đa tầng, phản ứng leo thang dần |
121
+ | `vmShielding` | Micro-interpreter riêng cho từng hàm, xáo trộn opcode độc lập |
122
+ | `incrementalCipher` | Đưa việc giải mã instruction vào vòng dispatch của VM |
123
+ | `semanticOpacity` | Opaque predicates, alias handler, đa dạng hoá kiểu mã hoá |
124
+ | `observationResistance` | Âm thầm làm hỏng kết quả tính toán khi phát hiện instrumentation |
125
+ | `encryptBytecode` | Mã hoá RC4 bằng khoá fingerprint của môi trường chạy |
126
+
127
+ ### Nghiền mã (Obfuscation)
128
+
129
+ | Option | Mô tả |
130
+ | -------------------------- | -------------------------------------------------------- |
131
+ | `mixedBooleanArithmetic` | Thay phép toán số học/bitwise bằng biểu thức MBA |
132
+ | `stackEncoding` | XOR-encode giá trị trên stack VM trong lúc chạy |
133
+ | `deadCodeInjection` | Chèn chuỗi bytecode không thể tới ngay sau RETURN |
134
+ | `handlerFragmentation` | Tách handler thành các mảnh đan xen nhau |
135
+ | `stringAtomization` | Thay string literal bằng tra bảng đã mã hoá |
136
+ | `blockPermutation` | Xáo trộn thứ tự basic block của bytecode |
137
+ | `opcodeMutation` | Đột biến bảng handler lúc runtime qua opcode MUTATE |
138
+ | `bytecodeScattering` | Tách bytecode thành mảnh trộn lẫn rải khắp output |
139
+
140
+ ### Tối ưu (Optimization)
141
+
142
+ | Option | Mô tả |
143
+ | ------------------------- | ------------------------------------------------------------ |
144
+ | `preprocessIdentifiers` | Đổi tên identifier trước khi compile |
145
+ | `dynamicOpcodes` | Lọc opcode handler không dùng khỏi interpreter |
146
+ | `decoyOpcodes` | Tiêm các handler opcode giả giống thật |
147
+ | `polymorphicDecoder` | Chuỗi phép toán byte ngẫu nhiên, đổi theo từng build |
148
+ | `scatteredKeys` | Phân mảnh key material qua các tầng closure |
149
+
150
+ ## Phân quyền theo gói
151
+
152
+ API key chỉ cấp từ gói **Pro** trở lên, nên với key `tnvm_…` mọi preset và
153
+ tuỳ biến đều mở:
154
+
155
+ | Gói | Preset | Tuỳ biến option | Giới hạn / request |
156
+ | --------------- | ---------------- | :------------------------: | -------------------- |
157
+ | Ẩn danh (web) | low, medium | ✗ | 128KB |
158
+ | Free | low, medium | ✗ | 268KB |
159
+ | **Pro** | low, medium, max | ✓ | 1MB |
160
+ | Team / Business | low, medium, max | ✓ + hàng đợi ưu tiên | 3MB |
161
+
162
+ ## API
163
+
164
+ ### `new VmCode({ apiKey, baseUrl, pollMs, timeoutMs, gzip })`
165
+
166
+ | Option | Default | Ghi chú |
167
+ | ------------- | -------------------------- | ------------------------------------------------------------------------------------------------------ |
168
+ | `apiKey` | — | **bắt buộc**, `tnvm_…` (Pro+) |
169
+ | `baseUrl` | `https://JsVM.MMO69.Com` | đổi khi chạy server local |
170
+ | `pollMs` | `1000` | nhịp poll kết quả |
171
+ | `timeoutMs` | `600000` | thời gian chờ tối đa của `protect()` (preset `max` file nhiều hàm có thể mất vài phút) |
172
+ | `gzip` | `true` | nén gzip request > 1KB |
173
+
174
+ ### Phương thức
175
+
176
+ | Phương thức | Trả về |
177
+ | ----------------------------------------- | -------------------------------------------------------------------------------------------------- |
178
+ | `vm.protect(code, options?, callOpts?)` | `{ result, elapsed, usage? }` — submit + tự poll tới khi xong |
179
+ | `vm.submit(code, options?)` | `{ taskId, status: 'pending' }` — tạo task, giữ `taskId` |
180
+ | `vm.poll(taskId)` | `{status:'pending'\|'running'}` \| `done{result,elapsed,usage}` \| `error{error}` \| `gone` |
181
+ | `vm.meta()` | `{ options, presets, plans, … }` từ `/api/meta` |
182
+
183
+ `callOpts` = `{ pollMs, timeoutMs }` đè cấu hình constructor cho 1 lần gọi.
184
+
185
+ **Kết quả chỉ lấy được MỘT LẦN** — poll nhận `done` xong task bị xoá; task
186
+ không ai lấy sẽ hết hạn sau 2 giờ. Queue-friendly: `submit()` xong có thể
187
+ làm việc khác, `poll()` khi cần.
188
+
189
+ ### `VmError`
190
+
191
+ Mọi lỗi đều throw `VmError` — `err.code` khớp mã lỗi API:
192
+ `size` `options_locked` `quota` `rate` `engine` `task` `timeout` `network` …
193
+ (`err.status` = HTTP status, `0` = lỗi mạng/timeout phía SDK).
194
+ `protect()` timeout thì message kèm sẵn `taskId` để poll thủ công tiếp.
package/index.mjs ADDED
@@ -0,0 +1,104 @@
1
+ // vmcode — NodeJS SDK for the JsVM.MMO69.Com code-protection API (TNVM VMCode).
2
+ // Async captcha-solver style API: POST /api/vm → {taskId} → poll POST /api/vm/result.
3
+ // Requires an API key (tnvm_…, Pro plan+) — create at https://JsVM.MMO69.Com/client/apikeys.html
4
+ // Zero dependencies — only the Node ≥ 18 built-in fetch (gzip responses auto-decompressed).
5
+
6
+ import zlib from 'node:zlib';
7
+
8
+ const DEFAULT_BASE = 'https://JsVM.MMO69.Com';
9
+ const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
10
+
11
+ /** Error thrown by the SDK/API — err.code matches API codes: size, options_locked, quota, rate, engine, task, timeout, network… */
12
+ export class VmError extends Error {
13
+ constructor(message, code, status = 0) {
14
+ super(message);
15
+ this.name = 'VmError';
16
+ this.code = code || 'error';
17
+ this.status = status; // HTTP status (0 = network/timeout error on the SDK side)
18
+ }
19
+ }
20
+
21
+ export class VmCode {
22
+ /**
23
+ * @param {object} opts
24
+ * @param {string} opts.apiKey tnvm_… API key (required — Pro plan or higher)
25
+ * @param {string} [opts.baseUrl] default https://JsVM.MMO69.Com (change for local/dev)
26
+ * @param {number} [opts.pollMs] result polling interval (default 1000)
27
+ * @param {number} [opts.timeoutMs] max wait of protect() (default 10 minutes)
28
+ * @param {boolean} [opts.gzip] gzip requests > 1KB (default true)
29
+ */
30
+ constructor({ apiKey, baseUrl, pollMs = 1000, timeoutMs = 10 * 60_000, gzip = true } = {}) {
31
+ if (!apiKey) throw new VmError('Missing apiKey (tnvm_…) — create one at /client/apikeys.html (Pro plan+)', 'apikey');
32
+ this.apiKey = String(apiKey);
33
+ this.base = String(baseUrl || DEFAULT_BASE).replace(/\/+$/, '');
34
+ this.pollMs = pollMs;
35
+ this.timeoutMs = timeoutMs;
36
+ this.gzip = gzip;
37
+ }
38
+
39
+ async #post(path, body) {
40
+ const json = JSON.stringify(body);
41
+ const headers = { 'content-type': 'application/json', authorization: `Bearer ${this.apiKey}` };
42
+ let payload = json;
43
+ if (this.gzip && json.length > 1024) { // large body → gzip it, the server decompresses automatically
44
+ payload = zlib.gzipSync(Buffer.from(json));
45
+ headers['content-encoding'] = 'gzip';
46
+ }
47
+ let res;
48
+ try {
49
+ res = await fetch(this.base + path, { method: 'POST', headers, body: payload });
50
+ } catch (err) {
51
+ throw new VmError(`Cannot connect to ${this.base}: ${err.message}`, 'network');
52
+ }
53
+ const data = await res.json().catch(() => null);
54
+ if (!res.ok) throw new VmError((data && data.error) || `HTTP ${res.status}`, (data && data.code) || 'http', res.status);
55
+ return data;
56
+ }
57
+
58
+ /** GET /api/meta — options + presets + plans (ground truth for building an option UI) */
59
+ async meta() {
60
+ let res;
61
+ try { res = await fetch(this.base + '/api/meta', { headers: { authorization: `Bearer ${this.apiKey}` } }); }
62
+ catch (err) { throw new VmError(`Cannot connect to ${this.base}: ${err.message}`, 'network'); }
63
+ const data = await res.json().catch(() => null);
64
+ if (!res.ok) throw new VmError((data && data.error) || `HTTP ${res.status}`, (data && data.code) || 'http', res.status);
65
+ return data;
66
+ }
67
+
68
+ /** Step 1 — create a compile task → {taskId, status:'pending'} (keep the taskId for polling) */
69
+ submit(code, options = {}) {
70
+ if (typeof code !== 'string' || !code.trim()) throw new VmError('Missing code (JavaScript source string)', 'missing');
71
+ return this.#post('/api/vm', { code, options });
72
+ }
73
+
74
+ /** Step 2 — poll the task → {status:'pending'|'running'} | done{result,elapsed,usage} | error | gone */
75
+ poll(taskId) {
76
+ return this.#post('/api/vm/result', { taskId });
77
+ }
78
+
79
+ /**
80
+ * Submit + auto-poll until done — the quickest way to use the SDK.
81
+ * @param {string} code source JavaScript
82
+ * @param {object} options preset 'low'|'medium'|'max' + targetMode 'root'|'comment' +
83
+ * target 'browser'|'node' + boolean engine options (see vm.meta())
84
+ * @param {object} [callOpts] {pollMs, timeoutMs} overrides the constructor config for this call
85
+ * @returns {Promise<{result:string, elapsed:number, usage?:object}>}
86
+ */
87
+ async protect(code, options = {}, { pollMs = this.pollMs, timeoutMs = this.timeoutMs } = {}) {
88
+ const { taskId } = await this.submit(code, options);
89
+ const deadline = Date.now() + timeoutMs;
90
+ for (;;) {
91
+ if (Date.now() > deadline) {
92
+ throw new VmError(`Timeout after ${timeoutMs}ms — task ${taskId} not finished, keep polling with poll("${taskId}")`, 'timeout');
93
+ }
94
+ await sleep(pollMs);
95
+ const r = await this.poll(taskId);
96
+ if (r.status === 'done') return { result: r.result, elapsed: r.elapsed, usage: r.usage };
97
+ if (r.status === 'error') throw new VmError(r.error || 'Engine failed to process the code', 'engine');
98
+ if (r.status === 'gone') throw new VmError(r.error || 'Task not found or result already fetched', 'task');
99
+ // pending / running → wait for the next tick
100
+ }
101
+ }
102
+ }
103
+
104
+ export default VmCode;
package/package.json CHANGED
@@ -1,6 +1,27 @@
1
1
  {
2
2
  "name": "@mmo69/vmcode",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "1.0.0",
4
+ "description": "NodeJS SDK for the JsVM.MMO69.Com VMCode API — protect JavaScript with server-side TNVM VM bytecode (async task: submit → taskId → poll).",
5
+ "type": "module",
6
+ "main": "index.mjs",
7
+ "exports": {
8
+ ".": "./index.mjs"
9
+ },
10
+ "repository": {
11
+ "type": "git",
12
+ "url": "git+https://github.com/nttrung9x/vmcode.git"
13
+ },
14
+ "files": [
15
+ "index.mjs",
16
+ "README.md",
17
+ "README_VI.md"
18
+ ],
19
+ "engines": {
20
+ "node": ">=18"
21
+ },
22
+ "keywords": [
23
+ "javascript", "obfuscator", "vm", "protect", "bytecode", "tnvm", "jsvm", "mmo69"
24
+ ],
25
+ "homepage": "https://JsVM.MMO69.Com",
26
+ "license": "MIT"
27
+ }