Skip to content
This repository was archived by the owner on May 20, 2025. It is now read-only.

Commit 97b8233

Browse files
author
Geoffrey Goh
committed
CR feedback
1 parent d6b8bff commit 97b8233

18 files changed

+884
-954
lines changed

windows/CodePush.cs

Lines changed: 0 additions & 668 deletions
This file was deleted.

windows/CodePush.csproj

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,16 +108,18 @@
108108
</ItemGroup>
109109
<ItemGroup>
110110
<Compile Include="CodePush.cs" />
111+
<Compile Include="CodePushConstants.cs" />
111112
<Compile Include="CodePushInstallMode.cs" />
112113
<Compile Include="CodePushInvalidUpdateException.cs" />
113-
<Compile Include="CodePushNotInitializedException.cs" />
114+
<Compile Include="CodePushNativeModule.cs" />
114115
<Compile Include="CodePushPackage.cs" />
115116
<Compile Include="CodePushUnknownException.cs" />
116117
<Compile Include="CodePushUpdateState.cs" />
117118
<Compile Include="CodePushUpdateUtils.cs" />
118119
<Compile Include="CodePushUtils.cs" />
119120
<Compile Include="FileUtils.cs" />
120121
<Compile Include="Properties\AssemblyInfo.cs" />
122+
<Compile Include="SettingsManager.cs" />
121123
<EmbeddedResource Include="Properties\CodePush.rd.xml" />
122124
</ItemGroup>
123125
<ItemGroup>

windows/CodePushConstants.cs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
namespace CodePush.ReactNative
2+
{
3+
internal class CodePushConstants
4+
{
5+
internal const string AssetsBundlePrefix = "ms-appx:///ReactAssets/";
6+
internal const string BinaryModifiedTimeKey = "binaryModifiedTime";
7+
internal const string CodePushServerUrl = "https://codepush.azurewebsites.net/";
8+
internal const string CodePushFolderPrefix = "CodePush";
9+
internal const string CodePushPreferences = "CodePush";
10+
internal const string CurrentPackageKey = "currentPackage";
11+
internal const string DefaultJsBundleName = "index.windows.bundle";
12+
internal const string DiffManifestFileName = "hotcodepush.json";
13+
internal const string DownloadFileName = "download.zip";
14+
internal const string DownloadProgressEventName = "CodePushDownloadProgress";
15+
internal const string DownloadUrlKey = "downloadUrl";
16+
internal const string FailedUpdatesKey = "CODE_PUSH_FAILED_UPDATES";
17+
internal const string FileBundlePrefix = "ms-appdata:///local";
18+
internal const string PackageFileName = "app.json";
19+
internal const string PackageHashKey = "packageHash";
20+
internal const string PendingUpdateHashKey = "hash";
21+
internal const string PendingUpdateKey = "CODE_PUSH_PENDING_UPDATE";
22+
internal const string PendingUpdateIsLoadingKey = "isLoading";
23+
internal const string PreviousPackageKey = "previousPackage";
24+
// This needs to be kept in sync with https://github.com/facebook/react-native/blob/master/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportManager.java#L78
25+
internal const string ReactDevBundleCacheFileName = "ReactNativeDevBundle.js";
26+
internal const string ReactNativeLogCategory = "ReactNative";
27+
internal const string RelativeBundlePathKey = "bundlePath";
28+
internal const string StatusFileName = "codepush.json";
29+
internal const string UnzippedFolderName = "unzipped";
30+
}
31+
}

windows/CodePushInstallMode.cs

Lines changed: 0 additions & 6 deletions
This file was deleted.

windows/CodePushInvalidUpdateException.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@
22

