@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,19 @@
1
+ import Foundation
2
+ import Capacitor
3
+
4
+ @objc public class MessageEvent: NSObject {
5
+ let args: [Any]
6
+ let eventName: String
7
+
8
+ init(eventName: String, args: [Any]) {
9
+ self.args = args
10
+ self.eventName = eventName
11
+ }
12
+
13
+ @objc public func toJSObject() -> AnyObject {
14
+ var result = JSObject()
15
+ result["args"] = JSTypes.coerceArrayToJSArray(args) ?? []
16
+ result["eventName"] = eventName
17
+ return result as AnyObject
18
+ }
19
+ }
@@ -0,0 +1,19 @@
1
+ import Foundation
2
+ import Capacitor
3
+
4
+ @objc public class SendOptions: NSObject {
5
+ let args: [Any]
6
+ let eventName: String
7
+
8
+ init(_ call: CAPPluginCall) throws {
9
+ self.args = call.getArray("args") ?? []
10
+ self.eventName = try SendOptions.getEventNameFromCall(call)
11
+ }
12
+
13
+ private static func getEventNameFromCall(_ call: CAPPluginCall) throws -> String {
14
+ guard let eventName = call.getString("eventName") else {
15
+ throw CustomError.eventNameMissing
16
+ }
17
+ return eventName
18
+ }
19
+ }
@@ -0,0 +1,32 @@
1
+ import Foundation
2
+ import Capacitor
3
+
4
+ @objc public class StartOptions: NSObject {
5
+ let args: [String]
6
+ let env: [String: String]
7
+ let script: String?
8
+
9
+ override init() {
10
+ self.args = []
11
+ self.env = [:]
12
+ self.script = nil
13
+ }
14
+
15
+ init(_ call: CAPPluginCall) {
16
+ self.args = call.getArray("args", String.self) ?? []
17
+ self.env = StartOptions.getEnvFromCall(call)
18
+ self.script = call.getString("script")
19
+ }
20
+
21
+ private static func getEnvFromCall(_ call: CAPPluginCall) -> [String: String] {
22
+ var env: [String: String] = [:]
23
+ if let envObject = call.getObject("env") {
24
+ for (key, value) in envObject {
25
+ if let value = value as? String {
26
+ env[key] = value
27
+ }
28
+ }
29
+ }
30
+ return env
31
+ }
32
+ }
@@ -0,0 +1,16 @@
1
+ import Foundation
2
+ import Capacitor
3
+
4
+ @objc public class IsReadyResult: NSObject, Result {
5
+ let ready: Bool
6
+
7
+ init(ready: Bool) {
8
+ self.ready = ready
9
+ }
10
+
11
+ @objc public func toJSObject() -> AnyObject {
12
+ var result = JSObject()
13
+ result["ready"] = ready
14
+ return result as AnyObject
15
+ }
16
+ }
@@ -0,0 +1,46 @@
1
+ import Foundation
2
+
3
+ enum CustomError: Error {
4
+ case eventNameMissing
5
+ case nodeAlreadyRunning
6
+ case nodeNotReady
7
+ case projectNotFound
8
+ case scriptNotFound
9
+ case startModeNotManual
10
+
11
+ var code: String? {
12
+ switch self {
13
+ case .eventNameMissing:
14
+ return "EVENT_NAME_MISSING"
15
+ case .nodeAlreadyRunning:
16
+ return "NODE_ALREADY_RUNNING"
17
+ case .nodeNotReady:
18
+ return "NODE_NOT_READY"
19
+ case .projectNotFound:
20
+ return "PROJECT_NOT_FOUND"
21
+ case .scriptNotFound:
22
+ return "SCRIPT_NOT_FOUND"
23
+ case .startModeNotManual:
24
+ return "START_MODE_NOT_MANUAL"
25
+ }
26
+ }
27
+ }
28
+
29
+ extension CustomError: LocalizedError {
30
+ public var errorDescription: String? {
31
+ switch self {
32
+ case .eventNameMissing:
33
+ return NSLocalizedString("eventName must be provided.", comment: "eventNameMissing")
34
+ case .nodeAlreadyRunning:
35
+ return NSLocalizedString("The Node.js runtime is already running.", comment: "nodeAlreadyRunning")
36
+ case .nodeNotReady:
37
+ return NSLocalizedString("The Node.js runtime is not ready to receive messages.", comment: "nodeNotReady")
38
+ case .projectNotFound:
39
+ return NSLocalizedString("The Node.js project directory was not found.", comment: "projectNotFound")
40
+ case .scriptNotFound:
41
+ return NSLocalizedString("The script file was not found.", comment: "scriptNotFound")
42
+ case .startModeNotManual:
43
+ return NSLocalizedString("The startMode configuration option must be set to 'manual'.", comment: "startModeNotManual")
44
+ }
45
+ }
46
+ }
@@ -0,0 +1,148 @@
1
+ import Foundation
2
+ import Capacitor
3
+ import NodejsPluginNative
4
+
5
+ @objc public class Nodejs: NSObject {
6
+ private static let channelEvents = "_EVENTS_"
7
+ private static let channelSystem = "_SYSTEM_"
8
+ private static let systemMessageReady = "ready-for-app-events"
9
+
10
+ private let config: NodejsConfig
11
+ private weak var plugin: NodejsPlugin?
12
+
13
+ private var ready = false
14
+ private var started = false
15
+
16
+ init(plugin: NodejsPlugin, config: NodejsConfig) {
17
+ self.config = config
18
+ self.plugin = plugin
19
+ super.init()
20
+ NodeRunner.setMessageHandler { [weak self] channelName, message in
21
+ self?.handleMessage(channelName: channelName, message: message)
22
+ }
23
+ if let dataDirPath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).first {
24
+ NodeRunner.registerDataDirPath(dataDirPath)
25
+ }
26
+ }
27
+
28
+ @objc public func isReady() -> Bool {
29
+ return ready
30
+ }
31
+
32
+ @objc public func send(_ options: SendOptions) throws {
33
+ guard ready else {
34
+ throw CustomError.nodeNotReady
35
+ }
36
+ guard JSONSerialization.isValidJSONObject(options.args) else {
37
+ throw NSError(domain: "NodejsPlugin", code: 0, userInfo: [NSLocalizedDescriptionKey: "args must be JSON-serializable."])
38
+ }
39
+ let payload = try JSONSerialization.data(withJSONObject: options.args)
40
+ let envelope: [String: Any] = [
41
+ "event": options.eventName,
42
+ "payload": String(data: payload, encoding: .utf8) ?? "[]"
43
+ ]
44
+ let envelopeData = try JSONSerialization.data(withJSONObject: envelope)
45
+ guard let message = String(data: envelopeData, encoding: .utf8) else {
46
+ return
47
+ }
48
+ NodeRunner.sendMessage(Nodejs.channelEvents, message: message)
49
+ }
50
+
51
+ @objc public func start(_ options: StartOptions, completion: @escaping (_ error: Error?) -> Void) {
52
+ objc_sync_enter(self)
53
+ defer {
54
+ objc_sync_exit(self)
55
+ }
56
+ guard !started else {
57
+ completion(CustomError.nodeAlreadyRunning)
58
+ return
59
+ }
60
+ started = true
61
+ let thread = Thread { [self] in
62
+ do {
63
+ let projectPath = try self.getProjectPath()
64
+ let builtinModulesPath = self.getBuiltinModulesPath()
65
+ let scriptPath = try self.resolveScriptPath(projectPath: projectPath, script: options.script)
66
+ for (key, value) in options.env {
67
+ setenv(key, value, 1)
68
+ }
69
+ setenv("TMPDIR", NSTemporaryDirectory(), 1)
70
+ var arguments = ["node", scriptPath]
71
+ arguments.append(contentsOf: options.args)
72
+ var nodePath = projectPath
73
+ if let builtinModulesPath = builtinModulesPath {
74
+ nodePath += ":" + builtinModulesPath
75
+ }
76
+ completion(nil)
77
+ NodeRunner.startEngine(withArguments: arguments, nodePath: nodePath)
78
+ } catch {
79
+ self.started = false
80
+ completion(error)
81
+ }
82
+ }
83
+ thread.name = "NodejsPlugin"
84
+ thread.stackSize = 2 * 1024 * 1024
85
+ thread.start()
86
+ }
87
+
88
+ private func handleMessage(channelName: String, message: String) {
89
+ if channelName == Nodejs.channelSystem {
90
+ if message == Nodejs.systemMessageReady {
91
+ ready = true
92
+ plugin?.notifyReadyListeners()
93
+ }
94
+ } else if channelName == Nodejs.channelEvents {
95
+ guard let envelopeData = message.data(using: .utf8),
96
+ let envelope = try? JSONSerialization.jsonObject(with: envelopeData) as? [String: Any],
97
+ let eventName = envelope["event"] as? String else {
98
+ return
99
+ }
100
+ var args: [Any] = []
101
+ if let payload = envelope["payload"] as? String, let payloadData = payload.data(using: .utf8) {
102
+ args = (try? JSONSerialization.jsonObject(with: payloadData) as? [Any]) ?? []
103
+ }
104
+ plugin?.notifyMessageListeners(MessageEvent(eventName: eventName, args: args))
105
+ }
106
+ }
107
+
108
+ private func getProjectPath() throws -> String {
109
+ let projectPath = Bundle.main.bundlePath + "/public/" + config.nodeDir
110
+ guard FileManager.default.fileExists(atPath: projectPath) else {
111
+ throw CustomError.projectNotFound
112
+ }
113
+ return projectPath
114
+ }
115
+
116
+ private func getBuiltinModulesPath() -> String? {
117
+ #if SWIFT_PACKAGE
118
+ return Bundle.module.path(forResource: "builtin_modules", ofType: nil)
119
+ #else
120
+ return Bundle(for: Nodejs.self).path(forResource: "builtin_modules", ofType: nil)
121
+ ?? Bundle.main.path(forResource: "builtin_modules", ofType: nil)
122
+ #endif
123
+ }
124
+
125
+ private func resolveScriptPath(projectPath: String, script: String?) throws -> String {
126
+ var scriptFile = script
127
+ if scriptFile == nil {
128
+ scriptFile = getMainFromPackageJson(projectPath: projectPath)
129
+ }
130
+ if scriptFile == nil {
131
+ scriptFile = "index.js"
132
+ }
133
+ let scriptPath = projectPath + "/" + (scriptFile ?? "index.js")
134
+ guard FileManager.default.fileExists(atPath: scriptPath) else {
135
+ throw CustomError.scriptNotFound
136
+ }
137
+ return scriptPath
138
+ }
139
+
140
+ private func getMainFromPackageJson(projectPath: String) -> String? {
141
+ let packageJsonPath = projectPath + "/package.json"
142
+ guard let packageJsonData = FileManager.default.contents(atPath: packageJsonPath),
143
+ let packageJson = try? JSONSerialization.jsonObject(with: packageJsonData) as? [String: Any] else {
144
+ return nil
145
+ }
146
+ return packageJson["main"] as? String
147
+ }
148
+ }
@@ -0,0 +1,6 @@
1
+ import Foundation
2
+
3
+ public struct NodejsConfig {
4
+ var nodeDir = "nodejs"
5
+ var startMode = "auto"
6
+ }
@@ -0,0 +1,102 @@
1
+ import Foundation
2
+ import Capacitor
3
+
4
+ @objc(NodejsPlugin)
5
+ public class NodejsPlugin: CAPPlugin, CAPBridgedPlugin {
6
+ public let identifier = "NodejsPlugin"
7
+ public let jsName = "Nodejs"
8
+ public let pluginMethods: [CAPPluginMethod] = [
9
+ CAPPluginMethod(name: "isReady", returnType: CAPPluginReturnPromise),
10
+ CAPPluginMethod(name: "send", returnType: CAPPluginReturnPromise),
11
+ CAPPluginMethod(name: "start", returnType: CAPPluginReturnPromise)
12
+ ]
13
+
14
+ public static let eventMessage = "message"
15
+ public static let eventReady = "ready"
16
+ public static let startModeAuto = "auto"
17
+ public static let startModeManual = "manual"
18
+ public static let tag = "NodejsPlugin"
19
+
20
+ private var config = NodejsConfig()
21
+ private var implementation: Nodejs?
22
+
23
+ override public func load() {
24
+ config = getNodejsConfig()
25
+ implementation = Nodejs(plugin: self, config: config)
26
+ if config.startMode == NodejsPlugin.startModeAuto {
27
+ implementation?.start(StartOptions()) { error in
28
+ if let error = error {
29
+ CAPLog.print("[", NodejsPlugin.tag, "] ", error)
30
+ }
31
+ }
32
+ }
33
+ }
34
+
35
+ @objc func isReady(_ call: CAPPluginCall) {
36
+ let result = IsReadyResult(ready: implementation?.isReady() == true)
37
+ resolveCall(call, result)
38
+ }
39
+
40
+ @objc func send(_ call: CAPPluginCall) {
41
+ do {
42
+ let options = try SendOptions(call)
43
+
44
+ try implementation?.send(options)
45
+ resolveCall(call)
46
+ } catch {
47
+ rejectCall(call, error)
48
+ }
49
+ }
50
+
51
+ @objc func start(_ call: CAPPluginCall) {
52
+ guard config.startMode == NodejsPlugin.startModeManual else {
53
+ rejectCall(call, CustomError.startModeNotManual)
54
+ return
55
+ }
56
+ let options = StartOptions(call)
57
+
58
+ implementation?.start(options) { error in
59
+ if let error = error {
60
+ self.rejectCall(call, error)
61
+ } else {
62
+ self.resolveCall(call)
63
+ }
64
+ }
65
+ }
66
+
67
+ func notifyMessageListeners(_ event: MessageEvent) {
68
+ notifyListeners(NodejsPlugin.eventMessage, data: event.toJSObject() as? JSObject ?? JSObject(), retainUntilConsumed: true)
69
+ }
70
+
71
+ func notifyReadyListeners() {
72
+ notifyListeners(NodejsPlugin.eventReady, data: JSObject(), retainUntilConsumed: true)
73
+ }
74
+
75
+ private func getNodejsConfig() -> NodejsConfig {
76
+ var config = NodejsConfig()
77
+ if let nodeDir = getConfig().getString("nodeDir") {
78
+ config.nodeDir = nodeDir
79
+ }
80
+ if let startMode = getConfig().getString("startMode") {
81
+ config.startMode = startMode
82
+ }
83
+ return config
84
+ }
85
+
86
+ private func rejectCall(_ call: CAPPluginCall, _ error: Error) {
87
+ CAPLog.print("[", NodejsPlugin.tag, "] ", error)
88
+ call.reject(error.localizedDescription, (error as? CustomError)?.code)
89
+ }
90
+
91
+ private func resolveCall(_ call: CAPPluginCall) {
92
+ call.resolve()
93
+ }
94
+
95
+ private func resolveCall(_ call: CAPPluginCall, _ result: Result?) {
96
+ if let result = result?.toJSObject() as? JSObject {
97
+ call.resolve(result)
98
+ } else {
99
+ call.resolve()
100
+ }
101
+ }
102
+ }
@@ -0,0 +1,6 @@
1
+ import Foundation
2
+ import Capacitor
3
+
4
+ @objc public protocol Result {
5
+ @objc func toJSObject() -> AnyObject
6
+ }
@@ -0,0 +1,221 @@
1
+ /*
2
+ Runs the Node.js engine and handles the system channel messages.
3
+
4
+ Based on the `NodeRunner` of the `nodejs-mobile-react-native` project
5
+ (MIT licensed, https://github.com/nodejs-mobile/nodejs-mobile-react-native).
6
+ */
7
+ #import <UIKit/UIKit.h>
8
+ #import "NodeRunner.h"
9
+ #include <NodeMobile/NodeMobile.h>
10
+ #include <string>
11
+ #include "bridge.h"
12
+
13
+ static NSString *const SYSTEM_CHANNEL = @"_SYSTEM_";
14
+
15
+ static NodeRunnerMessageHandler _messageHandler = nil;
16
+
17
+ // Flag to indicate if the Node.js engine is ready to receive app events.
18
+ static bool nodeIsReadyForAppEvents = false;
19
+
20
+ // Condition to wait on pause event handling on the Node.js side.
21
+ static NSCondition *appEventBeingProcessedCondition = nil;
22
+
23
+ // Set to keep ids for called pause events, so they can be unlocked later.
24
+ static NSMutableSet *appPauseEventsManagerSet = nil;
25
+
26
+ // Lock to manipulate the app pause events manager set.
27
+ static id appPauseEventsManagerSetLock = nil;
28
+
29
+ @interface NodeRunner ()
30
+
31
+ + (void)handleSystemChannelMessage:(NSString *)msg;
32
+ + (void)onPause;
33
+ + (void)onResume;
34
+ + (void)releasePauseEvent:(NSString *)eventId;
35
+ + (void)sendPauseEventAndWaitForRelease:(NSDate *)expectedFinishTime;
36
+
37
+ @end
38
+
39
+ static void rcv_message(const char *channelName, const char *msg) {
40
+ @autoreleasepool {
41
+ NSString *objectiveCChannelName = [NSString stringWithUTF8String:channelName];
42
+ NSString *objectiveCMessage = [NSString stringWithUTF8String:msg];
43
+
44
+ if ([objectiveCChannelName isEqualToString:SYSTEM_CHANNEL]) {
45
+ [NodeRunner handleSystemChannelMessage:objectiveCMessage];
46
+ }
47
+ if (_messageHandler) {
48
+ _messageHandler(objectiveCChannelName, objectiveCMessage);
49
+ }
50
+ }
51
+ }
52
+
53
+ @implementation NodeRunner
54
+
55
+ + (void)registerDataDirPath:(NSString *)path {
56
+ capacitor_register_node_data_dir_path([path UTF8String]);
57
+ }
58
+
59
+ + (void)sendMessage:(NSString *)channelName message:(NSString *)message {
60
+ capacitor_bridge_notify([channelName UTF8String], [message UTF8String]);
61
+ }
62
+
63
+ + (void)setMessageHandler:(NodeRunnerMessageHandler)handler {
64
+ _messageHandler = handler;
65
+ }
66
+
67
+ + (void)handleSystemChannelMessage:(NSString *)msg {
68
+ if ([msg hasPrefix:@"release-pause-event"]) {
69
+ // The Node.js engine has signaled it has finished handling a pause event.
70
+ // The expected format for this message is "release-pause-event|{eventId}".
71
+ NSArray *eventArguments = [msg componentsSeparatedByString:@"|"];
72
+ if (eventArguments.count >= 2) {
73
+ [NodeRunner releasePauseEvent:eventArguments[1]];
74
+ }
75
+ } else if ([msg isEqualToString:@"ready-for-app-events"]) {
76
+ nodeIsReadyForAppEvents = true;
77
+ }
78
+ }
79
+
80
+ + (void)onPause {
81
+ if (!nodeIsReadyForAppEvents) {
82
+ return;
83
+ }
84
+ UIApplication *application = [UIApplication sharedApplication];
85
+ // Inform the app intends to run something in the background. In this
86
+ // case we'll try to wait for the pause event to be properly taken
87
+ // care of by the Node.js engine.
88
+ __block UIBackgroundTaskIdentifier backgroundWaitForPauseHandlerTask = [application beginBackgroundTaskWithExpirationHandler:^{
89
+ // Expiration handler to avoid app crashes if the task doesn't end
90
+ // in the iOS allowed background duration time.
91
+ [application endBackgroundTask:backgroundWaitForPauseHandlerTask];
92
+ backgroundWaitForPauseHandlerTask = UIBackgroundTaskInvalid;
93
+ }];
94
+
95
+ NSTimeInterval intendedMaxDuration = [application backgroundTimeRemaining] + 1;
96
+ // Calls the event in a background thread, to let the
97
+ // UIApplicationDidEnterBackgroundNotification return as soon as possible.
98
+ dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
99
+ NSDate *targetMaximumFinishTime = [[NSDate date] dateByAddingTimeInterval:intendedMaxDuration];
100
+ [NodeRunner sendPauseEventAndWaitForRelease:targetMaximumFinishTime];
101
+ [application endBackgroundTask:backgroundWaitForPauseHandlerTask];
102
+ backgroundWaitForPauseHandlerTask = UIBackgroundTaskInvalid;
103
+ });
104
+ }
105
+
106
+ + (void)onResume {
107
+ if (!nodeIsReadyForAppEvents) {
108
+ return;
109
+ }
110
+ [NodeRunner sendMessage:SYSTEM_CHANNEL message:@"resume"];
111
+ }
112
+
113
+ // Sends the pause event to the Node.js engine and returns only after the
114
+ // engine signals the event has been handled explicitly or the background
115
+ // time is running out.
116
+ + (void)sendPauseEventAndWaitForRelease:(NSDate *)expectedFinishTime {
117
+ NSString *eventId = [[NSUUID UUID] UUIDString];
118
+ NSString *event = [NSString stringWithFormat:@"pause|%@", eventId];
119
+
120
+ [appEventBeingProcessedCondition lock];
121
+
122
+ @synchronized(appPauseEventsManagerSetLock) {
123
+ [appPauseEventsManagerSet addObject:eventId];
124
+ }
125
+
126
+ [NodeRunner sendMessage:SYSTEM_CHANNEL message:event];
127
+
128
+ while (YES) {
129
+ // Looping to avoid unintended spurious wake ups.
130
+ @synchronized(appPauseEventsManagerSetLock) {
131
+ if (![appPauseEventsManagerSet containsObject:eventId]) {
132
+ // The id for this event has been released.
133
+ break;
134
+ }
135
+ }
136
+ if ([expectedFinishTime timeIntervalSinceNow] <= 0) {
137
+ // We blocked the background thread long enough.
138
+ break;
139
+ }
140
+ [appEventBeingProcessedCondition waitUntilDate:expectedFinishTime];
141
+ }
142
+ [appEventBeingProcessedCondition unlock];
143
+
144
+ @synchronized(appPauseEventsManagerSetLock) {
145
+ [appPauseEventsManagerSet removeObject:eventId];
146
+ }
147
+ }
148
+
149
+ // Signals the pause event has been handled by the Node.js side.
150
+ + (void)releasePauseEvent:(NSString *)eventId {
151
+ [appEventBeingProcessedCondition lock];
152
+ @synchronized(appPauseEventsManagerSetLock) {
153
+ [appPauseEventsManagerSet removeObject:eventId];
154
+ }
155
+ [appEventBeingProcessedCondition broadcast];
156
+ [appEventBeingProcessedCondition unlock];
157
+ }
158
+
159
+ // Node's libuv requires all arguments being on contiguous memory.
160
+ + (void)startEngineWithArguments:(NSArray<NSString *> *)arguments nodePath:(NSString *)nodePath {
161
+ static dispatch_once_t onceToken;
162
+ dispatch_once(&onceToken, ^{
163
+ appEventBeingProcessedCondition = [[NSCondition alloc] init];
164
+ appPauseEventsManagerSet = [[NSMutableSet alloc] init];
165
+ appPauseEventsManagerSetLock = [[NSObject alloc] init];
166
+ dispatch_async(dispatch_get_main_queue(), ^{
167
+ [[NSNotificationCenter defaultCenter] addObserver:[NodeRunner class]
168
+ selector:@selector(onPause)
169
+ name:UIApplicationDidEnterBackgroundNotification
170
+ object:nil];
171
+ [[NSNotificationCenter defaultCenter] addObserver:[NodeRunner class]
172
+ selector:@selector(onResume)
173
+ name:UIApplicationWillEnterForegroundNotification
174
+ object:nil];
175
+ });
176
+ });
177
+
178
+ setenv("NODE_PATH", [nodePath UTF8String], 1);
179
+
180
+ int c_arguments_size = 0;
181
+
182
+ // Compute the byte size needed for all arguments in contiguous memory.
183
+ for (id argElement in arguments) {
184
+ c_arguments_size += strlen([argElement UTF8String]);
185
+ c_arguments_size++; // for '\0'
186
+ }
187
+
188
+ // Store the arguments in contiguous memory.
189
+ char *args_buffer = (char *)calloc(c_arguments_size, sizeof(char));
190
+
191
+ // The argv to pass into Node.js.
192
+ char *argv[[arguments count]];
193
+
194
+ // Iterate through the expected start position of each argument in args_buffer.
195
+ char *current_args_position = args_buffer;
196
+
197
+ int argument_count = 0;
198
+
199
+ // Populate the args_buffer and argv.
200
+ for (id argElement in arguments) {
201
+ const char *current_argument = [argElement UTF8String];
202
+
203
+ // Copy the current argument to its expected position in args_buffer.
204
+ strncpy(current_args_position, current_argument, strlen(current_argument));
205
+
206
+ // Save the current argument start position in argv and increment argc.
207
+ argv[argument_count] = current_args_position;
208
+ argument_count++;
209
+
210
+ // Increment to the next argument's expected position.
211
+ current_args_position += strlen(current_args_position) + 1;
212
+ }
213
+
214
+ capacitor_register_bridge_cb(rcv_message);
215
+
216
+ // Start Node.js, with argc and argv. This call blocks until the
217
+ // Node.js event loop exits.
218
+ node_start(argument_count, argv);
219
+ }
220
+
221
+ @end