afterpresets 0.2.0 → 0.3.0
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/README.md +6 -4
- package/native-runtime/CMakeLists.txt +82 -0
- package/native-runtime/README.md +40 -0
- package/native-runtime/THIRD_PARTY_NOTICES.md +14 -0
- package/native-runtime/include/ap_native.h +223 -0
- package/native-runtime/src/apply.cpp +199 -0
- package/native-runtime/src/crypto.cpp +225 -0
- package/native-runtime/src/crypto.hpp +51 -0
- package/native-runtime/src/dialog.cpp +62 -0
- package/native-runtime/src/dialog.hpp +35 -0
- package/native-runtime/src/engine.cpp +909 -0
- package/native-runtime/src/engine.hpp +232 -0
- package/native-runtime/src/fsutil.cpp +291 -0
- package/native-runtime/src/fsutil.hpp +57 -0
- package/native-runtime/src/json.cpp +326 -0
- package/native-runtime/src/json.hpp +70 -0
- package/native-runtime/src/platform.hpp +83 -0
- package/native-runtime/src/platform_common.cpp +60 -0
- package/native-runtime/src/platform_mac.mm +314 -0
- package/native-runtime/src/platform_win.cpp +277 -0
- package/native-runtime/src/runtime.cpp +473 -0
- package/native-runtime/src/util.cpp +169 -0
- package/native-runtime/src/util.hpp +32 -0
- package/native-runtime/src/zip.cpp +243 -0
- package/native-runtime/src/zip.hpp +58 -0
- package/native-runtime/third_party/monocypher/LICENCE.md +167 -0
- package/native-runtime/third_party/monocypher/monocypher-ed25519.c +500 -0
- package/native-runtime/third_party/monocypher/monocypher-ed25519.h +140 -0
- package/native-runtime/third_party/monocypher/monocypher.c +2956 -0
- package/native-runtime/third_party/monocypher/monocypher.h +321 -0
- package/native-runtime/third_party/tinf/LICENSE +22 -0
- package/native-runtime/third_party/tinf/crc32.c +57 -0
- package/native-runtime/third_party/tinf/tinf.h +140 -0
- package/native-runtime/third_party/tinf/tinflate.c +636 -0
- package/native-runtime/vendor-files.json +39 -0
- package/package.json +6 -3
- package/runtime/docs/cep/host.md +8 -0
- package/runtime/docs/common/README.md +1 -1
- package/runtime/docs/native/README.md +44 -0
- package/runtime/docs/native/debugging.md +140 -0
- package/runtime/docs/native/rules.md +402 -0
- package/runtime/docs/native/testing.md +154 -0
- package/runtime/package.json +1 -1
- package/runtime/scripts/ae-sdk-docs/config.js +102 -0
- package/runtime/scripts/ae-sdk-docs/convert.js +430 -0
- package/runtime/scripts/ae-sdk-docs/index.js +314 -0
- package/runtime/scripts/ae-sdk-docs/source.js +205 -0
- package/runtime/scripts/ae-sdk-docs/split.js +170 -0
- package/runtime/scripts/ae-sdk-docs.js +72 -0
- package/runtime/src/host.js +1 -1
- package/src/api/error-messages.js +1 -1
- package/src/check/check.js +3 -3
- package/src/check/native.js +14 -0
- package/src/check/pricing.js +1 -1
- package/src/cli.js +2 -2
- package/src/commands/build.js +8 -2
- package/src/commands/check.js +10 -2
- package/src/commands/dev.js +9 -2
- package/src/commands/docs.js +5 -1
- package/src/commands/init.js +10 -7
- package/src/commands/push.js +5 -2
- package/src/commands/sdk.js +14 -0
- package/src/commands/testers.js +1 -1
- package/src/commands/upgrade.js +2 -2
- package/src/commands/whoami.js +1 -1
- package/src/dev/bridge.js +8 -0
- package/src/dev/native.js +3 -0
- package/src/dev/session.js +4 -3
- package/src/mcp/extendscript.js +9 -8
- package/src/mcp/host-target.js +1 -0
- package/src/mcp/panels.js +1 -1
- package/src/mcp/png.js +1 -0
- package/src/mcp/server.js +2 -2
- package/src/mcp/tools/capture.js +3 -0
- package/src/mcp/tools/host.js +1 -1
- package/src/mcp/tools/native.js +6 -0
- package/src/mcp/tools/project.js +48 -32
- package/src/mcp/tools/releases.js +23 -20
- package/src/native/build.js +27 -0
- package/src/native/cmake/CMakeLists.txt +88 -0
- package/src/native/cmake/zig-windows.cmake +19 -0
- package/src/native/effect.js +3 -0
- package/src/native/install.js +8 -0
- package/src/native/installers.js +4 -0
- package/src/native/pipl.js +20 -0
- package/src/native/restart.js +2 -0
- package/src/native/sdk.js +8 -0
- package/src/project/docs.js +3 -3
- package/src/project/project.js +2 -2
- package/src/project/template.js +5 -4
- package/src/prompt.js +1 -1
- package/src/release/native.js +3 -0
- package/src/release/publish.js +1 -1
- package/src/system/run-command.js +1 -1
- package/src/targets.js +1 -1
- package/template/cep/AGENTS.md +99 -164
- package/template/cep/README.md +7 -2
- package/template/cep/docs/code_guide.md +67 -0
- package/template/cep/docs/initial_setup.md +58 -12
- package/template/cep/docs/release_checklist.md +24 -0
- package/template/hosts/AGENTS.two-apps.md +12 -0
- package/template/native/.mcp.json +8 -0
- package/template/native/AGENTS.md +105 -0
- package/template/native/README.md +71 -0
- package/template/native/docs/code_guide.md +72 -0
- package/template/native/docs/initial_setup.md +179 -0
- package/template/native/docs/release_checklist.md +24 -0
- package/template/native/effect.json +16 -0
- package/template/native/gitignore +8 -0
- package/template/native/package.json +14 -0
- package/template/native/src/Effect.cpp +224 -0
- package/template/native/src/License.cpp +95 -0
- package/template/native/src/License.h +29 -0
- package/template/native/src/Render.h +68 -0
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
#include "json.hpp"
|
|
2
|
+
|
|
3
|
+
#include <cmath>
|
|
4
|
+
#include <cstdio>
|
|
5
|
+
#include <cstdlib>
|
|
6
|
+
#include <cctype>
|
|
7
|
+
#include <cstring>
|
|
8
|
+
|
|
9
|
+
namespace apn {
|
|
10
|
+
|
|
11
|
+
namespace {
|
|
12
|
+
constexpr int kMaxDepth = 64;
|
|
13
|
+
constexpr size_t kMaxInput = 8 * 1024 * 1024;
|
|
14
|
+
|
|
15
|
+
void append_utf8(std::string &out, uint32_t cp) {
|
|
16
|
+
if (cp < 0x80) {
|
|
17
|
+
out += (char)cp;
|
|
18
|
+
} else if (cp < 0x800) {
|
|
19
|
+
out += (char)(0xC0 | (cp >> 6));
|
|
20
|
+
out += (char)(0x80 | (cp & 0x3F));
|
|
21
|
+
} else if (cp < 0x10000) {
|
|
22
|
+
out += (char)(0xE0 | (cp >> 12));
|
|
23
|
+
out += (char)(0x80 | ((cp >> 6) & 0x3F));
|
|
24
|
+
out += (char)(0x80 | (cp & 0x3F));
|
|
25
|
+
} else {
|
|
26
|
+
out += (char)(0xF0 | (cp >> 18));
|
|
27
|
+
out += (char)(0x80 | ((cp >> 12) & 0x3F));
|
|
28
|
+
out += (char)(0x80 | ((cp >> 6) & 0x3F));
|
|
29
|
+
out += (char)(0x80 | (cp & 0x3F));
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
} // namespace
|
|
33
|
+
|
|
34
|
+
class JsonParser {
|
|
35
|
+
public:
|
|
36
|
+
explicit JsonParser(const std::string &s) : s_(s) {}
|
|
37
|
+
|
|
38
|
+
bool parse(Json &out) {
|
|
39
|
+
ws();
|
|
40
|
+
if (!value(out, 0)) return false;
|
|
41
|
+
ws();
|
|
42
|
+
return p_ == s_.size();
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
private:
|
|
46
|
+
void ws() {
|
|
47
|
+
while (p_ < s_.size() && (s_[p_] == ' ' || s_[p_] == '\t' || s_[p_] == '\n' || s_[p_] == '\r')) p_++;
|
|
48
|
+
}
|
|
49
|
+
bool lit(const char *word) {
|
|
50
|
+
size_t n = std::strlen(word);
|
|
51
|
+
if (s_.compare(p_, n, word) != 0) return false;
|
|
52
|
+
p_ += n;
|
|
53
|
+
return true;
|
|
54
|
+
}
|
|
55
|
+
bool hex4(uint32_t &v) {
|
|
56
|
+
if (p_ + 4 > s_.size()) return false;
|
|
57
|
+
v = 0;
|
|
58
|
+
for (int i = 0; i < 4; i++) {
|
|
59
|
+
char c = s_[p_++];
|
|
60
|
+
v <<= 4;
|
|
61
|
+
if (c >= '0' && c <= '9') v |= (uint32_t)(c - '0');
|
|
62
|
+
else if (c >= 'a' && c <= 'f') v |= (uint32_t)(c - 'a' + 10);
|
|
63
|
+
else if (c >= 'A' && c <= 'F') v |= (uint32_t)(c - 'A' + 10);
|
|
64
|
+
else return false;
|
|
65
|
+
}
|
|
66
|
+
return true;
|
|
67
|
+
}
|
|
68
|
+
bool string(std::string &out) {
|
|
69
|
+
if (p_ >= s_.size() || s_[p_] != '"') return false;
|
|
70
|
+
p_++;
|
|
71
|
+
while (p_ < s_.size()) {
|
|
72
|
+
unsigned char c = (unsigned char)s_[p_++];
|
|
73
|
+
if (c == '"') return true;
|
|
74
|
+
if (c < 0x20) return false;
|
|
75
|
+
if (c != '\\') { out += (char)c; continue; }
|
|
76
|
+
if (p_ >= s_.size()) return false;
|
|
77
|
+
char e = s_[p_++];
|
|
78
|
+
switch (e) {
|
|
79
|
+
case '"': out += '"'; break;
|
|
80
|
+
case '\\': out += '\\'; break;
|
|
81
|
+
case '/': out += '/'; break;
|
|
82
|
+
case 'b': out += '\b'; break;
|
|
83
|
+
case 'f': out += '\f'; break;
|
|
84
|
+
case 'n': out += '\n'; break;
|
|
85
|
+
case 'r': out += '\r'; break;
|
|
86
|
+
case 't': out += '\t'; break;
|
|
87
|
+
case 'u': {
|
|
88
|
+
uint32_t cp;
|
|
89
|
+
if (!hex4(cp)) return false;
|
|
90
|
+
if (cp >= 0xD800 && cp <= 0xDBFF) {
|
|
91
|
+
uint32_t lo;
|
|
92
|
+
if (p_ + 2 <= s_.size() && s_[p_] == '\\' && s_[p_ + 1] == 'u') {
|
|
93
|
+
p_ += 2;
|
|
94
|
+
if (!hex4(lo)) return false;
|
|
95
|
+
if (lo >= 0xDC00 && lo <= 0xDFFF) cp = 0x10000 + ((cp - 0xD800) << 10) + (lo - 0xDC00);
|
|
96
|
+
else { append_utf8(out, 0xFFFD); cp = lo; }
|
|
97
|
+
} else {
|
|
98
|
+
cp = 0xFFFD;
|
|
99
|
+
}
|
|
100
|
+
} else if (cp >= 0xDC00 && cp <= 0xDFFF) {
|
|
101
|
+
cp = 0xFFFD;
|
|
102
|
+
}
|
|
103
|
+
append_utf8(out, cp);
|
|
104
|
+
break;
|
|
105
|
+
}
|
|
106
|
+
default: return false;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return false;
|
|
110
|
+
}
|
|
111
|
+
bool number(Json &out) {
|
|
112
|
+
size_t start = p_;
|
|
113
|
+
if (s_[p_] == '-') p_++;
|
|
114
|
+
if (p_ >= s_.size()) return false;
|
|
115
|
+
if (s_[p_] == '0') p_++;
|
|
116
|
+
else if (s_[p_] >= '1' && s_[p_] <= '9') { while (p_ < s_.size() && isdigit((unsigned char)s_[p_])) p_++; }
|
|
117
|
+
else return false;
|
|
118
|
+
if (p_ < s_.size() && s_[p_] == '.') {
|
|
119
|
+
p_++;
|
|
120
|
+
if (p_ >= s_.size() || !isdigit((unsigned char)s_[p_])) return false;
|
|
121
|
+
while (p_ < s_.size() && isdigit((unsigned char)s_[p_])) p_++;
|
|
122
|
+
}
|
|
123
|
+
if (p_ < s_.size() && (s_[p_] == 'e' || s_[p_] == 'E')) {
|
|
124
|
+
p_++;
|
|
125
|
+
if (p_ < s_.size() && (s_[p_] == '+' || s_[p_] == '-')) p_++;
|
|
126
|
+
if (p_ >= s_.size() || !isdigit((unsigned char)s_[p_])) return false;
|
|
127
|
+
while (p_ < s_.size() && isdigit((unsigned char)s_[p_])) p_++;
|
|
128
|
+
}
|
|
129
|
+
std::string text = s_.substr(start, p_ - start);
|
|
130
|
+
out = Json::number(std::strtod(text.c_str(), nullptr));
|
|
131
|
+
return true;
|
|
132
|
+
}
|
|
133
|
+
bool value(Json &out, int depth) {
|
|
134
|
+
if (depth > kMaxDepth || p_ >= s_.size()) return false;
|
|
135
|
+
char c = s_[p_];
|
|
136
|
+
if (c == '{') {
|
|
137
|
+
p_++;
|
|
138
|
+
out = Json::object();
|
|
139
|
+
ws();
|
|
140
|
+
if (p_ < s_.size() && s_[p_] == '}') { p_++; return true; }
|
|
141
|
+
for (;;) {
|
|
142
|
+
ws();
|
|
143
|
+
std::string key;
|
|
144
|
+
if (!string(key)) return false;
|
|
145
|
+
ws();
|
|
146
|
+
if (p_ >= s_.size() || s_[p_] != ':') return false;
|
|
147
|
+
p_++;
|
|
148
|
+
ws();
|
|
149
|
+
Json v;
|
|
150
|
+
if (!value(v, depth + 1)) return false;
|
|
151
|
+
out.set(key, std::move(v));
|
|
152
|
+
ws();
|
|
153
|
+
if (p_ >= s_.size()) return false;
|
|
154
|
+
if (s_[p_] == ',') { p_++; continue; }
|
|
155
|
+
if (s_[p_] == '}') { p_++; return true; }
|
|
156
|
+
return false;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
if (c == '[') {
|
|
160
|
+
p_++;
|
|
161
|
+
out = Json::array();
|
|
162
|
+
ws();
|
|
163
|
+
if (p_ < s_.size() && s_[p_] == ']') { p_++; return true; }
|
|
164
|
+
for (;;) {
|
|
165
|
+
ws();
|
|
166
|
+
Json v;
|
|
167
|
+
if (!value(v, depth + 1)) return false;
|
|
168
|
+
out.push(std::move(v));
|
|
169
|
+
ws();
|
|
170
|
+
if (p_ >= s_.size()) return false;
|
|
171
|
+
if (s_[p_] == ',') { p_++; continue; }
|
|
172
|
+
if (s_[p_] == ']') { p_++; return true; }
|
|
173
|
+
return false;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
if (c == '"') {
|
|
177
|
+
std::string str;
|
|
178
|
+
if (!string(str)) return false;
|
|
179
|
+
out = Json::string(std::move(str));
|
|
180
|
+
return true;
|
|
181
|
+
}
|
|
182
|
+
if (c == 't') { if (!lit("true")) return false; out = Json::boolean(true); return true; }
|
|
183
|
+
if (c == 'f') { if (!lit("false")) return false; out = Json::boolean(false); return true; }
|
|
184
|
+
if (c == 'n') { if (!lit("null")) return false; out = Json(); return true; }
|
|
185
|
+
if (c == '-' || (c >= '0' && c <= '9')) return number(out);
|
|
186
|
+
return false;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const std::string &s_;
|
|
190
|
+
size_t p_ = 0;
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
const Json &Json::operator[](const std::string &key) const {
|
|
194
|
+
static const Json null_value;
|
|
195
|
+
if (type_ != Type::Object) return null_value;
|
|
196
|
+
// Last duplicate wins, like JSON.parse.
|
|
197
|
+
for (auto it = object_.rbegin(); it != object_.rend(); ++it) {
|
|
198
|
+
if (it->first == key) return it->second;
|
|
199
|
+
}
|
|
200
|
+
return null_value;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
bool Json::has(const std::string &key) const {
|
|
204
|
+
if (type_ != Type::Object) return false;
|
|
205
|
+
for (const auto &kv : object_) {
|
|
206
|
+
if (kv.first == key) return true;
|
|
207
|
+
}
|
|
208
|
+
return false;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
Json &Json::set(const std::string &key, Json value) {
|
|
212
|
+
if (type_ != Type::Object) { *this = object(); }
|
|
213
|
+
for (auto &kv : object_) {
|
|
214
|
+
if (kv.first == key) { kv.second = std::move(value); return kv.second; }
|
|
215
|
+
}
|
|
216
|
+
object_.emplace_back(key, std::move(value));
|
|
217
|
+
return object_.back().second;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
const Json &Json::at(size_t i) const {
|
|
221
|
+
static const Json null_value;
|
|
222
|
+
if (type_ != Type::Array || i >= array_.size()) return null_value;
|
|
223
|
+
return array_[i];
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
Json &Json::push(Json value) {
|
|
227
|
+
if (type_ != Type::Array) { *this = array(); }
|
|
228
|
+
array_.push_back(std::move(value));
|
|
229
|
+
return array_.back();
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
std::string Json::str(const std::string &fallback) const {
|
|
233
|
+
switch (type_) {
|
|
234
|
+
case Type::String: return string_;
|
|
235
|
+
case Type::Bool: return bool_ ? "true" : "false";
|
|
236
|
+
case Type::Number: {
|
|
237
|
+
if (std::isfinite(number_) && std::floor(number_) == number_ && std::fabs(number_) < 9007199254740992.0) {
|
|
238
|
+
char buf[32];
|
|
239
|
+
std::snprintf(buf, sizeof buf, "%.0f", number_);
|
|
240
|
+
return buf;
|
|
241
|
+
}
|
|
242
|
+
char buf[40];
|
|
243
|
+
std::snprintf(buf, sizeof buf, "%.17g", number_);
|
|
244
|
+
return buf;
|
|
245
|
+
}
|
|
246
|
+
default: return fallback;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
static void dump_string(std::string &out, const std::string &s) {
|
|
251
|
+
out += '"';
|
|
252
|
+
for (unsigned char c : s) {
|
|
253
|
+
switch (c) {
|
|
254
|
+
case '"': out += "\\\""; break;
|
|
255
|
+
case '\\': out += "\\\\"; break;
|
|
256
|
+
case '\b': out += "\\b"; break;
|
|
257
|
+
case '\f': out += "\\f"; break;
|
|
258
|
+
case '\n': out += "\\n"; break;
|
|
259
|
+
case '\r': out += "\\r"; break;
|
|
260
|
+
case '\t': out += "\\t"; break;
|
|
261
|
+
default:
|
|
262
|
+
if (c < 0x20) {
|
|
263
|
+
char buf[8];
|
|
264
|
+
std::snprintf(buf, sizeof buf, "\\u%04x", c);
|
|
265
|
+
out += buf;
|
|
266
|
+
} else {
|
|
267
|
+
out += (char)c;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
out += '"';
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
void Json::dump_to(std::string &out, bool pretty, int indent) const {
|
|
275
|
+
auto newline = [&](int level) {
|
|
276
|
+
if (!pretty) return;
|
|
277
|
+
out += '\n';
|
|
278
|
+
out.append((size_t)level * 2, ' ');
|
|
279
|
+
};
|
|
280
|
+
switch (type_) {
|
|
281
|
+
case Type::Null: out += "null"; break;
|
|
282
|
+
case Type::Bool: out += bool_ ? "true" : "false"; break;
|
|
283
|
+
case Type::Number: out += std::isfinite(number_) ? str() : "null"; break;
|
|
284
|
+
case Type::String: dump_string(out, string_); break;
|
|
285
|
+
case Type::Array:
|
|
286
|
+
if (array_.empty()) { out += "[]"; break; }
|
|
287
|
+
out += '[';
|
|
288
|
+
for (size_t i = 0; i < array_.size(); i++) {
|
|
289
|
+
if (i) out += ',';
|
|
290
|
+
newline(indent + 1);
|
|
291
|
+
array_[i].dump_to(out, pretty, indent + 1);
|
|
292
|
+
}
|
|
293
|
+
newline(indent);
|
|
294
|
+
out += ']';
|
|
295
|
+
break;
|
|
296
|
+
case Type::Object:
|
|
297
|
+
if (object_.empty()) { out += "{}"; break; }
|
|
298
|
+
out += '{';
|
|
299
|
+
for (size_t i = 0; i < object_.size(); i++) {
|
|
300
|
+
if (i) out += ',';
|
|
301
|
+
newline(indent + 1);
|
|
302
|
+
dump_string(out, object_[i].first);
|
|
303
|
+
out += pretty ? ": " : ":";
|
|
304
|
+
object_[i].second.dump_to(out, pretty, indent + 1);
|
|
305
|
+
}
|
|
306
|
+
newline(indent);
|
|
307
|
+
out += '}';
|
|
308
|
+
break;
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
std::string Json::dump(bool pretty) const {
|
|
313
|
+
std::string out;
|
|
314
|
+
dump_to(out, pretty, 0);
|
|
315
|
+
return out;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
std::optional<Json> Json::parse(const std::string &text) {
|
|
319
|
+
if (text.size() > kMaxInput) return std::nullopt;
|
|
320
|
+
Json out;
|
|
321
|
+
JsonParser parser(text);
|
|
322
|
+
if (!parser.parse(out)) return std::nullopt;
|
|
323
|
+
return out;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
} // namespace apn
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
// Minimal JSON (RFC 8259) value, parser and serializer for ap_native.
|
|
2
|
+
// Inputs are small API responses and cache files; limits: depth 64, 8 MB.
|
|
3
|
+
#pragma once
|
|
4
|
+
|
|
5
|
+
#include <cstdint>
|
|
6
|
+
#include <map>
|
|
7
|
+
#include <memory>
|
|
8
|
+
#include <optional>
|
|
9
|
+
#include <string>
|
|
10
|
+
#include <utility>
|
|
11
|
+
#include <vector>
|
|
12
|
+
|
|
13
|
+
namespace apn {
|
|
14
|
+
|
|
15
|
+
class Json {
|
|
16
|
+
public:
|
|
17
|
+
enum class Type { Null, Bool, Number, String, Array, Object };
|
|
18
|
+
|
|
19
|
+
Json() = default;
|
|
20
|
+
static Json null() { return Json(); }
|
|
21
|
+
static Json boolean(bool b) { Json j; j.type_ = Type::Bool; j.bool_ = b; return j; }
|
|
22
|
+
static Json number(double n) { Json j; j.type_ = Type::Number; j.number_ = n; return j; }
|
|
23
|
+
static Json string(std::string s) { Json j; j.type_ = Type::String; j.string_ = std::move(s); return j; }
|
|
24
|
+
static Json array() { Json j; j.type_ = Type::Array; return j; }
|
|
25
|
+
static Json object() { Json j; j.type_ = Type::Object; return j; }
|
|
26
|
+
|
|
27
|
+
Type type() const { return type_; }
|
|
28
|
+
bool is_null() const { return type_ == Type::Null; }
|
|
29
|
+
bool is_string() const { return type_ == Type::String; }
|
|
30
|
+
bool is_number() const { return type_ == Type::Number; }
|
|
31
|
+
bool is_bool() const { return type_ == Type::Bool; }
|
|
32
|
+
bool is_object() const { return type_ == Type::Object; }
|
|
33
|
+
bool is_array() const { return type_ == Type::Array; }
|
|
34
|
+
|
|
35
|
+
bool as_bool() const { return type_ == Type::Bool && bool_; }
|
|
36
|
+
double as_number(double fallback = 0) const { return type_ == Type::Number ? number_ : fallback; }
|
|
37
|
+
const std::string &as_string() const { static const std::string empty; return type_ == Type::String ? string_ : empty; }
|
|
38
|
+
|
|
39
|
+
// Object access: missing keys (or a non-object) give a shared null.
|
|
40
|
+
const Json &operator[](const std::string &key) const;
|
|
41
|
+
bool has(const std::string &key) const;
|
|
42
|
+
// Inserts or replaces (keeps insertion order for serialization).
|
|
43
|
+
Json &set(const std::string &key, Json value);
|
|
44
|
+
const std::vector<std::pair<std::string, Json>> &items() const { return object_; }
|
|
45
|
+
|
|
46
|
+
// Array access.
|
|
47
|
+
size_t size() const { return type_ == Type::Array ? array_.size() : 0; }
|
|
48
|
+
const Json &at(size_t i) const;
|
|
49
|
+
Json &push(Json value);
|
|
50
|
+
|
|
51
|
+
// String value of a string, number (integral) or bool; fallback otherwise.
|
|
52
|
+
std::string str(const std::string &fallback = "") const;
|
|
53
|
+
|
|
54
|
+
std::string dump(bool pretty = false) const;
|
|
55
|
+
static std::optional<Json> parse(const std::string &text);
|
|
56
|
+
|
|
57
|
+
private:
|
|
58
|
+
void dump_to(std::string &out, bool pretty, int indent) const;
|
|
59
|
+
|
|
60
|
+
Type type_ = Type::Null;
|
|
61
|
+
bool bool_ = false;
|
|
62
|
+
double number_ = 0;
|
|
63
|
+
std::string string_;
|
|
64
|
+
std::vector<Json> array_;
|
|
65
|
+
std::vector<std::pair<std::string, Json>> object_;
|
|
66
|
+
|
|
67
|
+
friend class JsonParser;
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
} // namespace apn
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
// Platform seams for ap_native. Everything the engine needs from the OS goes
|
|
2
|
+
// through Env, so tests replace the network, clock, browser and fingerprint.
|
|
3
|
+
#pragma once
|
|
4
|
+
|
|
5
|
+
#include <atomic>
|
|
6
|
+
#include <cstdint>
|
|
7
|
+
#include <functional>
|
|
8
|
+
#include <memory>
|
|
9
|
+
#include <optional>
|
|
10
|
+
#include <string>
|
|
11
|
+
#include <tuple>
|
|
12
|
+
#include <utility>
|
|
13
|
+
#include <vector>
|
|
14
|
+
|
|
15
|
+
namespace apn {
|
|
16
|
+
|
|
17
|
+
struct HttpRequest {
|
|
18
|
+
std::string method = "POST";
|
|
19
|
+
std::string url;
|
|
20
|
+
std::string body; // sent when non-empty (JSON)
|
|
21
|
+
std::vector<std::pair<std::string, std::string>> headers; // in addition to Accept/User-Agent
|
|
22
|
+
int timeout_ms = 20000; // whole request (JS runtime: 20 s)
|
|
23
|
+
std::string download_path; // non-empty: stream the body to this file
|
|
24
|
+
uint64_t max_bytes = 4 * 1024 * 1024; // larger bodies fail as network errors
|
|
25
|
+
bool follow_redirects = false; // GET downloads only
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
struct HttpResponse {
|
|
29
|
+
bool network_error = false; // connection failure, timeout, cancel, TLS, too large
|
|
30
|
+
std::string error; // short reason; never contains the URL
|
|
31
|
+
int status = 0;
|
|
32
|
+
std::string body; // empty when download_path was used
|
|
33
|
+
std::string content_type;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
class HttpClient {
|
|
37
|
+
public:
|
|
38
|
+
virtual ~HttpClient() = default;
|
|
39
|
+
// Blocking; called only on the worker thread. Must return promptly once
|
|
40
|
+
// `cancel` becomes true.
|
|
41
|
+
virtual HttpResponse send(const HttpRequest &request, const std::atomic<bool> &cancel) = 0;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
// NSURLSession (macOS) / WinHTTP (Windows). Ephemeral: no cookies, no cache,
|
|
45
|
+
// no stored credentials.
|
|
46
|
+
std::shared_ptr<HttpClient> make_platform_http(const std::string &user_agent);
|
|
47
|
+
|
|
48
|
+
struct Env {
|
|
49
|
+
std::shared_ptr<HttpClient> http;
|
|
50
|
+
std::function<int64_t()> now_ms; // Unix epoch milliseconds
|
|
51
|
+
std::function<bool(const std::string &)> open_url; // default browser
|
|
52
|
+
std::string data_root; // machine data root, see platform_data_root()
|
|
53
|
+
std::function<std::optional<std::string>()> hardware_fingerprint;
|
|
54
|
+
std::function<std::optional<std::string>()> mac_fingerprint;
|
|
55
|
+
std::function<std::string(size_t)> random_hex; // cryptographically random, 2*n hex chars
|
|
56
|
+
std::function<bool(const std::string &)> strip_quarantine; // macOS: remove com.apple.quarantine recursively
|
|
57
|
+
std::function<void(int, const std::string &)> log;
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
// Real implementations.
|
|
61
|
+
Env platform_env(const std::string &user_agent);
|
|
62
|
+
// ~/Library/Application Support (macOS, = CEP USER_DATA) or the non-roaming
|
|
63
|
+
// %LOCALAPPDATA% (Windows). Empty if it cannot be determined.
|
|
64
|
+
std::string platform_data_root();
|
|
65
|
+
// "mac:<IOPlatformUUID upper>" / "win:<MachineGuid upper>"; same strings as the JS runtime.
|
|
66
|
+
std::optional<std::string> platform_hardware_fingerprint();
|
|
67
|
+
// "mac-list:<sorted physical MACs>"; same filter as the JS runtime.
|
|
68
|
+
std::optional<std::string> platform_mac_fingerprint();
|
|
69
|
+
bool platform_open_url(const std::string &url);
|
|
70
|
+
std::string platform_random_hex(size_t bytes);
|
|
71
|
+
// Absolute path of the binary (plugin) that contains this library.
|
|
72
|
+
std::string platform_self_binary_path();
|
|
73
|
+
bool platform_strip_quarantine(const std::string &path);
|
|
74
|
+
// "macos" / "windows"; "arm64" / "x64".
|
|
75
|
+
const char *platform_os();
|
|
76
|
+
const char *platform_arch();
|
|
77
|
+
|
|
78
|
+
// Pure helper shared with tests: the JS runtime's MAC filter.
|
|
79
|
+
// interfaces: (name, mac "aa:bb:..", is_loopback).
|
|
80
|
+
std::optional<std::string> mac_fingerprint_from(
|
|
81
|
+
const std::vector<std::tuple<std::string, std::string, bool>> &interfaces);
|
|
82
|
+
|
|
83
|
+
} // namespace apn
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
#include <algorithm>
|
|
2
|
+
#include <chrono>
|
|
3
|
+
#include <cstdlib>
|
|
4
|
+
#include <regex>
|
|
5
|
+
#include <set>
|
|
6
|
+
|
|
7
|
+
#include "platform.hpp"
|
|
8
|
+
|
|
9
|
+
namespace apn {
|
|
10
|
+
|
|
11
|
+
std::optional<std::string> mac_fingerprint_from(
|
|
12
|
+
const std::vector<std::tuple<std::string, std::string, bool>> &interfaces) {
|
|
13
|
+
// Same filter as packages/runtime/src/computer-id.js (VIRTUAL_IFACE, internal,
|
|
14
|
+
// all-zero and locally administered MACs).
|
|
15
|
+
static const std::regex virtual_iface(
|
|
16
|
+
"^(awdl|llw|utun|bridge|vmnet|vnic|gif|stf|anpi|ap\\d|tun|tap|lo|docker|veth|zt|wg|vboxnet|vEthernet)",
|
|
17
|
+
std::regex::icase);
|
|
18
|
+
std::set<std::string> seen;
|
|
19
|
+
for (const auto &iface : interfaces) {
|
|
20
|
+
const std::string &name = std::get<0>(iface);
|
|
21
|
+
std::string mac = std::get<1>(iface);
|
|
22
|
+
if (std::regex_search(name, virtual_iface)) continue;
|
|
23
|
+
if (std::get<2>(iface) || mac.empty()) continue;
|
|
24
|
+
std::transform(mac.begin(), mac.end(), mac.begin(), [](unsigned char c) { return (char)std::tolower(c); });
|
|
25
|
+
if (mac == "00:00:00:00:00:00") continue;
|
|
26
|
+
const std::string octet = mac.substr(0, mac.find(':')); // must outlive `end`
|
|
27
|
+
char *end = nullptr;
|
|
28
|
+
long first = std::strtol(octet.c_str(), &end, 16);
|
|
29
|
+
if (!end || *end != '\0' || (first & 2)) continue;
|
|
30
|
+
seen.insert(mac);
|
|
31
|
+
}
|
|
32
|
+
if (seen.empty()) return std::nullopt;
|
|
33
|
+
std::string out = "mac-list:";
|
|
34
|
+
bool firstItem = true;
|
|
35
|
+
for (const auto &m : seen) { // std::set iterates sorted, like Object.keys(...).sort()
|
|
36
|
+
if (!firstItem) out += ',';
|
|
37
|
+
out += m;
|
|
38
|
+
firstItem = false;
|
|
39
|
+
}
|
|
40
|
+
return out;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
Env platform_env(const std::string &user_agent) {
|
|
44
|
+
Env env;
|
|
45
|
+
env.http = make_platform_http(user_agent);
|
|
46
|
+
env.now_ms = [] {
|
|
47
|
+
return (int64_t)std::chrono::duration_cast<std::chrono::milliseconds>(
|
|
48
|
+
std::chrono::system_clock::now().time_since_epoch())
|
|
49
|
+
.count();
|
|
50
|
+
};
|
|
51
|
+
env.open_url = platform_open_url;
|
|
52
|
+
env.data_root = platform_data_root();
|
|
53
|
+
env.hardware_fingerprint = platform_hardware_fingerprint;
|
|
54
|
+
env.mac_fingerprint = platform_mac_fingerprint;
|
|
55
|
+
env.random_hex = platform_random_hex;
|
|
56
|
+
env.strip_quarantine = platform_strip_quarantine;
|
|
57
|
+
return env;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
} // namespace apn
|