react-native-workers
Advanced tools
| apply plugin: 'com.android.library' | ||
| android { | ||
| compileSdkVersion 23 | ||
| buildToolsVersion "23.0.1" | ||
| defaultConfig { | ||
| minSdkVersion 16 | ||
| targetSdkVersion 22 | ||
| versionCode 1 | ||
| versionName "1.0" | ||
| ndk { | ||
| abiFilters "armeabi-v7a", "x86" | ||
| } | ||
| } | ||
| } | ||
| dependencies { | ||
| compile 'com.facebook.react:react-native:+' | ||
| } |
| <?xml version="1.0" encoding="utf-8"?> | ||
| <manifest xmlns:android="http://schemas.android.com/apk/res/android" | ||
| package="co.apptailor.Worker" > | ||
| <uses-permission android:name="android.permission.INTERNET" /> | ||
| </manifest> |
| package co.apptailor.Worker.core; | ||
| import com.facebook.react.bridge.JavaScriptModule; | ||
| import com.facebook.react.bridge.NativeModule; | ||
| import com.facebook.react.bridge.ReactApplicationContext; | ||
| import com.facebook.react.modules.core.DeviceEventManagerModule; | ||
| import com.facebook.react.modules.core.JSTimersExecution; | ||
| import com.facebook.react.modules.core.RCTNativeAppEventEmitter; | ||
| import com.facebook.react.modules.core.Timing; | ||
| import com.facebook.react.modules.intent.IntentModule; | ||
| import com.facebook.react.modules.location.LocationModule; | ||
| import com.facebook.react.modules.netinfo.NetInfoModule; | ||
| import com.facebook.react.modules.network.NetworkingModule; | ||
| import com.facebook.react.modules.storage.AsyncStorageModule; | ||
| import com.facebook.react.modules.systeminfo.AndroidInfoModule; | ||
| import com.facebook.react.modules.vibration.VibrationModule; | ||
| import com.facebook.react.modules.websocket.WebSocketModule; | ||
| import com.facebook.react.uimanager.events.RCTEventEmitter; | ||
| import java.util.Arrays; | ||
| import java.util.List; | ||
| public class BaseModuleList { | ||
| public List<NativeModule> nativeModules(ReactApplicationContext reactContext) { | ||
| return Arrays.<NativeModule>asList( | ||
| new AndroidInfoModule(), | ||
| new Timing(reactContext), | ||
| new UIManagerStubModule(reactContext), | ||
| new AsyncStorageModule(reactContext), | ||
| new IntentModule(reactContext), | ||
| new LocationModule(reactContext), | ||
| new NetworkingModule(reactContext), | ||
| new NetInfoModule(reactContext), | ||
| new VibrationModule(reactContext), | ||
| new WebSocketModule(reactContext), | ||
| new WorkerSelfModule(reactContext) | ||
| ); | ||
| } | ||
| public List<Class<? extends JavaScriptModule>> jsModules() { | ||
| return Arrays.asList( | ||
| DeviceEventManagerModule.RCTDeviceEventEmitter.class, | ||
| JSTimersExecution.class, | ||
| RCTEventEmitter.class, | ||
| RCTNativeAppEventEmitter.class, | ||
| com.facebook.react.bridge.Systrace.class | ||
| ); | ||
| } | ||
| } |
| package co.apptailor.Worker.core; | ||
| import android.content.Context; | ||
| import com.facebook.react.bridge.CatalystInstance; | ||
| import com.facebook.react.bridge.CatalystInstanceImpl; | ||
| import com.facebook.react.bridge.JSBundleLoader; | ||
| import com.facebook.react.bridge.JSCJavaScriptExecutor; | ||
| import com.facebook.react.bridge.JavaScriptExecutor; | ||
| import com.facebook.react.bridge.JavaScriptModule; | ||
| import com.facebook.react.bridge.JavaScriptModulesConfig; | ||
| import com.facebook.react.bridge.NativeModule; | ||
| import com.facebook.react.bridge.NativeModuleCallExceptionHandler; | ||
| import com.facebook.react.bridge.NativeModuleRegistry; | ||
| import com.facebook.react.bridge.ReactApplicationContext; | ||
| import com.facebook.react.bridge.WritableNativeMap; | ||
| import com.facebook.react.bridge.queue.ReactQueueConfigurationSpec; | ||
| import com.facebook.soloader.SoLoader; | ||
| public class CatalystBuilder { | ||
| private String bundleName; | ||
| private Context parentContext; | ||
| private BaseModuleList moduleList; | ||
| private String sourceURL; | ||
| private String filePath; | ||
| public CatalystBuilder(Context context) { | ||
| this.parentContext = context; | ||
| SoLoader.init(context, /* native exopackage */ false); | ||
| } | ||
| public CatalystBuilder setBundleName(String bundleName) { | ||
| this.bundleName = bundleName; | ||
| return this; | ||
| } | ||
| public CatalystBuilder setSourceURL(String sourceURL) { | ||
| this.sourceURL = sourceURL; | ||
| return this; | ||
| } | ||
| public CatalystBuilder setBundleFilePath(String filePath) { | ||
| this.filePath = filePath; | ||
| return this; | ||
| } | ||
| public CatalystBuilder setModuleList(BaseModuleList moduleList) { | ||
| this.moduleList = moduleList; | ||
| return this; | ||
| } | ||
| public CatalystInstance build() throws Exception { | ||
| JSCJavaScriptExecutor.Factory factory = new JSCJavaScriptExecutor.Factory(); | ||
| // JSBundleLoader bundleLoader = JSBundleLoader.createFileLoader(parentContext, bundleName); | ||
| JSBundleLoader bundleLoader = JSBundleLoader.createCachedBundleFromNetworkLoader(sourceURL, filePath); | ||
| JavaScriptExecutor jsExecutor = factory.create(new WritableNativeMap()); | ||
| ReactApplicationContext reactContext = new ReactApplicationContext(parentContext); | ||
| NativeModuleRegistry.Builder nativeRegistryBuilder = new NativeModuleRegistry.Builder(); | ||
| addNativeModules(reactContext, nativeRegistryBuilder); | ||
| JavaScriptModulesConfig.Builder jsModulesBuilder = new JavaScriptModulesConfig.Builder(); | ||
| addJSModules(jsModulesBuilder); | ||
| NativeModuleRegistry nativeModuleRegistry = nativeRegistryBuilder.build(); | ||
| JavaScriptModulesConfig javaScriptModulesConfig = jsModulesBuilder.build(); | ||
| CatalystInstanceImpl.Builder catalystInstanceBuilder = new CatalystInstanceImpl.Builder() | ||
| .setReactQueueConfigurationSpec(ReactQueueConfigurationSpec.createDefault()) | ||
| .setJSExecutor(jsExecutor) | ||
| .setRegistry(nativeModuleRegistry) | ||
| .setJSModulesConfig(javaScriptModulesConfig) | ||
| .setJSBundleLoader(bundleLoader) | ||
| .setNativeModuleCallExceptionHandler(new NativeModuleCallExceptionHandler() { | ||
| @Override | ||
| public void handleException(Exception e) { | ||
| throw new RuntimeException(e); | ||
| } | ||
| }); | ||
| CatalystInstance catalystInstance = catalystInstanceBuilder.build(); | ||
| reactContext.initializeWithInstance(catalystInstance); | ||
| catalystInstance.runJSBundle(); | ||
| catalystInstance.initialize(); | ||
| reactContext.onHostResume(null); | ||
| return catalystInstance; | ||
| } | ||
| private void addJSModules(JavaScriptModulesConfig.Builder jsModulesBuilder) { | ||
| for (Class<? extends JavaScriptModule> jsModuleClass : moduleList.jsModules()) { | ||
| jsModulesBuilder.add(jsModuleClass); | ||
| } | ||
| } | ||
| private void addNativeModules(ReactApplicationContext reactContext, NativeModuleRegistry.Builder nativeRegistryBuilder) { | ||
| for (NativeModule nativeModule : moduleList.nativeModules(reactContext)) { | ||
| nativeRegistryBuilder.add(nativeModule); | ||
| } | ||
| } | ||
| } |
| package co.apptailor.Worker.core; | ||
| import com.facebook.react.bridge.ReactApplicationContext; | ||
| import com.facebook.react.bridge.ReactContextBaseJavaModule; | ||
| public class UIManagerStubModule extends ReactContextBaseJavaModule { | ||
| public UIManagerStubModule(ReactApplicationContext reactContext) { | ||
| super(reactContext); | ||
| } | ||
| @Override | ||
| public String getName() { | ||
| return "UIManager"; | ||
| } | ||
| } |
| package co.apptailor.Worker.core; | ||
| import com.facebook.react.bridge.ReactApplicationContext; | ||
| import com.facebook.react.bridge.ReactContextBaseJavaModule; | ||
| import com.facebook.react.bridge.ReactMethod; | ||
| import com.facebook.react.modules.core.DeviceEventManagerModule; | ||
| public class WorkerSelfModule extends ReactContextBaseJavaModule { | ||
| private int workerId; | ||
| private ReactApplicationContext parentContext; | ||
| public WorkerSelfModule(ReactApplicationContext context) { | ||
| super(context); | ||
| } | ||
| public void initialize(int workerId, ReactApplicationContext parentContext) { | ||
| this.parentContext = parentContext; | ||
| this.workerId = workerId; | ||
| } | ||
| @Override | ||
| public String getName() { | ||
| return "RNWorkerSelf"; | ||
| } | ||
| @ReactMethod | ||
| public void postMessage(String data) { | ||
| if (parentContext == null) { return; } | ||
| parentContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class) | ||
| .emit("Worker" + String.valueOf(workerId), data); | ||
| } | ||
| } |
| package co.apptailor.Worker; | ||
| import com.facebook.react.bridge.CatalystInstance; | ||
| import com.facebook.react.bridge.ReactApplicationContext; | ||
| import com.facebook.react.modules.core.DeviceEventManagerModule; | ||
| import java.util.Random; | ||
| import co.apptailor.Worker.core.CatalystBuilder; | ||
| import co.apptailor.Worker.core.BaseModuleList; | ||
| import co.apptailor.Worker.core.WorkerSelfModule; | ||
| public class JSWorker { | ||
| private int id; | ||
| private String bundleName; | ||
| private String sourceURL; | ||
| private String filePath; | ||
| private CatalystInstance catalystInstance; | ||
| public JSWorker(String bundleName, String sourceURL, String filePath) { | ||
| this.id = Math.abs(new Random().nextInt()); | ||
| this.bundleName = bundleName; | ||
| this.filePath = filePath; | ||
| this.sourceURL = sourceURL; | ||
| } | ||
| public int getWorkerId() { | ||
| return this.id; | ||
| } | ||
| public String getBundleName() { | ||
| return bundleName; | ||
| } | ||
| public String getFilePath() { | ||
| return filePath; | ||
| } | ||
| public void runFromContext(ReactApplicationContext context) throws Exception { | ||
| terminate(); | ||
| catalystInstance = new CatalystBuilder(context) | ||
| .setSourceURL(sourceURL) | ||
| .setBundleFilePath(filePath) | ||
| .setModuleList(new BaseModuleList()) | ||
| .build(); | ||
| WorkerSelfModule workerSelfModule = catalystInstance.getNativeModule(WorkerSelfModule.class); | ||
| if (workerSelfModule == null) { | ||
| throw new RuntimeException("Missing required WorkerSelfModule"); | ||
| } | ||
| workerSelfModule.initialize(id, context); | ||
| } | ||
| public void postMessage(String message) { | ||
| catalystInstance.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class) | ||
| .emit("WorkerMessage", message); | ||
| } | ||
| public void terminate() { | ||
| if (catalystInstance == null) { | ||
| return; | ||
| } | ||
| catalystInstance.destroy(); | ||
| catalystInstance = null; | ||
| } | ||
| } |
| package co.apptailor.Worker; | ||
| import android.app.Activity; | ||
| import android.os.Handler; | ||
| import android.util.Log; | ||
| import com.facebook.react.bridge.LifecycleEventListener; | ||
| 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.devsupport.DevInternalSettings; | ||
| import com.facebook.react.devsupport.DevServerHelper; | ||
| import java.io.File; | ||
| import java.util.HashMap; | ||
| public class WorkerModule extends ReactContextBaseJavaModule implements LifecycleEventListener { | ||
| private String TAG = "WorkerModule"; | ||
| private ReactApplicationContext context; | ||
| private HashMap<Integer, JSWorker> workers; | ||
| private DevServerHelper devServerHelper; | ||
| public WorkerModule(ReactApplicationContext reactContext) { | ||
| super(reactContext); | ||
| this.context = reactContext; | ||
| this.workers = new HashMap<>(); | ||
| context.addLifecycleEventListener(this); | ||
| } | ||
| @Override | ||
| public String getName() { | ||
| return "RNWorker"; | ||
| } | ||
| @ReactMethod | ||
| public void startWorker(String bundleName, final Promise promise) { | ||
| if (devServerHelper == null) { | ||
| DevInternalSettings devInternalSettings = new DevInternalSettings(context, null); | ||
| devInternalSettings.setHotModuleReplacementEnabled(false); | ||
| devInternalSettings.setElementInspectorEnabled(false); | ||
| devInternalSettings.setReloadOnJSChangeEnabled(false); | ||
| devServerHelper = new DevServerHelper(devInternalSettings); | ||
| } | ||
| String bundleSlug = bundleName; | ||
| if (bundleName.contains("/")) { | ||
| bundleSlug = bundleName.replaceAll("/", "_"); | ||
| } | ||
| final File bundleFile = new File(context.getFilesDir(), bundleSlug); | ||
| final JSWorker worker = new JSWorker(bundleName, devServerHelper.getSourceUrl(bundleName),bundleFile.getAbsolutePath()); | ||
| devServerHelper.downloadBundleFromURL(new DevServerHelper.BundleDownloadCallback() { | ||
| @Override | ||
| public void onSuccess() { | ||
| Activity activity = getCurrentActivity(); | ||
| if (activity == null) { | ||
| Log.d(TAG, "Worker startWorker - activity is null. aborting."); | ||
| return; | ||
| } | ||
| activity.runOnUiThread(new Runnable() { | ||
| @Override | ||
| public void run() { | ||
| try { | ||
| worker.runFromContext(context); | ||
| workers.put(worker.getWorkerId(), worker); | ||
| promise.resolve(worker.getWorkerId()); | ||
| } catch (Exception e) { | ||
| e.printStackTrace(); | ||
| promise.reject(e); | ||
| } | ||
| } | ||
| }); | ||
| } | ||
| @Override | ||
| public void onFailure(Exception cause) { | ||
| promise.reject(cause); | ||
| } | ||
| }, bundleName, bundleFile); | ||
| } | ||
| @ReactMethod | ||
| public void stopWorker(final int workerId) { | ||
| final JSWorker worker = workers.get(workerId); | ||
| if (worker == null) { | ||
| Log.d(TAG, "Cannot stop worker - worker is null for id " + workerId); | ||
| return; | ||
| } | ||
| new Handler().post(new Runnable() { | ||
| @Override | ||
| public void run() { | ||
| worker.terminate(); | ||
| workers.remove(workerId); | ||
| } | ||
| }); | ||
| } | ||
| @ReactMethod | ||
| public void postWorkerMessage(int workerId, String message) { | ||
| JSWorker worker = workers.get(workerId); | ||
| if (worker == null) { | ||
| Log.d(TAG, "Cannot post message to worker - worker is null for id " + workerId); | ||
| return; | ||
| } | ||
| worker.postMessage(message); | ||
| } | ||
| @Override | ||
| public void onHostResume() {} | ||
| @Override | ||
| public void onHostPause() {} | ||
| @Override | ||
| public void onHostDestroy() { | ||
| Log.d(TAG, "Clean JS Workers"); | ||
| new Handler().post(new Runnable() { | ||
| @Override | ||
| public void run() { | ||
| for (int workerId : workers.keySet()) { | ||
| workers.get(workerId).terminate(); | ||
| } | ||
| } | ||
| }); | ||
| } | ||
| @Override | ||
| public void onCatalystInstanceDestroy() { | ||
| super.onCatalystInstanceDestroy(); | ||
| onHostDestroy(); | ||
| } | ||
| } |
| package co.apptailor.Worker; | ||
| import com.facebook.react.ReactPackage; | ||
| import com.facebook.react.bridge.JavaScriptModule; | ||
| import com.facebook.react.bridge.NativeModule; | ||
| import com.facebook.react.bridge.ReactApplicationContext; | ||
| import com.facebook.react.uimanager.ViewManager; | ||
| import java.util.ArrayList; | ||
| import java.util.Collections; | ||
| import java.util.List; | ||
| public class WorkerPackage implements ReactPackage { | ||
| public WorkerPackage() { | ||
| super(); | ||
| } | ||
| @Override | ||
| public List<Class<? extends JavaScriptModule>> createJSModules() { | ||
| return Collections.emptyList(); | ||
| } | ||
| @Override | ||
| public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) { | ||
| return Collections.emptyList(); | ||
| } | ||
| @Override | ||
| public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) { | ||
| List<NativeModule> modules = new ArrayList<>(); | ||
| modules.add(new WorkerModule(reactContext)); | ||
| return modules; | ||
| } | ||
| } |
+1
-1
| { | ||
| "name": "react-native-workers", | ||
| "version": "0.1.2", | ||
| "version": "0.2.0", | ||
| "description": "react native web workers", | ||
@@ -5,0 +5,0 @@ "main": "main", |
+52
-6
@@ -6,5 +6,5 @@ # react-native-workers | ||
| ## Features | ||
| - JS workers run on both iOS and Android | ||
| - access all native modules from the workers (network, geolocation, async storage ...) | ||
| - write your Android Services in JS :tada: | ||
| - JS workers for iOS and Android | ||
| - access to native modules (network, geolocation, storage ...) | ||
| - Android Services in JS :tada: | ||
@@ -27,7 +27,53 @@ ## Warning | ||
| 1. Open your project in XCode, right click on Libraries and click Add Files to "Your Project Name". Look under node_modules/react-native-workers/ios and add `Workers.xcodeproj`. | ||
| 2. Add `libWorkers.a` to `Build Phases -> Link Binary With Libraries. | ||
| 1. Open your project in XCode, right click on Libraries and click Add Files to "Your Project Name". Look under node_modules/react-native-workers/ios and add `Workers.xcodeproj` | ||
| 2. Add `libWorkers.a` to `Build Phases -> Link Binary With Libraries` | ||
| ## API | ||
| ### Android | ||
| in `android/settings.gradle` | ||
| ``` | ||
| include ':app', ':react-native-workers' | ||
| project(':react-native-workers').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-workers/android') | ||
| ``` | ||
| in `android/app/build.gradle` add: | ||
| ``` | ||
| dependencies { | ||
| ... | ||
| compile project(':react-native-workers') | ||
| } | ||
| ``` | ||
| and finally, in your `MainActivity.java` add: | ||
| ```java | ||
| import co.apptailor.Worker.WorkerPackage; // <--- This! | ||
| public class MainActivity extends ReactActivity { | ||
| @Override | ||
| protected String getMainComponentName() { | ||
| return "MyApp"; | ||
| } | ||
| @Override | ||
| protected boolean getUseDeveloperSupport() { | ||
| return BuildConfig.DEBUG; | ||
| } | ||
| @Override | ||
| protected List<ReactPackage> getPackages() { | ||
| return Arrays.<ReactPackage>asList( | ||
| new MainReactPackage(), | ||
| new WorkerPackage() // <---- and This! | ||
| ); | ||
| } | ||
| } | ||
| ``` | ||
| ## JS API | ||
| From your application: | ||
@@ -34,0 +80,0 @@ ```js |
Sorry, the diff of this file is not supported yet
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
34972
94.46%22
69.23%426
769.39%110
71.88%1
Infinity%