Files
react-native-code-push/windows/CodePush.Shared/CodePushReactPackage.cs
Alexander Bodalevsky be96f07eda Added React Native Windows dotNet46 support (#684)
Added support react-native-windows dotNet
Added example for react-native-windows UWP based
Added example for react-native-windows dotNet based

Project structure:

CodePush.Shared - shared code between UWP and dotNet
CodePush - UWP specific code
CodePush.Net46 - dotNet specific code

For UWP solution it needs to be added the following projects:

CodePush.Shared
CodePush
For dotNet solution it needs to be added the following projects:

CodePush.Shared
CodePush.Net46
Examples:

Examples\CodePushDemoApp\windows\CodePushDemoApp.sln the solution contains both examples (UWP and dotNet).

Notes

Example for ARM configuration has not been tested. Since there is no changes in UWP part of implementation, there is low risk of failure.

In this implementation we tried to reuse UWP library as much as possible. The following issues are relevant for both platforms:

ZipFile.ExtractToDirectory is not reliable and throws exception if:
folder exists already
path is too long (> 250 chars)
Un-zipping is quite long operation. Does it make sense for async?
await UpdateUtils.UnzipBundleAsync(downloadFile.Path, unzippedFolder.Path);
2017-02-13 16:23:20 -08:00

178 lines
7.1 KiB
C#

using Newtonsoft.Json.Linq;
using ReactNative;
using ReactNative.Bridge;
using ReactNative.Modules.Core;
using ReactNative.UIManager;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace CodePush.ReactNative
{
public sealed class CodePushReactPackage : IReactPackage
{
private static CodePushReactPackage CurrentInstance;
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 = CodePushUtils.GetAppVersion();
DeploymentKey = deploymentKey;
MainPage = mainPage;
UpdateManager = new UpdateManager();
IsRunningBinaryVersion = false;
// TODO implement telemetryManager
// _codePushTelemetryManager = new CodePushTelemetryManager(this.applicationContext, CODE_PUSH_PREFERENCES);
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)
{
return new List<INativeModule>
{
new CodePushNativeModule(reactContext, this)
};
}
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 = CodePushUtils.GetAssetsBundlePrefix() + assetsBundleFileName;
var binaryResourcesModifiedTime = await FileUtils.GetBinaryResourcesModifiedTimeAsync(AssetsBundleFileName).ConfigureAwait(false);
var packageFile = await UpdateManager.GetCurrentPackageBundleAsync(AssetsBundleFileName).ConfigureAwait(false);
if (packageFile == null)
{
// There has not been any downloaded updates.
CodePushUtils.LogBundleUrl(binaryJsBundleUrl);
IsRunningBinaryVersion = true;
return binaryJsBundleUrl;
}
var packageMetadata = await UpdateManager.GetCurrentPackageAsync().ConfigureAwait(false);
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 CodePushUtils.GetFileBundlePrefix() + packageFile.Path.Replace(CodePushUtils.GetAppFolder(), "").Replace("\\", "/");
}
else
{
// The binary version is newer.
DidUpdate = false;
if (!MainPage.UseDeveloperSupport || !AppVersion.Equals(packageAppVersion))
{
await ClearUpdatesAsync().ConfigureAwait(false);
}
CodePushUtils.LogBundleUrl(binaryJsBundleUrl);
IsRunningBinaryVersion = true;
return binaryJsBundleUrl;
}
}
#endregion
#region Internal methods
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.");
RollbackPackageAsync().Wait();
}
else
{
DidUpdate = true;
// Clear the React dev bundle cache so that new updates can be loaded.
if (MainPage.UseDeveloperSupport)
{
FileUtils.ClearReactDevBundleCacheAsync().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 ClearUpdatesAsync()
{
await UpdateManager.ClearUpdatesAsync().ConfigureAwait(false);
SettingsManager.RemovePendingUpdate();
SettingsManager.RemoveFailedUpdates();
}
#endregion
#region Private methods
private async Task RollbackPackageAsync()
{
JObject failedPackage = await UpdateManager.GetCurrentPackageAsync().ConfigureAwait(false);
SettingsManager.SaveFailedUpdate(failedPackage);
await UpdateManager.RollbackPackageAsync().ConfigureAwait(false);
SettingsManager.RemovePendingUpdate();
}
#endregion
}
}