@capawesome/capacitor-nodejs 0.0.1

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.
Files changed (50) hide show
  1. package/Package.swift +42 -0
  2. package/README.md +443 -0
  3. package/android/CMakeLists.txt +32 -0
  4. package/android/build.gradle +123 -0
  5. package/android/src/main/AndroidManifest.xml +2 -0
  6. package/android/src/main/cpp/native-lib.cpp +192 -0
  7. package/android/src/main/java/io/capawesome/capacitorjs/plugins/nodejs/Nodejs.java +256 -0
  8. package/android/src/main/java/io/capawesome/capacitorjs/plugins/nodejs/NodejsConfig.java +30 -0
  9. package/android/src/main/java/io/capawesome/capacitorjs/plugins/nodejs/NodejsPlugin.java +159 -0
  10. package/android/src/main/java/io/capawesome/capacitorjs/plugins/nodejs/classes/CustomException.java +20 -0
  11. package/android/src/main/java/io/capawesome/capacitorjs/plugins/nodejs/classes/CustomExceptions.java +23 -0
  12. package/android/src/main/java/io/capawesome/capacitorjs/plugins/nodejs/classes/events/MessageEvent.java +27 -0
  13. package/android/src/main/java/io/capawesome/capacitorjs/plugins/nodejs/classes/options/SendOptions.java +48 -0
  14. package/android/src/main/java/io/capawesome/capacitorjs/plugins/nodejs/classes/options/StartOptions.java +81 -0
  15. package/android/src/main/java/io/capawesome/capacitorjs/plugins/nodejs/classes/results/IsReadyResult.java +22 -0
  16. package/android/src/main/java/io/capawesome/capacitorjs/plugins/nodejs/interfaces/Callback.java +5 -0
  17. package/android/src/main/java/io/capawesome/capacitorjs/plugins/nodejs/interfaces/EmptyCallback.java +5 -0
  18. package/android/src/main/java/io/capawesome/capacitorjs/plugins/nodejs/interfaces/NonEmptyResultCallback.java +7 -0
  19. package/android/src/main/java/io/capawesome/capacitorjs/plugins/nodejs/interfaces/Result.java +7 -0
  20. package/android/src/main/res/.gitkeep +0 -0
  21. package/dist/docs.json +432 -0
  22. package/dist/esm/definitions.d.ts +214 -0
  23. package/dist/esm/definitions.js +38 -0
  24. package/dist/esm/definitions.js.map +1 -0
  25. package/dist/esm/index.d.ts +4 -0
  26. package/dist/esm/index.js +7 -0
  27. package/dist/esm/index.js.map +1 -0
  28. package/dist/esm/web.d.ts +8 -0
  29. package/dist/esm/web.js +16 -0
  30. package/dist/esm/web.js.map +1 -0
  31. package/dist/plugin.cjs.js +68 -0
  32. package/dist/plugin.cjs.js.map +1 -0
  33. package/dist/plugin.js +71 -0
  34. package/dist/plugin.js.map +1 -0
  35. package/ios/Plugin/Assets/builtin_modules/bridge/index.js +205 -0
  36. package/ios/Plugin/Classes/Events/MessageEvent.swift +19 -0
  37. package/ios/Plugin/Classes/Options/SendOptions.swift +19 -0
  38. package/ios/Plugin/Classes/Options/StartOptions.swift +32 -0
  39. package/ios/Plugin/Classes/Results/IsReadyResult.swift +16 -0
  40. package/ios/Plugin/Enums/CustomError.swift +46 -0
  41. package/ios/Plugin/Nodejs.swift +148 -0
  42. package/ios/Plugin/NodejsConfig.swift +6 -0
  43. package/ios/Plugin/NodejsPlugin.swift +102 -0
  44. package/ios/Plugin/Protocols/Result.swift +6 -0
  45. package/ios/PluginNative/NodeRunner.mm +221 -0
  46. package/ios/PluginNative/bridge.cpp +225 -0
  47. package/ios/PluginNative/bridge.h +15 -0
  48. package/ios/PluginNative/include/NodeRunner.h +12 -0
  49. package/package.json +93 -0
  50. package/scripts/postinstall.js +109 -0
