sparkbun 0.1.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.
Files changed (131) hide show
  1. package/bin/sparkbun.cjs +18 -0
  2. package/dist-linux-arm64/bsdiff +0 -0
  3. package/dist-linux-arm64/bspatch +0 -0
  4. package/dist-linux-arm64/libElectrobunCore.so +0 -0
  5. package/dist-linux-arm64/libNativeWrapper.so +0 -0
  6. package/dist-linux-arm64/libasar.so +0 -0
  7. package/dist-linux-x64/bsdiff +0 -0
  8. package/dist-linux-x64/bspatch +0 -0
  9. package/dist-linux-x64/libElectrobunCore.so +0 -0
  10. package/dist-linux-x64/libNativeWrapper.so +0 -0
  11. package/dist-linux-x64/libasar.so +0 -0
  12. package/dist-macos-arm64/bsdiff +0 -0
  13. package/dist-macos-arm64/bspatch +0 -0
  14. package/dist-macos-arm64/libElectrobunCore.dylib +0 -0
  15. package/dist-macos-arm64/libNativeWrapper.dylib +0 -0
  16. package/dist-macos-arm64/libasar.dylib +0 -0
  17. package/dist-macos-arm64/libwebgpu_dawn.dylib +0 -0
  18. package/dist-macos-arm64/preload-full.js +885 -0
  19. package/dist-macos-arm64/preload-sandboxed.js +111 -0
  20. package/dist-macos-arm64/process_helper +0 -0
  21. package/dist-win-x64/ElectrobunCore.dll +0 -0
  22. package/dist-win-x64/WebView2Loader.dll +0 -0
  23. package/dist-win-x64/bsdiff.exe +0 -0
  24. package/dist-win-x64/bspatch.exe +0 -0
  25. package/dist-win-x64/libNativeWrapper.dll +0 -0
  26. package/dist-win-x64/zig-asar/arm64/libasar.dll +0 -0
  27. package/dist-win-x64/zig-asar/x64/libasar.dll +0 -0
  28. package/package.json +47 -0
  29. package/scripts/build-and-upload-artifacts.js +207 -0
  30. package/scripts/gen-webgpu-ffi.mjs +162 -0
  31. package/scripts/install-windows-deps.ps1 +80 -0
  32. package/scripts/package-release.js +237 -0
  33. package/scripts/push-version.js +84 -0
  34. package/scripts/update-bun-version.ts +122 -0
  35. package/scripts/update-cef-version.ts +145 -0
  36. package/src/browser/builtinrpcSchema.ts +19 -0
  37. package/src/browser/global.d.ts +36 -0
  38. package/src/browser/index.ts +234 -0
  39. package/src/browser/webviewtag.ts +88 -0
  40. package/src/browser/wgputag.ts +48 -0
  41. package/src/bun/SparkBunConfig.ts +497 -0
  42. package/src/bun/__tests__/ffi-contract.test.ts +105 -0
  43. package/src/bun/core/ApplicationMenu.ts +70 -0
  44. package/src/bun/core/BrowserView.ts +416 -0
  45. package/src/bun/core/BrowserWindow.ts +396 -0
  46. package/src/bun/core/BuildConfig.ts +71 -0
  47. package/src/bun/core/ContextMenu.ts +75 -0
  48. package/src/bun/core/GpuWindow.ts +289 -0
  49. package/src/bun/core/Paths.ts +5 -0
  50. package/src/bun/core/Socket.ts +22 -0
  51. package/src/bun/core/Tray.ts +197 -0
  52. package/src/bun/core/Updater.ts +1131 -0
  53. package/src/bun/core/Utils.ts +487 -0
  54. package/src/bun/core/WGPUView.ts +167 -0
  55. package/src/bun/core/menuRoles.ts +181 -0
  56. package/src/bun/events/ApplicationEvents.ts +22 -0
  57. package/src/bun/events/event.ts +27 -0
  58. package/src/bun/events/eventEmitter.ts +45 -0
  59. package/src/bun/events/trayEvents.ts +11 -0
  60. package/src/bun/events/webviewEvents.ts +39 -0
  61. package/src/bun/events/windowEvents.ts +23 -0
  62. package/src/bun/index.ts +120 -0
  63. package/src/bun/preload/.generated/compiled.ts +2 -0
  64. package/src/bun/preload/build.ts +65 -0
  65. package/src/bun/preload/dragRegions.ts +41 -0
  66. package/src/bun/preload/encryption.ts +86 -0
  67. package/src/bun/preload/events.ts +171 -0
  68. package/src/bun/preload/globals.d.ts +45 -0
  69. package/src/bun/preload/index-sandboxed.ts +28 -0
  70. package/src/bun/preload/index.ts +77 -0
  71. package/src/bun/preload/internalRpc.ts +80 -0
  72. package/src/bun/preload/overlaySync.ts +107 -0
  73. package/src/bun/preload/webviewTag.ts +451 -0
  74. package/src/bun/preload/wgpuTag.ts +246 -0
  75. package/src/bun/proc/linux.md +43 -0
  76. package/src/bun/proc/native.ts +3253 -0
  77. package/src/bun/webGPU.ts +346 -0
  78. package/src/bun/webgpuAdapter.ts +3011 -0
  79. package/src/cli/bun.lockb +0 -0
  80. package/src/cli/index.ts +4653 -0
  81. package/src/cli/package-lock.json +81 -0
  82. package/src/cli/package.json +11 -0
  83. package/src/cli/templates/embedded.ts +2 -0
  84. package/src/core/build.zig +16 -0
  85. package/src/core/main.zig +3378 -0
  86. package/src/extractor/build.zig +22 -0
  87. package/src/installer/installer-template.ts +216 -0
  88. package/src/launcher/main.ts +221 -0
  89. package/src/native/build/libNativeWrapper.so +0 -0
  90. package/src/native/linux/build/nativeWrapper.o +0 -0
  91. package/src/native/linux/cef_loader.cpp +110 -0
  92. package/src/native/linux/cef_loader.h +28 -0
  93. package/src/native/linux/cef_process_helper_linux.cpp +160 -0
  94. package/src/native/linux/nativeWrapper.cpp +11768 -0
  95. package/src/native/macos/cef_process_helper_mac.cc +160 -0
  96. package/src/native/macos/nativeWrapper.mm +9172 -0
  97. package/src/native/shared/accelerator_parser.h +72 -0
  98. package/src/native/shared/app_paths.h +110 -0
  99. package/src/native/shared/asar.h +35 -0
  100. package/src/native/shared/cache_migration.h +244 -0
  101. package/src/native/shared/callbacks.h +57 -0
  102. package/src/native/shared/cef_response_filter.h +189 -0
  103. package/src/native/shared/chromium_flags.h +181 -0
  104. package/src/native/shared/config.h +66 -0
  105. package/src/native/shared/download_event.h +197 -0
  106. package/src/native/shared/ffi_helpers.h +139 -0
  107. package/src/native/shared/glob_match.h +59 -0
  108. package/src/native/shared/json_menu_parser.h +223 -0
  109. package/src/native/shared/mime_types.h +101 -0
  110. package/src/native/shared/navigation_rules.h +98 -0
  111. package/src/native/shared/partition_context.h +137 -0
  112. package/src/native/shared/pending_resize_queue.h +45 -0
  113. package/src/native/shared/permissions.h +118 -0
  114. package/src/native/shared/permissions_cef.h +74 -0
  115. package/src/native/shared/preload_script.h +71 -0
  116. package/src/native/shared/shutdown_guard.h +134 -0
  117. package/src/native/shared/thread_safe_map.h +138 -0
  118. package/src/native/shared/webview_storage.h +91 -0
  119. package/src/native/win/cef_process_helper_win.cpp +143 -0
  120. package/src/native/win/dcomp_compositor.h +352 -0
  121. package/src/native/win/nativeWrapper.cpp +12434 -0
  122. package/src/npmbin/index.js +34 -0
  123. package/src/shared/bun-version.ts +3 -0
  124. package/src/shared/cef-version.ts +5 -0
  125. package/src/shared/naming.test.ts +327 -0
  126. package/src/shared/naming.ts +188 -0
  127. package/src/shared/platform.ts +48 -0
  128. package/src/shared/rpc.ts +541 -0
  129. package/src/shared/sparkbun-version.ts +2 -0
  130. package/src/types/three.d.ts +1 -0
  131. package/tsconfig.json +31 -0
