UNPKG

screenkitten

Version:

A cross-platform Node.js library for taking screenshots on iOS simulators and Android devices/emulators

234 lines (228 loc) 7.97 kB
// src/ios.ts import execa from "execa"; // src/errors.ts var ScreenkittenError = class extends Error { constructor(message, options) { super(message, options); this.name = "ScreenkittenError"; } }; var ScreenkittenDeviceNotFoundError = class extends ScreenkittenError { constructor(deviceId, cause) { super(`Device not found: ${deviceId}`, { cause }); this.name = "ScreenkittenDeviceNotFoundError"; } }; var ScreenkittenXcrunNotFoundError = class extends ScreenkittenError { constructor(xcrunPath, cause) { super(`xcrun not found at path: ${xcrunPath}`, { cause }); this.name = "ScreenkittenXcrunNotFoundError"; } }; var ScreenkittenAdbNotFoundError = class extends ScreenkittenError { constructor(adbPath, cause) { super(`adb not found at path: ${adbPath}`, { cause }); this.name = "ScreenkittenAdbNotFoundError"; } }; var ScreenkittenIOSSimulatorError = class extends ScreenkittenError { constructor(deviceId, cause) { super(`iOS Simulator not available or not booted: ${deviceId}`, { cause }); this.name = "ScreenkittenIOSSimulatorError"; } }; var ScreenkittenAndroidDeviceError = class extends ScreenkittenError { constructor(deviceId, cause) { super(`Android device/emulator not available: ${deviceId}`, { cause }); this.name = "ScreenkittenAndroidDeviceError"; } }; var ScreenkittenFileWriteError = class extends ScreenkittenError { constructor(outputPath, cause) { super(`Failed to write screenshot file: ${outputPath}`, { cause }); this.name = "ScreenkittenFileWriteError"; } }; var ScreenkittenOperationAbortedError = class extends ScreenkittenError { constructor(operation = "operation") { super(`${operation} was aborted`); this.name = "ScreenkittenOperationAbortedError"; } }; var ScreenkittenScreenshotFailedError = class extends ScreenkittenError { constructor(platform, cause) { super(`${platform.toUpperCase()} screenshot command failed`, { cause }); this.name = "ScreenkittenScreenshotFailedError"; } }; var ScreenkittenInvalidTypeError = class extends ScreenkittenError { constructor(type) { super(`Invalid screenshot type: ${type}. Valid types are: png, jpeg`); this.name = "ScreenkittenInvalidTypeError"; } }; // src/utils.ts import crypto from "node:crypto"; import path from "node:path"; import os from "node:os"; function doHandleError(handler, error, result) { if (handler) { if (handler === "throw") { throw error; } else if (handler === "ignore") { return result; } else { handler(error); return result; } } throw error; } function createTempScreenshotPath(platform, extension) { const timestamp = Date.now(); const uuid = crypto.randomUUID().slice(0, 8); const filename = `${platform}-screenshot-${timestamp}-${uuid}.${extension}`; return path.join(os.tmpdir(), filename); } // src/ios.ts var ScreenkittenIOS = class { xcrunPath; options; constructor(options) { this.options = options; this.xcrunPath = options.xcrunPath || "/usr/bin/xcrun"; } async takeScreenshot(overrideOptions = {}) { var _a; const options = { ...this.options, ...overrideOptions }; const deviceId = options.deviceId || "booted"; const type = options.type || "png"; const display = options.display || "internal"; const mask = options.mask || "ignored"; const outputPath = options.outputPath || createTempScreenshotPath("ios", type); const onError = options.onError || "throw"; if (type !== "png" && type !== "jpeg") { const error = new ScreenkittenInvalidTypeError(type); return doHandleError(onError, error, Promise.resolve(outputPath)); } const args = [ "simctl", "io", deviceId, "screenshot", "--type", type, "--display", display, "--mask", mask, outputPath ]; try { if ((_a = options.abortSignal) == null ? void 0 : _a.aborted) { throw new ScreenkittenOperationAbortedError("iOS screenshot"); } await execa(this.xcrunPath, args, { signal: options.abortSignal }); return outputPath; } catch (error) { const screenshotError = this._classifyError(error, deviceId); return doHandleError(onError, screenshotError, Promise.resolve(outputPath)); } } _classifyError(error, deviceId) { if (error instanceof Error) { if (error.message.includes("ENOENT") || error.message.includes("command not found")) { return new ScreenkittenXcrunNotFoundError(this.xcrunPath, error); } else if (error.message.includes("Invalid device") || error.message.includes("device not found")) { return new ScreenkittenIOSSimulatorError(deviceId, error); } else if (error.message.includes("aborted") || error.name === "AbortError") { return new ScreenkittenOperationAbortedError("iOS screenshot"); } else { return new ScreenkittenScreenshotFailedError("ios", error); } } else { return new ScreenkittenScreenshotFailedError("ios"); } } }; // src/android.ts import fs from "node:fs/promises"; import execa2 from "execa"; var ScreenkittenAndroid = class { adbPath; options; constructor(options) { this.options = options; this.adbPath = options.adbPath || "adb"; } async takeScreenshot(overrideOptions = {}) { var _a; const options = { ...this.options, ...overrideOptions }; const deviceId = options.deviceId || "booted"; const outputPath = options.outputPath || createTempScreenshotPath("android", "png"); const onError = options.onError || "throw"; try { if ((_a = options.abortSignal) == null ? void 0 : _a.aborted) { throw new ScreenkittenOperationAbortedError("Android screenshot"); } const args = deviceId === "booted" ? ["exec-out", "screencap", "-p"] : ["-s", deviceId, "exec-out", "screencap", "-p"]; const result = await execa2(this.adbPath, args, { encoding: null, // Return buffer for binary data signal: options.abortSignal }); await fs.writeFile(outputPath, result.stdout); return outputPath; } catch (error) { const screenshotError = this._classifyError(error, deviceId, outputPath); return doHandleError(onError, screenshotError, Promise.resolve(outputPath)); } } _classifyError(error, deviceId, outputPath) { if (error instanceof Error) { if (error.message.includes("ENOENT") || error.message.includes("command not found")) { return new ScreenkittenAdbNotFoundError(this.adbPath, error); } else if (error.message.includes("device not found") || error.message.includes("device offline")) { return new ScreenkittenAndroidDeviceError(deviceId, error); } else if (error.message.includes("aborted") || error.name === "AbortError") { return new ScreenkittenOperationAbortedError("Android screenshot"); } else if (error.code === "ENOENT" || error.code === "EACCES") { return new ScreenkittenFileWriteError(outputPath, error); } else { return new ScreenkittenScreenshotFailedError("android", error); } } else { return new ScreenkittenScreenshotFailedError("android"); } } }; // src/index.ts function screenkitten(options) { switch (options.platform) { case "ios": { return new ScreenkittenIOS(options); } case "android": { return new ScreenkittenAndroid(options); } default: { throw new Error(`Unsupported platform: ${options.platform}`); } } } export { ScreenkittenAdbNotFoundError, ScreenkittenAndroidDeviceError, ScreenkittenDeviceNotFoundError, ScreenkittenError, ScreenkittenFileWriteError, ScreenkittenIOSSimulatorError, ScreenkittenInvalidTypeError, ScreenkittenOperationAbortedError, ScreenkittenScreenshotFailedError, ScreenkittenXcrunNotFoundError, screenkitten }; //# sourceMappingURL=index.mjs.map