expo-updates 0.27.0 → 0.27.2
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 +19 -1
- package/android/build.gradle +2 -2
- package/android/src/main/java/expo/modules/updates/UpdatesUtils.kt +1 -1
- package/android/src/main/java/expo/modules/updates/launcher/DatabaseLauncher.kt +10 -4
- package/android/src/main/java/expo/modules/updates/loader/EmbeddedLoader.kt +3 -0
- package/android/src/main/java/expo/modules/updates/loader/FileDownloader.kt +24 -1
- package/android/src/main/java/expo/modules/updates/loader/Loader.kt +9 -2
- package/android/src/main/java/expo/modules/updates/loader/LoaderFiles.kt +1 -1
- package/android/src/main/java/expo/modules/updates/loader/RemoteLoader.kt +4 -1
- package/build-cli/cli.d.ts +2 -0
- package/build-cli/cli.js +46 -0
- package/build-cli/configureCodeSigning.d.ts +3 -0
- package/build-cli/configureCodeSigning.js +43 -0
- package/build-cli/configureCodeSigningAsync.d.ts +7 -0
- package/build-cli/configureCodeSigningAsync.js +42 -0
- package/build-cli/generateCodeSigning.d.ts +3 -0
- package/build-cli/generateCodeSigning.js +48 -0
- package/build-cli/generateCodeSigningAsync.d.ts +8 -0
- package/build-cli/generateCodeSigningAsync.js +43 -0
- package/build-cli/utils/args.d.ts +16 -0
- package/build-cli/utils/args.js +52 -0
- package/build-cli/utils/dir.d.ts +1 -0
- package/build-cli/utils/dir.js +8 -0
- package/build-cli/utils/log.d.ts +7 -0
- package/build-cli/utils/log.js +34 -0
- package/build-cli/utils/modifyConfigAsync.d.ts +3 -0
- package/build-cli/utils/modifyConfigAsync.js +41 -0
- package/e2e/setup/project.ts +21 -5
- package/ios/EXUpdates/AppLoader/AppLoader.swift +10 -3
- package/ios/EXUpdates/AppLoader/EmbeddedAppLoader.swift +1 -1
- package/ios/EXUpdates/AppLoader/FileDownloader.swift +24 -0
- package/ios/EXUpdates/AppLoader/RemoteAppLoader.swift +2 -3
- package/package.json +7 -7
package/CHANGELOG.md
CHANGED
|
@@ -10,7 +10,13 @@
|
|
|
10
10
|
|
|
11
11
|
### 💡 Others
|
|
12
12
|
|
|
13
|
-
## 0.27.
|
|
13
|
+
## 0.27.2 — 2025-02-26
|
|
14
|
+
|
|
15
|
+
### 💡 Others
|
|
16
|
+
|
|
17
|
+
- Add update id headers to asset requests ([#34453](https://github.com/expo/expo/pull/34453) by [@gabrieldonadel](https://github.com/gabrieldonadel))
|
|
18
|
+
|
|
19
|
+
## 0.27.1 — 2025-02-21
|
|
14
20
|
|
|
15
21
|
### 🎉 New features
|
|
16
22
|
|
|
@@ -21,6 +27,18 @@
|
|
|
21
27
|
- Fixed build error on iOS Expo Go. ([#34485](https://github.com/expo/expo/pull/34485) by [@kudo](https://github.com/kudo))
|
|
22
28
|
- Fixed Android unit test errors in BuilDataTest. ([#34510](https://github.com/expo/expo/pull/34510) by [@kudo](https://github.com/kudo))
|
|
23
29
|
|
|
30
|
+
## 0.26.19 — 2025-02-19
|
|
31
|
+
|
|
32
|
+
### 💡 Others
|
|
33
|
+
|
|
34
|
+
- Fixed incorrect error log on Android. ([#34785](https://github.com/expo/expo/pull/34785) by [@kudo](https://github.com/kudo))
|
|
35
|
+
|
|
36
|
+
## 0.26.18 — 2025-02-12
|
|
37
|
+
|
|
38
|
+
### 🐛 Bug fixes
|
|
39
|
+
|
|
40
|
+
- [Android] Fix `bytesToHex` `ArrayIndexOutOfBoundsException` during conversion. ([#34855](https://github.com/expo/expo/pull/34855) by [@gabrieldonadel](https://github.com/gabrieldonadel))
|
|
41
|
+
|
|
24
42
|
## 0.26.17 — 2025-02-06
|
|
25
43
|
|
|
26
44
|
_This version does not introduce any user-facing changes._
|
package/android/build.gradle
CHANGED
|
@@ -16,7 +16,7 @@ apply plugin: 'com.android.library'
|
|
|
16
16
|
apply plugin: 'com.google.devtools.ksp'
|
|
17
17
|
|
|
18
18
|
group = 'host.exp.exponent'
|
|
19
|
-
version = '0.27.
|
|
19
|
+
version = '0.27.2'
|
|
20
20
|
|
|
21
21
|
def expoModulesCorePlugin = new File(project(":expo-modules-core").projectDir.absolutePath, "ExpoModulesCorePlugin.gradle")
|
|
22
22
|
apply from: expoModulesCorePlugin
|
|
@@ -63,7 +63,7 @@ android {
|
|
|
63
63
|
namespace "expo.modules.updates"
|
|
64
64
|
defaultConfig {
|
|
65
65
|
versionCode 31
|
|
66
|
-
versionName '0.27.
|
|
66
|
+
versionName '0.27.2'
|
|
67
67
|
consumerProguardFiles("proguard-rules.pro")
|
|
68
68
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
|
69
69
|
|
|
@@ -185,7 +185,7 @@ object UpdatesUtils {
|
|
|
185
185
|
fun bytesToHex(bytes: ByteArray): String {
|
|
186
186
|
val hexChars = CharArray(bytes.size * 2)
|
|
187
187
|
for (j in bytes.indices) {
|
|
188
|
-
val v =
|
|
188
|
+
val v = bytes[j].toInt() and 0xFF
|
|
189
189
|
hexChars[j * 2] = HEX_ARRAY[v ushr 4]
|
|
190
190
|
hexChars[j * 2 + 1] = HEX_ARRAY[v and 0x0F]
|
|
191
191
|
}
|
|
@@ -15,8 +15,10 @@ import expo.modules.updates.loader.LoaderFiles
|
|
|
15
15
|
import expo.modules.updates.logging.UpdatesErrorCode
|
|
16
16
|
import expo.modules.updates.logging.UpdatesLogger
|
|
17
17
|
import expo.modules.updates.manifest.EmbeddedManifestUtils
|
|
18
|
+
import expo.modules.updates.manifest.EmbeddedUpdate
|
|
18
19
|
import expo.modules.updates.manifest.ManifestMetadata
|
|
19
20
|
import expo.modules.updates.selectionpolicy.SelectionPolicy
|
|
21
|
+
import org.json.JSONObject
|
|
20
22
|
import java.io.File
|
|
21
23
|
|
|
22
24
|
/**
|
|
@@ -92,7 +94,10 @@ class DatabaseLauncher(
|
|
|
92
94
|
this.callback!!.onFailure(Exception("Launch asset relative path should not be null. Debug info: ${launchedUpdate!!.debugInfo()}"))
|
|
93
95
|
}
|
|
94
96
|
|
|
95
|
-
val
|
|
97
|
+
val embeddedUpdate = EmbeddedManifestUtils.getEmbeddedUpdate(context, configuration)
|
|
98
|
+
val extraHeaders = FileDownloader.getExtraHeadersForRemoteAssetRequest(launchedUpdate, embeddedUpdate?.updateEntity, launchedUpdate)
|
|
99
|
+
|
|
100
|
+
val launchAssetFile = ensureAssetExists(launchAsset, database, embeddedUpdate, extraHeaders)
|
|
96
101
|
if (launchAssetFile != null) {
|
|
97
102
|
this.launchAssetFile = launchAssetFile.toString()
|
|
98
103
|
}
|
|
@@ -107,7 +112,7 @@ class DatabaseLauncher(
|
|
|
107
112
|
}
|
|
108
113
|
val filename = asset.relativePath
|
|
109
114
|
if (filename != null) {
|
|
110
|
-
val assetFile = ensureAssetExists(asset, database)
|
|
115
|
+
val assetFile = ensureAssetExists(asset, database, embeddedUpdate, extraHeaders)
|
|
111
116
|
if (assetFile != null) {
|
|
112
117
|
this[asset] = Uri.fromFile(assetFile).toString()
|
|
113
118
|
}
|
|
@@ -170,13 +175,12 @@ class DatabaseLauncher(
|
|
|
170
175
|
}
|
|
171
176
|
}
|
|
172
177
|
|
|
173
|
-
fun ensureAssetExists(asset: AssetEntity, database: UpdatesDatabase): File? {
|
|
178
|
+
fun ensureAssetExists(asset: AssetEntity, database: UpdatesDatabase, embeddedUpdate: EmbeddedUpdate?, extraHeaders: JSONObject): File? {
|
|
174
179
|
val assetFile = File(updatesDirectory, asset.relativePath ?: "")
|
|
175
180
|
var assetFileExists = assetFile.exists()
|
|
176
181
|
if (!assetFileExists) {
|
|
177
182
|
// something has gone wrong, we're missing this asset
|
|
178
183
|
// first we check to see if a copy is embedded in the binary
|
|
179
|
-
val embeddedUpdate = EmbeddedManifestUtils.getEmbeddedUpdate(context, configuration)
|
|
180
184
|
if (embeddedUpdate != null) {
|
|
181
185
|
val embeddedAssets = embeddedUpdate.assetEntityList
|
|
182
186
|
var matchingEmbeddedAsset: AssetEntity? = null
|
|
@@ -204,9 +208,11 @@ class DatabaseLauncher(
|
|
|
204
208
|
return if (!assetFileExists) {
|
|
205
209
|
// we still don't have the asset locally, so try downloading it remotely
|
|
206
210
|
assetsToDownload++
|
|
211
|
+
|
|
207
212
|
fileDownloader.downloadAsset(
|
|
208
213
|
asset,
|
|
209
214
|
updatesDirectory,
|
|
215
|
+
extraHeaders,
|
|
210
216
|
object : AssetDownloadCallback {
|
|
211
217
|
override fun onFailure(e: Exception, assetEntity: AssetEntity) {
|
|
212
218
|
logger.error("Failed to load asset from disk or network", e, UpdatesErrorCode.AssetsFailedToLoad)
|
|
@@ -7,6 +7,7 @@ import expo.modules.updates.db.UpdatesDatabase
|
|
|
7
7
|
import expo.modules.updates.loader.FileDownloader.AssetDownloadCallback
|
|
8
8
|
import expo.modules.updates.loader.FileDownloader.RemoteUpdateDownloadCallback
|
|
9
9
|
import expo.modules.updates.UpdatesUtils
|
|
10
|
+
import expo.modules.updates.db.entity.UpdateEntity
|
|
10
11
|
import expo.modules.updates.logging.UpdatesLogger
|
|
11
12
|
import java.io.File
|
|
12
13
|
import java.io.FileNotFoundException
|
|
@@ -71,6 +72,8 @@ class EmbeddedLoader internal constructor(
|
|
|
71
72
|
assetEntity: AssetEntity,
|
|
72
73
|
updatesDirectory: File?,
|
|
73
74
|
configuration: UpdatesConfiguration,
|
|
75
|
+
requestedUpdate: UpdateEntity?,
|
|
76
|
+
embeddedUpdate: UpdateEntity?,
|
|
74
77
|
callback: AssetDownloadCallback
|
|
75
78
|
) {
|
|
76
79
|
val filename = UpdatesUtils.createFilenameForAsset(assetEntity)
|
|
@@ -449,6 +449,7 @@ class FileDownloader(
|
|
|
449
449
|
fun downloadAsset(
|
|
450
450
|
asset: AssetEntity,
|
|
451
451
|
destinationDirectory: File?,
|
|
452
|
+
extraHeaders: JSONObject,
|
|
452
453
|
callback: AssetDownloadCallback
|
|
453
454
|
) {
|
|
454
455
|
if (asset.url == null) {
|
|
@@ -466,7 +467,7 @@ class FileDownloader(
|
|
|
466
467
|
} else {
|
|
467
468
|
try {
|
|
468
469
|
downloadAssetAndVerifyHashAndWriteToPath(
|
|
469
|
-
createRequestForAsset(asset, configuration, context),
|
|
470
|
+
createRequestForAsset(asset, extraHeaders, configuration, context),
|
|
470
471
|
asset.expectedHash,
|
|
471
472
|
path,
|
|
472
473
|
object : FileDownloadCallback {
|
|
@@ -594,12 +595,14 @@ class FileDownloader(
|
|
|
594
595
|
|
|
595
596
|
internal fun createRequestForAsset(
|
|
596
597
|
assetEntity: AssetEntity,
|
|
598
|
+
extraHeaders: JSONObject,
|
|
597
599
|
configuration: UpdatesConfiguration,
|
|
598
600
|
context: Context
|
|
599
601
|
): Request {
|
|
600
602
|
return Request.Builder()
|
|
601
603
|
.url(assetEntity.url!!.toString())
|
|
602
604
|
.addHeadersFromJSONObject(assetEntity.extraRequestHeaders)
|
|
605
|
+
.addHeadersFromJSONObject(extraHeaders)
|
|
603
606
|
.header("Expo-Platform", "android")
|
|
604
607
|
.header("Expo-Protocol-Version", "1")
|
|
605
608
|
.header("Expo-API-Version", "1")
|
|
@@ -700,5 +703,25 @@ class FileDownloader(
|
|
|
700
703
|
|
|
701
704
|
return extraHeaders
|
|
702
705
|
}
|
|
706
|
+
|
|
707
|
+
fun getExtraHeadersForRemoteAssetRequest(
|
|
708
|
+
launchedUpdate: UpdateEntity?,
|
|
709
|
+
embeddedUpdate: UpdateEntity?,
|
|
710
|
+
requestedUpdate: UpdateEntity?
|
|
711
|
+
): JSONObject {
|
|
712
|
+
val extraHeaders = JSONObject()
|
|
713
|
+
|
|
714
|
+
launchedUpdate?.let {
|
|
715
|
+
extraHeaders.put("Expo-Current-Update-ID", it.id.toString().lowercase())
|
|
716
|
+
}
|
|
717
|
+
embeddedUpdate?.let {
|
|
718
|
+
extraHeaders.put("Expo-Embedded-Update-ID", it.id.toString().lowercase())
|
|
719
|
+
}
|
|
720
|
+
requestedUpdate?.let {
|
|
721
|
+
extraHeaders.put("Expo-Requested-Update-ID", it.id.toString().lowercase())
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
return extraHeaders
|
|
725
|
+
}
|
|
703
726
|
}
|
|
704
727
|
}
|
|
@@ -86,6 +86,8 @@ abstract class Loader protected constructor(
|
|
|
86
86
|
assetEntity: AssetEntity,
|
|
87
87
|
updatesDirectory: File?,
|
|
88
88
|
configuration: UpdatesConfiguration,
|
|
89
|
+
requestedUpdate: UpdateEntity?,
|
|
90
|
+
embeddedUpdate: UpdateEntity?,
|
|
89
91
|
callback: AssetDownloadCallback
|
|
90
92
|
)
|
|
91
93
|
|
|
@@ -204,7 +206,7 @@ abstract class Loader protected constructor(
|
|
|
204
206
|
// however, it's not ready, so we should try to download all the assets again.
|
|
205
207
|
updateEntity = existingUpdateEntity
|
|
206
208
|
}
|
|
207
|
-
downloadAllAssets(update
|
|
209
|
+
downloadAllAssets(update)
|
|
208
210
|
}
|
|
209
211
|
}
|
|
210
212
|
|
|
@@ -214,8 +216,11 @@ abstract class Loader protected constructor(
|
|
|
214
216
|
ERRORED
|
|
215
217
|
}
|
|
216
218
|
|
|
217
|
-
private fun downloadAllAssets(
|
|
219
|
+
private fun downloadAllAssets(update: Update) {
|
|
220
|
+
val assetList = update.assetEntityList
|
|
218
221
|
assetTotal = assetList.size
|
|
222
|
+
|
|
223
|
+
val embeddedUpdate = loaderFiles.readEmbeddedUpdate(context, configuration)
|
|
219
224
|
for (assetEntityCur in assetList) {
|
|
220
225
|
var assetEntity = assetEntityCur
|
|
221
226
|
|
|
@@ -243,6 +248,8 @@ abstract class Loader protected constructor(
|
|
|
243
248
|
assetEntity,
|
|
244
249
|
updatesDirectory,
|
|
245
250
|
configuration,
|
|
251
|
+
requestedUpdate = update.updateEntity,
|
|
252
|
+
embeddedUpdate = embeddedUpdate?.updateEntity,
|
|
246
253
|
object : AssetDownloadCallback {
|
|
247
254
|
override fun onFailure(e: Exception, assetEntity: AssetEntity) {
|
|
248
255
|
val identifier = if (assetEntity.hash != null) {
|
|
@@ -69,7 +69,7 @@ open class LoaderFiles {
|
|
|
69
69
|
context.resources.openRawResource(id)
|
|
70
70
|
.use { inputStream -> return UpdatesUtils.verifySHA256AndWriteToFile(inputStream, destination, null) }
|
|
71
71
|
} catch (e: Exception) {
|
|
72
|
-
Log.e(TAG, "Failed to copy asset
|
|
72
|
+
Log.e(TAG, "Failed to copy resource asset ${asset.resourcesFolder}/${asset.embeddedAssetFilename}", e)
|
|
73
73
|
throw e
|
|
74
74
|
}
|
|
75
75
|
}
|
|
@@ -55,9 +55,12 @@ class RemoteLoader internal constructor(
|
|
|
55
55
|
assetEntity: AssetEntity,
|
|
56
56
|
updatesDirectory: File?,
|
|
57
57
|
configuration: UpdatesConfiguration,
|
|
58
|
+
requestedUpdate: UpdateEntity?,
|
|
59
|
+
embeddedUpdate: UpdateEntity?,
|
|
58
60
|
callback: AssetDownloadCallback
|
|
59
61
|
) {
|
|
60
|
-
|
|
62
|
+
val extraHeaders = FileDownloader.getExtraHeadersForRemoteAssetRequest(launchedUpdate, embeddedUpdate, requestedUpdate)
|
|
63
|
+
mFileDownloader.downloadAsset(assetEntity, updatesDirectory, extraHeaders, callback)
|
|
61
64
|
}
|
|
62
65
|
|
|
63
66
|
companion object {
|
package/build-cli/cli.js
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const arg_1 = tslib_1.__importDefault(require("arg"));
|
|
6
|
+
const chalk_1 = tslib_1.__importDefault(require("chalk"));
|
|
7
|
+
const Log = tslib_1.__importStar(require("./utils/log"));
|
|
8
|
+
const commands = {
|
|
9
|
+
// Add a new command here
|
|
10
|
+
'codesigning:generate': () => import('./generateCodeSigning.js').then((i) => i.generateCodeSigning),
|
|
11
|
+
'codesigning:configure': () => import('./configureCodeSigning.js').then((i) => i.configureCodeSigning),
|
|
12
|
+
};
|
|
13
|
+
const args = (0, arg_1.default)({
|
|
14
|
+
// Types
|
|
15
|
+
'--help': Boolean,
|
|
16
|
+
// Aliases
|
|
17
|
+
'-h': '--help',
|
|
18
|
+
}, {
|
|
19
|
+
permissive: true,
|
|
20
|
+
});
|
|
21
|
+
const command = args._[0];
|
|
22
|
+
const commandArgs = args._.slice(1);
|
|
23
|
+
// Handle `--help` flag
|
|
24
|
+
if ((args['--help'] && !command) || !command) {
|
|
25
|
+
Log.exit((0, chalk_1.default) `
|
|
26
|
+
{bold Usage}
|
|
27
|
+
{dim $} npx expo-updates <command>
|
|
28
|
+
|
|
29
|
+
{bold Commands}
|
|
30
|
+
${Object.keys(commands).sort().join(', ')}
|
|
31
|
+
|
|
32
|
+
{bold Options}
|
|
33
|
+
--help, -h Displays this message
|
|
34
|
+
|
|
35
|
+
For more information run a command with the --help flag
|
|
36
|
+
{dim $} npx expo-updates codesigning:generate --help
|
|
37
|
+
`, 0);
|
|
38
|
+
}
|
|
39
|
+
// Push the help flag to the subcommand args.
|
|
40
|
+
if (args['--help']) {
|
|
41
|
+
commandArgs.push('--help');
|
|
42
|
+
}
|
|
43
|
+
// Install exit hooks
|
|
44
|
+
process.on('SIGINT', () => process.exit(0));
|
|
45
|
+
process.on('SIGTERM', () => process.exit(0));
|
|
46
|
+
commands[command]().then((exec) => exec(commandArgs));
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.configureCodeSigning = void 0;
|
|
5
|
+
const tslib_1 = require("tslib");
|
|
6
|
+
const chalk_1 = tslib_1.__importDefault(require("chalk"));
|
|
7
|
+
const args_1 = require("./utils/args");
|
|
8
|
+
const Log = tslib_1.__importStar(require("./utils/log"));
|
|
9
|
+
const configureCodeSigning = async (argv) => {
|
|
10
|
+
const args = (0, args_1.assertArgs)({
|
|
11
|
+
// Types
|
|
12
|
+
'--help': Boolean,
|
|
13
|
+
'--certificate-input-directory': String,
|
|
14
|
+
'--key-input-directory': String,
|
|
15
|
+
'--keyid': String,
|
|
16
|
+
// Aliases
|
|
17
|
+
'-h': '--help',
|
|
18
|
+
}, argv !== null && argv !== void 0 ? argv : []);
|
|
19
|
+
if (args['--help']) {
|
|
20
|
+
Log.exit((0, chalk_1.default) `
|
|
21
|
+
{bold Description}
|
|
22
|
+
Configure expo-updates code signing for this project and verify setup
|
|
23
|
+
|
|
24
|
+
{bold Usage}
|
|
25
|
+
{dim $} npx expo-updates codesigning:configure --certificate-input-directory <dir> --key-input-directory <dir>
|
|
26
|
+
|
|
27
|
+
Options
|
|
28
|
+
--certificate-input-directory <string> Directory containing code signing certificate
|
|
29
|
+
--key-input-directory <string> Directory containing private and public keys
|
|
30
|
+
-h, --help Output usage information
|
|
31
|
+
`, 0);
|
|
32
|
+
}
|
|
33
|
+
const { configureCodeSigningAsync } = await import('./configureCodeSigningAsync.js');
|
|
34
|
+
const certificateInput = (0, args_1.requireArg)(args, '--certificate-input-directory');
|
|
35
|
+
const keyInput = (0, args_1.requireArg)(args, '--key-input-directory');
|
|
36
|
+
const keyid = args['--keyid'];
|
|
37
|
+
return await configureCodeSigningAsync((0, args_1.getProjectRoot)(args), {
|
|
38
|
+
certificateInput,
|
|
39
|
+
keyInput,
|
|
40
|
+
keyid,
|
|
41
|
+
});
|
|
42
|
+
};
|
|
43
|
+
exports.configureCodeSigning = configureCodeSigning;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.configureCodeSigningAsync = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const code_signing_certificates_1 = require("@expo/code-signing-certificates");
|
|
6
|
+
const config_1 = require("@expo/config");
|
|
7
|
+
const fs_1 = require("fs");
|
|
8
|
+
const path_1 = tslib_1.__importDefault(require("path"));
|
|
9
|
+
const log_1 = require("./utils/log");
|
|
10
|
+
const modifyConfigAsync_1 = require("./utils/modifyConfigAsync");
|
|
11
|
+
async function configureCodeSigningAsync(projectRoot, { certificateInput, keyInput, keyid }) {
|
|
12
|
+
const certificateInputDir = path_1.default.resolve(projectRoot, certificateInput);
|
|
13
|
+
const keyInputDir = path_1.default.resolve(projectRoot, keyInput);
|
|
14
|
+
const [certificatePEM, privateKeyPEM, publicKeyPEM] = await Promise.all([
|
|
15
|
+
fs_1.promises.readFile(path_1.default.join(certificateInputDir, 'certificate.pem'), 'utf8'),
|
|
16
|
+
fs_1.promises.readFile(path_1.default.join(keyInputDir, 'private-key.pem'), 'utf8'),
|
|
17
|
+
fs_1.promises.readFile(path_1.default.join(keyInputDir, 'public-key.pem'), 'utf8'),
|
|
18
|
+
]);
|
|
19
|
+
const certificate = (0, code_signing_certificates_1.convertCertificatePEMToCertificate)(certificatePEM);
|
|
20
|
+
const keyPair = (0, code_signing_certificates_1.convertKeyPairPEMToKeyPair)({ privateKeyPEM, publicKeyPEM });
|
|
21
|
+
(0, code_signing_certificates_1.validateSelfSignedCertificate)(certificate, keyPair);
|
|
22
|
+
const { exp } = (0, config_1.getConfig)(projectRoot, { skipSDKVersionRequirement: true });
|
|
23
|
+
const fields = {
|
|
24
|
+
codeSigningCertificate: `./${path_1.default.relative(projectRoot, certificateInputDir)}/certificate.pem`,
|
|
25
|
+
codeSigningMetadata: {
|
|
26
|
+
keyid: keyid !== null && keyid !== void 0 ? keyid : 'main',
|
|
27
|
+
alg: 'rsa-v1_5-sha256',
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
await (0, modifyConfigAsync_1.attemptModification)(projectRoot, {
|
|
31
|
+
updates: {
|
|
32
|
+
...exp.updates,
|
|
33
|
+
...fields,
|
|
34
|
+
},
|
|
35
|
+
}, {
|
|
36
|
+
updates: {
|
|
37
|
+
...fields,
|
|
38
|
+
},
|
|
39
|
+
});
|
|
40
|
+
(0, log_1.log)(`Code signing configuration written to app configuration.`);
|
|
41
|
+
}
|
|
42
|
+
exports.configureCodeSigningAsync = configureCodeSigningAsync;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.generateCodeSigning = void 0;
|
|
5
|
+
const tslib_1 = require("tslib");
|
|
6
|
+
const chalk_1 = tslib_1.__importDefault(require("chalk"));
|
|
7
|
+
const args_1 = require("./utils/args");
|
|
8
|
+
const Log = tslib_1.__importStar(require("./utils/log"));
|
|
9
|
+
const generateCodeSigning = async (argv) => {
|
|
10
|
+
const args = (0, args_1.assertArgs)({
|
|
11
|
+
// Types
|
|
12
|
+
'--help': Boolean,
|
|
13
|
+
'--key-output-directory': String,
|
|
14
|
+
'--certificate-output-directory': String,
|
|
15
|
+
'--certificate-validity-duration-years': Number,
|
|
16
|
+
'--certificate-common-name': String,
|
|
17
|
+
// Aliases
|
|
18
|
+
'-h': '--help',
|
|
19
|
+
}, argv !== null && argv !== void 0 ? argv : []);
|
|
20
|
+
if (args['--help']) {
|
|
21
|
+
Log.exit((0, chalk_1.default) `
|
|
22
|
+
{bold Description}
|
|
23
|
+
Generate expo-updates private key, public key, and code signing certificate using that public key (self-signed by the private key)
|
|
24
|
+
|
|
25
|
+
{bold Usage}
|
|
26
|
+
{dim $} npx expo-updates codesigning:generate --key-output-directory <dir> --certificate-output-directory <dir> --certificate-validity-duration-years <num years> --certificate-common-name <name>
|
|
27
|
+
|
|
28
|
+
Options
|
|
29
|
+
--key-output-directory <string> Directory in which to put the generated private and public keys
|
|
30
|
+
--certificate-output-directory <string> Directory in which to put the generated certificate
|
|
31
|
+
--certificate-validity-duration-years <number> Certificate validity duration in years (number of years before certificate needs rotation)
|
|
32
|
+
--certificate-common-name <string> Common name attribute for certificate (generally the human readable name of the organization owning this application)
|
|
33
|
+
-h, --help Output usage information
|
|
34
|
+
`, 0);
|
|
35
|
+
}
|
|
36
|
+
const { generateCodeSigningAsync } = await import('./generateCodeSigningAsync.js');
|
|
37
|
+
const keyOutput = (0, args_1.requireArg)(args, '--key-output-directory');
|
|
38
|
+
const certificateOutput = (0, args_1.requireArg)(args, '--certificate-output-directory');
|
|
39
|
+
const certificateValidityDurationYears = (0, args_1.requireArg)(args, '--certificate-validity-duration-years');
|
|
40
|
+
const certificateCommonName = (0, args_1.requireArg)(args, '--certificate-common-name');
|
|
41
|
+
return await generateCodeSigningAsync((0, args_1.getProjectRoot)(args), {
|
|
42
|
+
certificateValidityDurationYears,
|
|
43
|
+
keyOutput,
|
|
44
|
+
certificateOutput,
|
|
45
|
+
certificateCommonName,
|
|
46
|
+
});
|
|
47
|
+
};
|
|
48
|
+
exports.generateCodeSigning = generateCodeSigning;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
type Options = {
|
|
2
|
+
certificateValidityDurationYears: number;
|
|
3
|
+
keyOutput: string;
|
|
4
|
+
certificateOutput: string;
|
|
5
|
+
certificateCommonName: string;
|
|
6
|
+
};
|
|
7
|
+
export declare function generateCodeSigningAsync(projectRoot: string, { certificateValidityDurationYears, keyOutput, certificateOutput, certificateCommonName }: Options): Promise<void>;
|
|
8
|
+
export {};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.generateCodeSigningAsync = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const code_signing_certificates_1 = require("@expo/code-signing-certificates");
|
|
6
|
+
const assert_1 = tslib_1.__importDefault(require("assert"));
|
|
7
|
+
const fs_1 = require("fs");
|
|
8
|
+
const path_1 = tslib_1.__importDefault(require("path"));
|
|
9
|
+
const dir_1 = require("./utils/dir");
|
|
10
|
+
const log_1 = require("./utils/log");
|
|
11
|
+
async function generateCodeSigningAsync(projectRoot, { certificateValidityDurationYears, keyOutput, certificateOutput, certificateCommonName }) {
|
|
12
|
+
const validityDurationYears = Math.floor(certificateValidityDurationYears);
|
|
13
|
+
const certificateOutputDir = path_1.default.resolve(projectRoot, certificateOutput);
|
|
14
|
+
const keyOutputDir = path_1.default.resolve(projectRoot, keyOutput);
|
|
15
|
+
await Promise.all([(0, dir_1.ensureDirAsync)(certificateOutputDir), (0, dir_1.ensureDirAsync)(keyOutputDir)]);
|
|
16
|
+
const [certificateOutputDirContents, keyOutputDirContents] = await Promise.all([
|
|
17
|
+
fs_1.promises.readdir(certificateOutputDir),
|
|
18
|
+
fs_1.promises.readdir(keyOutputDir),
|
|
19
|
+
]);
|
|
20
|
+
(0, assert_1.default)(certificateOutputDirContents.length === 0, 'Certificate output directory must be empty');
|
|
21
|
+
(0, assert_1.default)(keyOutputDirContents.length === 0, 'Key output directory must be empty');
|
|
22
|
+
const keyPair = (0, code_signing_certificates_1.generateKeyPair)();
|
|
23
|
+
const validityNotBefore = new Date();
|
|
24
|
+
const validityNotAfter = new Date();
|
|
25
|
+
validityNotAfter.setFullYear(validityNotAfter.getFullYear() + validityDurationYears);
|
|
26
|
+
const certificate = (0, code_signing_certificates_1.generateSelfSignedCodeSigningCertificate)({
|
|
27
|
+
keyPair,
|
|
28
|
+
validityNotBefore,
|
|
29
|
+
validityNotAfter,
|
|
30
|
+
commonName: certificateCommonName,
|
|
31
|
+
});
|
|
32
|
+
const keyPairPEM = (0, code_signing_certificates_1.convertKeyPairToPEM)(keyPair);
|
|
33
|
+
const certificatePEM = (0, code_signing_certificates_1.convertCertificateToCertificatePEM)(certificate);
|
|
34
|
+
await Promise.all([
|
|
35
|
+
fs_1.promises.writeFile(path_1.default.join(keyOutputDir, 'public-key.pem'), keyPairPEM.publicKeyPEM),
|
|
36
|
+
fs_1.promises.writeFile(path_1.default.join(keyOutputDir, 'private-key.pem'), keyPairPEM.privateKeyPEM),
|
|
37
|
+
fs_1.promises.writeFile(path_1.default.join(certificateOutputDir, 'certificate.pem'), certificatePEM),
|
|
38
|
+
]);
|
|
39
|
+
(0, log_1.log)(`Generated public and private keys output in ${keyOutputDir}. Remember to add them to .gitignore or to encrypt them. (e.g. with git-crypt)`);
|
|
40
|
+
(0, log_1.log)(`Generated code signing certificate output in ${certificateOutputDir}.`);
|
|
41
|
+
(0, log_1.log)(`To automatically configure this project for code signing, run \`yarn expo-updates codesigning:configure --certificate-input-directory=${certificateOutput} --key-input-directory=${keyOutput}\`.`);
|
|
42
|
+
}
|
|
43
|
+
exports.generateCodeSigningAsync = generateCodeSigningAsync;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import arg from 'arg';
|
|
2
|
+
/**
|
|
3
|
+
* Parse the first argument as a project directory.
|
|
4
|
+
*
|
|
5
|
+
* @returns valid project directory.
|
|
6
|
+
*/
|
|
7
|
+
export declare function getProjectRoot(args: arg.Result<arg.Spec>): string;
|
|
8
|
+
/**
|
|
9
|
+
* Parse args and assert unknown options.
|
|
10
|
+
*
|
|
11
|
+
* @param schema the `args` schema for parsing the command line arguments.
|
|
12
|
+
* @param argv extra strings
|
|
13
|
+
* @returns processed args object.
|
|
14
|
+
*/
|
|
15
|
+
export declare function assertArgs(schema: arg.Spec, argv: string[]): arg.Result<arg.Spec>;
|
|
16
|
+
export declare function requireArg(args: arg.Result<arg.Spec>, name: any): any;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.requireArg = exports.assertArgs = exports.getProjectRoot = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
// Common utilities for interacting with `args` library.
|
|
6
|
+
// These functions should be used by every command.
|
|
7
|
+
const arg_1 = tslib_1.__importDefault(require("arg"));
|
|
8
|
+
const fs_1 = require("fs");
|
|
9
|
+
const path_1 = require("path");
|
|
10
|
+
const Log = tslib_1.__importStar(require("./log"));
|
|
11
|
+
/**
|
|
12
|
+
* Parse the first argument as a project directory.
|
|
13
|
+
*
|
|
14
|
+
* @returns valid project directory.
|
|
15
|
+
*/
|
|
16
|
+
function getProjectRoot(args) {
|
|
17
|
+
const projectRoot = (0, path_1.resolve)(args._[0] || '.');
|
|
18
|
+
if (!(0, fs_1.existsSync)(projectRoot)) {
|
|
19
|
+
Log.exit(`Invalid project root: ${projectRoot}`);
|
|
20
|
+
}
|
|
21
|
+
return projectRoot;
|
|
22
|
+
}
|
|
23
|
+
exports.getProjectRoot = getProjectRoot;
|
|
24
|
+
/**
|
|
25
|
+
* Parse args and assert unknown options.
|
|
26
|
+
*
|
|
27
|
+
* @param schema the `args` schema for parsing the command line arguments.
|
|
28
|
+
* @param argv extra strings
|
|
29
|
+
* @returns processed args object.
|
|
30
|
+
*/
|
|
31
|
+
function assertArgs(schema, argv) {
|
|
32
|
+
try {
|
|
33
|
+
return (0, arg_1.default)(schema, { argv });
|
|
34
|
+
}
|
|
35
|
+
catch (error) {
|
|
36
|
+
// Ensure unknown options are handled the same way.
|
|
37
|
+
if (error.code === 'ARG_UNKNOWN_OPTION') {
|
|
38
|
+
Log.exit(error.message, 1);
|
|
39
|
+
}
|
|
40
|
+
// Otherwise rethrow the error.
|
|
41
|
+
throw error;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
exports.assertArgs = assertArgs;
|
|
45
|
+
function requireArg(args, name) {
|
|
46
|
+
const value = args[name];
|
|
47
|
+
if (value === undefined || value === null) {
|
|
48
|
+
Log.exit(`${name} must be provided`, 1);
|
|
49
|
+
}
|
|
50
|
+
return value;
|
|
51
|
+
}
|
|
52
|
+
exports.requireArg = requireArg;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function ensureDirAsync(path: string): Promise<string | undefined>;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ensureDirAsync = void 0;
|
|
4
|
+
const fs_1 = require("fs");
|
|
5
|
+
function ensureDirAsync(path) {
|
|
6
|
+
return fs_1.promises.mkdir(path, { recursive: true });
|
|
7
|
+
}
|
|
8
|
+
exports.ensureDirAsync = ensureDirAsync;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export declare function time(label?: string): void;
|
|
2
|
+
export declare function timeEnd(label?: string): void;
|
|
3
|
+
export declare function error(...message: string[]): void;
|
|
4
|
+
export declare function warn(...message: string[]): void;
|
|
5
|
+
export declare function log(...message: string[]): void;
|
|
6
|
+
/** Log a message and exit the current process. If the `code` is non-zero then `console.error` will be used instead of `console.log`. */
|
|
7
|
+
export declare function exit(message: string, code?: number): never;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.exit = exports.log = exports.warn = exports.error = exports.timeEnd = exports.time = void 0;
|
|
4
|
+
function time(label) {
|
|
5
|
+
console.time(label);
|
|
6
|
+
}
|
|
7
|
+
exports.time = time;
|
|
8
|
+
function timeEnd(label) {
|
|
9
|
+
console.timeEnd(label);
|
|
10
|
+
}
|
|
11
|
+
exports.timeEnd = timeEnd;
|
|
12
|
+
function error(...message) {
|
|
13
|
+
console.error(...message);
|
|
14
|
+
}
|
|
15
|
+
exports.error = error;
|
|
16
|
+
function warn(...message) {
|
|
17
|
+
console.warn(...message);
|
|
18
|
+
}
|
|
19
|
+
exports.warn = warn;
|
|
20
|
+
function log(...message) {
|
|
21
|
+
console.log(...message);
|
|
22
|
+
}
|
|
23
|
+
exports.log = log;
|
|
24
|
+
/** Log a message and exit the current process. If the `code` is non-zero then `console.error` will be used instead of `console.log`. */
|
|
25
|
+
function exit(message, code = 1) {
|
|
26
|
+
if (code === 0) {
|
|
27
|
+
log(message);
|
|
28
|
+
}
|
|
29
|
+
else {
|
|
30
|
+
error(message);
|
|
31
|
+
}
|
|
32
|
+
process.exit(code);
|
|
33
|
+
}
|
|
34
|
+
exports.exit = exit;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.attemptModification = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const config_1 = require("@expo/config");
|
|
6
|
+
const chalk_1 = tslib_1.__importDefault(require("chalk"));
|
|
7
|
+
const Log = tslib_1.__importStar(require("./log"));
|
|
8
|
+
/** Wraps `[@expo/config] modifyConfigAsync()` and adds additional logging. */
|
|
9
|
+
async function attemptModification(projectRoot, edits, exactEdits) {
|
|
10
|
+
const modification = await (0, config_1.modifyConfigAsync)(projectRoot, edits, {
|
|
11
|
+
skipSDKVersionRequirement: true,
|
|
12
|
+
});
|
|
13
|
+
if (modification.type === 'success') {
|
|
14
|
+
Log.log();
|
|
15
|
+
}
|
|
16
|
+
else {
|
|
17
|
+
warnAboutConfigAndThrow(modification.type, modification.message, exactEdits);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
exports.attemptModification = attemptModification;
|
|
21
|
+
function logNoConfig() {
|
|
22
|
+
Log.log(chalk_1.default.yellow(`No Expo config was found. Please create an Expo config (${chalk_1.default.bold `app.json`} or ${chalk_1.default.bold `app.config.js`}) in your project root.`));
|
|
23
|
+
}
|
|
24
|
+
function warnAboutConfigAndThrow(type, message, edits) {
|
|
25
|
+
Log.log();
|
|
26
|
+
if (type === 'warn') {
|
|
27
|
+
// The project is using a dynamic config, give the user a helpful log and bail out.
|
|
28
|
+
Log.log(chalk_1.default.yellow(message));
|
|
29
|
+
}
|
|
30
|
+
else {
|
|
31
|
+
logNoConfig();
|
|
32
|
+
}
|
|
33
|
+
notifyAboutManualConfigEdits(edits);
|
|
34
|
+
throw new Error();
|
|
35
|
+
}
|
|
36
|
+
function notifyAboutManualConfigEdits(edits) {
|
|
37
|
+
Log.log(chalk_1.default.cyan(`Please add the following to your Expo config`));
|
|
38
|
+
Log.log();
|
|
39
|
+
Log.log(JSON.stringify(edits, null, 2));
|
|
40
|
+
Log.log();
|
|
41
|
+
}
|
package/e2e/setup/project.ts
CHANGED
|
@@ -312,10 +312,10 @@ async function preparePackageJson(
|
|
|
312
312
|
|
|
313
313
|
const extraDevDependencies = configureE2E
|
|
314
314
|
? {
|
|
315
|
-
'@config-plugins/detox': '^
|
|
315
|
+
'@config-plugins/detox': '^9.0.0',
|
|
316
316
|
'@types/express': '^4.17.17',
|
|
317
317
|
'@types/jest': '^29.4.0',
|
|
318
|
-
detox: '^20.
|
|
318
|
+
detox: '^20.33.0',
|
|
319
319
|
express: '^4.18.2',
|
|
320
320
|
'form-data': '^4.0.0',
|
|
321
321
|
jest: '^29.3.1',
|
|
@@ -458,7 +458,15 @@ function transformAppJsonForE2E(
|
|
|
458
458
|
runtimeVersion: string,
|
|
459
459
|
isTV: boolean
|
|
460
460
|
) {
|
|
461
|
-
const plugins: any[] = [
|
|
461
|
+
const plugins: any[] = [
|
|
462
|
+
'expo-updates',
|
|
463
|
+
[
|
|
464
|
+
'@config-plugins/detox',
|
|
465
|
+
{
|
|
466
|
+
subdomains: Array.from(new Set(['10.0.2.2', 'localhost', process.env.UPDATES_HOST])),
|
|
467
|
+
},
|
|
468
|
+
],
|
|
469
|
+
];
|
|
462
470
|
if (isTV) {
|
|
463
471
|
plugins.push([
|
|
464
472
|
'@react-native-tvos/config-tv',
|
|
@@ -574,7 +582,15 @@ export function transformAppJsonForUpdatesDisabledE2E(
|
|
|
574
582
|
projectName: string,
|
|
575
583
|
runtimeVersion: string
|
|
576
584
|
) {
|
|
577
|
-
const plugins: any[] = [
|
|
585
|
+
const plugins: any[] = [
|
|
586
|
+
'expo-updates',
|
|
587
|
+
[
|
|
588
|
+
'@config-plugins/detox',
|
|
589
|
+
{
|
|
590
|
+
subdomains: Array.from(new Set(['10.0.2.2', 'localhost', process.env.UPDATES_HOST])),
|
|
591
|
+
},
|
|
592
|
+
],
|
|
593
|
+
];
|
|
578
594
|
return {
|
|
579
595
|
...appJson,
|
|
580
596
|
expo: {
|
|
@@ -768,7 +784,7 @@ export async function initAsync(
|
|
|
768
784
|
// enable proguard on Android
|
|
769
785
|
await fs.appendFile(
|
|
770
786
|
path.join(projectRoot, 'android', 'gradle.properties'),
|
|
771
|
-
'\nandroid.enableProguardInReleaseBuilds=true\
|
|
787
|
+
'\nandroid.enableProguardInReleaseBuilds=true\nEXPO_UPDATES_NATIVE_DEBUG=true',
|
|
772
788
|
'utf-8'
|
|
773
789
|
);
|
|
774
790
|
|
|
@@ -97,7 +97,7 @@ open class AppLoader: NSObject {
|
|
|
97
97
|
preconditionFailure("Must override in concrete class")
|
|
98
98
|
}
|
|
99
99
|
|
|
100
|
-
open func downloadAsset(_ asset: UpdateAsset) {
|
|
100
|
+
open func downloadAsset(_ asset: UpdateAsset, extraHeaders: [String: Any]) {
|
|
101
101
|
preconditionFailure("Must override in concrete class")
|
|
102
102
|
}
|
|
103
103
|
|
|
@@ -202,6 +202,13 @@ open class AppLoader: NSObject {
|
|
|
202
202
|
if let assets = updateManifest.assets(),
|
|
203
203
|
!assets.isEmpty {
|
|
204
204
|
self.assetsToLoad = assets
|
|
205
|
+
let embeddedUpdate = EmbeddedAppLoader.embeddedManifest(withConfig: self.config, database: self.database)
|
|
206
|
+
let extraHeaders = FileDownloader.extraHeadersForRemoteAssetRequest(
|
|
207
|
+
launchedUpdate: self.launchedUpdate,
|
|
208
|
+
embeddedUpdate: embeddedUpdate,
|
|
209
|
+
requestedUpdate: updateManifest
|
|
210
|
+
)
|
|
211
|
+
|
|
205
212
|
for asset in assets {
|
|
206
213
|
// before downloading, check to see if we already have this asset in the database
|
|
207
214
|
let matchingDbEntry = try? self.database.asset(withKey: asset.key)
|
|
@@ -226,11 +233,11 @@ open class AppLoader: NSObject {
|
|
|
226
233
|
self.handleAssetDownloadAlreadyExists(asset)
|
|
227
234
|
}
|
|
228
235
|
} else {
|
|
229
|
-
self.downloadAsset(asset)
|
|
236
|
+
self.downloadAsset(asset, extraHeaders: extraHeaders)
|
|
230
237
|
}
|
|
231
238
|
}
|
|
232
239
|
} else {
|
|
233
|
-
self.downloadAsset(asset)
|
|
240
|
+
self.downloadAsset(asset, extraHeaders: extraHeaders)
|
|
234
241
|
}
|
|
235
242
|
}
|
|
236
243
|
} else {
|
|
@@ -119,7 +119,7 @@ public final class EmbeddedAppLoader: AppLoader {
|
|
|
119
119
|
))
|
|
120
120
|
}
|
|
121
121
|
|
|
122
|
-
override public func downloadAsset(_ asset: UpdateAsset) {
|
|
122
|
+
override public func downloadAsset(_ asset: UpdateAsset, extraHeaders: [String: Any]) {
|
|
123
123
|
FileDownloader.assetFilesQueue.async {
|
|
124
124
|
self.handleAssetDownloadAlreadyExists(asset)
|
|
125
125
|
}
|
|
@@ -221,6 +221,30 @@ public final class FileDownloader {
|
|
|
221
221
|
return extraHeaders
|
|
222
222
|
}
|
|
223
223
|
|
|
224
|
+
/**
|
|
225
|
+
* Get extra headers to pass into `downloadAsset:`
|
|
226
|
+
*/
|
|
227
|
+
static func extraHeadersForRemoteAssetRequest(
|
|
228
|
+
launchedUpdate: Update?,
|
|
229
|
+
embeddedUpdate: Update?,
|
|
230
|
+
requestedUpdate: Update?
|
|
231
|
+
) -> [String: Any] {
|
|
232
|
+
var extraHeaders: [String: Any] = [:]
|
|
233
|
+
if let launchedUpdate {
|
|
234
|
+
extraHeaders["Expo-Current-Update-ID"] = launchedUpdate.updateId.uuidString.lowercased()
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
if let embeddedUpdate {
|
|
238
|
+
extraHeaders["Expo-Embedded-Update-ID"] = embeddedUpdate.updateId.uuidString.lowercased()
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
if let requestedUpdate {
|
|
242
|
+
extraHeaders["Expo-Requested-Update-ID"] = requestedUpdate.updateId.uuidString.lowercased()
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
return extraHeaders
|
|
246
|
+
}
|
|
247
|
+
|
|
224
248
|
private static func setHTTPHeaderFields(_ headers: [String: Any?]?, onRequest request: inout URLRequest) {
|
|
225
249
|
guard let headers = headers else {
|
|
226
250
|
return
|
|
@@ -84,7 +84,7 @@ public final class RemoteAppLoader: AppLoader {
|
|
|
84
84
|
}
|
|
85
85
|
}
|
|
86
86
|
|
|
87
|
-
override public func downloadAsset(_ asset: UpdateAsset) {
|
|
87
|
+
override public func downloadAsset(_ asset: UpdateAsset, extraHeaders: [String: Any]) {
|
|
88
88
|
let urlOnDisk = self.directory.appendingPathComponent(asset.filename)
|
|
89
89
|
|
|
90
90
|
FileDownloader.assetFilesQueue.async {
|
|
@@ -101,12 +101,11 @@ public final class RemoteAppLoader: AppLoader {
|
|
|
101
101
|
)
|
|
102
102
|
return
|
|
103
103
|
}
|
|
104
|
-
|
|
105
104
|
self.downloader.downloadAsset(
|
|
106
105
|
fromURL: assetUrl,
|
|
107
106
|
verifyingHash: asset.expectedHash,
|
|
108
107
|
toPath: urlOnDisk.path,
|
|
109
|
-
extraHeaders: asset.extraRequestHeaders ?? [:]
|
|
108
|
+
extraHeaders: extraHeaders.merging(asset.extraRequestHeaders ?? [:]) { current, _ in current }
|
|
110
109
|
) { data, response, _ in
|
|
111
110
|
DispatchQueue.global().async {
|
|
112
111
|
self.handleAssetDownload(withData: data, response: response, asset: asset)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "expo-updates",
|
|
3
|
-
"version": "0.27.
|
|
3
|
+
"version": "0.27.2",
|
|
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",
|
|
@@ -38,13 +38,13 @@
|
|
|
38
38
|
},
|
|
39
39
|
"dependencies": {
|
|
40
40
|
"@expo/code-signing-certificates": "0.0.5",
|
|
41
|
-
"@expo/config": "~10.0.
|
|
42
|
-
"@expo/config-plugins": "~9.0.
|
|
41
|
+
"@expo/config": "~10.0.10",
|
|
42
|
+
"@expo/config-plugins": "~9.0.16",
|
|
43
43
|
"@expo/spawn-async": "^1.7.2",
|
|
44
44
|
"arg": "4.1.0",
|
|
45
45
|
"chalk": "^4.1.2",
|
|
46
|
-
"expo-eas-client": "~0.13.
|
|
47
|
-
"expo-manifests": "~0.15.
|
|
46
|
+
"expo-eas-client": "~0.13.3",
|
|
47
|
+
"expo-manifests": "~0.15.7",
|
|
48
48
|
"expo-structured-headers": "~4.0.0",
|
|
49
49
|
"expo-updates-interface": "~1.0.0",
|
|
50
50
|
"fast-glob": "^3.3.2",
|
|
@@ -56,7 +56,7 @@
|
|
|
56
56
|
"@types/jest": "^29.2.1",
|
|
57
57
|
"@types/node": "^18.19.34",
|
|
58
58
|
"@types/node-forge": "^1.0.0",
|
|
59
|
-
"expo-module-scripts": "^4.0.
|
|
59
|
+
"expo-module-scripts": "^4.0.4",
|
|
60
60
|
"express": "^4.21.1",
|
|
61
61
|
"form-data": "^4.0.0",
|
|
62
62
|
"fs-extra": "~8.1.0",
|
|
@@ -67,5 +67,5 @@
|
|
|
67
67
|
"expo": "*",
|
|
68
68
|
"react": "*"
|
|
69
69
|
},
|
|
70
|
-
"gitHead": "
|
|
70
|
+
"gitHead": "4f3b834d475d0de0d26cc6b1d39019c8bf11aca6"
|
|
71
71
|
}
|