appium-remote-debugger
Version:
Appium proxy for Remote Debugger protocol
193 lines • 7.3 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.RpcClientRealDeviceShim = void 0;
const logger_1 = require("../logger");
const rpc_client_1 = require("./rpc-client");
const lodash_1 = __importDefault(require("lodash"));
/**
* RPC client implementation for iOS 18+ real devices using the WebInspector shim.
* This client uses the `com.apple.webinspector.shim.remote` service via RemoteXPC
* tunneling, which is required for iOS 18 and later where the traditional
* Web Inspector service is no longer available.
*
* Extends RpcClient to provide device-specific connection handling using
* the appium-ios-remotexpc library.
*/
class RpcClientRealDeviceShim extends rpc_client_1.RpcClient {
webInspectorService;
remoteXPC;
messageListenerTask;
isListening = false;
/**
* Creates a new RpcClientRealDeviceShim instance.
*
* @param opts - Options for configuring the shim RPC client.
*/
constructor(opts) {
super(opts);
}
/**
* Connects to the WebInspector shim service on an iOS 18+ real device.
* Uses the RemoteXPC tunnel to establish a connection to the
* `com.apple.webinspector.shim.remote` service.
*/
async connect() {
if (this.isConnected) {
return;
}
logger_1.log.debug(`Connecting to WebInspector shim service for device ${this.udid}`);
const { Services } = await import('appium-ios-remotexpc');
const result = await Services.startWebInspectorService(this.udid);
this.webInspectorService = result.webInspectorService;
this.remoteXPC = result.remoteXPC;
this.startMessageListener();
this.isConnected = true;
logger_1.log.debug('Successfully connected to WebInspector shim service');
}
/**
* Disconnects from the WebInspector shim service.
* Closes the service connection and cleans up resources.
*/
async disconnect() {
if (!this.isConnected) {
return;
}
logger_1.log.debug('Disconnecting from WebInspector shim service');
await super.disconnect();
// Stop the message listener
this.isListening = false;
if (this.webInspectorService) {
try {
await this.webInspectorService.stopListeningAsync();
}
catch (err) {
logger_1.log.warn('Error while stopping shim message listener', err);
await this.webInspectorService.close();
this.webInspectorService = undefined;
}
}
// Wait for the listener task to complete
if (this.messageListenerTask) {
try {
await this.messageListenerTask;
}
catch {
// Ignore errors during shutdown
}
}
// Close the connections
if (this.webInspectorService) {
await this.webInspectorService.close();
this.webInspectorService = undefined;
}
if (this.remoteXPC) {
await this.remoteXPC.close();
this.remoteXPC = undefined;
}
this.isConnected = false;
logger_1.log.debug('Disconnected from WebInspector shim service');
}
/**
* Sends a command message to the WebInspector shim service.
* Translates the RemoteCommand format to the shim service format.
*
* @param cmd - The command to send to the device.
*/
async sendMessage(cmd) {
if (!this.webInspectorService) {
throw new Error('WebInspector shim service is not initialized. Is the client connected?');
}
const selector = cmd.__selector;
const args = this.translateArguments(cmd.__argument);
logger_1.log.debug(`Sending message via shim: ${selector}`);
await this.webInspectorService.sendMessage(selector, args);
}
/**
* Receives data from the WebInspector shim service and handles it.
* This method is called by the message listener when a message is received.
*
* @param data - The data received from the service.
*/
async receive(data) {
if (this.isConnected && data) {
await this.messageHandler.handleMessage(data);
}
}
/**
* Starts the background message listener that receives messages from
* the WebInspector shim service and forwards them to the message handler.
*/
startMessageListener() {
if (this.isListening || !this.webInspectorService) {
return;
}
this.isListening = true;
const service = this.webInspectorService;
this.messageListenerTask = (async () => {
try {
for await (const message of service.listenMessage()) {
if (!this.isListening) {
break;
}
// Convert the message to the expected format
const convertedMessage = this.convertMessage(message);
await this.receive(convertedMessage);
}
}
catch (err) {
if (this.isListening) {
logger_1.log.error('Error in shim message listener', err);
}
}
finally {
this.isListening = false;
}
})();
}
/**
* Converts a message from the WebInspector shim format to the format
* expected by the message handler.
*
* @param message - The message from the shim service.
* @returns The converted message in the expected format.
*/
convertMessage(message) {
const converted = {
__selector: message.__selector,
};
// Convert buffer data to strings where necessary
if (lodash_1.default.isPlainObject(message.__argument)) {
const args = { ...message.__argument };
// Handle WIRMessageDataKey and WIRSocketDataKey which may be buffers
for (const key of ['WIRMessageDataKey', 'WIRSocketDataKey', 'WIRDestinationKey']) {
if (args[key] !== undefined && Buffer.isBuffer(args[key])) {
args[key] = args[key].toString('utf8');
}
}
converted.__argument = args;
}
return converted;
}
/**
* Translates command arguments from the RemoteCommand format to the
* format expected by the WebInspector shim service.
*
* @param args - The arguments from the RemoteCommand.
* @returns The translated arguments for the shim service.
*/
translateArguments(args) {
if (!lodash_1.default.isPlainObject(args)) {
return {};
}
const translated = { ...args };
// Remove the connection identifier key as it will be added by the shim service
delete translated.WIRConnectionIdentifierKey;
return translated;
}
}
exports.RpcClientRealDeviceShim = RpcClientRealDeviceShim;
exports.default = RpcClientRealDeviceShim;
//# sourceMappingURL=rpc-client-real-device-shim.js.map