Repository URL to install this package:
|
Version:
0.0.6 ▾
|
react-native-motif-sdk
/
android
/
src
/
main
/
java
/
com
/
vipera
/
de
/
rn
/
assetmanager
/
RNAssetManagerModule.java
|
|---|
package com.vipera.de.rn.assetmanager;
import android.app.Activity;
import android.content.Context;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import com.facebook.react.ReactApplication;
import com.facebook.react.ReactInstanceManager;
import com.facebook.react.bridge.JSBundleLoader;
import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.WritableArray;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.bridge.WritableNativeArray;
import com.facebook.react.bridge.WritableNativeMap;
import com.facebook.react.devsupport.DevInternalSettings;
import com.facebook.react.devsupport.interfaces.DevSupportManager;
import com.facebook.react.modules.core.DeviceEventManagerModule;
import com.vipera.de.motifconnector.IDEServerManager;
import com.vipera.de.motifconnector.config.DEConnectorProvider;
import com.vipera.de.motifconnector.nativekit.DEAssetDownloadListener;
import com.vipera.de.motifconnector.nativekit.DEMotifRequest;
import com.vipera.de.motifconnector.nativekit.DEMotifRequestCallback;
import com.vipera.de.motifconnector.nativekit.DEMotifResponse;
import com.vipera.de.motifconnector.nativekit.IDEMotifClient;
import com.vipera.de.motifconnector.nativekit.assets.DEAssetDownloadProgressInfo;
import com.vipera.de.motifconnector.nativekit.assets.DEAssetDownloadRequest;
import com.vipera.de.motifconnector.nativekit.assets.DEAssetDownloadResult;
import com.vipera.de.motifconnector.nativekit.error.IDEError;
import com.vipera.de.motifconnector.nativekit.impl.DEDefaultMotifClient;
import com.vipera.de.rn.assetmanager.logs.RNUpdateLog;
import com.vipera.de.rn.assetmanager.models.AppCheckRequest;
import com.vipera.de.rn.assetmanager.models.AppCheckResult;
import com.vipera.de.rn.assetmanager.protocol.ProtocolHelper;
import com.vipera.de.rn.commons.RNContextHelper;
import com.vipera.de.rn.commons.event.JSEvent;
import com.vipera.de.utility.Version;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.File;
import java.lang.reflect.Field;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
public class RNAssetManagerModule extends ReactContextBaseJavaModule {
private static final String LOG_TAG = "RNAssetManagerModule";
//JS events
public static final String ASSETS_DOWNLOAD_FAIL = "assetsDownloadFail";
public static final String ASSETS_DOWNLOAD_PROGRESS = "assetsDownloadProgress";
public static final String ASSETS_DOWNLOAD_START = "assetsDownloadStart";
public static final String RN_ASSET_MANAGER_NAME = "RNAssetManager";
private final RNUpdateManager updateManager;
private final String domainName;
private final String appName;
private final String appContentSrv;
private final String appCheckOp;
private final String assetDownloadOp;
private final RNAssetManagerConfig configuration;
private final RNUpdateLog updateLog;
private boolean mIsDebugMode = false;
private AtomicBoolean pendingUpdateInProgress;
private IDEServerManager serverManager;
private IDEMotifClient motifClient;
public RNAssetManagerModule(ReactApplicationContext reactContext, RNUpdateManager updateManager, RNAssetManagerConfig configuration, RNUpdateLog updateLog) {
super(reactContext);
this.configuration = configuration;
this.updateLog = updateLog;
this.pendingUpdateInProgress = new AtomicBoolean(false);
this.domainName = RNAssetManager.DEFAULT_DOM;
this.appName = RNAssetManager.DEFAULT_APP;
this.appContentSrv = RNAssetManager.DEFAULT_SRV;
this.appCheckOp = RNAssetManager.DEFAULT_APP_CHECK_OP;
this.assetDownloadOp = RNAssetManager.DEFAULT_ASSET_DOWNLOAD_OP;
this.updateManager = updateManager;
initNetworkLayer(reactContext.getApplicationContext(),configuration);
}
private void initNetworkLayer(Context application, RNAssetManagerConfig config) {
if(config.isAssetManagerAvailable()) {
Log.i(LOG_TAG, "initNetworkLayer app: " + appName + "; dom: " + domainName);
DEConnectorProvider.initialize(application);
this.serverManager = DEConnectorProvider.createNewServerManagerInstance(config.getServerConfig(), application, false);
this.motifClient = new DEDefaultMotifClient(serverManager, appName, domainName);
} else {
Log.i(LOG_TAG, "AssetManager not enabled, skipping NetworkLayer initialization");
}
}
@Override
public String getName() {
return RN_ASSET_MANAGER_NAME;
}
private void setJSBundle(ReactInstanceManager instanceManager, RNAssetBundleInfo assetsBundleInfo) throws IllegalAccessException{
try {
JSBundleLoader latestJSBundleLoader;
String latestJSBundleFile = RNBundlePathResolver.resolvePath(getReactApplicationContext(),assetsBundleInfo);
if (assetsBundleInfo.getType() == RNAssetBundleInfo.Type.EMBEDDED) {
latestJSBundleLoader = JSBundleLoader.createAssetLoader(getReactApplicationContext(), latestJSBundleFile, false);
} else {
latestJSBundleLoader = JSBundleLoader.createFileLoader(latestJSBundleFile);
}
Field bundleLoaderField = instanceManager.getClass().getDeclaredField("mBundleLoader");
bundleLoaderField.setAccessible(true);
bundleLoaderField.set(instanceManager, latestJSBundleLoader);
} catch (Exception e) {
//CodePushUtils.log("Unable to set JSBundle - CodePush may not support this version of React Native");
throw new IllegalAccessException("Could not setJSBundle");
}
}
public void clearDebugCacheIfNeeded(ReactInstanceManager instanceManager) {
boolean isLiveReloadEnabled = false;
// Use instanceManager for checking if we use LiveRelaod mode. In this case we should not remove ReactNativeDevBundle.js file
// because we get error with trying to get this after reloading. Issue: https://github.com/Microsoft/react-native-code-push/issues/1272
if (instanceManager != null) {
DevSupportManager devSupportManager = instanceManager.getDevSupportManager();
if (devSupportManager != null) {
DevInternalSettings devInternalSettings = (DevInternalSettings) devSupportManager.getDevSettings();
isLiveReloadEnabled = devInternalSettings.isReloadOnJSChangeEnabled();
}
}
if (mIsDebugMode && !isLiveReloadEnabled) {
// This needs to be kept in sync with https://github.com/facebook/react-native/blob/master/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportManager.java#L78
File cachedDevBundle = new File(getReactApplicationContext().getFilesDir(), "ReactNativeDevBundle.js");
if (cachedDevBundle.exists()) {
cachedDevBundle.delete();
}
}
}
private void loadBundleLegacy() {
final Activity currentActivity = getCurrentActivity();
if (currentActivity == null) {
// The currentActivity can be null if it is backgrounded / destroyed, so we simply
// no-op to prevent any null pointer exceptions.
return;
}
updateManager.invalidateCurrentInstance();
currentActivity.runOnUiThread(new Runnable() {
@Override
public void run() {
currentActivity.recreate();
}
});
}
private void loadBundle() {
Log.i(LOG_TAG,"loadBundle");
clearLifecycleEventListener();
try {
Log.i(LOG_TAG," try clearDebugCacheIfNeeded");
clearDebugCacheIfNeeded(resolveInstanceManager());
} catch (Exception e) {
Log.w(LOG_TAG,"clearDebugCacheIfNeeded error: " + e.getMessage(),e);
// If we got error in out reflection we should clear debug cache anyway.
clearDebugCacheIfNeeded(null);
}
try {
// Get the ReactInstanceManager instance, which is what includes the
// logic to reload the current React context.
Log.i(LOG_TAG,"[STEP 1]: getInstanceManager");
final ReactInstanceManager instanceManager = resolveInstanceManager();
if (instanceManager == null) {
return;
}
Log.i(LOG_TAG,"[STEP 2]: readBundleInfo");
RNAssetBundleInfo bundleInfo = updateManager.getCurrentBundleInfo();
Log.i(LOG_TAG,"[STEP 3]: setJSBundle");
setJSBundle(instanceManager,bundleInfo);
Log.i(LOG_TAG,"[STEP 4]: recreate react context");
// Get the context creation method and fire it on the UI thread (which RN enforces)
new Handler(Looper.getMainLooper()).post(new Runnable() {
@Override
public void run() {
try {
// We don't need to resetReactRootViews anymore
// due the issue https://github.com/facebook/react-native/issues/14533
// has been fixed in RN 0.46.0
//resetReactRootViews(instanceManager);
Log.i(LOG_TAG,"recreateReactContextInBackground");
instanceManager.recreateReactContextInBackground();
//TODO check this
//mCodePush.initializeUpdateAfterRestart();
} catch (Exception e) {
// The recreation method threw an unknown exception
// so just simply fallback to restarting the Activity (if it exists)
loadBundleLegacy();
}
}
});
} catch (Exception e) {
Log.e(LOG_TAG,"loadBundle error: " + e.getMessage(),e);
// Our reflection logic failed somewhere
// so fall back to restarting the Activity (if it exists)
loadBundleLegacy();
}
}
private ReactInstanceManager resolveInstanceManager() throws NoSuchFieldException, IllegalAccessException {
ReactInstanceManager instanceManager = RNAssetManager.getReactInstanceManager();
if (instanceManager != null) {
return instanceManager;
}
final Activity currentActivity = getCurrentActivity();
if (currentActivity == null) {
return null;
}
ReactApplication reactApplication = (ReactApplication) currentActivity.getApplication();
instanceManager = reactApplication.getReactNativeHost().getReactInstanceManager();
return instanceManager;
}
private void clearLifecycleEventListener() {
//TODO evaluate this
}
@ReactMethod
public void checkForUpdates(final Promise promise){
if(!configuration.isAssetManagerAvailable()) {
rejectNotAvailable(promise);
return;
}
final AppCheckRequest appCheckRequest = updateManager.buildAppCheckRequest();
try {
DEMotifRequest motifRequest = toMotifRequest(appCheckRequest);
motifClient.postRequest(motifRequest, new DEMotifRequestCallback() {
@Override
public void onSuccess(DEMotifResponse motifResponse, DEMotifRequest deMotifRequest) {
Log.i("checkForUpdates","success: " + motifResponse.getHeader());
updateManager.markLatestAppCheck();
try {
AppCheckResult result = createAppCheckResult(motifResponse.getHeader(),appCheckRequest);
WritableMap writableMap = toWritableMap(result);
promise.resolve(writableMap);
} catch (JSONException e) {
Log.e(LOG_TAG,e.getMessage());
promise.reject("JSONException",e.getMessage());
} catch (Throwable ex){
Log.e(LOG_TAG,ex.getMessage(),ex);
promise.reject("GenericError",ex.getMessage());
}
}
@Override
public void onError(IDEError ideError) {
Log.e("checkForUpdates","fail: " + ideError);
promise.reject(ideError.getErrorCode().getCode(),ideError.getErrorMessage());
}
});
} catch (JSONException e) {
Log.e("JSONException",e.getMessage());
promise.reject("JSONException",e.getMessage());
}
}
private WritableMap toWritableMap(AppCheckResult appCheckResult) {
WritableMap result = new WritableNativeMap();
result.putString("appStoreUrl",appCheckResult.getAppStoreUrl());
result.putString("latestAssetsVersion",appCheckResult.getLatestAssetVersion().asString());
result.putString("latestAppVersion",appCheckResult.getLatestEngineVersion().asString());
result.putBoolean("assetsUpdateAvailable",appCheckResult.isNeedsToUpdateAsset());
result.putBoolean("appUpdateAvailable",appCheckResult.isNeedsAppUpdate());
result.putBoolean("appUpdateRequired",appCheckResult.isForceAppUpdate());
return result;
}
private AppCheckResult createAppCheckResult(JSONObject header,AppCheckRequest request) throws JSONException {
return ProtocolHelper.createAppCheckResult(header,request);
}
private DEMotifRequest toMotifRequest(AppCheckRequest request) throws JSONException {
DEMotifRequest deMotifRequest = motifClient.buildRequestForService(appContentSrv,appCheckOp,false);
ProtocolHelper.fillAppCheckMotifRequest(deMotifRequest,request);
return deMotifRequest;
}
@ReactMethod
public void updateAssets(){
if(!configuration.isAssetManagerAvailable()) {
Log.e(LOG_TAG, "Not Available");
return;
}
Log.i(LOG_TAG,"updateAssets");
if(!pendingUpdateInProgress.compareAndSet(false,true)){
Log.e(LOG_TAG,"updateAssets rejected: pending update in progress");
return;
}
final RNAssetBundleInfo bundleInfo = updateManager.getCurrentBundleInfo();
updateManager.prepareDownloadDir();
final DEAssetDownloadRequest downloadRequest = updateManager.buildAssetDownloadRequest();
motifClient.downloadAssets(downloadRequest, new DEAssetDownloadListener() {
@Override
public void onDownloadStart(int numberOfFiles) {
Log.i("downloadAssets","onDownloadStart: " + numberOfFiles);
notifyDownloadStart(numberOfFiles);
}
@Override
public void onPublishProgress(DEAssetDownloadProgressInfo progressInfo) {
Log.i("downloadAssets","onProgress : " + progressInfo.getAlreadyDownloaded() + " / " + progressInfo.getNumOfFile() );
notifyDownloadProgress(progressInfo);
}
@Override
public void onDownloadSuccess(DEAssetDownloadResult result) {
Log.i("downloadAssets","onDownloadSuccess" + result);
pendingUpdateInProgress.set(false);
boolean success = updateManager.commitDeploy(result.getDownloadedVersion());
if(success){
Log.i("downloadAssets","commitDeploy success");
updateLog.logSuccessUpdateEvent(bundleInfo.getVersion(),result.getDownloadedVersion());
}else{
Log.e("downloadAssets","commitDeploy fail");
updateLog.logFailUpdateEvent(bundleInfo.getVersion(),result.getDownloadedVersion());
}
loadBundle();
}
@Override
public void onDownloadFail() {
Log.e(LOG_TAG,"onDownloadFail: unknown reason");
pendingUpdateInProgress.set(false);
updateManager.clearTransitoryFolders();
updateLog.logFailUpdateEvent(bundleInfo.getVersion(),Version.zero());
notifyDownloadFail();
}
});
}
private void notifyDownloadFail() {
JSEvent jsEvent = new JSEvent(ASSETS_DOWNLOAD_FAIL);
dispatchEvent(jsEvent);
}
private void notifyDownloadProgress(DEAssetDownloadProgressInfo progressInfo) {
JSEvent jsEvent = new JSEvent(ASSETS_DOWNLOAD_PROGRESS);
jsEvent.addData("totalFilesCount",progressInfo.getNumOfFile());
jsEvent.addData("progress",progressInfo.getAlreadyDownloaded());
dispatchEvent(jsEvent);
}
private void notifyDownloadStart(int numberOfFiles) {
JSEvent jsEvent = new JSEvent(ASSETS_DOWNLOAD_START);
jsEvent.addData("totalFilesCount",numberOfFiles);
dispatchEvent(jsEvent);
}
private void dispatchEvent(JSEvent event){
getReactApplicationContext()
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
.emit(event.getName(),event.getData());
}
@ReactMethod
public void getCurrentState(Promise promise){
if(!configuration.isAssetManagerAvailable()) {
rejectNotAvailable(promise);
return;
}
try {
RNAssetBundleInfo bundleInfo = updateManager.getCurrentBundleInfo();
RNAssetBundleInfo.Type type = bundleInfo.getType();
long lastCheck = updateManager.getLastAppCheck();
Version appVersion = RNContextHelper.getAppVersion(getReactApplicationContext());
Version assetVersion = bundleInfo.getVersion();
long lastUpdate = bundleInfo.getDeployTimestamp();
WritableMap result = new WritableNativeMap();
if(lastCheck >= 0){
result.putString("lastCheckTimestamp", "" + lastCheck);
}else{
result.putNull("lastCheckTimestamp");
}
result.putString("currentApplicationVersion", appVersion != null ? appVersion.asString() : "");
result.putString("assetsName", configuration.getAssetManagerAssetsName());
result.putString("applicationName", configuration.getAssetManagerEngineName());
result.putString("bundledAssetsVersion", configuration.getEmbeddedAssetVersion().asString());
result.putString("currentAssetsType", type == RNAssetBundleInfo.Type.EMBEDDED ? "Bundled": "Downloaded");
result.putString("currentAssetsVersion",assetVersion.asString());
if(lastUpdate >= 0){
result.putString("lastUpdateTimestamp", "" + lastUpdate);
}else{
result.putNull("lastUpdateTimestamp");
}
promise.resolve(result);
}catch (Exception ex){
promise.reject("ILLEGAL_STATE",ex.getMessage(),ex);
}
}
@ReactMethod
public void getUpdateLog(Promise promise){
if(!configuration.isAssetManagerAvailable()) {
rejectNotAvailable(promise);
return;
}
RNUpdateLog updateLog = RNAssetManager.getUpdateLog();
List<RNUpdateLog.LogItem> items = updateLog.getLogs();
WritableArray writableArray = new WritableNativeArray();
if(items == null || items.size() == 0){
promise.resolve(writableArray);
return;
}
for(RNUpdateLog.LogItem item: items){
WritableMap itemMap = toWritableMap(item);
if(itemMap != null){
writableArray.pushMap(itemMap);
}
}
promise.resolve(writableArray);
}
private WritableMap toWritableMap(RNUpdateLog.LogItem item) {
if(item == null){
return null;
}
WritableMap writableMap = new WritableNativeMap();
writableMap.putString("type",item.getType());
writableMap.putString("timestamp","" + item.getTimestamp());
writableMap.putString("fromVersion",item.getFrom().asString());
writableMap.putString("toVersion",item.getTo().asString());
writableMap.putString("status",item.getStatus());
return writableMap;
}
@ReactMethod
public void resetState() {
if(!configuration.isAssetManagerAvailable()) {
Log.e(LOG_TAG, "Not Available");
return;
}
RNAssetBundleInfo toReset = updateManager.getCurrentBundleInfo();
updateManager.invalidateCurrentInstance();
updateLog.logResetEvent(toReset.getVersion(),configuration.getEmbeddedAssetVersion());
loadBundle();
}
@ReactMethod
public void reloadApp(){
if(!configuration.isAssetManagerAvailable()) {
Log.e(LOG_TAG, "Not Available");
return;
}
loadBundle();
}
@ReactMethod
public void getServiceStatus(Promise promise) {
WritableMap writableMap = new WritableNativeMap();
writableMap.putBoolean("enabled", configuration.isAssetManagerEnabled());
writableMap.putBoolean("available", configuration.isAssetManagerAvailable());
promise.resolve(writableMap);
}
public void rejectNotAvailable(Promise promise) {
Log.e(LOG_TAG, "Not Available");
promise.reject("NotAvailable", "AssetManager not available.");
}
}