@appzung/react-native-code-push 8.3.2 → 10.0.0-rc1
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/CodePush.js +22 -19
- package/CodePush.podspec +3 -3
- package/LICENSE.md +1 -1
- package/README.md +159 -296
- package/android/app/.gradle/config.properties +2 -0
- package/android/app/build.gradle +2 -0
- package/android/app/local.properties +8 -0
- package/android/app/src/main/AndroidManifest.xml +1 -2
- package/android/app/src/main/java/com/microsoft/codepush/react/CodePush.java +27 -21
- package/android/app/src/main/java/com/microsoft/codepush/react/CodePushBuilder.java +5 -5
- package/android/app/src/main/java/com/microsoft/codepush/react/CodePushConstants.java +1 -1
- package/android/app/src/main/java/com/microsoft/codepush/react/CodePushDialog.java +1 -1
- package/android/app/src/main/java/com/microsoft/codepush/react/CodePushInstallMode.java +2 -2
- package/android/app/src/main/java/com/microsoft/codepush/react/CodePushInvalidPublicKeyException.java +2 -2
- package/android/app/src/main/java/com/microsoft/codepush/react/CodePushInvalidUpdateException.java +1 -1
- package/android/app/src/main/java/com/microsoft/codepush/react/CodePushMalformedDataException.java +2 -2
- package/android/app/src/main/java/com/microsoft/codepush/react/CodePushNativeModule.java +5 -5
- package/android/app/src/main/java/com/microsoft/codepush/react/CodePushNotInitializedException.java +2 -2
- package/android/app/src/main/java/com/microsoft/codepush/react/CodePushTelemetryManager.java +14 -14
- package/android/app/src/main/java/com/microsoft/codepush/react/CodePushUnknownException.java +2 -2
- package/android/app/src/main/java/com/microsoft/codepush/react/CodePushUpdateManager.java +1 -1
- package/android/app/src/main/java/com/microsoft/codepush/react/CodePushUpdateState.java +2 -2
- package/android/app/src/main/java/com/microsoft/codepush/react/CodePushUpdateUtils.java +16 -9
- package/android/app/src/main/java/com/microsoft/codepush/react/CodePushUtils.java +1 -1
- package/android/app/src/main/java/com/microsoft/codepush/react/DownloadProgress.java +1 -1
- package/android/app/src/main/java/com/microsoft/codepush/react/DownloadProgressCallback.java +1 -1
- package/android/app/src/main/java/com/microsoft/codepush/react/FileUtils.java +1 -1
- package/android/app/src/main/java/com/microsoft/codepush/react/ReactInstanceHolder.java +1 -1
- package/android/app/src/main/java/com/microsoft/codepush/react/SettingsManager.java +1 -1
- package/android/app/src/main/java/com/microsoft/codepush/react/TLSSocketFactory.java +1 -1
- package/android/build.gradle +3 -0
- package/android/codepush.gradle +3 -3
- package/docs/advanced-usage.md +56 -0
- package/docs/api-android.md +12 -75
- package/docs/api-ios.md +5 -17
- package/docs/api-js.md +18 -55
- package/docs/setup-android.md +15 -397
- package/docs/setup-ios.md +24 -198
- package/docs/setup-windows.md +7 -74
- package/ios/CodePush/CodePush.h +4 -4
- package/ios/CodePush/CodePush.m +10 -9
- package/ios/CodePush/CodePushConfig.m +14 -11
- package/ios/CodePush/CodePushPackage.m +60 -60
- package/ios/CodePush/CodePushTelemetryManager.m +13 -13
- package/ios/CodePush.xcodeproj/project.pbxproj +24 -470
- package/ios/PrivacyInfo.xcprivacy +31 -0
- package/package.json +27 -24
- package/react-native.config.js +1 -1
- package/scripts/generateBundledResourcesHash.js +1 -1
- package/scripts/getFilesInFolder.js +1 -1
- package/scripts/recordFilesBeforeBundleCommand.js +1 -1
- package/typings/react-native-code-push.d.ts +22 -22
- package/windows/CodePush/CodePushConfig.cpp +3 -3
- package/windows/CodePush/CodePushConfig.h +3 -3
- package/windows/CodePush/CodePushNativeModule.cpp +27 -27
- package/windows/CodePush/CodePushNativeModule.h +4 -4
- package/windows/CodePush/CodePushTelemetryManager.cpp +12 -12
- package/windows/CodePush/CodePushTelemetryManager.h +1 -1
- package/.azurepipelines/build-rn-code-push-1es.yml +0 -104
- package/.azurepipelines/test-rn-code-push.yml +0 -94
- package/.config/CredScanSuppressions.json +0 -14
- package/SECURITY.md +0 -41
- package/docs/multi-deployment-testing-android.md +0 -148
- package/docs/multi-deployment-testing-ios.md +0 -59
- package/ios/CodePush/Base64/Base64/MF_Base64Additions.h +0 -34
- package/ios/CodePush/Base64/Base64/MF_Base64Additions.m +0 -252
- package/ios/CodePush/Base64/README.md +0 -47
- package/ios/CodePush/JWT/Core/Algorithms/Base/JWTAlgorithm.h +0 -69
- package/ios/CodePush/JWT/Core/Algorithms/Base/JWTAlgorithmFactory.h +0 -16
- package/ios/CodePush/JWT/Core/Algorithms/Base/JWTAlgorithmFactory.m +0 -51
- package/ios/CodePush/JWT/Core/Algorithms/Base/JWTAlgorithmNone.h +0 -15
- package/ios/CodePush/JWT/Core/Algorithms/Base/JWTAlgorithmNone.m +0 -55
- package/ios/CodePush/JWT/Core/Algorithms/ESFamily/JWTAlgorithmESBase.h +0 -24
- package/ios/CodePush/JWT/Core/Algorithms/ESFamily/JWTAlgorithmESBase.m +0 -41
- package/ios/CodePush/JWT/Core/Algorithms/HSFamily/JWTAlgorithmHSBase.h +0 -28
- package/ios/CodePush/JWT/Core/Algorithms/HSFamily/JWTAlgorithmHSBase.m +0 -205
- package/ios/CodePush/JWT/Core/Algorithms/Holders/JWTAlgorithmDataHolder.h +0 -103
- package/ios/CodePush/JWT/Core/Algorithms/Holders/JWTAlgorithmDataHolder.m +0 -322
- package/ios/CodePush/JWT/Core/Algorithms/Holders/JWTAlgorithmDataHolderChain.h +0 -37
- package/ios/CodePush/JWT/Core/Algorithms/Holders/JWTAlgorithmDataHolderChain.m +0 -145
- package/ios/CodePush/JWT/Core/Algorithms/RSFamily/JWTAlgorithmRSBase.h +0 -35
- package/ios/CodePush/JWT/Core/Algorithms/RSFamily/JWTAlgorithmRSBase.m +0 -551
- package/ios/CodePush/JWT/Core/Algorithms/RSFamily/JWTRSAlgorithm.h +0 -23
- package/ios/CodePush/JWT/Core/Algorithms/RSFamily/RSKeys/JWTCryptoKey.h +0 -43
- package/ios/CodePush/JWT/Core/Algorithms/RSFamily/RSKeys/JWTCryptoKey.m +0 -230
- package/ios/CodePush/JWT/Core/Algorithms/RSFamily/RSKeys/JWTCryptoKeyExtractor.h +0 -31
- package/ios/CodePush/JWT/Core/Algorithms/RSFamily/RSKeys/JWTCryptoKeyExtractor.m +0 -113
- package/ios/CodePush/JWT/Core/Algorithms/RSFamily/RSKeys/JWTCryptoSecurity.h +0 -38
- package/ios/CodePush/JWT/Core/Algorithms/RSFamily/RSKeys/JWTCryptoSecurity.m +0 -500
- package/ios/CodePush/JWT/Core/ClaimSet/JWTClaim.h +0 -18
- package/ios/CodePush/JWT/Core/ClaimSet/JWTClaim.m +0 -214
- package/ios/CodePush/JWT/Core/ClaimSet/JWTClaimsSet.h +0 -23
- package/ios/CodePush/JWT/Core/ClaimSet/JWTClaimsSet.m +0 -29
- package/ios/CodePush/JWT/Core/ClaimSet/JWTClaimsSetSerializer.h +0 -19
- package/ios/CodePush/JWT/Core/ClaimSet/JWTClaimsSetSerializer.m +0 -68
- package/ios/CodePush/JWT/Core/ClaimSet/JWTClaimsSetVerifier.h +0 -18
- package/ios/CodePush/JWT/Core/ClaimSet/JWTClaimsSetVerifier.m +0 -72
- package/ios/CodePush/JWT/Core/Coding/JWTCoding+ResultTypes.h +0 -67
- package/ios/CodePush/JWT/Core/Coding/JWTCoding+ResultTypes.m +0 -111
- package/ios/CodePush/JWT/Core/Coding/JWTCoding+VersionOne.h +0 -119
- package/ios/CodePush/JWT/Core/Coding/JWTCoding+VersionOne.m +0 -307
- package/ios/CodePush/JWT/Core/Coding/JWTCoding+VersionThree.h +0 -94
- package/ios/CodePush/JWT/Core/Coding/JWTCoding+VersionThree.m +0 -619
- package/ios/CodePush/JWT/Core/Coding/JWTCoding+VersionTwo.h +0 -164
- package/ios/CodePush/JWT/Core/Coding/JWTCoding+VersionTwo.m +0 -514
- package/ios/CodePush/JWT/Core/Coding/JWTCoding.h +0 -24
- package/ios/CodePush/JWT/Core/Coding/JWTCoding.m +0 -11
- package/ios/CodePush/JWT/Core/FrameworkSupplement/JWT.h +0 -52
- package/ios/CodePush/JWT/Core/FrameworkSupplement/Map.modulemap +0 -5
- package/ios/CodePush/JWT/Core/Supplement/JWTBase64Coder.h +0 -28
- package/ios/CodePush/JWT/Core/Supplement/JWTBase64Coder.m +0 -70
- package/ios/CodePush/JWT/Core/Supplement/JWTDeprecations.h +0 -22
- package/ios/CodePush/JWT/Core/Supplement/JWTErrorDescription.h +0 -34
- package/ios/CodePush/JWT/Core/Supplement/JWTErrorDescription.m +0 -73
- package/ios/CodePush/JWT/LICENSE +0 -19
- package/ios/CodePush/JWT/README.md +0 -489
- package/ios/CodePush/SSZipArchive/Common.h +0 -81
- package/ios/CodePush/SSZipArchive/README.md +0 -1
- package/ios/CodePush/SSZipArchive/SSZipArchive.h +0 -76
- package/ios/CodePush/SSZipArchive/SSZipArchive.m +0 -691
- package/ios/CodePush/SSZipArchive/aes/aes.h +0 -198
- package/ios/CodePush/SSZipArchive/aes/aes_via_ace.h +0 -541
- package/ios/CodePush/SSZipArchive/aes/aescrypt.c +0 -294
- package/ios/CodePush/SSZipArchive/aes/aeskey.c +0 -548
- package/ios/CodePush/SSZipArchive/aes/aesopt.h +0 -739
- package/ios/CodePush/SSZipArchive/aes/aestab.c +0 -391
- package/ios/CodePush/SSZipArchive/aes/aestab.h +0 -173
- package/ios/CodePush/SSZipArchive/aes/brg_endian.h +0 -126
- package/ios/CodePush/SSZipArchive/aes/brg_types.h +0 -219
- package/ios/CodePush/SSZipArchive/aes/entropy.c +0 -54
- package/ios/CodePush/SSZipArchive/aes/entropy.h +0 -16
- package/ios/CodePush/SSZipArchive/aes/fileenc.c +0 -144
- package/ios/CodePush/SSZipArchive/aes/fileenc.h +0 -121
- package/ios/CodePush/SSZipArchive/aes/hmac.c +0 -145
- package/ios/CodePush/SSZipArchive/aes/hmac.h +0 -103
- package/ios/CodePush/SSZipArchive/aes/prng.c +0 -155
- package/ios/CodePush/SSZipArchive/aes/prng.h +0 -82
- package/ios/CodePush/SSZipArchive/aes/pwd2key.c +0 -103
- package/ios/CodePush/SSZipArchive/aes/pwd2key.h +0 -57
- package/ios/CodePush/SSZipArchive/aes/sha1.c +0 -258
- package/ios/CodePush/SSZipArchive/aes/sha1.h +0 -73
- package/ios/CodePush/SSZipArchive/minizip/crypt.h +0 -130
- package/ios/CodePush/SSZipArchive/minizip/ioapi.c +0 -369
- package/ios/CodePush/SSZipArchive/minizip/ioapi.h +0 -175
- package/ios/CodePush/SSZipArchive/minizip/mztools.c +0 -284
- package/ios/CodePush/SSZipArchive/minizip/mztools.h +0 -31
- package/ios/CodePush/SSZipArchive/minizip/unzip.c +0 -1839
- package/ios/CodePush/SSZipArchive/minizip/unzip.h +0 -248
- package/ios/CodePush/SSZipArchive/minizip/zip.c +0 -1910
- package/ios/CodePush/SSZipArchive/minizip/zip.h +0 -202
- package/scripts/postlink/android/postlink.js +0 -87
- package/scripts/postlink/ios/postlink.js +0 -116
- package/scripts/postlink/run.js +0 -11
- package/scripts/postunlink/android/postunlink.js +0 -74
- package/scripts/postunlink/ios/postunlink.js +0 -87
- package/scripts/postunlink/run.js +0 -11
- package/scripts/tools/linkToolsAndroid.js +0 -57
- package/scripts/tools/linkToolsIos.js +0 -130
- package/windows-legacy/CodePush/CodePush.csproj +0 -128
- package/windows-legacy/CodePush/CodePushUtils.cs +0 -47
- package/windows-legacy/CodePush/FileUtils.cs +0 -40
- package/windows-legacy/CodePush/Properties/AssemblyInfo.cs +0 -29
- package/windows-legacy/CodePush/Properties/CodePush.rd.xml +0 -33
- package/windows-legacy/CodePush/UpdateManager.cs +0 -305
- package/windows-legacy/CodePush/UpdateUtils.cs +0 -46
- package/windows-legacy/CodePush.Net46/Adapters/Http/HttpProgress.cs +0 -28
- package/windows-legacy/CodePush.Net46/Adapters/Storage/ApplicationDataContainer.cs +0 -106
- package/windows-legacy/CodePush.Net46/CodePush.Net46.csproj +0 -103
- package/windows-legacy/CodePush.Net46/CodePushUtils.cs +0 -158
- package/windows-legacy/CodePush.Net46/FileUtils.cs +0 -55
- package/windows-legacy/CodePush.Net46/Properties/AssemblyInfo.cs +0 -36
- package/windows-legacy/CodePush.Net46/UpdateManager.cs +0 -330
- package/windows-legacy/CodePush.Net46/UpdateUtils.cs +0 -70
- package/windows-legacy/CodePush.Net46/packages.config +0 -5
- package/windows-legacy/CodePush.Net46.Test/ApplicationDataContainerTest.cs +0 -105
- package/windows-legacy/CodePush.Net46.Test/CodePush.Net46.Test.csproj +0 -137
- package/windows-legacy/CodePush.Net46.Test/Properties/AssemblyInfo.cs +0 -36
- package/windows-legacy/CodePush.Net46.Test/TelemetryManagerTest.cs +0 -117
- package/windows-legacy/CodePush.Net46.Test/app.config +0 -11
- package/windows-legacy/CodePush.Net46.Test/packages.config +0 -4
- package/windows-legacy/CodePush.Shared/CodePush.Shared.projitems +0 -22
- package/windows-legacy/CodePush.Shared/CodePush.Shared.shproj +0 -13
- package/windows-legacy/CodePush.Shared/CodePushConstants.cs +0 -35
- package/windows-legacy/CodePush.Shared/CodePushNativeModule.cs +0 -329
- package/windows-legacy/CodePush.Shared/CodePushReactPackage.cs +0 -235
- package/windows-legacy/CodePush.Shared/CodePushUtils.cs +0 -70
- package/windows-legacy/CodePush.Shared/InstallMode.cs +0 -9
- package/windows-legacy/CodePush.Shared/MinimumBackgroundListener.cs +0 -44
- package/windows-legacy/CodePush.Shared/SettingsManager.cs +0 -148
- package/windows-legacy/CodePush.Shared/TelemetryManager.cs +0 -250
- package/windows-legacy/CodePush.Shared/UpdateState.cs +0 -9
package/android/app/build.gradle
CHANGED
|
@@ -6,6 +6,8 @@ def DEFAULT_TARGET_SDK_VERSION = 26
|
|
|
6
6
|
def DEFAULT_MIN_SDK_VERSION = 16
|
|
7
7
|
|
|
8
8
|
android {
|
|
9
|
+
namespace "com.appzung.codepush.react"
|
|
10
|
+
|
|
9
11
|
compileSdkVersion rootProject.hasProperty('compileSdkVersion') ? rootProject.compileSdkVersion : DEFAULT_COMPILE_SDK_VERSION
|
|
10
12
|
buildToolsVersion rootProject.hasProperty('buildToolsVersion') ? rootProject.buildToolsVersion : DEFAULT_BUILD_TOOLS_VERSION
|
|
11
13
|
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
## This file must *NOT* be checked into Version Control Systems,
|
|
2
|
+
# as it contains information specific to your local configuration.
|
|
3
|
+
#
|
|
4
|
+
# Location of the SDK. This is only used by Gradle.
|
|
5
|
+
# For customization when using a Version Control System, please read the
|
|
6
|
+
# header note.
|
|
7
|
+
#Wed Jan 22 01:48:35 CET 2025
|
|
8
|
+
sdk.dir=/Users/louislagrange/Library/Android/sdk
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
package com.
|
|
1
|
+
package com.appzung.codepush.react;
|
|
2
2
|
|
|
3
3
|
import android.content.Context;
|
|
4
4
|
import android.content.pm.PackageInfo;
|
|
@@ -39,7 +39,7 @@ public class CodePush implements ReactPackage {
|
|
|
39
39
|
private SettingsManager mSettingsManager;
|
|
40
40
|
|
|
41
41
|
// Config properties.
|
|
42
|
-
private String
|
|
42
|
+
private String mReleaseChannelPublicId;
|
|
43
43
|
private static String mServerUrl = "https://codepush.appzung.com/";
|
|
44
44
|
|
|
45
45
|
private Context mContext;
|
|
@@ -50,20 +50,20 @@ public class CodePush implements ReactPackage {
|
|
|
50
50
|
private static ReactInstanceHolder mReactInstanceHolder;
|
|
51
51
|
private static CodePush mCurrentInstance;
|
|
52
52
|
|
|
53
|
-
public CodePush(String
|
|
54
|
-
this(
|
|
53
|
+
public CodePush(String releaseChannelPublicId, Context context) {
|
|
54
|
+
this(releaseChannelPublicId, context, false);
|
|
55
55
|
}
|
|
56
56
|
|
|
57
57
|
public static String getServiceUrl() {
|
|
58
58
|
return mServerUrl;
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
-
public CodePush(String
|
|
61
|
+
public CodePush(String releaseChannelPublicId, Context context, boolean isDebugMode) {
|
|
62
62
|
mContext = context.getApplicationContext();
|
|
63
63
|
|
|
64
64
|
mUpdateManager = new CodePushUpdateManager(context.getFilesDir().getAbsolutePath());
|
|
65
65
|
mTelemetryManager = new CodePushTelemetryManager(mContext);
|
|
66
|
-
|
|
66
|
+
mReleaseChannelPublicId = releaseChannelPublicId;
|
|
67
67
|
mIsDebugMode = isDebugMode;
|
|
68
68
|
mSettingsManager = new SettingsManager(mContext);
|
|
69
69
|
|
|
@@ -78,8 +78,11 @@ public class CodePush implements ReactPackage {
|
|
|
78
78
|
|
|
79
79
|
mCurrentInstance = this;
|
|
80
80
|
|
|
81
|
-
String publicKeyFromStrings = getCustomPropertyFromStringsIfExist("
|
|
82
|
-
if (publicKeyFromStrings != null)
|
|
81
|
+
String publicKeyFromStrings = getCustomPropertyFromStringsIfExist("SigningPublicKey");
|
|
82
|
+
if (publicKeyFromStrings != null) {
|
|
83
|
+
CodePushUtils.log("Executing CodePush with a signing public key.");
|
|
84
|
+
mPublicKey = publicKeyFromStrings;
|
|
85
|
+
}
|
|
83
86
|
|
|
84
87
|
String serverUrlFromStrings = getCustomPropertyFromStringsIfExist("ServerUrl");
|
|
85
88
|
if (serverUrlFromStrings != null) mServerUrl = serverUrlFromStrings;
|
|
@@ -88,19 +91,19 @@ public class CodePush implements ReactPackage {
|
|
|
88
91
|
initializeUpdateAfterRestart();
|
|
89
92
|
}
|
|
90
93
|
|
|
91
|
-
public CodePush(String
|
|
92
|
-
this(
|
|
94
|
+
public CodePush(String releaseChannelPublicId, Context context, boolean isDebugMode, String serverUrl) {
|
|
95
|
+
this(releaseChannelPublicId, context, isDebugMode);
|
|
93
96
|
mServerUrl = serverUrl;
|
|
94
97
|
}
|
|
95
98
|
|
|
96
|
-
public CodePush(String
|
|
97
|
-
this(
|
|
99
|
+
public CodePush(String releaseChannelPublicId, Context context, boolean isDebugMode, int publicKeyResourceDescriptor) {
|
|
100
|
+
this(releaseChannelPublicId, context, isDebugMode);
|
|
98
101
|
|
|
99
102
|
mPublicKey = getPublicKeyByResourceDescriptor(publicKeyResourceDescriptor);
|
|
100
103
|
}
|
|
101
104
|
|
|
102
|
-
public CodePush(String
|
|
103
|
-
this(
|
|
105
|
+
public CodePush(String releaseChannelPublicId, Context context, boolean isDebugMode, String serverUrl, Integer publicKeyResourceDescriptor) {
|
|
106
|
+
this(releaseChannelPublicId, context, isDebugMode);
|
|
104
107
|
|
|
105
108
|
if (publicKeyResourceDescriptor != null) {
|
|
106
109
|
mPublicKey = getPublicKeyByResourceDescriptor(publicKeyResourceDescriptor);
|
|
@@ -124,15 +127,18 @@ public class CodePush implements ReactPackage {
|
|
|
124
127
|
if (publicKey.isEmpty()) {
|
|
125
128
|
throw new CodePushInvalidPublicKeyException("Specified public key is empty");
|
|
126
129
|
}
|
|
130
|
+
|
|
131
|
+
CodePushUtils.log("Executing CodePush with a signing public key.");
|
|
132
|
+
|
|
127
133
|
return publicKey;
|
|
128
134
|
}
|
|
129
135
|
|
|
130
136
|
private String getCustomPropertyFromStringsIfExist(String propertyName) {
|
|
131
137
|
String property;
|
|
132
|
-
|
|
138
|
+
|
|
133
139
|
String packageName = mContext.getPackageName();
|
|
134
140
|
int resId = mContext.getResources().getIdentifier("CodePush" + propertyName, "string", packageName);
|
|
135
|
-
|
|
141
|
+
|
|
136
142
|
if (resId != 0) {
|
|
137
143
|
property = mContext.getString(resId);
|
|
138
144
|
|
|
@@ -140,7 +146,7 @@ public class CodePush implements ReactPackage {
|
|
|
140
146
|
return property;
|
|
141
147
|
} else {
|
|
142
148
|
CodePushUtils.log("Specified " + propertyName + " is empty");
|
|
143
|
-
}
|
|
149
|
+
}
|
|
144
150
|
}
|
|
145
151
|
|
|
146
152
|
return null;
|
|
@@ -230,8 +236,8 @@ public class CodePush implements ReactPackage {
|
|
|
230
236
|
return mContext;
|
|
231
237
|
}
|
|
232
238
|
|
|
233
|
-
public String
|
|
234
|
-
return
|
|
239
|
+
public String getReleaseChannelPublicId() {
|
|
240
|
+
return mReleaseChannelPublicId;
|
|
235
241
|
}
|
|
236
242
|
|
|
237
243
|
public static String getJSBundleFile() {
|
|
@@ -393,8 +399,8 @@ public class CodePush implements ReactPackage {
|
|
|
393
399
|
return sTestConfigurationFlag;
|
|
394
400
|
}
|
|
395
401
|
|
|
396
|
-
public void
|
|
397
|
-
|
|
402
|
+
public void setReleaseChannelPublicId(String releaseChannelPublicId) {
|
|
403
|
+
mReleaseChannelPublicId = releaseChannelPublicId;
|
|
398
404
|
}
|
|
399
405
|
|
|
400
406
|
public static void setUsingTestConfiguration(boolean shouldUseTestConfiguration) {
|
|
@@ -1,17 +1,17 @@
|
|
|
1
|
-
package com.
|
|
1
|
+
package com.appzung.codepush.react;
|
|
2
2
|
|
|
3
3
|
import android.content.Context;
|
|
4
4
|
|
|
5
5
|
public class CodePushBuilder {
|
|
6
|
-
private String
|
|
6
|
+
private String mReleaseChannelPublicId;
|
|
7
7
|
private Context mContext;
|
|
8
8
|
|
|
9
9
|
private boolean mIsDebugMode;
|
|
10
10
|
private String mServerUrl;
|
|
11
11
|
private Integer mPublicKeyResourceDescriptor;
|
|
12
12
|
|
|
13
|
-
public CodePushBuilder(String
|
|
14
|
-
this.
|
|
13
|
+
public CodePushBuilder(String releaseChannelPublicId, Context context) {
|
|
14
|
+
this.mReleaseChannelPublicId = releaseChannelPublicId;
|
|
15
15
|
this.mContext = context;
|
|
16
16
|
this.mServerUrl = CodePush.getServiceUrl();
|
|
17
17
|
}
|
|
@@ -32,6 +32,6 @@ public class CodePushBuilder {
|
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
public CodePush build() {
|
|
35
|
-
return new CodePush(this.
|
|
35
|
+
return new CodePush(this.mReleaseChannelPublicId, this.mContext, this.mIsDebugMode, this.mServerUrl, this.mPublicKeyResourceDescriptor);
|
|
36
36
|
}
|
|
37
37
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
package com.
|
|
1
|
+
package com.appzung.codepush.react;
|
|
2
2
|
|
|
3
3
|
class CodePushInvalidPublicKeyException extends RuntimeException {
|
|
4
4
|
|
|
@@ -9,4 +9,4 @@ class CodePushInvalidPublicKeyException extends RuntimeException {
|
|
|
9
9
|
public CodePushInvalidPublicKeyException(String message) {
|
|
10
10
|
super(message);
|
|
11
11
|
}
|
|
12
|
-
}
|
|
12
|
+
}
|
package/android/app/src/main/java/com/microsoft/codepush/react/CodePushMalformedDataException.java
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
package com.
|
|
1
|
+
package com.appzung.codepush.react;
|
|
2
2
|
|
|
3
3
|
import java.net.MalformedURLException;
|
|
4
4
|
|
|
@@ -9,4 +9,4 @@ public class CodePushMalformedDataException extends RuntimeException {
|
|
|
9
9
|
public CodePushMalformedDataException(String url, MalformedURLException cause) {
|
|
10
10
|
super("The package has an invalid downloadUrl: " + url, cause);
|
|
11
11
|
}
|
|
12
|
-
}
|
|
12
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
package com.
|
|
1
|
+
package com.appzung.codepush.react;
|
|
2
2
|
|
|
3
3
|
import android.app.Activity;
|
|
4
4
|
import android.content.SharedPreferences;
|
|
@@ -156,7 +156,7 @@ public class CodePushNativeModule extends ReactContextBaseJavaModule {
|
|
|
156
156
|
@Override
|
|
157
157
|
public void run() {
|
|
158
158
|
try {
|
|
159
|
-
// We don't need to resetReactRootViews anymore
|
|
159
|
+
// We don't need to resetReactRootViews anymore
|
|
160
160
|
// due the issue https://github.com/facebook/react-native/issues/14533
|
|
161
161
|
// has been fixed in RN 0.46.0
|
|
162
162
|
//resetReactRootViews(instanceManager);
|
|
@@ -365,7 +365,7 @@ public class CodePushNativeModule extends ReactContextBaseJavaModule {
|
|
|
365
365
|
WritableMap configMap = Arguments.createMap();
|
|
366
366
|
configMap.putString("appVersion", mCodePush.getAppVersion());
|
|
367
367
|
configMap.putString("clientUniqueId", mClientUniqueId);
|
|
368
|
-
configMap.putString("
|
|
368
|
+
configMap.putString("releaseChannelPublicId", mCodePush.getReleaseChannelPublicId());
|
|
369
369
|
configMap.putString("serverUrl", mCodePush.getServerUrl());
|
|
370
370
|
|
|
371
371
|
// The binary hash may be null in debug builds
|
|
@@ -492,7 +492,7 @@ public class CodePushNativeModule extends ReactContextBaseJavaModule {
|
|
|
492
492
|
return null;
|
|
493
493
|
}
|
|
494
494
|
}
|
|
495
|
-
|
|
495
|
+
|
|
496
496
|
promise.resolve("");
|
|
497
497
|
} catch(CodePushUnknownException e) {
|
|
498
498
|
CodePushUtils.log(e);
|
|
@@ -691,7 +691,7 @@ public class CodePushNativeModule extends ReactContextBaseJavaModule {
|
|
|
691
691
|
|
|
692
692
|
/**
|
|
693
693
|
* This method clears CodePush's downloaded updates.
|
|
694
|
-
* It is needed to switch to a different
|
|
694
|
+
* It is needed to switch to a different release channel if the current release channel is more recent.
|
|
695
695
|
* Note: we don’t recommend to use this method in scenarios other than that (CodePush will call
|
|
696
696
|
* this method automatically when needed in other cases) as it could lead to unpredictable
|
|
697
697
|
* behavior.
|
package/android/app/src/main/java/com/microsoft/codepush/react/CodePushNotInitializedException.java
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
package com.
|
|
1
|
+
package com.appzung.codepush.react;
|
|
2
2
|
|
|
3
3
|
public final class CodePushNotInitializedException extends RuntimeException {
|
|
4
4
|
|
|
@@ -9,4 +9,4 @@ public final class CodePushNotInitializedException extends RuntimeException {
|
|
|
9
9
|
public CodePushNotInitializedException(String message) {
|
|
10
10
|
super(message);
|
|
11
11
|
}
|
|
12
|
-
}
|
|
12
|
+
}
|
package/android/app/src/main/java/com/microsoft/codepush/react/CodePushTelemetryManager.java
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
package com.
|
|
1
|
+
package com.appzung.codepush.react;
|
|
2
2
|
|
|
3
3
|
import android.content.Context;
|
|
4
4
|
import android.content.SharedPreferences;
|
|
@@ -14,12 +14,12 @@ public class CodePushTelemetryManager {
|
|
|
14
14
|
private SharedPreferences mSettings;
|
|
15
15
|
private final String APP_VERSION_KEY = "appVersion";
|
|
16
16
|
private final String DEPLOYMENT_FAILED_STATUS = "DeploymentFailed";
|
|
17
|
-
private final String
|
|
17
|
+
private final String RELEASE_CHANNEL_PUBLIC_ID_KEY = "releaseChannelPublicId";
|
|
18
18
|
private final String DEPLOYMENT_SUCCEEDED_STATUS = "DeploymentSucceeded";
|
|
19
19
|
private final String LABEL_KEY = "label";
|
|
20
20
|
private final String LAST_DEPLOYMENT_REPORT_KEY = "CODE_PUSH_LAST_DEPLOYMENT_REPORT";
|
|
21
21
|
private final String PACKAGE_KEY = "package";
|
|
22
|
-
private final String
|
|
22
|
+
private final String PREVIOUS_RELEASE_CHANNEL_PUBLIC_ID_KEY = "previousReleaseChannelPublicId";
|
|
23
23
|
private final String PREVIOUS_LABEL_OR_APP_VERSION_KEY = "previousLabelOrAppVersion";
|
|
24
24
|
private final String RETRY_DEPLOYMENT_REPORT_KEY = "CODE_PUSH_RETRY_DEPLOYMENT_REPORT";
|
|
25
25
|
private final String STATUS_KEY = "status";
|
|
@@ -39,10 +39,10 @@ public class CodePushTelemetryManager {
|
|
|
39
39
|
this.clearRetryStatusReport();
|
|
40
40
|
reportMap = Arguments.createMap();
|
|
41
41
|
if (this.isStatusReportIdentifierCodePushLabel(previousStatusReportIdentifier)) {
|
|
42
|
-
String
|
|
42
|
+
String previousReleaseChannelPublicId = this.getReleaseChannelPublicIdFromStatusReportIdentifier(previousStatusReportIdentifier);
|
|
43
43
|
String previousLabel = this.getVersionLabelFromStatusReportIdentifier(previousStatusReportIdentifier);
|
|
44
44
|
reportMap.putString(APP_VERSION_KEY, appVersion);
|
|
45
|
-
reportMap.putString(
|
|
45
|
+
reportMap.putString(PREVIOUS_RELEASE_CHANNEL_PUBLIC_ID_KEY, previousReleaseChannelPublicId);
|
|
46
46
|
reportMap.putString(PREVIOUS_LABEL_OR_APP_VERSION_KEY, previousLabel);
|
|
47
47
|
} else {
|
|
48
48
|
// Previous status report was with a binary app version.
|
|
@@ -90,11 +90,11 @@ public class CodePushTelemetryManager {
|
|
|
90
90
|
this.clearRetryStatusReport();
|
|
91
91
|
reportMap = Arguments.createMap();
|
|
92
92
|
if (this.isStatusReportIdentifierCodePushLabel(previousStatusReportIdentifier)) {
|
|
93
|
-
String
|
|
93
|
+
String previousReleaseChannelPublicId = this.getReleaseChannelPublicIdFromStatusReportIdentifier(previousStatusReportIdentifier);
|
|
94
94
|
String previousLabel = this.getVersionLabelFromStatusReportIdentifier(previousStatusReportIdentifier);
|
|
95
95
|
reportMap.putMap(PACKAGE_KEY, currentPackage);
|
|
96
96
|
reportMap.putString(STATUS_KEY, DEPLOYMENT_SUCCEEDED_STATUS);
|
|
97
|
-
reportMap.putString(
|
|
97
|
+
reportMap.putString(PREVIOUS_RELEASE_CHANNEL_PUBLIC_ID_KEY, previousReleaseChannelPublicId);
|
|
98
98
|
reportMap.putString(PREVIOUS_LABEL_OR_APP_VERSION_KEY, previousLabel);
|
|
99
99
|
} else {
|
|
100
100
|
// Previous status report was with a binary app version.
|
|
@@ -113,7 +113,7 @@ public class CodePushTelemetryManager {
|
|
|
113
113
|
if (statusReport.hasKey(STATUS_KEY) && DEPLOYMENT_FAILED_STATUS.equals(statusReport.getString(STATUS_KEY))) {
|
|
114
114
|
return;
|
|
115
115
|
}
|
|
116
|
-
|
|
116
|
+
|
|
117
117
|
if (statusReport.hasKey(APP_VERSION_KEY)) {
|
|
118
118
|
saveStatusReportedForIdentifier(statusReport.getString(APP_VERSION_KEY));
|
|
119
119
|
} else if (statusReport.hasKey(PACKAGE_KEY)) {
|
|
@@ -131,7 +131,7 @@ public class CodePushTelemetryManager {
|
|
|
131
131
|
mSettings.edit().remove(RETRY_DEPLOYMENT_REPORT_KEY).commit();
|
|
132
132
|
}
|
|
133
133
|
|
|
134
|
-
private String
|
|
134
|
+
private String getReleaseChannelPublicIdFromStatusReportIdentifier(String statusReportIdentifier) {
|
|
135
135
|
String[] parsedIdentifier = statusReportIdentifier.split(":");
|
|
136
136
|
if (parsedIdentifier.length > 0) {
|
|
137
137
|
return parsedIdentifier[0];
|
|
@@ -141,12 +141,12 @@ public class CodePushTelemetryManager {
|
|
|
141
141
|
}
|
|
142
142
|
|
|
143
143
|
private String getPackageStatusReportIdentifier(ReadableMap updatePackage) {
|
|
144
|
-
// Because
|
|
145
|
-
// combination of the
|
|
146
|
-
String
|
|
144
|
+
// Because release channels can be dynamically switched, we use a
|
|
145
|
+
// combination of the releaseChannelPublicId and label as the packageIdentifier.
|
|
146
|
+
String releaseChannelPublicId = CodePushUtils.tryGetString(updatePackage, RELEASE_CHANNEL_PUBLIC_ID_KEY);
|
|
147
147
|
String label = CodePushUtils.tryGetString(updatePackage, LABEL_KEY);
|
|
148
|
-
if (
|
|
149
|
-
return
|
|
148
|
+
if (releaseChannelPublicId != null && label != null) {
|
|
149
|
+
return releaseChannelPublicId + ":" + label;
|
|
150
150
|
} else {
|
|
151
151
|
return null;
|
|
152
152
|
}
|
package/android/app/src/main/java/com/microsoft/codepush/react/CodePushUnknownException.java
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
package com.
|
|
1
|
+
package com.appzung.codepush.react;
|
|
2
2
|
|
|
3
3
|
class CodePushUnknownException extends RuntimeException {
|
|
4
4
|
|
|
@@ -9,4 +9,4 @@ class CodePushUnknownException extends RuntimeException {
|
|
|
9
9
|
public CodePushUnknownException(String message) {
|
|
10
10
|
super(message);
|
|
11
11
|
}
|
|
12
|
-
}
|
|
12
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
package com.
|
|
1
|
+
package com.appzung.codepush.react;
|
|
2
2
|
|
|
3
3
|
import android.content.Context;
|
|
4
4
|
import android.util.Base64;
|
|
@@ -9,7 +9,6 @@ import com.nimbusds.jwt.SignedJWT;
|
|
|
9
9
|
|
|
10
10
|
import java.security.interfaces.*;
|
|
11
11
|
|
|
12
|
-
|
|
13
12
|
import org.json.JSONArray;
|
|
14
13
|
import org.json.JSONException;
|
|
15
14
|
import org.json.JSONObject;
|
|
@@ -86,8 +85,12 @@ public class CodePushUpdateUtils {
|
|
|
86
85
|
throw new CodePushUnknownException("Unable to compute hash of update contents.", e);
|
|
87
86
|
} finally {
|
|
88
87
|
try {
|
|
89
|
-
if (digestInputStream != null)
|
|
90
|
-
|
|
88
|
+
if (digestInputStream != null) {
|
|
89
|
+
digestInputStream.close();
|
|
90
|
+
}
|
|
91
|
+
if (dataStream != null) {
|
|
92
|
+
dataStream.close();
|
|
93
|
+
}
|
|
91
94
|
} catch (IOException e) {
|
|
92
95
|
e.printStackTrace();
|
|
93
96
|
}
|
|
@@ -98,7 +101,11 @@ public class CodePushUpdateUtils {
|
|
|
98
101
|
}
|
|
99
102
|
|
|
100
103
|
public static void copyNecessaryFilesFromCurrentPackage(String diffManifestFilePath, String currentPackageFolderPath, String newPackageFolderPath) throws IOException {
|
|
101
|
-
|
|
104
|
+
if (currentPackageFolderPath == null || !new File(currentPackageFolderPath).exists()) {
|
|
105
|
+
CodePushUtils.log("Unable to copy files from current package during diff update, because currentPackageFolderPath is invalid.");
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
FileUtils.copyDirectoryContents(currentPackageFolderPath, newPackageFolderPath);
|
|
102
109
|
JSONObject diffManifest = CodePushUtils.getJsonObjectFromFile(diffManifestFilePath);
|
|
103
110
|
try {
|
|
104
111
|
JSONArray deletedFiles = diffManifest.getJSONArray("deletedFiles");
|
|
@@ -184,7 +191,7 @@ public class CodePushUpdateUtils {
|
|
|
184
191
|
public static Map<String, Object> verifyAndDecodeJWT(String jwt, PublicKey publicKey) {
|
|
185
192
|
try {
|
|
186
193
|
SignedJWT signedJWT = SignedJWT.parse(jwt);
|
|
187
|
-
JWSVerifier verifier = new RSASSAVerifier((RSAPublicKey)publicKey);
|
|
194
|
+
JWSVerifier verifier = new RSASSAVerifier((RSAPublicKey) publicKey);
|
|
188
195
|
if (signedJWT.verify(verifier)) {
|
|
189
196
|
Map<String, Object> claims = signedJWT.getJWTClaimsSet().getClaims();
|
|
190
197
|
CodePushUtils.log("JWT verification succeeded, payload content: " + claims.toString());
|
|
@@ -217,7 +224,7 @@ public class CodePushUpdateUtils {
|
|
|
217
224
|
}
|
|
218
225
|
}
|
|
219
226
|
|
|
220
|
-
public static String getSignatureFilePath(String updateFolderPath){
|
|
227
|
+
public static String getSignatureFilePath(String updateFolderPath) {
|
|
221
228
|
return CodePushUtils.appendPathComponent(
|
|
222
229
|
CodePushUtils.appendPathComponent(updateFolderPath, CodePushConstants.CODE_PUSH_FOLDER_PREFIX),
|
|
223
230
|
CodePushConstants.BUNDLE_JWT_FILE
|
|
@@ -254,7 +261,7 @@ public class CodePushUpdateUtils {
|
|
|
254
261
|
throw new CodePushInvalidUpdateException("The update could not be verified because it was not signed by a trusted party.");
|
|
255
262
|
}
|
|
256
263
|
|
|
257
|
-
final String contentHash = (String)claims.get("contentHash");
|
|
264
|
+
final String contentHash = (String) claims.get("contentHash");
|
|
258
265
|
if (contentHash == null) {
|
|
259
266
|
throw new CodePushInvalidUpdateException("The update could not be verified because the signature did not specify a content hash.");
|
|
260
267
|
}
|
|
@@ -265,4 +272,4 @@ public class CodePushUpdateUtils {
|
|
|
265
272
|
|
|
266
273
|
CodePushUtils.log("The update contents succeeded the code signing check.");
|
|
267
274
|
}
|
|
268
|
-
}
|
|
275
|
+
}
|
package/android/build.gradle
CHANGED
package/android/codepush.gradle
CHANGED
|
@@ -69,9 +69,9 @@ gradle.projectsEvaluated {
|
|
|
69
69
|
|
|
70
70
|
def nodeModulesPath;
|
|
71
71
|
if (project.hasProperty('nodeModulesPath')) {
|
|
72
|
-
nodeModulesPath = "${project.nodeModulesPath}/react-native-code-push"
|
|
72
|
+
nodeModulesPath = "${project.nodeModulesPath}/@appzung/react-native-code-push"
|
|
73
73
|
} else {
|
|
74
|
-
nodeModulesPath = findNodeModulePath(projectDir, "react-native-code-push")
|
|
74
|
+
nodeModulesPath = findNodeModulePath(projectDir, "@appzung/react-native-code-push")
|
|
75
75
|
}
|
|
76
76
|
|
|
77
77
|
def targetName = variant.name.capitalize()
|
|
@@ -95,7 +95,7 @@ gradle.projectsEvaluated {
|
|
|
95
95
|
|
|
96
96
|
// mitigates Resource and asset merger: Duplicate resources error
|
|
97
97
|
project.delete(files("${jsBundleDir}"))
|
|
98
|
-
|
|
98
|
+
|
|
99
99
|
jsBundleDir.mkdirs()
|
|
100
100
|
resourcesDir.mkdirs()
|
|
101
101
|
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
## Advanced usage
|
|
2
|
+
|
|
3
|
+
### Multiple environments
|
|
4
|
+
|
|
5
|
+
#### Staging / Production
|
|
6
|
+
|
|
7
|
+
In a real-world scenario you would have `Staging` and `Production` apps that are different binaries, each using specific environment config (different package name, bundle identifier, API, feature flags etc). Create a release channel for each of these environments with `appzung release-channels create` and change the `CodePushReleaseChannelPublicId` on the native side with environment variables. You might want to use a module like [react-native-config](https://github.com/lugg/react-native-config) (or do it manually using build variants and configurations).
|
|
8
|
+
|
|
9
|
+
```groovy
|
|
10
|
+
// android/app/build.gradle
|
|
11
|
+
android {
|
|
12
|
+
// ...
|
|
13
|
+
defaultConfig {
|
|
14
|
+
// ...
|
|
15
|
+
resValue 'string', "CodePushReleaseChannelPublicId", project.env.get("CODEPUSH_RELEASE_CHANNEL_PUBLIC_ID_ANDROID")
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
```
|
|
21
|
+
// ios/myapp/Info.plist
|
|
22
|
+
|
|
23
|
+
<key>CodePushReleaseChannelPublicId</key>
|
|
24
|
+
<string>$(CODEPUSH_RELEASE_CHANNEL_PUBLIC_ID_IOS)</string>
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
#### QA / Production
|
|
28
|
+
|
|
29
|
+
In the case where you have a `QA` environment that is completely iso-prod (meaning you can swap the JS bundle between those two apps), you may take advantage of the `promote` feature:
|
|
30
|
+
|
|
31
|
+
1. Release a CodePush update to your `QA` release channel using the `appzung releases deploy-react-native` command (or `appzung releases deploy` if you need more control)
|
|
32
|
+
2. Let QA test the build
|
|
33
|
+
3. Promote the tested release from `QA` to `Production` using the `appzung releases promote` command
|
|
34
|
+
|
|
35
|
+
*NOTE: If you want to take a more cautious approach, you can even choose to perform a "staged rollout" as part of #3, which allows you to mitigate additional potential risk with the update (like did your testing in #2 touch all possible devices/conditions?) by only making the production update available to a percentage of your users (for example `appzung releases promote -r 20`). Then, after waiting for a reasonable amount of time to see if any crash reports or customer feedback comes in, you can expand it to your entire audience by running `appzung releases edit -r 100`.*
|
|
36
|
+
|
|
37
|
+
### Dynamic release channel assignment
|
|
38
|
+
|
|
39
|
+
The above section illustrated how you can leverage multiple CodePush release channels in order to effectively test your updates before broadly releasing them to your end users. However, since that workflow statically embeds the release channel assignment into the actual binary, a staging or production build will only ever sync updates from that release channel. In many cases, this is sufficient, since you only want your team, customers, stakeholders, etc. to sync with your pre-production releases, and therefore, only they need a build that knows how to sync with staging. However, if you want to be able to perform A/B tests, or provide early access of your app to certain users, it can prove very useful to be able to dynamically place specific users (or audiences) into specific release channels at runtime.
|
|
40
|
+
|
|
41
|
+
In order to achieve this kind of workflow, all you need to do is specify the release channel public ID you want the current user to synchronize with when calling the `codePush` method. When specified, this key will override the "default" one that was provided in your app's `Info.plist` (iOS) or strings resources (Android) files. This allows you to produce a build for staging or production, that is also capable of being dynamically "redirected" as needed.
|
|
42
|
+
|
|
43
|
+
```javascript
|
|
44
|
+
// Imagine that "userProfile" is a prop that this component received
|
|
45
|
+
// which includes the release channel public ID that the current user should use.
|
|
46
|
+
codePush.sync({ releaseChannelPublicId: userProfile.RELEASE_CHANNEL_PUBLIC_ID });
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
With that change in place, now it's just a matter of choosing how your app determines the right release channel for the current user. In practice, there are typically two solutions for this:
|
|
50
|
+
|
|
51
|
+
1. Expose a user-visible mechanism for changing release channels at any time. For example, your settings page could have a toggle for enabling "beta" access. This model works well if you're not concerned with the privacy of your pre-production updates, and you have power users that may want to opt-in to earlier (and potentially buggy) updates at their own will (kind of like Chrome channels). However, this solution puts the decision in the hands of your users, which doesn't help you perform A/B tests transparently.
|
|
52
|
+
2. Annotate the server-side profile of your users with an additional piece of metadata that indicates the release channel they should sync with. By default, your app could just use the binary-embedded key, but after a user has authenticated, your server can choose to "redirect" them to a different release channel, which allows you to incrementally place certain users or groups in different release channels as needed. You could even choose to store the server-response in local storage so that it becomes the new default. How you store the key alongside your user's profiles is entirely up to your authentication solution (for example Auth0, Firebase, custom DB + REST API), but is generally pretty trivial to do.
|
|
53
|
+
|
|
54
|
+
*NOTE: If needed, you could also implement a hybrid solution that allowed your end-users to toggle between different release channels, while also allowing your server to override that decision. This way, you have a hierarchy of "release channel resolution" that ensures your app has the ability to update itself out-of-the-box, your end users can feel rewarded by getting early access to bits, but you also have the ability to run A/B tests on your users as needed.*
|
|
55
|
+
|
|
56
|
+
Are you using dynamic release channel assignments? Contact us at hello@appzung.com so that we may provide better integration.
|