Skip to content
This repository was archived by the owner on Feb 2, 2021. It is now read-only.

Commit d1de839

Browse files
Merge pull request #212 from telerik/vladimirov/merge-release-from-282
Merge release in master from 282
2 parents 650908a + 527c78a commit d1de839

File tree

5 files changed

+319
-16
lines changed

5 files changed

+319
-16
lines changed

definitions/mobile.d.ts

+13-3
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ declare module Mobile {
1919
sync(localToDevicePaths: ILocalToDevicePathData[], appIdentifier: IAppIdentifier, liveSyncUrl: string, options: ISyncOptions): IFuture<void>;
2020
debug(packageFile: string, packageName: string): IFuture<void>;
2121
openDeviceLogStream(): void;
22-
getInstalledApplications(): IFuture<string[]>;
2322
runApplication(applicationId: string): IFuture<void>;
2423
}
2524

@@ -119,6 +118,8 @@ declare module Mobile {
119118
deviceInstallApplication(service: number, packageFile: NodeBuffer, options: NodeBuffer, installationCallback: NodeBuffer): number;
120119
deviceMountImage(devicePointer: NodeBuffer, imagePath: NodeBuffer, options: NodeBuffer, mountCallBack: NodeBuffer): number;
121120
deviceLookupApplications(devicePointer: NodeBuffer, appType: number, result: NodeBuffer): number;
121+
deviceGetInterfaceType(devicePointer: NodeBuffer): number;
122+
deviceGetConnectionId(devicePointer: NodeBuffer): number;
122123
afcConnectionOpen(service: number, timeout: number, afcConnection: NodeBuffer): number;
123124
afcConnectionClose(afcConnection: NodeBuffer): number;
124125
afcDirectoryCreate(afcConnection: NodeBuffer, path: string): number;
@@ -132,6 +133,14 @@ declare module Mobile {
132133
afcDirectoryClose(afcConnection: NodeBuffer, afcdirectory: NodeBuffer): number;
133134
isDataReceivingCompleted(reply: IDictionary<any>): boolean;
134135
setLogLevel(logLevel: number): number;
136+
137+
/**
138+
* Connect to a port on iOS device connected over USB.
139+
* @param connectionId Connection ID obtained throught IMobileDevice deviceGetConnectionId.
140+
* @param port Port on the device to connect to. The native API expects it in big endian!
141+
* @param socketRef Out param, reference to the socket file descriptor.
142+
*/
143+
uSBMuxConnectByPort(connectionId: number, port: number, socketRef: NodeBuffer): number;
135144
}
136145

137146
interface IHouseArrestClient {
@@ -171,7 +180,8 @@ declare module Mobile {
171180
readSystemLog(action: (data: NodeBuffer) => void): void;
172181
sendMessage(message: {[key: string]: {}}, format?: number): void;
173182
sendMessage(message: string): void;
174-
sendAll?(data: NodeBuffer): void;
183+
sendAll? (data: NodeBuffer): void;
184+
receiveAll? (callback: (data: NodeBuffer) => void): void;
175185
exchange(message: IDictionary<any>): IFuture<IiOSSocketResponseData>;
176186
close(): void;
177187
}
@@ -201,7 +211,7 @@ declare module Mobile {
201211
interface IEmulatorPlatformServices {
202212
checkDependencies(): IFuture<void>;
203213
checkAvailability(dependsOnProject?: boolean): IFuture<void>;
204-
startEmulator(app: string, emulatorOptions?: IEmulatorOptions) : IFuture<void>;
214+
startEmulator(app: string, emulatorOptions?: IEmulatorOptions): IFuture<void>;
205215
}
206216

207217
interface IEmulatorSettingsService {

definitions/plistlib.d.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
11
declare module "plistlib" {
2-
export function toString(data: any): string;
2+
export function toString(data: any): string;
3+
export function loadBuffer(data: NodeBuffer, callback: (err: any, plist: any) => void): void;
4+
export function loadString(data: string, callback: (err: any, plist: any) => void): void;
35
}

mobile/ios/ios-core.ts

+37-2
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ export class CoreTypes {
2525
public static uintType = ref.types.uint;
2626
public static uint32Type = ref.types.uint32;
2727
public static intType = ref.types.int;
28+
public static longType = ref.types.long;
2829
public static boolType = ref.types.bool;
2930
public static doubleType = ref.types.double;
3031

@@ -204,7 +205,10 @@ class IOSCore implements Mobile.IiOSCore {
204205
"AMDeviceCopyValue": ffi.ForeignFunction(lib.get("AMDeviceCopyValue"), CoreTypes.cfStringRef, [CoreTypes.am_device_p, CoreTypes.cfStringRef, CoreTypes.cfStringRef]),
205206
"AMDeviceNotificationUnsubscribe": ffi.ForeignFunction(lib.get("AMDeviceNotificationUnsubscribe"), CoreTypes.intType, [CoreTypes.amDeviceNotificationRef]),
206207
"AMDeviceMountImage": hostInfo.isDarwin() ? ffi.ForeignFunction(lib.get("AMDeviceMountImage"), CoreTypes.uintType, [CoreTypes.am_device_p, CoreTypes.cfStringRef, CoreTypes.cfDictionaryRef, CoreTypes.am_device_mount_image_callback, CoreTypes.voidPtr]) : null,
207-
"AMDSetLogLevel": ffi.ForeignFunction(lib.get("AMDSetLogLevel"), CoreTypes.intType, [CoreTypes.intType])
208+
"AMDSetLogLevel": ffi.ForeignFunction(lib.get("AMDSetLogLevel"), CoreTypes.intType, [CoreTypes.intType]),
209+
"AMDeviceGetInterfaceType": ffi.ForeignFunction(lib.get("AMDeviceGetInterfaceType"), CoreTypes.longType, [CoreTypes.am_device_p]),
210+
"AMDeviceGetConnectionID": ffi.ForeignFunction(lib.get("AMDeviceGetConnectionID"), CoreTypes.longType, [CoreTypes.am_device_p]),
211+
"USBMuxConnectByPort": ffi.ForeignFunction(lib.get("USBMuxConnectByPort"), CoreTypes.intType, [CoreTypes.intType, CoreTypes.intType, CoreTypes.intPtr])
208212
};
209213
}
210214

@@ -556,6 +560,14 @@ export class MobileDevice implements Mobile.IMobileDevice {
556560
return this.mobileDeviceLibrary.AMDeviceLookupApplications(devicePointer, appType, result);
557561
}
558562

563+
public deviceGetInterfaceType(devicePointer: NodeBuffer): number {
564+
return this.mobileDeviceLibrary.AMDeviceGetInterfaceType(devicePointer);
565+
}
566+
567+
public deviceGetConnectionId(devicePointer: NodeBuffer): number {
568+
return this.mobileDeviceLibrary.AMDeviceGetConnectionID(devicePointer);
569+
}
570+
559571
public afcConnectionOpen(service: number, timeout: number, afcConnection: NodeBuffer): number {
560572
return this.mobileDeviceLibrary.AFCConnectionOpen(service, timeout, afcConnection);
561573
}
@@ -611,6 +623,10 @@ export class MobileDevice implements Mobile.IMobileDevice {
611623
public setLogLevel(logLevel: number): number {
612624
return this.mobileDeviceLibrary.AMDSetLogLevel(logLevel);
613625
}
626+
627+
public uSBMuxConnectByPort(connectionId: number, port: number, socketRef: NodeBuffer): number {
628+
return this.mobileDeviceLibrary.USBMuxConnectByPort(connectionId, port, socketRef);
629+
}
614630
}
615631
$injector.register("mobileDevice", MobileDevice);
616632

@@ -668,7 +684,7 @@ class WinSocket implements Mobile.IiOSDeviceSocket {
668684
if(typeof(data) === "string") {
669685
message = new Buffer(data);
670686
}
671-
else {
687+
else {
672688
var payload:NodeBuffer = new Buffer(plistlib.toString(this.createPlist(data)));
673689
var packed:any = bufferpack.pack(">i", [payload.length]);
674690
message = Buffer.concat([packed, payload]);
@@ -689,6 +705,15 @@ class WinSocket implements Mobile.IiOSDeviceSocket {
689705
}
690706
}
691707

708+
public receiveAll(handler: (data: NodeBuffer) => void): void {
709+
var data = this.read(WinSocket.BYTES_TO_READ);
710+
while (data) {
711+
handler(data);
712+
data = this.read(WinSocket.BYTES_TO_READ);
713+
}
714+
this.close();
715+
}
716+
692717
public exchange(message: IDictionary<any>): IFuture<Mobile.IiOSSocketResponseData> {
693718
this.sendMessage(message);
694719
return this.receiveMessage();
@@ -861,6 +886,10 @@ class PosixSocket implements Mobile.IiOSDeviceSocket {
861886
this.$errors.verifyHeap("sendMessage");
862887
}
863888

889+
public receiveAll(handler: (data: NodeBuffer) => void): void {
890+
this.socket.on('data', handler);
891+
}
892+
864893
public exchange(message: IDictionary<any>): IFuture<Mobile.IiOSSocketResponseData> {
865894
this.$errors.fail("Exchange function is not implemented for OSX");
866895
return null;
@@ -908,6 +937,12 @@ export class PlistService implements Mobile.IiOSDeviceSocket {
908937
public sendAll(data: NodeBuffer): void {
909938
this.socket.sendAll(data);
910939
}
940+
941+
public receiveAll(handler: (data: NodeBuffer) => void): void {
942+
if (this.socket.receiveAll) {
943+
this.socket.receiveAll(handler);
944+
}
945+
}
911946
}
912947

913948
export class GDBServer implements Mobile.IGDBServer {

mobile/ios/ios-device.ts

+181-7
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
"use strict";
33

44
import Future = require("fibers/future");
5+
import net = require("net");
56
import ref = require("ref");
67
import os = require("os");
78
import path = require("path");
@@ -18,6 +19,7 @@ var CoreTypes = iosCore.CoreTypes;
1819

1920
export class IOSDevice implements Mobile.IIOSDevice {
2021
private static IMAGE_ALREADY_MOUNTED_ERROR_CODE = 3892314230;
22+
private static INTERFACE_USB = 1;
2123

2224
private identifier: string = null;
2325
private voidPtr = ref.refType(ref.types.void);
@@ -299,6 +301,10 @@ export class IOSDevice implements Mobile.IIOSDevice {
299301
}).future<void>()();
300302
}
301303

304+
private getInterfaceType(): number {
305+
return this.$mobileDevice.deviceGetInterfaceType(this.devicePointer);
306+
}
307+
302308
public startService(serviceName: string): number {
303309
var func = () => {
304310
var socket = ref.alloc("int");
@@ -318,11 +324,16 @@ export class IOSDevice implements Mobile.IIOSDevice {
318324
}).future<void>()();
319325
}
320326

321-
public debug(packageFile: string, packageName: string): IFuture<void> {
322-
return (() => {
323-
this.$errors.fail({formatStr:"this will come in a future version", suppressCommandHelp: true});
324-
}).future<void>()();
325-
}
327+
public debug(packageFile: string, packageName: string): IFuture<void> {
328+
return (() => {
329+
var debugging: IOSDeviceDebugging = this.$injector.resolve(IOSDeviceDebugging, {
330+
packageFile: packageFile,
331+
packageName: packageName,
332+
$iOSDevice: this
333+
});
334+
debugging.debug();
335+
}).future<void>()();
336+
}
326337

327338
public sync(localToDevicePaths: Mobile.ILocalToDevicePathData[], appIdentifier: Mobile.IAppIdentifier, liveSyncUrl: string, options: Mobile.ISyncOptions = {}): IFuture<void> {
328339
return(() => {
@@ -340,7 +351,6 @@ export class IOSDevice implements Mobile.IIOSDevice {
340351
var notificationProxyClient = this.$injector.resolve(iOSProxyServices.NotificationProxyClient, {device: this});
341352
notificationProxyClient.postNotification("com.telerik.app.refreshWebView");
342353
notificationProxyClient.closeSocket();
343-
344354
}
345355

346356
this.$logger.out("Successfully synced device with identifier '%s'", this.getIdentifier());
@@ -381,5 +391,169 @@ export class IOSDevice implements Mobile.IIOSDevice {
381391
this.$logger.info("Successfully run application %s on device with ID %s", applicationId, this.getIdentifier());
382392
}).future<void>()();
383393
}
394+
395+
public connectToPort(port: number): net.NodeSocket {
396+
var interfaceType = this.getInterfaceType();
397+
if(interfaceType === IOSDevice.INTERFACE_USB) {
398+
var connectionId = this.$mobileDevice.deviceGetConnectionId(this.devicePointer);
399+
var socketRef = ref.alloc(CoreTypes.intType);
400+
401+
this.$mobileDevice.uSBMuxConnectByPort(connectionId, this.htons(port), socketRef);
402+
var socketValue = socketRef.deref();
403+
404+
return new net.Socket({ fd: socketValue });
405+
}
406+
407+
return null;
408+
}
409+
410+
/**
411+
* Converts a little endian 16 bit int number to 16 bit int big endian number.
412+
*/
413+
private htons(port: number): number {
414+
var result = (port & 0xff00) >> 8 | (port & 0x00ff) << 8;
415+
return result;
416+
}
417+
}
418+
$injector.register("iOSDevice", IOSDevice);
419+
420+
export class IOSDeviceDebugging {
421+
private notificationProxyClient: iOSProxyServices.NotificationProxyClient;
422+
423+
constructor(
424+
private packageFile: string,
425+
private packageName: string,
426+
private $iOSDevice: IOSDevice,
427+
428+
private $injector: IInjector,
429+
430+
private $logger: ILogger,
431+
private $errors: IErrors,
432+
433+
private $npm: INodePackageManager,
434+
private $childProcess: IChildProcess) {
435+
436+
this.notificationProxyClient = this.$injector.resolve(iOSProxyServices.NotificationProxyClient, { device: this.$iOSDevice });
437+
}
438+
439+
public debug(): void {
440+
if (options["get-port"]) {
441+
this.$errors.fail({ formatStr: "this feature is not supported in the selected platform", suppressCommandHelp: true });
442+
} else if (options["start"]) {
443+
this.attachDebugger();
444+
} else if (options["stop"]) {
445+
this.$errors.fail({ formatStr: "this feature is not supported in the selected platform", suppressCommandHelp: true });
446+
} else if (options["debug-brk"]) {
447+
this.startAppWithDebugger();
448+
} else {
449+
this.$logger.info("Should specify exactly one option: debug-brk, start");
450+
}
451+
}
452+
453+
public attachDebugger(): void {
454+
this.attachAtRuntime();
455+
}
456+
457+
public startAppWithDebugger(): void {
458+
this.$iOSDevice.deploy(this.packageFile, this.packageName).wait();
459+
this.attachAtStartup();
460+
this.$iOSDevice.runApplication(this.packageName).wait();
461+
}
462+
463+
private attachAtStartup() {
464+
this.startTheFrontEnd();
465+
466+
var appLaunchMessage = this.packageName + ":NativeScript.Debug.AppLaunching";
467+
this.notificationProxyClient.addObserver(appLaunchMessage, () => {
468+
this.$logger.info("Got AppLaunching");
469+
this.startTheBackend("WaitForDebugger");
470+
});
471+
}
472+
473+
private attachAtRuntime(): void {
474+
this.startTheFrontEnd();
475+
this.startTheBackend("AttachRequest");
476+
}
477+
478+
private startTheFrontEnd(): void {
479+
this.openSafariFrontEnd();
480+
this.printHowToTerminate();
481+
}
482+
483+
private startTheBackend(message: string) {
484+
this.attachWhenReady();
485+
this.postDebugNotification(message);
486+
}
487+
488+
private openSafariFrontEnd(): void {
489+
var tnsIosPackage = this.$npm.install("tns-ios").wait();
490+
var safariPath = path.join(tnsIosPackage, "WebInspectorUI/Safari/Main.html");
491+
this.$childProcess.exec("open -a Safari " + safariPath).wait();
492+
}
493+
494+
private attachWhenReady(): void {
495+
496+
var identifier = this.$iOSDevice.getIdentifier();
497+
this.$logger.info("Device Identifier: " + identifier);
498+
499+
var appReadyForAttachMessage = this.packageName + ":NativeScript.Debug.ReadyForAttach";
500+
this.notificationProxyClient.addObserver(appReadyForAttachMessage, () => {
501+
this.$logger.info("Got ReadyForAttach");
502+
503+
// NOTE: We will try to provide command line options to select ports, at least on the localhost.
504+
var devicePort = 8080;
505+
var localPort = 8080;
506+
507+
var localServer = net.createServer((localSocket) => {
508+
this.$logger.info("Front-end client connected to localhost " + localPort);
509+
510+
var deviceSocket: any;
511+
512+
var buff = new Array();
513+
localSocket.on('data', (data: NodeBuffer) => {
514+
if (deviceSocket) {
515+
deviceSocket.write(data);
516+
} else {
517+
buff.push(data);
518+
}
519+
});
520+
localSocket.on('end', () => {
521+
this.$logger.info('Localhost client disconnected!');
522+
process.exit(0);
523+
});
524+
525+
deviceSocket = this.$iOSDevice.connectToPort(devicePort);
526+
this.$logger.info("Connected localhost " + localPort + " to device " + devicePort);
527+
528+
buff.forEach((data) => {
529+
deviceSocket.write(data);
530+
});
531+
532+
deviceSocket.on('data', (data: NodeBuffer) => {
533+
localSocket.write(data);
534+
});
535+
536+
deviceSocket.on('end', () => {
537+
this.$logger.info("Device socket closed!");
538+
process.exit(0);
539+
});
540+
541+
});
542+
543+
localServer.listen(localPort, () => {
544+
this.$logger.info("Opened localhost " + localPort);
545+
});
546+
});
547+
}
548+
549+
private printHowToTerminate() {
550+
this.$logger.info("\nSetting up debugger proxy...\n\nPress Ctrl + C to terminate!\n");
551+
}
552+
553+
private postDebugNotification(notification: string): void {
554+
var attachRequestMessage = this.packageName + ":NativeScript.Debug.AttachRequest";
555+
this.notificationProxyClient.postNotification(attachRequestMessage);
556+
this.$logger.info("Send " + notification);
557+
}
384558
}
385-
$injector.register("iOSDevice", IOSDevice);
559+
$injector.register("iOSDeviceDebugging", IOSDeviceDebugging);

0 commit comments

Comments
 (0)