1- import { AcquisitionManager as Sdk } from "./lib/acquisition-sdk/acquisition-sdk" ;
1+ import { AcquisitionManager as Sdk , DownloadStatus } from "./lib/acquisition-sdk/acquisition-sdk" ;
22import { Alert } from "./AlertAdapter" ;
33import requestFetchAdapter from "./request-fetch-adapter" ;
4- import { AppState , Platform } from "react-native" ;
4+ import { AppState , NativeEventEmitter , Platform } from "react-native" ;
55import log from "./logging" ;
66import hoistStatics from 'hoist-non-react-statics' ;
77
88let NativeCodePush = require ( "react-native" ) . NativeModules . CodePush ;
9- const PackageMixins = require ( "./package-mixins" ) ( NativeCodePush ) ;
9+
10+ // Reporting this event is important, but avoid blocking install()/restartApp() indefinitely
11+ // on a stalled network request.
12+ const REPORT_STATUS_DOWNLOAD_TIMEOUT_MS = 5000 ;
13+
14+ async function withTimeout ( promise , timeoutMs ) {
15+ let timer ;
16+ const timeout = new Promise ( ( _ , reject ) => {
17+ timer = setTimeout ( ( ) => reject ( new Error ( `Timed out after ${ timeoutMs } ms` ) ) , timeoutMs ) ;
18+ } ) ;
19+
20+ try {
21+ return await Promise . race ( [ promise , timeout ] ) ;
22+ } finally {
23+ clearTimeout ( timer ) ;
24+ }
25+ }
26+
27+ // Downloads a remote package, augmenting it with a bound install() method
28+ // beyond what's included in the metadata sent by the server.
29+ async function downloadUpdate ( remotePackage , downloadProgressCallback , reportStatusDownload ) {
30+ if ( ! remotePackage . downloadUrl ) {
31+ throw new Error ( "Cannot download an update without a download url" ) ;
32+ }
33+
34+ let downloadProgressSubscription ;
35+ if ( downloadProgressCallback ) {
36+ const codePushEventEmitter = new NativeEventEmitter ( NativeCodePush ) ;
37+ // Use event subscription to obtain download progress.
38+ downloadProgressSubscription = codePushEventEmitter . addListener (
39+ "CodePushDownloadProgress" ,
40+ downloadProgressCallback
41+ ) ;
42+ }
43+
44+ const downloadStartTime = Date . now ( ) ;
45+ const reportDownloadStatus = async ( status ) => {
46+ if ( ! reportStatusDownload ) return ;
47+ // Only report a duration on success: on failure, this would be the time until
48+ // the download broke rather than a completed download's duration, and could be misleading.
49+ const downloadDurationMs = status === DownloadStatus . Succeeded ? Date . now ( ) - downloadStartTime : undefined ;
50+ try {
51+ await withTimeout ( reportStatusDownload ( { ...remotePackage , downloadDurationMs, status } ) , REPORT_STATUS_DOWNLOAD_TIMEOUT_MS ) ;
52+ } catch ( err ) {
53+ log ( `Report download status failed: ${ err } ` ) ;
54+ }
55+ } ;
56+
57+ // Use the downloaded package info. Native code will save the package info
58+ // so that the client knows what the current package version is.
59+ try {
60+ const updatePackageCopy = Object . assign ( { } , remotePackage ) ;
61+ Object . keys ( updatePackageCopy ) . forEach ( ( key ) => ( typeof updatePackageCopy [ key ] === 'function' ) && delete updatePackageCopy [ key ] ) ;
62+
63+ let downloadedPackage ;
64+ try {
65+ downloadedPackage = await NativeCodePush . downloadUpdate ( updatePackageCopy , ! ! downloadProgressCallback ) ;
66+ } catch ( err ) {
67+ await reportDownloadStatus ( DownloadStatus . Failed ) ;
68+ throw err ;
69+ }
70+
71+ await reportDownloadStatus ( DownloadStatus . Succeeded ) ;
72+
73+ return attachLocalPackageMethods ( { ...downloadedPackage , isPending : false } ) ; // A freshly downloaded package hasn't been installed yet
74+ } finally {
75+ downloadProgressSubscription && downloadProgressSubscription . remove ( ) ;
76+ }
77+ }
1078
1179async function checkForUpdate ( deploymentKey = null , handleBinaryVersionMismatchCallback = null ) {
1280 /*
@@ -82,7 +150,8 @@ async function checkForUpdate(deploymentKey = null, handleBinaryVersionMismatchC
82150
83151 return null ;
84152 } else {
85- const remotePackage = { ...update , ...PackageMixins . remote ( sdk . reportStatusDownload ) } ;
153+ const remotePackage = { ...update , isPending : false } ; // A remote package could never be in a pending state
154+ remotePackage . download = ( downloadProgressCallback ) => downloadUpdate ( remotePackage , downloadProgressCallback , sdk . reportStatusDownload ) ;
86155 remotePackage . failedInstall = await NativeCodePush . isFailedUpdate ( remotePackage . packageHash ) ;
87156 remotePackage . deploymentKey = deploymentKey || nativeConfig . deploymentKey ;
88157 return remotePackage ;
@@ -107,10 +176,30 @@ async function getCurrentPackage() {
107176 return await getUpdateMetadata ( CodePush . UpdateState . LATEST ) ;
108177}
109178
179+ async function installUpdate ( localPackage , installMode = NativeCodePush . codePushInstallModeOnNextRestart , minimumBackgroundDuration = 0 , updateInstalledCallback ) {
180+ const localPackageCopy = Object . assign ( { } , localPackage ) ; // In dev mode, React Native deep freezes any object queued over the bridge
181+ await NativeCodePush . installUpdate ( localPackageCopy , installMode , minimumBackgroundDuration ) ;
182+ updateInstalledCallback && updateInstalledCallback ( ) ;
183+ if ( installMode == NativeCodePush . codePushInstallModeImmediate ) {
184+ NativeCodePush . restartApp ( false ) ;
185+ } else {
186+ NativeCodePush . clearPendingRestart ( ) ;
187+ localPackage . isPending = true ; // Mark the package as pending since it hasn't been applied yet
188+ }
189+ }
190+
191+ // Augments a raw local package (as returned by native code) with a bound
192+ // install() method beyond what's included in the metadata sent by the server.
193+ function attachLocalPackageMethods ( localPackage ) {
194+ localPackage . install = ( installMode , minimumBackgroundDuration , updateInstalledCallback ) =>
195+ installUpdate ( localPackage , installMode , minimumBackgroundDuration , updateInstalledCallback ) ;
196+ return localPackage ;
197+ }
198+
110199async function getUpdateMetadata ( updateState ) {
111200 let updateMetadata = await NativeCodePush . getUpdateMetadata ( updateState || CodePush . UpdateState . RUNNING ) ;
112201 if ( updateMetadata ) {
113- updateMetadata = { ... PackageMixins . local , ...updateMetadata } ;
202+ updateMetadata = attachLocalPackageMethods ( { ...updateMetadata } ) ;
114203 updateMetadata . failedInstall = await NativeCodePush . isFailedUpdate ( updateMetadata . packageHash ) ;
115204 updateMetadata . isFirstRun = await NativeCodePush . isFirstRun ( updateMetadata . packageHash ) ;
116205 }
0 commit comments