Skip to content

Commit 517aef4

Browse files
Merge pull request #1977 from NativeScript/KristinaKoeva/LiveEditJavaScript
Send special message if we live sync javascript file and the liveEdit option is on
2 parents e70cd89 + 9b3b3a6 commit 517aef4

7 files changed

+152
-85
lines changed

lib/declarations.ts

+1
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ interface IOptions extends ICommonOptions {
100100
teamId: string;
101101
rebuild: boolean;
102102
syncAllFiles: boolean;
103+
liveEdit: boolean;
103104
}
104105

105106
interface IInitService {

lib/options.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,8 @@ export class Options extends commonOptionsLibPath.OptionsBase {
3838
all: {type: OptionType.Boolean },
3939
teamId: { type: OptionType.String },
4040
rebuild: { type: OptionType.Boolean, default: true },
41-
syncAllFiles: { type: OptionType.Boolean }
41+
syncAllFiles: { type: OptionType.Boolean },
42+
liveEdit: { type: OptionType.Boolean }
4243
},
4344
path.join($hostInfo.isWindows ? process.env.AppData : path.join(osenv.home(), ".local/share"), ".nativescript-cli"),
4445
$errors, $staticConfig);

lib/services/livesync/android-device-livesync-service.ts

+16-6
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
import {DeviceAndroidDebugBridge} from "../../common/mobile/android/device-android-debug-bridge";
22
import {AndroidDeviceHashService} from "../../common/mobile/android/android-device-hash-service";
3-
import {DeviceLiveSyncServiceBase} from "./device-livesync-service-base";
43
import Future = require("fibers/future");
54
import * as helpers from "../../common/helpers";
65
import * as path from "path";
76
import * as net from "net";
87