33
namespace CodePush.ReactNative
44
{
5-
class CodePushInvalidUpdateException : Exception
5+
internal class CodePushInvalidUpdateException : Exception
66
{
7-
public CodePushInvalidUpdateException(string message)
7+
internal CodePushInvalidUpdateException(string message)
88
: base(message)
99
{
1010
}

windows/CodePushNativeModule.cs

Lines changed: 279 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,279 @@
1+
using Newtonsoft.Json.Linq;
2+
using ReactNative;
3+
using ReactNative.Bridge;
4+
using ReactNative.Modules.Core;
5+
using System;
6+
using System.Collections.Generic;
7+
using System.Reflection;
8+
using System.Threading.Tasks;
9+
using Windows.Web.Http;
10+
11+
namespace CodePush.ReactNative
12+
{
13+
internal class CodePushNativeModule : ReactContextNativeModuleBase
14+
{
15+
private CodePushResumeListener _codePushLifecycleEventListener = null;
16+
private ReactContext _reactContext;
17+
private CodePushReactPackage _codePush;
18+
19+
public CodePushNativeModule(ReactContext reactContext, CodePushReactPackage codePush) : base(reactContext)
20+
{
21+
_reactContext = reactContext;
22+
_codePush = codePush;
23+
}
24+
25+
public override string Name
26+
{
27+
get
28+
{
29+
return "CodePush";
30+
}
31+
}
32+
33+
public override IReadOnlyDictionary<string, object> Constants
34+
{
35+
get
36+
{
37+
return new Dictionary<string, object>
38+
{
39+
{ "codePushInstallModeImmediate", InstallMode.ON_NEXT_RESTART },
40+
{ "codePushInstallModeOnNextResume", InstallMode.ON_NEXT_RESUME },
41+
{ "codePushInstallModeOnNextRestart", InstallMode.ON_NEXT_RESTART },
42+
{ "codePushUpdateStateRunning", UpdateState.RUNNING },
43+
{ "codePushUpdateStatePending", UpdateState.PENDING },
44+
{ "codePushUpdateStateLatest", UpdateState.LATEST },
45+
};
46+
}
47+
}
48+
49+
public override void Initialize()
50+
{
51+
_codePush.InitializeUpdateAfterRestart();
52+
}
53+
54+
[ReactMethod]
55+
public void downloadUpdate(JObject updatePackage, bool notifyProgress, IPromise promise)
56+
{
57+
Action downloadAction = async () =>
58+
{
59+
try
60+
{
61+
updatePackage[CodePushConstants.BinaryModifiedTimeKey] = "" + await _codePush.GetBinaryResourcesModifiedTime();
62+
await _codePush.UpdateManager.DownloadPackage(
63+
updatePackage,
64+
_codePush.AssetsBundleFileName,
65+
new Progress<HttpProgress>(
66+
(HttpProgress progress) =>
67+
{
68+
if (!notifyProgress)
69+
{
70+
return;
71+
}
72+
73+
var downloadProgress = new JObject();
74+
downloadProgress["totalBytes"] = progress.TotalBytesToReceive;
75+
downloadProgress["receivedBytes"] = progress.BytesReceived;
76+
_reactContext
77+
.GetJavaScriptModule<RCTDeviceEventEmitter>()
78+
.emit(CodePushConstants.DownloadProgressEventName, downloadProgress);
79+
}
80+
)
81+
);
82+
83+
JObject newPackage = await _codePush.UpdateManager.GetPackage((string)updatePackage[CodePushConstants.PackageHashKey]);
84+
promise.Resolve(newPackage);
85+
}
86+
catch (CodePushInvalidUpdateException e)
87+
{
88+
CodePushUtils.Log(e.ToString());
89+
SettingsManager.SaveFailedUpdate(updatePackage);
90+
promise.Reject(e);
91+
}
92+
catch (Exception e)
93+
{
94+
CodePushUtils.Log(e.ToString());
95+
promise.Reject(e);
96+
}
97+
};
98+
99+
Context.RunOnNativeModulesQueueThread(downloadAction);
100+
}
101+
102+
[ReactMethod]
103+
public void getConfiguration(IPromise promise)
104+
{
105+
var config = new JObject
106+
{
107+
{ "appVersion", _codePush.AppVersion },
108+
{ "deploymentKey", _codePush.DeploymentKey },
109+
{ "serverUrl", CodePushConstants.CodePushServerUrl },
110+
{ "clientUniqueId", CodePushUtils.GetDeviceId() },
111+
};
112+
113+
// TODO generate binary hash
114+
// string binaryHash = CodePushUpdateUtils.getHashForBinaryContents(mainActivity, isDebugMode);
115+
/*if (binaryHash != null)
116+
{
117+
configMap.putString(PACKAGE_HASH_KEY, binaryHash);
118+
}*/
119+
promise.Resolve(config);
120+
}
121+
122+
[ReactMethod]
123+
public void getUpdateMetadata(int updateState, IPromise promise)
124+
{
125+
Action getCurrentPackageAction = async () =>
126+
{
127+
JObject currentPackage = await _codePush.UpdateManager.GetCurrentPackage();
128+
if (currentPackage == null)
129+
{
130+
promise.Resolve("");
131+
return;
132+
}
133+
134+
var currentUpdateIsPending = false;
135+
136+
if (currentPackage[CodePushConstants.PackageHashKey] != null)
137+
{
138+
var currentHash = (string)currentPackage[CodePushConstants.PackageHashKey];
139+
currentUpdateIsPending = _codePush.IsPendingUpdate(currentHash);
140+
}
141+
142+
if (updateState == (int)UpdateState.PENDING && !currentUpdateIsPending)
143+
{
144+
// The caller wanted a pending update
145+
// but there isn't currently one.
146+
promise.Resolve("");
147+
}
148+
else if (updateState == (int)UpdateState.RUNNING && currentUpdateIsPending)
149+
{
150+
// The caller wants the running update, but the current
151+
// one is pending, so we need to grab the previous.
152+
promise.Resolve(await _codePush.UpdateManager.GetPreviousPackage());
153+
}
154+
else
155+
{
156+
// The current package satisfies the request:
157+
// 1) Caller wanted a pending, and there is a pending update
158+
// 2) Caller wanted the running update, and there isn't a pending
159+
// 3) Caller wants the latest update, regardless if it's pending or not
160+
if (_codePush.IsRunningBinaryVersion)
161+
{
162+
// This only matters in Debug builds. Since we do not clear "outdated" updates,
163+
// we need to indicate to the JS side that somehow we have a current update on
164+
// disk that is not actually running.
165+
currentPackage["_isDebugOnly"] = true;
166+
}
167+
168+
// Enable differentiating pending vs. non-pending updates
169+
currentPackage["isPending"] = currentUpdateIsPending;
170+
promise.Resolve(currentPackage);
171+
}
172+
};
173+
174+
Context.RunOnNativeModulesQueueThread(getCurrentPackageAction);
175+
}
176+
177+
178+
[ReactMethod]
179+
public void getNewStatusReport(IPromise promise)
180+
{
181+
// TODO implement this
182+
promise.Resolve("");
183+
}
184+
185+
[ReactMethod]
186+
public void installUpdate(JObject updatePackage, int installMode, int minimumBackgroundDuration, IPromise promise)
187+
{
188+
Action installUpdateAction = async () =>
189+
{
190+
await _codePush.UpdateManager.InstallPackage(updatePackage, _codePush.IsPendingUpdate(null));
191+
var pendingHash = (string)updatePackage[CodePushConstants.PackageHashKey];
192+
SettingsManager.SavePendingUpdate(pendingHash, /* isLoading */false);
193+
if (installMode == (int)InstallMode.ON_NEXT_RESUME)
194+
{
195+
if (_codePushLifecycleEventListener == null)
196+
{
197+
// Ensure we do not add the listener twice.
198+
_codePushLifecycleEventListener = new CodePushResumeListener(this, minimumBackgroundDuration);
199+
_reactContext.AddLifecycleEventListener(_codePushLifecycleEventListener);
200+
}
201+
else
202+
{
203+
_codePushLifecycleEventListener.MinimumBackgroundDuration = minimumBackgroundDuration;
204+
}
205+
}
206+
207+
promise.Resolve("");
208+
};
209+
210+
Context.RunOnNativeModulesQueueThread(installUpdateAction);
211+
}
212+
213+
[ReactMethod]
214+
public void isFailedUpdate(string packageHash, IPromise promise)
215+
{
216+
promise.Resolve(_codePush.IsFailedHash(packageHash));
217+
}
218+
219+
[ReactMethod]
220+
public void isFirstRun(string packageHash, IPromise promise)
221+
{
222+
Action isFirstRunAction = async () =>
223+
{
224+
bool isFirstRun = _codePush.DidUpdate
225+
&& packageHash != null
226+
&& packageHash.Length > 0
227+
&& packageHash.Equals(await _codePush.UpdateManager.GetCurrentPackageHash());
228+
promise.Resolve(isFirstRun);
229+
};
230+
231+
Context.RunOnNativeModulesQueueThread(isFirstRunAction);
232+
}
233+
234+
[ReactMethod]
235+
public void notifyApplicationReady(IPromise promise)
236+
{
237+
SettingsManager.RemovePendingUpdate();
238+
promise.Resolve("");
239+
}
240+
241+
[ReactMethod]
242+
public void restartApp(bool onlyIfUpdateIsPending)
243+
{
244+
Action restartAppAction = async () =>
245+
{
246+
// If this is an unconditional restart request, or there
247+
// is current pending update, then reload the app.
248+
if (!onlyIfUpdateIsPending || _codePush.IsPendingUpdate(null))
249+
{
250+
await LoadBundle();
251+
}
252+
};
253+
254+
Context.RunOnNativeModulesQueueThread(restartAppAction);
255+
}
256+
257+
internal async Task LoadBundle()
258+
{
259+
// #1) Get the private ReactInstanceManager, which is what includes
260+
// the logic to reload the current React context.
261+
FieldInfo info = typeof(ReactPage)
262+
.GetField("_reactInstanceManager", BindingFlags.NonPublic | BindingFlags.Instance);
263+
264+
var reactInstanceManager = (ReactInstanceManager)typeof(ReactPage)
265+
.GetField("_reactInstanceManager", BindingFlags.NonPublic | BindingFlags.Instance)
266+
.GetValue(_codePush.MainPage);
267+
268+
// #2) Update the locally stored JS bundle file path
269+
Type reactInstanceManagerType = typeof(ReactInstanceManager);
270+
string latestJSBundleFile = await _codePush.GetJavaScriptBundleFileAsync(_codePush.AssetsBundleFileName);
271+
reactInstanceManagerType
272+
.GetField("_jsBundleFile", BindingFlags.NonPublic | BindingFlags.Instance)
273+
.SetValue(reactInstanceManager, latestJSBundleFile);
274+
275+
// #3) Get the context creation method and fire it on the UI thread (which RN enforces)
276+
Context.RunOnDispatcherQueueThread(reactInstanceManager.RecreateReactContextInBackground);
277+
}
278+
}
279+
}

windows/CodePushNotInitializedException.cs

Lines changed: 0 additions & 12 deletions
This file was deleted.

0 commit comments

Comments
 (0)