Skip to content

Commit 4864734

Browse files
committed
Merge pull request #150 from Microsoft/report-acquisition-status
Report Acquisition Status
2 parents 1787f3f + 5fc5a0a commit 4864734

24 files changed

+437
-138
lines changed

CodePush.js

+44-3
Original file line numberDiff line numberDiff line change
@@ -60,8 +60,9 @@ async function checkForUpdate(deploymentKey = null) {
6060
if (!update || update.updateAppVersion || (update.packageHash === localPackage.packageHash)) {
6161
return null;
6262
} else {
63-
const remotePackage = { ...update, ...PackageMixins.remote };
63+
const remotePackage = { ...update, ...PackageMixins.remote(sdk.reportStatusDownload) };
6464
remotePackage.failedInstall = await NativeCodePush.isFailedUpdate(remotePackage.packageHash);
65+
remotePackage.deploymentKey = deploymentKey || nativeConfig.deploymentKey;
6566
return remotePackage;
6667
}
6768
}
@@ -101,7 +102,31 @@ function getPromisifiedSdk(requestFetchAdapter, config) {
101102
});
102103
});
103104
};
104-
105+
106+
sdk.reportStatusDeploy = (deployedPackage, status) => {
107+
return new Promise((resolve, reject) => {
108+
module.exports.AcquisitionSdk.prototype.reportStatusDeploy.call(sdk, deployedPackage, status, (err) => {
109+
if (err) {
110+
reject(err);
111+
} else {
112+
resolve();
113+
}
114+
});
115+
});
116+
};
117+
118+
sdk.reportStatusDownload = (downloadedPackage) => {
119+
return new Promise((resolve, reject) => {
120+
module.exports.AcquisitionSdk.prototype.reportStatusDownload.call(sdk, downloadedPackage, (err) => {
121+
if (err) {
122+
reject(err);
123+
} else {
124+
resolve();
125+
}
126+
});
127+
});
128+
};
129+
105130
return sdk;
106131
}
107132

@@ -110,6 +135,22 @@ function log(message) {
110135
console.log(`[CodePush] ${message}`)
111136
}
112137

