"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.LogLevel = exports.MessageType = exports.ScriptRuntime = exports.Script = void 0; const cancellable_1 = require("./cancellable"); const signals_1 = require("./signals"); const util_1 = require("util"); class Script { constructor(impl) { this.impl = impl; this.logHandlerImpl = log; const services = new ScriptServices(this, impl.signals); const rpcController = services; this.exportsProxy = new ScriptExportsProxy(rpcController); const signals = services; this.destroyed = new signals_1.Signal(signals, "destroyed"); this.message = new signals_1.Signal(signals, "message"); } get isDestroyed() { return this.impl.isDestroyed; } get exports() { return this.exportsProxy; } get logHandler() { return this.logHandlerImpl; } set logHandler(handler) { this.logHandlerImpl = handler; } get defaultLogHandler() { return log; } load(cancellable) { return this.impl.load(cancellable); } unload(cancellable) { return this.impl.unload(cancellable); } eternalize(cancellable) { return this.impl.eternalize(cancellable); } post(message, data = null) { this.impl.post(message, data); } [util_1.inspect.custom](depth, options) { return "Script {}"; } } exports.Script = Script; var ScriptRuntime; (function (ScriptRuntime) { ScriptRuntime["Default"] = "default"; ScriptRuntime["QJS"] = "qjs"; ScriptRuntime["V8"] = "v8"; })(ScriptRuntime = exports.ScriptRuntime || (exports.ScriptRuntime = {})); var MessageType; (function (MessageType) { MessageType["Send"] = "send"; MessageType["Error"] = "error"; })(MessageType = exports.MessageType || (exports.MessageType = {})); var LogLevel; (function (LogLevel) { LogLevel["Info"] = "info"; LogLevel["Warning"] = "warning"; LogLevel["Error"] = "error"; })(LogLevel = exports.LogLevel || (exports.LogLevel = {})); class ScriptServices extends signals_1.SignalAdapter { constructor(script, signals) { super(signals); this.script = script; this.pendingRequests = {}; this.nextRequestId = 1; this.onDestroyed = () => { this.signals.disconnect("destroyed", this.onDestroyed); this.signals.disconnect("message", this.onMessage); }; this.onMessage = (message, data) => { if (message.type === MessageType.Send && isRpcSendMessage(message)) { const [, id, operation, ...params] = message.payload; this.onRpcMessage(id, operation, params, data); } else if (isLogMessage(message)) { const opaqueMessage = message; const logMessage = opaqueMessage; this.script.logHandler(logMessage.level, logMessage.payload); } }; this.signals.connect("destroyed", this.onDestroyed); this.signals.connect("message", this.onMessage); } getProxy(name, userHandler) { if (name === "message") { return (message, data) => { if (!isInternalMessage(message)) { userHandler(message, data); } }; } return null; } request(operation, params, cancellable) { return new Promise((resolve, reject) => { const id = this.nextRequestId++; const complete = (error, result) => { if (cancellable !== undefined) { cancellable.cancelled.disconnect(onOperationCancelled); } this.signals.disconnect("destroyed", onScriptDestroyed); delete this.pendingRequests[id]; if (error === null) { resolve(result); } else { reject(error); } }; function onScriptDestroyed() { complete(new Error("Script is destroyed")); } function onOperationCancelled() { complete(new Error("Operation was cancelled")); } this.pendingRequests[id] = complete; this.script.post(["frida:rpc", id, operation].concat(params)); this.signals.connect("destroyed", onScriptDestroyed); if (cancellable !== undefined) { cancellable.cancelled.connect(onOperationCancelled); if (cancellable.isCancelled) { onOperationCancelled(); return; } } if (this.script.isDestroyed) { onScriptDestroyed(); } }); } onRpcMessage(id, operation, params, data) { if (operation === RpcOperation.Ok || operation === RpcOperation.Error) { const callback = this.pendingRequests[id]; if (callback === undefined) { return; } let value = null; let error = null; if (operation === RpcOperation.Ok) { value = (data !== null) ? data : params[0]; } else { const [message, name, stack, rawErr] = params; error = new Error(message); error.name = name; error.stack = stack; Object.assign(error, rawErr); } callback(error, value); } } } function ScriptExportsProxy(rpcController) { return new Proxy(this, { has(target, property) { return !isReservedMethodName(property); ; }, get(target, property, receiver) { if (property in target) { return target[property]; } if (property === util_1.inspect.custom) { return inspectProxy; } if (isReservedMethodName(property)) { return undefined; } return (...args) => { let cancellable; if (args[args.length - 1] instanceof cancellable_1.Cancellable) { cancellable = args.pop(); } return rpcController.request("call", [property, args], cancellable); }; }, set(target, property, value, receiver) { target[property] = value; return true; }, ownKeys(target) { return Object.getOwnPropertyNames(target); }, getOwnPropertyDescriptor(target, property) { if (property in target) { return Object.getOwnPropertyDescriptor(target, property); } if (isReservedMethodName(property)) { return undefined; } return { writable: true, configurable: true, enumerable: true }; }, }); } function inspectProxy() { return "ScriptExportsProxy {}"; } var RpcOperation; (function (RpcOperation) { RpcOperation["Ok"] = "ok"; RpcOperation["Error"] = "error"; })(RpcOperation || (RpcOperation = {})); function isInternalMessage(message) { return isRpcMessage(message) || isLogMessage(message); } function isRpcMessage(message) { return message.type === MessageType.Send && isRpcSendMessage(message); } function isRpcSendMessage(message) { const payload = message.payload; if (!(payload instanceof Array)) { return false; } return payload[0] === "frida:rpc"; } function isLogMessage(message) { return message.type === "log"; } function log(level, text) { switch (level) { case LogLevel.Info: console.log(text); break; case LogLevel.Warning: console.warn(text); break; case LogLevel.Error: console.error(text); break; } } const reservedMethodNames = new Set([ "then", "catch", "finally", ]); function isReservedMethodName(name) { return reservedMethodNames.has(name.toString()); }