feat: fix
This commit is contained in:
@@ -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);
|
||||
}
|
||||
|
||||
5782
js/src/anisette_rs.js
Normal file
5782
js/src/anisette_rs.js
Normal file
File diff suppressed because one or more lines are too long
5452
js/src/anisette_rs.node.js
Normal file
5452
js/src/anisette_rs.node.js
Normal file
File diff suppressed because one or more lines are too long
@@ -215,4 +215,89 @@ export class WasmBridge {
|
||||
machineId: this.readBytes(midPtr, midLen),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize IDBFS for browser persistence.
|
||||
* Only works in browser environments with IDBFS available.
|
||||
*/
|
||||
initIdbfs(path: string): void {
|
||||
// Check if FS and IDBFS are available (browser only)
|
||||
if (!this.m.FS || !this.m.FS.filesystems?.IDBFS) {
|
||||
return; // Node.js or environment without IDBFS
|
||||
}
|
||||
|
||||
const normalizedPath = this.normalizeMountPath(path);
|
||||
|
||||
// Create directory structure
|
||||
if (normalizedPath !== "/") {
|
||||
try {
|
||||
this.m.FS.mkdirTree(normalizedPath);
|
||||
} catch {
|
||||
// Directory already exists, ignore
|
||||
}
|
||||
}
|
||||
|
||||
// Mount IDBFS
|
||||
try {
|
||||
this.m.FS.mount(this.m.FS.filesystems.IDBFS, {}, normalizedPath);
|
||||
} catch {
|
||||
// Already mounted, ignore
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sync IDBFS from IndexedDB to memory (async).
|
||||
* Must be called after initIdbfs to load existing data from IndexedDB.
|
||||
*/
|
||||
async syncIdbfsFromStorage(): Promise<void> {
|
||||
if (!this.m.FS) {
|
||||
return; // FS not available
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
this.m.FS.syncfs(true, (err: Error | null) => {
|
||||
if (err) {
|
||||
console.error("[anisette] IDBFS sync from storage failed:", err);
|
||||
reject(err);
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Sync IDBFS from memory to IndexedDB (async).
|
||||
* Must be called after modifying files to persist them.
|
||||
*/
|
||||
async syncIdbfsToStorage(): Promise<void> {
|
||||
if (!this.m.FS) {
|
||||
return; // FS not available
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
this.m.FS.syncfs(false, (err: Error | null) => {
|
||||
if (err) {
|
||||
console.error("[anisette] IDBFS sync to storage failed:", err);
|
||||
reject(err);
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private normalizeMountPath(path: string): string {
|
||||
const trimmed = path.trim();
|
||||
const noSlash = trimmed.replace(/\/+$/, "");
|
||||
const noDot = noSlash.startsWith("./") ? noSlash.slice(2) : noSlash;
|
||||
|
||||
if (!noDot || noDot === ".") {
|
||||
return "/";
|
||||
} else if (noDot.startsWith("/")) {
|
||||
return noDot;
|
||||
} else {
|
||||
return "/" + noDot;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user