Skip to content

Send special message if we live sync javascript file and the liveEdit option is on #1977

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Aug 5, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions lib/declarations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ interface IOptions extends ICommonOptions {
teamId: string;
rebuild: boolean;
syncAllFiles: boolean;
liveEdit: boolean;
}

interface IInitService {
Expand Down
3 changes: 2 additions & 1 deletion lib/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@ export class Options extends commonOptionsLibPath.OptionsBase {
all: {type: OptionType.Boolean },
teamId: { type: OptionType.String },
rebuild: { type: OptionType.Boolean, default: true },
syncAllFiles: { type: OptionType.Boolean }
syncAllFiles: { type: OptionType.Boolean },
liveEdit: { type: OptionType.Boolean }
},
path.join($hostInfo.isWindows ? process.env.AppData : path.join(osenv.home(), ".local/share"), ".nativescript-cli"),
$errors, $staticConfig);
Expand Down
22 changes: 16 additions & 6 deletions lib/services/livesync/android-device-livesync-service.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import {DeviceAndroidDebugBridge} from "../../common/mobile/android/device-android-debug-bridge";
import {AndroidDeviceHashService} from "../../common/mobile/android/android-device-hash-service";
import {DeviceLiveSyncServiceBase} from "./device-livesync-service-base";
import Future = require("fibers/future");
import * as helpers from "../../common/helpers";
import * as path from "path";
import * as net from "net";