138+
async function notifyApplicationReady() {
139+
await NativeCodePush.notifyApplicationReady();
140+
const statusReport = await NativeCodePush.getNewStatusReport();
141+
if (statusReport) {
142+
const config = await getConfiguration();
143+
if (statusReport.appVersion) {
144+
const sdk = getPromisifiedSdk(requestFetchAdapter, config);
145+
sdk.reportStatusDeploy();
146+
} else {
147+
config.deploymentKey = statusReport.package.deploymentKey;
148+
const sdk = getPromisifiedSdk(requestFetchAdapter, config);
149+
sdk.reportStatusDeploy(statusReport.package, statusReport.status);
150+
}
151+
}
152+
}
153+
113154
function restartApp(onlyIfUpdateIsPending = false) {
114155
NativeCodePush.restartApp(onlyIfUpdateIsPending);
115156
}
@@ -269,7 +310,7 @@ const CodePush = {
269310
getConfiguration,
270311
getCurrentPackage,
271312
log,
272-
notifyApplicationReady: NativeCodePush.notifyApplicationReady,
313+
notifyApplicationReady,
273314
restartApp,
274315
setUpTestDependencies,
275316
sync,

CodePush.m

+114-7
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,17 @@ @implementation CodePush {
1313

1414
RCT_EXPORT_MODULE()
1515

16+
static BOOL needToReportRollback = NO;
17+
static BOOL isRunningBinaryVersion = NO;
1618
static BOOL testConfigurationFlag = NO;
1719

20+
// These constants represent valid deployment statuses
21+
static NSString *const DeploymentFailed = @"DeploymentFailed";
22+
static NSString *const DeploymentSucceeded = @"DeploymentSucceeded";
23+
1824
// These keys represent the names we use to store data in NSUserDefaults
1925
static NSString *const FailedUpdatesKey = @"CODE_PUSH_FAILED_UPDATES";
26+
static NSString *const LastDeploymentReportKey = @"CODE_PUSH_LAST_DEPLOYMENT_REPORT";
2027
static NSString *const PendingUpdateKey = @"CODE_PUSH_PENDING_UPDATE";
2128

2229
// These keys are already "namespaced" by the PendingUpdateKey, so
@@ -26,6 +33,8 @@ @implementation CodePush {
2633

2734
// These keys are used to inspect/augment the metadata
2835
// that is associated with an update's package.
36+
static NSString *const DeploymentKeyKey = @"deploymentKey";
37+
static NSString *const LabelKey = @"label";
2938
static NSString *const PackageHashKey = @"packageHash";
3039
static NSString *const PackageIsPendingKey = @"isPending";
3140

@@ -54,6 +63,7 @@ + (NSURL *)bundleURLForResource:(NSString *)resourceName
5463

5564
if (error || !packageFile) {
5665
NSLog(logMessageFormat, binaryJsBundleUrl);
66+
isRunningBinaryVersion = YES;
5767
return binaryJsBundleUrl;
5868
}
5969

@@ -65,22 +75,25 @@ + (NSURL *)bundleURLForResource:(NSString *)resourceName
6575
NSDictionary *currentPackageMetadata = [CodePushPackage getCurrentPackage:&error];
6676
if (error || !currentPackageMetadata) {
6777
NSLog(logMessageFormat, binaryJsBundleUrl);
78+
isRunningBinaryVersion = YES;
6879
return binaryJsBundleUrl;
6980
}
7081

7182
NSString *packageAppVersion = [currentPackageMetadata objectForKey:@"appVersion"];
7283

73-
if ([binaryDate compare:packageDate] == NSOrderedAscending && [binaryAppVersion isEqualToString:packageAppVersion]) {
84+
if ([binaryDate compare:packageDate] == NSOrderedAscending && ([CodePush isUsingTestConfiguration] ||[binaryAppVersion isEqualToString:packageAppVersion])) {
7485
// Return package file because it is newer than the app store binary's JS bundle
7586
NSURL *packageUrl = [[NSURL alloc] initFileURLWithPath:packageFile];
7687
NSLog(logMessageFormat, packageUrl);
88+
isRunningBinaryVersion = NO;
7789
return packageUrl;
7890
} else {
7991
#ifndef DEBUG
8092
[CodePush clearUpdates];
8193
#endif
8294

8395
NSLog(logMessageFormat, binaryJsBundleUrl);
96+
isRunningBinaryVersion = YES;
8497
return binaryJsBundleUrl;
8598
}
8699
}
@@ -148,6 +161,19 @@ - (void)dealloc
148161
[[NSNotificationCenter defaultCenter] removeObserver:self];
149162
}
150163

164+
- (NSString *)getPackageStatusReportIdentifier:(NSDictionary *)package
165+
{
166+
// Because deploymentKeys can be dynamically switched, we use a
167+
// combination of the deploymentKey and label as the packageIdentifier.
168+
NSString *deploymentKey = [package objectForKey:DeploymentKeyKey];
169+
NSString *label = [package objectForKey:LabelKey];
170+
if (deploymentKey && label) {
171+
return [[deploymentKey stringByAppendingString:@":"] stringByAppendingString:label];
172+
} else {
173+
return nil;
174+
}
175+
}
176+
151177
- (instancetype)init
152178
{
153179
self = [super init];
@@ -175,6 +201,7 @@ - (void)initializeUpdateAfterRestart
175201
// Pending update was initialized, but notifyApplicationReady was not called.
176202
// Therefore, deduce that it is a broken update and rollback.
177203
NSLog(@"Update did not finish loading the last time, rolling back to a previous version.");
204+
needToReportRollback = YES;
178205
[self rollbackPackage];
179206
} else {
180207
// Mark that we tried to initialize the new update, so that if it crashes,
@@ -185,6 +212,13 @@ - (void)initializeUpdateAfterRestart
185212
}
186213
}
187214

215+
- (BOOL)isDeploymentStatusNotYetReported:(NSString *)appVersionOrPackageIdentifier
216+
{
217+
NSUserDefaults *preferences = [NSUserDefaults standardUserDefaults];
218+
NSString *sentStatusReportIdentifier = [preferences objectForKey:LastDeploymentReportKey];
219+
return sentStatusReportIdentifier == nil || ![sentStatusReportIdentifier isEqualToString:appVersionOrPackageIdentifier];
220+
}
221+
188222
/*
189223
* This method checks to see whether a specific package hash
190224
* has previously failed installation.
@@ -193,7 +227,25 @@ - (BOOL)isFailedHash:(NSString*)packageHash
193227
{
194228
NSUserDefaults *preferences = [NSUserDefaults standardUserDefaults];
195229
NSMutableArray *failedUpdates = [preferences objectForKey:FailedUpdatesKey];
196-
return (failedUpdates != nil && [failedUpdates containsObject:packageHash]);
230+
if (failedUpdates == nil || packageHash == nil) {
231+
return NO;
232+
} else {
233+
for (NSDictionary *failedPackage in failedUpdates)
234+
{
235+
// Type check is needed for backwards compatibility, where we used to just store
236+
// the failed package hash instead of the metadata. This only impacts "dev"
237+
// scenarios, since in production we clear out old information whenever a new
238+
// binary is applied.
239+
if ([failedPackage isKindOfClass:[NSDictionary class]]) {
240+
NSString *failedPackageHash = [failedPackage objectForKey:PackageHashKey];
241+
if ([packageHash isEqualToString:failedPackageHash]) {
242+
return YES;
243+
}
244+
}
245+
}
246+
247+
return NO;
248+
}
197249
}
198250

199251
/*
@@ -237,6 +289,13 @@ - (void)loadBundle
237289
});
238290
}
239291

292+
- (void)recordDeploymentStatusReported:(NSString *)appVersionOrPackageIdentifier
293+
{
294+
NSUserDefaults *preferences = [NSUserDefaults standardUserDefaults];
295+
[preferences setValue:appVersionOrPackageIdentifier forKey:LastDeploymentReportKey];
296+
[preferences synchronize];
297+
}
298+
240299
/*
241300
* This method is used when an update has failed installation
242301
* and the app needs to be rolled back to the previous bundle.
@@ -247,10 +306,10 @@ - (void)loadBundle
247306
- (void)rollbackPackage
248307
{
249308
NSError *error;
250-
NSString *packageHash = [CodePushPackage getCurrentPackageHash:&error];
309+
NSDictionary *failedPackage = [CodePushPackage getCurrentPackage:&error];
251310

252-
// Write the current package's hash to the "failed list"
253-
[self saveFailedUpdate:packageHash];
311+
// Write the current package's metadata to the "failed list"
312+
[self saveFailedUpdate:failedPackage];
254313

255314
// Rollback to the previous version and de-register the new update
256315
[CodePushPackage rollbackPackage];
@@ -263,7 +322,7 @@ - (void)rollbackPackage
263322
* to store its hash so that it can be ignored on future
264323
* attempts to check the server for an update.
265324
*/
266-
- (void)saveFailedUpdate:(NSString *)packageHash
325+
- (void)saveFailedUpdate:(NSDictionary *)failedPackage
267326
{
268327
NSUserDefaults *preferences = [NSUserDefaults standardUserDefaults];
269328
NSMutableArray *failedUpdates = [preferences objectForKey:FailedUpdatesKey];
@@ -275,7 +334,7 @@ - (void)saveFailedUpdate:(NSString *)packageHash
275334
failedUpdates = [failedUpdates mutableCopy];
276335
}
277336

278-
[failedUpdates addObject:packageHash];
337+
[failedUpdates addObject:failedPackage];
279338
[preferences setObject:failedUpdates forKey:FailedUpdatesKey];
280339
[preferences synchronize];
281340
}
@@ -467,6 +526,54 @@ - (void)savePendingUpdate:(NSString *)packageHash
467526
resolve([NSNull null]);
468527
}
469528

