feat: frontend support.

This commit is contained in:
2026-02-28 18:44:57 +08:00
parent d75671596c
commit f1054e6476
25 changed files with 3681 additions and 120 deletions

View File

@@ -1,14 +0,0 @@
{
"permissions": {
"allow": [
"Bash(cat:*)",
"Bash(mkdir:*)",
"mcp__ide__getDiagnostics",
"Bash(bun run:*)",
"Bash(bun:*)",
"Bash(NODE_TLS_REJECT_UNAUTHORIZED=0 bun:*)"
],
"deny": [],
"ask": []
}
}

2
.gitignore vendored
View File

@@ -5,3 +5,5 @@ cache/
*.apk
anisette/*
js/node_modules/*
*.so
*.wasm

View File

@@ -1,91 +1,58 @@
# AGENTS.md
# CLAUDE.md
This file provides guidance to Every Agents when working with code in this repository.
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Overview
This crate provides Apple Anisette authentication in browser via WebAssembly. It uses a custom Unicorn Engine fork (https://github.com/lbr77/unicorn/tree/tci-emscripten) to emulate ARM64 Android binaries (`libstoreservicescore.so` + `libCoreADI.so`) for generating Anisette headers.
Rust/WASM project that emulates ARM64 Android binaries (`libstoreservicescore.so` + `libCoreADI.so`) via a custom Unicorn Engine fork to generate Apple Anisette headers in the browser or Node.js.
**Note**: The Android library blobs (`libstoreservicescore.so`, `libCoreADI.so`) are not included in this repository. Extract them from an APK or obtain separately.
The Android library blobs are not included — extract from an APK or obtain separately.
## Build Commands
### Prerequisites
- Rust nightly (edition 2024)
- Emscripten SDK (for WASM builds)
### Setup Unicorn Engine
Clone the custom Unicorn repository and checkout to the `tci-emscripten` branch:
Must source Emscripten before building WASM:
```bash
git clone https://github.com/lbr77/unicorn.git
cd unicorn && git checkout tci-emscripten
source "/Users/libr/Desktop/Life/emsdk/emsdk_env.sh"
```
Then build Unicorn for Emscripten:
```bash
bash script/rebuild-unicorn.sh
bun run build # WASM (debug) + JS bundle
bun run release # WASM (release) + JS bundle
bun run build:js # JS bundle only (no WASM rebuild)
bun run build:glue # WASM only
bun run build:unicorn # Rebuild Unicorn (rarely needed)
```
The rebuild script handles:
- Running `emcmake cmake` with appropriate flags
- Building only `arm` and `aarch64` architectures
- Using static archives (`-DUNICORN_LEGACY_STATIC_ARCHIVE=ON`)
JS bundle outputs to `dist/anisette.js`. WASM glue outputs to `dist/anisette_rs.node.{js,wasm}` and `dist/anisette_rs.{js,wasm}`.
### Build WASM Glue
```bash
bash script/build-glue.sh # Debug build
bash script/build-glue.sh --release # Release build
```
Outputs:
- `test/dist/anisette_rs.js` / `.wasm` (web)
- `test/dist/anisette_rs.node.js` / `.wasm` (Node.js)
- Copies to `../../frontend/public/anisette/`
### Run Native Example
```bash
cargo run --example anisette -- <libstoreservicescore.so> <libCoreADI.so> [library_path] [dsid] [apple_root_pem]
```
### Run Node.js Example
```bash
node example/run-node.mjs <libstoreservicescore.so> <libCoreADI.so> [library_path] [dsid] [identifier]
```
The `js/package.json` build script also outputs to `../dist/anisette.js` directly.
## Architecture
### Core Modules
### Rust → WASM layer (`src/`)
- **`adi.rs`**: ADI (Apple Device Identity) wrapper — provisioning, OTP requests
- **`emu.rs`**: Unicorn-based ARM64 emulator core — library loading, symbol resolution, function calls
- **`exports.rs`**: C FFI exports for WASM — `anisette_*` functions
- **`device.rs`**: Device identity management — UUIDs, identifiers, persistence
- **`idbfs.rs`**: IndexedDB filesystem integration for Emscripten
- **`provisioning.rs`** / **`provisioning_wasm.rs`**: Apple provisioning protocol
- `exports.rs` — all `#[no_mangle]` C FFI exports. Every new public function must also be added to `EXPORTED_FUNCTIONS` in `script/build-glue.sh` (both `WEB_EXPORTED_FUNCTIONS` and `NODE_EXPORTED_FUNCTIONS` as appropriate).
- `adi.rs` — wraps the emulated ADI library calls
- `emu.rs` — Unicorn ARM64 emulator core
- `idbfs.rs` — Emscripten IndexedDB FS integration (browser only)
### Emulator Memory Layout
### JS/TS layer (`js/src/`)
- Libraries mapped to import address space with stub hooks
- Stack, heap, and return addresses pre-allocated
- Import hooks dispatch to runtime stubs
- `anisette.ts` — main `Anisette` class. **Each `getData()` call fully reinits the WASM state** (new `WasmBridge`, re-writes VFS files, re-calls `initFromBlobs`) to work around a Unicorn emulator bug that causes illegal writes on repeated use.
- `wasm-bridge.ts` — raw pointer/length marshalling to WASM exports
- `wasm-loader.ts` — thin wrapper around `ModuleFactory`; caller must pass `locateFile` via `moduleOverrides` to resolve the `.wasm` path
- `provisioning.ts` — Apple provisioning HTTP flow (fetches SPIM, sends CPIM)
- `device.ts` — loads or generates `device.json`
### Public API (exports.rs)
### Key design decisions
- `anisette_init_from_blobs` — Initialize from library blobs
- `anisette_is_machine_provisioned` — Check provisioning state
- `anisette_start_provisioning` / `anisette_end_provisioning` — Provisioning flow
- `anisette_request_otp` — Generate OTP + machine ID headers
- `adi.pb` (provisioning state) lives in the WASM VFS. After provisioning, call `anisette.getAdiPb()` and persist it yourself — it is **not** automatically written to disk.
- `fromSo()` accepts `init.adiPb` and `init.deviceJsonBytes` to restore a previous session into the VFS before init.
- `loadWasm()` is environment-agnostic — no `node:` imports. Pass `locateFile` in `moduleOverrides`.
### Data Flow
### Example usage
1. Load Android `libstoreservicescore.so` + `libCoreADI.so`
2. Initialize device identity (`device.json`)
3. Provision with Apple (if needed)
4. Request OTP → `X-Apple-I-MD` + `X-Apple-I-MD-M` headers
```bash
NODE_TLS_REJECT_UNAUTHORIZED=0 bun example/anisette-api.mjs \
<libstoreservicescore.so> <libCoreADI.so> [library_path]
```

View File

@@ -10,14 +10,15 @@ import path from "node:path";
import { fileURLToPath, pathToFileURL } from "node:url";
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const bundlePath = path.join(__dirname, "..", "dist", "anisette.js");
const {Anisette, loadWasm} = await import("../js/dist");
// const bundlePath = path.join(__dirname, "..", "dist", "anisette.js");
const { Anisette, loadWasm } = await import(
pathToFileURL(bundlePath).href
).catch(() => {
console.error("Bundle not found. Run: npm run build:js");
process.exit(1);
});
// const { Anisette, loadWasm } = await import(
// pathToFileURL(bundlePath).href
// ).catch(() => {
// console.error("Bundle not found. Run: npm run build:js");
// process.exit(1);
// });
const args = process.argv.slice(2);
if (args.length < 2) {

24
frontend/.gitignore vendored Normal file
View File

@@ -0,0 +1,24 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

5
frontend/README.md Normal file
View File

@@ -0,0 +1,5 @@
# Vue 3 + Vite
This template should help get you started developing with Vue 3 in Vite. The template uses Vue 3 `<script setup>` SFCs, check out the [script setup docs](https://v3.vuejs.org/api/sfc-script-setup.html#sfc-script-setup) to learn more.
Learn more about IDE Support for Vue in the [Vue Docs Scaling up Guide](https://vuejs.org/guide/scaling-up/tooling.html#ide-support).

197
frontend/bun.lock Normal file
View File

@@ -0,0 +1,197 @@
{
"lockfileVersion": 1,
"configVersion": 1,
"workspaces": {
"": {
"name": "frontend",
"dependencies": {
"anisette-js": "file:../js",
"libcurl.js": "^0.7.4",
"vue": "^3.5.25",
},
"devDependencies": {
"@vitejs/plugin-vue": "^6.0.2",
"vite": "^7.3.1",
},
},
},
"packages": {
"@babel/helper-string-parser": ["@babel/helper-string-parser@7.27.1", "", {}, "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA=="],
"@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.28.5", "", {}, "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q=="],
"@babel/parser": ["@babel/parser@7.29.0", "", { "dependencies": { "@babel/types": "^7.29.0" }, "bin": "./bin/babel-parser.js" }, "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww=="],
"@babel/types": ["@babel/types@7.29.0", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A=="],
"@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.27.3", "", { "os": "aix", "cpu": "ppc64" }, "sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg=="],
"@esbuild/android-arm": ["@esbuild/android-arm@0.27.3", "", { "os": "android", "cpu": "arm" }, "sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA=="],
"@esbuild/android-arm64": ["@esbuild/android-arm64@0.27.3", "", { "os": "android", "cpu": "arm64" }, "sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg=="],
"@esbuild/android-x64": ["@esbuild/android-x64@0.27.3", "", { "os": "android", "cpu": "x64" }, "sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ=="],
"@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.27.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg=="],
"@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.27.3", "", { "os": "darwin", "cpu": "x64" }, "sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg=="],
"@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.27.3", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w=="],
"@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.27.3", "", { "os": "freebsd", "cpu": "x64" }, "sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA=="],
"@esbuild/linux-arm": ["@esbuild/linux-arm@0.27.3", "", { "os": "linux", "cpu": "arm" }, "sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw=="],
"@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.27.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg=="],
"@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.27.3", "", { "os": "linux", "cpu": "ia32" }, "sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg=="],
"@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.27.3", "", { "os": "linux", "cpu": "none" }, "sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA=="],
"@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.27.3", "", { "os": "linux", "cpu": "none" }, "sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw=="],
"@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.27.3", "", { "os": "linux", "cpu": "ppc64" }, "sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA=="],
"@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.27.3", "", { "os": "linux", "cpu": "none" }, "sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ=="],
"@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.27.3", "", { "os": "linux", "cpu": "s390x" }, "sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw=="],
"@esbuild/linux-x64": ["@esbuild/linux-x64@0.27.3", "", { "os": "linux", "cpu": "x64" }, "sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA=="],
"@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.27.3", "", { "os": "none", "cpu": "arm64" }, "sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA=="],
"@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.27.3", "", { "os": "none", "cpu": "x64" }, "sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA=="],
"@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.27.3", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw=="],
"@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.27.3", "", { "os": "openbsd", "cpu": "x64" }, "sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ=="],
"@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.27.3", "", { "os": "none", "cpu": "arm64" }, "sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g=="],
"@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.27.3", "", { "os": "sunos", "cpu": "x64" }, "sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA=="],
"@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.27.3", "", { "os": "win32", "cpu": "arm64" }, "sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA=="],
"@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.27.3", "", { "os": "win32", "cpu": "ia32" }, "sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q=="],
"@esbuild/win32-x64": ["@esbuild/win32-x64@0.27.3", "", { "os": "win32", "cpu": "x64" }, "sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA=="],
"@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="],
"@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-rc.2", "", {}, "sha512-izyXV/v+cHiRfozX62W9htOAvwMo4/bXKDrQ+vom1L1qRuexPock/7VZDAhnpHCLNejd3NJ6hiab+tO0D44Rgw=="],
"@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.59.0", "", { "os": "android", "cpu": "arm" }, "sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg=="],
"@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.59.0", "", { "os": "android", "cpu": "arm64" }, "sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q=="],
"@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.59.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg=="],
"@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.59.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w=="],
"@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.59.0", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA=="],
"@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.59.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg=="],
"@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.59.0", "", { "os": "linux", "cpu": "arm" }, "sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw=="],
"@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.59.0", "", { "os": "linux", "cpu": "arm" }, "sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA=="],
"@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.59.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA=="],
"@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.59.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA=="],
"@rollup/rollup-linux-loong64-gnu": ["@rollup/rollup-linux-loong64-gnu@4.59.0", "", { "os": "linux", "cpu": "none" }, "sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg=="],
"@rollup/rollup-linux-loong64-musl": ["@rollup/rollup-linux-loong64-musl@4.59.0", "", { "os": "linux", "cpu": "none" }, "sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q=="],
"@rollup/rollup-linux-ppc64-gnu": ["@rollup/rollup-linux-ppc64-gnu@4.59.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA=="],
"@rollup/rollup-linux-ppc64-musl": ["@rollup/rollup-linux-ppc64-musl@4.59.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA=="],
"@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.59.0", "", { "os": "linux", "cpu": "none" }, "sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg=="],
"@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.59.0", "", { "os": "linux", "cpu": "none" }, "sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg=="],
"@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.59.0", "", { "os": "linux", "cpu": "s390x" }, "sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w=="],
"@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.59.0", "", { "os": "linux", "cpu": "x64" }, "sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg=="],
"@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.59.0", "", { "os": "linux", "cpu": "x64" }, "sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg=="],
"@rollup/rollup-openbsd-x64": ["@rollup/rollup-openbsd-x64@4.59.0", "", { "os": "openbsd", "cpu": "x64" }, "sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ=="],
"@rollup/rollup-openharmony-arm64": ["@rollup/rollup-openharmony-arm64@4.59.0", "", { "os": "none", "cpu": "arm64" }, "sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA=="],
"@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.59.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A=="],
"@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.59.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA=="],
"@rollup/rollup-win32-x64-gnu": ["@rollup/rollup-win32-x64-gnu@4.59.0", "", { "os": "win32", "cpu": "x64" }, "sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA=="],
"@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.59.0", "", { "os": "win32", "cpu": "x64" }, "sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA=="],
"@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="],
"@types/node": ["@types/node@25.3.2", "", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-RpV6r/ij22zRRdyBPcxDeKAzH43phWVKEjL2iksqo1Vz3CuBUrgmPpPhALKiRfU7OMCmeeO9vECBMsV0hMTG8Q=="],
"@vitejs/plugin-vue": ["@vitejs/plugin-vue@6.0.4", "", { "dependencies": { "@rolldown/pluginutils": "1.0.0-rc.2" }, "peerDependencies": { "vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0", "vue": "^3.2.25" } }, "sha512-uM5iXipgYIn13UUQCZNdWkYk+sysBeA97d5mHsAoAt1u/wpN3+zxOmsVJWosuzX+IMGRzeYUNytztrYznboIkQ=="],
"@vue/compiler-core": ["@vue/compiler-core@3.5.29", "", { "dependencies": { "@babel/parser": "^7.29.0", "@vue/shared": "3.5.29", "entities": "^7.0.1", "estree-walker": "^2.0.2", "source-map-js": "^1.2.1" } }, "sha512-cuzPhD8fwRHk8IGfmYaR4eEe4cAyJEL66Ove/WZL7yWNL134nqLddSLwNRIsFlnnW1kK+p8Ck3viFnC0chXCXw=="],
"@vue/compiler-dom": ["@vue/compiler-dom@3.5.29", "", { "dependencies": { "@vue/compiler-core": "3.5.29", "@vue/shared": "3.5.29" } }, "sha512-n0G5o7R3uBVmVxjTIYcz7ovr8sy7QObFG8OQJ3xGCDNhbG60biP/P5KnyY8NLd81OuT1WJflG7N4KWYHaeeaIg=="],
"@vue/compiler-sfc": ["@vue/compiler-sfc@3.5.29", "", { "dependencies": { "@babel/parser": "^7.29.0", "@vue/compiler-core": "3.5.29", "@vue/compiler-dom": "3.5.29", "@vue/compiler-ssr": "3.5.29", "@vue/shared": "3.5.29", "estree-walker": "^2.0.2", "magic-string": "^0.30.21", "postcss": "^8.5.6", "source-map-js": "^1.2.1" } }, "sha512-oJZhN5XJs35Gzr50E82jg2cYdZQ78wEwvRO6Y63TvLVTc+6xICzJHP1UIecdSPPYIbkautNBanDiWYa64QSFIA=="],
"@vue/compiler-ssr": ["@vue/compiler-ssr@3.5.29", "", { "dependencies": { "@vue/compiler-dom": "3.5.29", "@vue/shared": "3.5.29" } }, "sha512-Y/ARJZE6fpjzL5GH/phJmsFwx3g6t2KmHKHx5q+MLl2kencADKIrhH5MLF6HHpRMmlRAYBRSvv347Mepf1zVNw=="],
"@vue/reactivity": ["@vue/reactivity@3.5.29", "", { "dependencies": { "@vue/shared": "3.5.29" } }, "sha512-zcrANcrRdcLtmGZETBxWqIkoQei8HaFpZWx/GHKxx79JZsiZ8j1du0VUJtu4eJjgFvU/iKL5lRXFXksVmI+5DA=="],
"@vue/runtime-core": ["@vue/runtime-core@3.5.29", "", { "dependencies": { "@vue/reactivity": "3.5.29", "@vue/shared": "3.5.29" } }, "sha512-8DpW2QfdwIWOLqtsNcds4s+QgwSaHSJY/SUe04LptianUQ/0xi6KVsu/pYVh+HO3NTVvVJjIPL2t6GdeKbS4Lg=="],
"@vue/runtime-dom": ["@vue/runtime-dom@3.5.29", "", { "dependencies": { "@vue/reactivity": "3.5.29", "@vue/runtime-core": "3.5.29", "@vue/shared": "3.5.29", "csstype": "^3.2.3" } }, "sha512-AHvvJEtcY9tw/uk+s/YRLSlxxQnqnAkjqvK25ZiM4CllCZWzElRAoQnCM42m9AHRLNJ6oe2kC5DCgD4AUdlvXg=="],
"@vue/server-renderer": ["@vue/server-renderer@3.5.29", "", { "dependencies": { "@vue/compiler-ssr": "3.5.29", "@vue/shared": "3.5.29" }, "peerDependencies": { "vue": "3.5.29" } }, "sha512-G/1k6WK5MusLlbxSE2YTcqAAezS+VuwHhOvLx2KnQU7G2zCH6KIb+5Wyt6UjMq7a3qPzNEjJXs1hvAxDclQH+g=="],
"@vue/shared": ["@vue/shared@3.5.29", "", {}, "sha512-w7SR0A5zyRByL9XUkCfdLs7t9XOHUyJ67qPGQjOou3p6GvBeBW+AVjUUmlxtZ4PIYaRvE+1LmK44O4uajlZwcg=="],
"anisette-js": ["anisette-js@file:../js", { "dependencies": { "@types/node": "^25.3.2" }, "devDependencies": { "typescript": "^5.4.0" } }],
"csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="],
"entities": ["entities@7.0.1", "", {}, "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA=="],
"esbuild": ["esbuild@0.27.3", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.27.3", "@esbuild/android-arm": "0.27.3", "@esbuild/android-arm64": "0.27.3", "@esbuild/android-x64": "0.27.3", "@esbuild/darwin-arm64": "0.27.3", "@esbuild/darwin-x64": "0.27.3", "@esbuild/freebsd-arm64": "0.27.3", "@esbuild/freebsd-x64": "0.27.3", "@esbuild/linux-arm": "0.27.3", "@esbuild/linux-arm64": "0.27.3", "@esbuild/linux-ia32": "0.27.3", "@esbuild/linux-loong64": "0.27.3", "@esbuild/linux-mips64el": "0.27.3", "@esbuild/linux-ppc64": "0.27.3", "@esbuild/linux-riscv64": "0.27.3", "@esbuild/linux-s390x": "0.27.3", "@esbuild/linux-x64": "0.27.3", "@esbuild/netbsd-arm64": "0.27.3", "@esbuild/netbsd-x64": "0.27.3", "@esbuild/openbsd-arm64": "0.27.3", "@esbuild/openbsd-x64": "0.27.3", "@esbuild/openharmony-arm64": "0.27.3", "@esbuild/sunos-x64": "0.27.3", "@esbuild/win32-arm64": "0.27.3", "@esbuild/win32-ia32": "0.27.3", "@esbuild/win32-x64": "0.27.3" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg=="],
"estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="],
"fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="],
"fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
"libcurl.js": ["libcurl.js@0.7.4", "", {}, "sha512-UpvVirvATP7fD0t4rnsxVRuUpPVIo2QvWj4+5JrMsd1KSEvYkON36+COOPAl88hPlGJddk+DfZRvyF7KG7YcSA=="],
"magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="],
"nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="],
"picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
"picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="],
"postcss": ["postcss@8.5.6", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg=="],
"rollup": ["rollup@4.59.0", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.59.0", "@rollup/rollup-android-arm64": "4.59.0", "@rollup/rollup-darwin-arm64": "4.59.0", "@rollup/rollup-darwin-x64": "4.59.0", "@rollup/rollup-freebsd-arm64": "4.59.0", "@rollup/rollup-freebsd-x64": "4.59.0", "@rollup/rollup-linux-arm-gnueabihf": "4.59.0", "@rollup/rollup-linux-arm-musleabihf": "4.59.0", "@rollup/rollup-linux-arm64-gnu": "4.59.0", "@rollup/rollup-linux-arm64-musl": "4.59.0", "@rollup/rollup-linux-loong64-gnu": "4.59.0", "@rollup/rollup-linux-loong64-musl": "4.59.0", "@rollup/rollup-linux-ppc64-gnu": "4.59.0", "@rollup/rollup-linux-ppc64-musl": "4.59.0", "@rollup/rollup-linux-riscv64-gnu": "4.59.0", "@rollup/rollup-linux-riscv64-musl": "4.59.0", "@rollup/rollup-linux-s390x-gnu": "4.59.0", "@rollup/rollup-linux-x64-gnu": "4.59.0", "@rollup/rollup-linux-x64-musl": "4.59.0", "@rollup/rollup-openbsd-x64": "4.59.0", "@rollup/rollup-openharmony-arm64": "4.59.0", "@rollup/rollup-win32-arm64-msvc": "4.59.0", "@rollup/rollup-win32-ia32-msvc": "4.59.0", "@rollup/rollup-win32-x64-gnu": "4.59.0", "@rollup/rollup-win32-x64-msvc": "4.59.0", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg=="],
"source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="],
"tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="],
"typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
"undici-types": ["undici-types@7.18.2", "", {}, "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w=="],
"vite": ["vite@7.3.1", "", { "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA=="],
"vue": ["vue@3.5.29", "", { "dependencies": { "@vue/compiler-dom": "3.5.29", "@vue/compiler-sfc": "3.5.29", "@vue/runtime-dom": "3.5.29", "@vue/server-renderer": "3.5.29", "@vue/shared": "3.5.29" }, "peerDependencies": { "typescript": "*" }, "optionalPeers": ["typescript"] }, "sha512-BZqN4Ze6mDQVNAni0IHeMJ5mwr8VAJ3MQC9FmprRhcBYENw+wOAAjRj8jfmN6FLl0j96OXbR+CjWhmAmM+QGnA=="],
}
}

12
frontend/index.html Normal file
View File

@@ -0,0 +1,12 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Anisette JS Demo</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.js"></script>
</body>
</html>

20
frontend/package.json Normal file
View File

@@ -0,0 +1,20 @@
{
"name": "frontend",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
},
"dependencies": {
"vue": "^3.5.25",
"anisette-js": "file:../js",
"libcurl.js": "^0.7.4"
},
"devDependencies": {
"@vitejs/plugin-vue": "^6.0.2",
"vite": "^7.3.1"
}
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

65
frontend/src/App.vue Normal file
View File

@@ -0,0 +1,65 @@
<script setup>
import { ref } from 'vue'
import { Anisette, loadWasmModule } from 'anisette-js'
import { initLibcurl } from './libcurl-init'
import { LibcurlHttpClient } from './libcurl-http'
const status = ref('Ready')
const headers = ref(null)
async function runDemo() {
try {
status.value = 'Loading libcurl...'
await initLibcurl()
const httpClient = new LibcurlHttpClient()
status.value = 'Loading WASM...'
const wasmModule = await loadWasmModule({ printErr: () => {}})
status.value = 'Loading library files...'
const [ssResp, caResp] = await Promise.all([
fetch('/libstoreservicescore.so'),
fetch('/libCoreADI.so')
])
if (!ssResp.ok || !caResp.ok) {
throw new Error('Failed to load .so files from public directory')
}
const ssBytes = new Uint8Array(await ssResp.arrayBuffer())
const caBytes = new Uint8Array(await caResp.arrayBuffer())
status.value = 'Initializing Anisette...'
const anisette = await Anisette.fromSo(ssBytes, caBytes, wasmModule, {
httpClient,
init: { libraryPath: '/anisette' }
})
console.log(anisette.getDevice())
if (!anisette.isProvisioned) {
status.value = 'Provisioning...'
await anisette.provision()
}
status.value = 'Getting headers...'
headers.value = await anisette.getData()
status.value = 'Done'
} catch (err) {
status.value = `Error: ${err.message}`
console.error(err)
}
}
</script>
<template>
<div>
<h1>Anisette JS Demo</h1>
<div>
<button @click="runDemo">Run</button>
</div>
<p>Status: {{ status }}</p>
<pre v-if="headers">{{ JSON.stringify(headers, null, 2) }}</pre>
</div>
</template>

View File

@@ -0,0 +1,28 @@
// Libcurl-based HTTP client for browser
import type { HttpClient } from "anisette-js";
import { libcurl } from "./libcurl-init";
export class LibcurlHttpClient implements HttpClient {
async get(url: string, headers: Record<string, string>): Promise<Uint8Array> {
// @ts-ignore
const response = (await libcurl.fetch(url, { method: "GET", headers, insecure: true })) as Response;
if (!response.ok) {
throw new Error(`HTTP GET ${url} failed: ${response.status} ${response.statusText}`);
}
return new Uint8Array(await response.arrayBuffer());
}
async post(
url: string,
body: string,
headers: Record<string, string>
): Promise<Uint8Array> {
// @ts-ignore
const response = (await libcurl.fetch(url, { method: "POST", body, headers, insecure: true})) as Response;
if (!response.ok) {
throw new Error(`HTTP POST ${url} failed: ${response.status} ${response.statusText}`);
}
return new Uint8Array(await response.arrayBuffer());
}
}

View File

@@ -0,0 +1,20 @@
import { libcurl } from "../public/libcurl_full.mjs";
let initialized = false;
let initPromise: Promise<void> | null = null;
export async function initLibcurl(): Promise<void> {
if (initialized) return;
if (initPromise) return initPromise;
initPromise = (async () => {
const wsProto = location.protocol === "https:" ? "wss:" : "ws:";
let wsUrl = `${wsProto}//${location.host}/wisp/`;
libcurl.set_websocket(wsUrl);
await libcurl.load_wasm("/libcurl.wasm");
initialized = true;
})();
return initPromise;
}
export { libcurl };

4
frontend/src/main.js Normal file
View File

@@ -0,0 +1,4 @@
import { createApp } from 'vue'
import App from './App.vue'
createApp(App).mount('#app')

40
frontend/src/types/libcurl.d.ts vendored Normal file
View File

@@ -0,0 +1,40 @@
declare module "libcurl.js" {
interface LibcurlResponse {
ok: boolean;
status: number;
statusText: string;
headers: Headers;
raw_headers: [string, string][];
text(): Promise<string>;
json(): Promise<any>;
arrayBuffer(): Promise<ArrayBuffer>;
}
interface LibcurlFetchOptions {
method?: string;
headers?: Record<string, string> | Headers;
body?: string | ArrayBuffer | Uint8Array;
redirect?: "follow" | "manual" | "error";
proxy?: string;
_libcurl_verbose?: number;
_libcurl_http_version?: number;
}
interface Libcurl {
ready: boolean;
onload: (() => void) | null;
set_websocket(url: string): void;
load_wasm(url?: string): Promise<void>;
fetch(url: string, options?: LibcurlFetchOptions): Promise<LibcurlResponse>;
get_error_string(code: number): string;
get_cacert(): string;
version: string;
}
export const libcurl: Libcurl;
}
declare module "libcurl.js/bundled" {
import type { Libcurl } from "libcurl.js";
export const libcurl: Libcurl;
}

19
frontend/vite.config.js Normal file
View File

@@ -0,0 +1,19 @@
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
// https://vite.dev/config/
export default defineConfig({
plugins: [vue()],
optimizeDeps: {
exclude: ['anisette-js']
},
build: {
target: 'esnext'
},
server: {
proxy: {
"/api": "http://localhost:8080",
"/wisp": { target: "ws://localhost:8080", ws: true },
},
},
})

0
js/.gitignore vendored Normal file
View File

View File

@@ -4,12 +4,19 @@
"workspaces": {
"": {
"name": "anisette-js",
"dependencies": {
"@types/node": "^25.3.2",
},
"devDependencies": {
"typescript": "^5.4.0",
},
},
},
"packages": {
"@types/node": ["@types/node@25.3.2", "", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-RpV6r/ij22zRRdyBPcxDeKAzH43phWVKEjL2iksqo1Vz3CuBUrgmPpPhALKiRfU7OMCmeeO9vECBMsV0hMTG8Q=="],
"typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
"undici-types": ["undici-types@7.18.2", "", {}, "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w=="],
}
}

View File

@@ -7,16 +7,24 @@
"types": "./dist/index.d.ts",
"exports": {
".": {
"import": "./dist/index.js",
"types": "./dist/index.d.ts"
"types": "./dist/index.d.ts",
"default": "./src/browser.ts",
"node": "./src/index.ts"
}
},
"files": [
"dist",
"./src/anisette_rs.js",
"./src/anisette_rs.node.js"
],
"scripts": {
"build": "bun build src/index.ts --outfile ../dist/anisette.js --target node --format esm --minify-syntax --minify-whitespace",
"build:cjs": "bun build src/index.ts --outfile dist/anisette.cjs --target node --format cjs --minify",
"build": "tsc && cp src/anisette_rs.js dist/ && cp src/anisette_rs.node.js dist/",
"typecheck": "tsc --noEmit"
},
"devDependencies": {
"typescript": "^5.4.0"
},
"dependencies": {
"@types/node": "^25.3.2"
}
}

25
js/src/browser.ts Normal file
View File

@@ -0,0 +1,25 @@
//@ts-expect-error no types for the Emscripten module factory
import ModuleFactory from "./anisette_rs.js";
export * from './index';
export type EmscriptenModule = any;
export interface ModuleOverrides {
[key: string]: any;
}
const wasmUrl = new URL("./anisette_rs.wasm", import.meta.url).href;
export async function loadWasmModule(
moduleOverrides: ModuleOverrides = {}
): Promise<EmscriptenModule> {
return ModuleFactory({
...moduleOverrides,
locateFile: (filename: string) => {
if (filename.endsWith(".wasm")) return wasmUrl;
return filename;
},
});
}
export const loadWasm = loadWasmModule;
export const isNodeEnvironment = () => false;
export const getWasmBinaryPath = () => wasmUrl;

View File

@@ -1,7 +1,6 @@
// Public entry point — re-exports everything users need
export { Anisette } from "./anisette.js";
export { loadWasm } from "./wasm-loader.js";
export { WasmBridge } from "./wasm-bridge.js";
export { Device } from "./device.js";
export { LibraryStore } from "./library.js";
@@ -15,3 +14,12 @@ export type {
DeviceJson,
} from "./types.js";
export type { AnisetteOptions } from "./anisette.js";
export {
loadWasmModule,
loadWasmModule as loadWasm,
isNodeEnvironment,
getWasmBinaryPath,
type EmscriptenModule,
type ModuleOverrides,
} from "./wasm-loader.js";

View File

@@ -1,7 +1,94 @@
// @ts-expect-error — glue file is generated, no types available
import ModuleFactory from "../../dist/anisette_rs.node.js";
// Unified WASM loader with automatic environment detection
// Uses import.meta.url + protocol detection to support both Node.js and Browser
//
// Browser (Vite/Webpack/etc): https://... or http://... -> anisette_rs.js + .wasm
// Node.js: file://... -> anisette_rs.node.js + .wasm
// Get the base URL from import.meta.url
const MODULE_URL = new URL(import.meta.url);
const IS_NODE = MODULE_URL.protocol === "file:";
// Determine which WASM build to use based on environment
const WASM_JS_PATH = IS_NODE
? new URL("../dist/anisette_rs.node.js", MODULE_URL)
: new URL("../dist/anisette_rs.js", MODULE_URL);
const WASM_BINARY_PATH = IS_NODE
? new URL("../dist/anisette_rs.node.wasm", MODULE_URL)
: new URL("../dist/anisette_rs.wasm", MODULE_URL);
// Module overrides type (Emscripten module configuration)
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export async function loadWasm(moduleOverrides?: Record<string, any>): Promise<any> {
return ModuleFactory({ ...moduleOverrides });
export type EmscriptenModule = any;
export interface ModuleOverrides {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
[key: string]: any;
}
/**
* Load the Emscripten WASM module with automatic environment detection.
*
* This function automatically:
* - Detects Node.js vs Browser environment via import.meta.url protocol
* - Loads the appropriate WASM build (node or web)
* - Configures locateFile to find the .wasm binary
* - Initializes the module with optional overrides
*
* @param moduleOverrides - Optional Emscripten module configuration overrides
* @returns Initialized Emscripten module with all exports (_malloc, _free, _anisette_* etc.)
*
* @example
* ```ts
* // Browser (Vue/React/Next.js) and Node.js - same code!
* const module = await loadWasmModule();
*
* // With custom overrides
* const module = await loadWasmModule({
* print: (text: string) => console.log("WASM:", text),
* printErr: (text: string) => console.error("WASM Error:", text),
* });
* ```
*/
export async function loadWasmModule(
moduleOverrides: ModuleOverrides = {}
): Promise<EmscriptenModule> {
// Dynamic import of the appropriate Emscripten glue file
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const { default: ModuleFactory } = await import(/* @vite-ignore */ WASM_JS_PATH.href) as { default: (config: ModuleOverrides) => Promise<EmscriptenModule> };
// In browser: let Emscripten use default fetch behavior
// In Node.js: provide locateFile to resolve the .wasm path
const config: ModuleOverrides = IS_NODE
? {
...moduleOverrides,
locateFile: (filename: string) => {
if (filename.endsWith(".wasm")) {
// In Node.js, return the absolute file path
return WASM_BINARY_PATH.pathname;
}
return filename;
},
}
: moduleOverrides;
return ModuleFactory(config);
}
/**
* Convenience function to check if running in Node.js environment.
* Uses the same protocol detection as the loader.
*/
export function isNodeEnvironment(): boolean {
return IS_NODE;
}
/**
* Get the resolved WASM binary path (useful for debugging).
*/
export function getWasmBinaryPath(): string {
return IS_NODE ? WASM_BINARY_PATH.pathname : WASM_BINARY_PATH.href;
}
// Re-export for backward compatibility
export { loadWasmModule as loadWasm };