class AndroidLiveSyncService extends DeviceLiveSyncServiceBase<Mobile.IAndroidDevice> implements IDeviceLiveSyncService {
class AndroidLiveSyncService implements IDeviceLiveSyncService {
private static BACKEND_PORT = 18182;
private device: Mobile.IAndroidDevice;

constructor(_device: Mobile.IDevice,
private $fs: IFileSystem,
Expand All @@ -16,15 +16,25 @@ class AndroidLiveSyncService extends DeviceLiveSyncServiceBase<Mobile.IAndroidDe
private $injector: IInjector,
private $projectData: IProjectData,
private $androidDebugService: IDebugService,
$liveSyncProvider: ILiveSyncProvider) {
super(_device, $liveSyncProvider);
private $liveSyncProvider: ILiveSyncProvider) {
this.device = <Mobile.IAndroidDevice>(_device);
}

public get debugService(): IDebugService {
return this.$androidDebugService;
}

public restartApplication(deviceAppData: Mobile.IDeviceAppData): IFuture<void> {
public refreshApplication(deviceAppData: Mobile.IDeviceAppData, localToDevicePaths: Mobile.ILocalToDevicePathData[], forceExecuteFullSync: boolean): IFuture<void> {
let canExecuteFastSync = !forceExecuteFullSync && !_.some(localToDevicePaths, (localToDevicePath:any) => !this.$liveSyncProvider.canExecuteFastSync(localToDevicePath.getLocalPath(), deviceAppData.platform));

if (canExecuteFastSync) {
return this.reloadPage(deviceAppData, localToDevicePaths);
}

return this.restartApplication(deviceAppData);
}

private restartApplication(deviceAppData: Mobile.IDeviceAppData): IFuture<void> {
return (() => {
this.device.adb.executeShellCommand(["chmod", "777", deviceAppData.deviceProjectRootPath, `/data/local/tmp/${deviceAppData.appIdentifier}`]).wait();

Expand Down Expand Up @@ -56,7 +66,7 @@ class AndroidLiveSyncService extends DeviceLiveSyncServiceBase<Mobile.IAndroidDe
}).future<void>()();
}

public reloadPage(deviceAppData: Mobile.IDeviceAppData): IFuture<void> {
private reloadPage(deviceAppData: Mobile.IDeviceAppData, localToDevicePaths: Mobile.ILocalToDevicePathData[]): IFuture<void> {
return (() => {
this.device.adb.executeCommand(["forward", `tcp:${AndroidLiveSyncService.BACKEND_PORT.toString()}`, `localabstract:${deviceAppData.appIdentifier}-livesync`]).wait();
this.sendPageReloadMessage().wait();
Expand Down
21 changes: 0 additions & 21 deletions lib/services/livesync/device-livesync-service-base.ts

This file was deleted.

187 changes: 131 additions & 56 deletions lib/services/livesync/ios-device-livesync-service.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import {DeviceLiveSyncServiceBase} from "./device-livesync-service-base";
import * as helpers from "../../common/helpers";
import * as net from "net";
import Future = require("fibers/future");

let currentPageReloadId = 0;

class IOSLiveSyncService extends DeviceLiveSyncServiceBase<Mobile.IiOSDevice> implements IDeviceLiveSyncService {
class IOSLiveSyncService implements IDeviceLiveSyncService {
private static BACKEND_PORT = 18181;
private socket: net.Socket;
private device: Mobile.IiOSDevice;

constructor(_device: Mobile.IDevice,
private $iOSSocketRequestExecutor: IiOSSocketRequestExecutor,
Expand All @@ -17,8 +18,9 @@ class IOSLiveSyncService extends DeviceLiveSyncServiceBase<Mobile.IiOSDevice> im
private $options: IOptions,
private $iOSDebugService: IDebugService,
private $childProcess: IChildProcess,
$liveSyncProvider: ILiveSyncProvider) {
super(_device, $liveSyncProvider);
private $fs: IFileSystem,
private $liveSyncProvider: ILiveSyncProvider) {
this.device = <Mobile.IiOSDevice>(_device);
}

public get debugService(): IDebugService {
Expand All @@ -31,76 +33,155 @@ class IOSLiveSyncService extends DeviceLiveSyncServiceBase<Mobile.IiOSDevice> im
}).future<boolean>()();
}

private setupSocketIfNeeded(): IFuture<boolean> {
return (() => {
if (this.socket) {
return true;
}

let enableDebuggerMessage = `{ "method":"Debugger.enable","id":${++currentPageReloadId} }`;
if (this.device.isEmulator) {
this.$iOSEmulatorServices.postDarwinNotification(this.$iOSNotification.attachRequest).wait();
try {
this.socket = helpers.connectEventuallyUntilTimeout(() => net.connect(IOSLiveSyncService.BACKEND_PORT), 5000).wait();
} catch (e) {
this.$logger.warn(e);

return false;
}
} else {
let timeout = 9000;
this.$iOSSocketRequestExecutor.executeAttachRequest(this.device, timeout).wait();
this.socket = this.device.connectToPort(IOSLiveSyncService.BACKEND_PORT);
}

this.attachEventHandlers();
this.sendMessage(enableDebuggerMessage).wait();

return true;
}).future<boolean>()();
}

public removeFiles(appIdentifier: string, localToDevicePaths: Mobile.ILocalToDevicePathData[]): IFuture<void> {
return (() => {
_.each(localToDevicePaths, localToDevicePathData => this.device.fileSystem.deleteFile(localToDevicePathData.getDevicePath(), appIdentifier));
}).future<void>()();
}

protected restartApplication(deviceAppData: Mobile.IDeviceAppData): IFuture<void> {
public refreshApplication(deviceAppData: Mobile.IDeviceAppData, localToDevicePaths: Mobile.ILocalToDevicePathData[], forceExecuteFullSync: boolean): IFuture<void> {
return (() => {
if (forceExecuteFullSync) {
this.restartApplication(deviceAppData).wait();
return;
}
let scriptFiles = _.filter(localToDevicePaths, localToDevicePath => _.endsWith(localToDevicePath.getDevicePath(), ".js"));
let otherFiles = _.difference(localToDevicePaths, scriptFiles);
let shouldRestart = _.some(otherFiles, (localToDevicePath: Mobile.ILocalToDevicePathData) => !this.$liveSyncProvider.canExecuteFastSync(localToDevicePath.getLocalPath(), deviceAppData.platform));

if (shouldRestart) {
this.restartApplication(deviceAppData).wait();

return;
}

if (!this.$options.liveEdit && scriptFiles.length) {
this.restartApplication(deviceAppData).wait();

return;
}

if (this.setupSocketIfNeeded().wait()) {
this.liveEdit(scriptFiles);
this.reloadPage(deviceAppData, otherFiles).wait();
}
}).future<void>()();
}

private restartApplication(deviceAppData: Mobile.IDeviceAppData): IFuture<void> {
let projectData: IProjectData = this.$injector.resolve("projectData");
return this.device.applicationManager.restartApplication(deviceAppData.appIdentifier, projectData.projectName);
}

protected reloadPage(deviceAppData: Mobile.IDeviceAppData): IFuture<void> {
private reloadPage(deviceAppData: Mobile.IDeviceAppData, localToDevicePaths: Mobile.ILocalToDevicePathData[]): IFuture<void> {
return (() => {
let timeout = 9000;
if (this.device.isEmulator) {
if (!this.socket) {
helpers.connectEventually(() => net.connect(IOSLiveSyncService.BACKEND_PORT), (socket: net.Socket) => {
this.socket = socket;
this.attachEventHandlersIfNecessary();
this.sendPageReloadMessage();
});
} else {
this.sendPageReloadMessage();
}
this.$iOSEmulatorServices.postDarwinNotification(this.$iOSNotification.attachRequest).wait();
} else {
if(!this.socket) {
this.$iOSSocketRequestExecutor.executeAttachRequest(this.device, timeout).wait();
this.socket = this.device.connectToPort(IOSLiveSyncService.BACKEND_PORT);
this.attachEventHandlersIfNecessary();
}
this.sendPageReloadMessage();
if (localToDevicePaths.length) {
let message = JSON.stringify({
method: "Page.reload",
params: {
ignoreCache: false
},
id: ++currentPageReloadId
});

this.sendMessage(message).wait();
}
}).future<void>()();
}

private attachEventHandlersIfNecessary(): void {
if(this.$options.watch) {
this.attachProcessExitHandlers();
this.attachSocketCloseEvent();
}
private liveEdit(localToDevicePaths: Mobile.ILocalToDevicePathData[]) {
return (() => {
_.each(localToDevicePaths, localToDevicePath => {
let content = this.$fs.readText(localToDevicePath.getLocalPath()).wait();
let message = JSON.stringify({
method: "Debugger.setScriptSource",
params: {
scriptUrl: localToDevicePath.getDevicePath(),
scriptSource: content
},
id: ++currentPageReloadId
});

this.sendMessage(message).wait();
});
}).future<void>()();
}

private attachSocketCloseEvent(): void {
private attachEventHandlers(): void {
this.attachProcessExitHandlers();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you can replace this with

this.$processService.attachToProcessExitSignals(this, this.destroySocket);

This will prevent multiple handlers for exit, SIGINT and SIGTERM events.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Totally agree with you and if you don't mind I will do this in another PR because it may require additional changes


this.socket.on("close", (hadError: boolean) => {
this.$logger.trace(`Socket closed, hadError is ${hadError}.`);
this.socket = null;
});
}

private sendPageReloadMessage(): void {
try {
this.sendPageReloadMessageCore();
this.socket.on("data", (data: NodeBuffer|string) => {
this.$logger.trace(`Socket sent data: ${data.toString()}`);
this.destroySocketIfNecessary();
});
} catch(err) {
this.$logger.trace("Error while sending page reload:", err);
this.destroySocketIfNecessary();
}
this.socket.on("error", (error: any) => {
this.$logger.trace(`Socket error received: ${error}`);
});

this.socket.on("data", (data: NodeBuffer|string) => {
this.$logger.trace(`Socket sent data: ${data.toString()}`);
});
}

private sendPageReloadMessageCore(): void {
let message = `{ "method":"Page.reload","params":{"ignoreCache":false},"id":${++currentPageReloadId} }`;
let length = Buffer.byteLength(message, "utf16le");
let payload = new Buffer(length + 4);
payload.writeInt32BE(length, 0);
payload.write(message, 4, length, "utf16le");
this.socket.write(payload);
private sendMessage(message: string): IFuture<void> {
return (() => {
let socketWriteFuture = new Future<void>();
try {
let length = Buffer.byteLength(message, "utf16le");
let payload = new Buffer(length + 4);
payload.writeInt32BE(length, 0);
payload.write(message, 4, length, "utf16le");

this.socket.once("error", (error: Error) => {
if (!socketWriteFuture.isResolved()) {
socketWriteFuture.throw(error);
}
});

this.socket.write(payload, "utf16le", () => {
this.socket.removeAllListeners("error");

if (!socketWriteFuture.isResolved()) {
socketWriteFuture.return();
}
});

socketWriteFuture.wait();
} catch(error) {
this.$logger.trace("Error while sending message:", error);
this.destroySocket();
}
}).future<void>()();
}

private attachProcessExitHandlers(): void {
Expand All @@ -118,12 +199,6 @@ class IOSLiveSyncService extends DeviceLiveSyncServiceBase<Mobile.IiOSDevice> im
});
}

private destroySocketIfNecessary(): void {
if(!this.$options.watch) {
this.destroySocket();
}
}

private destroySocket(): void {
if(this.socket) {
this.socket.destroy();
Expand Down
1 change: 1 addition & 0 deletions test/ios-project-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ function createTestInjector(projectPath: string, projectName: string): IInjector
testInjector.register("pluginVariablesService", PluginVariablesService);
testInjector.register("pluginVariablesHelper", PluginVariablesHelper);
testInjector.register("androidProcessService", {});
testInjector.register("processService", {});
return testInjector;
}

Expand Down