@@ -0,0 +1,192 @@
1
+ /*
2
+ JNI layer between the plugin's Java code and the Node.js engine.
3
+
4
+ Based on the `nodejs-mobile-react-native` project
5
+ (MIT licensed, https://github.com/nodejs-mobile/nodejs-mobile-react-native).
6
+ */
7
+ #include <jni.h>
8
+ #include <string>
9
+ #include <cstdlib>
10
+ #include <pthread.h>
11
+ #include <unistd.h>
12
+ #include <android/log.h>
13
+
14
+ #include "node.h"
15
+ #include "bridge.h"
16
+
17
+ #define LOG_TAG "NodejsPlugin"
18
+
19
+ // Cache the environment variable for the thread running Node.js to call into Java.
20
+ JNIEnv* cacheEnvPointer = NULL;
21
+
22
+ extern "C"
23
+ JNIEXPORT void JNICALL
24
+ Java_io_capawesome_capacitorjs_plugins_nodejs_Nodejs_sendMessageToNodeChannel(
25
+ JNIEnv *env,
26
+ jclass /* clazz */,
27
+ jstring channelName,
28
+ jstring msg) {
29
+ const char* nativeChannelName = env->GetStringUTFChars(channelName, 0);
30
+ const char* nativeMessage = env->GetStringUTFChars(msg, 0);
31
+ capacitor_bridge_notify(nativeChannelName, nativeMessage);
32
+ env->ReleaseStringUTFChars(channelName, nativeChannelName);
33
+ env->ReleaseStringUTFChars(msg, nativeMessage);
34
+ }
35
+
36
+ extern "C"
37
+ JNIEXPORT void JNICALL
38
+ Java_io_capawesome_capacitorjs_plugins_nodejs_Nodejs_registerNodeDataDirPath(
39
+ JNIEnv *env,
40
+ jclass /* clazz */,
41
+ jstring dataDir) {
42
+ const char* nativeDataDir = env->GetStringUTFChars(dataDir, 0);
43
+ capacitor_register_node_data_dir_path(nativeDataDir);
44
+ env->ReleaseStringUTFChars(dataDir, nativeDataDir);
45
+ }
46
+
47
+ void rcv_message(const char* channel_name, const char* msg) {
48
+ JNIEnv *env = cacheEnvPointer;
49
+ if (!env) {
50
+ return;
51
+ }
52
+ jclass cls = env->FindClass("io/capawesome/capacitorjs/plugins/nodejs/Nodejs");
53
+ if (cls != nullptr) {
54
+ jmethodID m_onMessageReceived = env->GetStaticMethodID(cls, "onMessageReceived", "(Ljava/lang/String;Ljava/lang/String;)V");
55
+ if (m_onMessageReceived != nullptr) {
56
+ jstring java_channel_name = env->NewStringUTF(channel_name);
57
+ jstring java_msg = env->NewStringUTF(msg);
58
+ env->CallStaticVoidMethod(cls, m_onMessageReceived, java_channel_name, java_msg);
59
+ env->DeleteLocalRef(java_channel_name);
60
+ env->DeleteLocalRef(java_msg);
61
+ }
62
+ }
63
+ env->DeleteLocalRef(cls);
64
+ }
65
+
66
+ // Threads to redirect stdout and stderr to logcat.
67
+ int pipe_stdout[2];
68
+ int pipe_stderr[2];
69
+ pthread_t thread_stdout;
70
+ pthread_t thread_stderr;
71
+
72
+ void *thread_stderr_func(void*) {
73
+ ssize_t redirect_size;
74
+ char buf[2048];
75
+ while ((redirect_size = read(pipe_stderr[0], buf, sizeof buf - 1)) > 0) {
76
+ // __android_log will add a new line anyway.
77
+ if (buf[redirect_size - 1] == '\n') {
78
+ --redirect_size;
79
+ }
80
+ buf[redirect_size] = 0;
81
+ __android_log_write(ANDROID_LOG_ERROR, LOG_TAG, buf);
82
+ }
83
+ return 0;
84
+ }
85
+
86
+ void *thread_stdout_func(void*) {
87
+ ssize_t redirect_size;
88
+ char buf[2048];
89
+ while ((redirect_size = read(pipe_stdout[0], buf, sizeof buf - 1)) > 0) {
90
+ // __android_log will add a new line anyway.
91
+ if (buf[redirect_size - 1] == '\n') {
92
+ --redirect_size;
93
+ }
94
+ buf[redirect_size] = 0;
95
+ __android_log_write(ANDROID_LOG_INFO, LOG_TAG, buf);
96
+ }
97
+ return 0;
98
+ }
99
+
100
+ int start_redirecting_stdout_stderr() {
101
+ // Set stdout as unbuffered.
102
+ setvbuf(stdout, 0, _IONBF, 0);
103
+ pipe(pipe_stdout);
104
+ dup2(pipe_stdout[1], STDOUT_FILENO);
105
+
106
+ // Set stderr as unbuffered.
107
+ setvbuf(stderr, 0, _IONBF, 0);
108
+ pipe(pipe_stderr);
109
+ dup2(pipe_stderr[1], STDERR_FILENO);
110
+
111
+ if (pthread_create(&thread_stdout, 0, thread_stdout_func, 0) == -1) {
112
+ return -1;
113
+ }
114
+ pthread_detach(thread_stdout);
115
+
116
+ if (pthread_create(&thread_stderr, 0, thread_stderr_func, 0) == -1) {
117
+ return -1;
118
+ }
119
+ pthread_detach(thread_stderr);
120
+
121
+ return 0;
122
+ }
123
+
124
+ // Node's libuv requires all arguments being on contiguous memory.
125
+ extern "C"
126
+ JNIEXPORT jint JNICALL
127
+ Java_io_capawesome_capacitorjs_plugins_nodejs_Nodejs_startNodeWithArguments(
128
+ JNIEnv *env,
129
+ jclass /* clazz */,
130
+ jobjectArray arguments,
131
+ jstring nodePath,
132
+ jboolean redirectOutputToLogcat) {
133
+
134
+ // Set the NODE_PATH environment variable.
135
+ const char* node_path = env->GetStringUTFChars(nodePath, 0);
136
+ setenv("NODE_PATH", node_path, 1);
137
+ env->ReleaseStringUTFChars(nodePath, node_path);
138
+
139
+ jsize argument_count = env->GetArrayLength(arguments);
140
+
141
+ // Compute the byte size needed for all arguments in contiguous memory.
142
+ int c_arguments_size = 0;
143
+ for (int i = 0; i < argument_count; i++) {
144
+ jstring argument = (jstring)env->GetObjectArrayElement(arguments, i);
145
+ const char* current_argument = env->GetStringUTFChars(argument, 0);
146
+ c_arguments_size += strlen(current_argument);
147
+ c_arguments_size++; // for '\0'
148
+ env->ReleaseStringUTFChars(argument, current_argument);
149
+ env->DeleteLocalRef(argument);
150
+ }
151
+
152
+ // Store the arguments in contiguous memory.
153
+ char* args_buffer = (char*)calloc(c_arguments_size, sizeof(char));
154
+
155
+ // The argv to pass into Node.js.
156
+ char* argv[argument_count];
157
+
158
+ // Iterate through the expected start position of each argument in args_buffer.
159
+ char* current_args_position = args_buffer;
160
+
161
+ // Populate the args_buffer and argv.
162
+ for (int i = 0; i < argument_count; i++) {
163
+ jstring argument = (jstring)env->GetObjectArrayElement(arguments, i);
164
+ const char* current_argument = env->GetStringUTFChars(argument, 0);
165
+
166
+ // Copy the current argument to its expected position in args_buffer.
167
+ strncpy(current_args_position, current_argument, strlen(current_argument));
168
+
169
+ // Save the current argument start position in argv.
170
+ argv[i] = current_args_position;
171
+
172
+ // Increment to the next argument's expected position.
173
+ current_args_position += strlen(current_args_position) + 1;
174
+
175
+ env->ReleaseStringUTFChars(argument, current_argument);
176
+ env->DeleteLocalRef(argument);
177
+ }
178
+
179
+ capacitor_register_bridge_cb(&rcv_message);
180
+
181
+ cacheEnvPointer = env;
182
+
183
+ if (redirectOutputToLogcat) {
184
+ if (start_redirecting_stdout_stderr() == -1) {
185
+ __android_log_write(ANDROID_LOG_ERROR, LOG_TAG, "Couldn't start redirecting stdout and stderr to logcat.");
186
+ }
187
+ }
188
+
189
+ // Start Node.js, with argc and argv. This call blocks until the
190
+ // Node.js event loop exits.
191
+ return jint(node::Start(argument_count, argv));
192
+ }
@@ -0,0 +1,256 @@
1
+ package io.capawesome.capacitorjs.plugins.nodejs;
2
+
3
+ import android.content.Context;
4
+ import android.content.SharedPreferences;
5
+ import android.content.pm.PackageInfo;
6
+ import android.content.res.AssetManager;
7
+ import android.system.Os;
8
+ import androidx.annotation.NonNull;
9
+ import androidx.annotation.Nullable;
10
+ import com.getcapacitor.JSArray;
11
+ import com.getcapacitor.Logger;
12
+ import io.capawesome.capacitorjs.plugins.nodejs.classes.CustomExceptions;
13
+ import io.capawesome.capacitorjs.plugins.nodejs.classes.events.MessageEvent;
14
+ import io.capawesome.capacitorjs.plugins.nodejs.classes.options.SendOptions;
15
+ import io.capawesome.capacitorjs.plugins.nodejs.classes.options.StartOptions;
16
+ import io.capawesome.capacitorjs.plugins.nodejs.interfaces.EmptyCallback;
17
+ import java.io.File;
18
+ import java.io.FileOutputStream;
19
+ import java.io.InputStream;
20
+ import java.io.OutputStream;
21
+ import java.nio.charset.StandardCharsets;
22
+ import java.util.ArrayList;
23
+ import java.util.List;
24
+ import java.util.Map;
25
+ import org.json.JSONObject;
26
+
27
+ public class Nodejs {
28
+
29
+ private static final String ASSETS_BUILTIN_MODULES_DIR = "builtin_modules";
30
+ private static final String BASE_DIR = "capawesome_nodejs";
31
+ private static final String CHANNEL_EVENTS = "_EVENTS_";
32
+ private static final String CHANNEL_SYSTEM = "_SYSTEM_";
33
+ private static final String SHARED_PREFS = "CapawesomeNodejsPlugin";
34
+ private static final String SHARED_PREFS_APK_LAST_UPDATE_TIME = "apkLastUpdateTime";
35
+ private static final String SYSTEM_MESSAGE_PAUSE = "pause";
36
+ private static final String SYSTEM_MESSAGE_READY = "ready-for-app-events";
37
+ private static final String SYSTEM_MESSAGE_RESUME = "resume";
38
+
39
+ @Nullable
40
+ private static Nodejs instance;
41
+
42
+ static {
43
+ System.loadLibrary("node");
44
+ System.loadLibrary("capacitor-nodejs");
45
+ }
46
+
47
+ @NonNull
48
+ private final NodejsConfig config;
49
+
50
+ @NonNull
51
+ private final Context context;
52
+
53
+ @NonNull
54
+ private final NodejsPlugin plugin;
55
+
56
+ private volatile boolean ready = false;
57
+
58
+ private volatile boolean started = false;
59
+
60
+ public Nodejs(@NonNull NodejsPlugin plugin, @NonNull Context context, @NonNull NodejsConfig config) {
61
+ this.config = config;
62
+ this.context = context;
63
+ this.plugin = plugin;
64
+ Nodejs.instance = this;
65
+ }
66
+
67
+ public boolean isReady() {
68
+ return ready;
69
+ }
70
+
71
+ public void send(@NonNull SendOptions options) throws Exception {
72
+ if (!ready) {
73
+ throw CustomExceptions.NODE_NOT_READY;
74
+ }
75
+ JSONObject envelope = new JSONObject();
76
+ envelope.put("event", options.getEventName());
77
+ envelope.put("payload", options.getArgs().toString());
78
+ sendMessageToNodeChannel(CHANNEL_EVENTS, envelope.toString());
79
+ }
80
+
81
+ public synchronized void start(@NonNull StartOptions options, @NonNull EmptyCallback callback) {
82
+ if (started) {
83
+ callback.error(CustomExceptions.NODE_ALREADY_RUNNING);
84
+ return;
85
+ }
86
+ started = true;
87
+ Thread thread = new Thread(() -> {
88
+ try {
89
+ File projectDir = new File(getBaseDir(), "nodejs-project");
90
+ File builtinModulesDir = new File(getBaseDir(), ASSETS_BUILTIN_MODULES_DIR);
91
+ copyNodeProjectFromApk(projectDir, builtinModulesDir);
92
+ File scriptFile = resolveScriptFile(projectDir, options.getScript());
93
+ for (Map.Entry<String, String> entry : options.getEnv().entrySet()) {
94
+ Os.setenv(entry.getKey(), entry.getValue(), true);
95
+ }
96
+ Os.setenv("TMPDIR", context.getCacheDir().getAbsolutePath(), true);
97
+ registerNodeDataDirPath(context.getFilesDir().getAbsolutePath());
98
+ List<String> args = new ArrayList<>();
99
+ args.add("node");
100
+ args.add(scriptFile.getAbsolutePath());
101
+ args.addAll(options.getArgs());
102
+ String nodePath = projectDir.getAbsolutePath() + ":" + builtinModulesDir.getAbsolutePath();
103
+ callback.success();
104
+ startNodeWithArguments(args.toArray(new String[0]), nodePath, true);
105
+ } catch (Exception exception) {
106
+ started = false;
107
+ callback.error(exception);
108
+ }
109
+ });
110
+ thread.start();
111
+ }
112
+
113
+ public void handleOnPause() {
114
+ if (ready) {
115
+ sendMessageToNodeChannel(CHANNEL_SYSTEM, SYSTEM_MESSAGE_PAUSE);
116
+ }
117
+ }
118
+
119
+ public void handleOnResume() {
120
+ if (ready) {
121
+ sendMessageToNodeChannel(CHANNEL_SYSTEM, SYSTEM_MESSAGE_RESUME);
122
+ }
123
+ }
124
+
125
+ @SuppressWarnings("unused")
126
+ private static void onMessageReceived(@NonNull String channelName, @NonNull String message) {
127
+ Nodejs instance = Nodejs.instance;
128
+ if (instance == null) {
129
+ return;
130
+ }
131
+ try {
132
+ instance.handleMessage(channelName, message);
133
+ } catch (Exception exception) {
134
+ Logger.error(NodejsPlugin.TAG, "Failed to handle message from the Node.js runtime.", exception);
135
+ }
136
+ }
137
+
138
+ private void handleMessage(@NonNull String channelName, @NonNull String message) throws Exception {
139
+ if (CHANNEL_SYSTEM.equals(channelName)) {
140
+ if (SYSTEM_MESSAGE_READY.equals(message)) {
141
+ ready = true;
142
+ plugin.notifyReadyListeners();
143
+ }
144
+ } else if (CHANNEL_EVENTS.equals(channelName)) {
145
+ JSONObject envelope = new JSONObject(message);
146
+ String eventName = envelope.getString("event");
147
+ JSArray args = new JSArray(envelope.optString("payload", "[]"));
148
+ plugin.notifyMessageListeners(new MessageEvent(eventName, args));
149
+ }
150
+ }
151
+
152
+ private void copyNodeProjectFromApk(@NonNull File projectDir, @NonNull File builtinModulesDir) throws Exception {
153
+ AssetManager assetManager = context.getAssets();
154
+ String projectAssetsPath = "public/" + config.getNodeDir();
155
+ String[] projectAssets = assetManager.list(projectAssetsPath);
156
+ if (projectAssets == null || projectAssets.length == 0) {
157
+ throw CustomExceptions.PROJECT_NOT_FOUND;
158
+ }
159
+ SharedPreferences sharedPreferences = context.getSharedPreferences(SHARED_PREFS, Context.MODE_PRIVATE);
160
+ long previousLastUpdateTime = sharedPreferences.getLong(SHARED_PREFS_APK_LAST_UPDATE_TIME, 0);
161
+ PackageInfo packageInfo = context.getPackageManager().getPackageInfo(context.getPackageName(), 0);
162
+ long lastUpdateTime = packageInfo.lastUpdateTime;
163
+ if (lastUpdateTime != previousLastUpdateTime || !projectDir.exists()) {
164
+ deleteRecursively(projectDir);
165
+ deleteRecursively(builtinModulesDir);
166
+ copyAssetDirectory(assetManager, projectAssetsPath, projectDir);
167
+ copyAssetDirectory(assetManager, ASSETS_BUILTIN_MODULES_DIR, builtinModulesDir);
168
+ sharedPreferences.edit().putLong(SHARED_PREFS_APK_LAST_UPDATE_TIME, lastUpdateTime).apply();
169
+ }
170
+ }
171
+
172
+ @NonNull
173
+ private File resolveScriptFile(@NonNull File projectDir, @Nullable String script) throws Exception {
174
+ String scriptPath = script;
175
+ if (scriptPath == null) {
176
+ scriptPath = getMainFromPackageJson(projectDir);
177
+ }
178
+ if (scriptPath == null) {
179
+ scriptPath = "index.js";
180
+ }
181
+ File scriptFile = new File(projectDir, scriptPath);
182
+ if (!scriptFile.exists()) {
183
+ throw CustomExceptions.SCRIPT_NOT_FOUND;
184
+ }
185
+ return scriptFile;
186
+ }
187
+
188
+ @Nullable
189
+ private String getMainFromPackageJson(@NonNull File projectDir) {
190
+ try {
191
+ File packageJsonFile = new File(projectDir, "package.json");
192
+ if (!packageJsonFile.exists()) {
193
+ return null;
194
+ }
195
+ byte[] bytes = new byte[(int) packageJsonFile.length()];
196
+ try (InputStream inputStream = new java.io.FileInputStream(packageJsonFile)) {
197
+ int read = inputStream.read(bytes);
198
+ if (read != bytes.length) {
199
+ return null;
200
+ }
201
+ }
202
+ JSONObject packageJson = new JSONObject(new String(bytes, StandardCharsets.UTF_8));
203
+ return packageJson.optString("main", null);
204
+ } catch (Exception exception) {
205
+ return null;
206
+ }
207
+ }
208
+
209
+ @NonNull
210
+ private File getBaseDir() {
211
+ return new File(context.getFilesDir(), BASE_DIR);
212
+ }
213
+
214
+ private void copyAssetDirectory(@NonNull AssetManager assetManager, @NonNull String assetPath, @NonNull File targetDir)
215
+ throws Exception {
216
+ String[] children = assetManager.list(assetPath);
217
+ if (children == null || children.length == 0) {
218
+ copyAssetFile(assetManager, assetPath, targetDir);
219
+ } else {
220
+ if (!targetDir.exists() && !targetDir.mkdirs()) {
221
+ throw new Exception("Failed to create directory: " + targetDir.getAbsolutePath());
222
+ }
223
+ for (String child : children) {
224
+ copyAssetDirectory(assetManager, assetPath + "/" + child, new File(targetDir, child));
225
+ }
226
+ }
227
+ }
228
+
229
+ private void copyAssetFile(@NonNull AssetManager assetManager, @NonNull String assetPath, @NonNull File targetFile) throws Exception {
230
+ try (InputStream inputStream = assetManager.open(assetPath); OutputStream outputStream = new FileOutputStream(targetFile)) {
231
+ byte[] buffer = new byte[8192];
232
+ int length;
233
+ while ((length = inputStream.read(buffer)) > 0) {
234
+ outputStream.write(buffer, 0, length);
235
+ }
236
+ }
237
+ }
238
+
239
+ private void deleteRecursively(@NonNull File file) {
240
+ if (file.isDirectory()) {
241
+ File[] children = file.listFiles();
242
+ if (children != null) {
243
+ for (File child : children) {
244
+ deleteRecursively(child);
245
+ }
246
+ }
247
+ }
248
+ file.delete();
249
+ }
250
+
251
+ private static native void registerNodeDataDirPath(String dataDir);
252
+
253
+ private static native void sendMessageToNodeChannel(String channelName, String msg);
254
+
255
+ private static native int startNodeWithArguments(String[] arguments, String nodePath, boolean redirectOutputToLogcat);
256
+ }
@@ -0,0 +1,30 @@
1
+ package io.capawesome.capacitorjs.plugins.nodejs;
2
+
3
+ import androidx.annotation.NonNull;
4
+
5
+ public class NodejsConfig {
6
+
7
+ @NonNull
8
+ private String nodeDir = "nodejs";
9
+
10
+ @NonNull
11
+ private String startMode = "auto";
12
+
13
+ @NonNull
14
+ public String getNodeDir() {
15
+ return nodeDir;
16
+ }
17
+
18
+ public void setNodeDir(@NonNull String nodeDir) {
19
+ this.nodeDir = nodeDir;
20
+ }
21
+
22
+ @NonNull
23
+ public String getStartMode() {
24
+ return startMode;
25
+ }
26
+
27
+ public void setStartMode(@NonNull String startMode) {
28
+ this.startMode = startMode;
29
+ }
30
+ }
@@ -0,0 +1,159 @@
1
+ package io.capawesome.capacitorjs.plugins.nodejs;
2
+
3
+ import androidx.annotation.NonNull;
4
+ import androidx.annotation.Nullable;
5
+ import com.getcapacitor.JSObject;
6
+ import com.getcapacitor.Logger;
7
+ import com.getcapacitor.Plugin;
8
+ import com.getcapacitor.PluginCall;
9
+ import com.getcapacitor.PluginMethod;
10
+ import com.getcapacitor.annotation.CapacitorPlugin;
11
+ import io.capawesome.capacitorjs.plugins.nodejs.classes.CustomException;
12
+ import io.capawesome.capacitorjs.plugins.nodejs.classes.CustomExceptions;
13
+ import io.capawesome.capacitorjs.plugins.nodejs.classes.events.MessageEvent;
14
+ import io.capawesome.capacitorjs.plugins.nodejs.classes.options.SendOptions;
15
+ import io.capawesome.capacitorjs.plugins.nodejs.classes.options.StartOptions;
16
+ import io.capawesome.capacitorjs.plugins.nodejs.classes.results.IsReadyResult;
17
+ import io.capawesome.capacitorjs.plugins.nodejs.interfaces.EmptyCallback;
18
+ import io.capawesome.capacitorjs.plugins.nodejs.interfaces.Result;
19
+
20
+ @CapacitorPlugin(name = "Nodejs")
21
+ public class NodejsPlugin extends Plugin {
22
+
23
+ public static final String ERROR_UNKNOWN_ERROR = "An unknown error has occurred.";
24
+ public static final String EVENT_MESSAGE = "message";
25
+ public static final String EVENT_READY = "ready";
26
+ public static final String START_MODE_AUTO = "auto";
27
+ public static final String START_MODE_MANUAL = "manual";
28
+ public static final String TAG = "NodejsPlugin";
29
+
30
+ private NodejsConfig config;
31
+ private Nodejs implementation;
32
+
33
+ @Override
34
+ public void load() {
35
+ config = getNodejsConfig();
36
+ implementation = new Nodejs(this, getContext(), config);
37
+ if (START_MODE_AUTO.equals(config.getStartMode())) {
38
+ implementation.start(
39
+ new StartOptions(),
40
+ new EmptyCallback() {
41
+ @Override
42
+ public void success() {}
43
+
44
+ @Override
45
+ public void error(Exception exception) {
46
+ Logger.error(TAG, "Failed to start the Node.js runtime.", exception);
47
+ }
48
+ }
49
+ );
50
+ }
51
+ }
52
+
53
+ @PluginMethod
54
+ public void isReady(PluginCall call) {
55
+ try {
56
+ IsReadyResult result = new IsReadyResult(implementation.isReady());
57
+ resolveCall(call, result);
58
+ } catch (Exception exception) {
59
+ rejectCall(call, exception);
60
+ }
61
+ }
62
+
63
+ @PluginMethod
64
+ public void send(PluginCall call) {
65
+ try {
66
+ SendOptions options = new SendOptions(call);
67
+
68
+ implementation.send(options);
69
+ resolveCall(call);
70
+ } catch (Exception exception) {
71
+ rejectCall(call, exception);
72
+ }
73
+ }
74
+
75
+ @PluginMethod
76
+ public void start(PluginCall call) {
77
+ try {
78
+ if (!START_MODE_MANUAL.equals(config.getStartMode())) {
79
+ rejectCall(call, CustomExceptions.START_MODE_NOT_MANUAL);
80
+ return;
81
+ }
82
+ StartOptions options = new StartOptions(call);
83
+ EmptyCallback callback = new EmptyCallback() {
84
+ @Override
85
+ public void success() {
86
+ resolveCall(call);
87
+ }
88
+
89
+ @Override
90
+ public void error(Exception exception) {
91
+ rejectCall(call, exception);
92
+ }
93
+ };
94
+
95
+ implementation.start(options, callback);
96
+ } catch (Exception exception) {
97
+ rejectCall(call, exception);
98
+ }
99
+ }
100
+
101
+ public void notifyMessageListeners(@NonNull MessageEvent event) {
102
+ notifyListeners(EVENT_MESSAGE, event.toJSObject(), true);
103
+ }
104
+
105
+ public void notifyReadyListeners() {
106
+ notifyListeners(EVENT_READY, new JSObject(), true);
107
+ }
108
+
109
+ @Override
110
+ protected void handleOnPause() {
111
+ super.handleOnPause();
112
+ implementation.handleOnPause();
113
+ }
114
+
115
+ @Override
116
+ protected void handleOnResume() {
117
+ super.handleOnResume();
118
+ implementation.handleOnResume();
119
+ }
120
+
121
+ @NonNull
122
+ private NodejsConfig getNodejsConfig() {
123
+ NodejsConfig config = new NodejsConfig();
124
+ String nodeDir = getConfig().getString("nodeDir", null);
125
+ if (nodeDir != null) {
126
+ config.setNodeDir(nodeDir);
127
+ }
128
+ String startMode = getConfig().getString("startMode", null);
129
+ if (startMode != null) {
130
+ config.setStartMode(startMode);
131
+ }
132
+ return config;
133
+ }
134
+
135
+ private void rejectCall(@NonNull PluginCall call, @NonNull Exception exception) {
136
+ String message = exception.getMessage();
137
+ if (message == null) {
138
+ message = ERROR_UNKNOWN_ERROR;
139
+ }
140
+ String code = null;
141
+ if (exception instanceof CustomException) {
142
+ code = ((CustomException) exception).getCode();
143
+ }
144
+ Logger.error(TAG, message, exception);
145
+ call.reject(message, code);
146
+ }
147
+
148
+ private void resolveCall(@NonNull PluginCall call) {
149
+ call.resolve();
150
+ }
151
+
152
+ private void resolveCall(@NonNull PluginCall call, @Nullable Result result) {
153
+ if (result == null) {
154
+ call.resolve();
155
+ } else {
156
+ call.resolve(result.toJSObject());
157
+ }
158
+ }
159
+ }
@@ -0,0 +1,20 @@
1
+ package io.capawesome.capacitorjs.plugins.nodejs.classes;
2
+
3
+ import androidx.annotation.NonNull;
4
+ import androidx.annotation.Nullable;
5
+
6
+ public class CustomException extends Exception {
7
+
8
+ @Nullable
9
+ private final String code;
10
+
11
+ public CustomException(@Nullable String code, @NonNull String message) {
12
+ super(message);
13
+ this.code = code;
14
+ }
15
+
16
+ @Nullable
17
+ public String getCode() {
18
+ return code;
19
+ }
20
+ }
@@ -0,0 +1,23 @@
1
+ package io.capawesome.capacitorjs.plugins.nodejs.classes;
2
+
3
+ public class CustomExceptions {
4
+
5
+ public static final CustomException EVENT_NAME_MISSING = new CustomException("EVENT_NAME_MISSING", "eventName must be provided.");
6
+ public static final CustomException NODE_ALREADY_RUNNING = new CustomException(
7
+ "NODE_ALREADY_RUNNING",
8
+ "The Node.js runtime is already running."
9
+ );
10
+ public static final CustomException NODE_NOT_READY = new CustomException(
11
+ "NODE_NOT_READY",
12
+ "The Node.js runtime is not ready to receive messages."
13
+ );
14
+ public static final CustomException PROJECT_NOT_FOUND = new CustomException(
15
+ "PROJECT_NOT_FOUND",
16
+ "The Node.js project directory was not found."
17
+ );
18
+ public static final CustomException SCRIPT_NOT_FOUND = new CustomException("SCRIPT_NOT_FOUND", "The script file was not found.");
19
+ public static final CustomException START_MODE_NOT_MANUAL = new CustomException(
20
+ "START_MODE_NOT_MANUAL",
21
+ "The startMode configuration option must be set to 'manual'."
22
+ );
23
+ }