View File

@@ -1,9 +1,9 @@
{
"compilerOptions": {
"target": "ES2020",
"target": "ES2022",
"module": "ESNext",
"moduleResolution": "bundler",
"lib": ["ES2020", "DOM"],
"lib": ["ES2022", "DOM"],
"outDir": "./dist",
"declaration": true,
"declarationMap": true,

View File

@@ -8,7 +8,7 @@ fi
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
TARGET_DIR="${ROOT_DIR}/target/wasm32-unknown-emscripten/${BUILD_MODE}"
DIST_DIR="${ROOT_DIR}/dist"
DIST_DIR="${ROOT_DIR}/js/src"
# EMSDK_DIR="${EMSDK:-/Users/libr/Desktop/Life/emsdk}"
UNICORN_BUILD_DIR="${UNICORN_BUILD_DIR:-${ROOT_DIR}/../unicorn/build}"
NODE_DIST_JS="${DIST_DIR}/anisette_rs.node.js"
@@ -66,6 +66,7 @@ emcc \
-sEXPORT_ES6=1 \
-sENVIRONMENT=web \
-sWASM=1 \
-sSINGLE_FILE=1 \
-sALLOW_MEMORY_GROWTH=1 \
-sINITIAL_MEMORY=268435456 \
-sWASM_BIGINT=1 \
@@ -81,6 +82,7 @@ emcc \
-sEXPORT_ES6=1 \
-sENVIRONMENT=node \
-sWASM=1 \
-sSINGLE_FILE=1 \
-sALLOW_MEMORY_GROWTH=1 \
-sINITIAL_MEMORY=268435456 \
-sWASM_BIGINT=1 \
@@ -91,24 +93,4 @@ emcc \
echo "glue build done:"
echo " ${DIST_DIR}/anisette_rs.js"
echo " ${DIST_DIR}/anisette_rs.wasm"
echo " ${NODE_DIST_JS}"
echo " ${NODE_DIST_WASM}"
# Bundle TS API + glue into a single JS file
JS_DIR="${ROOT_DIR}/js"
if command -v bun >/dev/null 2>&1 && [[ -f "${JS_DIR}/src/index.ts" ]]; then
echo "bundling TS API..."
bun build "${JS_DIR}/src/index.ts" \
--outfile "${DIST_DIR}/anisette.js" \
--target node \
--format esm \
--minify-syntax --minify-whitespace
echo " ${DIST_DIR}/anisette.js"
fi
# Copy to frontend if directory exists (skip in CI if not present)
if [[ -d "${ROOT_DIR}/../../frontend/public/anisette" ]]; then
cp "${DIST_DIR}/anisette_rs.js" "${ROOT_DIR}/../../frontend/public/anisette/anisette_rs.js"
cp "${DIST_DIR}/anisette_rs.wasm" "${ROOT_DIR}/../../frontend/public/anisette/anisette_rs.wasm"
fi
echo " ${DIST_DIR}/anisette_rs.node.js"