Skip to content

Commit 0e1fb1a

Browse files
committed
feat: recover hmr fail in preview
1 parent fdcb78a commit 0e1fb1a

9 files changed

+65
-17
lines changed

lib/definitions/preview-app-livesync.d.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { FilePayload, Device, FilesPayload } from "nativescript-preview-sdk";
2+
import { EventEmitter } from "events";
23

34
declare global {
45
interface IPreviewAppLiveSyncService {
@@ -9,7 +10,7 @@ declare global {
910

1011
interface IPreviewAppLiveSyncData extends IProjectDir, IAppFilesUpdaterOptionsComposition, IEnvOptions { }
1112

12-
interface IPreviewSdkService {
13+
interface IPreviewSdkService extends EventEmitter {
1314
getQrCodeUrl(options: IHasUseHotModuleReloadOption): string;
1415
connectedDevices: Device[];
1516
initialize(getInitialFiles: (device: Device) => Promise<FilesPayload>): void;

lib/services/hmr-status-service.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { HmrConstants } from "../common/constants";
44
export class HmrStatusService implements IHmrStatusService {
55
public static HMR_STATUS_LOG_REGEX = /([a-z A-Z]*) hmr hash ([a-z0-9]*)\./;
66
public static STARTED_MESSAGE = "Checking for updates to the bundle with";
7-
public static SUCCESS_MESSAGE = "Successfuly applied update with";
7+
public static SUCCESS_MESSAGE = "Successfully applied update with";
88
public static FAILED_MESSAGE = "Cannot apply update with";
99
private hashOperationStatuses: IDictionary<any> = {};
1010

lib/services/livesync/playground/preview-app-livesync-service.ts

+29-7
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import * as path from "path";
22
import { FilePayload, Device, FilesPayload } from "nativescript-preview-sdk";
33
import { PreviewSdkEventNames } from "./preview-app-constants";
44
import { APP_FOLDER_NAME, APP_RESOURCES_FOLDER_NAME, TNS_MODULES_FOLDER_NAME } from "../../../constants";
5+
import { HmrConstants } from "../../../common/constants";
56
const isTextOrBinary = require('istextorbinary');
67

78
export class PreviewAppLiveSyncService implements IPreviewAppLiveSyncService {
@@ -19,6 +20,7 @@ export class PreviewAppLiveSyncService implements IPreviewAppLiveSyncService {
1920
private $previewSdkService: IPreviewSdkService,
2021
private $previewAppPluginsService: IPreviewAppPluginsService,
2122
private $projectFilesManager: IProjectFilesManager,
23+
private $hmrStatusService: IHmrStatusService,
2224
private $projectFilesProvider: IProjectFilesProvider) { }
2325

2426
public async initialize(data: IPreviewAppLiveSyncData): Promise<void> {
@@ -43,19 +45,37 @@ export class PreviewAppLiveSyncService implements IPreviewAppLiveSyncService {
4345

4446
private async initializePreviewForDevice(data: IPreviewAppLiveSyncData, device: Device): Promise<FilesPayload> {
4547
const filesToSyncMap: IDictionary<string[]> = {};
48+
const hmrData: { hash: string; fallbackFiles: IDictionary<string[]> } = {
49+
hash: "",
50+
fallbackFiles: {}
51+
};
4652
let promise = Promise.resolve<FilesPayload>(null);
4753
const startSyncFilesTimeout = async (platform: string) => {
4854
await promise
4955
.then(async () => {
5056
const projectData = this.$projectDataService.getProjectData(data.projectDir);
51-
promise = this.applyChanges(this.$platformsData.getPlatformData(platform, projectData), projectData, filesToSyncMap[platform]);
57+
const platformData = this.$platformsData.getPlatformData(platform, projectData);
58+
const currentHmrData = _.cloneDeep(hmrData);
59+
const filesToSync = _.cloneDeep(filesToSyncMap[platform]);
60+
promise = this.applyChanges(platformData, projectData, filesToSync, data.appFilesUpdaterOptions.useHotModuleReload);
5261
await promise;
62+
63+
if (data.appFilesUpdaterOptions.useHotModuleReload && currentHmrData.hash) {
64+
const devices = _.filter(this.$previewSdkService.connectedDevices, { platform: platform.toLowerCase() });
65+
_.forEach(devices, async (previewDevice: Device) => {
66+
const status = await this.$hmrStatusService.awaitHmrStatus(previewDevice.id, currentHmrData.hash);
67+
if (status === HmrConstants.HMR_ERROR_STATUS) {
68+
await this.applyChanges(platformData, projectData, currentHmrData.fallbackFiles[platform], false);
69+
}
70+
});
71+
}
5372
});
5473
filesToSyncMap[platform] = [];
5574
};
5675
await this.$hooksService.executeBeforeHooks("preview-sync", {
5776
hookArgs: {
5877
projectData: this.$projectDataService.getProjectData(data.projectDir),
78+
hmrData,
5979
config: {
6080
env: data.env,
6181
platform: device.platform,
@@ -104,10 +124,11 @@ export class PreviewAppLiveSyncService implements IPreviewAppLiveSyncService {
104124

105125
let result: FilesPayload = null;
106126
if (files && files.length) {
107-
result = await this.applyChanges(platformData, projectData, files);
127+
result = await this.applyChanges(platformData, projectData, files, data.appFilesUpdaterOptions.useHotModuleReload);
108128
this.$logger.info(`Successfully synced ${result.files.map(filePayload => filePayload.file.yellow)} for platform ${platform}.`);
109129
} else {
110-
result = await this.getFilesPayload(platformData, projectData);
130+
const hmrMode = data.appFilesUpdaterOptions.useHotModuleReload ? 1 : 0;
131+
result = await this.getFilesPayload(platformData, projectData, hmrMode);
111132
this.$logger.info(`Successfully synced changes for platform ${platform}.`);
112133
}
113134

@@ -117,14 +138,15 @@ export class PreviewAppLiveSyncService implements IPreviewAppLiveSyncService {
117138
}
118139
}
119140

120-
private async applyChanges(platformData: IPlatformData, projectData: IProjectData, files: string[]): Promise<FilesPayload> {
121-
const payloads = this.getFilesPayload(platformData, projectData, _(files).uniq().value());
141+
private async applyChanges(platformData: IPlatformData, projectData: IProjectData, files: string[], useHotModuleReload: Boolean, deviceId?: string): Promise<FilesPayload> {
142+
const hmrMode = useHotModuleReload ? 1 : 0;
143+
const payloads = this.getFilesPayload(platformData, projectData, hmrMode, _(files).uniq().value(), deviceId);
122144
await this.$previewSdkService.applyChanges(payloads);
123145

124146
return payloads;
125147
}
126148

127-
private getFilesPayload(platformData: IPlatformData, projectData: IProjectData, files?: string[]): FilesPayload {
149+
private getFilesPayload(platformData: IPlatformData, projectData: IProjectData, hmrMode: number, files?: string[], deviceId?: string): FilesPayload {
128150
const platformsAppFolderPath = path.join(platformData.appDestinationDirectoryPath, APP_FOLDER_NAME);
129151

130152
if (files && files.length) {
@@ -163,7 +185,7 @@ export class PreviewAppLiveSyncService implements IPreviewAppLiveSyncService {
163185
return filePayload;
164186
});
165187

166-
return { files: payloads, platform: platformData.normalizedPlatformName.toLowerCase() };
188+
return { files: payloads, platform: platformData.normalizedPlatformName.toLowerCase(), hmrMode, deviceId};
167189
}
168190

169191
private async preparePlatform(platform: string, appFilesUpdaterOptions: IAppFilesUpdaterOptions, env: Object, projectData: IProjectData): Promise<void> {

lib/services/livesync/playground/preview-sdk-service.ts

+6-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
import { MessagingService, Config, Device, DeviceConnectedMessage, SdkCallbacks, ConnectedDevices, FilesPayload } from "nativescript-preview-sdk";
22
import { PubnubKeys } from "./preview-app-constants";
3+
import { DEVICE_LOG_EVENT_NAME } from "../../../common/constants";
4+
import { EventEmitter } from "events";
35
const pako = require("pako");
46

5-
export class PreviewSdkService implements IPreviewSdkService {
7+
export class PreviewSdkService extends EventEmitter implements IPreviewSdkService {
68
private static MAX_FILES_UPLOAD_BYTE_LENGTH = 15 * 1024 * 1024; // In MBs
79
private messagingService: MessagingService = null;
810
private instanceId: string = null;
@@ -11,6 +13,7 @@ export class PreviewSdkService implements IPreviewSdkService {
1113
constructor(private $logger: ILogger,
1214
private $httpClient: Server.IHttpClient,
1315
private $config: IConfiguration) {
16+
super();
1417
}
1518

1619
public getQrCodeUrl(options: IHasUseHotModuleReloadOption): string {
@@ -58,7 +61,8 @@ export class PreviewSdkService implements IPreviewSdkService {
5861
this.$logger.trace("Received onLogSdkMessage message: ", log);
5962
},
6063
onConnectedDevicesChange: (connectedDevices: ConnectedDevices) => ({ }),
61-
onLogMessage: (log: string, deviceName: string) => {
64+
onLogMessage: (log: string, deviceName: string, deviceId: string) => {
65+
this.emit(DEVICE_LOG_EVENT_NAME, log, deviceId);
6266
this.$logger.info(`LOG from device ${deviceName}: ${log}`);
6367
},
6468
onRestartMessage: () => {

lib/services/log-parser-service.ts

+17-4
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ export class LogParserService extends EventEmitter implements ILogParserService
77

88
constructor(private $deviceLogProvider: Mobile.IDeviceLogProvider,
99
private $devicesService: Mobile.IDevicesService,
10-
private $errors: IErrors) {
10+
private $errors: IErrors,
11+
private $previewSdkService: IPreviewSdkService) {
1112
super();
1213
}
1314

@@ -23,16 +24,16 @@ export class LogParserService extends EventEmitter implements ILogParserService
2324
@cache()
2425
private startParsingLogCore(): void {
2526
this.$deviceLogProvider.on(DEVICE_LOG_EVENT_NAME, (message: string, deviceIdentifier: string) => this.processDeviceLogResponse(message, deviceIdentifier));
27+
this.$previewSdkService.on(DEVICE_LOG_EVENT_NAME, (message: string, deviceIdentifier: string) => this.processDeviceLogResponse(message, deviceIdentifier));
2628
}
2729

2830
private processDeviceLogResponse(message: string, deviceIdentifier: string) {
29-
const device = this.$devicesService.getDeviceByIdentifier(deviceIdentifier);
30-
const devicePlatform = device.deviceInfo.platform.toLowerCase();
31+
const devicePlatform = this.tryGetPlatform(deviceIdentifier);
3132

3233
const lines = message.split("\n");
3334
_.forEach(lines, line => {
3435
_.forEach(this.parseRules, (parseRule) => {
35-
if (!parseRule.platform || parseRule.platform.toLowerCase() === devicePlatform) {
36+
if (!devicePlatform || !parseRule.platform || parseRule.platform.toLowerCase() === devicePlatform) {
3637
const matches = parseRule.regex.exec(line);
3738

3839
if (matches) {
@@ -42,6 +43,18 @@ export class LogParserService extends EventEmitter implements ILogParserService
4243
});
4344
});
4445
}
46+
47+
private tryGetPlatform (deviceIdentifier: string): string {
48+
let devicePlatform;
49+
try {
50+
const device = this.$devicesService.getDeviceByIdentifier(deviceIdentifier);
51+
devicePlatform = device.deviceInfo.platform.toLowerCase();
52+
} catch (err) {
53+
devicePlatform = null;
54+
}
55+
56+
return devicePlatform;
57+
}
4558
}
4659

4760
$injector.register("logParserService", LogParserService);

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@
5656
"mkdirp": "0.5.1",
5757
"mute-stream": "0.0.5",
5858
"nativescript-doctor": "1.5.0",
59-
"nativescript-preview-sdk": "0.2.18",
59+
"nativescript-preview-sdk": "0.3.0",
6060
"open": "0.0.5",
6161
"ora": "2.0.0",
6262
"osenv": "0.1.3",

test/services/ios-debugger-port-service.ts

+3
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,9 @@ function createTestInjector() {
3838
injector.register("iOSProjectService", {
3939
getFrameworkVersion: () => "4.1.0"
4040
});
41+
injector.register("previewSdkService", {
42+
on: () => {}
43+
});
4144
injector.register("iOSSimResolver", {
4245
iOSSim: () => ({})
4346
});

test/services/log-parser-service.ts

+3
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@ class DeveiceLogProviderMock extends EventEmitter {
2121
function createTestInjector() {
2222
const injector = new Yok();
2323
injector.register("deviceLogProvider", DeveiceLogProviderMock);
24+
injector.register("previewSdkService", {
25+
on: () => {}
26+
});
2427
injector.register("devicePlatformsConstants", DevicePlatformsConstants);
2528
injector.register("logParserService", LogParserService);
2629
injector.register("devicesService", {

test/services/playground/preview-app-livesync-service.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { PreviewAppLiveSyncService } from "../../../lib/services/livesync/playgr
66
import * as chai from "chai";
77
import * as path from "path";
88
import { ProjectFilesManager } from "../../../lib/common/services/project-files-manager";
9+
import { EventEmitter } from "events";
910

1011
interface ITestCase {
1112
name: string;
@@ -67,7 +68,7 @@ const syncFilesMockData = {
6768
env: {}
6869
};
6970

70-
class PreviewSdkServiceMock implements IPreviewSdkService {
71+
class PreviewSdkServiceMock extends EventEmitter implements IPreviewSdkService {
7172
public getInitialFiles: (device: Device) => Promise<FilesPayload>;
7273

7374
public getQrCodeUrl(options: IHasUseHotModuleReloadOption) {
@@ -107,6 +108,7 @@ function createTestInjector(options?: {
107108
injector.register("platformService", {
108109
preparePlatform: async () => ({})
109110
});
111+
injector.register("hmrStatusService", {});
110112
injector.register("errors", ErrorsStub);
111113
injector.register("platformsData", {
112114
getPlatformData: () => ({

0 commit comments

Comments
 (0)