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
+ }
0 commit comments