The Node client is published today as jso-protector on npm. Reference clients for Python, Go, .NET, Ruby, PHP, Rust, Java, and Kotlin are implemented and tested in the product source tree, but they are not yet published to their public registries. Their package-manager commands will fail until release; contact us if one should be prioritised.
The client matrix
| Language |
Package |
Availability |
Min version |
HTTP transport |
Locally test-verified |
| Node |
jso-protector (npm) |
npm install --save-dev jso-protector |
Node ≥18 |
built-in fetch |
Yes — 152 tests |
| Python |
jso-protector (PyPI) |
Unreleased — not on PyPI |
Python ≥3.8 |
stdlib urllib (no requests dep) |
Yes — 8 tests |
| Go |
jso-protector-go |
Unreleased — no public repository |
Go ≥1.21 |
stdlib net/http |
Tests written (no Go toolchain locally) |
| .NET |
JsoProtector (NuGet) |
Unreleased — not on NuGet |
.NET Standard 2.0 |
HttpClient (IHttpClientFactory-friendly) |
Yes — 8 xUnit tests |
| Ruby |
jso_protector (RubyGems) |
Unreleased — not on RubyGems |
Ruby ≥2.7 |
stdlib net/http |
Tests written (no Ruby toolchain locally) |
| PHP |
javascriptobfuscator/jso-protector (Packagist) |
Unreleased — not on Packagist |
PHP ≥7.4 |
ext-curl when present; stream context fallback |
Tests written (no PHP 7.4+ locally) |
| Rust |
jso-protector (crates.io) |
Unreleased — not on crates.io |
Rust ≥1.70 |
ureq (sync, rustls) |
Tests written (no Rust toolchain locally) |
| Java |
com.javascriptobfuscator:jso-protector (Maven Central) |
Unreleased — not on Maven Central |
JDK 11+ |
stdlib java.net.http.HttpClient |
Tests written (JDK 8 only locally) |
| Kotlin |
com.javascriptobfuscator:jso-protector-kotlin (Maven Central) |
Unreleased — not on Maven Central |
Kotlin ≥1.9, JDK 11+ |
stdlib java.net.http.HttpClient with coroutines |
Tests written (awaiting Kotlin CI) |
| Anything else |
Wire format spec + examples/curl/protect.sh |
curl + jq |
POSIX shell |
curl |
Syntax-checked |
Shared design rules
Every client honors these invariants. If you switch languages, your protection code reads almost identically:
- Same
protect() surface. Inputs: files (map of filename to source), preset (one of standard, balanced, maximum), optional options override map, optional label (forwarded as ReleaseLabel).
- Same Result shape.
files (protected source by name), build_id (stable identifier for this run), polymorphism_fingerprint (short SHA-256 over output), report (full Report including identifier maps), raw (complete response body).
- Env-var-first credentials.
JSO_API_KEY / JSO_API_PASSWORD (or the long-form JAVASCRIPT_OBFUSCATOR_API_KEY / JAVASCRIPT_OBFUSCATOR_API_PASSWORD) read from the environment before falling back to constructor arguments. Use env vars on shared / CI machines.
- Same three preset definitions. Standard, balanced, maximum. Explicit options always override preset defaults.
- Typed error class.
Error / Exception with the API's Type and ErrorCode when the server replies non-Succeed. Messages never include the API key or password.
- No mandatory third-party deps. Except where the language ecosystem requires it (Rust has no stdlib HTTP client; we use
ureq). Everywhere else: stdlib transport, no async runtime.
Side-by-side example
The same "protect app.js and print the BuildId" task in each language. The body of the program is structurally identical because the surface is.
Node:
npx jso-protector --config jso.config.json --label "$GIT_COMMIT" --report jso-report.json
Python:
from jso_protector import protect
result = protect(
files={"app.js": open("dist/app.js").read()},
preset="balanced", label=os.environ.get("GIT_COMMIT"))
print("BuildId:", result.build_id)
Go:
res, err := jso.Protect(ctx, jso.Request{
Files: map[string]string{"app.js": string(src)},
Preset: "balanced", Label: os.Getenv("GIT_COMMIT"),
})
log.Println("BuildId:", res.BuildID)
.NET:
var r = await client.ProtectAsync(new ProtectOptions {
Files = new() { ["app.js"] = src },
Preset = "balanced",
Label = Environment.GetEnvironmentVariable("GIT_COMMIT"),
});
Console.WriteLine($"BuildId: {r.BuildId}");
Ruby:
result = JsoProtector.protect(
files: { "app.js" => File.read("dist/app.js") },
preset: "balanced", label: ENV["GIT_COMMIT"])
puts "BuildId: #{result.build_id}"
PHP:
$result = (new \JsoProtector\Client())->protect([
'files' => ['app.js' => file_get_contents('dist/app.js')],
'preset' => 'balanced',
'label' => getenv('GIT_COMMIT') ?: null,
]);
echo "BuildId: {$result->buildId}\n";
Rust:
let result = Client::new().protect(ProtectRequest {
files: HashMap::from([("app.js".into(), src)]),
preset: Some("balanced".into()),
label: env::var("GIT_COMMIT").ok(),
..Default::default()
})?;
println!("BuildId: {:?}", result.build_id);
Java:
var result = client.protect(ProtectOptions.builder()
.files(Map.of("app.js", Files.readString(Path.of("dist/app.js"))))
.preset("balanced")
.label(System.getenv("GIT_COMMIT"))
.build());
System.out.println("BuildId: " + result.buildId());
Kotlin:
val result = client.protect(ProtectRequest(
files = mapOf("app.js" to File("dist/app.js").readText()),
preset = "balanced",
label = System.getenv("GIT_COMMIT")
))
when (result) {
is ProtectResult.Success -> println("BuildId: ${result.buildId}")
is ProtectResult.Failure -> error(result.message)
}
Which client should you use?
- Node-first build (Vite, Webpack, Next.js, Bun) →
jso-protector npm CLI is the richest surface: 8 bundler plugins, dry-run, release-check, manifest, report flags.
- Python, Go, .NET, Ruby, PHP, Rust, Java, or Kotlin → use the wire-format reference for now or contact us to prioritise the relevant registry release. The implementations and examples below document the intended client shape, but they are not installable public packages today.
The wire format is one HTTP POST per protection call. If Node is not your release environment, use the documented wire format directly until the matching client is published.
Symbolication is available in the browser-only
interactive demo and as a versioned
jso-symbolicate direct download. The CLI is not registry-published. Identifier maps in
Report.GlobalIdentifierMap and
Report.MemberIdentifierMap remain plain JSON for local tooling.