@zogien/native-sdk 0.11.0-fork.3 → 0.11.0-fork.4
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 +1 -1
- package/build/app.zig +3 -0
- package/build/cli.zig +22 -0
- package/package.json +10 -10
- package/packages/core/package-lock.json +2 -2
- package/packages/core/package.json +1 -1
- package/schemas/app.schema.json +9 -1
- package/skill-data/ts-core/SKILL.md +2 -0
- package/src/app_runner/app_assets.zig +85 -1
- package/src/app_runner/root.zig +63 -3
- package/src/app_runner/ts_core_main.zig +8 -0
- package/src/platform/macos/appkit_host.h +9 -0
- package/src/platform/macos/appkit_host.m +366 -0
- package/src/platform/macos/root.zig +29 -6
- package/src/platform/root.zig +1 -0
- package/src/platform/types.zig +9 -0
- package/src/platform/windows/root.zig +37 -0
- package/src/platform/windows/webview2_host.cpp +371 -26
- package/src/primitives/app_manifest/root.zig +1 -0
- package/src/primitives/app_manifest/tests.zig +35 -0
- package/src/primitives/app_manifest/types.zig +12 -0
- package/src/primitives/app_manifest/validation.zig +44 -6
- package/src/root.zig +1 -0
- package/src/runtime/channel_sources.zig +49 -1
- package/src/runtime/effects.zig +12 -1
- package/src/runtime/flow.zig +6 -0
- package/src/runtime/flow_reference.zig +6 -0
- package/src/runtime/root.zig +4 -0
- package/src/runtime/single_instance.zig +893 -0
- package/src/tooling/manifest.zig +111 -3
- package/src/tooling/package.zig +98 -8
- package/src/tooling/raw_manifest.zig +7 -0
- package/src/tooling/root.zig +2 -0
- package/src/tooling/templates.zig +44 -2
- package/src/tooling/update.zig +228 -10
- package/src/tooling/windows_installer.zig +245 -0
- package/src/tooling/zip_writer.zig +211 -0
- package/src/updater/engine.zig +936 -0
- package/src/updater/feed.zig +261 -12
- package/src/updater/macos.zig +187 -0
- package/src/updater/root.zig +41 -0
- package/src/updater/windows.zig +470 -0
- package/src/windows_install/config.zig +247 -0
- package/src/windows_install/main.zig +690 -0
- package/src/windows_install/payload.zig +146 -0
- package/src/windows_install/switch.zig +184 -0
- package/src/windows_install/tests.zig +11 -0
- package/src/windows_install/win32.zig +248 -0
- package/tools/native-sdk/main.zig +10 -9
|
@@ -657,17 +657,55 @@ pub fn validateSecurity(security: SecurityConfig) ValidationError!void {
|
|
|
657
657
|
pub fn validateUpdates(updates: UpdateConfig) ValidationError!void {
|
|
658
658
|
if ((updates.feed_url == null) != (updates.public_key == null)) return error.MissingRequiredField;
|
|
659
659
|
if (updates.check_on_start and updates.feed_url == null) return error.MissingRequiredField;
|
|
660
|
+
if (updates.mode == .app and updates.feed_url == null) return error.MissingRequiredField;
|
|
661
|
+
if (updates.public_keys.len > 0 and updates.public_key == null) return error.MissingRequiredField;
|
|
662
|
+
if (updates.public_keys.len > types.max_update_public_keys) return error.MissingRequiredField;
|
|
660
663
|
if (updates.feed_url) |url| {
|
|
661
664
|
if (url.len > 4096) return error.InvalidUrl;
|
|
662
665
|
if (!std.mem.startsWith(u8, url, "https://")) return error.InvalidUrl;
|
|
663
666
|
try validateUrl(url);
|
|
667
|
+
try validateFeedUrlPlaceholders(url);
|
|
668
|
+
}
|
|
669
|
+
if (updates.public_key) |key| try validateUpdatePublicKey(key);
|
|
670
|
+
for (updates.public_keys, 0..) |key, index| {
|
|
671
|
+
try validateUpdatePublicKey(key);
|
|
672
|
+
if (std.mem.eql(u8, key, updates.public_key.?)) return error.MissingRequiredField;
|
|
673
|
+
for (updates.public_keys[0..index]) |previous| {
|
|
674
|
+
if (std.mem.eql(u8, key, previous)) return error.MissingRequiredField;
|
|
675
|
+
}
|
|
664
676
|
}
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
fn validateUpdatePublicKey(key: []const u8) ValidationError!void {
|
|
680
|
+
var decoded: [std.crypto.sign.Ed25519.PublicKey.encoded_length]u8 = undefined;
|
|
681
|
+
const decoded_len = std.base64.standard.Decoder.calcSizeForSlice(key) catch return error.MissingRequiredField;
|
|
682
|
+
if (decoded_len != decoded.len) return error.MissingRequiredField;
|
|
683
|
+
std.base64.standard.Decoder.decode(&decoded, key) catch return error.MissingRequiredField;
|
|
684
|
+
_ = std.crypto.sign.Ed25519.PublicKey.fromBytes(decoded) catch return error.MissingRequiredField;
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
/// A feed_url may name `{target}` and `{current_version}`, which each
|
|
688
|
+
/// installed app expands for itself, in its path or query only.
|
|
689
|
+
fn validateFeedUrlPlaceholders(url: []const u8) ValidationError!void {
|
|
690
|
+
const authority_start = "https://".len;
|
|
691
|
+
const authority_end = std.mem.findAnyPos(u8, url, authority_start, "/?#") orelse url.len;
|
|
692
|
+
if (std.mem.findAny(u8, url[0..authority_end], "{}") != null) return error.InvalidUrl;
|
|
693
|
+
var index = authority_end;
|
|
694
|
+
while (index < url.len) : (index += 1) {
|
|
695
|
+
switch (url[index]) {
|
|
696
|
+
'{' => {
|
|
697
|
+
const rest = url[index..];
|
|
698
|
+
const placeholder = if (std.mem.startsWith(u8, rest, "{target}"))
|
|
699
|
+
"{target}"
|
|
700
|
+
else if (std.mem.startsWith(u8, rest, "{current_version}"))
|
|
701
|
+
"{current_version}"
|
|
702
|
+
else
|
|
703
|
+
return error.InvalidUrl;
|
|
704
|
+
index += placeholder.len - 1;
|
|
705
|
+
},
|
|
706
|
+
'}' => return error.InvalidUrl,
|
|
707
|
+
else => {},
|
|
708
|
+
}
|
|
671
709
|
}
|
|
672
710
|
}
|
|
673
711
|
|
package/src/root.zig
CHANGED
|
@@ -157,6 +157,7 @@ pub const EffectChannelSourceState = runtime.EffectChannelSourceState;
|
|
|
157
157
|
pub const EffectChannelSourceResult = runtime.EffectChannelSourceResult;
|
|
158
158
|
pub const max_channel_source_payload = runtime.max_channel_source_payload;
|
|
159
159
|
pub const ChannelHandle = runtime.ChannelHandle;
|
|
160
|
+
pub const single_instance = runtime.single_instance;
|
|
160
161
|
pub const max_effect_channels = runtime.max_effect_channels;
|
|
161
162
|
pub const max_effect_channel_bytes = runtime.max_effect_channel_bytes;
|
|
162
163
|
pub const max_effect_channel_pending = runtime.max_effect_channel_pending;
|
|
@@ -1,19 +1,67 @@
|
|
|
1
1
|
//! Explicit native external-producer capabilities. Portable app state and
|
|
2
|
-
//! channel lifecycle stay in the app;
|
|
2
|
+
//! channel lifecycle stay in the app; `native-sdk.process.samples` owns a
|
|
3
|
+
//! detached OS sampler, `native-sdk.app.launches` hands the channel to
|
|
4
|
+
//! the single-instance listener (single_instance.zig), and
|
|
5
|
+
//! `native-sdk.updates` receives the updater's status records
|
|
6
|
+
//! (updater/engine.zig).
|
|
3
7
|
const std = @import("std");
|
|
4
8
|
const builtin = @import("builtin");
|
|
5
9
|
const effects = @import("effects.zig");
|
|
6
10
|
const clock = @import("clock.zig");
|
|
11
|
+
const single_instance = @import("single_instance.zig");
|
|
12
|
+
const update_engine = @import("../updater/engine.zig");
|
|
7
13
|
const sample_interval_ms = 500;
|
|
8
14
|
const max_line_bytes = 96;
|
|
9
15
|
|
|
10
16
|
pub fn start(name: []const u8, payload: []const u8, handle: effects.ChannelHandle) !void {
|
|
17
|
+
if (std.mem.eql(u8, name, single_instance.source_name)) return single_instance.attach(payload, handle);
|
|
18
|
+
if (std.mem.eql(u8, name, update_engine.channel_source)) return attachUpdates(payload, handle);
|
|
11
19
|
if (!std.mem.eql(u8, name, "native-sdk.process.samples")) return error.UnknownChannelSource;
|
|
12
20
|
if (payload.len != 0) return error.InvalidChannelSourceArguments;
|
|
13
21
|
if (comptime builtin.single_threaded) return error.UnsupportedChannelSource;
|
|
14
22
|
try startSamplerThread(handle);
|
|
15
23
|
}
|
|
16
24
|
|
|
25
|
+
/// The open `native-sdk.updates` channels, each the engine's sink. A slot
|
|
26
|
+
/// frees itself when its channel answers `.closed`.
|
|
27
|
+
const UpdateChannel = struct {
|
|
28
|
+
handle: effects.ChannelHandle = undefined,
|
|
29
|
+
used: std.atomic.Value(bool) = .init(false),
|
|
30
|
+
|
|
31
|
+
fn post(context: *anyopaque, bytes: []const u8) update_engine.Posted {
|
|
32
|
+
const self: *UpdateChannel = @ptrCast(@alignCast(context));
|
|
33
|
+
return switch (self.handle.post(bytes)) {
|
|
34
|
+
.accepted => .delivered,
|
|
35
|
+
.dropped_full, .dropped_oversized => .dropped,
|
|
36
|
+
.closed => {
|
|
37
|
+
self.used.store(false, .release);
|
|
38
|
+
return .closed;
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
var update_channels: [4]UpdateChannel = .{UpdateChannel{}} ** 4;
|
|
44
|
+
|
|
45
|
+
comptime {
|
|
46
|
+
// Every status record fits one post; notes travel by request.
|
|
47
|
+
std.debug.assert(update_engine.max_status_bytes <= effects.max_effect_channel_bytes);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
fn attachUpdates(payload: []const u8, handle: effects.ChannelHandle) !void {
|
|
51
|
+
if (payload.len != 0) return error.InvalidChannelSourceArguments;
|
|
52
|
+
for (&update_channels) |*channel| {
|
|
53
|
+
if (channel.used.cmpxchgStrong(false, true, .acq_rel, .acquire) == null) {
|
|
54
|
+
channel.handle = handle;
|
|
55
|
+
update_engine.attach(.{ .context = channel, .post = UpdateChannel.post }) catch {
|
|
56
|
+
channel.used.store(false, .release);
|
|
57
|
+
return error.UnsupportedChannelSource;
|
|
58
|
+
};
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return error.UnsupportedChannelSource;
|
|
63
|
+
}
|
|
64
|
+
|
|
17
65
|
fn startSamplerThread(handle: effects.ChannelHandle) std.Thread.SpawnError!void {
|
|
18
66
|
const thread = try std.Thread.spawn(.{}, samplerMain, .{handle});
|
|
19
67
|
thread.detach();
|
package/src/runtime/effects.zig
CHANGED
|
@@ -79,6 +79,7 @@ const relational_store = @import("relational_store.zig");
|
|
|
79
79
|
const credentials_store = @import("credentials_store.zig");
|
|
80
80
|
const file_access = @import("file_access.zig");
|
|
81
81
|
const pty_transport = @import("pty.zig");
|
|
82
|
+
const update_engine = @import("../updater/engine.zig");
|
|
82
83
|
|
|
83
84
|
/// Maximum in-flight effects (spawn slots / worker threads).
|
|
84
85
|
pub const max_effects: usize = 16;
|
|
@@ -9665,7 +9666,8 @@ pub fn Effects(comptime Msg: type) type {
|
|
|
9665
9666
|
return @import("desktop_files.zig").isName(name) or std.mem.startsWith(u8, name, "core.store.") or
|
|
9666
9667
|
std.mem.eql(u8, name, "native-sdk.launch-at-login.status") or
|
|
9667
9668
|
std.mem.eql(u8, name, "native-sdk.launch-at-login.set") or
|
|
9668
|
-
std.mem.eql(u8, name, "native-sdk.time.formatLocal")
|
|
9669
|
+
std.mem.eql(u8, name, "native-sdk.time.formatLocal") or
|
|
9670
|
+
std.mem.startsWith(u8, name, update_engine.request_prefix);
|
|
9669
9671
|
}
|
|
9670
9672
|
|
|
9671
9673
|
fn isNativeHostSendName(name: []const u8) bool {
|
|
@@ -9826,6 +9828,15 @@ pub fn Effects(comptime Msg: type) type {
|
|
|
9826
9828
|
self.performBoundSystemRequest(name, key, payload);
|
|
9827
9829
|
return;
|
|
9828
9830
|
}
|
|
9831
|
+
if (std.mem.startsWith(u8, name, update_engine.request_prefix)) {
|
|
9832
|
+
// The updater answers at once; its work runs off the loop
|
|
9833
|
+
// and reports through the `native-sdk.updates` channel.
|
|
9834
|
+
var output: [update_engine.max_response_bytes]u8 = undefined;
|
|
9835
|
+
const response = update_engine.handleRequest(name, payload, &output);
|
|
9836
|
+
self.feedHostResult(key, response.ok, response.bytes) catch {};
|
|
9837
|
+
if (response.quit) if (self.services) |services| services.quitApp() catch {};
|
|
9838
|
+
return;
|
|
9839
|
+
}
|
|
9829
9840
|
const services = self.services orelse {
|
|
9830
9841
|
self.feedHostResult(key, false, "unsupported") catch {};
|
|
9831
9842
|
return;
|
package/src/runtime/flow.zig
CHANGED
|
@@ -30,6 +30,7 @@ const app_manifest = @import("app_manifest");
|
|
|
30
30
|
const platform = @import("../platform/root.zig");
|
|
31
31
|
const host_flow = @import("host_flow_policy.zig");
|
|
32
32
|
const native_reference = @import("flow_reference.zig");
|
|
33
|
+
const update_engine = @import("../updater/engine.zig");
|
|
33
34
|
const HostEventPump = @import("host_event_pump.zig").HostEventPump;
|
|
34
35
|
const security = @import("../security/root.zig");
|
|
35
36
|
|
|
@@ -455,6 +456,11 @@ pub fn RuntimeFlow(comptime Runtime: type) type {
|
|
|
455
456
|
},
|
|
456
457
|
.mark_stop => {
|
|
457
458
|
self.app_stop_delivered = true;
|
|
459
|
+
// A release the app staged but did not restart into
|
|
460
|
+
// installs as the app quits, where the platform
|
|
461
|
+
// installs on exit (macOS); hosts may exit the process
|
|
462
|
+
// right after this event, so it cannot wait for run().
|
|
463
|
+
update_engine.finish();
|
|
458
464
|
},
|
|
459
465
|
.stop => {
|
|
460
466
|
try app.stop(self);
|
|
@@ -12,6 +12,7 @@ const runtime_automation_snapshot = @import("automation_snapshot.zig");
|
|
|
12
12
|
const runtime_automation_widget_dispatch = @import("automation_widget_dispatch.zig");
|
|
13
13
|
const automation_commands = @import("automation_command_policy.zig");
|
|
14
14
|
const launch_timing = @import("launch_timing.zig");
|
|
15
|
+
const update_engine = @import("../updater/engine.zig");
|
|
15
16
|
const runtime_clock = @import("clock.zig");
|
|
16
17
|
const runtime_state = @import("state.zig");
|
|
17
18
|
const shell_layout = @import("shell_layout.zig");
|
|
@@ -524,6 +525,11 @@ pub fn RuntimeFlow(comptime Runtime: type) type {
|
|
|
524
525
|
.app_shutdown => {
|
|
525
526
|
try dispatchEvent(self, app, .{ .lifecycle = .stop });
|
|
526
527
|
if (self.options.extensions) |registry| try registry.stopAll(extensionContext(self));
|
|
528
|
+
// A release the app staged but did not restart into
|
|
529
|
+
// installs as the app quits, where the platform
|
|
530
|
+
// installs on exit (macOS); hosts may exit the process
|
|
531
|
+
// right after this event, so it cannot wait for run().
|
|
532
|
+
update_engine.finish();
|
|
527
533
|
// Marked delivered BEFORE the call: if the hook itself
|
|
528
534
|
// errors, the run loop's exit path must not invoke it a
|
|
529
535
|
// second time (the hook contract is exactly-once).
|
package/src/runtime/root.zig
CHANGED
|
@@ -241,6 +241,10 @@ pub const max_session_journal_bytes = runtime_session_journal.max_session_journa
|
|
|
241
241
|
pub const max_session_record_bytes = runtime_session_journal.max_session_record_bytes;
|
|
242
242
|
pub const max_session_event_bytes = runtime_session_journal.max_session_event_bytes;
|
|
243
243
|
|
|
244
|
+
/// Manifest `single_instance` ownership; the launcher claims it before
|
|
245
|
+
/// startup and the `native-sdk.app.launches` source delivers launches.
|
|
246
|
+
pub const single_instance = @import("single_instance.zig");
|
|
247
|
+
|
|
244
248
|
const runtime_clock = @import("clock.zig");
|
|
245
249
|
pub const Clock = runtime_clock.Clock;
|
|
246
250
|
pub const TestClock = runtime_clock.TestClock;
|