@zappdev/cli 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 (209) hide show
  1. package/README.md +55 -0
  2. package/dist/zapp-cli.js +9471 -0
  3. package/native/src/app/app.zc +490 -0
  4. package/native/src/event/event.zc +24 -0
  5. package/native/src/event/events.zc +70 -0
  6. package/native/src/platform/darwin/backend.zc +923 -0
  7. package/native/src/platform/darwin/backend_bootstrap.zc +9 -0
  8. package/native/src/platform/darwin/bootstrap.zc +9 -0
  9. package/native/src/platform/darwin/engine_jsc.zc +86 -0
  10. package/native/src/platform/darwin/engine_qjs.zc +92 -0
  11. package/native/src/platform/darwin/platform.zc +156 -0
  12. package/native/src/platform/darwin/webview.zc +550 -0
  13. package/native/src/platform/darwin/webview_bootstrap.zc +9 -0
  14. package/native/src/platform/darwin/window.zc +1223 -0
  15. package/native/src/platform/darwin/worker/common.zc +223 -0
  16. package/native/src/platform/darwin/worker/core/base64_core.zc +29 -0
  17. package/native/src/platform/darwin/worker/core/crypto_core.zc +19 -0
  18. package/native/src/platform/darwin/worker/core/encoding_core.zc +32 -0
  19. package/native/src/platform/darwin/worker/core/fetch_core.zc +145 -0
  20. package/native/src/platform/darwin/worker/core/url_core.zc +69 -0
  21. package/native/src/platform/darwin/worker/core/websocket_core.zc +179 -0
  22. package/native/src/platform/darwin/worker/dispatch.zc +55 -0
  23. package/native/src/platform/darwin/worker/jsc/base64_jsc.zc +39 -0
  24. package/native/src/platform/darwin/worker/jsc/crypto_jsc.zc +49 -0
  25. package/native/src/platform/darwin/worker/jsc/encoding_jsc.zc +86 -0
  26. package/native/src/platform/darwin/worker/jsc/fetch_jsc.zc +149 -0
  27. package/native/src/platform/darwin/worker/jsc/url_jsc.zc +54 -0
  28. package/native/src/platform/darwin/worker/jsc/websocket_jsc.zc +127 -0
  29. package/native/src/platform/darwin/worker/jsc.zc +670 -0
  30. package/native/src/platform/darwin/worker/mod.zc +30 -0
  31. package/native/src/platform/darwin/worker/qjs/fetch_qjs.zc +233 -0
  32. package/native/src/platform/darwin/worker/qjs/qjs_macros.zc +23 -0
  33. package/native/src/platform/darwin/worker/qjs/websocket_qjs.zc +223 -0
  34. package/native/src/platform/darwin/worker/qjs.zc +1053 -0
  35. package/native/src/platform/darwin/worker/timers.zc +149 -0
  36. package/native/src/platform/darwin/worker/timers_qjs.zc +209 -0
  37. package/native/src/platform/platform.zc +64 -0
  38. package/native/src/platform/shared/log.zc +156 -0
  39. package/native/src/platform/shared/worker/qjs/base64_qjs.zc +38 -0
  40. package/native/src/platform/shared/worker/qjs/crypto_qjs.zc +44 -0
  41. package/native/src/platform/shared/worker/qjs/encoding_qjs.zc +95 -0
  42. package/native/src/platform/shared/worker/qjs/url_qjs.zc +65 -0
  43. package/native/src/platform/shared/worker_registry.zc +206 -0
  44. package/native/src/platform/window.zc +446 -0
  45. package/native/src/platform/windows/backend.zc +452 -0
  46. package/native/src/platform/windows/backend_bootstrap.zc +9 -0
  47. package/native/src/platform/windows/bootstrap.zc +9 -0
  48. package/native/src/platform/windows/engine_qjs.zc +60 -0
  49. package/native/src/platform/windows/platform.zc +387 -0
  50. package/native/src/platform/windows/webview.zc +1175 -0
  51. package/native/src/platform/windows/webview_bootstrap.zc +9 -0
  52. package/native/src/platform/windows/window.zc +1271 -0
  53. package/native/src/platform/windows/worker/common.zc +409 -0
  54. package/native/src/platform/windows/worker/core/base64_core.zc +52 -0
  55. package/native/src/platform/windows/worker/core/crypto_core.zc +34 -0
  56. package/native/src/platform/windows/worker/core/encoding_core.zc +60 -0
  57. package/native/src/platform/windows/worker/core/fetch_core.zc +274 -0
  58. package/native/src/platform/windows/worker/core/url_core.zc +216 -0
  59. package/native/src/platform/windows/worker/core/websocket_core.zc +343 -0
  60. package/native/src/platform/windows/worker/dispatch.zc +34 -0
  61. package/native/src/platform/windows/worker/mod.zc +46 -0
  62. package/native/src/platform/windows/worker/qjs/fetch_qjs.zc +255 -0
  63. package/native/src/platform/windows/worker/qjs/websocket_qjs.zc +263 -0
  64. package/native/src/platform/windows/worker/qjs.zc +1049 -0
  65. package/native/src/platform/windows/worker/timers_qjs.zc +288 -0
  66. package/native/src/platform/worker.zc +8 -0
  67. package/native/src/service/service.zc +228 -0
  68. package/native/vendor/quickjs-ng/.gitattributes +4 -0
  69. package/native/vendor/quickjs-ng/.github/dependabot.yml +7 -0
  70. package/native/vendor/quickjs-ng/.github/workflows/ci.yml +812 -0
  71. package/native/vendor/quickjs-ng/.github/workflows/docs.yml +49 -0
  72. package/native/vendor/quickjs-ng/.github/workflows/release.yml +162 -0
  73. package/native/vendor/quickjs-ng/.github/workflows/test-docs.yml +23 -0
  74. package/native/vendor/quickjs-ng/.github/workflows/tsan.yml +32 -0
  75. package/native/vendor/quickjs-ng/.github/workflows/valgrind.yml +33 -0
  76. package/native/vendor/quickjs-ng/.gitmodules +5 -0
  77. package/native/vendor/quickjs-ng/CMakeLists.txt +553 -0
  78. package/native/vendor/quickjs-ng/LICENSE +24 -0
  79. package/native/vendor/quickjs-ng/Makefile +149 -0
  80. package/native/vendor/quickjs-ng/amalgam.js +53 -0
  81. package/native/vendor/quickjs-ng/api-test.c +927 -0
  82. package/native/vendor/quickjs-ng/builtin-array-fromasync.h +119 -0
  83. package/native/vendor/quickjs-ng/builtin-array-fromasync.js +36 -0
  84. package/native/vendor/quickjs-ng/builtin-iterator-zip-keyed.h +332 -0
  85. package/native/vendor/quickjs-ng/builtin-iterator-zip-keyed.js +194 -0
  86. package/native/vendor/quickjs-ng/builtin-iterator-zip.h +337 -0
  87. package/native/vendor/quickjs-ng/builtin-iterator-zip.js +210 -0
  88. package/native/vendor/quickjs-ng/ctest.c +17 -0
  89. package/native/vendor/quickjs-ng/cutils.h +2013 -0
  90. package/native/vendor/quickjs-ng/cxxtest.cc +2 -0
  91. package/native/vendor/quickjs-ng/dtoa.c +1619 -0
  92. package/native/vendor/quickjs-ng/dtoa.h +87 -0
  93. package/native/vendor/quickjs-ng/examples/fib.c +67 -0
  94. package/native/vendor/quickjs-ng/examples/fib_module.js +10 -0
  95. package/native/vendor/quickjs-ng/examples/hello.js +1 -0
  96. package/native/vendor/quickjs-ng/examples/hello_module.js +6 -0
  97. package/native/vendor/quickjs-ng/examples/meson.build +17 -0
  98. package/native/vendor/quickjs-ng/examples/pi_bigint.js +118 -0
  99. package/native/vendor/quickjs-ng/examples/point.c +154 -0
  100. package/native/vendor/quickjs-ng/examples/test_fib.js +8 -0
  101. package/native/vendor/quickjs-ng/examples/test_point.js +43 -0
  102. package/native/vendor/quickjs-ng/fuzz.c +51 -0
  103. package/native/vendor/quickjs-ng/gen/function_source.c +81 -0
  104. package/native/vendor/quickjs-ng/gen/hello.c +53 -0
  105. package/native/vendor/quickjs-ng/gen/hello_module.c +106 -0
  106. package/native/vendor/quickjs-ng/gen/repl.c +3053 -0
  107. package/native/vendor/quickjs-ng/gen/standalone.c +324 -0
  108. package/native/vendor/quickjs-ng/gen/test_fib.c +81 -0
  109. package/native/vendor/quickjs-ng/libregexp-opcode.h +58 -0
  110. package/native/vendor/quickjs-ng/libregexp.c +2687 -0
  111. package/native/vendor/quickjs-ng/libregexp.h +98 -0
  112. package/native/vendor/quickjs-ng/libunicode-table.h +4707 -0
  113. package/native/vendor/quickjs-ng/libunicode.c +1746 -0
  114. package/native/vendor/quickjs-ng/libunicode.h +126 -0
  115. package/native/vendor/quickjs-ng/list.h +107 -0
  116. package/native/vendor/quickjs-ng/lre-test.c +73 -0
  117. package/native/vendor/quickjs-ng/meson.build +684 -0
  118. package/native/vendor/quickjs-ng/meson_options.txt +6 -0
  119. package/native/vendor/quickjs-ng/qjs-wasi-reactor.c +208 -0
  120. package/native/vendor/quickjs-ng/qjs.c +748 -0
  121. package/native/vendor/quickjs-ng/qjsc.c +673 -0
  122. package/native/vendor/quickjs-ng/quickjs-atom.h +267 -0
  123. package/native/vendor/quickjs-ng/quickjs-c-atomics.h +54 -0
  124. package/native/vendor/quickjs-ng/quickjs-libc.c +4986 -0
  125. package/native/vendor/quickjs-ng/quickjs-libc.h +79 -0
  126. package/native/vendor/quickjs-ng/quickjs-opcode.h +369 -0
  127. package/native/vendor/quickjs-ng/quickjs.c +60259 -0
  128. package/native/vendor/quickjs-ng/quickjs.h +1419 -0
  129. package/native/vendor/quickjs-ng/repl.js +1927 -0
  130. package/native/vendor/quickjs-ng/run-test262.c +2417 -0
  131. package/native/vendor/quickjs-ng/standalone.js +129 -0
  132. package/native/vendor/quickjs-ng/tests/assert.js +49 -0
  133. package/native/vendor/quickjs-ng/tests/bug1221.js +16 -0
  134. package/native/vendor/quickjs-ng/tests/bug1296.js +12 -0
  135. package/native/vendor/quickjs-ng/tests/bug1297.js +22 -0
  136. package/native/vendor/quickjs-ng/tests/bug1301.js +21 -0
  137. package/native/vendor/quickjs-ng/tests/bug1302.js +24 -0
  138. package/native/vendor/quickjs-ng/tests/bug1305.js +26 -0
  139. package/native/vendor/quickjs-ng/tests/bug1318.js +54 -0
  140. package/native/vendor/quickjs-ng/tests/bug1352.js +8 -0
  141. package/native/vendor/quickjs-ng/tests/bug1354.js +6 -0
  142. package/native/vendor/quickjs-ng/tests/bug1355.js +58 -0
  143. package/native/vendor/quickjs-ng/tests/bug1368.js +9 -0
  144. package/native/vendor/quickjs-ng/tests/bug39/1.js +6 -0
  145. package/native/vendor/quickjs-ng/tests/bug39/2.js +6 -0
  146. package/native/vendor/quickjs-ng/tests/bug39/3.js +7 -0
  147. package/native/vendor/quickjs-ng/tests/bug488-upstream.js +7 -0
  148. package/native/vendor/quickjs-ng/tests/bug633/0.js +7 -0
  149. package/native/vendor/quickjs-ng/tests/bug633/1.js +4 -0
  150. package/native/vendor/quickjs-ng/tests/bug633/2.js +4 -0
  151. package/native/vendor/quickjs-ng/tests/bug633/3.js +4 -0
  152. package/native/vendor/quickjs-ng/tests/bug645/0.js +4 -0
  153. package/native/vendor/quickjs-ng/tests/bug645/1.js +9 -0
  154. package/native/vendor/quickjs-ng/tests/bug645/2.js +7 -0
  155. package/native/vendor/quickjs-ng/tests/bug648.js +13 -0
  156. package/native/vendor/quickjs-ng/tests/bug652.js +4 -0
  157. package/native/vendor/quickjs-ng/tests/bug741.js +19 -0
  158. package/native/vendor/quickjs-ng/tests/bug775.js +7 -0
  159. package/native/vendor/quickjs-ng/tests/bug776.js +7 -0
  160. package/native/vendor/quickjs-ng/tests/bug832.js +2 -0
  161. package/native/vendor/quickjs-ng/tests/bug858.js +26 -0
  162. package/native/vendor/quickjs-ng/tests/bug904.js +6 -0
  163. package/native/vendor/quickjs-ng/tests/bug988.js +7 -0
  164. package/native/vendor/quickjs-ng/tests/bug999.js +3 -0
  165. package/native/vendor/quickjs-ng/tests/destructured-export.js +8 -0
  166. package/native/vendor/quickjs-ng/tests/detect_module/0.js +1 -0
  167. package/native/vendor/quickjs-ng/tests/detect_module/1.js +2 -0
  168. package/native/vendor/quickjs-ng/tests/detect_module/2.js +1 -0
  169. package/native/vendor/quickjs-ng/tests/detect_module/3.js +8 -0
  170. package/native/vendor/quickjs-ng/tests/detect_module/4.js +3 -0
  171. package/native/vendor/quickjs-ng/tests/empty.js +0 -0
  172. package/native/vendor/quickjs-ng/tests/fixture_cyclic_import.js +2 -0
  173. package/native/vendor/quickjs-ng/tests/fixture_string_exports.js +12 -0
  174. package/native/vendor/quickjs-ng/tests/function_source.js +14 -0
  175. package/native/vendor/quickjs-ng/tests/microbench.js +1267 -0
  176. package/native/vendor/quickjs-ng/tests/null_or_undefined.js +38 -0
  177. package/native/vendor/quickjs-ng/tests/str-pad-leak.js +5 -0
  178. package/native/vendor/quickjs-ng/tests/test_bigint.js +107 -0
  179. package/native/vendor/quickjs-ng/tests/test_bjson.js +366 -0
  180. package/native/vendor/quickjs-ng/tests/test_builtin.js +1314 -0
  181. package/native/vendor/quickjs-ng/tests/test_closure.js +220 -0
  182. package/native/vendor/quickjs-ng/tests/test_cyclic_import.js +12 -0
  183. package/native/vendor/quickjs-ng/tests/test_domexception.js +35 -0
  184. package/native/vendor/quickjs-ng/tests/test_language.js +755 -0
  185. package/native/vendor/quickjs-ng/tests/test_loop.js +367 -0
  186. package/native/vendor/quickjs-ng/tests/test_queue_microtask.js +39 -0
  187. package/native/vendor/quickjs-ng/tests/test_std.js +340 -0
  188. package/native/vendor/quickjs-ng/tests/test_string_exports.js +25 -0
  189. package/native/vendor/quickjs-ng/tests/test_worker.js +43 -0
  190. package/native/vendor/quickjs-ng/tests/test_worker_module.js +30 -0
  191. package/native/vendor/quickjs-ng/tests.conf +14 -0
  192. package/native/vendor/quickjs-ng/unicode_download.sh +19 -0
  193. package/native/vendor/quickjs-ng/unicode_gen.c +3108 -0
  194. package/native/vendor/quickjs-ng/unicode_gen_def.h +310 -0
  195. package/native/vendor/quickjs-ng/update-version.sh +32 -0
  196. package/native/vendor/webview2/include/WebView2.h +60636 -0
  197. package/native/vendor/webview2/include/WebView2EnvironmentOptions.h +406 -0
  198. package/package.json +33 -0
  199. package/src/backend.ts +139 -0
  200. package/src/build-config.ts +87 -0
  201. package/src/build.ts +276 -0
  202. package/src/common.ts +195 -0
  203. package/src/config.ts +89 -0
  204. package/src/dev.ts +164 -0
  205. package/src/generate.ts +200 -0
  206. package/src/icons.ts +116 -0
  207. package/src/init.ts +190 -0
  208. package/src/package.ts +150 -0
  209. package/src/zapp-cli.ts +263 -0
