bunite-core 0.0.1 → 0.0.3
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.json +2 -2
- package/src/bun/core/App.ts +155 -15
- package/src/bun/core/BrowserView.ts +124 -44
- package/src/bun/core/BrowserWindow.ts +94 -47
- package/src/bun/core/Socket.ts +2 -1
- package/src/bun/core/SurfaceBrowserIPC.ts +65 -0
- package/src/bun/core/SurfaceManager.ts +201 -0
- package/src/bun/core/SurfaceRegistry.ts +60 -0
- package/src/bun/core/Utils.ts +275 -46
- package/src/bun/events/appEvents.ts +2 -1
- package/src/bun/events/webviewEvents.ts +1 -3
- package/src/bun/events/windowEvents.ts +2 -0
- package/src/bun/index.ts +4 -3
- package/src/bun/preload/inline.ts +19 -25
- package/src/bun/proc/native.ts +158 -122
- package/src/native/shared/callbacks.h +6 -6
- package/src/native/shared/ffi_exports.h +123 -119
- package/src/native/shared/log.h +24 -0
- package/src/native/shared/webview_storage.h +5 -5
- package/src/native/win/native_host_appres.cpp +258 -0
- package/src/native/win/native_host_cef.cpp +834 -0
- package/src/native/win/native_host_ffi.cpp +935 -0
- package/src/native/win/native_host_internal.h +285 -0
- package/src/native/win/native_host_runtime.cpp +286 -0
- package/src/native/win/native_host_utils.cpp +314 -0
- package/src/native/win/process_helper_win.cpp +126 -26
- package/src/preload/runtime.built.js +1 -1
- package/src/preload/runtime.ts +65 -42
- package/src/preload/tsconfig.json +2 -1
- package/src/preload/tsconfig.tsbuildinfo +1 -0
- package/src/preload/webviewElement.ts +307 -0
- package/src/shared/cefVersion.ts +2 -0
- package/src/shared/log.ts +40 -0
- package/src/shared/paths.ts +122 -52
- package/src/shared/rpc.ts +7 -1
- package/src/view/index.ts +8 -5
- package/src/native/shared/cef_response_filter.h +0 -116
- package/src/native/win/native_host.cpp +0 -2453
- package/src/types/config.ts +0 -29
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
#include "native_host_internal.h"
|
|
2
|
+
|
|
3
|
+
namespace bunite_win {
|
|
4
|
+
|
|
5
|
+
// ---------------------------------------------------------------------------
|
|
6
|
+
// String / encoding utilities
|
|
7
|
+
// ---------------------------------------------------------------------------
|
|
8
|
+
|
|
9
|
+
std::wstring utf8ToWide(const std::string& value) {
|
|
10
|
+
if (value.empty()) {
|
|
11
|
+
return {};
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const int required = MultiByteToWideChar(CP_UTF8, 0, value.c_str(), -1, nullptr, 0);
|
|
15
|
+
if (required <= 0) {
|
|
16
|
+
return {};
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
std::wstring converted(required - 1, L'\0');
|
|
20
|
+
MultiByteToWideChar(CP_UTF8, 0, value.c_str(), -1, converted.data(), required);
|
|
21
|
+
return converted;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
std::string escapeJsonString(const std::string& value) {
|
|
25
|
+
std::string escaped;
|
|
26
|
+
escaped.reserve(value.size());
|
|
27
|
+
|
|
28
|
+
for (const unsigned char ch : value) {
|
|
29
|
+
switch (ch) {
|
|
30
|
+
case '\\':
|
|
31
|
+
escaped += "\\\\";
|
|
32
|
+
break;
|
|
33
|
+
case '"':
|
|
34
|
+
escaped += "\\\"";
|
|
35
|
+
break;
|
|
36
|
+
case '\n':
|
|
37
|
+
escaped += "\\n";
|
|
38
|
+
break;
|
|
39
|
+
case '\r':
|
|
40
|
+
escaped += "\\r";
|
|
41
|
+
break;
|
|
42
|
+
case '\t':
|
|
43
|
+
escaped += "\\t";
|
|
44
|
+
break;
|
|
45
|
+
default:
|
|
46
|
+
if (ch < 0x20) {
|
|
47
|
+
char buffer[7];
|
|
48
|
+
std::snprintf(buffer, sizeof(buffer), "\\u%04x", ch);
|
|
49
|
+
escaped += buffer;
|
|
50
|
+
} else {
|
|
51
|
+
escaped.push_back(static_cast<char>(ch));
|
|
52
|
+
}
|
|
53
|
+
break;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return escaped;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
std::vector<std::string> splitButtonLabels(const std::string& buttons_csv) {
|
|
61
|
+
std::vector<std::string> labels;
|
|
62
|
+
if (buttons_csv.empty()) {
|
|
63
|
+
return labels;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
std::stringstream stream(buttons_csv);
|
|
67
|
+
std::string label;
|
|
68
|
+
while (std::getline(stream, label, '\x1f')) {
|
|
69
|
+
const size_t first = label.find_first_not_of(" \t");
|
|
70
|
+
if (first == std::string::npos) {
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
const size_t last = label.find_last_not_of(" \t");
|
|
74
|
+
std::string normalized = label.substr(first, last - first + 1);
|
|
75
|
+
std::transform(
|
|
76
|
+
normalized.begin(),
|
|
77
|
+
normalized.end(),
|
|
78
|
+
normalized.begin(),
|
|
79
|
+
[](unsigned char ch) { return static_cast<char>(std::tolower(ch)); }
|
|
80
|
+
);
|
|
81
|
+
if (!normalized.empty()) {
|
|
82
|
+
labels.push_back(normalized);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return labels;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
std::string trimAsciiWhitespace(const std::string& value) {
|
|
90
|
+
const size_t first = value.find_first_not_of(" \t\r\n");
|
|
91
|
+
if (first == std::string::npos) {
|
|
92
|
+
return {};
|
|
93
|
+
}
|
|
94
|
+
const size_t last = value.find_last_not_of(" \t\r\n");
|
|
95
|
+
return value.substr(first, last - first + 1);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
std::string toLowerAscii(std::string value) {
|
|
99
|
+
std::transform(
|
|
100
|
+
value.begin(),
|
|
101
|
+
value.end(),
|
|
102
|
+
value.begin(),
|
|
103
|
+
[](unsigned char ch) { return static_cast<char>(std::tolower(ch)); }
|
|
104
|
+
);
|
|
105
|
+
return value;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// ---------------------------------------------------------------------------
|
|
109
|
+
// Chromium flags parsing
|
|
110
|
+
// ---------------------------------------------------------------------------
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
// Simple flat JSON object parser for chromiumFlags.
|
|
114
|
+
// Input is always a JSON-serialized Record<string, string | boolean> from TS.
|
|
115
|
+
// Does not depend on CEF, so it can run before CefInitialize.
|
|
116
|
+
std::map<std::string, std::string> parseChromiumFlagsJson(const std::string& json) {
|
|
117
|
+
std::map<std::string, std::string> flags;
|
|
118
|
+
if (json.empty()) {
|
|
119
|
+
return flags;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
size_t pos = json.find('{');
|
|
123
|
+
if (pos == std::string::npos) {
|
|
124
|
+
return flags;
|
|
125
|
+
}
|
|
126
|
+
++pos;
|
|
127
|
+
|
|
128
|
+
auto skipWhitespace = [&]() {
|
|
129
|
+
while (pos < json.size() && (json[pos] == ' ' || json[pos] == '\t' || json[pos] == '\n' || json[pos] == '\r')) {
|
|
130
|
+
++pos;
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
auto parseString = [&]() -> std::string {
|
|
135
|
+
if (pos >= json.size() || json[pos] != '"') {
|
|
136
|
+
return {};
|
|
137
|
+
}
|
|
138
|
+
++pos;
|
|
139
|
+
std::string result;
|
|
140
|
+
while (pos < json.size() && json[pos] != '"') {
|
|
141
|
+
if (json[pos] == '\\' && pos + 1 < json.size()) {
|
|
142
|
+
++pos;
|
|
143
|
+
}
|
|
144
|
+
result += json[pos++];
|
|
145
|
+
}
|
|
146
|
+
if (pos < json.size()) {
|
|
147
|
+
++pos; // closing quote
|
|
148
|
+
}
|
|
149
|
+
return result;
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
while (pos < json.size()) {
|
|
153
|
+
skipWhitespace();
|
|
154
|
+
if (pos >= json.size() || json[pos] == '}') {
|
|
155
|
+
break;
|
|
156
|
+
}
|
|
157
|
+
if (json[pos] == ',') {
|
|
158
|
+
++pos;
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
std::string key = parseString();
|
|
163
|
+
if (key.empty()) {
|
|
164
|
+
break;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
skipWhitespace();
|
|
168
|
+
if (pos >= json.size() || json[pos] != ':') {
|
|
169
|
+
break;
|
|
170
|
+
}
|
|
171
|
+
++pos;
|
|
172
|
+
skipWhitespace();
|
|
173
|
+
|
|
174
|
+
if (pos >= json.size()) {
|
|
175
|
+
break;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
if (json[pos] == '"') {
|
|
179
|
+
flags[key] = parseString();
|
|
180
|
+
} else if (json.compare(pos, 4, "true") == 0) {
|
|
181
|
+
flags[key] = "true";
|
|
182
|
+
pos += 4;
|
|
183
|
+
} else if (json.compare(pos, 5, "false") == 0) {
|
|
184
|
+
flags[key] = "false";
|
|
185
|
+
pos += 5;
|
|
186
|
+
} else {
|
|
187
|
+
while (pos < json.size() && json[pos] != ',' && json[pos] != '}') {
|
|
188
|
+
++pos;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
return flags;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// ---------------------------------------------------------------------------
|
|
197
|
+
// Navigation rules
|
|
198
|
+
// ---------------------------------------------------------------------------
|
|
199
|
+
|
|
200
|
+
bool globMatchCaseInsensitive(const std::string& pattern, const std::string& value) {
|
|
201
|
+
size_t pattern_index = 0;
|
|
202
|
+
size_t value_index = 0;
|
|
203
|
+
size_t star_pattern_index = std::string::npos;
|
|
204
|
+
size_t star_value_index = 0;
|
|
205
|
+
|
|
206
|
+
while (value_index < value.size()) {
|
|
207
|
+
if (
|
|
208
|
+
pattern_index < pattern.size() &&
|
|
209
|
+
std::tolower(static_cast<unsigned char>(pattern[pattern_index])) ==
|
|
210
|
+
std::tolower(static_cast<unsigned char>(value[value_index]))
|
|
211
|
+
) {
|
|
212
|
+
pattern_index += 1;
|
|
213
|
+
value_index += 1;
|
|
214
|
+
} else if (pattern_index < pattern.size() && pattern[pattern_index] == '*') {
|
|
215
|
+
star_pattern_index = pattern_index++;
|
|
216
|
+
star_value_index = value_index;
|
|
217
|
+
} else if (star_pattern_index != std::string::npos) {
|
|
218
|
+
pattern_index = star_pattern_index + 1;
|
|
219
|
+
value_index = ++star_value_index;
|
|
220
|
+
} else {
|
|
221
|
+
return false;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
while (pattern_index < pattern.size() && pattern[pattern_index] == '*') {
|
|
226
|
+
pattern_index += 1;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
return pattern_index == pattern.size();
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
std::vector<std::string> parseNavigationRulesJson(const std::string& rules_json) {
|
|
233
|
+
std::vector<std::string> rules;
|
|
234
|
+
if (rules_json.empty()) {
|
|
235
|
+
return rules;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
CefRefPtr<CefValue> parsed = CefParseJSON(rules_json, JSON_PARSER_RFC);
|
|
239
|
+
if (!parsed || parsed->GetType() != VTYPE_LIST) {
|
|
240
|
+
return rules;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
CefRefPtr<CefListValue> list = parsed->GetList();
|
|
244
|
+
if (!list) {
|
|
245
|
+
return rules;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
rules.reserve(list->GetSize());
|
|
249
|
+
for (size_t index = 0; index < list->GetSize(); index += 1) {
|
|
250
|
+
if (list->GetType(index) != VTYPE_STRING) {
|
|
251
|
+
continue;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
const std::string rule = list->GetString(index).ToString();
|
|
255
|
+
if (!rule.empty()) {
|
|
256
|
+
rules.push_back(rule);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
return rules;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
bool shouldAlwaysAllowNavigationUrl(const std::string& url) {
|
|
264
|
+
return url == "about:blank" || url.rfind("appres://app.internal/internal/", 0) == 0;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
bool shouldAllowNavigation(const ViewHost* view, const std::string& url) {
|
|
268
|
+
if (!view || shouldAlwaysAllowNavigationUrl(url) || view->navigation_rules.empty()) {
|
|
269
|
+
return true;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
bool allowed = true; // Match electrobun's last-match-wins, default-allow semantics.
|
|
273
|
+
for (const std::string& raw_rule : view->navigation_rules) {
|
|
274
|
+
const bool is_block_rule = !raw_rule.empty() && raw_rule.front() == '^';
|
|
275
|
+
const std::string pattern = is_block_rule ? raw_rule.substr(1) : raw_rule;
|
|
276
|
+
|
|
277
|
+
if (pattern.empty()) {
|
|
278
|
+
continue;
|
|
279
|
+
}
|
|
280
|
+
if (globMatchCaseInsensitive(pattern, url)) {
|
|
281
|
+
allowed = !is_block_rule;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
return allowed;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// ---------------------------------------------------------------------------
|
|
289
|
+
// Event emit
|
|
290
|
+
// ---------------------------------------------------------------------------
|
|
291
|
+
|
|
292
|
+
void emitWindowEvent(uint32_t window_id, const char* event_name, const std::string& payload) {
|
|
293
|
+
BuniteWindowEventHandler handler = nullptr;
|
|
294
|
+
{
|
|
295
|
+
std::lock_guard<std::mutex> lock(g_runtime.lifecycle_mutex);
|
|
296
|
+
handler = g_runtime.window_event_handler;
|
|
297
|
+
}
|
|
298
|
+
if (handler) {
|
|
299
|
+
handler(window_id, _strdup(event_name ? event_name : ""), _strdup(payload.c_str()));
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
void emitWebviewEvent(uint32_t view_id, const char* event_name, const std::string& payload) {
|
|
304
|
+
BuniteWebviewEventHandler handler = nullptr;
|
|
305
|
+
{
|
|
306
|
+
std::lock_guard<std::mutex> lock(g_runtime.lifecycle_mutex);
|
|
307
|
+
handler = g_runtime.webview_event_handler;
|
|
308
|
+
}
|
|
309
|
+
if (handler) {
|
|
310
|
+
handler(view_id, _strdup(event_name ? event_name : ""), _strdup(payload.c_str()));
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
} // namespace bunite_win
|
|
@@ -1,26 +1,126 @@
|
|
|
1
|
-
#include "include/cef_app.h"
|
|
2
|
-
|
|
3
|
-
#include
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
1
|
+
#include "include/cef_app.h"
|
|
2
|
+
#include "include/cef_parser.h"
|
|
3
|
+
#include "include/cef_v8.h"
|
|
4
|
+
|
|
5
|
+
#include <windows.h>
|
|
6
|
+
|
|
7
|
+
#include <map>
|
|
8
|
+
#include <string>
|
|
9
|
+
|
|
10
|
+
namespace {
|
|
11
|
+
|
|
12
|
+
struct PreloadScriptInfo {
|
|
13
|
+
std::string script;
|
|
14
|
+
std::vector<std::string> allowed_origins; // e.g. "http://localhost:3000"
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
std::string getUrlOrigin(const std::string& url) {
|
|
18
|
+
CefURLParts parts;
|
|
19
|
+
if (!CefParseURL(url, parts)) {
|
|
20
|
+
return "";
|
|
21
|
+
}
|
|
22
|
+
const std::string scheme = CefString(&parts.scheme).ToString();
|
|
23
|
+
const std::string host = CefString(&parts.host).ToString();
|
|
24
|
+
const std::string port = CefString(&parts.port).ToString();
|
|
25
|
+
if (scheme.empty() || host.empty()) return "";
|
|
26
|
+
if (port.empty()) return scheme + "://" + host;
|
|
27
|
+
return scheme + "://" + host + ":" + port;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
} // namespace
|
|
31
|
+
|
|
32
|
+
class BuniteHelperApp : public CefApp, public CefRenderProcessHandler {
|
|
33
|
+
public:
|
|
34
|
+
CefRefPtr<CefRenderProcessHandler> GetRenderProcessHandler() override {
|
|
35
|
+
return this;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
void OnRegisterCustomSchemes(CefRawPtr<CefSchemeRegistrar> registrar) override {
|
|
39
|
+
registrar->AddCustomScheme(
|
|
40
|
+
"appres",
|
|
41
|
+
CEF_SCHEME_OPTION_STANDARD |
|
|
42
|
+
CEF_SCHEME_OPTION_CORS_ENABLED |
|
|
43
|
+
CEF_SCHEME_OPTION_SECURE |
|
|
44
|
+
CEF_SCHEME_OPTION_CSP_BYPASSING |
|
|
45
|
+
CEF_SCHEME_OPTION_FETCH_ENABLED
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
void OnBrowserCreated(
|
|
50
|
+
CefRefPtr<CefBrowser> browser,
|
|
51
|
+
CefRefPtr<CefDictionaryValue> extra_info
|
|
52
|
+
) override {
|
|
53
|
+
if (extra_info && (extra_info->HasKey("preloadScript") || extra_info->HasKey("preloadOrigins"))) {
|
|
54
|
+
PreloadScriptInfo info;
|
|
55
|
+
if (extra_info->HasKey("preloadScript")) {
|
|
56
|
+
info.script = extra_info->GetString("preloadScript").ToString();
|
|
57
|
+
}
|
|
58
|
+
if (extra_info->HasKey("preloadOrigins")) {
|
|
59
|
+
auto list = extra_info->GetList("preloadOrigins");
|
|
60
|
+
for (size_t i = 0; i < list->GetSize(); ++i) {
|
|
61
|
+
info.allowed_origins.push_back(list->GetString(i).ToString());
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
preload_scripts_[browser->GetIdentifier()] = std::move(info);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
void OnBrowserDestroyed(CefRefPtr<CefBrowser> browser) override {
|
|
69
|
+
preload_scripts_.erase(browser->GetIdentifier());
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
void OnContextCreated(
|
|
73
|
+
CefRefPtr<CefBrowser> browser,
|
|
74
|
+
CefRefPtr<CefFrame> frame,
|
|
75
|
+
CefRefPtr<CefV8Context> context
|
|
76
|
+
) override {
|
|
77
|
+
if (!frame->IsMain()) return;
|
|
78
|
+
|
|
79
|
+
const std::string url = frame->GetURL().ToString();
|
|
80
|
+
if (url.empty() || url == "about:blank") return;
|
|
81
|
+
|
|
82
|
+
const auto it = preload_scripts_.find(browser->GetIdentifier());
|
|
83
|
+
if (it == preload_scripts_.end() || it->second.script.empty()) return;
|
|
84
|
+
|
|
85
|
+
const bool is_appres = url.rfind("appres://app.internal/", 0) == 0;
|
|
86
|
+
bool is_allowed_origin = false;
|
|
87
|
+
if (!it->second.allowed_origins.empty()) {
|
|
88
|
+
const std::string origin = getUrlOrigin(url);
|
|
89
|
+
for (const auto& allowed : it->second.allowed_origins) {
|
|
90
|
+
if (origin == allowed) { is_allowed_origin = true; break; }
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
if (!is_appres && !is_allowed_origin) return;
|
|
94
|
+
|
|
95
|
+
// Skip isolated-world contexts (DevTools overlay, extensions, etc.) that
|
|
96
|
+
// lack full Web APIs. The page's main-world context has customElements;
|
|
97
|
+
// DevTools-injected contexts do not.
|
|
98
|
+
context->Enter();
|
|
99
|
+
CefRefPtr<CefV8Value> ce = context->GetGlobal()->GetValue("customElements");
|
|
100
|
+
bool is_main_world = ce && !ce->IsNull() && !ce->IsUndefined();
|
|
101
|
+
context->Exit();
|
|
102
|
+
if (!is_main_world) return;
|
|
103
|
+
|
|
104
|
+
CefRefPtr<CefV8Value> retval;
|
|
105
|
+
CefRefPtr<CefV8Exception> exception;
|
|
106
|
+
bool ok = context->Eval(it->second.script, "bunite://preload", 0, retval, exception);
|
|
107
|
+
if (!ok && exception) {
|
|
108
|
+
std::string msg = exception->GetMessage().ToString();
|
|
109
|
+
int line = exception->GetLineNumber();
|
|
110
|
+
std::string src_line = exception->GetSourceLine().ToString();
|
|
111
|
+
LOG(ERROR) << "bunite preload eval failed at line " << line
|
|
112
|
+
<< ": " << msg << "\n " << src_line;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
private:
|
|
117
|
+
std::map<int, PreloadScriptInfo> preload_scripts_;
|
|
118
|
+
|
|
119
|
+
IMPLEMENT_REFCOUNTING(BuniteHelperApp);
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
int APIENTRY wWinMain(HINSTANCE hInstance, HINSTANCE, PWSTR, int) {
|
|
123
|
+
CefMainArgs main_args(hInstance);
|
|
124
|
+
CefRefPtr<CefApp> app = new BuniteHelperApp();
|
|
125
|
+
return CefExecuteProcess(main_args, app, nullptr);
|
|
126
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
var W=(()=>{let F;return()=>{if(!F){let q=Uint8Array.from(atob(__buniteSecretKeyBase64),(H)=>H.charCodeAt(0));F=crypto.subtle.importKey("raw",q,"AES-GCM",!1,["encrypt","decrypt"])}return F}})();async function X(F){let q=await W(),H=crypto.getRandomValues(new Uint8Array(12)),A=new Uint8Array(await crypto.subtle.encrypt({name:"AES-GCM",iv:H},q,F)),z=new Uint8Array(1+H.length+A.length);return z[0]=1,z.set(H,1),z.set(A,1+H.length),z}async function Y(F){if(F.length<29)throw Error("Invalid bunite RPC frame.");if(F[0]!==1)throw Error("Unsupported bunite RPC frame version.");let q=await W(),H=F.slice(1,13),A=F.slice(13);return new Uint8Array(await crypto.subtle.decrypt({name:"AES-GCM",iv:H},q,A))}function B(F){let q=[];function H(A){if(A===null||A===void 0)q.push(192);else if(A===!0)q.push(195);else if(A===!1)q.push(194);else if(typeof A==="number")if(Number.isInteger(A)&&A>=0&&A<128)q.push(A);else if(Number.isInteger(A)&&A>=-32&&A<0)q.push(A&255);else if(Number.isInteger(A)&&A>=0&&A<=65535)q.push(205,A>>8&255,A&255);else if(Number.isInteger(A)&&A>=0&&A<=4294967295)q.push(206,A>>24&255,A>>16&255,A>>8&255,A&255);else{let z=new ArrayBuffer(9),G=new DataView(z);G.setUint8(0,203),G.setFloat64(1,A);for(let J=0;J<9;J++)q.push(G.getUint8(J))}else if(typeof A==="string"){let z=new TextEncoder().encode(A);if(z.length<32)q.push(160|z.length);else if(z.length<256)q.push(217,z.length);else if(z.length<65536)q.push(218,z.length>>8&255,z.length&255);else q.push(219,z.length>>24&255,z.length>>16&255,z.length>>8&255,z.length&255);for(let G=0;G<z.length;G++)q.push(z[G])}else if(Array.isArray(A)){if(A.length<16)q.push(144|A.length);else if(A.length<65536)q.push(220,A.length>>8&255,A.length&255);else q.push(221,A.length>>24&255,A.length>>16&255,A.length>>8&255,A.length&255);A.forEach(H)}else if(typeof A==="object"){let z=Object.keys(A);if(z.length<16)q.push(128|z.length);else if(z.length<65536)q.push(222,z.length>>8&255,z.length&255);else q.push(223,z.length>>24&255,z.length>>16&255,z.length>>8&255,z.length&255);for(let G of z)H(G),H(A[G])}}return H(F),new Uint8Array(q)}function I(F){let q=0;function H(){let A=F[q++];if(A<=127)return A;if(A>=224)return A-256;if(A===192)return null;if(A===194)return!1;if(A===195)return!0;if(A===204)return F[q++];if(A===205){let z=F[q]<<8|F[q+1];return q+=2,z}if(A===206){let z=(F[q]<<24|F[q+1]<<16|F[q+2]<<8|F[q+3])>>>0;return q+=4,z}if(A===203){let z=new DataView(F.buffer,F.byteOffset+q,8);return q+=8,z.getFloat64(0)}if(A===208){let z=F[q++];return z>127?z-256:z}if(A===209){let z=F[q]<<8|F[q+1];return q+=2,z>32767?z-65536:z}if(A===210){let z=F[q]<<24|F[q+1]<<16|F[q+2]<<8|F[q+3];return q+=4,z}if((A&224)===160){let z=A&31,G=new TextDecoder().decode(F.subarray(q,q+z));return q+=z,G}if(A===217){let z=F[q++],G=new TextDecoder().decode(F.subarray(q,q+z));return q+=z,G}if(A===218){let z=F[q]<<8|F[q+1];q+=2;let G=new TextDecoder().decode(F.subarray(q,q+z));return q+=z,G}if(A===219){let z=(F[q]<<24|F[q+1]<<16|F[q+2]<<8|F[q+3])>>>0;q+=4;let G=new TextDecoder().decode(F.subarray(q,q+z));return q+=z,G}if((A&240)===144){let z=A&15,G=[];for(let J=0;J<z;J++)G.push(H());return G}if(A===220){let z=F[q]<<8|F[q+1];q+=2;let G=[];for(let J=0;J<z;J++)G.push(H());return G}if(A===221){let z=(F[q]<<24|F[q+1]<<16|F[q+2]<<8|F[q+3])>>>0;q+=4;let G=[];for(let J=0;J<z;J++)G.push(H());return G}if((A&240)===128){let z=A&15,G={};for(let J=0;J<z;J++)G[H()]=H();return G}if(A===222){let z=F[q]<<8|F[q+1];q+=2;let G={};for(let J=0;J<z;J++)G[H()]=H();return G}if(A===223){let z=(F[q]<<24|F[q+1]<<16|F[q+2]<<8|F[q+3])>>>0;q+=4;let G={};for(let J=0;J<z;J++)G[H()]=H();return G}if(A===196){let z=F[q++],G=F.slice(q,q+z);return q+=z,G}if(A===197){let z=F[q]<<8|F[q+1];q+=2;let G=F.slice(q,q+z);return q+=z,G}return}return H()}var N=window;N.__bunite??={};N.__buniteWebviewId=__buniteWebviewId;N.__buniteRpcSocketPort=__buniteRpcSocketPort;N.__bunite_encrypt=X;N.__bunite_decrypt=Y;N.bunite=N.__bunite;N.bunite.invoke=(()=>{let F=null,q=1,H=new Map;function A(){let G=N.__bunite?._socket;if(G&&G.readyState<=WebSocket.OPEN&&G!==F)return F=G,z(G),G;if(F&&F.readyState<=WebSocket.OPEN)return F;return F=new WebSocket(`ws://localhost:${__buniteRpcSocketPort}/socket?webviewId=${__buniteWebviewId}`),F.binaryType="arraybuffer",N.__bunite._socket=F,z(F),F}function z(G){G.addEventListener("message",async(J)=>{try{let Q=await Y(new Uint8Array(J.data)),M=I(Q);if(M?.type==="response"&&M.scope==="global"){let O=H.get(M.id);if(O)H.delete(M.id),clearTimeout(O.timeout),M.success?O.resolve(M.payload):O.reject(Error(M.error||"Unknown error"))}}catch{}})}return(G,J)=>new Promise((Q,M)=>{let O=A(),T=q++,Z=setTimeout(()=>{H.delete(T),M(Error(`bunite.invoke timed out: ${G}`))},15000);H.set(T,{resolve:Q,reject:M,timeout:Z});let _={type:"request",id:T,method:G,params:J??null,scope:"global"},U=async()=>{let $=await X(B(_));O.send($.buffer)};if(O.readyState===WebSocket.OPEN)U();else O.addEventListener("open",()=>U(),{once:!0})})})();
|
|
1
|
+
class _{element;onBoundsChange;observer=null;rafId=0;lastRect={x:0,y:0,width:0,height:0};dirty=!1;stopped=!1;constructor(z,q){this.element=z,this.onBoundsChange=q}start(){this.observer=new ResizeObserver(()=>this.markDirty()),this.observer.observe(this.element),this.scheduleFrame()}stop(){if(this.stopped=!0,this.observer?.disconnect(),this.observer=null,this.rafId)cancelAnimationFrame(this.rafId),this.rafId=0}markDirty(){this.dirty=!0}scheduleFrame(){if(this.stopped)return;this.rafId=requestAnimationFrame(()=>{this.flush(),this.scheduleFrame()})}flush(){let z=window.devicePixelRatio||1,q=this.element.getBoundingClientRect(),J={x:Math.round(q.x*z),y:Math.round(q.y*z),width:Math.round(q.width*z),height:Math.round(q.height*z)};if(!this.dirty&&J.x===this.lastRect.x&&J.y===this.lastRect.y&&J.width===this.lastRect.width&&J.height===this.lastRect.height)return;this.dirty=!1,this.lastRect=J,this.onBoundsChange(J)}}class $ extends HTMLElement{static observedAttributes=["src"];_surfaceId=null;_syncCtrl=null;_initPromise=null;_aborted=!1;_pendingSrc=null;_syncHidden=!1;_userHidden=!1;_layoutObserver=null;_unsubNavigate=null;constructor(){super()}connectedCallback(){this._aborted=!1,this._syncHidden=!1,this._userHidden=!1,this._unsubNavigate=bunite.on("__bunite:webview.didNavigate",(z)=>{if(z?.surfaceId===this._surfaceId)this.dispatchEvent(new CustomEvent("did-navigate",{detail:{url:z.url}}))}),this._waitForLayout()}_waitForLayout(){if(this._layoutObserver)return;let z=()=>{if(!this.isConnected||this._aborted)return!0;let q=this.getBoundingClientRect();if(q.width>0&&q.height>0){if(this.getAttribute("src")||this._pendingSrc||"")this.initSurface();return!0}return!1};requestAnimationFrame(()=>{if(z())return;this._layoutObserver=new ResizeObserver(()=>{if(z())this._layoutObserver?.disconnect(),this._layoutObserver=null}),this._layoutObserver.observe(this)})}disconnectedCallback(){if(this._aborted=!0,this._unsubNavigate?.(),this._unsubNavigate=null,this._layoutObserver?.disconnect(),this._layoutObserver=null,this._syncCtrl?.stop(),this._syncCtrl=null,this._surfaceId!=null){let z=this._surfaceId;this._surfaceId=null,bunite.invoke("__bunite:surface.remove",{surfaceId:z}).catch(()=>{})}else if(this._initPromise)this._initPromise.then((z)=>{bunite.invoke("__bunite:surface.remove",{surfaceId:z.surfaceId}).catch(()=>{})}).catch(()=>{});this._initPromise=null}attributeChangedCallback(z,q,J){if(z!=="src")return;if(this._surfaceId!=null)bunite.invoke("__bunite:webview.navigate",{surfaceId:this._surfaceId,url:J||""}).catch(()=>{});else if(this._initPromise)this._pendingSrc=J||"";else if(this.isConnected&&!this._aborted&&J)this._waitForLayout()}setHidden(z){this._userHidden=z,this._applySurfaceHidden()}goBack(){if(this._surfaceId!=null)bunite.invoke("__bunite:webview.goBack",{surfaceId:this._surfaceId}).catch(()=>{})}reload(){if(this._surfaceId!=null)bunite.invoke("__bunite:webview.reload",{surfaceId:this._surfaceId}).catch(()=>{})}navigate(z){this.setAttribute("src",z)}_applySurfaceHidden(){if(this._surfaceId==null)return;bunite.invoke("__bunite:surface.setHidden",{surfaceId:this._surfaceId,hidden:this._userHidden||this._syncHidden}).catch(()=>{})}initSurface(){if(this._surfaceId!=null||this._initPromise!=null)return;let z=window.devicePixelRatio||1,q=this.getBoundingClientRect(),J=this._pendingSrc||this.getAttribute("src")||"";this._pendingSrc=null;let F=bunite.invoke("__bunite:surface.init",{src:J,x:Math.round(q.x*z),y:Math.round(q.y*z),width:Math.round(q.width*z),height:Math.round(q.height*z),hidden:this._userHidden});this._initPromise=F,F.then((G)=>{if(this._initPromise!==F)return;if(this._aborted){bunite.invoke("__bunite:surface.remove",{surfaceId:G.surfaceId}).catch(()=>{});return}if(this._surfaceId=G.surfaceId,this._userHidden)this._applySurfaceHidden();if(this._pendingSrc!=null){let H=this._pendingSrc;this._pendingSrc=null,bunite.invoke("__bunite:webview.navigate",{surfaceId:this._surfaceId,url:H}).catch(()=>{})}this._syncCtrl=new _(this,(H)=>{if(this._surfaceId==null)return;if(H.width===0&&H.height===0){if(!this._syncHidden)this._syncHidden=!0,this._applySurfaceHidden();return}if(this._syncHidden)this._syncHidden=!1,this._applySurfaceHidden();bunite.invoke("__bunite:surface.resize",{surfaceId:this._surfaceId,x:H.x,y:H.y,w:H.width,h:H.height}).catch(()=>{})}),this._syncCtrl.start()}).catch(()=>{}).finally(()=>{if(this._initPromise===F)this._initPromise=null})}}if(typeof customElements<"u"){customElements.define("bunite-webview",$);let z=()=>bunite.invoke("__bunite:surface.bringAllVisiblesToFront").catch(()=>{});document.addEventListener("pointerdown",z,!0),document.addEventListener("dragstart",()=>{bunite.invoke("__bunite:surface.setAllPassthrough",{passthrough:!0}).catch(()=>{})},!0),document.addEventListener("dragend",()=>{bunite.invoke("__bunite:surface.setAllPassthrough",{passthrough:!1}).catch(()=>{}),z()},!0)}var Z=(()=>{let z;return()=>{if(!z){let q=Uint8Array.from(atob(__buniteSecretKeyBase64),(J)=>J.charCodeAt(0));z=crypto.subtle.importKey("raw",q,"AES-GCM",!1,["encrypt","decrypt"])}return z}})();async function K(z){let q=await Z(),J=crypto.getRandomValues(new Uint8Array(12)),F=new Uint8Array(await crypto.subtle.encrypt({name:"AES-GCM",iv:J},q,z)),G=new Uint8Array(1+J.length+F.length);return G[0]=1,G.set(J,1),G.set(F,1+J.length),G}async function W(z){if(z.length<29)throw Error("Invalid bunite RPC frame.");if(z[0]!==1)throw Error("Unsupported bunite RPC frame version.");let q=await Z(),J=z.slice(1,13),F=z.slice(13);return new Uint8Array(await crypto.subtle.decrypt({name:"AES-GCM",iv:J},q,F))}function I(z){let q=[];function J(F){if(F===null||F===void 0)q.push(192);else if(F===!0)q.push(195);else if(F===!1)q.push(194);else if(typeof F==="number")if(Number.isInteger(F)&&F>=0&&F<128)q.push(F);else if(Number.isInteger(F)&&F>=-32&&F<0)q.push(F&255);else if(Number.isInteger(F)&&F>=0&&F<=65535)q.push(205,F>>8&255,F&255);else if(Number.isInteger(F)&&F>=0&&F<=4294967295)q.push(206,F>>24&255,F>>16&255,F>>8&255,F&255);else{let G=new ArrayBuffer(9),H=new DataView(G);H.setUint8(0,203),H.setFloat64(1,F);for(let M=0;M<9;M++)q.push(H.getUint8(M))}else if(typeof F==="string"){let G=new TextEncoder().encode(F);if(G.length<32)q.push(160|G.length);else if(G.length<256)q.push(217,G.length);else if(G.length<65536)q.push(218,G.length>>8&255,G.length&255);else q.push(219,G.length>>24&255,G.length>>16&255,G.length>>8&255,G.length&255);for(let H=0;H<G.length;H++)q.push(G[H])}else if(Array.isArray(F)){if(F.length<16)q.push(144|F.length);else if(F.length<65536)q.push(220,F.length>>8&255,F.length&255);else q.push(221,F.length>>24&255,F.length>>16&255,F.length>>8&255,F.length&255);F.forEach(J)}else if(typeof F==="object"){let G=Object.keys(F);if(G.length<16)q.push(128|G.length);else if(G.length<65536)q.push(222,G.length>>8&255,G.length&255);else q.push(223,G.length>>24&255,G.length>>16&255,G.length>>8&255,G.length&255);for(let H of G)J(H),J(F[H])}}return J(z),new Uint8Array(q)}function D(z){let q=0;function J(){let F=z[q++];if(F<=127)return F;if(F>=224)return F-256;if(F===192)return null;if(F===194)return!1;if(F===195)return!0;if(F===204)return z[q++];if(F===205){let G=z[q]<<8|z[q+1];return q+=2,G}if(F===206){let G=(z[q]<<24|z[q+1]<<16|z[q+2]<<8|z[q+3])>>>0;return q+=4,G}if(F===203){let G=new DataView(z.buffer,z.byteOffset+q,8);return q+=8,G.getFloat64(0)}if(F===208){let G=z[q++];return G>127?G-256:G}if(F===209){let G=z[q]<<8|z[q+1];return q+=2,G>32767?G-65536:G}if(F===210){let G=z[q]<<24|z[q+1]<<16|z[q+2]<<8|z[q+3];return q+=4,G}if((F&224)===160){let G=F&31,H=new TextDecoder().decode(z.subarray(q,q+G));return q+=G,H}if(F===217){let G=z[q++],H=new TextDecoder().decode(z.subarray(q,q+G));return q+=G,H}if(F===218){let G=z[q]<<8|z[q+1];q+=2;let H=new TextDecoder().decode(z.subarray(q,q+G));return q+=G,H}if(F===219){let G=(z[q]<<24|z[q+1]<<16|z[q+2]<<8|z[q+3])>>>0;q+=4;let H=new TextDecoder().decode(z.subarray(q,q+G));return q+=G,H}if((F&240)===144){let G=F&15,H=[];for(let M=0;M<G;M++)H.push(J());return H}if(F===220){let G=z[q]<<8|z[q+1];q+=2;let H=[];for(let M=0;M<G;M++)H.push(J());return H}if(F===221){let G=(z[q]<<24|z[q+1]<<16|z[q+2]<<8|z[q+3])>>>0;q+=4;let H=[];for(let M=0;M<G;M++)H.push(J());return H}if((F&240)===128){let G=F&15,H={};for(let M=0;M<G;M++)H[J()]=J();return H}if(F===222){let G=z[q]<<8|z[q+1];q+=2;let H={};for(let M=0;M<G;M++)H[J()]=J();return H}if(F===223){let G=(z[q]<<24|z[q+1]<<16|z[q+2]<<8|z[q+3])>>>0;q+=4;let H={};for(let M=0;M<G;M++)H[J()]=J();return H}if(F===196){let G=z[q++],H=z.slice(q,q+G);return q+=G,H}if(F===197){let G=z[q]<<8|z[q+1];q+=2;let H=z.slice(q,q+G);return q+=G,H}return}return J()}var N=window;N.__bunite??={};N.__buniteWebviewId=__buniteWebviewId;N.__buniteRpcSocketPort=__buniteRpcSocketPort;N.__bunite_encrypt=K;N.__bunite_decrypt=W;var R=new Map,U=new Map,Q=null,X=!1;function T(z){if(X)return;X=!0,z.addEventListener("message",async(q)=>{try{let J=await W(new Uint8Array(q.data)),F=D(J);if(F?.type==="response"&&F.scope==="global"){let G=U.get(F.id);if(G)U.delete(F.id),clearTimeout(G.timeout),F.success?G.resolve(F.payload):G.reject(Error(F.error||"Unknown error"))}else if(F?.type==="event"){let G=R.get(F.channel);if(G)for(let H of G)H(F.data)}}catch{}}),z.addEventListener("close",()=>{X=!1,Q=null})}function O(){let z=N.__bunite?._socket;if(z&&z.readyState<=WebSocket.OPEN&&z!==Q)return Q=z,T(z),z;if(Q&&Q.readyState<=WebSocket.OPEN)return Q;return Q=new WebSocket(`ws://localhost:${__buniteRpcSocketPort}/socket?webviewId=${__buniteWebviewId}`),Q.binaryType="arraybuffer",N.__bunite._socket=Q,T(Q),Q}N.bunite=N.__bunite;N.bunite.on=(z,q)=>{O();let J=R.get(z);if(!J)J=new Set,R.set(z,J);return J.add(q),()=>{J.delete(q)}};N.bunite.off=(z,q)=>{R.get(z)?.delete(q)};var S=1;N.bunite.invoke=(z,q)=>new Promise((J,F)=>{let G=O(),H=S++,M=setTimeout(()=>{U.delete(H),F(Error(`bunite.invoke timed out: ${z}`))},15000);U.set(H,{resolve:J,reject:F,timeout:M});let B={type:"request",id:H,method:z,params:q??null,scope:"global"},Y=async()=>{let A=await K(I(B));G.send(A.buffer)};if(G.readyState===WebSocket.OPEN)Y();else G.addEventListener("open",()=>Y(),{once:!0})});
|
package/src/preload/runtime.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// Preload runtime — built once at package build time, injected into every
|
|
1
|
+
// Preload runtime — built once at package build time, injected into every appres:// page.
|
|
2
2
|
// The config variables (__buniteWebviewId, __buniteRpcSocketPort, __buniteSecretKeyBase64)
|
|
3
3
|
// are injected by inline.ts as a small preamble before this script.
|
|
4
4
|
|
|
@@ -147,50 +147,72 @@ w.__buniteRpcSocketPort = __buniteRpcSocketPort;
|
|
|
147
147
|
w.__bunite_encrypt = buniteEncrypt;
|
|
148
148
|
w.__bunite_decrypt = buniteDecrypt;
|
|
149
149
|
|
|
150
|
-
// ---
|
|
150
|
+
// --- Shared WebSocket transport ---
|
|
151
|
+
|
|
152
|
+
const eventListeners = new Map<string, Set<(data: any) => void>>();
|
|
153
|
+
const pending = new Map<number, { resolve: (v: unknown) => void; reject: (e: Error) => void; timeout: ReturnType<typeof setTimeout> }>();
|
|
154
|
+
let socket: WebSocket | null = null;
|
|
155
|
+
let listenerAttached = false;
|
|
156
|
+
|
|
157
|
+
function attachListener(ws: WebSocket) {
|
|
158
|
+
if (listenerAttached) return;
|
|
159
|
+
listenerAttached = true;
|
|
160
|
+
ws.addEventListener("message", async (event) => {
|
|
161
|
+
try {
|
|
162
|
+
const decrypted = await buniteDecrypt(new Uint8Array(event.data as ArrayBuffer));
|
|
163
|
+
const packet = mpDecode(decrypted) as any;
|
|
164
|
+
if (packet?.type === "response" && packet.scope === "global") {
|
|
165
|
+
const p = pending.get(packet.id);
|
|
166
|
+
if (p) {
|
|
167
|
+
pending.delete(packet.id);
|
|
168
|
+
clearTimeout(p.timeout);
|
|
169
|
+
packet.success ? p.resolve(packet.payload) : p.reject(new Error(packet.error || "Unknown error"));
|
|
170
|
+
}
|
|
171
|
+
} else if (packet?.type === "event") {
|
|
172
|
+
const set = eventListeners.get(packet.channel);
|
|
173
|
+
if (set) for (const fn of set) fn(packet.data);
|
|
174
|
+
}
|
|
175
|
+
} catch { /* ignore malformed frames */ }
|
|
176
|
+
});
|
|
177
|
+
ws.addEventListener("close", () => { listenerAttached = false; socket = null; });
|
|
178
|
+
}
|
|
151
179
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
function ensureSocket(): WebSocket {
|
|
159
|
-
// Reuse socket opened by BuniteView if available
|
|
160
|
-
const existing = w.__bunite?._socket;
|
|
161
|
-
if (existing && existing.readyState <= WebSocket.OPEN && existing !== socket) {
|
|
162
|
-
socket = existing;
|
|
163
|
-
attachListener(existing);
|
|
164
|
-
return existing;
|
|
165
|
-
}
|
|
166
|
-
if (socket && socket.readyState <= WebSocket.OPEN) return socket;
|
|
167
|
-
socket = new WebSocket(
|
|
168
|
-
`ws://localhost:${__buniteRpcSocketPort}/socket?webviewId=${__buniteWebviewId}`
|
|
169
|
-
);
|
|
170
|
-
socket.binaryType = "arraybuffer";
|
|
171
|
-
w.__bunite._socket = socket;
|
|
172
|
-
attachListener(socket);
|
|
173
|
-
return socket;
|
|
180
|
+
function ensureSocket(): WebSocket {
|
|
181
|
+
const existing = w.__bunite?._socket;
|
|
182
|
+
if (existing && existing.readyState <= WebSocket.OPEN && existing !== socket) {
|
|
183
|
+
socket = existing;
|
|
184
|
+
attachListener(existing);
|
|
185
|
+
return existing;
|
|
174
186
|
}
|
|
187
|
+
if (socket && socket.readyState <= WebSocket.OPEN) return socket;
|
|
188
|
+
socket = new WebSocket(
|
|
189
|
+
`ws://localhost:${__buniteRpcSocketPort}/socket?webviewId=${__buniteWebviewId}`
|
|
190
|
+
);
|
|
191
|
+
socket.binaryType = "arraybuffer";
|
|
192
|
+
w.__bunite._socket = socket;
|
|
193
|
+
attachListener(socket);
|
|
194
|
+
return socket;
|
|
195
|
+
}
|
|
175
196
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
197
|
+
// --- bunite.on/off: main→renderer events ---
|
|
198
|
+
|
|
199
|
+
w.bunite = w.__bunite;
|
|
200
|
+
w.bunite.on = (channel: string, handler: (data: any) => void) => {
|
|
201
|
+
ensureSocket();
|
|
202
|
+
let set = eventListeners.get(channel);
|
|
203
|
+
if (!set) { set = new Set(); eventListeners.set(channel, set); }
|
|
204
|
+
set.add(handler);
|
|
205
|
+
return () => { set!.delete(handler); };
|
|
206
|
+
};
|
|
207
|
+
w.bunite.off = (channel: string, handler: (data: any) => void) => {
|
|
208
|
+
eventListeners.get(channel)?.delete(handler);
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
// --- bunite.invoke: global IPC ---
|
|
212
|
+
|
|
213
|
+
let nextId = 1;
|
|
192
214
|
|
|
193
|
-
|
|
215
|
+
w.bunite.invoke = (method: string, params?: unknown) =>
|
|
194
216
|
new Promise((resolve, reject) => {
|
|
195
217
|
const ws = ensureSocket();
|
|
196
218
|
const id = nextId++;
|
|
@@ -212,4 +234,5 @@ w.bunite.invoke = (() => {
|
|
|
212
234
|
ws.addEventListener("open", () => doSend(), { once: true });
|
|
213
235
|
}
|
|
214
236
|
});
|
|
215
|
-
|
|
237
|
+
|
|
238
|
+
import "./webviewElement";
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"compilerOptions": {
|
|
3
|
+
"composite": true,
|
|
3
4
|
"target": "ES2022",
|
|
4
5
|
"module": "ES2022",
|
|
5
6
|
"moduleResolution": "bundler",
|
|
@@ -9,5 +10,5 @@
|
|
|
9
10
|
"skipLibCheck": true,
|
|
10
11
|
"noEmit": true
|
|
11
12
|
},
|
|
12
|
-
"include": ["runtime.ts"]
|
|
13
|
+
"include": ["runtime.ts", "webviewElement.ts"]
|
|
13
14
|
}
|