afterpresets 0.2.0 → 0.4.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 +9 -6
- 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 +7 -4
- package/runtime/docs/cep/host-after-effects.md +3 -3
- package/runtime/docs/cep/host-premiere.md +4 -4
- package/runtime/docs/cep/host.md +10 -2
- 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/agent/cli-group.js +9 -0
- package/src/agent/extendscript.js +10 -0
- package/src/agent/host-target.js +1 -0
- package/src/agent/panels.js +1 -0
- package/src/agent/png.js +1 -0
- package/src/agent/run-tool.js +3 -0
- package/src/{mcp → agent}/snapshot.js +2 -2
- package/src/agent/tool-error.js +1 -0
- package/src/agent/tools/capture.js +3 -0
- package/src/agent/tools/host.js +1 -0
- package/src/agent/tools/native.js +1 -0
- package/src/agent/tools/panel.js +7 -0
- package/src/agent/tools.js +2 -0
- package/src/api/error-messages.js +1 -1
- package/src/build/build.js +9 -8
- 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 +3 -3
- package/src/commands/build.js +8 -2
- package/src/commands/check.js +10 -2
- package/src/commands/comp.js +6 -0
- package/src/commands/dev.js +29 -2
- package/src/commands/docs.js +5 -1
- package/src/commands/host.js +1 -0
- package/src/commands/init.js +12 -7
- package/src/commands/panel.js +1 -0
- package/src/commands/push.js +9 -2
- package/src/commands/releases.js +8 -3
- 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 +5 -4
- 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/confirm.js +5 -0
- 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 +102 -164
- package/template/cep/README.md +7 -3
- package/template/cep/docs/code_guide.md +67 -0
- package/template/cep/docs/initial_setup.md +63 -42
- package/template/cep/docs/release_checklist.md +25 -0
- package/template/cep/gitignore +2 -1
- package/template/cep/src/features/update-screen/update-screen.js +1 -1
- package/template/hosts/AGENTS.two-apps.md +12 -0
- package/template/native/AGENTS.md +106 -0
- package/template/native/README.md +71 -0
- package/template/native/docs/code_guide.md +72 -0
- package/template/native/docs/initial_setup.md +154 -0
- package/template/native/docs/release_checklist.md +25 -0
- package/template/native/effect.json +16 -0
- package/template/native/gitignore +9 -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
- package/src/commands/mcp.js +0 -7
- package/src/mcp/extendscript.js +0 -9
- package/src/mcp/panels.js +0 -1
- package/src/mcp/protocol.js +0 -4
- package/src/mcp/server.js +0 -2
- package/src/mcp/tools/account.js +0 -3
- package/src/mcp/tools/host.js +0 -1
- package/src/mcp/tools/panel.js +0 -7
- package/src/mcp/tools/project.js +0 -40
- package/src/mcp/tools/releases.js +0 -29
- package/template/cep/.mcp.json +0 -8
- /package/src/{mcp → agent}/capture.js +0 -0
- /package/src/{mcp → agent}/redact.js +0 -0
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
// macOS platform layer: NSURLSession HTTP, NSWorkspace browser, IOKit hardware
|
|
2
|
+
// UUID, getifaddrs MAC list, dladdr self path, quarantine removal.
|
|
3
|
+
// Compiled with -fobjc-arc.
|
|
4
|
+
#import <Foundation/Foundation.h>
|
|
5
|
+
#import <AppKit/AppKit.h>
|
|
6
|
+
#include <IOKit/IOKitLib.h>
|
|
7
|
+
|
|
8
|
+
#include <dlfcn.h>
|
|
9
|
+
#include <ifaddrs.h>
|
|
10
|
+
#include <net/if.h>
|
|
11
|
+
#include <net/if_dl.h>
|
|
12
|
+
#include <pwd.h>
|
|
13
|
+
#include <stdlib.h>
|
|
14
|
+
#include <sys/xattr.h>
|
|
15
|
+
#include <unistd.h>
|
|
16
|
+
|
|
17
|
+
#include <map>
|
|
18
|
+
#include <set>
|
|
19
|
+
|
|
20
|
+
#include "dialog.hpp"
|
|
21
|
+
#include "platform.hpp"
|
|
22
|
+
|
|
23
|
+
#if !__has_feature(objc_arc)
|
|
24
|
+
#error "platform_mac.mm must be compiled with -fobjc-arc"
|
|
25
|
+
#endif
|
|
26
|
+
|
|
27
|
+
// ------------------------------------------------------------------ HTTP
|
|
28
|
+
|
|
29
|
+
@interface APNSessionDelegate : NSObject <NSURLSessionDataDelegate, NSURLSessionDownloadDelegate>
|
|
30
|
+
@property(nonatomic) BOOL followRedirects;
|
|
31
|
+
@property(nonatomic) uint64_t maxBytes;
|
|
32
|
+
@property(nonatomic, copy) NSString *downloadPath;
|
|
33
|
+
@property(nonatomic, strong) NSMutableData *body;
|
|
34
|
+
@property(nonatomic, copy) NSString *error;
|
|
35
|
+
@property(nonatomic) BOOL tooLarge;
|
|
36
|
+
@property(nonatomic) BOOL moved;
|
|
37
|
+
@property(nonatomic, strong) dispatch_semaphore_t done;
|
|
38
|
+
@end
|
|
39
|
+
|
|
40
|
+
@implementation APNSessionDelegate
|
|
41
|
+
- (void)URLSession:(NSURLSession *)session
|
|
42
|
+
task:(NSURLSessionTask *)task
|
|
43
|
+
willPerformHTTPRedirection:(NSHTTPURLResponse *)response
|
|
44
|
+
newRequest:(NSURLRequest *)request
|
|
45
|
+
completionHandler:(void (^)(NSURLRequest *))completionHandler {
|
|
46
|
+
// POSTs never follow redirects (the JS runtime's Node client does not either);
|
|
47
|
+
// downloads follow https redirects (http only to the local machine, for development).
|
|
48
|
+
NSString *scheme = request.URL.scheme.lowercaseString;
|
|
49
|
+
NSString *host = request.URL.host.lowercaseString;
|
|
50
|
+
BOOL local = [host isEqualToString:@"127.0.0.1"] || [host isEqualToString:@"localhost"];
|
|
51
|
+
BOOL ok = self.followRedirects && ([scheme isEqualToString:@"https"] || ([scheme isEqualToString:@"http"] && local));
|
|
52
|
+
completionHandler(ok ? request : nil);
|
|
53
|
+
}
|
|
54
|
+
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data {
|
|
55
|
+
if (self.body.length + data.length > self.maxBytes) {
|
|
56
|
+
self.tooLarge = YES;
|
|
57
|
+
[dataTask cancel];
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
[self.body appendData:data];
|
|
61
|
+
}
|
|
62
|
+
- (void)URLSession:(NSURLSession *)session
|
|
63
|
+
downloadTask:(NSURLSessionDownloadTask *)downloadTask
|
|
64
|
+
didWriteData:(int64_t)bytesWritten
|
|
65
|
+
totalBytesWritten:(int64_t)totalBytesWritten
|
|
66
|
+
totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite {
|
|
67
|
+
if ((uint64_t)totalBytesWritten > self.maxBytes) {
|
|
68
|
+
self.tooLarge = YES;
|
|
69
|
+
[downloadTask cancel];
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
- (void)URLSession:(NSURLSession *)session
|
|
73
|
+
downloadTask:(NSURLSessionDownloadTask *)downloadTask
|
|
74
|
+
didFinishDownloadingToURL:(NSURL *)location {
|
|
75
|
+
// The temp file is deleted when this returns: move it now.
|
|
76
|
+
NSFileManager *fm = [NSFileManager defaultManager];
|
|
77
|
+
NSURL *dest = [NSURL fileURLWithPath:self.downloadPath];
|
|
78
|
+
[fm removeItemAtURL:dest error:nil];
|
|
79
|
+
NSError *err = nil;
|
|
80
|
+
self.moved = [fm moveItemAtURL:location toURL:dest error:&err];
|
|
81
|
+
if (!self.moved) self.error = @"could not save the download";
|
|
82
|
+
}
|
|
83
|
+
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {
|
|
84
|
+
if (error && !self.error) {
|
|
85
|
+
// Domain/code only: localizedDescription can contain the URL.
|
|
86
|
+
self.error = [NSString stringWithFormat:@"%@ %ld", error.domain, (long)error.code];
|
|
87
|
+
if ([error.domain isEqualToString:NSURLErrorDomain]) {
|
|
88
|
+
switch (error.code) {
|
|
89
|
+
case NSURLErrorTimedOut: self.error = @"Request timed out."; break;
|
|
90
|
+
case NSURLErrorNotConnectedToInternet: self.error = @"Not connected to the internet."; break;
|
|
91
|
+
case NSURLErrorCannotFindHost: self.error = @"Host not found."; break;
|
|
92
|
+
case NSURLErrorCannotConnectToHost: self.error = @"Could not connect."; break;
|
|
93
|
+
case NSURLErrorCancelled: self.error = self.tooLarge ? @"Response too large." : @"Cancelled."; break;
|
|
94
|
+
default: break;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
dispatch_semaphore_signal(self.done);
|
|
99
|
+
}
|
|
100
|
+
@end
|
|
101
|
+
|
|
102
|
+
namespace apn {
|
|
103
|
+
|
|
104
|
+
namespace {
|
|
105
|
+
|
|
106
|
+
class MacHttp : public HttpClient {
|
|
107
|
+
public:
|
|
108
|
+
explicit MacHttp(std::string ua) : ua_(std::move(ua)) {}
|
|
109
|
+
|
|
110
|
+
HttpResponse send(const HttpRequest &r, const std::atomic<bool> &cancel) override {
|
|
111
|
+
@autoreleasepool {
|
|
112
|
+
HttpResponse out;
|
|
113
|
+
NSURL *url = [NSURL URLWithString:[NSString stringWithUTF8String:r.url.c_str()]];
|
|
114
|
+
if (!url || !url.scheme) {
|
|
115
|
+
out.network_error = true;
|
|
116
|
+
out.error = "Unsupported URL.";
|
|
117
|
+
return out;
|
|
118
|
+
}
|
|
119
|
+
NSURLSessionConfiguration *cfg = [NSURLSessionConfiguration ephemeralSessionConfiguration];
|
|
120
|
+
cfg.HTTPCookieStorage = nil;
|
|
121
|
+
cfg.HTTPShouldSetCookies = NO;
|
|
122
|
+
cfg.URLCache = nil;
|
|
123
|
+
cfg.URLCredentialStorage = nil;
|
|
124
|
+
cfg.requestCachePolicy = NSURLRequestReloadIgnoringLocalCacheData;
|
|
125
|
+
cfg.timeoutIntervalForRequest = r.download_path.empty() ? r.timeout_ms / 1000.0 : 60;
|
|
126
|
+
cfg.timeoutIntervalForResource = r.timeout_ms / 1000.0;
|
|
127
|
+
cfg.waitsForConnectivity = NO;
|
|
128
|
+
APNSessionDelegate *d = [APNSessionDelegate new];
|
|
129
|
+
d.followRedirects = r.follow_redirects;
|
|
130
|
+
d.maxBytes = r.max_bytes;
|
|
131
|
+
d.body = [NSMutableData data];
|
|
132
|
+
d.done = dispatch_semaphore_create(0);
|
|
133
|
+
if (!r.download_path.empty()) d.downloadPath = [NSString stringWithUTF8String:r.download_path.c_str()];
|
|
134
|
+
NSOperationQueue *queue = [NSOperationQueue new];
|
|
135
|
+
queue.maxConcurrentOperationCount = 1;
|
|
136
|
+
NSURLSession *session = [NSURLSession sessionWithConfiguration:cfg delegate:d delegateQueue:queue];
|
|
137
|
+
|
|
138
|
+
NSMutableURLRequest *req = [NSMutableURLRequest requestWithURL:url];
|
|
139
|
+
req.HTTPMethod = [NSString stringWithUTF8String:r.method.c_str()];
|
|
140
|
+
[req setValue:@"application/json" forHTTPHeaderField:@"Accept"];
|
|
141
|
+
[req setValue:[NSString stringWithUTF8String:ua_.c_str()] forHTTPHeaderField:@"User-Agent"];
|
|
142
|
+
for (const auto &h : r.headers) {
|
|
143
|
+
[req setValue:[NSString stringWithUTF8String:h.second.c_str()]
|
|
144
|
+
forHTTPHeaderField:[NSString stringWithUTF8String:h.first.c_str()]];
|
|
145
|
+
}
|
|
146
|
+
if (!r.body.empty()) req.HTTPBody = [NSData dataWithBytes:r.body.data() length:r.body.size()];
|
|
147
|
+
|
|
148
|
+
NSURLSessionTask *task = r.download_path.empty() ? [session dataTaskWithRequest:req]
|
|
149
|
+
: [session downloadTaskWithRequest:req];
|
|
150
|
+
[task resume];
|
|
151
|
+
// Wait in slices so a shutdown can cancel promptly.
|
|
152
|
+
while (dispatch_semaphore_wait(d.done, dispatch_time(DISPATCH_TIME_NOW, 100 * NSEC_PER_MSEC)) != 0) {
|
|
153
|
+
if (cancel.load()) [task cancel];
|
|
154
|
+
}
|
|
155
|
+
[session finishTasksAndInvalidate];
|
|
156
|
+
|
|
157
|
+
NSHTTPURLResponse *resp = [task.response isKindOfClass:[NSHTTPURLResponse class]] ? (NSHTTPURLResponse *)task.response : nil;
|
|
158
|
+
if (d.error || !resp) {
|
|
159
|
+
out.network_error = true;
|
|
160
|
+
out.error = d.error ? std::string(d.error.UTF8String) : "No response.";
|
|
161
|
+
return out;
|
|
162
|
+
}
|
|
163
|
+
if (!r.download_path.empty() && resp.statusCode == 200 && !d.moved) {
|
|
164
|
+
out.network_error = true;
|
|
165
|
+
out.error = "could not save the download";
|
|
166
|
+
return out;
|
|
167
|
+
}
|
|
168
|
+
out.status = (int)resp.statusCode;
|
|
169
|
+
NSString *ct = resp.allHeaderFields[@"Content-Type"];
|
|
170
|
+
if (ct) out.content_type = ct.UTF8String;
|
|
171
|
+
if (r.download_path.empty()) out.body.assign((const char *)d.body.bytes, d.body.length);
|
|
172
|
+
return out;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
private:
|
|
177
|
+
std::string ua_;
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
std::string to_std(NSString *s) { return s ? std::string(s.UTF8String) : std::string(); }
|
|
181
|
+
|
|
182
|
+
} // namespace
|
|
183
|
+
|
|
184
|
+
std::shared_ptr<HttpClient> make_platform_http(const std::string &user_agent) {
|
|
185
|
+
return std::make_shared<MacHttp>(user_agent);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
std::string platform_data_root() {
|
|
189
|
+
// CEP's SystemPath.USER_DATA on macOS (the JS runtime's root).
|
|
190
|
+
const char *home = getenv("HOME");
|
|
191
|
+
std::string h = home && *home ? home : "";
|
|
192
|
+
if (h.empty()) {
|
|
193
|
+
struct passwd *pw = getpwuid(getuid());
|
|
194
|
+
if (pw && pw->pw_dir) h = pw->pw_dir;
|
|
195
|
+
}
|
|
196
|
+
return h.empty() ? "" : h + "/Library/Application Support";
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
std::optional<std::string> platform_hardware_fingerprint() {
|
|
200
|
+
io_service_t dev = IOServiceGetMatchingService(kIOMainPortDefault, IOServiceMatching("IOPlatformExpertDevice"));
|
|
201
|
+
if (!dev) return std::nullopt;
|
|
202
|
+
CFTypeRef value = IORegistryEntryCreateCFProperty(dev, CFSTR("IOPlatformUUID"), kCFAllocatorDefault, 0);
|
|
203
|
+
IOObjectRelease(dev);
|
|
204
|
+
if (!value) return std::nullopt;
|
|
205
|
+
std::string uuid;
|
|
206
|
+
if (CFGetTypeID(value) == CFStringGetTypeID()) uuid = to_std((__bridge NSString *)value);
|
|
207
|
+
CFRelease(value);
|
|
208
|
+
// The JS runtime's regex only accepts [0-9A-Fa-f-]+.
|
|
209
|
+
if (uuid.empty() || uuid.find_first_not_of("0123456789abcdefABCDEF-") != std::string::npos) return std::nullopt;
|
|
210
|
+
for (auto &c : uuid) c = (char)toupper((unsigned char)c);
|
|
211
|
+
return "mac:" + uuid;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
std::optional<std::string> platform_mac_fingerprint() {
|
|
215
|
+
// Mirrors Node's os.networkInterfaces() (libuv): interfaces that are UP and
|
|
216
|
+
// RUNNING with an address, MAC taken from the AF_LINK entry of the same name.
|
|
217
|
+
struct ifaddrs *list = nullptr;
|
|
218
|
+
if (getifaddrs(&list) != 0) return std::nullopt;
|
|
219
|
+
std::map<std::string, std::string> macs;
|
|
220
|
+
std::map<std::string, bool> listed; // name -> loopback
|
|
221
|
+
for (struct ifaddrs *a = list; a; a = a->ifa_next) {
|
|
222
|
+
if (!a->ifa_addr || !a->ifa_name) continue;
|
|
223
|
+
if (a->ifa_addr->sa_family == AF_LINK) {
|
|
224
|
+
auto *sdl = (struct sockaddr_dl *)a->ifa_addr;
|
|
225
|
+
if (sdl->sdl_alen == 6) {
|
|
226
|
+
const unsigned char *m = (const unsigned char *)LLADDR(sdl);
|
|
227
|
+
char buf[18];
|
|
228
|
+
snprintf(buf, sizeof buf, "%02x:%02x:%02x:%02x:%02x:%02x", m[0], m[1], m[2], m[3], m[4], m[5]);
|
|
229
|
+
macs[a->ifa_name] = buf;
|
|
230
|
+
}
|
|
231
|
+
continue;
|
|
232
|
+
}
|
|
233
|
+
if (!((a->ifa_flags & IFF_UP) && (a->ifa_flags & IFF_RUNNING))) continue;
|
|
234
|
+
listed[a->ifa_name] = (a->ifa_flags & IFF_LOOPBACK) != 0;
|
|
235
|
+
}
|
|
236
|
+
freeifaddrs(list);
|
|
237
|
+
std::vector<std::tuple<std::string, std::string, bool>> ifaces;
|
|
238
|
+
for (const auto &kv : listed) {
|
|
239
|
+
auto it = macs.find(kv.first);
|
|
240
|
+
ifaces.emplace_back(kv.first, it == macs.end() ? "00:00:00:00:00:00" : it->second, kv.second);
|
|
241
|
+
}
|
|
242
|
+
return mac_fingerprint_from(ifaces);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
bool platform_open_url(const std::string &url) {
|
|
246
|
+
@autoreleasepool {
|
|
247
|
+
NSURL *u = [NSURL URLWithString:[NSString stringWithUTF8String:url.c_str()]];
|
|
248
|
+
NSString *scheme = u.scheme.lowercaseString;
|
|
249
|
+
if (!u || !([scheme isEqualToString:@"https"] || [scheme isEqualToString:@"http"])) return false;
|
|
250
|
+
// NSWorkspace is safe to call from any thread.
|
|
251
|
+
return [[NSWorkspace sharedWorkspace] openURL:u];
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
std::string platform_random_hex(size_t bytes) {
|
|
256
|
+
std::vector<unsigned char> buf(bytes);
|
|
257
|
+
arc4random_buf(buf.data(), bytes);
|
|
258
|
+
static const char digits[] = "0123456789abcdef";
|
|
259
|
+
std::string out;
|
|
260
|
+
for (unsigned char b : buf) {
|
|
261
|
+
out += digits[b >> 4];
|
|
262
|
+
out += digits[b & 15];
|
|
263
|
+
}
|
|
264
|
+
return out;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
std::string platform_self_binary_path() {
|
|
268
|
+
Dl_info info;
|
|
269
|
+
if (dladdr((const void *)&platform_self_binary_path, &info) && info.dli_fname) {
|
|
270
|
+
char resolved[PATH_MAX];
|
|
271
|
+
if (realpath(info.dli_fname, resolved)) return resolved;
|
|
272
|
+
return info.dli_fname;
|
|
273
|
+
}
|
|
274
|
+
return "";
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
bool platform_strip_quarantine(const std::string &path) {
|
|
278
|
+
@autoreleasepool {
|
|
279
|
+
removexattr(path.c_str(), "com.apple.quarantine", XATTR_NOFOLLOW);
|
|
280
|
+
NSDirectoryEnumerator *e = [[NSFileManager defaultManager] enumeratorAtPath:[NSString stringWithUTF8String:path.c_str()]];
|
|
281
|
+
for (NSString *rel in e) {
|
|
282
|
+
std::string p = path + "/" + rel.UTF8String;
|
|
283
|
+
removexattr(p.c_str(), "com.apple.quarantine", XATTR_NOFOLLOW);
|
|
284
|
+
}
|
|
285
|
+
return true;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
int platform_show_dialog(const DialogModel &m) {
|
|
290
|
+
// NSAlert must run on the main thread (PF_Cmd_DO_DIALOG is called there).
|
|
291
|
+
if (![NSThread isMainThread]) return -1;
|
|
292
|
+
@autoreleasepool {
|
|
293
|
+
NSAlert *alert = [NSAlert new];
|
|
294
|
+
alert.messageText = [NSString stringWithUTF8String:(m.title + ": " + m.message).c_str()] ?: @"";
|
|
295
|
+
alert.informativeText = [NSString stringWithUTF8String:m.details.c_str()] ?: @"";
|
|
296
|
+
alert.alertStyle = NSAlertStyleInformational;
|
|
297
|
+
for (const auto &b : m.buttons) [alert addButtonWithTitle:[NSString stringWithUTF8String:b.label.c_str()] ?: @"OK"];
|
|
298
|
+
NSModalResponse r = [alert runModal];
|
|
299
|
+
int index = (int)(r - NSAlertFirstButtonReturn);
|
|
300
|
+
return index >= 0 && index < (int)m.buttons.size() ? index : -1;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
const char *platform_os() { return "macos"; }
|
|
305
|
+
|
|
306
|
+
const char *platform_arch() {
|
|
307
|
+
#if defined(__arm64__) || defined(__aarch64__)
|
|
308
|
+
return "arm64";
|
|
309
|
+
#else
|
|
310
|
+
return "x64";
|
|
311
|
+
#endif
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
} // namespace apn
|
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
// Windows platform layer: WinHTTP, ShellExecuteW, MachineGuid, adapters, BCrypt.
|
|
2
|
+
// UNVERIFIED: compiled (zig c++ -target x86_64-windows-gnu -fsyntax-only) but never
|
|
3
|
+
// run on Windows. See docs/native-runtime-design.md, "Windows".
|
|
4
|
+
#ifdef _WIN32
|
|
5
|
+
|
|
6
|
+
#ifndef NOMINMAX
|
|
7
|
+
#define NOMINMAX
|
|
8
|
+
#endif
|
|
9
|
+
#include <winsock2.h>
|
|
10
|
+
#include <windows.h>
|
|
11
|
+
#include <bcrypt.h>
|
|
12
|
+
#include <iphlpapi.h>
|
|
13
|
+
#include <shellapi.h>
|
|
14
|
+
#include <shlobj.h>
|
|
15
|
+
#include <winhttp.h>
|
|
16
|
+
#include <commctrl.h>
|
|
17
|
+
|
|
18
|
+
#include <cstdio>
|
|
19
|
+
#include <fstream>
|
|
20
|
+
#include <map>
|
|
21
|
+
|
|
22
|
+
#include "dialog.hpp"
|
|
23
|
+
#include "platform.hpp"
|
|
24
|
+
|
|
25
|
+
namespace apn {
|
|
26
|
+
|
|
27
|
+
namespace {
|
|
28
|
+
|
|
29
|
+
std::wstring widen(const std::string &s) {
|
|
30
|
+
if (s.empty()) return std::wstring();
|
|
31
|
+
int n = MultiByteToWideChar(CP_UTF8, 0, s.data(), (int)s.size(), nullptr, 0);
|
|
32
|
+
std::wstring w((size_t)n, L'\0');
|
|
33
|
+
MultiByteToWideChar(CP_UTF8, 0, s.data(), (int)s.size(), &w[0], n);
|
|
34
|
+
return w;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
std::string narrow(const std::wstring &w) {
|
|
38
|
+
if (w.empty()) return std::string();
|
|
39
|
+
int n = WideCharToMultiByte(CP_UTF8, 0, w.data(), (int)w.size(), nullptr, 0, nullptr, nullptr);
|
|
40
|
+
std::string s((size_t)n, '\0');
|
|
41
|
+
WideCharToMultiByte(CP_UTF8, 0, w.data(), (int)w.size(), &s[0], n, nullptr, nullptr);
|
|
42
|
+
return s;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
struct Handle {
|
|
46
|
+
HINTERNET h = nullptr;
|
|
47
|
+
explicit Handle(HINTERNET v) : h(v) {}
|
|
48
|
+
~Handle() { if (h) WinHttpCloseHandle(h); }
|
|
49
|
+
operator HINTERNET() const { return h; }
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
class WinHttp : public HttpClient {
|
|
53
|
+
public:
|
|
54
|
+
explicit WinHttp(std::string ua) : ua_(widen(ua)) {}
|
|
55
|
+
|
|
56
|
+
HttpResponse send(const HttpRequest &r, const std::atomic<bool> &cancel) override {
|
|
57
|
+
HttpResponse out;
|
|
58
|
+
auto fail = [&](const char *why) {
|
|
59
|
+
out.network_error = true;
|
|
60
|
+
out.error = why;
|
|
61
|
+
return out;
|
|
62
|
+
};
|
|
63
|
+
std::wstring url = widen(r.url);
|
|
64
|
+
URL_COMPONENTS uc{};
|
|
65
|
+
uc.dwStructSize = sizeof uc;
|
|
66
|
+
wchar_t host[256], path[4096];
|
|
67
|
+
uc.lpszHostName = host;
|
|
68
|
+
uc.dwHostNameLength = 256;
|
|
69
|
+
uc.lpszUrlPath = path;
|
|
70
|
+
uc.dwUrlPathLength = 4096;
|
|
71
|
+
wchar_t extra[4096];
|
|
72
|
+
uc.lpszExtraInfo = extra;
|
|
73
|
+
uc.dwExtraInfoLength = 4096;
|
|
74
|
+
if (!WinHttpCrackUrl(url.c_str(), 0, 0, &uc)) return fail("Unsupported URL.");
|
|
75
|
+
bool https = uc.nScheme == INTERNET_SCHEME_HTTPS;
|
|
76
|
+
Handle session(WinHttpOpen(ua_.c_str(), WINHTTP_ACCESS_TYPE_AUTOMATIC_PROXY, WINHTTP_NO_PROXY_NAME,
|
|
77
|
+
WINHTTP_NO_PROXY_BYPASS, 0));
|
|
78
|
+
if (!session) return fail("Network unavailable.");
|
|
79
|
+
int t = r.download_path.empty() ? r.timeout_ms : 60000;
|
|
80
|
+
WinHttpSetTimeouts(session, t, t, t, t);
|
|
81
|
+
DWORD protocols = WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_2 | 0x00002000 /* TLS 1.3 */;
|
|
82
|
+
WinHttpSetOption(session, WINHTTP_OPTION_SECURE_PROTOCOLS, &protocols, sizeof protocols);
|
|
83
|
+
Handle connect(WinHttpConnect(session, std::wstring(host, uc.dwHostNameLength).c_str(), uc.nPort, 0));
|
|
84
|
+
if (!connect) return fail("Could not connect.");
|
|
85
|
+
std::wstring object = std::wstring(path, uc.dwUrlPathLength) + std::wstring(extra, uc.dwExtraInfoLength);
|
|
86
|
+
Handle req(WinHttpOpenRequest(connect, widen(r.method).c_str(), object.c_str(), nullptr, WINHTTP_NO_REFERER,
|
|
87
|
+
WINHTTP_DEFAULT_ACCEPT_TYPES, https ? WINHTTP_FLAG_SECURE : 0));
|
|
88
|
+
if (!req) return fail("Could not connect.");
|
|
89
|
+
DWORD policy = r.follow_redirects ? WINHTTP_OPTION_REDIRECT_POLICY_DISALLOW_HTTPS_TO_HTTP
|
|
90
|
+
: WINHTTP_OPTION_REDIRECT_POLICY_NEVER;
|
|
91
|
+
WinHttpSetOption(req, WINHTTP_OPTION_REDIRECT_POLICY, &policy, sizeof policy);
|
|
92
|
+
DWORD no_cookies = WINHTTP_DISABLE_COOKIES;
|
|
93
|
+
WinHttpSetOption(req, WINHTTP_OPTION_DISABLE_FEATURE, &no_cookies, sizeof no_cookies);
|
|
94
|
+
std::wstring headers = L"Accept: application/json\r\n";
|
|
95
|
+
for (const auto &h : r.headers) headers += widen(h.first) + L": " + widen(h.second) + L"\r\n";
|
|
96
|
+
if (!WinHttpSendRequest(req, headers.c_str(), (DWORD)-1L, (LPVOID)(r.body.empty() ? nullptr : r.body.data()),
|
|
97
|
+
(DWORD)r.body.size(), (DWORD)r.body.size(), 0) ||
|
|
98
|
+
!WinHttpReceiveResponse(req, nullptr)) {
|
|
99
|
+
return fail(GetLastError() == ERROR_WINHTTP_TIMEOUT ? "Request timed out." : "Could not connect.");
|
|
100
|
+
}
|
|
101
|
+
DWORD status = 0, size = sizeof status;
|
|
102
|
+
WinHttpQueryHeaders(req, WINHTTP_QUERY_STATUS_CODE | WINHTTP_QUERY_FLAG_NUMBER, WINHTTP_HEADER_NAME_BY_INDEX,
|
|
103
|
+
&status, &size, WINHTTP_NO_HEADER_INDEX);
|
|
104
|
+
out.status = (int)status;
|
|
105
|
+
wchar_t ct[256];
|
|
106
|
+
DWORD ct_size = sizeof ct;
|
|
107
|
+
if (WinHttpQueryHeaders(req, WINHTTP_QUERY_CONTENT_TYPE, WINHTTP_HEADER_NAME_BY_INDEX, ct, &ct_size,
|
|
108
|
+
WINHTTP_NO_HEADER_INDEX)) {
|
|
109
|
+
out.content_type = narrow(std::wstring(ct, ct_size / sizeof(wchar_t)));
|
|
110
|
+
}
|
|
111
|
+
std::ofstream file;
|
|
112
|
+
bool to_file = !r.download_path.empty() && status == 200;
|
|
113
|
+
if (to_file) {
|
|
114
|
+
file.open(widen(r.download_path), std::ios::binary | std::ios::trunc);
|
|
115
|
+
if (!file) return fail("could not save the download");
|
|
116
|
+
}
|
|
117
|
+
uint64_t total = 0;
|
|
118
|
+
for (;;) {
|
|
119
|
+
if (cancel.load()) return fail("Cancelled.");
|
|
120
|
+
DWORD avail = 0;
|
|
121
|
+
if (!WinHttpQueryDataAvailable(req, &avail)) return fail("Connection lost.");
|
|
122
|
+
if (avail == 0) break;
|
|
123
|
+
std::string chunk(avail, '\0');
|
|
124
|
+
DWORD read = 0;
|
|
125
|
+
if (!WinHttpReadData(req, &chunk[0], avail, &read)) return fail("Connection lost.");
|
|
126
|
+
total += read;
|
|
127
|
+
if (total > r.max_bytes) return fail("Response too large.");
|
|
128
|
+
if (to_file) file.write(chunk.data(), read);
|
|
129
|
+
else if (r.download_path.empty()) out.body.append(chunk.data(), read);
|
|
130
|
+
}
|
|
131
|
+
if (to_file) {
|
|
132
|
+
file.close();
|
|
133
|
+
if (file.fail()) return fail("could not save the download");
|
|
134
|
+
}
|
|
135
|
+
return out;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
private:
|
|
139
|
+
std::wstring ua_;
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
} // namespace
|
|
143
|
+
|
|
144
|
+
std::shared_ptr<HttpClient> make_platform_http(const std::string &user_agent) {
|
|
145
|
+
return std::make_shared<WinHttp>(user_agent);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
std::string platform_data_root() {
|
|
149
|
+
// Non-roaming: a roaming profile must never carry the computer id or a
|
|
150
|
+
// computer-bound license token to another machine.
|
|
151
|
+
PWSTR path = nullptr;
|
|
152
|
+
std::string out;
|
|
153
|
+
if (SUCCEEDED(SHGetKnownFolderPath(FOLDERID_LocalAppData, 0, nullptr, &path))) out = narrow(path);
|
|
154
|
+
if (path) CoTaskMemFree(path);
|
|
155
|
+
return out;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
std::optional<std::string> platform_hardware_fingerprint() {
|
|
159
|
+
wchar_t buf[128];
|
|
160
|
+
DWORD size = sizeof buf;
|
|
161
|
+
if (RegGetValueW(HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\Cryptography", L"MachineGuid",
|
|
162
|
+
RRF_RT_REG_SZ | RRF_SUBKEY_WOW6464KEY, nullptr, buf, &size) != ERROR_SUCCESS) {
|
|
163
|
+
return std::nullopt;
|
|
164
|
+
}
|
|
165
|
+
std::string guid = narrow(buf);
|
|
166
|
+
if (guid.empty() || guid.find_first_not_of("0123456789abcdefABCDEF-") != std::string::npos) return std::nullopt;
|
|
167
|
+
for (auto &c : guid) c = (char)toupper((unsigned char)c);
|
|
168
|
+
return "win:" + guid;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
std::optional<std::string> platform_mac_fingerprint() {
|
|
172
|
+
// Node (libuv) lists adapters that are up and have unicast addresses, named
|
|
173
|
+
// by FriendlyName, internal = loopback.
|
|
174
|
+
ULONG size = 16 * 1024;
|
|
175
|
+
std::vector<unsigned char> buf;
|
|
176
|
+
ULONG rc;
|
|
177
|
+
do {
|
|
178
|
+
buf.resize(size);
|
|
179
|
+
rc = GetAdaptersAddresses(AF_UNSPEC, GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_MULTICAST | GAA_FLAG_SKIP_DNS_SERVER,
|
|
180
|
+
nullptr, (PIP_ADAPTER_ADDRESSES)buf.data(), &size);
|
|
181
|
+
} while (rc == ERROR_BUFFER_OVERFLOW);
|
|
182
|
+
if (rc != NO_ERROR) return std::nullopt;
|
|
183
|
+
std::vector<std::tuple<std::string, std::string, bool>> ifaces;
|
|
184
|
+
for (auto *a = (PIP_ADAPTER_ADDRESSES)buf.data(); a; a = a->Next) {
|
|
185
|
+
if (a->OperStatus != IfOperStatusUp || !a->FirstUnicastAddress) continue;
|
|
186
|
+
char mac[18] = "00:00:00:00:00:00";
|
|
187
|
+
if (a->PhysicalAddressLength == 6) {
|
|
188
|
+
const unsigned char *m = a->PhysicalAddress;
|
|
189
|
+
snprintf(mac, sizeof mac, "%02x:%02x:%02x:%02x:%02x:%02x", m[0], m[1], m[2], m[3], m[4], m[5]);
|
|
190
|
+
}
|
|
191
|
+
ifaces.emplace_back(narrow(a->FriendlyName ? a->FriendlyName : L""), mac,
|
|
192
|
+
a->IfType == IF_TYPE_SOFTWARE_LOOPBACK);
|
|
193
|
+
}
|
|
194
|
+
return mac_fingerprint_from(ifaces);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
bool platform_open_url(const std::string &url) {
|
|
198
|
+
if (url.rfind("https://", 0) != 0 && url.rfind("http://", 0) != 0) return false;
|
|
199
|
+
HRESULT co = CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE);
|
|
200
|
+
HINSTANCE r = ShellExecuteW(nullptr, L"open", widen(url).c_str(), nullptr, nullptr, SW_SHOWNORMAL);
|
|
201
|
+
if (SUCCEEDED(co)) CoUninitialize();
|
|
202
|
+
return (INT_PTR)r > 32;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
std::string platform_random_hex(size_t bytes) {
|
|
206
|
+
std::vector<unsigned char> buf(bytes);
|
|
207
|
+
BCryptGenRandom(nullptr, buf.data(), (ULONG)bytes, BCRYPT_USE_SYSTEM_PREFERRED_RNG);
|
|
208
|
+
static const char digits[] = "0123456789abcdef";
|
|
209
|
+
std::string out;
|
|
210
|
+
for (unsigned char b : buf) {
|
|
211
|
+
out += digits[b >> 4];
|
|
212
|
+
out += digits[b & 15];
|
|
213
|
+
}
|
|
214
|
+
return out;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
std::string platform_self_binary_path() {
|
|
218
|
+
HMODULE module = nullptr;
|
|
219
|
+
if (!GetModuleHandleExW(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT,
|
|
220
|
+
(LPCWSTR)(void *)&platform_self_binary_path, &module)) {
|
|
221
|
+
return "";
|
|
222
|
+
}
|
|
223
|
+
wchar_t path[32768];
|
|
224
|
+
DWORD n = GetModuleFileNameW(module, path, 32768);
|
|
225
|
+
return n ? narrow(std::wstring(path, n)) : "";
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
bool platform_strip_quarantine(const std::string &) { return true; } // Mark-of-the-Web does not block DLL loads
|
|
229
|
+
|
|
230
|
+
int platform_show_dialog(const DialogModel &m) {
|
|
231
|
+
// TaskDialogIndirect needs comctl32 v6 (After Effects loads it); looked up at
|
|
232
|
+
// run time so a missing export falls back to a plain message box.
|
|
233
|
+
std::wstring title = widen(m.title), main = widen(m.message), content = widen(m.details);
|
|
234
|
+
std::vector<std::wstring> labels;
|
|
235
|
+
for (const auto &b : m.buttons) labels.push_back(widen(b.label));
|
|
236
|
+
typedef HRESULT(WINAPI * TaskDialogIndirectFn)(const TASKDIALOGCONFIG *, int *, int *, BOOL *);
|
|
237
|
+
HMODULE comctl = LoadLibraryW(L"comctl32.dll");
|
|
238
|
+
auto fn = comctl ? (TaskDialogIndirectFn)(void *)GetProcAddress(comctl, "TaskDialogIndirect") : nullptr;
|
|
239
|
+
if (fn) {
|
|
240
|
+
std::vector<TASKDIALOG_BUTTON> buttons;
|
|
241
|
+
for (size_t i = 0; i < labels.size(); i++) buttons.push_back({(int)(100 + i), labels[i].c_str()});
|
|
242
|
+
TASKDIALOGCONFIG cfg{};
|
|
243
|
+
cfg.cbSize = sizeof cfg;
|
|
244
|
+
cfg.hwndParent = GetActiveWindow();
|
|
245
|
+
cfg.dwFlags = TDF_ALLOW_DIALOG_CANCELLATION | TDF_POSITION_RELATIVE_TO_WINDOW;
|
|
246
|
+
cfg.pszWindowTitle = title.c_str();
|
|
247
|
+
cfg.pszMainInstruction = main.c_str();
|
|
248
|
+
cfg.pszContent = content.c_str();
|
|
249
|
+
cfg.cButtons = (UINT)buttons.size();
|
|
250
|
+
cfg.pButtons = buttons.data();
|
|
251
|
+
cfg.nDefaultButton = 100;
|
|
252
|
+
int pressed = 0;
|
|
253
|
+
HRESULT hr = fn(&cfg, &pressed, nullptr, nullptr);
|
|
254
|
+
FreeLibrary(comctl);
|
|
255
|
+
if (SUCCEEDED(hr)) return pressed >= 100 && pressed < 100 + (int)buttons.size() ? pressed - 100 : -1;
|
|
256
|
+
return -1;
|
|
257
|
+
}
|
|
258
|
+
if (comctl) FreeLibrary(comctl);
|
|
259
|
+
// Fallback: OK / Cancel for the first button only.
|
|
260
|
+
std::wstring text = main + L"\n\n" + content + (labels.empty() ? L"" : L"\n\nOK: " + labels[0]);
|
|
261
|
+
int r = MessageBoxW(GetActiveWindow(), text.c_str(), title.c_str(), MB_OKCANCEL | MB_ICONINFORMATION);
|
|
262
|
+
return r == IDOK ? 0 : -1;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
const char *platform_os() { return "windows"; }
|
|
266
|
+
|
|
267
|
+
const char *platform_arch() {
|
|
268
|
+
#if defined(_M_ARM64) || defined(__aarch64__)
|
|
269
|
+
return "arm64";
|
|
270
|
+
#else
|
|
271
|
+
return "x64";
|
|
272
|
+
#endif
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
} // namespace apn
|
|
276
|
+
|
|
277
|
+
#endif // _WIN32
|