@@ -0,0 +1,1053 @@
1
+ import "../../platform.zc";
2
+ import "../bootstrap.zc";
3
+ import "./common.zc";
4
+ import "../../shared/log.zc";
5
+ import "../../shared/worker_registry.zc";
6
+ @cfg(ZAPP_WORKER_ENGINE_QJS)
7
+ import "../engine_qjs.zc";
8
+ @cfg(ZAPP_WORKER_ENGINE_QJS)
9
+ import "./timers_qjs.zc";
10
+ @cfg(ZAPP_WORKER_ENGINE_QJS)
11
+ import "./core/encoding_core.zc";
12
+ @cfg(ZAPP_WORKER_ENGINE_QJS)
13
+ import "./core/url_core.zc";
14
+ @cfg(ZAPP_WORKER_ENGINE_QJS)
15
+ import "./core/base64_core.zc";
16
+ @cfg(ZAPP_WORKER_ENGINE_QJS)
17
+ import "./core/websocket_core.zc";
18
+ @cfg(ZAPP_WORKER_ENGINE_QJS)
19
+ import "./core/crypto_core.zc";
20
+ @cfg(ZAPP_WORKER_ENGINE_QJS)
21
+ import "./core/fetch_core.zc";
22
+ @cfg(ZAPP_WORKER_ENGINE_QJS)
23
+ import "./qjs/qjs_macros.zc";
24
+ @cfg(ZAPP_WORKER_ENGINE_QJS)
25
+ import "../../shared/worker/qjs/encoding_qjs.zc";
26
+ @cfg(ZAPP_WORKER_ENGINE_QJS)
27
+ import "../../shared/worker/qjs/url_qjs.zc";
28
+ @cfg(ZAPP_WORKER_ENGINE_QJS)
29
+ import "../../shared/worker/qjs/base64_qjs.zc";
30
+ @cfg(ZAPP_WORKER_ENGINE_QJS)
31
+ import "../../shared/worker/qjs/crypto_qjs.zc";
32
+ @cfg(ZAPP_WORKER_ENGINE_QJS)
33
+ import "./qjs/websocket_qjs.zc";
34
+ @cfg(ZAPP_WORKER_ENGINE_QJS)
35
+ import "./qjs/fetch_qjs.zc";
36
+
37
+ include <string.h>
38
+ include <stdlib.h>
39
+ //> macos: framework: Security
40
+ raw {
41
+ #ifdef __APPLE__
42
+ #include <Security/Security.h>
43
+ #endif
44
+ }
45
+
46
+ // Rename QuickJS types to avoid conflicts with JavaScriptCore (included via WebKit)
47
+ raw {
48
+ #ifdef ZAPP_WORKER_ENGINE_QJS
49
+ #ifdef __APPLE__
50
+ extern void* engine_qjs_create_ctx(void* rt);
51
+ extern int engine_qjs_eval(void* ctx, const char* utf8, size_t len, const char* filename);
52
+ extern const char* engine_qjs_get_exception_message(void* ctx);
53
+ extern void engine_qjs_free_ctx(void* ctx);
54
+
55
+ #define JSContext QJSContext
56
+ #define JSRuntime QJSRuntime
57
+ #define JSValue QJSValue
58
+ #define JSClassID QJSClassID
59
+ #define JSClassDef QJSClassDef
60
+ #define JSClassEx QJSClassEx
61
+ #define JSValueConst QJSValueConst
62
+ #define JSPropertyEnum QJSPropertyEnum
63
+ #define JSPropertyDescriptor QJSPropertyDescriptor
64
+
65
+ #include "quickjs.h"
66
+ #include "quickjs-libc.h"
67
+
68
+ extern void zapp_dispatch_worker_to_webviews(const char* kind, const char* worker_id, const char* payload_json);
69
+ static void zapp_darwin_send_event_to_js(void* app_ptr, char* name, char* payload);
70
+ extern const char* zapp_darwin_worker_bootstrap_script(void);
71
+ extern void zapp_handle_message(void* app, char* msg);
72
+ extern void* app_get_active(void);
73
+
74
+ extern void zapp_qjs_encoding_install(QJSContext* ctx, QJSValue bridge);
75
+ extern void zapp_qjs_url_install(QJSContext* ctx, QJSValue bridge);
76
+ extern void zapp_qjs_base64_install(QJSContext* ctx, QJSValue bridge);
77
+ extern void zapp_darwin_qjs_websocket_install(QJSContext* ctx, QJSValue bridge, const char* worker_id);
78
+ extern void zapp_qjs_crypto_install(QJSContext* ctx, QJSValue bridge);
79
+ extern void zapp_darwin_qjs_fetch_install(QJSContext* ctx, QJSValue bridge, const char* worker_id);
80
+ extern void zapp_darwin_qjs_fetch_cancel(NSString* workerId);
81
+ extern void zapp_darwin_qjs_fetch_cancel_all(void);
82
+
83
+ // Log functions from log.zc
84
+ extern void zapp_log_worker(int level, int window_id, int worker_id, const char* message);
85
+ extern void zapp_log_backend(int level, const char* message);
86
+ #define ZAPP_LOG_ERROR 0
87
+ #define ZAPP_LOG_WARN 1
88
+ #define ZAPP_LOG_INFO 2
89
+ #define ZAPP_LOG_DEBUG 3
90
+ #define ZAPP_LOG_TRACE 4
91
+
92
+ static dispatch_queue_t qjs_queue = nil;
93
+ static QJSRuntime* qjs_rt = NULL;
94
+ static NSMutableDictionary<NSString*, NSValue*>* qjs_contexts = nil;
95
+ static NSMutableDictionary<NSString*, NSString*>* qjs_owners = nil;
96
+
97
+ static NSMutableDictionary<NSString*, NSString*>* qjs_worker_script_urls = nil;
98
+ static NSMutableDictionary<NSString*, NSNumber*>* qjs_worker_shared_flags = nil;
99
+ static NSMutableDictionary<NSString*, NSValue*>* qjs_shared_worker_contexts = nil;
100
+ static NSMutableDictionary<NSString*, NSNumber*>* qjs_shared_worker_refcounts = nil;
101
+ static void* qjs_shared_loading_sentinel = (void*)(uintptr_t)1;
102
+
103
+ static void qjs_drain_jobs(QJSContext* ctx, const char* label) {
104
+ if (!ctx) return;
105
+
106
+ QJSContext* pctx = NULL;
107
+ int err_job = 0;
108
+
109
+ for (;;) {
110
+ err_job = JS_ExecutePendingJob(JS_GetRuntime(ctx), &pctx);
111
+ if (err_job <= 0) {
112
+ if (err_job < 0 && pctx) {
113
+ QJSValue exception = JS_GetException(pctx);
114
+ const char *str = JS_ToCString(pctx, exception);
115
+ char buf[256];
116
+ snprintf(buf, sizeof(buf), "pending job error (%s): %s", label ? label : "unknown", str ? str : "unknown");
117
+ zapp_log_worker(ZAPP_LOG_ERROR, -1, -1, buf);
118
+ if (str) JS_FreeCString(pctx, str);
119
+ JS_FreeValue(pctx, exception);
120
+ }
121
+ break;
122
+ }
123
+ }
124
+ }
125
+
126
+ static NSUInteger qjs_active_context_count(void) {
127
+ if (!qjs_contexts) return 0;
128
+ NSMutableSet<NSValue*>* seen = [NSMutableSet set];
129
+ for (NSString* key in qjs_contexts) {
130
+ NSValue* boxedCtx = qjs_contexts[key];
131
+ if (boxedCtx != nil) {
132
+ [seen addObject:boxedCtx];
133
+ }
134
+ }
135
+ return [seen count];
136
+ }
137
+
138
+ static void qjs_call_bridge_method(QJSContext* ctx, NSString* methodName, int argc, QJSValue* argv) {
139
+ if (!ctx || !methodName) return;
140
+
141
+ QJSValue global = JS_GetGlobalObject(ctx);
142
+ QJSValue symbol = JS_GetPropertyStr(ctx, global, "Symbol");
143
+ QJSValue symbolFor = JS_GetPropertyStr(ctx, symbol, "for");
144
+ QJSValue arg = JS_NewString(ctx, "zapp.bridge");
145
+ QJSValue bridgeSym = JS_Call(ctx, symbolFor, symbol, 1, &arg);
146
+
147
+ JSAtom bridgeAtom = JS_ValueToAtom(ctx, bridgeSym);
148
+ QJSValue bridge = JS_GetProperty(ctx, global, bridgeAtom);
149
+
150
+ if (JS_IsObject(bridge)) {
151
+ QJSValue fn = JS_GetPropertyStr(ctx, bridge, [methodName UTF8String]);
152
+ if (JS_IsFunction(ctx, fn)) {
153
+ QJSValue ret = JS_Call(ctx, fn, bridge, argc, argv);
154
+ if (JS_IsException(ret)) {
155
+ QJSValue exception = JS_GetException(ctx);
156
+ const char* str = JS_ToCString(ctx, exception);
157
+ char buf[256];
158
+ snprintf(buf, sizeof(buf), "bridge.%s threw: %s", [methodName UTF8String], str ? str : "unknown");
159
+ zapp_log_worker(ZAPP_LOG_ERROR, -1, -1, buf);
160
+ if (str) JS_FreeCString(ctx, str);
161
+ JS_FreeValue(ctx, exception);
162
+ }
163
+ JS_FreeValue(ctx, ret);
164
+ qjs_drain_jobs(ctx, [methodName UTF8String]);
165
+ }
166
+ JS_FreeValue(ctx, fn);
167
+ }
168
+
169
+ JS_FreeAtom(ctx, bridgeAtom);
170
+ JS_FreeValue(ctx, bridge);
171
+ JS_FreeValue(ctx, bridgeSym);
172
+ JS_FreeValue(ctx, arg);
173
+ JS_FreeValue(ctx, symbolFor);
174
+ JS_FreeValue(ctx, symbol);
175
+ JS_FreeValue(ctx, global);
176
+ }
177
+
178
+ static QJSValue qjs_bridge_post(QJSContext *ctx, QJSValue this_val, int argc, QJSValue *argv, int magic, QJSValue *func_data) {
179
+ if (argc < 1) return JS_UNDEFINED;
180
+
181
+ const char *worker_id = JS_ToCString(ctx, func_data[0]);
182
+ if (!worker_id) return JS_UNDEFINED;
183
+
184
+ const char *target_id = worker_id;
185
+ if (argc >= 2 && !JS_IsUndefined(argv[1]) && !JS_IsNull(argv[1])) {
186
+ target_id = JS_ToCString(ctx, argv[1]);
187
+ }
188
+
189
+ QJSValue global = JS_GetGlobalObject(ctx);
190
+ QJSValue JSON = JS_GetPropertyStr(ctx, global, "JSON");
191
+ QJSValue stringify = JS_GetPropertyStr(ctx, JSON, "stringify");
192
+ QJSValue jsonStrVal = JS_Call(ctx, stringify, JSON, 1, &argv[0]);
193
+
194
+ const char *payload = JS_ToCString(ctx, jsonStrVal);
195
+ zapp_dispatch_worker_to_webviews("message", target_id, payload ? payload : "null");
196
+
197
+ if (payload) JS_FreeCString(ctx, payload);
198
+ JS_FreeValue(ctx, jsonStrVal);
199
+ JS_FreeValue(ctx, stringify);
200
+ JS_FreeValue(ctx, JSON);
201
+ JS_FreeValue(ctx, global);
202
+
203
+ if (target_id != worker_id) {
204
+ JS_FreeCString(ctx, target_id);
205
+ }
206
+ JS_FreeCString(ctx, worker_id);
207
+
208
+ return JS_UNDEFINED;
209
+ }
210
+
211
+ static QJSValue qjs_bridge_emitToHost(QJSContext *ctx, QJSValue this_val, int argc, QJSValue *argv, int magic, QJSValue *func_data) {
212
+ if (argc < 2) return JS_UNDEFINED;
213
+
214
+ const char *name = JS_ToCString(ctx, argv[0]);
215
+ if (!name) return JS_UNDEFINED;
216
+
217
+ QJSValue global = JS_GetGlobalObject(ctx);
218
+ QJSValue JSON = JS_GetPropertyStr(ctx, global, "JSON");
219
+ QJSValue stringify = JS_GetPropertyStr(ctx, JSON, "stringify");
220
+ QJSValue jsonStrVal = JS_Call(ctx, stringify, JSON, 1, &argv[1]);
221
+
222
+ const char *payload = JS_ToCString(ctx, jsonStrVal);
223
+ const char *p = payload ? payload : "{}";
224
+
225
+ void* app_ptr = app_get_active();
226
+ zapp_darwin_send_event_to_js(app_ptr, (char*)name, (char*)p);
227
+
228
+ if (payload) JS_FreeCString(ctx, payload);
229
+ JS_FreeValue(ctx, jsonStrVal);
230
+ JS_FreeValue(ctx, stringify);
231
+ JS_FreeValue(ctx, JSON);
232
+ JS_FreeValue(ctx, global);
233
+ JS_FreeCString(ctx, name);
234
+
235
+ return JS_UNDEFINED;
236
+ }
237
+
238
+ static QJSValue qjs_bridge_invokeToHost(QJSContext *ctx, QJSValue this_val, int argc, QJSValue *argv, int magic, QJSValue *func_data) {
239
+ if (argc < 2) return JS_UNDEFINED;
240
+
241
+ const char *name = JS_ToCString(ctx, argv[0]);
242
+ if (!name) return JS_UNDEFINED;
243
+
244
+ QJSValue global = JS_GetGlobalObject(ctx);
245
+ QJSValue JSON = JS_GetPropertyStr(ctx, global, "JSON");
246
+ QJSValue stringify = JS_GetPropertyStr(ctx, JSON, "stringify");
247
+ QJSValue jsonStrVal = JS_Call(ctx, stringify, JSON, 1, &argv[1]);
248
+
249
+ const char *payload = JS_ToCString(ctx, jsonStrVal);
250
+ const char *p = payload ? payload : "{}";
251
+
252
+ void* app_ptr = app_get_active();
253
+ if (app_ptr) {
254
+ NSString* wire = [NSString stringWithFormat:@"invoke\n%s\n%s", name, p];
255
+ zapp_handle_message(app_ptr, (char*)[wire UTF8String]);
256
+ }
257
+
258
+ if (payload) JS_FreeCString(ctx, payload);
259
+ JS_FreeValue(ctx, jsonStrVal);
260
+ JS_FreeValue(ctx, stringify);
261
+ JS_FreeValue(ctx, JSON);
262
+ JS_FreeValue(ctx, global);
263
+ JS_FreeCString(ctx, name);
264
+
265
+ return JS_UNDEFINED;
266
+ }
267
+
268
+ static QJSValue qjs_bridge_reportError(QJSContext *ctx, QJSValue this_val, int argc, QJSValue *argv, int magic, QJSValue *func_data) {
269
+ if (argc < 1) return JS_UNDEFINED;
270
+
271
+ const char *worker_id = JS_ToCString(ctx, func_data[0]);
272
+ if (!worker_id) return JS_UNDEFINED;
273
+
274
+ const char *msg = JS_ToCString(ctx, argv[0]);
275
+ NSString* message = msg ? [NSString stringWithUTF8String:msg] : @"Worker error";
276
+ if (msg) JS_FreeCString(ctx, msg);
277
+
278
+ zapp_worker_dispatch_error_message([NSString stringWithUTF8String:worker_id], message);
279
+ JS_FreeCString(ctx, worker_id);
280
+
281
+ return JS_UNDEFINED;
282
+ }
283
+
284
+ static QJSValue qjs_bridge_closeWorker(QJSContext *ctx, QJSValue this_val, int argc, QJSValue *argv, int magic, QJSValue *func_data) {
285
+ const char *worker_id = JS_ToCString(ctx, func_data[0]);
286
+ if (!worker_id) return JS_UNDEFINED;
287
+
288
+ zapp_dispatch_worker_to_webviews("close", worker_id, "{}");
289
+ JS_FreeCString(ctx, worker_id);
290
+ return JS_UNDEFINED;
291
+ }
292
+
293
+ static QJSValue qjs_bridge_sync_forward(QJSContext *ctx, const char* action, QJSValue* argv, int argc, QJSValue *func_data) {
294
+ if (argc < 1 || action == NULL) return JS_UNDEFINED;
295
+ const char *worker_id = JS_ToCString(ctx, func_data[0]);
296
+ if (!worker_id) return JS_UNDEFINED;
297
+
298
+ QJSValue global = JS_GetGlobalObject(ctx);
299
+ QJSValue JSON = JS_GetPropertyStr(ctx, global, "JSON");
300
+ QJSValue stringify = JS_GetPropertyStr(ctx, JSON, "stringify");
301
+ QJSValue jsonStrVal = JS_Call(ctx, stringify, JSON, 1, &argv[0]);
302
+ const char *payload = JS_ToCString(ctx, jsonStrVal);
303
+
304
+ NSString* payloadJson = payload ? [NSString stringWithUTF8String:payload] : @"{}";
305
+ NSData* payloadData = [payloadJson dataUsingEncoding:NSUTF8StringEncoding];
306
+ id rawObj = payloadData ? [NSJSONSerialization JSONObjectWithData:payloadData options:0 error:nil] : nil;
307
+ NSMutableDictionary* body = [NSMutableDictionary dictionary];
308
+ if ([rawObj isKindOfClass:[NSDictionary class]]) {
309
+ [body addEntriesFromDictionary:(NSDictionary*)rawObj];
310
+ }
311
+ [body setObject:[NSString stringWithUTF8String:worker_id] forKey:@"targetWorkerId"];
312
+ NSData* withTargetData = [NSJSONSerialization dataWithJSONObject:body options:0 error:nil];
313
+ NSString* withTarget = [[NSString alloc] initWithData:withTargetData encoding:NSUTF8StringEncoding];
314
+ if (withTarget == nil) withTarget = @"{}";
315
+
316
+ void* app_ptr = app_get_active();
317
+ if (app_ptr) {
318
+ NSString* wire = [NSString stringWithFormat:@"sync\n%s\n%@", action, withTarget];
319
+ zapp_handle_message(app_ptr, (char*)[wire UTF8String]);
320
+ }
321
+
322
+ if (payload) JS_FreeCString(ctx, payload);
323
+ JS_FreeValue(ctx, jsonStrVal);
324
+ JS_FreeValue(ctx, stringify);
325
+ JS_FreeValue(ctx, JSON);
326
+ JS_FreeValue(ctx, global);
327
+ JS_FreeCString(ctx, worker_id);
328
+ return JS_UNDEFINED;
329
+ }
330
+
331
+ static QJSValue qjs_bridge_syncWait(QJSContext *ctx, QJSValue this_val, int argc, QJSValue *argv, int magic, QJSValue *func_data) {
332
+ return qjs_bridge_sync_forward(ctx, "wait", argv, argc, func_data);
333
+ }
334
+
335
+ static QJSValue qjs_bridge_syncNotify(QJSContext *ctx, QJSValue this_val, int argc, QJSValue *argv, int magic, QJSValue *func_data) {
336
+ return qjs_bridge_sync_forward(ctx, "notify", argv, argc, func_data);
337
+ }
338
+
339
+ static QJSValue qjs_bridge_syncCancel(QJSContext *ctx, QJSValue this_val, int argc, QJSValue *argv, int magic, QJSValue *func_data) {
340
+ return qjs_bridge_sync_forward(ctx, "cancel", argv, argc, func_data);
341
+ }
342
+
343
+ // Helper to extract owner_id from worker_id (format: owner:ctx:zw-N)
344
+ static void qjs_extract_owner_id(const char* worker_id, char* owner_buf, size_t buf_len) {
345
+ if (!worker_id || !owner_buf || buf_len == 0) {
346
+ if (owner_buf && buf_len > 0) owner_buf[0] = '\0';
347
+ return;
348
+ }
349
+
350
+ // Find first colon
351
+ const char* colon = strchr(worker_id, ':');
352
+ if (!colon) {
353
+ strncpy(owner_buf, worker_id, buf_len - 1);
354
+ owner_buf[buf_len - 1] = '\0';
355
+ return;
356
+ }
357
+
358
+ size_t owner_len = colon - worker_id;
359
+ if (owner_len >= buf_len) owner_len = buf_len - 1;
360
+ strncpy(owner_buf, worker_id, owner_len);
361
+ owner_buf[owner_len] = '\0';
362
+ }
363
+
364
+ // Console log with worker context for unified logging
365
+ // func_data[0] = worker_id string
366
+ // magic = log level (0=error, 1=warn, 2=info, 3=debug, 4=trace)
367
+ static QJSValue qjs_console_log_with_data(QJSContext *ctx, QJSValue this_val, int argc, QJSValue *argv, int magic, QJSValue *func_data) {
368
+ // Build message from arguments
369
+ char msg_buf[1024];
370
+ msg_buf[0] = '\0';
371
+ size_t msg_len = 0;
372
+
373
+ QJSValue global = JS_GetGlobalObject(ctx);
374
+ QJSValue JSON_val = JS_GetPropertyStr(ctx, global, "JSON");
375
+ QJSValue stringify = JS_IsObject(JSON_val) ? JS_GetPropertyStr(ctx, JSON_val, "stringify") : JS_UNDEFINED;
376
+
377
+ for (int i = 0; i < argc && msg_len < sizeof(msg_buf) - 1; i++) {
378
+ const char *str = NULL;
379
+
380
+ if (JS_IsFunction(ctx, stringify)) {
381
+ QJSValue args[1] = { argv[i] };
382
+ QJSValue jsonVal = JS_Call(ctx, stringify, JSON_val, 1, args);
383
+ if (!JS_IsException(jsonVal)) {
384
+ str = JS_ToCString(ctx, jsonVal);
385
+ }
386
+ JS_FreeValue(ctx, jsonVal);
387
+ }
388
+
389
+ if (!str) str = JS_ToCString(ctx, argv[i]);
390
+ if (str) {
391
+ size_t str_len = strlen(str);
392
+ if (i > 0 && msg_len < sizeof(msg_buf) - 1) {
393
+ msg_buf[msg_len++] = ' ';
394
+ }
395
+ if (msg_len + str_len < sizeof(msg_buf)) {
396
+ strcpy(msg_buf + msg_len, str);
397
+ msg_len += str_len;
398
+ }
399
+ JS_FreeCString(ctx, str);
400
+ }
401
+ }
402
+ msg_buf[msg_len] = '\0';
403
+
404
+ JS_FreeValue(ctx, global);
405
+ if (JS_IsObject(JSON_val)) JS_FreeValue(ctx, stringify);
406
+ JS_FreeValue(ctx, JSON_val);
407
+
408
+ // Get worker_id from func_data
409
+ const char* worker_id = NULL;
410
+ char worker_id_buf[256];
411
+ if (func_data && JS_IsString(func_data[0])) {
412
+ worker_id = JS_ToCString(ctx, func_data[0]);
413
+ }
414
+
415
+ // Extract owner_id and get window/worker IDs
416
+ int window_id = -1;
417
+ int worker_seq_id = -1;
418
+
419
+ if (worker_id) {
420
+ zapp_worker_lookup(worker_id, &window_id, &worker_seq_id);
421
+ JS_FreeCString(ctx, worker_id);
422
+ }
423
+
424
+ // Log level from magic
425
+ int level = (magic >= ZAPP_LOG_ERROR && magic <= ZAPP_LOG_TRACE) ? magic : ZAPP_LOG_INFO;
426
+
427
+ zapp_log_worker(level, window_id, worker_seq_id, msg_buf);
428
+
429
+ return JS_UNDEFINED;
430
+ }
431
+
432
+ NSValue* zapp_darwin_qjs_get_context(NSString* workerId) {
433
+ return qjs_contexts ? qjs_contexts[workerId] : nil;
434
+ }
435
+
436
+ dispatch_queue_t zapp_darwin_qjs_get_queue(void) {
437
+ return qjs_queue;
438
+ }
439
+
440
+ void zapp_darwin_qjs_drain_jobs(void* ctx_opaque, const char* label) {
441
+ qjs_drain_jobs((QJSContext*)ctx_opaque, label);
442
+ }
443
+
444
+ void zapp_darwin_qjs_dispatch_async(dispatch_block_t block) {
445
+ dispatch_async(qjs_queue, block);
446
+ }
447
+
448
+ void zapp_darwin_qjs_ensure_init(void) {
449
+ static dispatch_once_t onceToken;
450
+ dispatch_once(&onceToken, ^{
451
+ qjs_queue = dispatch_queue_create("dev.zapp.worker-qjs", DISPATCH_QUEUE_SERIAL);
452
+ qjs_contexts = [NSMutableDictionary dictionary];
453
+ qjs_owners = [NSMutableDictionary dictionary];
454
+ qjs_worker_script_urls = [NSMutableDictionary dictionary];
455
+ qjs_worker_shared_flags = [NSMutableDictionary dictionary];
456
+ qjs_shared_worker_contexts = [NSMutableDictionary dictionary];
457
+ qjs_shared_worker_refcounts = [NSMutableDictionary dictionary];
458
+ extern int app_get_bootstrap_qjs_stack_size(void);
459
+ qjs_rt = JS_NewRuntime();
460
+ JS_SetMaxStackSize(qjs_rt, (size_t)app_get_bootstrap_qjs_stack_size());
461
+ });
462
+ }
463
+
464
+ void* zapp_darwin_qjs_get_runtime(void) {
465
+ return (void*)qjs_rt;
466
+ }
467
+
468
+ void zapp_darwin_qjs_register_context(NSString* ctxId, void* ctx_opaque) {
469
+ if (ctxId == nil || ctx_opaque == NULL) return;
470
+ if (qjs_contexts == nil) return;
471
+ [qjs_contexts setObject:[NSValue valueWithPointer:ctx_opaque] forKey:ctxId];
472
+ }
473
+
474
+ void zapp_darwin_qjs_unregister_context(NSString* ctxId) {
475
+ if (ctxId == nil || qjs_contexts == nil) return;
476
+ [qjs_contexts removeObjectForKey:ctxId];
477
+ }
478
+
479
+ void zapp_darwin_worker_dispatch_sync_result(const char* worker_id, const char* payload_json) {
480
+ if (worker_id == NULL || payload_json == NULL) return;
481
+ NSString* workerId = [NSString stringWithUTF8String:worker_id];
482
+ if (![workerId isKindOfClass:[NSString class]] || [workerId length] == 0) return;
483
+ dispatch_async(qjs_queue, ^{
484
+ NSValue* val = qjs_contexts[workerId];
485
+ if (!val) return;
486
+ QJSContext* ctx = (QJSContext*)[val pointerValue];
487
+ QJSValue arg = JS_NewString(ctx, payload_json);
488
+ qjs_call_bridge_method(ctx, @"dispatchSyncResult", 1, &arg);
489
+ JS_FreeValue(ctx, arg);
490
+ });
491
+ }
492
+
493
+ void zapp_darwin_reset_owner_workers(const char* owner_id) {
494
+ if (owner_id == NULL || !qjs_owners) return;
495
+
496
+ NSString* ownerId = [NSString stringWithUTF8String:owner_id];
497
+ NSArray* keys = [qjs_owners allKeys];
498
+
499
+ for (NSString* key in keys) {
500
+ if (![qjs_owners[key] isEqualToString:ownerId]) continue;
501
+
502
+ NSValue* val = qjs_contexts[key];
503
+ NSString* scriptUrl = qjs_worker_script_urls[key];
504
+ NSNumber* sharedFlag = qjs_worker_shared_flags[key];
505
+ BOOL isShared = (sharedFlag != nil && [sharedFlag boolValue]);
506
+
507
+ [qjs_contexts removeObjectForKey:key];
508
+ [qjs_owners removeObjectForKey:key];
509
+ [qjs_worker_script_urls removeObjectForKey:key];
510
+ [qjs_worker_shared_flags removeObjectForKey:key];
511
+
512
+ if (!val) continue;
513
+
514
+ QJSContext* ctx = (QJSContext*)[val pointerValue];
515
+
516
+ dispatch_async(qjs_queue, ^{
517
+ zapp_darwin_qjs_timers_cancel(key);
518
+ zapp_darwin_qjs_fetch_cancel(key);
519
+
520
+ if (isShared && scriptUrl != nil) {
521
+ QJSValue args[1];
522
+ args[0] = JS_NewString(ctx, [key UTF8String]);
523
+ qjs_call_bridge_method(ctx, @"dispatchDisconnect", 1, args);
524
+ JS_FreeValue(ctx, args[0]);
525
+
526
+ NSNumber* rc = qjs_shared_worker_refcounts[scriptUrl];
527
+ NSInteger next = (rc != nil ? [rc integerValue] : 1) - 1;
528
+
529
+ if (next > 0) {
530
+ qjs_shared_worker_refcounts[scriptUrl] = @(next);
531
+ } else {
532
+ [qjs_shared_worker_refcounts removeObjectForKey:scriptUrl];
533
+ [qjs_shared_worker_contexts removeObjectForKey:scriptUrl];
534
+ engine_qjs_free_ctx(ctx);
535
+ }
536
+ } else {
537
+ engine_qjs_free_ctx(ctx);
538
+ }
539
+ });
540
+ }
541
+ }
542
+
543
+ void zapp_darwin_reset_all_workers(void) {
544
+ if (!qjs_contexts) return;
545
+
546
+ NSMutableSet<NSValue*>* seen = [NSMutableSet set];
547
+ NSDictionary* contextsCopy = [qjs_contexts copy];
548
+
549
+ [qjs_contexts removeAllObjects];
550
+ [qjs_owners removeAllObjects];
551
+ [qjs_worker_script_urls removeAllObjects];
552
+ [qjs_worker_shared_flags removeAllObjects];
553
+ [qjs_shared_worker_contexts removeAllObjects];
554
+ [qjs_shared_worker_refcounts removeAllObjects];
555
+
556
+ for (NSString* key in contextsCopy) {
557
+ NSValue* val = contextsCopy[key];
558
+ if (!val || [seen containsObject:val]) continue;
559
+ [seen addObject:val];
560
+
561
+ QJSContext* ctx = (QJSContext*)[val pointerValue];
562
+ dispatch_async(qjs_queue, ^{
563
+ zapp_darwin_qjs_timers_cancel(key);
564
+ zapp_darwin_qjs_fetch_cancel(key);
565
+ engine_qjs_free_ctx(ctx);
566
+ });
567
+ }
568
+ }
569
+
570
+ static void zapp_darwin_send_event_to_js(void* app_ptr, char* name, char* payload) {
571
+ (void)app_ptr;
572
+ if (name == NULL) return;
573
+ if (payload == NULL) payload = "{}";
574
+
575
+ NSString* n = [NSString stringWithUTF8String:name] ?: @"";
576
+ NSString* p = [NSString stringWithUTF8String:payload] ?: @"{}";
577
+
578
+ NSDictionary* envelope = @{
579
+ @"name": n,
580
+ @"payload": p
581
+ };
582
+
583
+ NSData* envelopeData = [NSJSONSerialization dataWithJSONObject:envelope options:0 error:nil];
584
+ if (!envelopeData) return;
585
+
586
+ NSString* envelopeJson = [[NSString alloc] initWithData:envelopeData encoding:NSUTF8StringEncoding];
587
+ if (!envelopeJson) return;
588
+
589
+ void (^deliverToWebviews)(void) = ^{
590
+ NSString* js = [NSString stringWithFormat:
591
+ @"(function(){"
592
+ "var e=%@;"
593
+ "var b=globalThis[Symbol.for('zapp.bridge')];"
594
+ "if(b&&typeof b.deliverEvent==='function'){"
595
+ "b.deliverEvent(e.name,e.payload);"
596
+ "}"
597
+ "})();",
598
+ envelopeJson
599
+ ];
600
+
601
+ for (NSWindow* window in [NSApp windows]) {
602
+ NSView* content = [window contentView];
603
+ if ([content isKindOfClass:[WKWebView class]]) {
604
+ WKWebView* webview = (WKWebView*)content;
605
+ [webview evaluateJavaScript:js completionHandler:^(id result, NSError *error) {
606
+ if (error) {
607
+ char buf[256];
608
+ snprintf(buf, sizeof(buf), "deliverEvent error: %s", [[error localizedDescription] UTF8String]);
609
+ zapp_log_worker(ZAPP_LOG_ERROR, -1, -1, buf);
610
+ }
611
+ }];
612
+ }
613
+ }
614
+ };
615
+
616
+ if ([NSThread isMainThread]) {
617
+ deliverToWebviews();
618
+ } else {
619
+ dispatch_async(dispatch_get_main_queue(), deliverToWebviews);
620
+ }
621
+
622
+ if (!qjs_contexts || !qjs_queue) return;
623
+
624
+ dispatch_async(qjs_queue, ^{
625
+ NSMutableSet<NSValue*>* seen = [NSMutableSet set];
626
+ NSDictionary<NSString*, NSValue*>* contextsCopy = [qjs_contexts copy];
627
+
628
+ for (NSString* key in contextsCopy) {
629
+ NSValue* boxedCtx = contextsCopy[key];
630
+ if (!boxedCtx || [seen containsObject:boxedCtx]) continue;
631
+ [seen addObject:boxedCtx];
632
+
633
+ QJSContext* ctx = (QJSContext*)[boxedCtx pointerValue];
634
+
635
+ QJSValue args[2];
636
+ args[0] = JS_NewString(ctx, [n UTF8String]);
637
+ args[1] = JS_NewString(ctx, [p UTF8String]);
638
+ qjs_call_bridge_method(ctx, @"deliverEvent", 2, args);
639
+ JS_FreeValue(ctx, args[0]);
640
+ JS_FreeValue(ctx, args[1]);
641
+ }
642
+ });
643
+ }
644
+ }
645
+ #endif /* __APPLE__ */
646
+ #endif
647
+ }
648
+
649
+ struct DarwinWorkerQjs {}
650
+
651
+ @unused
652
+ impl WorkerBackend for DarwinWorkerQjs {
653
+ fn bind_runtime(self, app: App*) -> void {
654
+ raw {
655
+ #ifdef ZAPP_WORKER_ENGINE_QJS
656
+ #ifdef __APPLE__
657
+ app->event.send_event_to_js = (void*)zapp_darwin_send_event_to_js;
658
+ #endif /* __APPLE__ */
659
+ #endif
660
+ }
661
+ }
662
+
663
+ fn handle_bridge(self, app: App*, action: string, payload: string) -> void {
664
+ raw {
665
+ #ifdef ZAPP_WORKER_ENGINE_QJS
666
+ #ifdef __APPLE__
667
+ if (action == NULL || payload == NULL) return;
668
+
669
+ zapp_darwin_qjs_ensure_init();
670
+
671
+ NSDictionary* body = zapp_worker_parse_bridge_payload((const char*)payload);
672
+ if (body == nil) return;
673
+
674
+ NSString* actionStr = [NSString stringWithUTF8String:(const char*)action];
675
+ NSString* workerId = zapp_worker_body_worker_id(body);
676
+ if (workerId == nil) {
677
+ NSString* legacyWorkerId = body[@"workerId"];
678
+ workerId = [legacyWorkerId isKindOfClass:[NSString class]] ? legacyWorkerId : nil;
679
+ }
680
+ if (workerId == nil) return;
681
+
682
+ NSString* ownerId = zapp_worker_body_owner_id(body);
683
+ BOOL shared = zapp_worker_body_shared(body);
684
+
685
+ if ([actionStr isEqualToString:@"reset_owner"]) {
686
+ if (ownerId != nil && [ownerId length] > 0) {
687
+ zapp_darwin_reset_owner_workers([ownerId UTF8String]);
688
+ }
689
+ return;
690
+ }
691
+
692
+ if ([actionStr isEqualToString:@"create"]) {
693
+ NSString* scriptUrl = zapp_worker_body_script_url(body);
694
+ if (scriptUrl == nil) return;
695
+
696
+ int maxWorkers = app->config.maxWorkers;
697
+ if (maxWorkers > 0) {
698
+ BOOL willCreateNew = YES;
699
+ if (shared && qjs_shared_worker_contexts[scriptUrl] != nil) {
700
+ willCreateNew = NO;
701
+ }
702
+ if (willCreateNew && qjs_active_context_count() >= (NSUInteger)maxWorkers) {
703
+ zapp_worker_dispatch_max_workers_error(workerId, maxWorkers);
704
+ return;
705
+ }
706
+ }
707
+
708
+ if (shared) {
709
+ NSValue* sharedVal = qjs_shared_worker_contexts[scriptUrl];
710
+ if (sharedVal != nil && [sharedVal pointerValue] != qjs_shared_loading_sentinel) {
711
+ QJSContext* existingCtx = (QJSContext*)[sharedVal pointerValue];
712
+
713
+ NSNumber* rc = qjs_shared_worker_refcounts[scriptUrl];
714
+ NSInteger next = (rc != nil ? [rc integerValue] : 0) + 1;
715
+ qjs_shared_worker_refcounts[scriptUrl] = @(next);
716
+
717
+ qjs_contexts[workerId] = [NSValue valueWithPointer:existingCtx];
718
+ qjs_worker_script_urls[workerId] = scriptUrl;
719
+ qjs_worker_shared_flags[workerId] = @(YES);
720
+ if (ownerId != nil) {
721
+ qjs_owners[workerId] = ownerId;
722
+ }
723
+ zapp_worker_register([workerId UTF8String]);
724
+
725
+ dispatch_async(qjs_queue, ^{
726
+ QJSValue args[1];
727
+ args[0] = JS_NewString(existingCtx, [workerId UTF8String]);
728
+ qjs_call_bridge_method(existingCtx, @"dispatchConnect", 1, args);
729
+ JS_FreeValue(existingCtx, args[0]);
730
+ });
731
+
732
+ return;
733
+ }
734
+ }
735
+
736
+ dispatch_async(qjs_queue, ^{
737
+ /* Re-check shared context: another create for same scriptUrl may have run before us on this queue */
738
+ if (shared) {
739
+ NSValue* existingVal = qjs_shared_worker_contexts[scriptUrl];
740
+ if (existingVal != nil) {
741
+ void* ptr = [existingVal pointerValue];
742
+ if (ptr == qjs_shared_loading_sentinel) {
743
+ /* First creator is still loading; re-dispatch so we run again after it finishes */
744
+ dispatch_async(qjs_queue, ^{
745
+ /* Re-invoke create handling for this workerId/scriptUrl (payload not re-used; we only need reuse path) */
746
+ NSValue* val = qjs_shared_worker_contexts[scriptUrl];
747
+ if (val == nil || [val pointerValue] == qjs_shared_loading_sentinel) {
748
+ zapp_dispatch_worker_to_webviews("error", [workerId UTF8String], "{\"message\":\"Shared worker failed to load\"}");
749
+ return;
750
+ }
751
+ QJSContext* existingCtx = (QJSContext*)[val pointerValue];
752
+ NSNumber* rc = qjs_shared_worker_refcounts[scriptUrl];
753
+ NSInteger next = (rc != nil ? [rc integerValue] : 0) + 1;
754
+ qjs_shared_worker_refcounts[scriptUrl] = @(next);
755
+ qjs_contexts[workerId] = [NSValue valueWithPointer:existingCtx];
756
+ qjs_worker_script_urls[workerId] = scriptUrl;
757
+ qjs_worker_shared_flags[workerId] = @(YES);
758
+ if (ownerId) [qjs_owners setObject:ownerId forKey:workerId];
759
+ zapp_worker_register([workerId UTF8String]);
760
+ QJSValue args[1];
761
+ args[0] = JS_NewString(existingCtx, [workerId UTF8String]);
762
+ qjs_call_bridge_method(existingCtx, @"dispatchConnect", 1, args);
763
+ JS_FreeValue(existingCtx, args[0]);
764
+ });
765
+ return;
766
+ }
767
+ QJSContext* existingCtx = (QJSContext*)ptr;
768
+ NSNumber* rc = qjs_shared_worker_refcounts[scriptUrl];
769
+ NSInteger next = (rc != nil ? [rc integerValue] : 0) + 1;
770
+ qjs_shared_worker_refcounts[scriptUrl] = @(next);
771
+ qjs_contexts[workerId] = [NSValue valueWithPointer:existingCtx];
772
+ qjs_worker_script_urls[workerId] = scriptUrl;
773
+ qjs_worker_shared_flags[workerId] = @(YES);
774
+ if (ownerId) [qjs_owners setObject:ownerId forKey:workerId];
775
+ zapp_worker_register([workerId UTF8String]);
776
+ QJSValue args[1];
777
+ args[0] = JS_NewString(existingCtx, [workerId UTF8String]);
778
+ qjs_call_bridge_method(existingCtx, @"dispatchConnect", 1, args);
779
+ JS_FreeValue(existingCtx, args[0]);
780
+ return;
781
+ }
782
+ /* Reserve slot so second creator will wait for us instead of loading too */
783
+ qjs_shared_worker_contexts[scriptUrl] = [NSValue valueWithPointer:qjs_shared_loading_sentinel];
784
+ }
785
+
786
+ if (maxWorkers > 0 && qjs_active_context_count() >= (NSUInteger)maxWorkers) {
787
+ if (shared) [qjs_shared_worker_contexts removeObjectForKey:scriptUrl];
788
+ zapp_worker_dispatch_max_workers_error(workerId, maxWorkers);
789
+ return;
790
+ }
791
+
792
+ NSError* err = nil;
793
+ NSString* source = zapp_worker_read_source(scriptUrl, &err);
794
+ if (!source) {
795
+ if (shared) [qjs_shared_worker_contexts removeObjectForKey:scriptUrl];
796
+ zapp_worker_dispatch_error_message(workerId, @"Failed to load script");
797
+ return;
798
+ }
799
+
800
+ void* ctx_opaque = engine_qjs_create_ctx(qjs_rt);
801
+ if (!ctx_opaque) {
802
+ if (shared) [qjs_shared_worker_contexts removeObjectForKey:scriptUrl];
803
+ zapp_worker_dispatch_error_message(workerId, @"Failed to create context");
804
+ return;
805
+ }
806
+ QJSContext* ctx = (QJSContext*)ctx_opaque;
807
+
808
+ [qjs_contexts setObject:[NSValue valueWithPointer:ctx] forKey:workerId];
809
+ [qjs_worker_script_urls setObject:scriptUrl forKey:workerId];
810
+ [qjs_worker_shared_flags setObject:@(shared) forKey:workerId];
811
+ if (ownerId) [qjs_owners setObject:ownerId forKey:workerId];
812
+ zapp_worker_register([workerId UTF8String]);
813
+
814
+ QJSValue global = JS_GetGlobalObject(ctx);
815
+
816
+ // Console with unified logging - pass workerId via func_data, log level via magic
817
+ QJSValue wIdVal = JS_NewString(ctx, [workerId UTF8String]);
818
+ QJSValue consoleFuncData[1] = { wIdVal };
819
+
820
+ QJSValue consoleObj = JS_NewObject(ctx);
821
+ JS_SetPropertyStr(ctx, consoleObj, "log", JS_NewCFunctionData(ctx, qjs_console_log_with_data, 1, ZAPP_LOG_INFO, 1, consoleFuncData));
822
+ JS_SetPropertyStr(ctx, consoleObj, "error", JS_NewCFunctionData(ctx, qjs_console_log_with_data, 1, ZAPP_LOG_ERROR, 1, consoleFuncData));
823
+ JS_SetPropertyStr(ctx, consoleObj, "warn", JS_NewCFunctionData(ctx, qjs_console_log_with_data, 1, ZAPP_LOG_WARN, 1, consoleFuncData));
824
+ JS_SetPropertyStr(ctx, consoleObj, "info", JS_NewCFunctionData(ctx, qjs_console_log_with_data, 1, ZAPP_LOG_INFO, 1, consoleFuncData));
825
+ JS_SetPropertyStr(ctx, consoleObj, "debug", JS_NewCFunctionData(ctx, qjs_console_log_with_data, 1, ZAPP_LOG_DEBUG, 1, consoleFuncData));
826
+ JS_SetPropertyStr(ctx, consoleObj, "trace", JS_NewCFunctionData(ctx, qjs_console_log_with_data, 1, ZAPP_LOG_TRACE, 1, consoleFuncData));
827
+ JS_SetPropertyStr(ctx, global, "console", consoleObj);
828
+
829
+ QJSValue bridge = JS_NewObject(ctx);
830
+
831
+ QJSValue funcData[1] = { wIdVal };
832
+
833
+ QJSValue postFunc = JS_NewCFunctionData(ctx, qjs_bridge_post, 1, 0, 1, funcData);
834
+ JS_SetPropertyStr(ctx, bridge, "postMessage", postFunc);
835
+
836
+ QJSValue emitFunc = JS_NewCFunctionData(ctx, qjs_bridge_emitToHost, 2, 0, 1, funcData);
837
+ JS_SetPropertyStr(ctx, bridge, "emitToHost", emitFunc);
838
+
839
+ QJSValue invokeFunc = JS_NewCFunctionData(ctx, qjs_bridge_invokeToHost, 2, 0, 1, funcData);
840
+ JS_SetPropertyStr(ctx, bridge, "invokeToHost", invokeFunc);
841
+
842
+ QJSValue reportFunc = JS_NewCFunctionData(ctx, qjs_bridge_reportError, 1, 0, 1, funcData);
843
+ JS_SetPropertyStr(ctx, bridge, "reportError", reportFunc);
844
+
845
+ QJSValue closeFunc = JS_NewCFunctionData(ctx, qjs_bridge_closeWorker, 0, 0, 1, funcData);
846
+ JS_SetPropertyStr(ctx, bridge, "closeWorker", closeFunc);
847
+
848
+ QJSValue syncWaitFunc = JS_NewCFunctionData(ctx, qjs_bridge_syncWait, 1, 0, 1, funcData);
849
+ JS_SetPropertyStr(ctx, bridge, "syncWait", syncWaitFunc);
850
+ QJSValue syncNotifyFunc = JS_NewCFunctionData(ctx, qjs_bridge_syncNotify, 1, 0, 1, funcData);
851
+ JS_SetPropertyStr(ctx, bridge, "syncNotify", syncNotifyFunc);
852
+ QJSValue syncCancelFunc = JS_NewCFunctionData(ctx, qjs_bridge_syncCancel, 1, 0, 1, funcData);
853
+ JS_SetPropertyStr(ctx, bridge, "syncCancel", syncCancelFunc);
854
+
855
+ zapp_darwin_qjs_timers_install(ctx, bridge, [workerId UTF8String]);
856
+ zapp_darwin_qjs_fetch_install(ctx, bridge, [workerId UTF8String]);
857
+ zapp_qjs_crypto_install(ctx, bridge);
858
+ zapp_qjs_encoding_install(ctx, bridge);
859
+ zapp_qjs_url_install(ctx, bridge);
860
+ zapp_qjs_base64_install(ctx, bridge);
861
+ zapp_darwin_qjs_websocket_install(ctx, bridge, [workerId UTF8String]);
862
+
863
+ JS_SetPropertyStr(ctx, global, "globalThis", JS_DupValue(ctx, global));
864
+ JS_SetPropertyStr(ctx, global, "self", JS_DupValue(ctx, global));
865
+ JS_SetPropertyStr(ctx, global, "__zappWorkerDispatchId", JS_NewString(ctx, [workerId UTF8String]));
866
+ JS_SetPropertyStr(ctx, global, "__zappBridge", bridge);
867
+
868
+ JS_FreeValue(ctx, wIdVal);
869
+
870
+ const char* bsrc = zapp_darwin_worker_bootstrap_script();
871
+ int qjs_ret = engine_qjs_eval(ctx, bsrc, strlen(bsrc), "bootstrap.js");
872
+ if (qjs_ret != 0) {
873
+ if (shared) [qjs_shared_worker_contexts removeObjectForKey:scriptUrl];
874
+ const char* msg = engine_qjs_get_exception_message(ctx);
875
+ NSString* errMsg = msg
876
+ ? [NSString stringWithFormat:@"{\"message\":\"Bootstrap failed: %s\"}", msg]
877
+ : @"{\"message\":\"Bootstrap failed\"}";
878
+ zapp_dispatch_worker_to_webviews("error", [workerId UTF8String], [errMsg UTF8String]);
879
+ engine_qjs_free_ctx(ctx);
880
+ return;
881
+ }
882
+
883
+ NSString* wrapped = [[@"(async function(){\n" stringByAppendingString:source] stringByAppendingString:@"\n})();"];
884
+ NSData* wrappedData = [wrapped dataUsingEncoding:NSUTF8StringEncoding];
885
+ if (!wrappedData || [wrappedData length] == 0) {
886
+ if (shared) [qjs_shared_worker_contexts removeObjectForKey:scriptUrl];
887
+ zapp_worker_dispatch_error_message(workerId, @"Script encoding failed");
888
+ [qjs_contexts removeObjectForKey:workerId];
889
+ [qjs_worker_script_urls removeObjectForKey:workerId];
890
+ [qjs_worker_shared_flags removeObjectForKey:workerId];
891
+ if (ownerId) [qjs_owners removeObjectForKey:workerId];
892
+ engine_qjs_free_ctx(ctx);
893
+ return;
894
+ }
895
+ size_t wrappedLen = [wrappedData length];
896
+ char* scriptCopy = (char*)malloc(wrappedLen + 1);
897
+ if (!scriptCopy) {
898
+ if (shared) [qjs_shared_worker_contexts removeObjectForKey:scriptUrl];
899
+ zapp_dispatch_worker_to_webviews("error", [workerId UTF8String], "{\"message\":\"Out of memory\"}");
900
+ [qjs_contexts removeObjectForKey:workerId];
901
+ [qjs_worker_script_urls removeObjectForKey:workerId];
902
+ [qjs_worker_shared_flags removeObjectForKey:workerId];
903
+ if (ownerId) [qjs_owners removeObjectForKey:workerId];
904
+ engine_qjs_free_ctx(ctx);
905
+ return;
906
+ }
907
+ memcpy(scriptCopy, [wrappedData bytes], wrappedLen);
908
+ scriptCopy[wrappedLen] = '\0';
909
+ const char* scriptUrlCstr = [scriptUrl UTF8String];
910
+ qjs_ret = engine_qjs_eval(ctx, scriptCopy, wrappedLen, scriptUrlCstr ? scriptUrlCstr : "<script>");
911
+ free(scriptCopy);
912
+ if (qjs_ret != 0) {
913
+ if (shared) [qjs_shared_worker_contexts removeObjectForKey:scriptUrl];
914
+ const char* msg = engine_qjs_get_exception_message(ctx);
915
+ NSString* errMsg = msg
916
+ ? [NSString stringWithFormat:@"{\"message\":\"%@\"}", [NSString stringWithUTF8String:msg]]
917
+ : @"{\"message\":\"Script error\"}";
918
+ zapp_dispatch_worker_to_webviews("error", [workerId UTF8String], [errMsg UTF8String]);
919
+ [qjs_contexts removeObjectForKey:workerId];
920
+ [qjs_worker_script_urls removeObjectForKey:workerId];
921
+ [qjs_worker_shared_flags removeObjectForKey:workerId];
922
+ if (ownerId) [qjs_owners removeObjectForKey:workerId];
923
+ engine_qjs_free_ctx(ctx);
924
+ return;
925
+ }
926
+
927
+ qjs_drain_jobs(ctx, "create");
928
+
929
+ if (shared) {
930
+ [qjs_shared_worker_contexts setObject:[NSValue valueWithPointer:ctx] forKey:scriptUrl];
931
+ [qjs_shared_worker_refcounts setObject:@(1) forKey:scriptUrl];
932
+
933
+ QJSValue args[1];
934
+ args[0] = JS_NewString(ctx, [workerId UTF8String]);
935
+ qjs_call_bridge_method(ctx, @"dispatchConnect", 1, args);
936
+ JS_FreeValue(ctx, args[0]);
937
+ }
938
+
939
+ JS_FreeValue(ctx, global);
940
+ });
941
+ return;
942
+ }
943
+
944
+ if ([actionStr isEqualToString:@"post"]) {
945
+ id dataObj = body[@"data"];
946
+ NSData* dataData = [NSJSONSerialization dataWithJSONObject:dataObj ?: @{} options:NSJSONWritingFragmentsAllowed error:nil];
947
+ NSString* dataJson = [[NSString alloc] initWithData:dataData encoding:NSUTF8StringEncoding];
948
+
949
+ dispatch_async(qjs_queue, ^{
950
+ NSValue* val = qjs_contexts[workerId];
951
+ if (!val) return;
952
+
953
+ QJSContext* ctx = (QJSContext*)[val pointerValue];
954
+
955
+ QJSValue global = JS_GetGlobalObject(ctx);
956
+ QJSValue globalJSON = JS_GetPropertyStr(ctx, global, "JSON");
957
+ QJSValue parseFunc = JS_GetPropertyStr(ctx, globalJSON, "parse");
958
+ QJSValue argJson = JS_NewString(ctx, [dataJson UTF8String]);
959
+ QJSValue parsedData = JS_Call(ctx, parseFunc, globalJSON, 1, &argJson);
960
+
961
+ if (JS_IsException(parsedData)) {
962
+ QJSValue exception = JS_GetException(ctx);
963
+ const char *str = JS_ToCString(ctx, exception);
964
+ char buf[256];
965
+ snprintf(buf, sizeof(buf), "JSON.parse in post failed: %s", str ? str : "unknown");
966
+ zapp_log_worker(ZAPP_LOG_ERROR, -1, -1, buf);
967
+ if (str) JS_FreeCString(ctx, str);
968
+ JS_FreeValue(ctx, exception);
969
+ } else {
970
+ QJSValue args[2];
971
+ args[0] = parsedData;
972
+ args[1] = JS_NewString(ctx, [workerId UTF8String]);
973
+ qjs_call_bridge_method(ctx, @"dispatchMessage", 2, args);
974
+ JS_FreeValue(ctx, args[1]);
975
+ }
976
+
977
+ JS_FreeValue(ctx, parsedData);
978
+ JS_FreeValue(ctx, argJson);
979
+ JS_FreeValue(ctx, parseFunc);
980
+ JS_FreeValue(ctx, globalJSON);
981
+ JS_FreeValue(ctx, global);
982
+ });
983
+ return;
984
+ }
985
+
986
+ if ([actionStr isEqualToString:@"terminate"]) {
987
+ if (ownerId != nil) {
988
+ NSString* existingOwner = qjs_owners[workerId];
989
+ if (existingOwner != nil && ![existingOwner isEqualToString:ownerId]) return;
990
+ }
991
+
992
+ NSValue* val = qjs_contexts[workerId];
993
+ if (!val) return;
994
+
995
+ NSString* scriptUrl = qjs_worker_script_urls[workerId];
996
+ NSNumber* sharedFlag = qjs_worker_shared_flags[workerId];
997
+ BOOL isShared = (sharedFlag != nil && [sharedFlag boolValue]);
998
+
999
+ [qjs_contexts removeObjectForKey:workerId];
1000
+ [qjs_owners removeObjectForKey:workerId];
1001
+ [qjs_worker_script_urls removeObjectForKey:workerId];
1002
+ [qjs_worker_shared_flags removeObjectForKey:workerId];
1003
+
1004
+ dispatch_async(qjs_queue, ^{
1005
+ QJSContext* ctx = (QJSContext*)[val pointerValue];
1006
+
1007
+ zapp_darwin_qjs_timers_cancel(workerId);
1008
+ zapp_darwin_qjs_fetch_cancel(workerId);
1009
+
1010
+ if (isShared && scriptUrl != nil) {
1011
+ QJSValue args[1];
1012
+ args[0] = JS_NewString(ctx, [workerId UTF8String]);
1013
+ qjs_call_bridge_method(ctx, @"dispatchDisconnect", 1, args);
1014
+ JS_FreeValue(ctx, args[0]);
1015
+
1016
+ NSNumber* rc = qjs_shared_worker_refcounts[scriptUrl];
1017
+ NSInteger next = (rc != nil ? [rc integerValue] : 1) - 1;
1018
+
1019
+ if (next > 0) {
1020
+ qjs_shared_worker_refcounts[scriptUrl] = @(next);
1021
+ } else {
1022
+ [qjs_shared_worker_refcounts removeObjectForKey:scriptUrl];
1023
+ [qjs_shared_worker_contexts removeObjectForKey:scriptUrl];
1024
+ engine_qjs_free_ctx(ctx);
1025
+ }
1026
+ } else {
1027
+ engine_qjs_free_ctx(ctx);
1028
+ }
1029
+ });
1030
+
1031
+ return;
1032
+ }
1033
+ #endif /* __APPLE__ */
1034
+ #endif
1035
+ }
1036
+ }
1037
+ }
1038
+
1039
+ raw {
1040
+ #ifdef ZAPP_WORKER_ENGINE_QJS
1041
+ #ifdef __APPLE__
1042
+ #undef JSContext
1043
+ #undef JSRuntime
1044
+ #undef JSValue
1045
+ #undef JSClassID
1046
+ #undef JSClassDef
1047
+ #undef JSClassEx
1048
+ #undef JSValueConst
1049
+ #undef JSPropertyEnum
1050
+ #undef JSPropertyDescriptor
1051
+ #endif /* __APPLE__ */
1052
+ #endif
1053
+ }