Why Gemfury? Push, build, and install  RubyGems npm packages Python packages Maven artifacts PHP packages Go Modules Debian packages RPM packages NuGet packages

Repository URL to install this package:

Details    
react-native-motif-sdk / android / src / main / java / com / vipera / de / rn / assetmanager / RNAssetManagerModule.java
Size: Mime:

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.");
    }

}