mirror of
https://github.com/morluto/rea.git
synced 2026-11-15 19:23:24 +00:00
refactor(jadx): read the bridge identity from the bridge source it launches (#10720)
* refactor(jadx): read the bridge identity from the bridge source it launches * fix(jadx): read the bridge identity before launching the bridge * fix(jadx): read the bridge identity from the outer class's own fields * fix(jadx): read the bridge identity only from the ReaJadxBridge class body * fix(jadx): return a malformed packaged bridge source as a read result JadxRelease.ts is loaded by fixture scripts without a build, so it cannot import src/domain modules (the ParseFailure import broke the android-metadata job and the spawned JADX fixture). The identity read now returns a Result-shaped value; JadxProvider refuses a malformed packaged source as AnalysisCapabilityUnavailableError before acquiring resources, and tests and the fixture unwrap it.
This commit is contained in:
1 parent
2237f936f0
commit
da3541bae3
9 files changed
+292
-23
No files matched your search
@@ -46,6 +46,10 @@ import org.xml.sax.InputSource;
|
||||
|
||||
/** REA-owned metadata boundary for the unmodified jadx-headless-mcp 0.7.1 JAR. */
|
||||
class ReaJadxBridge {
|
||||
// REA reads this identity from this source (JadxRelease.ts), so it is typed
|
||||
// once. Change BRIDGE_VERSION with any protocol change REA must not mix.
|
||||
static final String BRIDGE_NAME = "rea-jadx-bridge";
|
||||
static final String BRIDGE_VERSION = "11";
|
||||
private static final Gson JSON = new Gson();
|
||||
private final SessionHolder holder;
|
||||
private JadxSession indexedSession;
|
||||
@@ -1080,7 +1084,7 @@ class ReaJadxBridge {
|
||||
SessionHolder holder = new SessionHolder(defaults);
|
||||
ServerCapabilities capabilities = new ServerCapabilities(
|
||||
new ServerCapabilities.Tools(null), null, null, null, null, null, null, Map.of());
|
||||
Server server = new Server(new Implementation("rea-jadx-bridge", "11", null, null, null),
|
||||
Server server = new Server(new Implementation(BRIDGE_NAME, BRIDGE_VERSION, null, null, null),
|
||||
new ServerOptions(capabilities), "", ignored -> Unit.INSTANCE);
|
||||
SessionToolsKt.registerSessionTools(server, holder);
|
||||
ClassToolsKt.registerClassTools(server, holder);
|
||||
|
||||
@@ -55,10 +55,13 @@ import type { JadxLauncher } from "./JadxMcpTransport.js";
|
||||
|
||||
import { JadxSession } from "./JadxSession.js";
|
||||
import { captureJadxFailure } from "./JadxFailure.js";
|
||||
import {
|
||||
JADX_BRIDGE_SOURCE_URL,
|
||||
jadxBridgeIdentity,
|
||||
type JadxBridgeIdentity,
|
||||
} from "./JadxRelease.js";
|
||||
|
||||
const BRIDGE_SOURCE = fileURLToPath(
|
||||
new URL("../../bridge/android/ReaJadxBridge.java", import.meta.url),
|
||||
);
|
||||
const BRIDGE_SOURCE = fileURLToPath(JADX_BRIDGE_SOURCE_URL);
|
||||
type Outcome = Awaited<ReturnType<AndroidAnalysisPort["execute"]>>;
|
||||
|
||||
const waitForTurn = (
|
||||
@@ -268,6 +271,7 @@ interface RetainedSession {
|
||||
readonly snapshot: string;
|
||||
readonly jarHash: string;
|
||||
readonly bridgeHash: string;
|
||||
readonly bridgeIdentity: JadxBridgeIdentity;
|
||||
}
|
||||
|
||||
/** One serialized, immutable APK session per REA instance, retired on idle or shutdown. */
|
||||
@@ -556,6 +560,16 @@ export class JadxProvider implements AndroidAnalysisPort {
|
||||
signal,
|
||||
retainFailedClose(BRIDGE_SOURCE),
|
||||
);
|
||||
// Read before any child or workspace exists, so a malformed packaged
|
||||
// bridge is refused without resources to clean up.
|
||||
const identity = jadxBridgeIdentity();
|
||||
if (!identity.ok)
|
||||
throw new AnalysisCapabilityUnavailableError(
|
||||
"jadx",
|
||||
request.operation,
|
||||
`${identity.error} (${BRIDGE_SOURCE}); reinstall REA to restore the packaged JADX bridge.`,
|
||||
);
|
||||
const bridgeIdentity = identity.value;
|
||||
let targetHash: string;
|
||||
try {
|
||||
targetHash = (
|
||||
@@ -669,6 +683,7 @@ export class JadxProvider implements AndroidAnalysisPort {
|
||||
snapshot,
|
||||
jarHash: engine.sha256,
|
||||
bridgeHash,
|
||||
bridgeIdentity,
|
||||
};
|
||||
}
|
||||
root = retained.root;
|
||||
@@ -685,6 +700,7 @@ export class JadxProvider implements AndroidAnalysisPort {
|
||||
snapshot,
|
||||
jarHash: retained.jarHash,
|
||||
bridgeHash: retained.bridgeHash,
|
||||
bridgeIdentity: retained.bridgeIdentity,
|
||||
signal: activeSignal,
|
||||
timeoutMs: timeoutMs.value,
|
||||
mapping: mapping.value,
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
import { expect, it } from "vitest";
|
||||
|
||||
import { jadxBridgeIdentity, parseJadxBridgeIdentity } from "./JadxRelease.js";
|
||||
|
||||
const declaration = (name: string, version: string): string =>
|
||||
`class ReaJadxBridge {\n static final String BRIDGE_NAME = "${name}";\n static final String BRIDGE_VERSION = "${version}";\n}\n`;
|
||||
|
||||
const identity = (name: string, version: string) => ({
|
||||
ok: true,
|
||||
value: { name, version },
|
||||
});
|
||||
|
||||
const refusal = (pattern: RegExp) => ({
|
||||
ok: false,
|
||||
error: expect.stringMatching(pattern),
|
||||
});
|
||||
|
||||
it("reads the bridge identity the packaged bridge source declares", () => {
|
||||
expect(jadxBridgeIdentity()).toEqual({
|
||||
ok: true,
|
||||
value: {
|
||||
name: "rea-jadx-bridge",
|
||||
version: expect.stringMatching(/^[A-Za-z0-9._-]+$/u),
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("refuses a bridge source that does not declare its identity exactly once", () => {
|
||||
expect(parseJadxBridgeIdentity(declaration("rea-jadx-bridge", "12"))).toEqual(
|
||||
identity("rea-jadx-bridge", "12"),
|
||||
);
|
||||
expect(parseJadxBridgeIdentity("class ReaJadxBridge {}\n")).toEqual(
|
||||
refusal(/BRIDGE_NAME exactly once; found 0/u),
|
||||
);
|
||||
const duplicated = declaration("rea-jadx-bridge", "12").replace(
|
||||
"}\n",
|
||||
' static final String BRIDGE_VERSION = "13";\n}\n',
|
||||
);
|
||||
expect(parseJadxBridgeIdentity(duplicated)).toEqual(
|
||||
refusal(/BRIDGE_VERSION exactly once; found 2/u),
|
||||
);
|
||||
});
|
||||
|
||||
it("reads only the outer class's own fields, whatever their formatting", () => {
|
||||
const source = [
|
||||
'// static final String BRIDGE_VERSION = "line-comment";',
|
||||
"class ReaJadxBridge {",
|
||||
' /* static final String BRIDGE_VERSION = "block-comment"; */',
|
||||
' private static final String BRIDGE_NAME="rea-jadx-bridge" ;',
|
||||
" public static final String",
|
||||
' BRIDGE_VERSION = "12";',
|
||||
' static final String BRACE = "}{";',
|
||||
" private static final class Nested {",
|
||||
' static final String BRIDGE_VERSION = "nested";',
|
||||
" }",
|
||||
" void run() {",
|
||||
' String BRIDGE_NAME = "local";',
|
||||
" }",
|
||||
"}",
|
||||
"",
|
||||
].join("\n");
|
||||
|
||||
expect(parseJadxBridgeIdentity(source)).toEqual(
|
||||
identity("rea-jadx-bridge", "12"),
|
||||
);
|
||||
});
|
||||
|
||||
it("ignores other top-level types in the bridge source", () => {
|
||||
const sibling = [
|
||||
"final class Helper {",
|
||||
' static final String BRIDGE_NAME = "helper";',
|
||||
' static final String BRIDGE_VERSION = "helper";',
|
||||
"}",
|
||||
"",
|
||||
].join("\n");
|
||||
|
||||
expect(
|
||||
parseJadxBridgeIdentity(
|
||||
`${sibling}${declaration("rea-jadx-bridge", "12")}`,
|
||||
),
|
||||
).toEqual(identity("rea-jadx-bridge", "12"));
|
||||
expect(
|
||||
parseJadxBridgeIdentity(`${sibling}class ReaJadxBridgeV2 {}\n`),
|
||||
).toEqual(refusal(/top-level class ReaJadxBridge exactly once; found 0/u));
|
||||
expect(parseJadxBridgeIdentity(`${sibling}class ReaJadxBridge {}\n`)).toEqual(
|
||||
refusal(/BRIDGE_NAME exactly once; found 0/u),
|
||||
);
|
||||
});
|
||||
+145
-5
@@ -1,4 +1,6 @@
|
||||
// Imported directly by pre-install fixture scripts; keep this module dependency-free.
|
||||
import { readFileSync } from "node:fs";
|
||||
|
||||
/** Audited upstream release; source remains unmodified in the Git submodule. */
|
||||
export const JADX_RELEASE = Object.freeze({
|
||||
version: "0.7.1",
|
||||
@@ -7,11 +9,149 @@ export const JADX_RELEASE = Object.freeze({
|
||||
url: "https://github.com/1013503897/jadx-headless-mcp/releases/download/v0.7.1/jadx-headless-mcp-0.7.1-all.jar",
|
||||
});
|
||||
|
||||
/** Identity of REA's metadata bridge, distinct from the producing engine. */
|
||||
export const JADX_BRIDGE_IDENTITY = Object.freeze({
|
||||
name: "rea-jadx-bridge",
|
||||
version: "11",
|
||||
});
|
||||
/** Packaged single-file bridge source that REA copies and launches. */
|
||||
export const JADX_BRIDGE_SOURCE_URL = new URL(
|
||||
"../../bridge/android/ReaJadxBridge.java",
|
||||
import.meta.url,
|
||||
);
|
||||
|
||||
export interface JadxBridgeIdentity {
|
||||
readonly name: string;
|
||||
readonly version: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Java source as string and character literals, single characters, and one
|
||||
* space per comment, each with the brace depth it sits at (a brace carries the
|
||||
* depth outside it). Braces inside literals and comments are not counted. Text
|
||||
* blocks are not handled; the bridge source does not use them.
|
||||
*/
|
||||
function* javaPieces(
|
||||
source: string,
|
||||
): Generator<{ readonly text: string; readonly depth: number }> {
|
||||
let depth = 0;
|
||||
let index = 0;
|
||||
while (index < source.length) {
|
||||
const character = source[index] ?? "";
|
||||
const next = source[index + 1];
|
||||
let end = index + 1;
|
||||
let text = character;
|
||||
if (character === "/" && (next === "/" || next === "*")) {
|
||||
const close =
|
||||
next === "/"
|
||||
? source.indexOf("\n", index)
|
||||
: source.indexOf("*/", index + 2);
|
||||
end = close === -1 ? source.length : next === "/" ? close : close + 2;
|
||||
text = " ";
|
||||
} else if (character === '"' || character === "'") {
|
||||
while (end < source.length && source[end] !== character)
|
||||
end += source[end] === "\\" ? 2 : 1;
|
||||
end += 1;
|
||||
text = source.slice(index, end);
|
||||
}
|
||||
if (text === "}") depth -= 1;
|
||||
yield { text, depth };
|
||||
if (text === "{") depth += 1;
|
||||
index = end;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The body of the top-level class named `className`, with comments removed and
|
||||
* nested bodies blanked, so only that class's own member declarations remain.
|
||||
* Other top-level types in the same file are ignored.
|
||||
*/
|
||||
const topLevelClassMembers = (
|
||||
source: string,
|
||||
className: string,
|
||||
): JadxBridgeSourceRead<string> => {
|
||||
const declaresClass = new RegExp(
|
||||
String.raw`(?:^|\s)class\s+${className}(?![\w$])`,
|
||||
"u",
|
||||
);
|
||||
let members = "";
|
||||
let header = "";
|
||||
let inClass = false;
|
||||
let classBodies = 0;
|
||||
for (const { text, depth } of javaPieces(source)) {
|
||||
if (depth === 0 && text === "{") {
|
||||
// A top-level type body opens; its header names the type.
|
||||
inClass = declaresClass.test(header);
|
||||
if (inClass) classBodies += 1;
|
||||
header = "";
|
||||
} else if (depth === 0)
|
||||
header = text === ";" || text === "}" ? "" : header + text;
|
||||
else if (depth === 1 && inClass)
|
||||
members += text === "{" || text === "}" ? " " : text;
|
||||
}
|
||||
return classBodies === 1
|
||||
? { ok: true, value: members }
|
||||
: {
|
||||
ok: false,
|
||||
error: `REA's JADX bridge source must declare top-level class ${className} exactly once; found ${String(classBodies)}`,
|
||||
};
|
||||
};
|
||||
|
||||
const declaredString = (
|
||||
members: string,
|
||||
constant: string,
|
||||
): JadxBridgeSourceRead<string> => {
|
||||
const matches = [
|
||||
...members.matchAll(
|
||||
new RegExp(
|
||||
String.raw`(?:^|[;\s])(?:(?:public|protected|private|static|final)\s+)*String\s+${constant}\s*=\s*"([A-Za-z0-9._-]+)"\s*;`,
|
||||
"gu",
|
||||
),
|
||||
),
|
||||
];
|
||||
const value = matches.length === 1 ? matches[0]?.[1] : undefined;
|
||||
return value === undefined
|
||||
? {
|
||||
ok: false,
|
||||
error: `REA's JADX bridge source must declare ${constant} exactly once; found ${String(matches.length)}`,
|
||||
}
|
||||
: { ok: true, value };
|
||||
};
|
||||
|
||||
/**
|
||||
* A read of REA's own packaged bridge source, shaped like `Result` from
|
||||
* src/domain/result.ts. This module is loaded by fixture scripts without a
|
||||
* build, so it cannot import that module.
|
||||
*/
|
||||
export type JadxBridgeSourceRead<T> =
|
||||
| { readonly ok: true; readonly value: T }
|
||||
| { readonly ok: false; readonly error: string };
|
||||
|
||||
/** Read the bridge identity the bridge source itself declares. */
|
||||
export const parseJadxBridgeIdentity = (
|
||||
source: string,
|
||||
): JadxBridgeSourceRead<JadxBridgeIdentity> => {
|
||||
const members = topLevelClassMembers(source, "ReaJadxBridge");
|
||||
if (!members.ok) return members;
|
||||
const name = declaredString(members.value, "BRIDGE_NAME");
|
||||
if (!name.ok) return name;
|
||||
const version = declaredString(members.value, "BRIDGE_VERSION");
|
||||
if (!version.ok) return version;
|
||||
return {
|
||||
ok: true,
|
||||
value: Object.freeze({ name: name.value, version: version.value }),
|
||||
};
|
||||
};
|
||||
|
||||
let bridgeIdentity: JadxBridgeSourceRead<JadxBridgeIdentity> | undefined;
|
||||
|
||||
/**
|
||||
* Identity of REA's metadata bridge, distinct from the producing engine. The
|
||||
* bridge source owns it; REA reads it from the packaged copy it launches, so a
|
||||
* version bump cannot be made in one place and missed in the other.
|
||||
*/
|
||||
export const jadxBridgeIdentity =
|
||||
(): JadxBridgeSourceRead<JadxBridgeIdentity> => {
|
||||
bridgeIdentity ??= parseJadxBridgeIdentity(
|
||||
readFileSync(JADX_BRIDGE_SOURCE_URL, "utf8"),
|
||||
);
|
||||
return bridgeIdentity;
|
||||
};
|
||||
|
||||
/** Remediation when `REA_JADX_MCP_JAR` is missing or not an absolute path. */
|
||||
export const JADX_JAR_CONFIGURATION_REMEDIATION = `Download the jadx-headless-mcp ${JADX_RELEASE.version} release JAR from ${JADX_RELEASE.url}, verify SHA-256 ${JADX_RELEASE.sha256}, and set REA_JADX_MCP_JAR to its absolute path. REA does not download or install it.`;
|
||||
|
||||
@@ -45,7 +45,7 @@ import {
|
||||
parseJadxJson,
|
||||
} from "./JadxProtocol.js";
|
||||
import {
|
||||
JADX_BRIDGE_IDENTITY,
|
||||
type JadxBridgeIdentity,
|
||||
JADX_RELEASE,
|
||||
JADX_PROVIDER_IDENTITY,
|
||||
JADX_LIMITATIONS,
|
||||
@@ -78,6 +78,7 @@ export class JadxSession {
|
||||
readonly target: BinaryTarget;
|
||||
readonly jarHash: string;
|
||||
readonly bridgeHash: string;
|
||||
readonly bridgeIdentity: JadxBridgeIdentity;
|
||||
}
|
||||
| undefined;
|
||||
#observedCalls: JadxCallObservation[] = [];
|
||||
@@ -104,6 +105,8 @@ export class JadxSession {
|
||||
readonly snapshot: string;
|
||||
readonly jarHash: string;
|
||||
readonly bridgeHash: string;
|
||||
/** Identity read from the packaged bridge before the child was launched. */
|
||||
readonly bridgeIdentity: JadxBridgeIdentity;
|
||||
readonly signal: AbortSignal;
|
||||
/** Selected whole-operation deadline; also bounds each upstream call. */
|
||||
readonly timeoutMs: number;
|
||||
@@ -120,6 +123,7 @@ export class JadxSession {
|
||||
target,
|
||||
jarHash,
|
||||
bridgeHash: context.bridgeHash,
|
||||
bridgeIdentity: context.bridgeIdentity,
|
||||
};
|
||||
this.#observedCalls = [];
|
||||
this.#jniObservation = undefined;
|
||||
@@ -163,14 +167,12 @@ export class JadxSession {
|
||||
const server = this.#client.getServerVersion();
|
||||
this.#serverName = server?.name ?? null;
|
||||
this.#serverVersion = server?.version ?? null;
|
||||
if (
|
||||
server?.name !== JADX_BRIDGE_IDENTITY.name ||
|
||||
server.version !== JADX_BRIDGE_IDENTITY.version
|
||||
)
|
||||
const expected = context.bridgeIdentity;
|
||||
if (server?.name !== expected.name || server.version !== expected.version)
|
||||
throw new AnalysisCapabilityUnavailableError(
|
||||
"jadx",
|
||||
request.operation,
|
||||
`Expected ${JADX_BRIDGE_IDENTITY.name} ${JADX_BRIDGE_IDENTITY.version}; server reported ${server?.name ?? "unknown"} ${server?.version ?? "unknown"}.`,
|
||||
`Expected ${expected.name} ${expected.version}; server reported ${server?.name ?? "unknown"} ${server?.version ?? "unknown"}.`,
|
||||
);
|
||||
const tools = this.#tools(request, target, signal, timeoutMs);
|
||||
const runtime = jadxRuntimeSchema.parse(
|
||||
@@ -333,8 +335,8 @@ export class JadxSession {
|
||||
provider_facts: {
|
||||
jar_sha256: context.jarHash,
|
||||
bridge_sha256: context.bridgeHash,
|
||||
bridge_name: JADX_BRIDGE_IDENTITY.name,
|
||||
bridge_version: JADX_BRIDGE_IDENTITY.version,
|
||||
bridge_name: context.bridgeIdentity.name,
|
||||
bridge_version: context.bridgeIdentity.version,
|
||||
server_name: this.#serverName,
|
||||
server_version: this.#serverVersion,
|
||||
engine_version: this.#engineVersion,
|
||||
|
||||
@@ -6,7 +6,7 @@ import { closeVerifierResources } from "../../../scripts/lib/verifier-cleanup.mj
|
||||
import { z } from "zod";
|
||||
import { access } from "node:fs/promises";
|
||||
import { ANDROID_TOOL_CONTRACTS } from "../../../src/contracts/android/androidToolContracts.js";
|
||||
import { JADX_BRIDGE_IDENTITY } from "../../../src/android/JadxRelease.js";
|
||||
import { packagedJadxBridgeIdentity } from "../../support/packagedJadxBridge.js";
|
||||
import { androidPartialObservationSchema } from "../../../src/domain/android/androidPartialObservation.js";
|
||||
import { parseEvidence } from "../../../src/domain/evidence.js";
|
||||
import { projectAnalysisError } from "../../../src/domain/analysisErrorProjection.js";
|
||||
@@ -186,7 +186,7 @@ test.each([
|
||||
mode: "tool-error",
|
||||
errorCode: "execution_failure",
|
||||
serverName: "rea-jadx-bridge",
|
||||
serverVersion: JADX_BRIDGE_IDENTITY.version,
|
||||
serverVersion: packagedJadxBridgeIdentity().version,
|
||||
engineVersion: "0.7.1",
|
||||
calls: [
|
||||
{ operation: "rea_jvm_status" },
|
||||
@@ -204,7 +204,7 @@ test.each([
|
||||
mode: "rpc-disconnect",
|
||||
errorCode: "execution_failure",
|
||||
serverName: "rea-jadx-bridge",
|
||||
serverVersion: JADX_BRIDGE_IDENTITY.version,
|
||||
serverVersion: packagedJadxBridgeIdentity().version,
|
||||
engineVersion: "0.7.1",
|
||||
calls: [{ operation: "rea_jvm_status" }, { operation: "load_apk" }],
|
||||
containsAmbientEnvironment: true,
|
||||
@@ -259,7 +259,7 @@ test.each([
|
||||
target: { selected_path: apk, path: apk, format: "apk" },
|
||||
provider_facts: {
|
||||
bridge_name: "rea-jadx-bridge",
|
||||
bridge_version: JADX_BRIDGE_IDENTITY.version,
|
||||
bridge_version: packagedJadxBridgeIdentity().version,
|
||||
server_name: serverName,
|
||||
server_version: serverVersion,
|
||||
engine_version: engineVersion,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { SdkErrorCode } from "@modelcontextprotocol/client";
|
||||
import { packagedJadxBridgeIdentity } from "../../support/packagedJadxBridge.js";
|
||||
import { ArtifactResourceScope } from "../../../src/artifacts/ArtifactResourceScope.js";
|
||||
import { expect, it as test, onTestFinished, vi } from "vitest";
|
||||
import {
|
||||
@@ -1002,6 +1003,7 @@ it("bounds each upstream call by the selected deadline instead of a fixed limit"
|
||||
snapshot: apk,
|
||||
jarHash: "0".repeat(64),
|
||||
bridgeHash: "0".repeat(64),
|
||||
bridgeIdentity: packagedJadxBridgeIdentity(),
|
||||
signal: new AbortController().signal,
|
||||
timeoutMs: 300,
|
||||
resources: new ArtifactResourceScope(),
|
||||
|
||||
Vendored
+8
-2
@@ -1,7 +1,13 @@
|
||||
import { McpServer } from "@modelcontextprotocol/server";
|
||||
import { StdioServerTransport } from "@modelcontextprotocol/server/stdio";
|
||||
import { z } from "zod";
|
||||
import { JADX_BRIDGE_IDENTITY } from "../../../src/android/JadxRelease.ts";
|
||||
import { jadxBridgeIdentity } from "../../../src/android/JadxRelease.ts";
|
||||
|
||||
const packagedBridgeVersion = () => {
|
||||
const identity = jadxBridgeIdentity();
|
||||
if (!identity.ok) throw new Error(identity.error);
|
||||
return identity.value.version;
|
||||
};
|
||||
|
||||
// An actual child MCP server exercising the production stdio/process seam.
|
||||
// It is a protocol fixture, not evidence of JADX decompiler support.
|
||||
@@ -21,7 +27,7 @@ const server = new McpServer({
|
||||
? ""
|
||||
: mode === "version"
|
||||
? "99.0.0"
|
||||
: JADX_BRIDGE_IDENTITY.version,
|
||||
: packagedBridgeVersion(),
|
||||
});
|
||||
const requestedCount = Number.parseInt(
|
||||
process.env.REA_FAKE_JADX_CLASS_COUNT ?? "",
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
import {
|
||||
jadxBridgeIdentity,
|
||||
type JadxBridgeIdentity,
|
||||
} from "../../src/android/JadxRelease.js";
|
||||
|
||||
/** The packaged bridge identity; a test fails if the packaged source is malformed. */
|
||||
export const packagedJadxBridgeIdentity = (): JadxBridgeIdentity => {
|
||||
const identity = jadxBridgeIdentity();
|
||||
if (!identity.ok) throw new Error(identity.error);
|
||||
return identity.value;
|
||||
};
|
||||
Reference in new issue
Block a user