@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.
- package/Package.swift +42 -0
- package/README.md +443 -0
- package/android/CMakeLists.txt +32 -0
- package/android/build.gradle +123 -0
- package/android/src/main/AndroidManifest.xml +2 -0
- package/android/src/main/cpp/native-lib.cpp +192 -0
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/nodejs/Nodejs.java +256 -0
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/nodejs/NodejsConfig.java +30 -0
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/nodejs/NodejsPlugin.java +159 -0
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/nodejs/classes/CustomException.java +20 -0
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/nodejs/classes/CustomExceptions.java +23 -0
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/nodejs/classes/events/MessageEvent.java +27 -0
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/nodejs/classes/options/SendOptions.java +48 -0
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/nodejs/classes/options/StartOptions.java +81 -0
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/nodejs/classes/results/IsReadyResult.java +22 -0
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/nodejs/interfaces/Callback.java +5 -0
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/nodejs/interfaces/EmptyCallback.java +5 -0
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/nodejs/interfaces/NonEmptyResultCallback.java +7 -0
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/nodejs/interfaces/Result.java +7 -0
- package/android/src/main/res/.gitkeep +0 -0
- package/dist/docs.json +432 -0
- package/dist/esm/definitions.d.ts +214 -0
- package/dist/esm/definitions.js +38 -0
- package/dist/esm/definitions.js.map +1 -0
- package/dist/esm/index.d.ts +4 -0
- package/dist/esm/index.js +7 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/web.d.ts +8 -0
- package/dist/esm/web.js +16 -0
- package/dist/esm/web.js.map +1 -0
- package/dist/plugin.cjs.js +68 -0
- package/dist/plugin.cjs.js.map +1 -0
- package/dist/plugin.js +71 -0
- package/dist/plugin.js.map +1 -0
- package/ios/Plugin/Assets/builtin_modules/bridge/index.js +205 -0
- package/ios/Plugin/Classes/Events/MessageEvent.swift +19 -0
- package/ios/Plugin/Classes/Options/SendOptions.swift +19 -0
- package/ios/Plugin/Classes/Options/StartOptions.swift +32 -0
- package/ios/Plugin/Classes/Results/IsReadyResult.swift +16 -0
- package/ios/Plugin/Enums/CustomError.swift +46 -0
- package/ios/Plugin/Nodejs.swift +148 -0
- package/ios/Plugin/NodejsConfig.swift +6 -0
- package/ios/Plugin/NodejsPlugin.swift +102 -0
- package/ios/Plugin/Protocols/Result.swift +6 -0
- package/ios/PluginNative/NodeRunner.mm +221 -0
- package/ios/PluginNative/bridge.cpp +225 -0
- package/ios/PluginNative/bridge.h +15 -0
- package/ios/PluginNative/include/NodeRunner.h +12 -0
- package/package.json +93 -0
- package/scripts/postinstall.js +109 -0
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Implements the bridge APIs between the plugin's native layer and the
|
|
3
|
+
Node.js engine.
|
|
4
|
+
|
|
5
|
+
Based on the bridge of the `nodejs-mobile-react-native` project
|
|
6
|
+
(MIT licensed, https://github.com/nodejs-mobile/nodejs-mobile-react-native).
|
|
7
|
+
*/
|
|
8
|
+
#include "node.h"
|
|
9
|
+
#include "uv.h"
|
|
10
|
+
#include "bridge.h"
|
|
11
|
+
|
|
12
|
+
#include <map>
|
|
13
|
+
#include <mutex>
|
|
14
|
+
#include <queue>
|
|
15
|
+
#include <string>
|
|
16
|
+
#include <cstring>
|
|
17
|
+
#include <cstdlib>
|
|
18
|
+
|
|
19
|
+
void FlushMessageQueue(uv_async_t* handle);
|
|
20
|
+
class Channel;
|
|
21
|
+
|
|
22
|
+
std::mutex channelsMutex;
|
|
23
|
+
std::map<std::string, Channel*> channels;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Channel class. Messages sent from the native layer to the Node.js
|
|
27
|
+
* engine are queued per channel and delivered on the main libuv loop
|
|
28
|
+
* thread.
|
|
29
|
+
*/
|
|
30
|
+
class Channel {
|
|
31
|
+
private:
|
|
32
|
+
v8::Isolate* isolate = nullptr;
|
|
33
|
+
v8::Persistent<v8::Function> function;
|
|
34
|
+
uv_async_t* queue_uv_handle = nullptr;
|
|
35
|
+
std::mutex uvhandleMutex;
|
|
36
|
+
std::mutex queueMutex;
|
|
37
|
+
std::queue<char*> messageQueue;
|
|
38
|
+
std::string name;
|
|
39
|
+
bool initialized = false;
|
|
40
|
+
|
|
41
|
+
public:
|
|
42
|
+
Channel(std::string name) : name(name) {};
|
|
43
|
+
|
|
44
|
+
// Set up the channel's V8 data. This method can be called
|
|
45
|
+
// only once per channel.
|
|
46
|
+
void setV8Function(v8::Isolate* isolate, v8::Local<v8::Function> func) {
|
|
47
|
+
this->isolate = isolate;
|
|
48
|
+
this->function.Reset(isolate, func);
|
|
49
|
+
this->uvhandleMutex.lock();
|
|
50
|
+
if (this->queue_uv_handle == nullptr) {
|
|
51
|
+
this->queue_uv_handle = (uv_async_t*)malloc(sizeof(uv_async_t));
|
|
52
|
+
uv_async_init(uv_default_loop(), this->queue_uv_handle, FlushMessageQueue);
|
|
53
|
+
this->queue_uv_handle->data = (void*)this;
|
|
54
|
+
initialized = true;
|
|
55
|
+
uv_async_send(this->queue_uv_handle);
|
|
56
|
+
} else {
|
|
57
|
+
isolate->ThrowException(v8::Exception::TypeError(
|
|
58
|
+
v8::String::NewFromUtf8(isolate, "Channel already exists.").ToLocalChecked()
|
|
59
|
+
));
|
|
60
|
+
}
|
|
61
|
+
this->uvhandleMutex.unlock();
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
// Add a new message to the channel's queue and notify libuv to
|
|
65
|
+
// call us back to do the actual message delivery.
|
|
66
|
+
void queueMessage(char* msg) {
|
|
67
|
+
this->queueMutex.lock();
|
|
68
|
+
this->messageQueue.push(msg);
|
|
69
|
+
this->queueMutex.unlock();
|
|
70
|
+
|
|
71
|
+
if (initialized) {
|
|
72
|
+
uv_async_send(this->queue_uv_handle);
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
// Process one message at the time, to simplify synchronization
|
|
77
|
+
// between threads and minimize lock retention.
|
|
78
|
+
void flushQueue() {
|
|
79
|
+
char* message = nullptr;
|
|
80
|
+
bool empty = true;
|
|
81
|
+
|
|
82
|
+
this->queueMutex.lock();
|
|
83
|
+
if (!(this->messageQueue.empty())) {
|
|
84
|
+
message = this->messageQueue.front();
|
|
85
|
+
this->messageQueue.pop();
|
|
86
|
+
empty = this->messageQueue.empty();
|
|
87
|
+
}
|
|
88
|
+
this->queueMutex.unlock();
|
|
89
|
+
|
|
90
|
+
if (message != nullptr) {
|
|
91
|
+
this->invokeNodeListener(message);
|
|
92
|
+
free(message);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (!empty) {
|
|
96
|
+
uv_async_send(this->queue_uv_handle);
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
// Calls into Node.js to execute the registered listener.
|
|
101
|
+
// This method is always executed on the main libuv loop thread.
|
|
102
|
+
void invokeNodeListener(char* msg) {
|
|
103
|
+
v8::HandleScope scope(isolate);
|
|
104
|
+
|
|
105
|
+
v8::Local<v8::Function> node_function = v8::Local<v8::Function>::New(isolate, function);
|
|
106
|
+
v8::Local<v8::Value> global = isolate->GetCurrentContext()->Global();
|
|
107
|
+
|
|
108
|
+
v8::Local<v8::String> channel_name = v8::String::NewFromUtf8(isolate, this->name.c_str(), v8::NewStringType::kNormal).ToLocalChecked();
|
|
109
|
+
v8::Local<v8::String> message = v8::String::NewFromUtf8(isolate, msg, v8::NewStringType::kNormal).ToLocalChecked();
|
|
110
|
+
|
|
111
|
+
const int argc = 2;
|
|
112
|
+
v8::Local<v8::Value> argv[argc] = { channel_name, message };
|
|
113
|
+
|
|
114
|
+
v8::MaybeLocal<v8::Value> result = node_function->Call(isolate->GetCurrentContext(), global, argc, argv);
|
|
115
|
+
(void)result;
|
|
116
|
+
};
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
char* datadir_path = nullptr;
|
|
120
|
+
|
|
121
|
+
void capacitor_register_node_data_dir_path(const char* path) {
|
|
122
|
+
size_t pathLength = strlen(path);
|
|
123
|
+
datadir_path = (char*)calloc(sizeof(char), pathLength + 1);
|
|
124
|
+
strncpy(datadir_path, path, pathLength);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
capacitor_bridge_cb embedder_callback = nullptr;
|
|
128
|
+
|
|
129
|
+
void capacitor_register_bridge_cb(capacitor_bridge_cb cb) {
|
|
130
|
+
embedder_callback = cb;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
Channel* GetOrCreateChannel(std::string channelName) {
|
|
134
|
+
channelsMutex.lock();
|
|
135
|
+
Channel* channel = nullptr;
|
|
136
|
+
auto it = channels.find(channelName);
|
|
137
|
+
if (it != channels.end()) {
|
|
138
|
+
channel = it->second;
|
|
139
|
+
} else {
|
|
140
|
+
channel = new Channel(channelName);
|
|
141
|
+
channels[channelName] = channel;
|
|
142
|
+
}
|
|
143
|
+
channelsMutex.unlock();
|
|
144
|
+
return channel;
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
void FlushMessageQueue(uv_async_t* handle) {
|
|
148
|
+
Channel* channel = (Channel*)handle->data;
|
|
149
|
+
channel->flushQueue();
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
void Method_RegisterChannel(const v8::FunctionCallbackInfo<v8::Value>& args) {
|
|
153
|
+
v8::Isolate* isolate = args.GetIsolate();
|
|
154
|
+
if (args.Length() != 2) {
|
|
155
|
+
isolate->ThrowException(v8::Exception::TypeError(
|
|
156
|
+
v8::String::NewFromUtf8(isolate, "Wrong number of arguments.").ToLocalChecked()
|
|
157
|
+
));
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
v8::String::Utf8Value channel_name(isolate, args[0]);
|
|
162
|
+
std::string channel_name_str(*channel_name);
|
|
163
|
+
|
|
164
|
+
if (!args[1]->IsFunction()) {
|
|
165
|
+
isolate->ThrowException(v8::Exception::TypeError(
|
|
166
|
+
v8::String::NewFromUtf8(isolate, "Expected a function.").ToLocalChecked()
|
|
167
|
+
));
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
v8::Local<v8::Function> listener = v8::Local<v8::Function>::Cast(args[1]);
|
|
172
|
+
|
|
173
|
+
Channel* channel = GetOrCreateChannel(channel_name_str);
|
|
174
|
+
channel->setV8Function(isolate, listener);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
void Method_SendMessage(const v8::FunctionCallbackInfo<v8::Value>& args) {
|
|
178
|
+
v8::Isolate* isolate = args.GetIsolate();
|
|
179
|
+
if (args.Length() != 2) {
|
|
180
|
+
isolate->ThrowException(v8::Exception::TypeError(
|
|
181
|
+
v8::String::NewFromUtf8(isolate, "Wrong number of arguments.").ToLocalChecked()
|
|
182
|
+
));
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
v8::String::Utf8Value channel_name(isolate, args[0]);
|
|
187
|
+
std::string channel_name_str(*channel_name);
|
|
188
|
+
|
|
189
|
+
v8::String::Utf8Value message(isolate, args[1]);
|
|
190
|
+
std::string message_str(*message);
|
|
191
|
+
|
|
192
|
+
if (embedder_callback) {
|
|
193
|
+
embedder_callback(channel_name_str.c_str(), message_str.c_str());
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
void Method_GetDataDir(const v8::FunctionCallbackInfo<v8::Value>& args) {
|
|
198
|
+
v8::Isolate* isolate = args.GetIsolate();
|
|
199
|
+
if (datadir_path == nullptr) {
|
|
200
|
+
isolate->ThrowException(v8::Exception::TypeError(
|
|
201
|
+
v8::String::NewFromUtf8(isolate, "Data directory not set from native side.").ToLocalChecked()
|
|
202
|
+
));
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
v8::Local<v8::String> return_datadir = v8::String::NewFromUtf8(isolate, datadir_path, v8::NewStringType::kNormal).ToLocalChecked();
|
|
207
|
+
args.GetReturnValue().Set(return_datadir);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
void Init(v8::Local<v8::Object> exports) {
|
|
211
|
+
NODE_SET_METHOD(exports, "sendMessage", Method_SendMessage);
|
|
212
|
+
NODE_SET_METHOD(exports, "registerChannel", Method_RegisterChannel);
|
|
213
|
+
NODE_SET_METHOD(exports, "getDataDir", Method_GetDataDir);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
void capacitor_bridge_notify(const char* channelName, const char* message) {
|
|
217
|
+
size_t messageLength = strlen(message);
|
|
218
|
+
char* messageCopy = (char*)calloc(sizeof(char), messageLength + 1);
|
|
219
|
+
strncpy(messageCopy, message, messageLength);
|
|
220
|
+
|
|
221
|
+
Channel* channel = GetOrCreateChannel(std::string(channelName));
|
|
222
|
+
channel->queueMessage(messageCopy);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
NODE_MODULE_LINKED(capacitor_bridge, Init)
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Bridge APIs between the plugin's native layer and the Node.js engine.
|
|
3
|
+
|
|
4
|
+
Based on the bridge of the `nodejs-mobile-react-native` project
|
|
5
|
+
(MIT licensed, https://github.com/nodejs-mobile/nodejs-mobile-react-native).
|
|
6
|
+
*/
|
|
7
|
+
#ifndef CAPACITOR_NODEJS_BRIDGE_H_
|
|
8
|
+
#define CAPACITOR_NODEJS_BRIDGE_H_
|
|
9
|
+
|
|
10
|
+
typedef void (*capacitor_bridge_cb)(const char* channelName, const char* message);
|
|
11
|
+
void capacitor_register_bridge_cb(capacitor_bridge_cb cb);
|
|
12
|
+
void capacitor_bridge_notify(const char* channelName, const char* message);
|
|
13
|
+
void capacitor_register_node_data_dir_path(const char* path);
|
|
14
|
+
|
|
15
|
+
#endif // CAPACITOR_NODEJS_BRIDGE_H_
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
#import <Foundation/Foundation.h>
|
|
2
|
+
|
|
3
|
+
typedef void (^NodeRunnerMessageHandler)(NSString *_Nonnull channelName, NSString *_Nonnull message);
|
|
4
|
+
|
|
5
|
+
@interface NodeRunner : NSObject
|
|
6
|
+
|
|
7
|
+
+ (void)registerDataDirPath:(NSString *_Nonnull)path;
|
|
8
|
+
+ (void)sendMessage:(NSString *_Nonnull)channelName message:(NSString *_Nonnull)message;
|
|
9
|
+
+ (void)setMessageHandler:(NodeRunnerMessageHandler _Nonnull)handler;
|
|
10
|
+
+ (void)startEngineWithArguments:(NSArray<NSString *> *_Nonnull)arguments nodePath:(NSString *_Nonnull)nodePath;
|
|
11
|
+
|
|
12
|
+
@end
|
package/package.json
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@capawesome/capacitor-nodejs",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Capacitor plugin for running Node.js in mobile apps.",
|
|
5
|
+
"main": "dist/plugin.cjs.js",
|
|
6
|
+
"module": "dist/esm/index.js",
|
|
7
|
+
"types": "dist/esm/index.d.ts",
|
|
8
|
+
"unpkg": "dist/plugin.js",
|
|
9
|
+
"files": [
|
|
10
|
+
"android/src/main/",
|
|
11
|
+
"android/build.gradle",
|
|
12
|
+
"android/CMakeLists.txt",
|
|
13
|
+
"dist/",
|
|
14
|
+
"ios/Plugin/",
|
|
15
|
+
"ios/PluginNative/",
|
|
16
|
+
"scripts/",
|
|
17
|
+
"Package.swift"
|
|
18
|
+
],
|
|
19
|
+
"author": "Robin Genz <mail@robingenz.dev>",
|
|
20
|
+
"license": "MIT",
|
|
21
|
+
"repository": {
|
|
22
|
+
"type": "git",
|
|
23
|
+
"url": "git+https://github.com/capawesome-team/capacitor-plugins.git"
|
|
24
|
+
},
|
|
25
|
+
"bugs": {
|
|
26
|
+
"url": "https://github.com/capawesome-team/capacitor-plugins/issues"
|
|
27
|
+
},
|
|
28
|
+
"homepage": "https://capawesome.io/docs/plugins/nodejs/",
|
|
29
|
+
"funding": [
|
|
30
|
+
{
|
|
31
|
+
"type": "github",
|
|
32
|
+
"url": "https://github.com/sponsors/capawesome-team/"
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
"type": "opencollective",
|
|
36
|
+
"url": "https://opencollective.com/capawesome"
|
|
37
|
+
}
|
|
38
|
+
],
|
|
39
|
+
"keywords": [
|
|
40
|
+
"capacitor",
|
|
41
|
+
"plugin",
|
|
42
|
+
"native"
|
|
43
|
+
],
|
|
44
|
+
"scripts": {
|
|
45
|
+
"verify": "npm run verify:ios && npm run verify:android && npm run verify:web",
|
|
46
|
+
"verify:ios": "node scripts/postinstall.js && xcodebuild -scheme CapawesomeCapacitorNodejs -destination generic/platform=iOS",
|
|
47
|
+
"verify:android": "cd android && ./gradlew clean build test && cd ..",
|
|
48
|
+
"verify:web": "npm run build",
|
|
49
|
+
"lint": "npm run eslint && npm run prettier -- --check && npm run swiftlint -- lint",
|
|
50
|
+
"fmt": "npm run eslint -- --fix && npm run prettier -- --write && npm run swiftlint -- --fix --format",
|
|
51
|
+
"eslint": "eslint . --ext ts",
|
|
52
|
+
"prettier": "prettier \"**/*.{css,html,ts,js,java}\"",
|
|
53
|
+
"swiftlint": "node-swiftlint",
|
|
54
|
+
"docgen": "docgen --api NodejsPlugin --output-readme README.md --output-json dist/docs.json",
|
|
55
|
+
"build": "npm run clean && npm run docgen && tsc && rollup -c rollup.config.mjs",
|
|
56
|
+
"clean": "rimraf ./dist",
|
|
57
|
+
"watch": "tsc --watch",
|
|
58
|
+
"ios:spm:install": "cd ios && swift package resolve && cd ..",
|
|
59
|
+
"postinstall": "node scripts/postinstall.js",
|
|
60
|
+
"prepublishOnly": "npm run build"
|
|
61
|
+
},
|
|
62
|
+
"devDependencies": {
|
|
63
|
+
"@capacitor/android": "8.0.0",
|
|
64
|
+
"@capacitor/cli": "8.0.0",
|
|
65
|
+
"@capacitor/core": "8.0.0",
|
|
66
|
+
"@capacitor/docgen": "0.3.1",
|
|
67
|
+
"@capacitor/ios": "8.0.0",
|
|
68
|
+
"@ionic/eslint-config": "0.4.0",
|
|
69
|
+
"eslint": "8.57.0",
|
|
70
|
+
"prettier-plugin-java": "2.6.7",
|
|
71
|
+
"rimraf": "6.1.2",
|
|
72
|
+
"rollup": "4.53.3",
|
|
73
|
+
"swiftlint": "2.0.0",
|
|
74
|
+
"typescript": "5.9.3"
|
|
75
|
+
},
|
|
76
|
+
"peerDependencies": {
|
|
77
|
+
"@capacitor/core": ">=8.0.0"
|
|
78
|
+
},
|
|
79
|
+
"eslintConfig": {
|
|
80
|
+
"extends": "@ionic/eslint-config/recommended"
|
|
81
|
+
},
|
|
82
|
+
"capacitor": {
|
|
83
|
+
"ios": {
|
|
84
|
+
"src": "ios"
|
|
85
|
+
},
|
|
86
|
+
"android": {
|
|
87
|
+
"src": "android"
|
|
88
|
+
}
|
|
89
|
+
},
|
|
90
|
+
"publishConfig": {
|
|
91
|
+
"access": "public"
|
|
92
|
+
}
|
|
93
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Downloads the Node.js for Mobile Apps iOS artifacts (NodeMobile.xcframework
|
|
3
|
+
* and the Node.js headers) into `ios/libnode`. The Android artifacts are
|
|
4
|
+
* downloaded by the Gradle build (see `android/build.gradle`).
|
|
5
|
+
*
|
|
6
|
+
* Environment variables:
|
|
7
|
+
* - `CAPACITOR_NODEJS_SKIP_DOWNLOAD`: Set to `1` to skip the download.
|
|
8
|
+
* - `CAPACITOR_NODEJS_IOS_URL`: Overwrite the download URL.
|
|
9
|
+
* - `CAPACITOR_NODEJS_IOS_SHA256`: Overwrite the checksum of the download.
|
|
10
|
+
*/
|
|
11
|
+
const { execFileSync } = require('child_process');
|
|
12
|
+
const crypto = require('crypto');
|
|
13
|
+
const fs = require('fs');
|
|
14
|
+
const https = require('https');
|
|
15
|
+
const os = require('os');
|
|
16
|
+
const path = require('path');
|
|
17
|
+
|
|
18
|
+
const version = '18.20.4-capawesome.1';
|
|
19
|
+
const url =
|
|
20
|
+
process.env.CAPACITOR_NODEJS_IOS_URL ||
|
|
21
|
+
`https://github.com/capawesome-team/nodejs-mobile/releases/download/v${version}/nodejs-mobile-v${version}-ios.zip`;
|
|
22
|
+
const sha256 =
|
|
23
|
+
process.env.CAPACITOR_NODEJS_IOS_SHA256 ||
|
|
24
|
+
'8c5ca3a0d1e38de7f182a5642593e82593b820efd375a14b3ecafc4bcfee620e';
|
|
25
|
+
|
|
26
|
+
const libnodeDir = path.join(__dirname, '..', 'ios', 'libnode');
|
|
27
|
+
const cacheDir = path.join(
|
|
28
|
+
os.homedir(),
|
|
29
|
+
'.cache',
|
|
30
|
+
'capawesome-capacitor-nodejs',
|
|
31
|
+
);
|
|
32
|
+
const zipFile = path.join(cacheDir, `nodejs-mobile-v${version}-ios.zip`);
|
|
33
|
+
|
|
34
|
+
function download(downloadUrl, targetFile, redirectCount = 0) {
|
|
35
|
+
return new Promise((resolve, reject) => {
|
|
36
|
+
if (redirectCount > 5) {
|
|
37
|
+
reject(new Error('Too many redirects.'));
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
https
|
|
41
|
+
.get(downloadUrl, response => {
|
|
42
|
+
if (
|
|
43
|
+
response.statusCode >= 300 &&
|
|
44
|
+
response.statusCode < 400 &&
|
|
45
|
+
response.headers.location
|
|
46
|
+
) {
|
|
47
|
+
response.resume();
|
|
48
|
+
resolve(
|
|
49
|
+
download(response.headers.location, targetFile, redirectCount + 1),
|
|
50
|
+
);
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
if (response.statusCode !== 200) {
|
|
54
|
+
reject(
|
|
55
|
+
new Error(
|
|
56
|
+
`Download failed with status code ${response.statusCode}.`,
|
|
57
|
+
),
|
|
58
|
+
);
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
const fileStream = fs.createWriteStream(targetFile);
|
|
62
|
+
response.pipe(fileStream);
|
|
63
|
+
fileStream.on('finish', () => fileStream.close(resolve));
|
|
64
|
+
fileStream.on('error', reject);
|
|
65
|
+
})
|
|
66
|
+
.on('error', reject);
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function calculateSha256(file) {
|
|
71
|
+
const hash = crypto.createHash('sha256');
|
|
72
|
+
hash.update(fs.readFileSync(file));
|
|
73
|
+
return hash.digest('hex');
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
async function run() {
|
|
77
|
+
if (process.env.CAPACITOR_NODEJS_SKIP_DOWNLOAD === '1') {
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
if (process.platform !== 'darwin') {
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
if (
|
|
84
|
+
fs.existsSync(path.join(libnodeDir, 'NodeMobile.xcframework')) &&
|
|
85
|
+
fs.existsSync(path.join(libnodeDir, 'include'))
|
|
86
|
+
) {
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
fs.mkdirSync(cacheDir, { recursive: true });
|
|
90
|
+
if (!fs.existsSync(zipFile) || calculateSha256(zipFile) !== sha256) {
|
|
91
|
+
console.log(`Downloading Node.js for Mobile Apps from ${url}...`);
|
|
92
|
+
await download(url, zipFile);
|
|
93
|
+
}
|
|
94
|
+
const actualSha256 = calculateSha256(zipFile);
|
|
95
|
+
if (actualSha256 !== sha256) {
|
|
96
|
+
fs.rmSync(zipFile);
|
|
97
|
+
throw new Error(
|
|
98
|
+
`Checksum verification failed for ${zipFile}. Expected ${sha256} but got ${actualSha256}.`,
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
fs.rmSync(libnodeDir, { recursive: true, force: true });
|
|
102
|
+
fs.mkdirSync(libnodeDir, { recursive: true });
|
|
103
|
+
execFileSync('unzip', ['-q', zipFile, '-d', libnodeDir]);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
run().catch(error => {
|
|
107
|
+
console.error(error.message || error);
|
|
108
|
+
process.exit(1);
|
|
109
|
+
});
|