feat: fix

This commit is contained in:
2026-02-28 18:50:57 +08:00
parent f1054e6476
commit d0bb6f357e
8 changed files with 11474 additions and 85 deletions

View File

@@ -11,6 +11,7 @@ import {
toAppleClientTime,
detectLocale,
encodeUtf8,
decodeUtf8,
} from "./utils.js";
const DEFAULT_DSID = BigInt(-2);
@@ -91,31 +92,41 @@ export class Anisette {
): Promise<Anisette> {
const bridge = new WasmBridge(wasmModule);
const initOpts = options.init ?? {};
const libraryPath = initOpts.libraryPath ?? DEFAULT_LIBRARY_PATH;
const provisioningPath = initOpts.provisioningPath ?? libraryPath;
const libraryPath = normalizeAdiPath(initOpts.libraryPath ?? DEFAULT_LIBRARY_PATH);
const provisioningPath = normalizeAdiPath(initOpts.provisioningPath ?? libraryPath);
const dsid = options.dsid ?? DEFAULT_DSID;
// Load or generate device config
const savedDeviceJson = initOpts.deviceJsonBytes
? (() => { try { return JSON.parse(new TextDecoder().decode(initOpts.deviceJsonBytes)) as import("./types.js").DeviceJson; } catch { return null; } })()
: null;
const device = Device.fromJson(savedDeviceJson, initOpts.deviceConfig);
// Mount + load persisted IDBFS first so file state is stable before init.
mountIdbfsPaths(bridge, libraryPath, provisioningPath);
try {
await bridge.syncIdbfsFromStorage();
} catch {
// Ignore errors - might be first run with no existing data
}
// Restore adi.pb into VFS if provided
// Load device config from explicit bytes first, then from persisted VFS.
const savedDeviceJson =
parseDeviceJsonBytes(initOpts.deviceJsonBytes) ??
readDeviceJsonFromVfs(bridge, joinPath(libraryPath, "device.json"));
const device = Device.fromJson(savedDeviceJson, initOpts.deviceConfig);
const identifier = initOpts.identifier ?? device.adiIdentifier;
// Restore explicit adi.pb into VFS if provided.
if (initOpts.adiPb) {
bridge.writeVirtualFile(joinPath(provisioningPath, "adi.pb"), initOpts.adiPb);
}
// Write device.json into WASM VFS
// Keep VFS device.json consistent with the active in-memory device.
const deviceJsonBytes = initOpts.deviceJsonBytes ?? encodeUtf8(JSON.stringify(device.toJson(), null, 2));
bridge.writeVirtualFile(joinPath(libraryPath, "device.json"), deviceJsonBytes);
// Initialize WASM ADI
bridge.initFromBlobs(
libs.storeservicescore,
libs.coreadi,
libraryPath,
provisioningPath,
initOpts.identifier ?? device.adiIdentifier
identifier
);
const provisioning = new ProvisioningSession(
@@ -124,8 +135,6 @@ export class Anisette {
options.httpClient
);
const identifier = initOpts.identifier ?? device.adiIdentifier;
return new Anisette(bridge, device, provisioning, dsid, provisioningPath, libraryPath, libs, wasmModule, identifier, options.httpClient);
}
@@ -139,6 +148,12 @@ export class Anisette {
/** Run the provisioning flow against Apple servers. */
async provision(): Promise<void> {
await this.provisioning.provision(this.dsid);
// Sync provisioning state to IndexedDB (browser only)
try {
await this.bridge.syncIdbfsToStorage();
} catch {
// Ignore errors in Node.js or if IDBFS unavailable
}
}
/** Read adi.pb from the WASM VFS for persistence. */
@@ -149,12 +164,27 @@ export class Anisette {
/** Generate Anisette headers. Throws if not provisioned. */
async getData(): Promise<AnisetteHeaders> {
// Reinit WASM state before each call to avoid emulator corruption on repeated use
const adiPb = this.bridge.readVirtualFile(joinPath(this.provisioningPath, "adi.pb"));
const adiPb = readOptionalFile(
this.bridge,
joinPath(this.provisioningPath, "adi.pb")
);
const deviceJsonBytes = encodeUtf8(JSON.stringify(this.device.toJson(), null, 2));
this.bridge = new WasmBridge(this.wasmModule);
this.bridge.writeVirtualFile(joinPath(this.provisioningPath, "adi.pb"), adiPb);
mountIdbfsPaths(this.bridge, this.libraryPath, this.provisioningPath);
try {
await this.bridge.syncIdbfsFromStorage();
} catch {
// Ignore errors - might be first run or Node.js
}
if (adiPb) {
this.bridge.writeVirtualFile(joinPath(this.provisioningPath, "adi.pb"), adiPb);
}
this.bridge.writeVirtualFile(joinPath(this.libraryPath, "device.json"), deviceJsonBytes);
this.bridge.initFromBlobs(this.libs.storeservicescore, this.libs.coreadi, this.libraryPath, this.provisioningPath, this.identifier);
this.provisioning = new ProvisioningSession(this.bridge, this.device, this.httpClient);
const { otp, machineId } = this.bridge.requestOtp(this.dsid);
@@ -195,3 +225,66 @@ function joinPath(base: string, file: string): string {
const b = base.endsWith("/") ? base : `${base}/`;
return `${b}${file}`;
}
function normalizeAdiPath(path: string): string {
const trimmed = path.trim().replace(/\\/g, "/");
if (!trimmed || trimmed === "." || trimmed === "./" || trimmed === "/") {
return "./";
}
const noTrail = trimmed.replace(/\/+$/, "");
if (!noTrail || noTrail === ".") {
return "./";
}
if (noTrail.startsWith("./") || noTrail.startsWith("../")) {
return `${noTrail}/`;
}
if (noTrail.startsWith("/")) {
return `.${noTrail}/`;
}
return `./${noTrail}/`;
}
function mountIdbfsPaths(
bridge: WasmBridge,
libraryPath: string,
provisioningPath: string
): void {
const paths = new Set([libraryPath, provisioningPath]);
for (const path of paths) {
bridge.initIdbfs(path);
}
}
function readOptionalFile(bridge: WasmBridge, path: string): Uint8Array | null {
try {
return bridge.readVirtualFile(path);
} catch {
return null;
}
}
function parseDeviceJsonBytes(
bytes: Uint8Array | undefined
): import("./types.js").DeviceJson | null {
if (!bytes) {
return null;
}
try {
return JSON.parse(decodeUtf8(bytes)) as import("./types.js").DeviceJson;
} catch {
return null;
}
}
function readDeviceJsonFromVfs(
bridge: WasmBridge,
path: string
): import("./types.js").DeviceJson | null {
const bytes = readOptionalFile(bridge, path);
if (!bytes) {
return null;
}
return parseDeviceJsonBytes(bytes);
}