@mmo69/vmcode 0.0.0-stage → 1.1.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.
- package/README.md +197 -2
- package/README_VI.md +196 -0
- package/index.mjs +104 -0
- package/package.json +25 -4
package/README.md
CHANGED
|
@@ -1,3 +1,198 @@
|
|
|
1
|
-
#
|
|
1
|
+
# vmcode
|
|
2
2
|
|
|
3
|
-
|
|
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. Roughly ~1s per MB of source.
|
|
76
|
+
- **`max`** — all 21 options. Strongest but slow — cost scales with
|
|
77
|
+
**function count and file size** (~6–7s per MB): a 3MB file takes ~20s,
|
|
78
|
+
a 20MB file can take several minutes — `timeoutMs` (21 min) already covers 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 | ✓ | 5MB |
|
|
161
|
+
| Team | low, medium, max | ✓ + priority queue | 12MB |
|
|
162
|
+
| Business | low, medium, max | ✓ + priority queue | 20MB |
|
|
163
|
+
|
|
164
|
+
## API
|
|
165
|
+
|
|
166
|
+
### `new VmCode({ apiKey, baseUrl, pollMs, timeoutMs, gzip })`
|
|
167
|
+
|
|
168
|
+
| Option | Default | Note |
|
|
169
|
+
|---|---|---|
|
|
170
|
+
| `apiKey` | — | **required**, `tnvm_…` (Pro+) |
|
|
171
|
+
| `baseUrl` | `https://JsVM.MMO69.Com` | change for a local server |
|
|
172
|
+
| `pollMs` | `1000` | result polling interval |
|
|
173
|
+
| `timeoutMs` | `1260000` | max wait of `protect()` — the server compiles max-size files up to 20 min, default 21 min covers it |
|
|
174
|
+
| `gzip` | `true` | gzip requests > 1KB |
|
|
175
|
+
|
|
176
|
+
### Methods
|
|
177
|
+
|
|
178
|
+
| Method | Returns |
|
|
179
|
+
|---|---|
|
|
180
|
+
| `vm.protect(code, options?, callOpts?)` | `{ result, elapsed, usage? }` — submits and polls until done |
|
|
181
|
+
| `vm.submit(code, options?)` | `{ taskId, status: 'pending' }` — creates the task, keep the `taskId` |
|
|
182
|
+
| `vm.poll(taskId)` | `{status:'pending'\|'running'}` \| `done{result,elapsed,usage}` \| `error{error}` \| `gone` |
|
|
183
|
+
| `vm.meta()` | `{ options, presets, plans, … }` from `/api/meta` |
|
|
184
|
+
|
|
185
|
+
`callOpts` = `{ pollMs, timeoutMs }` overrides the constructor config for a
|
|
186
|
+
single call.
|
|
187
|
+
|
|
188
|
+
**The result can be fetched only ONCE** — after a `done` poll the task is
|
|
189
|
+
deleted; unclaimed tasks expire after 2 hours. Queue-friendly: after
|
|
190
|
+
`submit()` you can do other work and `poll()` whenever you're ready.
|
|
191
|
+
|
|
192
|
+
### `VmError`
|
|
193
|
+
|
|
194
|
+
Every failure throws a `VmError` — `err.code` matches the API error codes:
|
|
195
|
+
`size` `options_locked` `quota` `rate` `engine` `task` `timeout` `network` …
|
|
196
|
+
(`err.status` = HTTP status, `0` = network/timeout error on the SDK side).
|
|
197
|
+
On `protect()` timeout the message already includes the `taskId` so you can
|
|
198
|
+
resume polling manually.
|
package/README_VI.md
ADDED
|
@@ -0,0 +1,196 @@
|
|
|
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. Tốc độ ~1 giây mỗi MB mã nguồn.
|
|
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 lẫn size file** (~6–7 giây mỗi MB): file 3MB ~20 giây,
|
|
78
|
+
file 20MB có thể mất vài phút — `timeoutMs` mặc định 21 phút đã đủ.
|
|
79
|
+
|
|
80
|
+
**Auto-enable** (engine tự bật kèm, không cần khai báo):
|
|
81
|
+
bật `integrityBinding` / `vmShielding` / `opcodeMutation` / `incrementalCipher` /
|
|
82
|
+
`observationResistance` → tự bật `rollingCipher`; bật `stringAtomization` → tự
|
|
83
|
+
bật `polymorphicDecoder`.
|
|
84
|
+
|
|
85
|
+
## Tuỳ chọn engine (options)
|
|
86
|
+
|
|
87
|
+
Truyền vào tham số thứ 2 của `submit()` / `protect()`. **Boolean chỉ có tác
|
|
88
|
+
dụng BẬT THÊM** ngoài preset (server chỉ nhận `true` — không thể tắt option mà
|
|
89
|
+
preset đã bật):
|
|
90
|
+
|
|
91
|
+
```js
|
|
92
|
+
await vm.protect(code, {
|
|
93
|
+
preset: 'medium', // low | medium | max (mặc định low)
|
|
94
|
+
targetMode: 'comment', // root (mặc định) | comment — Pro+
|
|
95
|
+
target: 'node', // browser (mặc định) | node — Pro+
|
|
96
|
+
rollingCipher: true, // bật THÊM option ngoài preset
|
|
97
|
+
vmShielding: true, // → tự bật rollingCipher kèm theo
|
|
98
|
+
});
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### `targetMode`
|
|
102
|
+
|
|
103
|
+
- **`root`** *(mặc định)* — bảo vệ TOÀN BỘ hàm top-level của file
|
|
104
|
+
- **`comment`** *(Pro+)* — chỉ bảo vệ hàm có comment đánh dấu ngay phía trên:
|
|
105
|
+
```js
|
|
106
|
+
// tnvm:vm
|
|
107
|
+
function secret(a, b) { /* chỉ hàm này được VM hoá */ }
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### `target`
|
|
111
|
+
|
|
112
|
+
- **`browser`** *(mặc định)* — output chạy trong trình duyệt
|
|
113
|
+
- **`node`** *(Pro+)* — output chạy trong NodeJS
|
|
114
|
+
|
|
115
|
+
### Bảo mật (Security)
|
|
116
|
+
|
|
117
|
+
| Option | Mô tả |
|
|
118
|
+
| ------------------------- | ---------------------------------------------------------------------------- |
|
|
119
|
+
| `rollingCipher` | Mã hoá XOR phụ thuộc vị trí trên từng instruction |
|
|
120
|
+
| `integrityBinding` | Ràng buộc giải mã bytecode vào tính toàn vẹn source của interpreter |
|
|
121
|
+
| `debugProtection` | Chống debugger đa tầng, phản ứng leo thang dần |
|
|
122
|
+
| `vmShielding` | Micro-interpreter riêng cho từng hàm, xáo trộn opcode độc lập |
|
|
123
|
+
| `incrementalCipher` | Đưa việc giải mã instruction vào vòng dispatch của VM |
|
|
124
|
+
| `semanticOpacity` | Opaque predicates, alias handler, đa dạng hoá kiểu mã hoá |
|
|
125
|
+
| `observationResistance` | Âm thầm làm hỏng kết quả tính toán khi phát hiện instrumentation |
|
|
126
|
+
| `encryptBytecode` | Mã hoá RC4 bằng khoá fingerprint của môi trường chạy |
|
|
127
|
+
|
|
128
|
+
### Nghiền mã (Obfuscation)
|
|
129
|
+
|
|
130
|
+
| Option | Mô tả |
|
|
131
|
+
| -------------------------- | -------------------------------------------------------- |
|
|
132
|
+
| `mixedBooleanArithmetic` | Thay phép toán số học/bitwise bằng biểu thức MBA |
|
|
133
|
+
| `stackEncoding` | XOR-encode giá trị trên stack VM trong lúc chạy |
|
|
134
|
+
| `deadCodeInjection` | Chèn chuỗi bytecode không thể tới ngay sau RETURN |
|
|
135
|
+
| `handlerFragmentation` | Tách handler thành các mảnh đan xen nhau |
|
|
136
|
+
| `stringAtomization` | Thay string literal bằng tra bảng đã mã hoá |
|
|
137
|
+
| `blockPermutation` | Xáo trộn thứ tự basic block của bytecode |
|
|
138
|
+
| `opcodeMutation` | Đột biến bảng handler lúc runtime qua opcode MUTATE |
|
|
139
|
+
| `bytecodeScattering` | Tách bytecode thành mảnh trộn lẫn rải khắp output |
|
|
140
|
+
|
|
141
|
+
### Tối ưu (Optimization)
|
|
142
|
+
|
|
143
|
+
| Option | Mô tả |
|
|
144
|
+
| ------------------------- | ------------------------------------------------------------ |
|
|
145
|
+
| `preprocessIdentifiers` | Đổi tên identifier trước khi compile |
|
|
146
|
+
| `dynamicOpcodes` | Lọc opcode handler không dùng khỏi interpreter |
|
|
147
|
+
| `decoyOpcodes` | Tiêm các handler opcode giả giống thật |
|
|
148
|
+
| `polymorphicDecoder` | Chuỗi phép toán byte ngẫu nhiên, đổi theo từng build |
|
|
149
|
+
| `scatteredKeys` | Phân mảnh key material qua các tầng closure |
|
|
150
|
+
|
|
151
|
+
## Phân quyền theo gói
|
|
152
|
+
|
|
153
|
+
API key chỉ cấp từ gói **Pro** trở lên, nên với key `tnvm_…` mọi preset và
|
|
154
|
+
tuỳ biến đều mở:
|
|
155
|
+
|
|
156
|
+
| Gói | Preset | Tuỳ biến option | Giới hạn / request |
|
|
157
|
+
| --------------- | ---------------- | :------------------------: | -------------------- |
|
|
158
|
+
| Ẩn danh (web) | low, medium | ✗ | 128KB |
|
|
159
|
+
| Free | low, medium | ✗ | 268KB |
|
|
160
|
+
| **Pro** | low, medium, max | ✓ | 5MB |
|
|
161
|
+
| Team | low, medium, max | ✓ + hàng đợi ưu tiên | 12MB |
|
|
162
|
+
| Business | low, medium, max | ✓ + hàng đợi ưu tiên | 20MB |
|
|
163
|
+
|
|
164
|
+
## API
|
|
165
|
+
|
|
166
|
+
### `new VmCode({ apiKey, baseUrl, pollMs, timeoutMs, gzip })`
|
|
167
|
+
|
|
168
|
+
| Option | Default | Ghi chú |
|
|
169
|
+
| ------------- | -------------------------- | ------------------------------------------------------------------------------------------------------ |
|
|
170
|
+
| `apiKey` | — | **bắt buộc**, `tnvm_…` (Pro+) |
|
|
171
|
+
| `baseUrl` | `https://JsVM.MMO69.Com` | đổi khi chạy server local |
|
|
172
|
+
| `pollMs` | `1000` | nhịp poll kết quả |
|
|
173
|
+
| `timeoutMs` | `1260000` | thời gian chờ tối đa của `protect()` — server compile file cỡ lớn tối đa 20 phút, mặc định 21 phút là đủ |
|
|
174
|
+
| `gzip` | `true` | nén gzip request > 1KB |
|
|
175
|
+
|
|
176
|
+
### Phương thức
|
|
177
|
+
|
|
178
|
+
| Phương thức | Trả về |
|
|
179
|
+
| ----------------------------------------- | -------------------------------------------------------------------------------------------------- |
|
|
180
|
+
| `vm.protect(code, options?, callOpts?)` | `{ result, elapsed, usage? }` — submit + tự poll tới khi xong |
|
|
181
|
+
| `vm.submit(code, options?)` | `{ taskId, status: 'pending' }` — tạo task, giữ `taskId` |
|
|
182
|
+
| `vm.poll(taskId)` | `{status:'pending'\|'running'}` \| `done{result,elapsed,usage}` \| `error{error}` \| `gone` |
|
|
183
|
+
| `vm.meta()` | `{ options, presets, plans, … }` từ `/api/meta` |
|
|
184
|
+
|
|
185
|
+
`callOpts` = `{ pollMs, timeoutMs }` đè cấu hình constructor cho 1 lần gọi.
|
|
186
|
+
|
|
187
|
+
**Kết quả chỉ lấy được MỘT LẦN** — poll nhận `done` xong task bị xoá; task
|
|
188
|
+
không ai lấy sẽ hết hạn sau 2 giờ. Queue-friendly: `submit()` xong có thể
|
|
189
|
+
làm việc khác, `poll()` khi cần.
|
|
190
|
+
|
|
191
|
+
### `VmError`
|
|
192
|
+
|
|
193
|
+
Mọi lỗi đều throw `VmError` — `err.code` khớp mã lỗi API:
|
|
194
|
+
`size` `options_locked` `quota` `rate` `engine` `task` `timeout` `network` …
|
|
195
|
+
(`err.status` = HTTP status, `0` = lỗi mạng/timeout phía SDK).
|
|
196
|
+
`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 21 minutes — the server compiles max-size files up to 20 min)
|
|
28
|
+
* @param {boolean} [opts.gzip] gzip requests > 1KB (default true)
|
|
29
|
+
*/
|
|
30
|
+
constructor({ apiKey, baseUrl, pollMs = 1000, timeoutMs = 21 * 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": "
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "1.1.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
|
+
}
|