Skip to content

Commit fdcb78a

Browse files
committed
feat: add hmr status service
1 parent 47f07d5 commit fdcb78a

File tree

7 files changed

+136
-11
lines changed

7 files changed

+136
-11
lines changed

lib/bootstrap.ts

+1
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,7 @@ $injector.requirePublic("assetsGenerationService", "./services/assets-generation
178178
$injector.require("filesHashService", "./services/files-hash-service");
179179
$injector.require("logParserService", "./services/log-parser-service");
180180
$injector.require("iOSDebuggerPortService", "./services/ios-debugger-port-service");
181+
$injector.require("hmrStatusService", "./services/hmr-status-service");
181182

182183
$injector.require("pacoteService", "./services/pacote-service");
183184
$injector.require("qrCodeTerminalService", "./services/qr-code-terminal-service");

lib/common/constants.ts

+5
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,11 @@ export class LiveSyncConstants {
4848
static IOS_PROJECT_PATH = "/Documents/AppBuilder/LiveSync";
4949
}
5050

51+
export class HmrConstants {
52+
public static HMR_ERROR_STATUS = 3;
53+
public static HMR_SUCCESS_STATUS = 2;
54+
}
55+
5156
export class DeviceDiscoveryEventNames {
5257
static DEVICE_FOUND = "deviceFound";
5358
static DEVICE_LOST = "deviceLost";
+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
interface IHmrStatusService {
2+
awaitHmrStatus(deviceId: string, operationHash: string): Promise<number>;
3+
attachToHrmStatusEvent(): void;
4+
}

lib/services/hmr-status-service.ts

+86
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
import { cache } from "../common/decorators";
2+
import { HmrConstants } from "../common/constants";
3+
4+
export class HmrStatusService implements IHmrStatusService {
5+
public static HMR_STATUS_LOG_REGEX = /([a-z A-Z]*) hmr hash ([a-z0-9]*)\./;
6+
public static STARTED_MESSAGE = "Checking for updates to the bundle with";
7+
public static SUCCESS_MESSAGE = "Successfuly applied update with";
8+
public static FAILED_MESSAGE = "Cannot apply update with";
9+
private hashOperationStatuses: IDictionary<any> = {};
10+
11+
constructor(private $logParserService: ILogParserService,
12+
private $logger: ILogger) { }
13+
14+
public awaitHmrStatus(deviceId: string, operationHash: string): Promise<number> {
15+
return new Promise((resolve, reject) => {
16+
const key = `${deviceId}${operationHash}`;
17+
let retryCount = 40;
18+
19+
const interval = setInterval(() => {
20+
const status = this.getStatusByKey(key);
21+
if (status || retryCount === 0) {
22+
clearInterval(interval);
23+
resolve(status);
24+
} else {
25+
retryCount--;
26+
}
27+
}, 250);
28+
});
29+
}
30+
31+
@cache()
32+
public attachToHrmStatusEvent(): void {
33+
this.$logParserService.addParseRule({
34+
regex: HmrStatusService.HMR_STATUS_LOG_REGEX,
35+
handler: this.handleHmrStatusFound.bind(this),
36+
name: "hmrStatus"
37+
});
38+
}
39+
40+
private handleHmrStatusFound(matches: RegExpMatchArray, deviceId: string): void {
41+
const message = matches[1].trim();
42+
const hash = matches[2];
43+
let status;
44+
45+
switch (message) {
46+
case HmrStatusService.SUCCESS_MESSAGE: {
47+
status = HmrConstants.HMR_SUCCESS_STATUS;
48+
break;
49+
}
50+
case HmrStatusService.FAILED_MESSAGE: {
51+
status = HmrConstants.HMR_ERROR_STATUS;
52+
break;
53+
}
54+
default: {
55+
status = null;
56+
break;
57+
}
58+
}
59+
60+
this.$logger.trace("Found hmr status.", {status, hash});
61+
62+
if (status) {
63+
this.setData(status, hash, deviceId);
64+
}
65+
}
66+
67+
private getStatusByKey(key: string): number {
68+
if (this.hashOperationStatuses[key]) {
69+
return this.hashOperationStatuses[key].status;
70+
}
71+
72+
return null;
73+
}
74+
75+
private setData(status: Number, operationHash: string, deviceId: string): void {
76+
const key = `${deviceId}${operationHash}`;
77+
78+
if (!this.hashOperationStatuses[key]) {
79+
this.hashOperationStatuses[key] = <any>{};
80+
}
81+
82+
this.hashOperationStatuses[key].status = status;
83+
}
84+
}
85+
86+
$injector.register("hmrStatusService", HmrStatusService);

lib/services/livesync/livesync-service.ts

+27-4
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { EOL } from "os";
44
import { EventEmitter } from "events";
55
import { hook } from "../../common/helpers";
66
import { PACKAGE_JSON_FILE_NAME, LiveSyncTrackActionNames, USER_INTERACTION_NEEDED_EVENT_NAME, DEBUGGER_ATTACHED_EVENT_NAME, DEBUGGER_DETACHED_EVENT_NAME, TrackActionNames } from "../../constants";
7-
import { DeviceTypes, DeviceDiscoveryEventNames } from "../../common/constants";
7+
import { DeviceTypes, DeviceDiscoveryEventNames, HmrConstants } from "../../common/constants";
88
import { cache } from "../../common/decorators";
99

1010
const deviceDescriptorPrimaryKey = "identifier";
@@ -38,6 +38,7 @@ export class LiveSyncService extends EventEmitter implements IDebugLiveSyncServi
3838
private $analyticsService: IAnalyticsService,
3939
private $usbLiveSyncService: DeprecatedUsbLiveSyncService,
4040
private $previewAppLiveSyncService: IPreviewAppLiveSyncService,
41+
private $hmrStatusService: IHmrStatusService,
4142
private $injector: IInjector) {
4243
super();
4344
}
@@ -147,8 +148,6 @@ export class LiveSyncService extends EventEmitter implements IDebugLiveSyncServi
147148
deviceIdentifier: liveSyncResultInfo.deviceAppData.device.deviceInfo.identifier,
148149
isFullSync: liveSyncResultInfo.isFullSync
149150
});
150-
151-
this.$logger.info(`Successfully synced application ${liveSyncResultInfo.deviceAppData.appIdentifier} on device ${liveSyncResultInfo.deviceAppData.device.deviceInfo.identifier}.`);
152151
}
153152

