Files
react-native-code-push/windows/CodePushReactPackage.cs
Geoffrey Goh 08283efe2d CR feedback
2016-05-05 10:37:38 -07:00

200 lines
8.1 KiB
C#

using Newtonsoft.Json.Linq;
using Newtonsoft.Json;
using ReactNative;
using ReactNative.Bridge;
using ReactNative.Modules.Core;
using ReactNative.UIManager;
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Threading.Tasks;
using Windows.ApplicationModel;
using Windows.Storage;
using Windows.Web.Http;
namespace CodePush.ReactNative
{
public sealed class CodePushReactPackage : IReactPackage
{
private static CodePushReactPackage CurrentInstance;
private static bool NeedToReportRollback = false;
private CodePushNativeModule _codePushNativeModule;
internal string AppVersion { get; private set; }
internal string DeploymentKey { get; private set; }
internal string AssetsBundleFileName { get; private set; }
internal bool DidUpdate { get; private set; }
internal bool IsRunningBinaryVersion { get; set; }
internal ReactPage MainPage { get; private set; }
internal UpdateManager UpdateManager { get; private set; }
public CodePushReactPackage(string deploymentKey, ReactPage mainPage)
{
AppVersion = Package.Current.Id.Version.Major + "." + Package.Current.Id.Version.Minor + "." + Package.Current.Id.Version.Build;
DeploymentKey = deploymentKey;
MainPage = mainPage;
UpdateManager = new UpdateManager();
IsRunningBinaryVersion = false;
// TODO implement telemetryManager
// _codePushTelemetryManager = new CodePushTelemetryManager(this.applicationContext, CODE_PUSH_PREFERENCES);
InitializeUpdateAfterRestart();
if (CurrentInstance != null)
{
CodePushUtils.Log("More than one CodePush instance has been initialized. Please use the instance method codePush.getBundleUrlInternal() to get the correct bundleURL for a particular instance.");
}
CurrentInstance = this;
}
#region Public methods
public IReadOnlyList<Type> CreateJavaScriptModulesConfig()
{
return new List<Type>();
}
public IReadOnlyList<INativeModule> CreateNativeModules(ReactContext reactContext)
{
_codePushNativeModule = new CodePushNativeModule(reactContext, this);
var nativeModules = new List<INativeModule>
{
_codePushNativeModule
};
return nativeModules;
}
public IReadOnlyList<IViewManager> CreateViewManagers(ReactContext reactContext)
{
return new List<IViewManager>();
}
public string GetJavaScriptBundleFile()
{
return GetJavaScriptBundleFile(CodePushConstants.DefaultJsBundleName);
}
public string GetJavaScriptBundleFile(string assetsBundleFileName)
{
if (CurrentInstance == null)
{
throw new InvalidOperationException("A CodePush instance has not been created yet. Have you added it to your app's list of ReactPackages?");
}
return CurrentInstance.GetJavaScriptBundleFileAsync(assetsBundleFileName).Result;
}
public async Task<string> GetJavaScriptBundleFileAsync(string assetsBundleFileName)
{
AssetsBundleFileName = assetsBundleFileName;
string binaryJsBundleUrl = CodePushConstants.AssetsBundlePrefix + assetsBundleFileName;
var binaryResourcesModifiedTime = await GetBinaryResourcesModifiedTime();
var packageFile = await UpdateManager.GetCurrentPackageBundle(AssetsBundleFileName);
if (packageFile == null)
{
// There has not been any downloaded updates.
CodePushUtils.LogBundleUrl(binaryJsBundleUrl);
IsRunningBinaryVersion = true;
return binaryJsBundleUrl;
}
var packageMetadata = await UpdateManager.GetCurrentPackage();
long? binaryModifiedDateDuringPackageInstall = null;
var binaryModifiedDateDuringPackageInstallString = (string)packageMetadata[CodePushConstants.BinaryModifiedTimeKey];
if (binaryModifiedDateDuringPackageInstallString != null)
{
binaryModifiedDateDuringPackageInstall = long.Parse(binaryModifiedDateDuringPackageInstallString);
}
var packageAppVersion = (string)packageMetadata["appVersion"];
if (binaryModifiedDateDuringPackageInstall != null &&
binaryModifiedDateDuringPackageInstall == binaryResourcesModifiedTime &&
AppVersion.Equals(packageAppVersion))
{
CodePushUtils.LogBundleUrl(packageFile.Path);
IsRunningBinaryVersion = false;
return CodePushConstants.FileBundlePrefix + packageFile.Path.Replace(ApplicationData.Current.LocalFolder.Path, "").Replace("\\", "/");
}
else
{
// The binary version is newer.
DidUpdate = false;
if (!MainPage.UseDeveloperSupport || !AppVersion.Equals(packageAppVersion))
{
await ClearUpdates();
}
CodePushUtils.LogBundleUrl(binaryJsBundleUrl);
IsRunningBinaryVersion = true;
return binaryJsBundleUrl;
}
}
#endregion
#region Internal methods
internal async Task<long> GetBinaryResourcesModifiedTime()
{
var assetJSBundleFile = await StorageFile.GetFileFromApplicationUriAsync(new Uri(CodePushConstants.AssetsBundlePrefix + AssetsBundleFileName));
var fileProperties = await assetJSBundleFile.GetBasicPropertiesAsync();
return fileProperties.DateModified.ToUnixTimeMilliseconds();
}
internal void InitializeUpdateAfterRestart()
{
JObject pendingUpdate = SettingsManager.GetPendingUpdate();
if (pendingUpdate != null)
{
var updateIsLoading = (bool)pendingUpdate[CodePushConstants.PendingUpdateIsLoadingKey];
if (updateIsLoading)
{
// Pending update was initialized, but notifyApplicationReady was not called.
// Therefore, deduce that it is a broken update and rollback.
CodePushUtils.Log("Update did not finish loading the last time, rolling back to a previous version.");
NeedToReportRollback = true;
RollbackPackage().Wait();
}
else
{
DidUpdate = true;
// Clear the React dev bundle cache so that new updates can be loaded.
if (MainPage.UseDeveloperSupport)
{
ClearReactDevBundleCache().Wait();
}
// Mark that we tried to initialize the new update, so that if it crashes,
// we will know that we need to rollback when the app next starts.
SettingsManager.SavePendingUpdate((string)pendingUpdate[CodePushConstants.PendingUpdateHashKey], /* isLoading */true);
}
}
}
internal async Task ClearUpdates()
{
await UpdateManager.ClearUpdates();
SettingsManager.RemovePendingUpdate();
SettingsManager.RemoveFailedUpdates();
}
#endregion
#region Private methods
private async Task ClearReactDevBundleCache()
{
var devBundleCacheFile = (StorageFile) await ApplicationData.Current.LocalFolder.TryGetItemAsync(CodePushConstants.ReactDevBundleCacheFileName);
if (devBundleCacheFile != null)
{
await devBundleCacheFile.DeleteAsync();
}
}
private async Task RollbackPackage()
{
JObject failedPackage = await UpdateManager.GetCurrentPackage();
SettingsManager.SaveFailedUpdate(failedPackage);
await UpdateManager.RollbackPackage();
SettingsManager.RemovePendingUpdate();
}
#endregion
}
}