529+
/*
530+
* This method is checks if a new status update exists (new version was installed,
531+
* or an update failed) and return its details (version label, status).
532+
*/
533+
RCT_EXPORT_METHOD(getNewStatusReport:(RCTPromiseResolveBlock)resolve
534+
rejecter:(RCTPromiseRejectBlock)reject)
535+
{
536+
if (needToReportRollback) {
537+
// Check if there was a rollback that was not yet reported
538+
needToReportRollback = NO;
539+
NSUserDefaults *preferences = [NSUserDefaults standardUserDefaults];
540+
NSMutableArray *failedUpdates = [preferences objectForKey:FailedUpdatesKey];
541+
if (failedUpdates) {
542+
NSDictionary *lastFailedPackage = [failedUpdates lastObject];
543+
if (lastFailedPackage) {
544+
NSString *lastFailedPackageIdentifier = [self getPackageStatusReportIdentifier:lastFailedPackage];
545+
if (lastFailedPackageIdentifier && [self isDeploymentStatusNotYetReported:lastFailedPackageIdentifier]) {
546+
[self recordDeploymentStatusReported:lastFailedPackageIdentifier];
547+
resolve(@{ @"package": lastFailedPackage, @"status": DeploymentFailed });
548+
return;
549+
}
550+
}
551+
}
552+
} else if (_isFirstRunAfterUpdate) {
553+
// Check if the current CodePush package has been reported
554+
NSError *error;
555+
NSDictionary *currentPackage = [CodePushPackage getCurrentPackage:&error];
556+
if (!error && currentPackage) {
557+
NSString *currentPackageIdentifier = [self getPackageStatusReportIdentifier:currentPackage];
558+
if (currentPackageIdentifier && [self isDeploymentStatusNotYetReported:currentPackageIdentifier]) {
559+
[self recordDeploymentStatusReported:currentPackageIdentifier];
560+
resolve(@{ @"package": currentPackage, @"status": DeploymentSucceeded });
561+
return;
562+
}
563+
}
564+
} else if (isRunningBinaryVersion || [_bridge.bundleURL.scheme hasPrefix:@"http"]) {
565+
// Check if the current appVersion has been reported.
566+
NSString *appVersion = [[CodePushConfig current] appVersion];
567+
if ([self isDeploymentStatusNotYetReported:appVersion]) {
568+
[self recordDeploymentStatusReported:appVersion];
569+
resolve(@{ @"appVersion": appVersion });
570+
return;
571+
}
572+
}
573+
574+
resolve([NSNull null]);
575+
}
576+
470577
/*
471578
* This method is the native side of the CodePush.restartApp() method.
472579
*/

