fix: update sdk to receive browser param and base url

This commit is contained in:
moebius
2025-02-20 11:31:44 +01:00
parent 1ec86987c7
commit 5dcfc40be7
9 changed files with 97 additions and 47 deletions

View File

@@ -0,0 +1,2 @@
import { fs } from "memfs";
export default fs;

View File

@@ -9,6 +9,12 @@ import {
Version,
VersionString,
} from "./circuits.interface.js";
import { importFetchVersionedArtifact } from "./fetchArtifacts.js";
interface CircuitOptions {
baseUrl?: string;
browser?: boolean;
}
/**
* Class representing circuit management and artifact handling.
@@ -40,6 +46,22 @@ export class Circuits implements CircuitsInterface {
*/
protected baseUrl: string = import.meta.url;
protected readonly browser: boolean = true;
/**
* Constructor to initialize the Circuits class with an optional custom base URL.
* @param {string} [options.baseUrl] - The base URL for fetching circuit artifacts (optional).
* @param {boolean} [options.browser] - Controls how the circuits will be loaded, using either `fetch` if true or `fs` otherwise. Defaults to true.
*/
constructor(options?: CircuitOptions) {
if (options?.baseUrl) {
this.baseUrl = options.baseUrl;
}
if (options?.browser !== undefined) {
this.browser = options.browser;
}
}
/**
* Determines whether the environment is a browser.
* @returns {boolean} True if running in a browser environment, false otherwise.
@@ -96,23 +118,10 @@ export class Circuits implements CircuitsInterface {
*/
async _fetchVersionedArtifact(artifactPath: string): Promise<Uint8Array> {
const artifactUrl = new URL(artifactPath, this.baseUrl);
if (this._browser()) {
const res = await fetch(artifactUrl);
if (res.status !== 200) {
throw new FetchArtifact(artifactUrl);
}
const aBuf = await res.arrayBuffer();
return new Uint8Array(aBuf);
} else {
try {
const fs = (await import("node:fs/promises")).default;
const buf = await fs.readFile(artifactUrl);
return new Uint8Array(buf);
} catch (error) {
console.error(error);
throw new FetchArtifact(artifactUrl);
}
}
const { fetchVersionedArtifact } = await importFetchVersionedArtifact(
this.browser,
);
return fetchVersionedArtifact(artifactUrl);
}
/**

View File

@@ -0,0 +1,12 @@
import { FetchArtifact } from "../exceptions/fetchArtifacts.exception.js";
export async function fetchVersionedArtifact(
artifactUrl: URL,
): Promise<Uint8Array> {
const res = await fetch(artifactUrl);
if (res.status !== 200) {
throw new FetchArtifact(artifactUrl);
}
const aBuf = await res.arrayBuffer();
return new Uint8Array(aBuf);
}

View File

@@ -0,0 +1,23 @@
import { FetchArtifact } from "../exceptions/fetchArtifacts.exception.js";
export async function fetchVersionedArtifact(
artifactUrl: URL,
): Promise<Uint8Array> {
try {
const fs = (await import("fs")).default;
const readPromise: Promise<Buffer> = new Promise((resolve, reject) => {
fs.readFile(artifactUrl.pathname, (err, data) => {
if (err) {
reject(err);
} else {
resolve(data);
}
});
});
const buf = await readPromise;
return new Uint8Array(buf);
} catch (error) {
console.error(error);
throw new FetchArtifact(artifactUrl);
}
}

View File

@@ -0,0 +1,7 @@
export async function importFetchVersionedArtifact(isBrowser: boolean) {
if (isBrowser) {
return import(`./fetchArtifacts.esm.js`);
} else {
return import(`./fetchArtifacts.node.js`);
}
}

View File

@@ -38,7 +38,7 @@ describe("Circuits for browser", () => {
});
it("throws a FetchArtifact exception if artifact is not found at URI", async () => {
expect(async () => {
await expect(async () => {
return await circuits._fetchVersionedArtifact(
"artifacts/artifact_not_here.wasm",
);
@@ -46,7 +46,7 @@ describe("Circuits for browser", () => {
});
it("loads artifact if correctly served", async () => {
expect(
await expect(
circuits._fetchVersionedArtifact("artifacts/withdraw.wasm"),
).resolves.toBeInstanceOf(Uint8Array);
});

View File

@@ -1,19 +1,15 @@
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { fs, vol } from "memfs";
import { fs as memfs, vol } from "memfs";
import { Circuits } from "../../src/circuits/index.js";
import { FetchArtifact } from "../../src/internal.js";
import { CircuitsMock } from "../mocks/index.js";
vi.mock("node:fs/promises");
vi.mock("fs");
const ARTIFACT_DIR = "/dist/node/artifacts";
const WASM_PATH = `${ARTIFACT_DIR}/withdraw.wasm`;
class CircuitsMockNode extends CircuitsMock {
override baseUrl: string = `file://${ARTIFACT_DIR}`;
}
describe("Circuits for Node", () => {
let circuits: Circuits;
afterEach(() => {
@@ -22,26 +18,27 @@ describe("Circuits for Node", () => {
beforeEach(() => {
vol.reset();
fs.mkdirSync(ARTIFACT_DIR, { recursive: true });
fs.writeFileSync(WASM_PATH, "somedata");
circuits = new CircuitsMockNode();
memfs.mkdirSync(ARTIFACT_DIR, { recursive: true });
memfs.writeFileSync(WASM_PATH, "somedata");
circuits = new CircuitsMock({
baseUrl: `file://${ARTIFACT_DIR}`,
browser: false,
});
});
it("virtual file exists", () => {
expect(fs.existsSync(WASM_PATH)).toStrictEqual(true);
expect(fs.existsSync("non_existent_file")).toStrictEqual(false);
expect(memfs.existsSync(WASM_PATH)).toStrictEqual(true);
expect(memfs.existsSync("non_existent_file")).toStrictEqual(false);
});
it("throws a FetchArtifact exception if artifact is not found in filesystem", async () => {
expect(async () => {
return await circuits._fetchVersionedArtifact(
"artifacts/artifact_not_here.wasm",
);
}).rejects.toThrowError(FetchArtifact);
await expect(
circuits._fetchVersionedArtifact("artifacts/artifact_not_here.wasm"),
).rejects.toThrowError(FetchArtifact);
});
it("loads artifact if it exists on filesystem", async () => {
expect(
await expect(
circuits._fetchVersionedArtifact("artifacts/withdraw.wasm"),
).resolves.toBeInstanceOf(Uint8Array);
});

View File

@@ -54,32 +54,32 @@ describe("Circuits", () => {
expect(circuits.introspectBinaries).toStrictEqual(binariesMock);
});
it("_downloadCircuitArtifacts raises FetchArtifact if _fetchVersionedArtifact throws", () => {
it("_downloadCircuitArtifacts raises FetchArtifact if _fetchVersionedArtifact throws", async () => {
const fetchVersionedSpy = vi
.spyOn(circuits, "_fetchVersionedArtifact")
.mockRejectedValue(fetchArtifactError);
expect(
await expect(
async () =>
await circuits._downloadCircuitArtifacts(CircuitName.Withdraw),
).rejects.toThrowError(FetchArtifact);
expect(fetchVersionedSpy).toHaveBeenCalled();
});
it("downloadArtifacts raises FetchArtifact if _downloadCircuitArtifacts throws", () => {
it("downloadArtifacts raises FetchArtifact if _downloadCircuitArtifacts throws", async () => {
const downloadCircuitArtifactsSpy = vi
.spyOn(circuits, "_downloadCircuitArtifacts")
.mockRejectedValue(fetchArtifactError);
expect(
await expect(
async () => await circuits.downloadArtifacts("latest"),
).rejects.toThrowError(FetchArtifact);
expect(downloadCircuitArtifactsSpy).toHaveBeenCalled();
});
it("initArtifacts raises FetchArtifact", () => {
it("initArtifacts raises FetchArtifact", async () => {
const downloadArtifactsSpy = vi
.spyOn(circuits, "downloadArtifacts")
.mockRejectedValue(fetchArtifactError);
expect(
await expect(
async () => await circuits.initArtifacts("latest"),
).rejects.toThrowError(FetchArtifact);
expect(downloadArtifactsSpy).toHaveBeenCalled();
@@ -87,15 +87,15 @@ describe("Circuits", () => {
expect(downloadArtifactsSpy).toHaveBeenCalledWith("latest");
});
it("_handleInitialization raises CircuitInitialization error when something happens", () => {
it("_handleInitialization raises CircuitInitialization error when something happens", async () => {
vi.spyOn(circuits, "initArtifacts").mockRejectedValue(fetchArtifactError);
expect(
await expect(
async () => await circuits._handleInitialization("latest"),
).rejects.toThrowError(CircuitInitialization);
vi.spyOn(circuits, "initArtifacts").mockRejectedValue(
new Error("DifferentError"),
);
expect(
await expect(
async () => await circuits._handleInitialization("latest"),
).rejects.toThrowError(CircuitInitialization);
});

View File

@@ -25,7 +25,7 @@ describe("PrivacyPoolSDK", () => {
let sdk: PrivacyPoolSDK;
beforeEach(() => {
circuits = new CircuitsMock();
circuits = new CircuitsMock({ browser: false });
sdk = new PrivacyPoolSDK(circuits);
});
@@ -82,7 +82,7 @@ describe("PrivacyPoolSDK", () => {
proof: {} as snarkjs.Groth16Proof,
publicSignals: [],
}),
).rejects.toThrow(ProofError);
).rejects.toThrowError(ProofError);
});
it("should return true for a valid commitment proof", async () => {