@aptre/bldr-saucer 0.2.6 → 0.4.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/CMakeLists.txt +1 -0
- package/package.json +18 -14
- package/src/main.cpp +13 -6
- package/src/scheme_forwarder.cpp +68 -33
- package/src/scheme_forwarder.h +2 -1
package/CMakeLists.txt
CHANGED
|
@@ -19,6 +19,7 @@ set(saucer_serializer "Glaze" CACHE STRING "" FORCE)
|
|
|
19
19
|
set(saucer_no_version_check ON CACHE BOOL "" FORCE)
|
|
20
20
|
set(saucer_examples OFF CACHE BOOL "" FORCE)
|
|
21
21
|
set(saucer_tests OFF CACHE BOOL "" FORCE)
|
|
22
|
+
set(saucer_exceptions OFF CACHE BOOL "" FORCE)
|
|
22
23
|
|
|
23
24
|
# cpp-yamux options.
|
|
24
25
|
set(YAMUX_BUILD_TESTS OFF CACHE BOOL "" FORCE)
|
package/package.json
CHANGED
|
@@ -1,24 +1,28 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aptre/bldr-saucer",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.1",
|
|
4
4
|
"description": "Native webview bridge for Bldr using Saucer",
|
|
5
5
|
"main": "index.js",
|
|
6
|
-
"
|
|
7
|
-
"type": "git",
|
|
8
|
-
"url": "git+https://github.com/aperturerobotics/bldr-saucer.git"
|
|
9
|
-
},
|
|
6
|
+
"license": "MIT",
|
|
10
7
|
"keywords": [
|
|
11
8
|
"webview",
|
|
12
9
|
"saucer",
|
|
13
10
|
"native",
|
|
14
11
|
"bldr"
|
|
15
12
|
],
|
|
16
|
-
"
|
|
17
|
-
|
|
13
|
+
"repository": {
|
|
14
|
+
"type": "git",
|
|
15
|
+
"url": "git+https://github.com/aperturerobotics/bldr-saucer.git"
|
|
16
|
+
},
|
|
18
17
|
"bugs": {
|
|
19
18
|
"url": "https://github.com/aperturerobotics/bldr-saucer/issues"
|
|
20
19
|
},
|
|
21
20
|
"homepage": "https://github.com/aperturerobotics/bldr-saucer#readme",
|
|
21
|
+
"author": {
|
|
22
|
+
"name": "Aperture Robotics LLC.",
|
|
23
|
+
"email": "support@aperture.us",
|
|
24
|
+
"url": "http://aperture.us"
|
|
25
|
+
},
|
|
22
26
|
"scripts": {
|
|
23
27
|
"postinstall": "node install.js",
|
|
24
28
|
"gen": "bun run go:aptre -- generate",
|
|
@@ -33,11 +37,11 @@
|
|
|
33
37
|
"release:publish": "git push && git push --tags"
|
|
34
38
|
},
|
|
35
39
|
"optionalDependencies": {
|
|
36
|
-
"@aptre/bldr-saucer-darwin-arm64": "0.
|
|
37
|
-
"@aptre/bldr-saucer-darwin-x64": "0.
|
|
38
|
-
"@aptre/bldr-saucer-linux-x64": "0.
|
|
39
|
-
"@aptre/bldr-saucer-linux-arm64": "0.
|
|
40
|
-
"@aptre/bldr-saucer-win32-x64": "0.
|
|
40
|
+
"@aptre/bldr-saucer-darwin-arm64": "0.4.1",
|
|
41
|
+
"@aptre/bldr-saucer-darwin-x64": "0.4.1",
|
|
42
|
+
"@aptre/bldr-saucer-linux-x64": "0.4.1",
|
|
43
|
+
"@aptre/bldr-saucer-linux-arm64": "0.4.1",
|
|
44
|
+
"@aptre/bldr-saucer-win32-x64": "0.4.1"
|
|
41
45
|
},
|
|
42
46
|
"files": [
|
|
43
47
|
"index.js",
|
|
@@ -46,9 +50,9 @@
|
|
|
46
50
|
"CMakeLists.txt"
|
|
47
51
|
],
|
|
48
52
|
"devDependencies": {
|
|
49
|
-
"@aptre/common": "^0.
|
|
53
|
+
"@aptre/common": "^0.32.0"
|
|
50
54
|
},
|
|
51
55
|
"dependencies": {
|
|
52
|
-
"@aptre/protobuf-es-lite": "^0.
|
|
56
|
+
"@aptre/protobuf-es-lite": "^1.0.0"
|
|
53
57
|
}
|
|
54
58
|
}
|
package/src/main.cpp
CHANGED
|
@@ -113,16 +113,23 @@ coco::stray start(saucer::application* app) {
|
|
|
113
113
|
// Register bldr:// scheme BEFORE creating the webview.
|
|
114
114
|
saucer::webview::register_scheme("bldr");
|
|
115
115
|
|
|
116
|
+
// Construct a per-instance storage path from the runtime ID.
|
|
117
|
+
// This ensures concurrent bldr-saucer instances don't share webview storage.
|
|
118
|
+
auto storage = std::filesystem::temp_directory_path() / ("bldr-saucer-" + runtime_id);
|
|
119
|
+
|
|
116
120
|
auto window = saucer::window::create(app).value();
|
|
117
|
-
auto webview = saucer::smartview::create({
|
|
121
|
+
auto webview = saucer::smartview::create({
|
|
122
|
+
.window = window,
|
|
123
|
+
.storage_path = storage,
|
|
124
|
+
});
|
|
118
125
|
|
|
119
126
|
window->set_title("Bldr");
|
|
120
127
|
window->set_size({1024, 768});
|
|
121
128
|
|
|
122
129
|
// Handle bldr:// scheme: forward all requests to Go over yamux.
|
|
123
|
-
webview->
|
|
124
|
-
std::thread([forwarder, req = std::move(req),
|
|
125
|
-
forwarder->forward(req,
|
|
130
|
+
webview->handle_scheme("bldr", [forwarder](saucer::scheme::request req, saucer::scheme::executor executor) {
|
|
131
|
+
std::thread([forwarder, req = std::move(req), executor = std::move(executor)]() mutable {
|
|
132
|
+
forwarder->forward(req, executor);
|
|
126
133
|
}).detach();
|
|
127
134
|
});
|
|
128
135
|
|
|
@@ -157,7 +164,7 @@ coco::stray start(saucer::application* app) {
|
|
|
157
164
|
// The smartview's own handler returns unhandled for unrecognized messages,
|
|
158
165
|
// so this handler sees them next.
|
|
159
166
|
constexpr std::string_view eval_prefix = "__bldr_eval:";
|
|
160
|
-
webview->on<saucer::webview::event::message>(
|
|
167
|
+
webview->on<saucer::webview::event::message>([eval_registry, eval_prefix](std::string_view message) -> saucer::status {
|
|
161
168
|
if (!message.starts_with(eval_prefix)) {
|
|
162
169
|
return saucer::status::unhandled;
|
|
163
170
|
}
|
|
@@ -183,7 +190,7 @@ coco::stray start(saucer::application* app) {
|
|
|
183
190
|
eval_registry->Deliver(eval_id, "", data);
|
|
184
191
|
}
|
|
185
192
|
return saucer::status::handled;
|
|
186
|
-
}
|
|
193
|
+
});
|
|
187
194
|
|
|
188
195
|
// Start accept loop for Go-initiated streams (debug eval).
|
|
189
196
|
// webview is a std::expected; use &(*webview) to get a pointer to the contained value.
|
package/src/scheme_forwarder.cpp
CHANGED
|
@@ -14,18 +14,42 @@ static std::string toLower(const std::string& s) {
|
|
|
14
14
|
return out;
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
//
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
17
|
+
// corsHeaders are Access-Control headers added to all scheme responses.
|
|
18
|
+
// WebKit treats custom scheme origins as opaque (null), so all fetch requests
|
|
19
|
+
// from pages loaded via bldr:// are cross-origin. These headers allow them.
|
|
20
|
+
static const std::map<std::string, std::string> corsHeaders = {
|
|
21
|
+
{"Access-Control-Allow-Origin", "*"},
|
|
22
|
+
{"Access-Control-Allow-Methods", "GET, POST, OPTIONS"},
|
|
23
|
+
{"Access-Control-Allow-Headers", "*"},
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
// sendError resolves the executor with an error status response.
|
|
27
|
+
static void sendError(saucer::scheme::executor& executor, int status) {
|
|
28
|
+
executor.resolve({
|
|
29
|
+
.data = saucer::stash::empty(),
|
|
30
|
+
.mime = "text/plain",
|
|
31
|
+
.headers = corsHeaders,
|
|
32
|
+
.status = status,
|
|
33
|
+
});
|
|
21
34
|
}
|
|
22
35
|
|
|
23
36
|
void SchemeForwarder::forward(const saucer::scheme::request& req,
|
|
24
|
-
saucer::scheme::
|
|
37
|
+
saucer::scheme::executor& executor) {
|
|
38
|
+
// Handle CORS preflight directly without forwarding to Go.
|
|
39
|
+
if (toLower(req.method()) == "options") {
|
|
40
|
+
executor.resolve({
|
|
41
|
+
.data = saucer::stash::empty(),
|
|
42
|
+
.mime = "text/plain",
|
|
43
|
+
.headers = corsHeaders,
|
|
44
|
+
.status = 204,
|
|
45
|
+
});
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
|
|
25
49
|
// Open a new yamux stream.
|
|
26
50
|
auto [stream, err] = session_->OpenStream();
|
|
27
51
|
if (err != yamux::Error::OK || !stream) {
|
|
28
|
-
sendError(
|
|
52
|
+
sendError(executor, 502);
|
|
29
53
|
return;
|
|
30
54
|
}
|
|
31
55
|
|
|
@@ -40,62 +64,68 @@ void SchemeForwarder::forward(const saucer::scheme::request& req,
|
|
|
40
64
|
}
|
|
41
65
|
|
|
42
66
|
// Check if request has a body.
|
|
43
|
-
auto content = req.content();
|
|
67
|
+
auto content = req.content().data();
|
|
44
68
|
info.has_body = (content.size() > 0);
|
|
45
69
|
|
|
46
70
|
// Serialize and send FetchRequestInfo frame.
|
|
47
71
|
auto reqInfoMsg = proto::EncodeFetchRequest_Info(info);
|
|
48
72
|
if (!writeFrame(stream.get(), reqInfoMsg)) {
|
|
49
73
|
stream->Close();
|
|
50
|
-
sendError(
|
|
74
|
+
sendError(executor, 502);
|
|
51
75
|
return;
|
|
52
76
|
}
|
|
53
77
|
|
|
54
78
|
// Send body if present.
|
|
55
79
|
if (info.has_body) {
|
|
56
80
|
proto::FetchRequestData bodyData;
|
|
57
|
-
bodyData.data.assign(
|
|
58
|
-
static_cast<const uint8_t*>(content.data()),
|
|
59
|
-
static_cast<const uint8_t*>(content.data()) + content.size()
|
|
60
|
-
);
|
|
81
|
+
bodyData.data.assign(content.data(), content.data() + content.size());
|
|
61
82
|
bodyData.done = true;
|
|
62
83
|
|
|
63
84
|
auto reqDataMsg = proto::EncodeFetchRequest_Data(bodyData);
|
|
64
85
|
if (!writeFrame(stream.get(), reqDataMsg)) {
|
|
65
86
|
stream->Close();
|
|
66
|
-
sendError(
|
|
87
|
+
sendError(executor, 502);
|
|
67
88
|
return;
|
|
68
89
|
}
|
|
69
90
|
}
|
|
70
91
|
|
|
92
|
+
// Create streaming stash for incremental response delivery.
|
|
93
|
+
auto result = saucer::scheme::response::stream();
|
|
94
|
+
if (!result) {
|
|
95
|
+
executor.reject(saucer::scheme::error::failed);
|
|
96
|
+
stream->Close();
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
auto [stash, write] = std::move(*result);
|
|
100
|
+
|
|
71
101
|
// Read response frames from Go.
|
|
72
|
-
bool
|
|
102
|
+
bool resolved = false;
|
|
73
103
|
bool done = false;
|
|
74
104
|
|
|
75
105
|
while (!done) {
|
|
76
106
|
std::vector<uint8_t> frame;
|
|
77
107
|
if (!readFrame(stream.get(), frame)) {
|
|
78
|
-
if (!
|
|
79
|
-
|
|
108
|
+
if (!resolved) {
|
|
109
|
+
executor.reject(saucer::scheme::error::failed);
|
|
80
110
|
}
|
|
81
111
|
break;
|
|
82
112
|
}
|
|
83
113
|
|
|
84
114
|
proto::FetchResponse resp;
|
|
85
115
|
if (!proto::DecodeFetchResponse(frame.data(), frame.size(), resp)) {
|
|
86
|
-
if (!
|
|
87
|
-
|
|
116
|
+
if (!resolved) {
|
|
117
|
+
executor.reject(saucer::scheme::error::failed);
|
|
88
118
|
}
|
|
89
119
|
break;
|
|
90
120
|
}
|
|
91
121
|
|
|
92
|
-
// Process ResponseInfo (first frame).
|
|
93
|
-
if (resp.has_info && !
|
|
94
|
-
|
|
122
|
+
// Process ResponseInfo (first frame): resolve executor with headers and streaming stash.
|
|
123
|
+
if (resp.has_info && !resolved) {
|
|
124
|
+
resolved = true;
|
|
95
125
|
|
|
96
|
-
// Extract Content-Type header (case-insensitive).
|
|
126
|
+
// Extract Content-Type header (case-insensitive) and merge CORS headers.
|
|
97
127
|
std::string mime = "application/octet-stream";
|
|
98
|
-
std::map<std::string, std::string> hdrs;
|
|
128
|
+
std::map<std::string, std::string> hdrs(corsHeaders);
|
|
99
129
|
for (const auto& [key, val] : resp.info.headers) {
|
|
100
130
|
if (toLower(key) == "content-type") {
|
|
101
131
|
mime = val;
|
|
@@ -104,22 +134,29 @@ void SchemeForwarder::forward(const saucer::scheme::request& req,
|
|
|
104
134
|
}
|
|
105
135
|
}
|
|
106
136
|
|
|
107
|
-
|
|
137
|
+
executor.resolve({
|
|
138
|
+
.data = std::move(stash),
|
|
108
139
|
.mime = mime,
|
|
109
140
|
.headers = hdrs,
|
|
110
141
|
.status = static_cast<int>(resp.info.status),
|
|
111
142
|
});
|
|
112
143
|
}
|
|
113
144
|
|
|
114
|
-
// Process ResponseData.
|
|
145
|
+
// Process ResponseData: push body chunks via streaming write callback.
|
|
115
146
|
if (resp.has_data) {
|
|
116
|
-
if (!
|
|
117
|
-
|
|
118
|
-
|
|
147
|
+
if (!resolved) {
|
|
148
|
+
resolved = true;
|
|
149
|
+
executor.resolve({
|
|
150
|
+
.data = std::move(stash),
|
|
151
|
+
.mime = "application/octet-stream",
|
|
152
|
+
.status = 200,
|
|
153
|
+
});
|
|
119
154
|
}
|
|
120
155
|
|
|
121
|
-
if (!resp.data.data.empty()
|
|
122
|
-
|
|
156
|
+
if (!resp.data.data.empty()) {
|
|
157
|
+
if (!write({resp.data.data.data(), resp.data.data.size()})) {
|
|
158
|
+
break;
|
|
159
|
+
}
|
|
123
160
|
}
|
|
124
161
|
|
|
125
162
|
if (resp.data.done) {
|
|
@@ -128,9 +165,7 @@ void SchemeForwarder::forward(const saucer::scheme::request& req,
|
|
|
128
165
|
}
|
|
129
166
|
}
|
|
130
167
|
|
|
131
|
-
|
|
132
|
-
writer.finish();
|
|
133
|
-
}
|
|
168
|
+
// Destroying write closes the streaming stash.
|
|
134
169
|
stream->Close();
|
|
135
170
|
}
|
|
136
171
|
|
package/src/scheme_forwarder.h
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
#include "fetch_proto.h"
|
|
4
4
|
#include "yamux/session.hpp"
|
|
5
5
|
|
|
6
|
+
#include <saucer/scheme.hpp>
|
|
6
7
|
#include <saucer/smartview.hpp>
|
|
7
8
|
|
|
8
9
|
#include <cstdint>
|
|
@@ -21,7 +22,7 @@ public:
|
|
|
21
22
|
explicit SchemeForwarder(yamux::Session* session) : session_(session) {}
|
|
22
23
|
|
|
23
24
|
// forward handles a single scheme request by forwarding it to Go.
|
|
24
|
-
void forward(const saucer::scheme::request& req, saucer::scheme::
|
|
25
|
+
void forward(const saucer::scheme::request& req, saucer::scheme::executor& executor);
|
|
25
26
|
|
|
26
27
|
private:
|
|
27
28
|
// writeFrame writes a length-prefixed frame to a yamux stream.
|