1/** 2 * Copyright (c) 2021 Expo, Inc. 3 * Copyright (c) 2018 Drifty Co. 4 * 5 * This source code is licensed under the MIT license found in the 6 * LICENSE file in the root directory of this source tree. 7 */ 8import Debug from 'debug'; 9import * as fs from 'fs'; 10import { Socket } from 'net'; 11 12import type { LockdownCommand, LockdownResponse } from '../protocol/LockdownProtocol'; 13import { isLockdownResponse, LockdownProtocolClient } from '../protocol/LockdownProtocol'; 14import { ResponseError, ServiceClient } from './ServiceClient'; 15 16const debug = Debug('expo:apple-device:client:mobile_image_mounter'); 17 18export type MIMMountResponse = LockdownResponse; 19 20export interface MIMMessage extends LockdownCommand { 21 ImageType: string; 22} 23 24export interface MIMLookupResponse extends LockdownResponse { 25 ImageSignature?: string; 26} 27 28export interface MIMUploadCompleteResponse extends LockdownResponse { 29 Status: 'Complete'; 30} 31 32export interface MIMUploadReceiveBytesResponse extends LockdownResponse { 33 Status: 'ReceiveBytesAck'; 34} 35 36function isMIMUploadCompleteResponse(resp: any): resp is MIMUploadCompleteResponse { 37 return resp.Status === 'Complete'; 38} 39 40function isMIMUploadReceiveBytesResponse(resp: any): resp is MIMUploadReceiveBytesResponse { 41 return resp.Status === 'ReceiveBytesAck'; 42} 43 44export class MobileImageMounterClient extends ServiceClient<LockdownProtocolClient<MIMMessage>> { 45 constructor(socket: Socket) { 46 super(socket, new LockdownProtocolClient(socket)); 47 } 48 49 async mountImage(imagePath: string, imageSig: Buffer) { 50 debug(`mountImage: ${imagePath}`); 51 52 const resp = await this.protocolClient.sendMessage({ 53 Command: 'MountImage', 54 ImagePath: imagePath, 55 ImageSignature: imageSig, 56 ImageType: 'Developer', 57 }); 58 59 if (!isLockdownResponse(resp) || resp.Status !== 'Complete') { 60 throw new ResponseError(`There was an error mounting ${imagePath} on device`, resp); 61 } 62 } 63 64 async uploadImage(imagePath: string, imageSig: Buffer) { 65 debug(`uploadImage: ${imagePath}`); 66 67 const imageSize = fs.statSync(imagePath).size; 68 return this.protocolClient.sendMessage( 69 { 70 Command: 'ReceiveBytes', 71 ImageSize: imageSize, 72 ImageSignature: imageSig, 73 ImageType: 'Developer', 74 }, 75 (resp: any, resolve, reject) => { 76 if (isMIMUploadReceiveBytesResponse(resp)) { 77 const imageStream = fs.createReadStream(imagePath); 78 imageStream.pipe(this.protocolClient.socket, { end: false }); 79 imageStream.on('error', (err) => reject(err)); 80 } else if (isMIMUploadCompleteResponse(resp)) { 81 resolve(); 82 } else { 83 reject( 84 new ResponseError(`There was an error uploading image ${imagePath} to the device`, resp) 85 ); 86 } 87 } 88 ); 89 } 90 91 async lookupImage() { 92 debug('lookupImage'); 93 94 return this.protocolClient.sendMessage<MIMLookupResponse>({ 95 Command: 'LookupImage', 96 ImageType: 'Developer', 97 }); 98 } 99} 100