9-
class AndroidLiveSyncService extends DeviceLiveSyncServiceBase<Mobile.IAndroidDevice> implements IDeviceLiveSyncService {
8+
class AndroidLiveSyncService implements IDeviceLiveSyncService {
109
private static BACKEND_PORT = 18182;
10+
private device: Mobile.IAndroidDevice;
1111

1212
constructor(_device: Mobile.IDevice,
1313
private $fs: IFileSystem,
@@ -16,15 +16,25 @@ class AndroidLiveSyncService extends DeviceLiveSyncServiceBase<Mobile.IAndroidDe
1616
private $injector: IInjector,
1717
private $projectData: IProjectData,
1818
private $androidDebugService: IDebugService,
19-
$liveSyncProvider: ILiveSyncProvider) {
20-
super(_device, $liveSyncProvider);
19+
private $liveSyncProvider: ILiveSyncProvider) {
20+
this.device = <Mobile.IAndroidDevice>(_device);
2121
}
2222

2323
public get debugService(): IDebugService {
2424
return this.$androidDebugService;
2525
}
2626

27-
public restartApplication(deviceAppData: Mobile.IDeviceAppData): IFuture<void> {
27+
public refreshApplication(deviceAppData: Mobile.IDeviceAppData, localToDevicePaths: Mobile.ILocalToDevicePathData[], forceExecuteFullSync: boolean): IFuture<void> {
28+
let canExecuteFastSync = !forceExecuteFullSync && !_.some(localToDevicePaths, (localToDevicePath:any) => !this.$liveSyncProvider.canExecuteFastSync(localToDevicePath.getLocalPath(), deviceAppData.platform));
29+
30+
if (canExecuteFastSync) {
31+
return this.reloadPage(deviceAppData, localToDevicePaths);
32+
}
33+
34+
return this.restartApplication(deviceAppData);
35+
}
36+
37+
private restartApplication(deviceAppData: Mobile.IDeviceAppData): IFuture<void> {
2838
return (() => {
2939
this.device.adb.executeShellCommand(["chmod", "777", deviceAppData.deviceProjectRootPath, `/data/local/tmp/${deviceAppData.appIdentifier}`]).wait();
3040

@@ -56,7 +66,7 @@ class AndroidLiveSyncService extends DeviceLiveSyncServiceBase<Mobile.IAndroidDe
5666
}).future<void>()();
5767
}
5868

59-
public reloadPage(deviceAppData: Mobile.IDeviceAppData): IFuture<void> {
69+
private reloadPage(deviceAppData: Mobile.IDeviceAppData, localToDevicePaths: Mobile.ILocalToDevicePathData[]): IFuture<void> {
6070
return (() => {
6171
this.device.adb.executeCommand(["forward", `tcp:${AndroidLiveSyncService.BACKEND_PORT.toString()}`, `localabstract:${deviceAppData.appIdentifier}-livesync`]).wait();
6272
this.sendPageReloadMessage().wait();

lib/services/livesync/device-livesync-service-base.ts

-21
This file was deleted.

lib/services/livesync/ios-device-livesync-service.ts

+131-56
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
1-
import {DeviceLiveSyncServiceBase} from "./device-livesync-service-base";
21
import * as helpers from "../../common/helpers";
32
import * as net from "net";
3+
import Future = require("fibers/future");
44

55
let currentPageReloadId = 0;
66

7-
class IOSLiveSyncService extends DeviceLiveSyncServiceBase<Mobile.IiOSDevice> implements IDeviceLiveSyncService {
7+
class IOSLiveSyncService implements IDeviceLiveSyncService {
88
private static BACKEND_PORT = 18181;
99
private socket: net.Socket;
10+
private device: Mobile.IiOSDevice;
1011

1112
constructor(_device: Mobile.IDevice,
1213
private $iOSSocketRequestExecutor: IiOSSocketRequestExecutor,
@@ -17,8 +18,9 @@ class IOSLiveSyncService extends DeviceLiveSyncServiceBase<Mobile.IiOSDevice> im
1718
private $options: IOptions,
1819
private $iOSDebugService: IDebugService,
1920
private $childProcess: IChildProcess,
20-
$liveSyncProvider: ILiveSyncProvider) {
21-
super(_device, $liveSyncProvider);
21+
private $fs: IFileSystem,
22+
private $liveSyncProvider: ILiveSyncProvider) {
23+
this.device = <Mobile.IiOSDevice>(_device);
2224
}
2325

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

36+
private setupSocketIfNeeded(): IFuture<boolean> {
37+
return (() => {
38+
if (this.socket) {
39+
return true;
40+
}
41+
42+
let enableDebuggerMessage = `{ "method":"Debugger.enable","id":${++currentPageReloadId} }`;
43+
if (this.device.isEmulator) {
44+
this.$iOSEmulatorServices.postDarwinNotification(this.$iOSNotification.attachRequest).wait();
45+
try {
46+
this.socket = helpers.connectEventuallyUntilTimeout(() => net.connect(IOSLiveSyncService.BACKEND_PORT), 5000).wait();
47+
} catch (e) {
48+
this.$logger.warn(e);
49+
50+
return false;
51+
}
52+
} else {
53+
let timeout = 9000;
54+
this.$iOSSocketRequestExecutor.executeAttachRequest(this.device, timeout).wait();
55+
this.socket = this.device.connectToPort(IOSLiveSyncService.BACKEND_PORT);
56+
}
57+
58+
this.attachEventHandlers();
59+
this.sendMessage(enableDebuggerMessage).wait();
60+
61+
return true;
62+
}).future<boolean>()();
63+
}
64+
3465
public removeFiles(appIdentifier: string, localToDevicePaths: Mobile.ILocalToDevicePathData[]): IFuture<void> {
3566
return (() => {
3667
_.each(localToDevicePaths, localToDevicePathData => this.device.fileSystem.deleteFile(localToDevicePathData.getDevicePath(), appIdentifier));
3768
}).future<void>()();
3869
}
3970

40-
protected restartApplication(deviceAppData: Mobile.IDeviceAppData): IFuture<void> {
71+
public refreshApplication(deviceAppData: Mobile.IDeviceAppData, localToDevicePaths: Mobile.ILocalToDevicePathData[], forceExecuteFullSync: boolean): IFuture<void> {
72+
return (() => {
73+
if (forceExecuteFullSync) {
74+
this.restartApplication(deviceAppData).wait();
75+
return;
76+
}
77+
let scriptFiles = _.filter(localToDevicePaths, localToDevicePath => _.endsWith(localToDevicePath.getDevicePath(), ".js"));
78+
let otherFiles = _.difference(localToDevicePaths, scriptFiles);
79+
let shouldRestart = _.some(otherFiles, (localToDevicePath: Mobile.ILocalToDevicePathData) => !this.$liveSyncProvider.canExecuteFastSync(localToDevicePath.getLocalPath(), deviceAppData.platform));
80+
81+
if (shouldRestart) {
82+
this.restartApplication(deviceAppData).wait();
83+
84+
return;
85+
}
86+
87+
if (!this.$options.liveEdit && scriptFiles.length) {
88+
this.restartApplication(deviceAppData).wait();
89+
90+
return;
91+
}
92+
93+
if (this.setupSocketIfNeeded().wait()) {
94+
this.liveEdit(scriptFiles);
95+
this.reloadPage(deviceAppData, otherFiles).wait();
96+
}
97+
}).future<void>()();
98+
}
99+
100+
private restartApplication(deviceAppData: Mobile.IDeviceAppData): IFuture<void> {
41101
let projectData: IProjectData = this.$injector.resolve("projectData");
42102
return this.device.applicationManager.restartApplication(deviceAppData.appIdentifier, projectData.projectName);
43103
}
44104

45-
protected reloadPage(deviceAppData: Mobile.IDeviceAppData): IFuture<void> {
105+
private reloadPage(deviceAppData: Mobile.IDeviceAppData, localToDevicePaths: Mobile.ILocalToDevicePathData[]): IFuture<void> {
46106
return (() => {
47-
let timeout = 9000;
48-
if (this.device.isEmulator) {
49-
if (!this.socket) {
50-
helpers.connectEventually(() => net.connect(IOSLiveSyncService.BACKEND_PORT), (socket: net.Socket) => {
51-
this.socket = socket;
52-
this.attachEventHandlersIfNecessary();
53-
this.sendPageReloadMessage();
54-
});
55-
} else {
56-
this.sendPageReloadMessage();
57-
}
58-
this.$iOSEmulatorServices.postDarwinNotification(this.$iOSNotification.attachRequest).wait();
59-
} else {
60-
if(!this.socket) {
61-
this.$iOSSocketRequestExecutor.executeAttachRequest(this.device, timeout).wait();
62-
this.socket = this.device.connectToPort(IOSLiveSyncService.BACKEND_PORT);
63-
this.attachEventHandlersIfNecessary();
64-
}
65-
this.sendPageReloadMessage();
107+
if (localToDevicePaths.length) {
108+
let message = JSON.stringify({
109+
method: "Page.reload",
110+
params: {
111+
ignoreCache: false
112+
},
113+
id: ++currentPageReloadId
114+
});
115+
116+
this.sendMessage(message).wait();
66117
}
67118
}).future<void>()();
68119
}
69120

70-
private attachEventHandlersIfNecessary(): void {
71-
if(this.$options.watch) {
72-
this.attachProcessExitHandlers();
73-
this.attachSocketCloseEvent();
74-
}
121+
private liveEdit(localToDevicePaths: Mobile.ILocalToDevicePathData[]) {
122+
return (() => {
123+
_.each(localToDevicePaths, localToDevicePath => {
124+
let content = this.$fs.readText(localToDevicePath.getLocalPath()).wait();
125+
let message = JSON.stringify({
126+
method: "Debugger.setScriptSource",
127+
params: {
128+
scriptUrl: localToDevicePath.getDevicePath(),
129+
scriptSource: content
130+
},
131+
id: ++currentPageReloadId
132+
});
133+
134+
this.sendMessage(message).wait();
135+
});
136+
}).future<void>()();
75137
}
76138

77-
private attachSocketCloseEvent(): void {
139+
private attachEventHandlers(): void {
140+
this.attachProcessExitHandlers();
141+
78142
this.socket.on("close", (hadError: boolean) => {
79143
this.$logger.trace(`Socket closed, hadError is ${hadError}.`);
80144
this.socket = null;
81145
});
82-
}
83146

84-
private sendPageReloadMessage(): void {
85-
try {
86-
this.sendPageReloadMessageCore();
87-
this.socket.on("data", (data: NodeBuffer|string) => {
88-
this.$logger.trace(`Socket sent data: ${data.toString()}`);
89-
this.destroySocketIfNecessary();
90-
});
91-
} catch(err) {
92-
this.$logger.trace("Error while sending page reload:", err);
93-
this.destroySocketIfNecessary();
94-
}
147+
this.socket.on("error", (error: any) => {
148+
this.$logger.trace(`Socket error received: ${error}`);
149+
});
150+
151+
this.socket.on("data", (data: NodeBuffer|string) => {
152+
this.$logger.trace(`Socket sent data: ${data.toString()}`);
153+
});
95154
}
96155

97-
private sendPageReloadMessageCore(): void {
98-
let message = `{ "method":"Page.reload","params":{"ignoreCache":false},"id":${++currentPageReloadId} }`;
99-
let length = Buffer.byteLength(message, "utf16le");
100-
let payload = new Buffer(length + 4);
101-
payload.writeInt32BE(length, 0);
102-
payload.write(message, 4, length, "utf16le");
103-
this.socket.write(payload);
156+
private sendMessage(message: string): IFuture<void> {
157+
return (() => {
158+
let socketWriteFuture = new Future<void>();
159+
try {
160+
let length = Buffer.byteLength(message, "utf16le");
161+
let payload = new Buffer(length + 4);
162+
payload.writeInt32BE(length, 0);
163+
payload.write(message, 4, length, "utf16le");
164+
165+
this.socket.once("error", (error: Error) => {
166+
if (!socketWriteFuture.isResolved()) {
167+
socketWriteFuture.throw(error);
168+
}
169+
});
170+
171+
this.socket.write(payload, "utf16le", () => {
172+
this.socket.removeAllListeners("error");
173+
174+
if (!socketWriteFuture.isResolved()) {
175+
socketWriteFuture.return();
176+
}
177+
});
178+
179+
socketWriteFuture.wait();
180+
} catch(error) {
181+
this.$logger.trace("Error while sending message:", error);
182+
this.destroySocket();
183+
}
184+
}).future<void>()();
104185
}
105186

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

121-
private destroySocketIfNecessary(): void {
122-
if(!this.$options.watch) {
123-
this.destroySocket();
124-
}
125-
}
126-
127202
private destroySocket(): void {
128203
if(this.socket) {
129204
this.socket.destroy();

test/ios-project-service.ts

+1
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ function createTestInjector(projectPath: string, projectName: string): IInjector
8282
testInjector.register("pluginVariablesService", PluginVariablesService);
8383
testInjector.register("pluginVariablesHelper", PluginVariablesHelper);
8484
testInjector.register("androidProcessService", {});
85+
testInjector.register("processService", {});
8586
return testInjector;
8687
}
8788

0 commit comments

Comments
 (0)