CodePushConfig.m

+16
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
#import "CodePush.h"
2+
#import <UIKit/UIKit.h>
23

34
@implementation CodePushConfig {
45
NSMutableDictionary *_configDictionary;
@@ -8,6 +9,7 @@ @implementation CodePushConfig {
89

910
static NSString * const AppVersionConfigKey = @"appVersion";
1011
static NSString * const BuildVdersionConfigKey = @"buildVersion";
12+
static NSString * const ClientUniqueIDConfigKey = @"clientUniqueId";
1113
static NSString * const DeploymentKeyConfigKey = @"deploymentKey";
1214
static NSString * const ServerURLConfigKey = @"serverUrl";
1315

@@ -31,6 +33,14 @@ - (instancetype)init
3133
NSString *deploymentKey = [infoDictionary objectForKey:@"CodePushDeploymentKey"];
3234
NSString *serverURL = [infoDictionary objectForKey:@"CodePushServerURL"];
3335

36+
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
37+
NSString *clientUniqueId = [userDefaults stringForKey:ClientUniqueIDConfigKey];
38+
if (clientUniqueId == nil) {
39+
clientUniqueId = [[[UIDevice currentDevice] identifierForVendor] UUIDString];
40+
[userDefaults setObject:clientUniqueId forKey:ClientUniqueIDConfigKey];
41+
[userDefaults synchronize];
42+
}
43+
3444
if (!serverURL) {
3545
serverURL = @"https://codepush.azurewebsites.net/";
3646
}
@@ -39,6 +49,7 @@ - (instancetype)init
3949
appVersion,AppVersionConfigKey,
4050
buildVersion,BuildVdersionConfigKey,
4151
serverURL,ServerURLConfigKey,
52+
clientUniqueId,ClientUniqueIDConfigKey,
4253
deploymentKey,DeploymentKeyConfigKey,
4354
nil];
4455

@@ -70,6 +81,11 @@ - (NSString *)serverURL
7081
return [_configDictionary objectForKey:ServerURLConfigKey];
7182
}
7283