@@ -0,0 +1,189 @@
1
+ // cef_response_filter.h - Cross-platform CEF response filter for preload script injection
2
+ // Injects preload scripts into HTML responses before any page scripts execute
3
+ // Used across Windows, macOS, and Linux CEF implementations
4
+ //
5
+ // This is a header-only implementation to avoid build complexity.
6
+ // Requires CEF headers to be included before this file.
7
+
8
+ #ifndef ELECTROBUN_CEF_RESPONSE_FILTER_H
9
+ #define ELECTROBUN_CEF_RESPONSE_FILTER_H
10
+
11
+ #include <string>
12
+ #include <algorithm>
13
+ #include <cstring>
14
+ #include "preload_script.h"
15
+
16
+ namespace electrobun {
17
+
18
+ // CEF Response Filter that injects preload scripts into HTML responses
19
+ // Injection happens right after <head> tag to ensure scripts run before page scripts
20
+ class ElectrobunResponseFilter : public CefResponseFilter {
21
+ private:
22
+ std::string buffer_;
23
+ bool injected_;
24
+ PreloadScript electrobun_script_;
25
+ PreloadScript custom_script_;
26
+
27
+ public:
28
+ // Constructor with PreloadScript structs (preferred)
29
+ ElectrobunResponseFilter(const PreloadScript& electrobunScript,
30
+ const PreloadScript& customScript)
31
+ : injected_(false),
32
+ electrobun_script_(electrobunScript),
33
+ custom_script_(customScript) {}
34
+
35
+ // Constructor with raw strings (for compatibility)
36
+ ElectrobunResponseFilter(const std::string& electrobunScript,
37
+ const std::string& customScript = "")
38
+ : injected_(false),
39
+ electrobun_script_(electrobunScript),
40
+ custom_script_(customScript) {}
41
+
42
+ // Single script constructor (for simpler use cases)
43
+ explicit ElectrobunResponseFilter(const std::string& script)
44
+ : injected_(false),
45
+ electrobun_script_(script) {}
46
+
47
+ bool InitFilter() override {
48
+ buffer_.clear();
49
+ injected_ = false;
50
+ return true;
51
+ }
52
+
53
+ FilterStatus Filter(void* data_in,
54
+ size_t data_in_size,
55
+ size_t& data_in_read,
56
+ void* data_out,
57
+ size_t data_out_size,
58
+ size_t& data_out_written) override {
59
+
60
+ // If no scripts to inject, pass through directly
61
+ if (electrobun_script_.empty() && custom_script_.empty()) {
62
+ size_t copy_size = std::min(data_in_size, data_out_size);
63
+ std::memcpy(data_out, data_in, copy_size);
64
+ data_in_read = copy_size;
65
+ data_out_written = copy_size;
66
+ return RESPONSE_FILTER_DONE;
67
+ }
68
+
69
+ // Append incoming data to buffer
70
+ if (data_in_size > 0) {
71
+ buffer_.append(static_cast<char*>(data_in), data_in_size);
72
+ data_in_read = data_in_size;
73
+ } else {
74
+ data_in_read = 0;
75
+ }
76
+
77
+ // If already injected, just output buffered data
78
+ if (injected_) {
79
+ return OutputBufferedData(data_out, data_out_size, data_out_written);
80
+ }
81
+
82
+ // Try to inject scripts
83
+ TryInjectScripts();
84
+
85
+ // Output buffered data
86
+ return OutputBufferedData(data_out, data_out_size, data_out_written);
87
+ }
88
+
89
+ private:
90
+ void TryInjectScripts() {
91
+ if (injected_) return;
92
+
93
+ std::string script_tag = BuildScriptTag();
94
+ if (script_tag.empty()) return;
95
+
96
+ // Strategy 1: Look for <head> tag (most common case)
97
+ size_t head_pos = FindTagCaseInsensitive("<head>");
98
+ if (head_pos != std::string::npos) {
99
+ // Find the end of <head> or <head ...>
100
+ size_t insert_pos = buffer_.find('>', head_pos);
101
+ if (insert_pos != std::string::npos) {
102
+ buffer_.insert(insert_pos + 1, script_tag);
103
+ injected_ = true;
104
+ return;
105
+ }
106
+ }
107
+
108
+ // Strategy 2: Look for <head with attributes (e.g., <head class="...">)
109
+ head_pos = FindTagCaseInsensitive("<head ");
110
+ if (head_pos != std::string::npos) {
111
+ size_t insert_pos = buffer_.find('>', head_pos);
112
+ if (insert_pos != std::string::npos) {
113
+ buffer_.insert(insert_pos + 1, script_tag);
114
+ injected_ = true;
115
+ return;
116
+ }
117
+ }
118
+
119
+ // If buffer is large enough, try fallback strategies
120
+ if (buffer_.size() > 1024) {
121
+ // Strategy 3: Look for <html> and create a <head> section
122
+ size_t html_pos = FindTagCaseInsensitive("<html");
123
+ if (html_pos != std::string::npos) {
124
+ size_t insert_pos = buffer_.find('>', html_pos);
125
+ if (insert_pos != std::string::npos) {
126
+ std::string head_with_script = "<head>" + script_tag + "</head>";
127
+ buffer_.insert(insert_pos + 1, head_with_script);
128
+ injected_ = true;
129
+ return;
130
+ }
131
+ }
132
+
133
+ // Strategy 4: Last resort - inject at the very beginning
134
+ buffer_.insert(0, script_tag);
135
+ injected_ = true;
136
+ }
137
+ }
138
+
139
+ std::string BuildScriptTag() const {
140
+ if (electrobun_script_.empty() && custom_script_.empty()) {
141
+ return "";
142
+ }
143
+
144
+ std::string result = "<script>\n";
145
+
146
+ if (!electrobun_script_.empty()) {
147
+ result += electrobun_script_.code;
148
+ result += "\n";
149
+ }
150
+
151
+ if (!custom_script_.empty()) {
152
+ result += custom_script_.code;
153
+ result += "\n";
154
+ }
155
+
156
+ result += "</script>\n";
157
+ return result;
158
+ }
159
+
160
+ // Case-insensitive tag search
161
+ size_t FindTagCaseInsensitive(const std::string& tag) const {
162
+ std::string lower_buffer = buffer_;
163
+ std::string lower_tag = tag;
164
+
165
+ std::transform(lower_buffer.begin(), lower_buffer.end(), lower_buffer.begin(),
166
+ [](unsigned char c) { return std::tolower(c); });
167
+ std::transform(lower_tag.begin(), lower_tag.end(), lower_tag.begin(),
168
+ [](unsigned char c) { return std::tolower(c); });
169
+
170
+ return lower_buffer.find(lower_tag);
171
+ }
172
+
173
+ FilterStatus OutputBufferedData(void* data_out, size_t data_out_size, size_t& data_out_written) {
174
+ size_t copy_size = std::min(buffer_.size(), data_out_size);
175
+ if (copy_size > 0) {
176
+ std::memcpy(data_out, buffer_.c_str(), copy_size);
177
+ buffer_.erase(0, copy_size);
178
+ }
179
+ data_out_written = copy_size;
180
+
181
+ return buffer_.empty() ? RESPONSE_FILTER_DONE : RESPONSE_FILTER_NEED_MORE_DATA;
182
+ }
183
+
184
+ IMPLEMENT_REFCOUNTING(ElectrobunResponseFilter);
185
+ };
186
+
187
+ } // namespace electrobun
188
+
189
+ #endif // ELECTROBUN_CEF_RESPONSE_FILTER_H
@@ -0,0 +1,181 @@
1
+ // chromium_flags.h - Cross-platform Chromium CLI flag passthrough
2
+ // Reads user-defined Chromium flags from build.json and applies them
3
+ // to CEF's command line during initialization.
4
+ // Used across Windows, macOS, and Linux.
5
+ //
6
+ // This is a header-only implementation to avoid build complexity.
7
+
8
+ #ifndef ELECTROBUN_CHROMIUM_FLAGS_H
9
+ #define ELECTROBUN_CHROMIUM_FLAGS_H
10
+
11
+ #include <string>
12
+ #include <vector>
13
+ #include <set>
14
+ #include <fstream>
15
+ #include <sstream>
16
+
17
+ // Forward-declare CEF types so this header can be included without
18
+ // pulling in the full CEF headers (the call sites already include them).
19
+ #include "include/cef_command_line.h"
20
+
21
+ namespace electrobun {
22
+
23
+ struct ChromiumFlag {
24
+ std::string name;
25
+ std::string value;
26
+ bool hasValue;
27
+ };
28
+
29
+ struct ChromiumFlagConfig {
30
+ std::vector<ChromiumFlag> flags; // flags to add (true / "value")
31
+ std::set<std::string> skip; // default flags to skip (any user-specified flag overrides its default)
32
+ };
33
+
34
+ // Read an entire file into a string. Returns empty string on failure.
35
+ inline std::string readFileToString(const std::string& path) {
36
+ std::ifstream file(path);
37
+ if (!file.is_open()) {
38
+ return "";
39
+ }
40
+ std::stringstream buf;
41
+ buf << file.rdbuf();
42
+ return buf.str();
43
+ }
44
+
45
+ // Parse the "chromiumFlags" object from build.json content.
46
+ // Handles three value types:
47
+ // "flag-name": true -> add switch (hasValue = false)
48
+ // "flag-name": "value" -> add switch with value (hasValue = true)
49
+ // "flag-name": false -> skip a default flag set by Electrobun
50
+ inline ChromiumFlagConfig parseChromiumFlags(const std::string& json) {
51
+ ChromiumFlagConfig config;
52
+
53
+ // Find the "chromiumFlags" key
54
+ std::string key = "\"chromiumFlags\"";
55
+ size_t keyPos = json.find(key);
56
+ if (keyPos == std::string::npos) {
57
+ return config;
58
+ }
59
+
60
+ // Find the opening brace of the object
61
+ size_t objStart = json.find('{', keyPos + key.length());
62
+ if (objStart == std::string::npos) {
63
+ return config;
64
+ }
65
+
66
+ // Find the matching closing brace (handles nested depth = 0 since
67
+ // chromiumFlags values are only primitives, not nested objects)
68
+ int depth = 1;
69
+ size_t objEnd = objStart + 1;
70
+ while (objEnd < json.size() && depth > 0) {
71
+ if (json[objEnd] == '{') depth++;
72
+ else if (json[objEnd] == '}') depth--;
73
+ objEnd++;
74
+ }
75
+
76
+ std::string objContent = json.substr(objStart + 1, objEnd - objStart - 2);
77
+
78
+ // Iterate over key-value pairs inside the object
79
+ size_t pos = 0;
80
+ while (pos < objContent.size()) {
81
+ // Find next quoted key
82
+ size_t nameStart = objContent.find('"', pos);
83
+ if (nameStart == std::string::npos) break;
84
+ nameStart++;
85
+ size_t nameEnd = objContent.find('"', nameStart);
86
+ if (nameEnd == std::string::npos) break;
87
+
88
+ std::string flagName = objContent.substr(nameStart, nameEnd - nameStart);
89
+
90
+ // Skip past the colon
91
+ size_t colon = objContent.find(':', nameEnd + 1);
92
+ if (colon == std::string::npos) break;
93
+
94
+ // Skip whitespace after colon
95
+ size_t valStart = colon + 1;
96
+ while (valStart < objContent.size() &&
97
+ (objContent[valStart] == ' ' || objContent[valStart] == '\t' ||
98
+ objContent[valStart] == '\n' || objContent[valStart] == '\r')) {
99
+ valStart++;
100
+ }
101
+
102
+ if (valStart >= objContent.size()) break;
103
+
104
+ // Any user-specified flag overrides the corresponding default
105
+ config.skip.insert(flagName);
106
+
107
+ if (objContent[valStart] == '"') {
108
+ // String value — add flag with value
109
+ valStart++;
110
+ size_t valEnd = objContent.find('"', valStart);
111
+ if (valEnd == std::string::npos) break;
112
+ ChromiumFlag flag;
113
+ flag.name = flagName;
114
+ flag.value = objContent.substr(valStart, valEnd - valStart);
115
+ flag.hasValue = true;
116
+ config.flags.push_back(flag);
117
+ pos = valEnd + 1;
118
+ } else {
119
+ // Boolean token — "false" means skip only, "true" means add
120
+ size_t tokenEnd = valStart;
121
+ while (tokenEnd < objContent.size() &&
122
+ objContent[tokenEnd] != ',' && objContent[tokenEnd] != '}' &&
123
+ objContent[tokenEnd] != '\n') {
124
+ tokenEnd++;
125
+ }
126
+ std::string token = objContent.substr(valStart, tokenEnd - valStart);
127
+ // Trim whitespace from token
128
+ while (!token.empty() && (token.back() == ' ' || token.back() == '\t' || token.back() == '\r')) {
129
+ token.pop_back();
130
+ }
131
+
132
+ if (token != "false") {
133
+ // true or any other value — add as switch-only
134
+ ChromiumFlag flag;
135
+ flag.name = flagName;
136
+ flag.hasValue = false;
137
+ config.flags.push_back(flag);
138
+ }
139
+ pos = tokenEnd;
140
+ }
141
+ }
142
+
143
+ return config;
144
+ }
145
+
146
+ // A default flag: either a switch-only or a switch with a value.
147
+ struct DefaultFlag {
148
+ std::string name;
149
+ std::string value; // empty = switch-only
150
+ };
151
+
152
+ // Apply a list of default flags, skipping any that the user overrode.
153
+ inline void applyDefaultFlags(const std::vector<DefaultFlag>& defaults,
154
+ const std::set<std::string>& skip,
155
+ CefRefPtr<CefCommandLine> command_line) {
156
+ for (const auto& def : defaults) {
157
+ if (skip.count(def.name) > 0) continue;
158
+ if (def.value.empty()) {
159
+ command_line->AppendSwitch(def.name);
160
+ } else {
161
+ command_line->AppendSwitchWithValue(def.name, def.value);
162
+ }
163
+ }
164
+ }
165
+
166
+ // Apply user-defined flags to a CefCommandLine. Call this from
167
+ // OnBeforeCommandLineProcessing after default flags.
168
+ inline void applyChromiumFlags(const ChromiumFlagConfig& config,
169
+ CefRefPtr<CefCommandLine> command_line) {
170
+ for (const auto& flag : config.flags) {
171
+ if (flag.hasValue) {
172
+ command_line->AppendSwitchWithValue(flag.name, flag.value);
173
+ } else {
174
+ command_line->AppendSwitch(flag.name);
175
+ }
176
+ }
177
+ }
178
+
179
+ } // namespace electrobun
180
+
181
+ #endif // ELECTROBUN_CHROMIUM_FLAGS_H
@@ -0,0 +1,66 @@
1
+ // config.h - Cross-platform global configuration
2
+ // Used for CEF cache path isolation and app identification
3
+ // across Windows, macOS, and Linux
4
+ //
5
+ // This is a header-only implementation to avoid build complexity.
6
+
7
+ #ifndef ELECTROBUN_CONFIG_H
8
+ #define ELECTROBUN_CONFIG_H
9
+
10
+ #include <string>
11
+ #include <mutex>
12
+
13
+ namespace electrobun {
14
+
15
+ // Thread-safe configuration singleton
16
+ class Config {
17
+ public:
18
+ static Config& getInstance() {
19
+ static Config instance;
20
+ return instance;
21
+ }
22
+
23
+ void setChannel(const std::string& channel) {
24
+ std::lock_guard<std::mutex> lock(mutex_);
25
+ channel_ = channel;
26
+ }
27
+
28
+ std::string getChannel() const {
29
+ std::lock_guard<std::mutex> lock(mutex_);
30
+ return channel_;
31
+ }
32
+
33
+ void setIdentifier(const std::string& identifier) {
34
+ std::lock_guard<std::mutex> lock(mutex_);
35
+ identifier_ = identifier;
36
+ }
37
+
38
+ std::string getIdentifier() const {
39
+ std::lock_guard<std::mutex> lock(mutex_);
40
+ return identifier_;
41
+ }
42
+
43
+ void setName(const std::string& name) {
44
+ std::lock_guard<std::mutex> lock(mutex_);
45
+ name_ = name;
46
+ }
47
+
48
+ std::string getName() const {
49
+ std::lock_guard<std::mutex> lock(mutex_);
50
+ return name_;
51
+ }
52
+
53
+ private:
54
+ Config() = default;
55
+ Config(const Config&) = delete;
56
+ Config& operator=(const Config&) = delete;
57
+
58
+ mutable std::mutex mutex_;
59
+ std::string channel_;
60
+ std::string identifier_;
61
+ std::string name_;
62
+ };
63
+
64
+ } // namespace electrobun
65
+
66
+ #endif // ELECTROBUN_CONFIG_H
@@ -0,0 +1,197 @@
1
+ // download_event.h - Cross-platform download event structures
2
+ // Common data structures for download events
3
+ // Used across Windows, macOS, and Linux
4
+ //
5
+ // This is a header-only implementation to avoid build complexity.
6
+
7
+ #ifndef ELECTROBUN_DOWNLOAD_EVENT_H
8
+ #define ELECTROBUN_DOWNLOAD_EVENT_H
9
+
10
+ #include <string>
11
+ #include <cstdint>
12
+ #include <sstream>
13
+
14
+ namespace electrobun {
15
+
16
+ // Download event types
17
+ enum class DownloadEventType {
18
+ STARTED,
19
+ PROGRESS,
20
+ COMPLETED,
21
+ CANCELLED,
22
+ FAILED
23
+ };
24
+
25
+ // Convert event type to string
26
+ inline const char* downloadEventTypeToString(DownloadEventType type) {
27
+ switch (type) {
28
+ case DownloadEventType::STARTED: return "download-started";
29
+ case DownloadEventType::PROGRESS: return "download-progress";
30
+ case DownloadEventType::COMPLETED: return "download-completed";
31
+ case DownloadEventType::CANCELLED: return "download-cancelled";
32
+ case DownloadEventType::FAILED: return "download-failed";
33
+ default: return "download-unknown";
34
+ }
35
+ }
36
+
37
+ // Download event data structure
38
+ struct DownloadEvent {
39
+ uint32_t downloadId = 0;
40
+ std::string url;
41
+ std::string filename;
42
+ std::string mimeType;
43
+ std::string destinationPath;
44
+ int64_t totalBytes = -1; // -1 if unknown
45
+ int64_t receivedBytes = 0;
46
+ int percentComplete = 0;
47
+ bool canResume = false;
48
+ std::string errorMessage;
49
+
50
+ // Serialize to JSON string for FFI callbacks
51
+ std::string toJson() const {
52
+ std::ostringstream ss;
53
+ ss << "{";
54
+ ss << "\"downloadId\":" << downloadId;
55
+
56
+ if (!url.empty()) {
57
+ ss << ",\"url\":\"" << escapeJson(url) << "\"";
58
+ }
59
+ if (!filename.empty()) {
60
+ ss << ",\"filename\":\"" << escapeJson(filename) << "\"";
61
+ }
62
+ if (!mimeType.empty()) {
63
+ ss << ",\"mimeType\":\"" << escapeJson(mimeType) << "\"";
64
+ }
65
+ if (!destinationPath.empty()) {
66
+ ss << ",\"destinationPath\":\"" << escapeJson(destinationPath) << "\"";
67
+ }
68
+
69
+ ss << ",\"totalBytes\":" << totalBytes;
70
+ ss << ",\"receivedBytes\":" << receivedBytes;
71
+ ss << ",\"percentComplete\":" << percentComplete;
72
+ ss << ",\"canResume\":" << (canResume ? "true" : "false");
73
+
74
+ if (!errorMessage.empty()) {
75
+ ss << ",\"errorMessage\":\"" << escapeJson(errorMessage) << "\"";
76
+ }
77
+
78
+ ss << "}";
79
+ return ss.str();
80
+ }
81
+
82
+ // Serialize to simple key=value format (legacy format)
83
+ std::string toKeyValue() const {
84
+ std::ostringstream ss;
85
+ ss << "downloadId=" << downloadId;
86
+
87
+ if (!url.empty()) {
88
+ ss << "&url=" << url;
89
+ }
90
+ if (!filename.empty()) {
91
+ ss << "&filename=" << filename;
92
+ }
93
+ if (!mimeType.empty()) {
94
+ ss << "&mimeType=" << mimeType;
95
+ }
96
+ if (!destinationPath.empty()) {
97
+ ss << "&destinationPath=" << destinationPath;
98
+ }
99
+
100
+ ss << "&totalBytes=" << totalBytes;
101
+ ss << "&receivedBytes=" << receivedBytes;
102
+ ss << "&percentComplete=" << percentComplete;
103
+
104
+ return ss.str();
105
+ }
106
+
107
+ private:
108
+ // Simple JSON string escaping
109
+ static std::string escapeJson(const std::string& str) {
110
+ std::string result;
111
+ result.reserve(str.length() * 2);
112
+
113
+ for (char c : str) {
114
+ switch (c) {
115
+ case '"': result += "\\\""; break;
116
+ case '\\': result += "\\\\"; break;
117
+ case '\b': result += "\\b"; break;
118
+ case '\f': result += "\\f"; break;
119
+ case '\n': result += "\\n"; break;
120
+ case '\r': result += "\\r"; break;
121
+ case '\t': result += "\\t"; break;
122
+ default: result += c; break;
123
+ }
124
+ }
125
+
126
+ return result;
127
+ }
128
+ };
129
+
130
+ // Builder pattern for download events
131
+ class DownloadEventBuilder {
132
+ public:
133
+ DownloadEventBuilder& setDownloadId(uint32_t id) {
134
+ event_.downloadId = id;
135
+ return *this;
136
+ }
137
+
138
+ DownloadEventBuilder& setUrl(const std::string& url) {
139
+ event_.url = url;
140
+ return *this;
141
+ }
142
+
143
+ DownloadEventBuilder& setFilename(const std::string& filename) {
144
+ event_.filename = filename;
145
+ return *this;
146
+ }
147
+
148
+ DownloadEventBuilder& setMimeType(const std::string& mimeType) {
149
+ event_.mimeType = mimeType;
150
+ return *this;
151
+ }
152
+
153
+ DownloadEventBuilder& setDestinationPath(const std::string& path) {
154
+ event_.destinationPath = path;
155
+ return *this;
156
+ }
157
+
158
+ DownloadEventBuilder& setTotalBytes(int64_t bytes) {
159
+ event_.totalBytes = bytes;
160
+ return *this;
161
+ }
162
+
163
+ DownloadEventBuilder& setReceivedBytes(int64_t bytes) {
164
+ event_.receivedBytes = bytes;
165
+ return *this;
166
+ }
167
+
168
+ DownloadEventBuilder& setPercentComplete(int percent) {
169
+ event_.percentComplete = percent;
170
+ return *this;
171
+ }
172
+
173
+ DownloadEventBuilder& setCanResume(bool canResume) {
174
+ event_.canResume = canResume;
175
+ return *this;
176
+ }
177
+
178
+ DownloadEventBuilder& setErrorMessage(const std::string& msg) {
179
+ event_.errorMessage = msg;
180
+ return *this;
181
+ }
182
+
183
+ DownloadEvent build() const {
184
+ return event_;
185
+ }
186
+
187
+ std::string toJson() const {
188
+ return event_.toJson();
189
+ }
190
+
191
+ private:
192
+ DownloadEvent event_;
193
+ };
194
+
195
+ } // namespace electrobun
196
+
197
+ #endif // ELECTROBUN_DOWNLOAD_EVENT_H