expo-updates 55.0.28 → 55.0.29

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -10,6 +10,17 @@
10
10
 
11
11
  ### 💡 Others
12
12
 
13
+ ## 55.0.29 — 2026-08-27
14
+
15
+ ### 🐛 Bug fixes
16
+
17
+ - [Android] Widen `UpdatesLogEntry.create`'s catch from `JSONException` to `Exception` so log-line parse failures consistently degrade to "skip the entry" instead of propagating. ([#46182](https://github.com/expo/expo/pull/46182) by [@jakequade-pc](https://github.com/jakequade-pc))
18
+ - [Android] Correct `UpdatesLogReader.ONE_DAY_MILLISECONDS` from `86400` (seconds) to `86_400_000` (milliseconds), so the "older than one day" purge filter actually retains a day's worth of entries instead of ~86 seconds' worth. ([#46182](https://github.com/expo/expo/pull/46182) by [@jakequade-pc](https://github.com/jakequade-pc))
19
+
20
+ ### 💡 Others
21
+
22
+ - [Android] Log purge completion errors via `android.util.Log.e` directly instead of `logger.error`, so the failure path doesn't re-enter the `PersistentFileLog` dispatch queue from inside one of its own tasks. ([#46182](https://github.com/expo/expo/pull/46182) by [@jakequade-pc](https://github.com/jakequade-pc))
23
+
13
24
  ## 55.0.28 — 2026-08-25
14
25
 
15
26
  ### 🐛 Bug fixes
@@ -42,7 +42,7 @@ expoModule {
42
42
  }
43
43
 
44
44
  group = 'host.exp.exponent'
45
- version = '55.0.28'
45
+ version = '55.0.29'
46
46
 
47
47
  // Utility method to derive boolean values from the environment or from Java properties,
48
48
  // and return them as strings to be used in BuildConfig fields
@@ -89,7 +89,7 @@ android {
89
89
  namespace "expo.modules.updates"
90
90
  defaultConfig {
91
91
  versionCode 31
92
- versionName '55.0.28'
92
+ versionName '55.0.29'
93
93
  consumerProguardFiles("proguard-rules.pro")
94
94
  testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
95
95
 
@@ -4,6 +4,7 @@ import android.app.Activity
4
4
  import android.content.Context
5
5
  import android.net.Uri
6
6
  import android.os.Bundle
7
+ import android.util.Log
7
8
  import com.facebook.react.ReactHost
8
9
  import com.facebook.react.bridge.ReactContext
9
10
  import com.facebook.react.devsupport.interfaces.DevSupportManager
@@ -18,7 +19,6 @@ import expo.modules.updates.events.IUpdatesEventManager
18
19
  import expo.modules.updates.events.UpdatesEventManager
19
20
  import expo.modules.updates.launcher.Launcher.LauncherCallback
20
21
  import expo.modules.updates.loader.FileDownloader
21
- import expo.modules.updates.logging.UpdatesErrorCode
22
22
  import expo.modules.updates.logging.UpdatesLogReader
23
23
  import expo.modules.updates.logging.UpdatesLogger
24
24
  import expo.modules.updates.manifest.EmbeddedManifestUtils
@@ -35,6 +35,7 @@ import expo.modules.updates.statemachine.UpdatesStateValue
35
35
  import expo.modules.updatesinterface.UpdatesInterface
36
36
  import expo.modules.updatesinterface.UpdatesStateChangeListener
37
37
  import expo.modules.updatesinterface.UpdatesStateChangeSubscription
38
+ import kotlinx.coroutines.CancellationException
38
39
  import kotlinx.coroutines.CompletableDeferred
39
40
  import kotlinx.coroutines.CoroutineScope
40
41
  import kotlinx.coroutines.Dispatchers
@@ -89,7 +90,12 @@ class EnabledUpdatesController(
89
90
  private fun purgeUpdatesLogsOlderThanOneDay() {
90
91
  UpdatesLogReader(context.filesDir).purgeLogEntries {
91
92
  if (it != null) {
92
- logger.error("UpdatesLogReader: error in purgeLogEntries", it, UpdatesErrorCode.Unknown)
93
+ // Log directly via android.util.Log rather than through `logger.error`,
94
+ // which writes via the PersistentFileLog dispatch queue. This callback
95
+ // is invoked from inside one of that queue's own tasks, so feeding
96
+ // another entry back into the queue from here re-enters it. Bypassing
97
+ // the queue keeps the failure path off its own back.
98
+ Log.e("expo-updates", "UpdatesLogReader: error in purgeLogEntries", it)
93
99
  }
94
100
  }
95
101
  }
@@ -256,7 +262,7 @@ class EnabledUpdatesController(
256
262
  }
257
263
 
258
264
  override suspend fun fetchUpdate() = suspendCancellableCoroutine { continuation ->
259
- val procedure = FetchUpdateProcedure(context, updatesConfiguration, logger, databaseHolder, updatesDirectory, fileDownloader, selectionPolicy, launchedUpdate) {
265
+ val procedure = FetchUpdateProcedure(context, updatesConfiguration, logger, databaseHolder, updatesDirectory, fileDownloader, selectionPolicy, launchedUpdate, controllerScope) {
260
266
  continuation.resume(it)
261
267
  }
262
268
  stateMachine.queueExecution(procedure)
@@ -280,6 +286,8 @@ class EnabledUpdatesController(
280
286
  }
281
287
  }
282
288
  continuation.resume(resultMap)
289
+ } catch (e: CancellationException) {
290
+ throw e
283
291
  } catch (e: Exception) {
284
292
  continuation.resumeWithException(e.toCodedException())
285
293
  }
@@ -288,7 +296,7 @@ class EnabledUpdatesController(
288
296
 
289
297
  override suspend fun setExtraParam(key: String, value: String?) = suspendCancellableCoroutine { continuation ->
290
298
  controllerScope.launch {
291
- runCatching {
299
+ try {
292
300
  ManifestMetadata.setExtraParam(
293
301
  databaseHolder.database,
294
302
  updatesConfiguration,
@@ -296,7 +304,9 @@ class EnabledUpdatesController(
296
304
  value
297
305
  )
298
306
  continuation.resume(Unit)
299
- }.onFailure { e ->
307
+ } catch (e: CancellationException) {
308
+ throw e
309
+ } catch (e: Exception) {
300
310
  continuation.resumeWithException(e.toCodedException())
301
311
  }
302
312
  }
@@ -34,9 +34,11 @@ import expo.modules.updatesinterface.UpdatesDevLauncherInterface
34
34
  import expo.modules.updatesinterface.UpdatesInterfaceCallbacks
35
35
  import expo.modules.updatesinterface.UpdatesStateChangeListener
36
36
  import expo.modules.updatesinterface.UpdatesStateChangeSubscription
37
+ import kotlinx.coroutines.CancellationException
37
38
  import kotlinx.coroutines.CoroutineScope
38
39
  import kotlinx.coroutines.Dispatchers
39
40
  import kotlinx.coroutines.SupervisorJob
41
+ import kotlinx.coroutines.cancel
40
42
  import kotlinx.coroutines.launch
41
43
  import kotlinx.coroutines.suspendCancellableCoroutine
42
44
  import org.json.JSONObject
@@ -183,7 +185,8 @@ class UpdatesDevLauncherController(
183
185
  databaseHolder.database,
184
186
  fileDownloader,
185
187
  updatesDirectory,
186
- null
188
+ null,
189
+ controllerScope
187
190
  )
188
191
  controllerScope.launch {
189
192
  val progressJob = launch {
@@ -215,6 +218,8 @@ class UpdatesDevLauncherController(
215
218
  return@launch
216
219
  }
217
220
  launchUpdate(loaderResult.updateEntity, updatesConfiguration!!, fileDownloader, callback)
221
+ } catch (e: CancellationException) {
222
+ throw e
218
223
  } catch (e: Exception) {
219
224
  // reset controller's configuration to what it was before this request
220
225
  updatesConfiguration = previousUpdatesConfiguration
@@ -309,6 +314,8 @@ class UpdatesDevLauncherController(
309
314
  get() = launcher.launchAssetFile!!
310
315
  })
311
316
  runReaper()
317
+ } catch (e: CancellationException) {
318
+ throw e
312
319
  } catch (e: Exception) {
313
320
  // reset controller's configuration to what it was before this request
314
321
  updatesConfiguration = previousUpdatesConfiguration
@@ -385,7 +392,7 @@ class UpdatesDevLauncherController(
385
392
  }
386
393
 
387
394
  override fun shutdown() {
388
- // no-op
395
+ controllerScope.cancel()
389
396
  }
390
397
 
391
398
  companion object {
@@ -9,6 +9,9 @@ import expo.modules.updates.UpdatesUtils
9
9
  import expo.modules.updates.db.entity.UpdateEntity
10
10
  import expo.modules.updates.logging.UpdatesLogger
11
11
  import expo.modules.updates.utils.AndroidResourceAssetUtils
12
+ import kotlinx.coroutines.CoroutineScope
13
+ import kotlinx.coroutines.Dispatchers
14
+ import kotlinx.coroutines.SupervisorJob
12
15
  import java.io.File
13
16
  import java.io.FileNotFoundException
14
17
  import java.lang.AssertionError
@@ -32,14 +35,16 @@ class EmbeddedLoader internal constructor(
32
35
  database: UpdatesDatabase,
33
36
  updatesDirectory: File,
34
37
  private val loaderFiles: LoaderFiles,
35
- private val shouldCopyEmbeddedAssets: Boolean = BuildConfig.EX_UPDATES_COPY_EMBEDDED_ASSETS
38
+ private val shouldCopyEmbeddedAssets: Boolean = BuildConfig.EX_UPDATES_COPY_EMBEDDED_ASSETS,
39
+ scope: CoroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
36
40
  ) : Loader(
37
41
  context,
38
42
  configuration,
39
43
  logger,
40
44
  database,
41
45
  updatesDirectory,
42
- loaderFiles
46
+ loaderFiles,
47
+ scope
43
48
  ) {
44
49
 
45
50
  constructor(
@@ -47,8 +52,9 @@ class EmbeddedLoader internal constructor(
47
52
  configuration: UpdatesConfiguration,
48
53
  logger: UpdatesLogger,
49
54
  database: UpdatesDatabase,
50
- updatesDirectory: File
51
- ) : this(context, configuration, logger, database, updatesDirectory, LoaderFiles())
55
+ updatesDirectory: File,
56
+ scope: CoroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
57
+ ) : this(context, configuration, logger, database, updatesDirectory, LoaderFiles(), scope = scope)
52
58
 
53
59
  override suspend fun loadRemoteUpdate(
54
60
  database: UpdatesDatabase,
@@ -25,6 +25,7 @@ import kotlinx.coroutines.SupervisorJob
25
25
  import java.io.File
26
26
  import java.io.IOException
27
27
  import java.util.*
28
+ import java.util.Collections
28
29
  import java.util.concurrent.ConcurrentHashMap
29
30
 
30
31
  /**
@@ -46,9 +47,9 @@ abstract class Loader protected constructor(
46
47
  private var updateResponse: UpdateResponse? = null
47
48
  private var updateEntity: UpdateEntity? = null
48
49
  private var assetTotal = 0
49
- private var erroredAssetList = mutableListOf<AssetEntity>()
50
- private var existingAssetList = mutableListOf<AssetEntity>()
51
- private var finishedAssetList = mutableListOf<AssetEntity>()
50
+ private var erroredAssetList: MutableList<AssetEntity> = Collections.synchronizedList(mutableListOf())
51
+ private var existingAssetList: MutableList<AssetEntity> = Collections.synchronizedList(mutableListOf())
52
+ private var finishedAssetList: MutableList<AssetEntity> = Collections.synchronizedList(mutableListOf())
52
53
  private val _progressFlow = MutableSharedFlow<AssetLoadProgress>()
53
54
  private var assetProgressMap: MutableMap<AssetEntity, Double> = ConcurrentHashMap()
54
55
 
@@ -117,9 +118,9 @@ abstract class Loader protected constructor(
117
118
  updateResponse = null
118
119
  updateEntity = null
119
120
  assetTotal = 0
120
- erroredAssetList = mutableListOf()
121
- existingAssetList = mutableListOf()
122
- finishedAssetList = mutableListOf()
121
+ erroredAssetList = Collections.synchronizedList(mutableListOf())
122
+ existingAssetList = Collections.synchronizedList(mutableListOf())
123
+ finishedAssetList = Collections.synchronizedList(mutableListOf())
123
124
  assetProgressMap = ConcurrentHashMap()
124
125
  assetLoadProgressBlock = null
125
126
  }
@@ -17,6 +17,7 @@ import expo.modules.updates.manifest.EmbeddedManifestUtils
17
17
  import expo.modules.updates.manifest.ManifestMetadata
18
18
  import expo.modules.updates.manifest.Update
19
19
  import expo.modules.updates.selectionpolicy.SelectionPolicy
20
+ import kotlinx.coroutines.CancellationException
20
21
  import kotlinx.coroutines.launch
21
22
  import kotlinx.coroutines.CoroutineScope
22
23
  import org.json.JSONObject
@@ -189,6 +190,8 @@ class LoaderTask(
189
190
  callback.onFinishedAllLoading()
190
191
  }
191
192
  }
193
+ } catch (e: CancellationException) {
194
+ throw e
192
195
  } catch (e: Exception) {
193
196
  if (!shouldCheckForUpdate) {
194
197
  finish(e)
@@ -209,6 +212,8 @@ class LoaderTask(
209
212
  isRunning = false
210
213
  runReaper()
211
214
  callback.onFinishedAllLoading()
215
+ } catch (e: CancellationException) {
216
+ throw e
212
217
  } catch (e: Exception) {
213
218
  finish(e)
214
219
  isRunning = false
@@ -296,10 +301,12 @@ class LoaderTask(
296
301
  )
297
302
  ) {
298
303
  try {
299
- val embeddedLoader = EmbeddedLoader(context, configuration, logger, database, directory)
304
+ val embeddedLoader = EmbeddedLoader(context, configuration, logger, database, directory, scope)
300
305
  embeddedLoader.load { _ ->
301
306
  Loader.OnUpdateResponseLoadedResult(shouldDownloadManifestIfPresentInResponse = true)
302
307
  }
308
+ } catch (e: CancellationException) {
309
+ throw e
303
310
  } catch (e: Exception) {
304
311
  logger.error("Unexpected error copying embedded update", e, UpdatesErrorCode.Unknown)
305
312
  }
@@ -315,7 +322,7 @@ class LoaderTask(
315
322
  private suspend fun launchRemoteUpdateInBackground() {
316
323
  val database = databaseHolder.database
317
324
  callback.onRemoteCheckForUpdateStarted()
318
- val remoteLoader = RemoteLoader(context, configuration, logger, database, fileDownloader, directory, candidateLauncher?.launchedUpdate)
325
+ val remoteLoader = RemoteLoader(context, configuration, logger, database, fileDownloader, directory, candidateLauncher?.launchedUpdate, scope)
319
326
 
320
327
  remoteLoader.assetLoadProgressBlock = { progress ->
321
328
  callback.onRemoteUpdateProgressChanged(progress)
@@ -10,6 +10,9 @@ import expo.modules.updates.logging.UpdatesLogger
10
10
  import expo.modules.updates.manifest.EmbeddedManifestUtils
11
11
  import expo.modules.updates.manifest.ManifestMetadata
12
12
  import expo.modules.updates.selectionpolicy.SelectionPolicy
13
+ import kotlinx.coroutines.CoroutineScope
14
+ import kotlinx.coroutines.Dispatchers
15
+ import kotlinx.coroutines.SupervisorJob
13
16
  import java.io.File
14
17
 
15
18
  data class ProcessSuccessLoaderResult(
@@ -32,8 +35,9 @@ class RemoteLoader internal constructor(
32
35
  private val mFileDownloader: FileDownloader,
33
36
  updatesDirectory: File,
34
37
  private val launchedUpdate: UpdateEntity?,
35
- private val loaderFiles: LoaderFiles
36
- ) : Loader(context, configuration, logger, database, updatesDirectory, loaderFiles) {
38
+ private val loaderFiles: LoaderFiles,
39
+ scope: CoroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
40
+ ) : Loader(context, configuration, logger, database, updatesDirectory, loaderFiles, scope) {
37
41
  constructor(
38
42
  context: Context,
39
43
  configuration: UpdatesConfiguration,
@@ -41,8 +45,9 @@ class RemoteLoader internal constructor(
41
45
  database: UpdatesDatabase,
42
46
  fileDownloader: FileDownloader,
43
47
  updatesDirectory: File,
44
- launchedUpdate: UpdateEntity?
45
- ) : this(context, configuration, logger, database, fileDownloader, updatesDirectory, launchedUpdate, LoaderFiles())
48
+ launchedUpdate: UpdateEntity?,
49
+ scope: CoroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
50
+ ) : this(context, configuration, logger, database, fileDownloader, updatesDirectory, launchedUpdate, LoaderFiles(), scope)
46
51
 
47
52
  override suspend fun loadRemoteUpdate(
48
53
  database: UpdatesDatabase,
@@ -3,7 +3,6 @@ package expo.modules.updates.logging
3
3
  import expo.modules.jsonutils.getNullable
4
4
  import expo.modules.jsonutils.require
5
5
  import org.json.JSONArray
6
- import org.json.JSONException
7
6
  import org.json.JSONObject
8
7
 
9
8
  /**
@@ -59,7 +58,11 @@ data class UpdatesLogEntry(
59
58
  List(jsonArray.length()) { i -> jsonArray.getString(i) }
60
59
  }
61
60
  )
62
- } catch (e: JSONException) {
61
+ } catch (e: Exception) {
62
+ // `JSONObject.require<T>` can surface a ClassCastException via a blind
63
+ // cast in the catch-all branch (e.g. an int-sized numeric `duration`
64
+ // token surfaces as `Integer`), which would otherwise escape and
65
+ // propagate to the caller.
63
66
  null
64
67
  }
65
68
  }
@@ -54,6 +54,6 @@ class UpdatesLogReader(
54
54
  }
55
55
 
56
56
  companion object {
57
- private const val ONE_DAY_MILLISECONDS = 86400
57
+ private const val ONE_DAY_MILLISECONDS = 86_400_000L
58
58
  }
59
59
  }
@@ -43,6 +43,8 @@ class CheckForUpdateProcedure(
43
43
  try {
44
44
  val updateResponse = downloadRemoteUpdate(extraHeaders)
45
45
  processUpdatesResponse(updateResponse, procedureContext, embeddedUpdate)
46
+ } catch (e: CancellationException) {
47
+ throw e
46
48
  } catch (e: Exception) {
47
49
  procedureContext.processStateEvent(UpdatesStateEvent.CheckError(e.localizedMessageWithCauseLocalizedMessage()))
48
50
  callback(IUpdatesController.CheckForUpdateResult.ErrorResult(e))
@@ -14,6 +14,7 @@ import expo.modules.updates.logging.UpdatesLogger
14
14
  import expo.modules.updates.selectionpolicy.SelectionPolicy
15
15
  import expo.modules.updates.statemachine.UpdatesStateEvent
16
16
  import kotlinx.coroutines.CancellationException
17
+ import kotlinx.coroutines.CoroutineScope
17
18
  import java.io.File
18
19
 
19
20
  class FetchUpdateProcedure(
@@ -25,6 +26,7 @@ class FetchUpdateProcedure(
25
26
  private val fileDownloader: FileDownloader,
26
27
  private val selectionPolicy: SelectionPolicy,
27
28
  private val launchedUpdate: UpdateEntity?,
29
+ private val scope: CoroutineScope,
28
30
  private val callback: (IUpdatesController.FetchUpdateResult) -> Unit
29
31
  ) : StateMachineProcedure() {
30
32
  override val loggerTimerLabel = "timer-fetch-update"
@@ -36,6 +38,8 @@ class FetchUpdateProcedure(
36
38
  try {
37
39
  val loaderResult = startRemoteLoader(database, procedureContext)
38
40
  processSuccessLoaderResult(loaderResult, procedureContext)
41
+ } catch (e: CancellationException) {
42
+ throw e
39
43
  } catch (e: Exception) {
40
44
  logger.error("Failed to download new update", e)
41
45
  procedureContext.processStateEvent(
@@ -55,7 +59,8 @@ class FetchUpdateProcedure(
55
59
  database,
56
60
  fileDownloader,
57
61
  updatesDirectory,
58
- launchedUpdate
62
+ launchedUpdate,
63
+ scope
59
64
  )
60
65
 
61
66
  remoteLoader.assetLoadProgressBlock = { progress ->
@@ -13,6 +13,7 @@ import expo.modules.updates.logging.UpdatesLogger
13
13
  import expo.modules.updates.reloadscreen.ReloadScreenManager
14
14
  import expo.modules.updates.selectionpolicy.SelectionPolicy
15
15
  import expo.modules.updates.statemachine.UpdatesStateEvent
16
+ import kotlinx.coroutines.CancellationException
16
17
  import kotlinx.coroutines.CoroutineScope
17
18
  import kotlinx.coroutines.Dispatchers
18
19
  import kotlinx.coroutines.launch
@@ -57,6 +58,8 @@ class RelaunchProcedure(
57
58
  )
58
59
  try {
59
60
  launchWith(newLauncher)
61
+ } catch (e: CancellationException) {
62
+ throw e
60
63
  } catch (e: Exception) {
61
64
  logger.error("Error launching new launcher", e, UpdatesErrorCode.Unknown)
62
65
  callback.onFailure(e)
@@ -91,6 +94,8 @@ class RelaunchProcedure(
91
94
  getCurrentLauncher().launchedUpdate,
92
95
  selectionPolicy
93
96
  )
97
+ } catch (e: CancellationException) {
98
+ throw e
94
99
  } catch (e: Exception) {
95
100
  logger.error("Could not run Reaper.", e, UpdatesErrorCode.Unknown)
96
101
  }
@@ -21,6 +21,7 @@ import expo.modules.updates.manifest.Update
21
21
  import expo.modules.updates.selectionpolicy.SelectionPolicy
22
22
  import expo.modules.updates.statemachine.UpdatesStateEvent
23
23
  import expo.modules.updates.statemachine.UpdatesStateValue
24
+ import kotlinx.coroutines.CancellationException
24
25
  import kotlinx.coroutines.CoroutineScope
25
26
  import kotlinx.coroutines.Dispatchers
26
27
  import kotlinx.coroutines.launch
@@ -242,7 +243,7 @@ class StartupProcedure(
242
243
  return
243
244
  }
244
245
  remoteLoadStatus = ErrorRecoveryDelegate.RemoteLoadStatus.NEW_UPDATE_LOADING
245
- val remoteLoader = RemoteLoader(context, updatesConfiguration, logger, databaseHolder.database, fileDownloader, updatesDirectory, launchedUpdate)
246
+ val remoteLoader = RemoteLoader(context, updatesConfiguration, logger, databaseHolder.database, fileDownloader, updatesDirectory, launchedUpdate, procedureScope)
246
247
  procedureScope.launch {
247
248
  try {
248
249
  val loaderResult = remoteLoader.load { updateResponse ->
@@ -267,6 +268,8 @@ class StartupProcedure(
267
268
  ErrorRecoveryDelegate.RemoteLoadStatus.IDLE
268
269
  }
269
270
  )
271
+ } catch (e: CancellationException) {
272
+ throw e
270
273
  } catch (e: Exception) {
271
274
  logger.error("UpdatesController loadRemoteUpdate onFailure", e, UpdatesErrorCode.UpdateFailedToLoad, launchedUpdate?.loggingId, null)
272
275
  setRemoteLoadStatus(ErrorRecoveryDelegate.RemoteLoadStatus.IDLE)
@@ -11,6 +11,7 @@ import android.view.View
11
11
  import android.widget.FrameLayout
12
12
  import android.widget.ImageView
13
13
  import android.widget.ProgressBar
14
+ import kotlinx.coroutines.CancellationException
14
15
  import kotlinx.coroutines.CoroutineScope
15
16
  import kotlinx.coroutines.Dispatchers
16
17
  import kotlinx.coroutines.cancel
@@ -135,6 +136,8 @@ class ReloadScreenView @JvmOverloads constructor(
135
136
  imageView.setImageBitmap(it)
136
137
  } ?: handleImageLoadFailure()
137
138
  }
139
+ } catch (e: CancellationException) {
140
+ throw e
138
141
  } catch (e: Exception) {
139
142
  withContext(Dispatchers.Main) {
140
143
  handleImageLoadFailure()
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "expo-updates",
3
- "version": "55.0.28",
3
+ "version": "55.0.29",
4
4
  "description": "Fetches and manages remotely-hosted assets and updates to your app's JS bundle.",
5
5
  "main": "build/index.js",
6
6
  "types": "build/index.d.ts",
@@ -71,5 +71,5 @@
71
71
  "react": "*",
72
72
  "react-native": "*"
73
73
  },
74
- "gitHead": "856b99321eeb04bd528b33f90c0e7fa2859a1fcb"
74
+ "gitHead": "5defc36eae34b72f7bee8385276200242e5458fe"
75
75
  }