154153
private async refreshApplicationWithDebug(projectData: IProjectData, liveSyncResultInfo: ILiveSyncResultInfo, debugOptions: IDebugOptions, outputPath?: string): Promise<IDebugInformation> {
@@ -518,6 +517,7 @@ export class LiveSyncService extends EventEmitter implements IDebugLiveSyncServi
518517

519518
await this.$platformService.trackActionForPlatform({ action: "LiveSync", platform: device.deviceInfo.platform, isForDevice: !device.isEmulator, deviceOsVersion: device.deviceInfo.version });
520519
await this.refreshApplication(projectData, liveSyncResultInfo, deviceBuildInfoDescriptor.debugOptions, deviceBuildInfoDescriptor.outputPath);
520+
this.$logger.info(`Successfully synced application ${liveSyncResultInfo.deviceAppData.appIdentifier} on device ${liveSyncResultInfo.deviceAppData.device.deviceInfo.identifier}.`);
521521

522522
this.emitLivesyncEvent(LiveSyncEvents.liveSyncStarted, {
523523
projectDir: projectData.projectDir,
@@ -564,6 +564,10 @@ export class LiveSyncService extends EventEmitter implements IDebugLiveSyncServi
564564
const platforms = _(devices).map(device => device.deviceInfo.platform).uniq().value();
565565
const patterns = await this.getWatcherPatterns(liveSyncData, projectData, platforms);
566566

567+
if (liveSyncData.useHotModuleReload) {
568+
this.$hmrStatusService.attachToHrmStatusEvent();
569+
}
570+
567571
if (liveSyncData.watchAllFiles) {
568572
const productionDependencies = this.$nodeModulesDependenciesBuilder.getProductionDependencies(projectData.projectDir);
569573
patterns.push(PACKAGE_JSON_FILE_NAME);
@@ -582,6 +586,10 @@ export class LiveSyncService extends EventEmitter implements IDebugLiveSyncServi
582586
}
583587

584588
let filesToSync: string[] = [];
589+
const hmrData: { hash: string; fallbackFiles: IDictionary<string[]> } = {
590+
hash: "",
591+
fallbackFiles: {}
592+
};
585593
const filesToSyncMap: IDictionary<string[]> = {};
586594
let filesToRemove: string[] = [];
587595
let timeoutTimer: NodeJS.Timer;
@@ -610,6 +618,7 @@ export class LiveSyncService extends EventEmitter implements IDebugLiveSyncServi
610618
if (filesToSync.length || filesToRemove.length) {
611619
try {
612620
const currentFilesToSync = _.cloneDeep(filesToSync);
621+
const currentHmrData = _.cloneDeep(hmrData);
613622
filesToSync.splice(0, filesToSync.length);
614623

615624
const currentFilesToRemove = _.cloneDeep(filesToRemove);
@@ -655,8 +664,21 @@ export class LiveSyncService extends EventEmitter implements IDebugLiveSyncServi
655664
force: liveSyncData.force
656665
};
657666

658-
const liveSyncResultInfo = await service.liveSyncWatchAction(device, settings);
667+
let liveSyncResultInfo = await service.liveSyncWatchAction(device, settings);
668+
659669
await this.refreshApplication(projectData, liveSyncResultInfo, deviceBuildInfoDescriptor.debugOptions, deviceBuildInfoDescriptor.outputPath);
670+
671+
if (liveSyncData.useHotModuleReload && currentHmrData.hash) {
672+
const status = await this.$hmrStatusService.awaitHmrStatus(device.deviceInfo.identifier, currentHmrData.hash);
673+
if (status === HmrConstants.HMR_ERROR_STATUS) {
674+
settings.filesToSync = currentHmrData.fallbackFiles[device.deviceInfo.platform];
675+
liveSyncResultInfo = await service.liveSyncWatchAction(device, settings);
676+
liveSyncResultInfo.isFullSync = true;
677+
await this.refreshApplication(projectData, liveSyncResultInfo, deviceBuildInfoDescriptor.debugOptions, deviceBuildInfoDescriptor.outputPath);
678+
}
679+
}
680+
681+
this.$logger.info(`Successfully synced application ${liveSyncResultInfo.deviceAppData.appIdentifier} on device ${liveSyncResultInfo.deviceAppData.device.deviceInfo.identifier}.`);
660682
},
661683
(device: Mobile.IDevice) => {
662684
const liveSyncProcessInfo = this.liveSyncProcessesInfo[projectData.projectDir];
@@ -704,6 +726,7 @@ export class LiveSyncService extends EventEmitter implements IDebugLiveSyncServi
704726
},
705727
filesToSync,
706728
filesToSyncMap,
729+
hmrData,
707730
filesToRemove,
708731
startSyncFilesTimeout: async (platform: string) => {
709732
if (platform) {

lib/services/log-parser-service.ts

+10-7
Original file line numberDiff line numberDiff line change
@@ -29,14 +29,17 @@ export class LogParserService extends EventEmitter implements ILogParserService
2929
const device = this.$devicesService.getDeviceByIdentifier(deviceIdentifier);
3030
const devicePlatform = device.deviceInfo.platform.toLowerCase();
3131

32-
_.forEach(this.parseRules, (parseRule) => {
33-
if (!parseRule.platform || parseRule.platform.toLowerCase() === devicePlatform) {
34-
const matches = parseRule.regex.exec(message);
35-
36-
if (matches) {
37-
parseRule.handler(matches, deviceIdentifier);
32+
const lines = message.split("\n");
33+
_.forEach(lines, line => {
34+
_.forEach(this.parseRules, (parseRule) => {
35+
if (!parseRule.platform || parseRule.platform.toLowerCase() === devicePlatform) {
36+
const matches = parseRule.regex.exec(line);
37+
38+
if (matches) {
39+
parseRule.handler(matches, deviceIdentifier);
40+
}
3841
}
39-
}
42+
});
4043
});
4144
}
4245
}

test/services/livesync-service.ts

+3
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ const createTestInjector = (): IInjector => {
77
const testInjector = new Yok();
88

99
testInjector.register("platformService", {});
10+
testInjector.register("hmrStatusService", {});
1011
testInjector.register("projectDataService", {
1112
getProjectData: (projectDir: string): IProjectData => (<any>{})
1213
});
@@ -59,6 +60,7 @@ class LiveSyncServiceInheritor extends LiveSyncService {
5960
$usbLiveSyncService: DeprecatedUsbLiveSyncService,
6061
$injector: IInjector,
6162
$previewAppLiveSyncService: IPreviewAppLiveSyncService,
63+
$hmrStatusService: IHmrStatusService,
6264
$platformsData: IPlatformsData) {
6365

6466
super(
@@ -78,6 +80,7 @@ class LiveSyncServiceInheritor extends LiveSyncService {
7880
$analyticsService,
7981
$usbLiveSyncService,
8082
$previewAppLiveSyncService,
83+
$hmrStatusService,
8184
$injector
8285
);
8386
}

0 commit comments

Comments
 (0)