84+
- (NSString *)clientUniqueId
85+
{
86+
return [_configDictionary objectForKey:ClientUniqueIDConfigKey];
87+
}
88+
7389
- (void)setDeploymentKey:(NSString *)deploymentKey
7490
{
7591
[_configDictionary setValue:deploymentKey forKey:DeploymentKeyConfigKey];

Examples/CodePushDemoApp/CodePushDemoAppTests/CheckForUpdateTests/testcases/FirstUpdateTest.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ let FirstUpdateTest = createTestCaseComponent(
2424
},
2525
async () => {
2626
let update = await CodePush.checkForUpdate();
27-
assert.equal(JSON.stringify(update), JSON.stringify({ ...serverPackage, ...PackageMixins.remote, failedInstall: false }), "checkForUpdate did not return the update from the server");
27+
assert.equal(JSON.stringify(update), JSON.stringify({ ...serverPackage, ...PackageMixins.remote(), failedInstall: false }), "checkForUpdate did not return the update from the server");
2828
}
2929
);
3030

Examples/CodePushDemoApp/CodePushDemoAppTests/CheckForUpdateTests/testcases/NewUpdateTest.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ let NewUpdateTest = createTestCaseComponent(
2323
},
2424
async () => {
2525
let update = await CodePush.checkForUpdate();
26-
assert.equal(JSON.stringify(update), JSON.stringify({ ...serverPackage, ...PackageMixins.remote, failedInstall: false }), "checkForUpdate did not return the update from the server");
26+
assert.equal(JSON.stringify(update), JSON.stringify({ ...serverPackage, ...PackageMixins.remote(), failedInstall: false }), "checkForUpdate did not return the update from the server");
2727
}
2828
);
2929

Examples/CodePushDemoApp/CodePushDemoAppTests/CheckForUpdateTests/testcases/SwitchDeploymentKeyTest.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ let SwitchDeploymentKeyTest = createTestCaseComponent(
2525
},
2626
async () => {
2727
let update = await CodePush.checkForUpdate(deploymentKey);
28-
assert.equal(JSON.stringify(update), JSON.stringify({ ...serverPackage, ...PackageMixins.remote, failedInstall: false }), "checkForUpdate did not return the update from the server");
28+
assert.equal(JSON.stringify(update), JSON.stringify({ ...serverPackage, ...PackageMixins.remote(), failedInstall: false, deploymentKey }), "checkForUpdate did not return the update from the server");
2929
}
3030
);
3131

Examples/CodePushDemoApp/CodePushDemoAppTests/DownloadProgressTests/testcases/DownloadProgressTest.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ let DownloadProgressTest = createTestCaseComponent(
2828
"should successfully download all the bytes contained in the test packages",
2929
() => {
3030
testPackages.forEach((aPackage, index) => {
31-
testPackages[index] = Object.assign(aPackage, PackageMixins.remote);
31+
testPackages[index] = Object.assign(aPackage, PackageMixins.remote());
3232
});
3333
return Promise.resolve();
3434
},

0 commit comments

Comments
 (0)