@mikrojs/native 0.18.3-next.20260829153835 → 0.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CMakeLists.txt +24 -2
- package/include/mikrojs/private.h +28 -0
- package/package.json +4 -5
- package/prebuilds/darwin-arm64/mikrojs.napi.node +0 -0
- package/prebuilds/linux-arm64/mikrojs.napi.node +0 -0
- package/prebuilds/linux-x64/mikrojs.napi.node +0 -0
- package/runtime/http/helpers.ts +5 -0
- package/runtime/internal.d.ts +28 -0
- package/runtime/ota/types.ts +3 -3
- package/runtime/schema/schema.ts +16 -3
- package/runtime/schema/types.ts +1 -1
- package/runtime/test/test.ts +6 -2
- package/runtime/wifi/types.ts +3 -0
- package/scripts/gen-schema-fixtures.js +317 -0
- package/src/mik_app_config.cpp +9 -1
- package/src/mik_http_client.cpp +1357 -0
- package/src/mik_repl.cpp +44 -5
- package/src/mik_schema.cpp +892 -0
- package/src/mik_wifi_client.cpp +742 -0
- package/src/mikrojs.cpp +34 -8
- package/src/modules.cpp +66 -0
- package/dist/runtime/schema/core.d.ts +0 -163
- package/dist/runtime/schema/core.d.ts.map +0 -1
- package/dist/runtime/schema/core.js +0 -306
- package/dist/runtime/schema/core.js.map +0 -1
- package/dist/runtime/schema/shared.d.ts +0 -79
- package/dist/runtime/schema/shared.d.ts.map +0 -1
- package/dist/runtime/schema/shared.js +0 -1005
- package/dist/runtime/schema/shared.js.map +0 -1
- package/runtime/http/native.ts +0 -160
- package/runtime/http/request.ts +0 -25
- package/runtime/schema/core.ts +0 -842
- package/runtime/schema/shared.ts +0 -1040
- package/runtime/wifi/wifi.ts +0 -191
|
@@ -0,0 +1,1357 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Native implementation of the `mikro/http/helpers` and `mikro/http/request`
|
|
3
|
+
* builtins (formerly TypeScript bundled to bytecode; ~17 KB heap per import).
|
|
4
|
+
* The transport stays behind `native:mikro/http` and is resolved through the
|
|
5
|
+
* module loader, so virtual-module overrides (simulator stubs, host test
|
|
6
|
+
* fakes) keep working. Behavior is pinned by test/http_client_test.cpp; the
|
|
7
|
+
* TS sources in runtime/http/ remain as the type surface and Node-side double.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
#include <mikrojs/mikrojs.h>
|
|
11
|
+
#include <mikrojs/platform.h>
|
|
12
|
+
#include <mikrojs/private.h>
|
|
13
|
+
#include <mikrojs/utils.h>
|
|
14
|
+
|
|
15
|
+
#include <string.h>
|
|
16
|
+
|
|
17
|
+
#include <string>
|
|
18
|
+
|
|
19
|
+
#include "quickjs.h"
|
|
20
|
+
|
|
21
|
+
namespace {
|
|
22
|
+
|
|
23
|
+
/* obj.method(args) with atom lifecycle handled. Borrows obj and args. */
|
|
24
|
+
JSValue hc_invoke(JSContext* ctx, JSValue obj, const char* method, int argc, JSValue* argv) {
|
|
25
|
+
JSAtom atom = JS_NewAtom(ctx, method);
|
|
26
|
+
JSValue r = JS_Invoke(ctx, obj, atom, argc, argv);
|
|
27
|
+
JS_FreeAtom(ctx, atom);
|
|
28
|
+
return r;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/* Call settle function `fn` (borrowed) with `arg` (consumed). */
|
|
32
|
+
void hc_settle(JSContext* ctx, JSValue fn, JSValue arg) {
|
|
33
|
+
JSValue r = JS_Call(ctx, fn, JS_UNDEFINED, 1, &arg);
|
|
34
|
+
JS_FreeValue(ctx, r);
|
|
35
|
+
JS_FreeValue(ctx, arg);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/* Convert the pending exception into a rejected promise. The TS originals
|
|
39
|
+
* were async methods, so failures must reject, never throw synchronously. */
|
|
40
|
+
JSValue hc_reject_pending_exception(JSContext* ctx) {
|
|
41
|
+
JSValue exc = JS_GetException(ctx);
|
|
42
|
+
return MIK_NewRejectedPromise(ctx, 1, &exc);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/* {done, value} iterator-result object. Consumes `value`. */
|
|
46
|
+
JSValue hc_iter_result(JSContext* ctx, bool done, JSValue value) {
|
|
47
|
+
JSValue o = JS_NewObject(ctx);
|
|
48
|
+
JS_DefinePropertyValueStr(ctx, o, "done", JS_NewBool(ctx, done), JS_PROP_C_W_E);
|
|
49
|
+
JS_DefinePropertyValueStr(ctx, o, "value", value, JS_PROP_C_W_E);
|
|
50
|
+
return o;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
JSAtom hc_async_iterator_atom(JSContext* ctx) {
|
|
54
|
+
JSValue g = JS_GetGlobalObject(ctx);
|
|
55
|
+
JSValue sym_ctor = JS_GetPropertyStr(ctx, g, "Symbol");
|
|
56
|
+
JSValue sym = JS_GetPropertyStr(ctx, sym_ctor, "asyncIterator");
|
|
57
|
+
JSAtom atom = JS_ValueToAtom(ctx, sym);
|
|
58
|
+
JS_FreeValue(ctx, sym);
|
|
59
|
+
JS_FreeValue(ctx, sym_ctor);
|
|
60
|
+
JS_FreeValue(ctx, g);
|
|
61
|
+
return atom;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/* source[Symbol.asyncIterator]() — returns the iterator (owned) or exception. */
|
|
65
|
+
JSValue hc_get_async_iterator(JSContext* ctx, JSValue source) {
|
|
66
|
+
JSAtom atom = hc_async_iterator_atom(ctx);
|
|
67
|
+
JSValue fn = JS_GetProperty(ctx, source, atom);
|
|
68
|
+
JS_FreeAtom(ctx, atom);
|
|
69
|
+
if (JS_IsException(fn)) return fn;
|
|
70
|
+
JSValue it = JS_Call(ctx, fn, source, 0, nullptr);
|
|
71
|
+
JS_FreeValue(ctx, fn);
|
|
72
|
+
return it;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/* Promise.resolve(v) — normalizes thenables and plain values. Consumes `v`. */
|
|
76
|
+
JSValue hc_promise_resolve(JSContext* ctx, JSValue v) {
|
|
77
|
+
JSValue g = JS_GetGlobalObject(ctx);
|
|
78
|
+
JSValue promise_ctor = JS_GetPropertyStr(ctx, g, "Promise");
|
|
79
|
+
JS_FreeValue(ctx, g);
|
|
80
|
+
JSValue p = hc_invoke(ctx, promise_ctor, "resolve", 1, &v);
|
|
81
|
+
JS_FreeValue(ctx, promise_ctor);
|
|
82
|
+
JS_FreeValue(ctx, v);
|
|
83
|
+
return p;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/* p.then(on_fulfilled, on_rejected). Consumes both handlers; borrows `p`. */
|
|
87
|
+
void hc_then2(JSContext* ctx, JSValue p, JSValue on_fulfilled, JSValue on_rejected) {
|
|
88
|
+
JSValue args[2] = {on_fulfilled, on_rejected};
|
|
89
|
+
JSValue derived = hc_invoke(ctx, p, "then", 2, args);
|
|
90
|
+
JS_FreeValue(ctx, derived);
|
|
91
|
+
JS_FreeValue(ctx, on_fulfilled);
|
|
92
|
+
JS_FreeValue(ctx, on_rejected);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
bool hc_get_flag(JSContext* ctx, JSValue st, const char* name) {
|
|
96
|
+
JSValue v = JS_GetPropertyStr(ctx, st, name);
|
|
97
|
+
bool b = JS_ToBool(ctx, v) > 0;
|
|
98
|
+
JS_FreeValue(ctx, v);
|
|
99
|
+
return b;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
void hc_set_flag(JSContext* ctx, JSValue st, const char* name, bool v) {
|
|
103
|
+
JS_SetPropertyStr(ctx, st, name, JS_NewBool(ctx, v));
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/* `e instanceof Error ? e.message : String(e)` */
|
|
107
|
+
std::string hc_error_message(JSContext* ctx, JSValue e) {
|
|
108
|
+
JSValue mv = JS_UNDEFINED;
|
|
109
|
+
if (JS_IsError(e)) {
|
|
110
|
+
mv = JS_GetPropertyStr(ctx, e, "message");
|
|
111
|
+
}
|
|
112
|
+
const char* s = JS_ToCString(ctx, JS_IsUndefined(mv) ? e : mv);
|
|
113
|
+
std::string out = s ? s : "";
|
|
114
|
+
JS_FreeCString(ctx, s);
|
|
115
|
+
JS_FreeValue(ctx, mv);
|
|
116
|
+
return out;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/* `msg.message || fallback` with JS falsy semantics for the string case. */
|
|
120
|
+
std::string hc_message_or(JSContext* ctx, JSValue msg_obj, const std::string& fallback) {
|
|
121
|
+
JSValue mv = JS_GetPropertyStr(ctx, msg_obj, "message");
|
|
122
|
+
if (JS_IsUndefined(mv) || JS_IsNull(mv)) {
|
|
123
|
+
JS_FreeValue(ctx, mv);
|
|
124
|
+
return fallback;
|
|
125
|
+
}
|
|
126
|
+
const char* s = JS_ToCString(ctx, mv);
|
|
127
|
+
std::string out = s ? s : "";
|
|
128
|
+
JS_FreeCString(ctx, s);
|
|
129
|
+
JS_FreeValue(ctx, mv);
|
|
130
|
+
if (out.empty()) return fallback;
|
|
131
|
+
return out;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/* `String(signal.reason ?? 'aborted')`; "aborted" when signal is undefined. */
|
|
135
|
+
std::string hc_abort_reason(JSContext* ctx, JSValue signal) {
|
|
136
|
+
if (!JS_IsObject(signal)) return "aborted";
|
|
137
|
+
JSValue reason = JS_GetPropertyStr(ctx, signal, "reason");
|
|
138
|
+
if (JS_IsUndefined(reason) || JS_IsNull(reason)) {
|
|
139
|
+
JS_FreeValue(ctx, reason);
|
|
140
|
+
return "aborted";
|
|
141
|
+
}
|
|
142
|
+
const char* s = JS_ToCString(ctx, reason);
|
|
143
|
+
std::string out = s ? s : "";
|
|
144
|
+
JS_FreeCString(ctx, s);
|
|
145
|
+
JS_FreeValue(ctx, reason);
|
|
146
|
+
return out;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/* ── BodyConsumedError ──────────────────────────────────────────────────── */
|
|
150
|
+
|
|
151
|
+
/* new.target is deliberately ignored: subclassing BodyConsumedError is out
|
|
152
|
+
* of scope (minus-100) — instances always get BodyConsumedError.prototype. */
|
|
153
|
+
JSValue hc_body_consumed_ctor_cf(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) {
|
|
154
|
+
(void)this_val;
|
|
155
|
+
(void)argc;
|
|
156
|
+
(void)argv;
|
|
157
|
+
JSValue e = JS_NewError(ctx);
|
|
158
|
+
JS_DefinePropertyValueStr(ctx, e, "message",
|
|
159
|
+
JS_NewString(ctx, "response body already consumed"),
|
|
160
|
+
JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE);
|
|
161
|
+
JS_DefinePropertyValueStr(ctx, e, "name", JS_NewString(ctx, "BodyConsumed"), JS_PROP_C_W_E);
|
|
162
|
+
MIKRuntime* mik_rt = MIK_GetRuntime(ctx);
|
|
163
|
+
if (!JS_IsUndefined(mik_rt->http_body_consumed_ctor)) {
|
|
164
|
+
JSValue proto = JS_GetPropertyStr(ctx, mik_rt->http_body_consumed_ctor, "prototype");
|
|
165
|
+
JS_SetPrototype(ctx, e, proto);
|
|
166
|
+
JS_FreeValue(ctx, proto);
|
|
167
|
+
}
|
|
168
|
+
return e;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/* Lazily create the shared BodyConsumedError constructor; returns a dup. */
|
|
172
|
+
JSValue hc_body_consumed_ctor(JSContext* ctx) {
|
|
173
|
+
MIKRuntime* mik_rt = MIK_GetRuntime(ctx);
|
|
174
|
+
if (!JS_IsUndefined(mik_rt->http_body_consumed_ctor)) {
|
|
175
|
+
return JS_DupValue(ctx, mik_rt->http_body_consumed_ctor);
|
|
176
|
+
}
|
|
177
|
+
JSValue g = JS_GetGlobalObject(ctx);
|
|
178
|
+
JSValue error_ctor = JS_GetPropertyStr(ctx, g, "Error");
|
|
179
|
+
JSValue error_proto = JS_GetPropertyStr(ctx, error_ctor, "prototype");
|
|
180
|
+
JS_FreeValue(ctx, error_ctor);
|
|
181
|
+
JS_FreeValue(ctx, g);
|
|
182
|
+
|
|
183
|
+
JSValue proto = JS_NewObjectProto(ctx, error_proto);
|
|
184
|
+
JS_FreeValue(ctx, error_proto);
|
|
185
|
+
JSValue ctor = JS_NewCFunction2(ctx, hc_body_consumed_ctor_cf, "BodyConsumedError", 0,
|
|
186
|
+
JS_CFUNC_constructor, 0);
|
|
187
|
+
JS_DefinePropertyValueStr(ctx, proto, "constructor", JS_DupValue(ctx, ctor),
|
|
188
|
+
JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE);
|
|
189
|
+
JS_DefinePropertyValueStr(ctx, ctor, "prototype", proto, 0);
|
|
190
|
+
|
|
191
|
+
mik_rt->http_body_consumed_ctor = JS_DupValue(ctx, ctor);
|
|
192
|
+
return ctor;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/* Throw a fresh BodyConsumedError instance. Always returns JS_EXCEPTION. */
|
|
196
|
+
JSValue hc_throw_body_consumed(JSContext* ctx) {
|
|
197
|
+
JSValue ctor = hc_body_consumed_ctor(ctx);
|
|
198
|
+
JSValue e = JS_CallConstructor(ctx, ctor, 0, nullptr);
|
|
199
|
+
JS_FreeValue(ctx, ctor);
|
|
200
|
+
if (JS_IsException(e)) return e;
|
|
201
|
+
return JS_Throw(ctx, e);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/* ── RequestError factories ─────────────────────────────────────────────── */
|
|
205
|
+
|
|
206
|
+
enum {
|
|
207
|
+
RE_HARDWARE,
|
|
208
|
+
RE_NETWORK,
|
|
209
|
+
RE_TIMEOUT,
|
|
210
|
+
RE_BODY_TOO_LARGE,
|
|
211
|
+
RE_INVALID_RESPONSE,
|
|
212
|
+
RE_ABORTED,
|
|
213
|
+
RE_TOO_MANY_PENDING,
|
|
214
|
+
RE_INVALID_JSON,
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
const char* const kRequestErrorNames[] = {
|
|
218
|
+
"Hardware", "Network", "Timeout", "BodyTooLarge",
|
|
219
|
+
"InvalidResponse", "Aborted", "TooManyPending", "InvalidJson",
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
JSValue hc_request_error_factory(JSContext* ctx, JSValue this_val, int argc, JSValue* argv,
|
|
223
|
+
int magic) {
|
|
224
|
+
(void)this_val;
|
|
225
|
+
JSValue o = JS_NewObject(ctx);
|
|
226
|
+
JS_DefinePropertyValueStr(ctx, o, "name", JS_NewString(ctx, kRequestErrorNames[magic]),
|
|
227
|
+
JS_PROP_C_W_E);
|
|
228
|
+
if (magic == RE_TOO_MANY_PENDING) return o;
|
|
229
|
+
if (magic == RE_BODY_TOO_LARGE) {
|
|
230
|
+
JS_DefinePropertyValueStr(ctx, o, "size",
|
|
231
|
+
JS_DupValue(ctx, argc > 0 ? argv[0] : JS_UNDEFINED),
|
|
232
|
+
JS_PROP_C_W_E);
|
|
233
|
+
JS_DefinePropertyValueStr(ctx, o, "cap",
|
|
234
|
+
JS_DupValue(ctx, argc > 1 ? argv[1] : JS_UNDEFINED),
|
|
235
|
+
JS_PROP_C_W_E);
|
|
236
|
+
return o;
|
|
237
|
+
}
|
|
238
|
+
JS_DefinePropertyValueStr(ctx, o, "message",
|
|
239
|
+
JS_DupValue(ctx, argc > 0 ? argv[0] : JS_UNDEFINED), JS_PROP_C_W_E);
|
|
240
|
+
return o;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
JSValue hc_make_request_error_obj(JSContext* ctx) {
|
|
244
|
+
JSValue re = JS_NewObject(ctx);
|
|
245
|
+
static const struct {
|
|
246
|
+
const char* prop;
|
|
247
|
+
int magic;
|
|
248
|
+
int length;
|
|
249
|
+
} kFactories[] = {
|
|
250
|
+
{"Hardware", RE_HARDWARE, 1},
|
|
251
|
+
{"Network", RE_NETWORK, 1},
|
|
252
|
+
{"Timeout", RE_TIMEOUT, 1},
|
|
253
|
+
{"BodyTooLarge", RE_BODY_TOO_LARGE, 2},
|
|
254
|
+
{"InvalidResponse", RE_INVALID_RESPONSE, 1},
|
|
255
|
+
{"Aborted", RE_ABORTED, 1},
|
|
256
|
+
{"TooManyPending", RE_TOO_MANY_PENDING, 0},
|
|
257
|
+
{"InvalidJson", RE_INVALID_JSON, 1},
|
|
258
|
+
};
|
|
259
|
+
for (const auto& f : kFactories) {
|
|
260
|
+
JS_DefinePropertyValueStr(
|
|
261
|
+
ctx, re, f.prop,
|
|
262
|
+
JS_NewCFunctionMagic(ctx, hc_request_error_factory, f.prop, f.length,
|
|
263
|
+
JS_CFUNC_generic_magic, f.magic),
|
|
264
|
+
JS_PROP_C_W_E);
|
|
265
|
+
}
|
|
266
|
+
return re;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/* ── prepareBody ────────────────────────────────────────────────────────── */
|
|
270
|
+
|
|
271
|
+
/* Normalize headers into a fresh [k, v][] array. Borrows `input`. */
|
|
272
|
+
JSValue hc_normalize_headers(JSContext* ctx, JSValue input) {
|
|
273
|
+
JSValue out = JS_NewArray(ctx);
|
|
274
|
+
if (!JS_IsObject(input)) return out;
|
|
275
|
+
uint32_t out_idx = 0;
|
|
276
|
+
if (JS_IsArray(input)) {
|
|
277
|
+
JSValue len_v = JS_GetPropertyStr(ctx, input, "length");
|
|
278
|
+
uint32_t len = 0;
|
|
279
|
+
JS_ToUint32(ctx, &len, len_v);
|
|
280
|
+
JS_FreeValue(ctx, len_v);
|
|
281
|
+
for (uint32_t i = 0; i < len; i++) {
|
|
282
|
+
JSValue pair = JS_GetPropertyUint32(ctx, input, i);
|
|
283
|
+
JSValue k = JS_IsException(pair) ? JS_EXCEPTION : JS_GetPropertyUint32(ctx, pair, 0);
|
|
284
|
+
JSValue v = JS_IsException(k) ? JS_EXCEPTION : JS_GetPropertyUint32(ctx, pair, 1);
|
|
285
|
+
JS_FreeValue(ctx, pair);
|
|
286
|
+
if (JS_IsException(v)) {
|
|
287
|
+
JS_FreeValue(ctx, k);
|
|
288
|
+
JS_FreeValue(ctx, out);
|
|
289
|
+
return JS_EXCEPTION;
|
|
290
|
+
}
|
|
291
|
+
JSValue tuple = JS_NewArray(ctx);
|
|
292
|
+
JS_DefinePropertyValueUint32(ctx, tuple, 0, k, JS_PROP_C_W_E);
|
|
293
|
+
JS_DefinePropertyValueUint32(ctx, tuple, 1, v, JS_PROP_C_W_E);
|
|
294
|
+
JS_DefinePropertyValueUint32(ctx, out, out_idx++, tuple, JS_PROP_C_W_E);
|
|
295
|
+
}
|
|
296
|
+
return out;
|
|
297
|
+
}
|
|
298
|
+
JSPropertyEnum* tab = nullptr;
|
|
299
|
+
uint32_t count = 0;
|
|
300
|
+
if (JS_GetOwnPropertyNames(ctx, &tab, &count, input,
|
|
301
|
+
JS_GPN_STRING_MASK | JS_GPN_ENUM_ONLY) < 0) {
|
|
302
|
+
JS_FreeValue(ctx, out);
|
|
303
|
+
return JS_EXCEPTION;
|
|
304
|
+
}
|
|
305
|
+
for (uint32_t i = 0; i < count; i++) {
|
|
306
|
+
JSValue val = JS_GetProperty(ctx, input, tab[i].atom);
|
|
307
|
+
if (JS_IsException(val)) {
|
|
308
|
+
JS_FreePropertyEnum(ctx, tab, count);
|
|
309
|
+
JS_FreeValue(ctx, out);
|
|
310
|
+
return JS_EXCEPTION;
|
|
311
|
+
}
|
|
312
|
+
JSValue key = JS_AtomToString(ctx, tab[i].atom);
|
|
313
|
+
JSValue tuple = JS_NewArray(ctx);
|
|
314
|
+
JS_DefinePropertyValueUint32(ctx, tuple, 0, key, JS_PROP_C_W_E);
|
|
315
|
+
JS_DefinePropertyValueUint32(ctx, tuple, 1, val, JS_PROP_C_W_E);
|
|
316
|
+
JS_DefinePropertyValueUint32(ctx, out, out_idx++, tuple, JS_PROP_C_W_E);
|
|
317
|
+
}
|
|
318
|
+
JS_FreePropertyEnum(ctx, tab, count);
|
|
319
|
+
return out;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/* Core of prepareBody: {body: Uint8Array|null, headers: [k,v][]}. Borrows opts. */
|
|
323
|
+
JSValue hc_prepare_body(JSContext* ctx, JSValue opts) {
|
|
324
|
+
JSValue headers_in =
|
|
325
|
+
JS_IsObject(opts) ? JS_GetPropertyStr(ctx, opts, "headers") : JS_UNDEFINED;
|
|
326
|
+
JSValue headers = JS_IsException(headers_in) ? JS_EXCEPTION
|
|
327
|
+
: hc_normalize_headers(ctx, headers_in);
|
|
328
|
+
JS_FreeValue(ctx, headers_in);
|
|
329
|
+
if (JS_IsException(headers)) return JS_EXCEPTION;
|
|
330
|
+
|
|
331
|
+
JSValue body = JS_IsObject(opts) ? JS_GetPropertyStr(ctx, opts, "body") : JS_UNDEFINED;
|
|
332
|
+
if (JS_IsException(body)) {
|
|
333
|
+
JS_FreeValue(ctx, headers);
|
|
334
|
+
return JS_EXCEPTION;
|
|
335
|
+
}
|
|
336
|
+
JSValue out = JS_NewObject(ctx);
|
|
337
|
+
if (JS_IsUndefined(body)) {
|
|
338
|
+
JS_DefinePropertyValueStr(ctx, out, "body", JS_NULL, JS_PROP_C_W_E);
|
|
339
|
+
} else if (JS_IsString(body)) {
|
|
340
|
+
size_t len = 0;
|
|
341
|
+
const char* s = JS_ToCStringLen(ctx, &len, body);
|
|
342
|
+
JSValue arr = JS_NewUint8ArrayCopy(ctx, reinterpret_cast<const uint8_t*>(s), len);
|
|
343
|
+
JS_FreeCString(ctx, s);
|
|
344
|
+
JS_FreeValue(ctx, body);
|
|
345
|
+
JS_DefinePropertyValueStr(ctx, out, "body", arr, JS_PROP_C_W_E);
|
|
346
|
+
} else {
|
|
347
|
+
JS_DefinePropertyValueStr(ctx, out, "body", body, JS_PROP_C_W_E);
|
|
348
|
+
}
|
|
349
|
+
JS_DefinePropertyValueStr(ctx, out, "headers", headers, JS_PROP_C_W_E);
|
|
350
|
+
return out;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
JSValue hc_prepare_body_cf(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) {
|
|
354
|
+
(void)this_val;
|
|
355
|
+
return hc_prepare_body(ctx, argc > 0 ? argv[0] : JS_UNDEFINED);
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
/* ── Body drain machinery (bytes / text / json) ─────────────────────────── */
|
|
359
|
+
|
|
360
|
+
enum { DRAIN_BYTES, DRAIN_TEXT, DRAIN_JSON };
|
|
361
|
+
|
|
362
|
+
/* Drain state object props: it (iterator), arr (chunk array), res/rej
|
|
363
|
+
* (capability settle functions), mode (int). */
|
|
364
|
+
void hc_drain_step(JSContext* ctx, JSValue st);
|
|
365
|
+
|
|
366
|
+
void hc_drain_reject_exception(JSContext* ctx, JSValue st) {
|
|
367
|
+
JSValue exc = JS_GetException(ctx);
|
|
368
|
+
JSValue reject = JS_GetPropertyStr(ctx, st, "rej");
|
|
369
|
+
hc_settle(ctx, reject, exc);
|
|
370
|
+
JS_FreeValue(ctx, reject);
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
/* Best-effort AsyncIteratorClose: call it.return() when present, discard the
|
|
374
|
+
* outcome (marked handled if a promise). Releases foreign-transport resources
|
|
375
|
+
* on early exit; deliberately no await/error propagation. */
|
|
376
|
+
void hc_iterator_close(JSContext* ctx, JSValue it) {
|
|
377
|
+
JSValue ret_fn = JS_GetPropertyStr(ctx, it, "return");
|
|
378
|
+
if (!JS_IsFunction(ctx, ret_fn)) {
|
|
379
|
+
if (JS_IsException(ret_fn)) JS_FreeValue(ctx, JS_GetException(ctx));
|
|
380
|
+
JS_FreeValue(ctx, ret_fn);
|
|
381
|
+
return;
|
|
382
|
+
}
|
|
383
|
+
JSValue r = JS_Call(ctx, ret_fn, it, 0, nullptr);
|
|
384
|
+
JS_FreeValue(ctx, ret_fn);
|
|
385
|
+
if (JS_IsException(r)) {
|
|
386
|
+
JS_FreeValue(ctx, JS_GetException(ctx));
|
|
387
|
+
return;
|
|
388
|
+
}
|
|
389
|
+
if (JS_IsPromise(r)) JS_PromiseMarkAsHandled(ctx, r);
|
|
390
|
+
JS_FreeValue(ctx, r);
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
void hc_drain_finalize(JSContext* ctx, JSValue st) {
|
|
394
|
+
JSValue resolve = JS_GetPropertyStr(ctx, st, "res");
|
|
395
|
+
JSValue arr = JS_GetPropertyStr(ctx, st, "arr");
|
|
396
|
+
JSValue mode_v = JS_GetPropertyStr(ctx, st, "mode");
|
|
397
|
+
int32_t mode = 0;
|
|
398
|
+
JS_ToInt32(ctx, &mode, mode_v);
|
|
399
|
+
JS_FreeValue(ctx, mode_v);
|
|
400
|
+
|
|
401
|
+
JSValue len_v = JS_GetPropertyStr(ctx, arr, "length");
|
|
402
|
+
uint32_t nchunks = 0;
|
|
403
|
+
JS_ToUint32(ctx, &nchunks, len_v);
|
|
404
|
+
JS_FreeValue(ctx, len_v);
|
|
405
|
+
|
|
406
|
+
if (mode == DRAIN_BYTES && nchunks == 1) {
|
|
407
|
+
/* Single-chunk fast path returns the original array (TS parity). */
|
|
408
|
+
JSValue chunk = JS_GetPropertyUint32(ctx, arr, 0);
|
|
409
|
+
JS_FreeValue(ctx, arr);
|
|
410
|
+
hc_settle(ctx, resolve, mik__result_ok(ctx, chunk));
|
|
411
|
+
JS_FreeValue(ctx, resolve);
|
|
412
|
+
return;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
/* Merge chunks into one contiguous buffer. */
|
|
416
|
+
size_t total = 0;
|
|
417
|
+
for (uint32_t i = 0; i < nchunks; i++) {
|
|
418
|
+
JSValue chunk = JS_GetPropertyUint32(ctx, arr, i);
|
|
419
|
+
size_t sz = 0;
|
|
420
|
+
if (JS_GetUint8Array(ctx, &sz, chunk)) {
|
|
421
|
+
total += sz;
|
|
422
|
+
} else {
|
|
423
|
+
JSValue exc = JS_GetException(ctx);
|
|
424
|
+
JS_FreeValue(ctx, exc);
|
|
425
|
+
}
|
|
426
|
+
JS_FreeValue(ctx, chunk);
|
|
427
|
+
}
|
|
428
|
+
uint8_t* buf = static_cast<uint8_t*>(js_malloc(ctx, total ? total : 1));
|
|
429
|
+
if (!buf) {
|
|
430
|
+
JS_FreeValue(ctx, arr);
|
|
431
|
+
JS_FreeValue(ctx, resolve);
|
|
432
|
+
hc_drain_reject_exception(ctx, st);
|
|
433
|
+
return;
|
|
434
|
+
}
|
|
435
|
+
size_t off = 0;
|
|
436
|
+
for (uint32_t i = 0; i < nchunks; i++) {
|
|
437
|
+
JSValue chunk = JS_GetPropertyUint32(ctx, arr, i);
|
|
438
|
+
size_t sz = 0;
|
|
439
|
+
uint8_t* p = JS_GetUint8Array(ctx, &sz, chunk);
|
|
440
|
+
if (p && sz) {
|
|
441
|
+
memcpy(buf + off, p, sz);
|
|
442
|
+
off += sz;
|
|
443
|
+
} else if (!p) {
|
|
444
|
+
JSValue exc = JS_GetException(ctx);
|
|
445
|
+
JS_FreeValue(ctx, exc);
|
|
446
|
+
}
|
|
447
|
+
JS_FreeValue(ctx, chunk);
|
|
448
|
+
}
|
|
449
|
+
JS_FreeValue(ctx, arr);
|
|
450
|
+
|
|
451
|
+
JSValue settled = JS_UNDEFINED;
|
|
452
|
+
if (mode == DRAIN_BYTES) {
|
|
453
|
+
settled = mik__result_ok(ctx, JS_NewUint8ArrayCopy(ctx, buf, total));
|
|
454
|
+
} else {
|
|
455
|
+
/* Decode through the runtime's TextDecoder for exact TS parity
|
|
456
|
+
* (invalid-sequence replacement included). */
|
|
457
|
+
JSValue g = JS_GetGlobalObject(ctx);
|
|
458
|
+
JSValue td_ctor = JS_GetPropertyStr(ctx, g, "TextDecoder");
|
|
459
|
+
JS_FreeValue(ctx, g);
|
|
460
|
+
JSValue td = JS_CallConstructor(ctx, td_ctor, 0, nullptr);
|
|
461
|
+
JS_FreeValue(ctx, td_ctor);
|
|
462
|
+
JSValue u8 = JS_NewUint8ArrayCopy(ctx, buf, total);
|
|
463
|
+
JSValue text = hc_invoke(ctx, td, "decode", 1, &u8);
|
|
464
|
+
JS_FreeValue(ctx, u8);
|
|
465
|
+
JS_FreeValue(ctx, td);
|
|
466
|
+
if (JS_IsException(text)) {
|
|
467
|
+
js_free(ctx, buf);
|
|
468
|
+
JS_FreeValue(ctx, resolve);
|
|
469
|
+
hc_drain_reject_exception(ctx, st);
|
|
470
|
+
return;
|
|
471
|
+
}
|
|
472
|
+
if (mode == DRAIN_TEXT) {
|
|
473
|
+
settled = mik__result_ok(ctx, text);
|
|
474
|
+
} else {
|
|
475
|
+
size_t tlen = 0;
|
|
476
|
+
const char* ts = JS_ToCStringLen(ctx, &tlen, text);
|
|
477
|
+
JSValue parsed = JS_ParseJSON(ctx, ts ? ts : "", tlen, "<json>");
|
|
478
|
+
JS_FreeCString(ctx, ts);
|
|
479
|
+
JS_FreeValue(ctx, text);
|
|
480
|
+
if (JS_IsException(parsed)) {
|
|
481
|
+
JSValue exc = JS_GetException(ctx);
|
|
482
|
+
std::string msg = hc_error_message(ctx, exc);
|
|
483
|
+
JS_FreeValue(ctx, exc);
|
|
484
|
+
settled = mik__result_err_named(ctx, "InvalidJson", "%s", msg.c_str());
|
|
485
|
+
} else {
|
|
486
|
+
settled = mik__result_ok(ctx, parsed);
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
js_free(ctx, buf);
|
|
491
|
+
hc_settle(ctx, resolve, settled);
|
|
492
|
+
JS_FreeValue(ctx, resolve);
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
JSValue hc_drain_on_item(JSContext* ctx, JSValue this_val, int argc, JSValue* argv, int magic,
|
|
496
|
+
JSValue* func_data) {
|
|
497
|
+
(void)this_val;
|
|
498
|
+
(void)magic;
|
|
499
|
+
JSValue st = func_data[0];
|
|
500
|
+
JSValue item = argc > 0 ? argv[0] : JS_UNDEFINED;
|
|
501
|
+
|
|
502
|
+
JSValue done_v = JS_GetPropertyStr(ctx, item, "done");
|
|
503
|
+
if (JS_IsException(done_v)) {
|
|
504
|
+
hc_drain_reject_exception(ctx, st);
|
|
505
|
+
return JS_UNDEFINED;
|
|
506
|
+
}
|
|
507
|
+
bool done = JS_ToBool(ctx, done_v) > 0;
|
|
508
|
+
JS_FreeValue(ctx, done_v);
|
|
509
|
+
if (done) {
|
|
510
|
+
hc_drain_finalize(ctx, st);
|
|
511
|
+
return JS_UNDEFINED;
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
JSValue r = JS_GetPropertyStr(ctx, item, "value");
|
|
515
|
+
JSValue ok_v = JS_IsException(r) ? JS_EXCEPTION : JS_GetPropertyStr(ctx, r, "ok");
|
|
516
|
+
if (JS_IsException(ok_v)) {
|
|
517
|
+
if (!JS_IsException(r)) JS_FreeValue(ctx, r);
|
|
518
|
+
hc_drain_reject_exception(ctx, st);
|
|
519
|
+
return JS_UNDEFINED;
|
|
520
|
+
}
|
|
521
|
+
bool ok = JS_ToBool(ctx, ok_v) > 0;
|
|
522
|
+
JS_FreeValue(ctx, ok_v);
|
|
523
|
+
if (!ok) {
|
|
524
|
+
/* Early exit on an err item: close the iterator (AsyncIteratorClose
|
|
525
|
+
* parity — a foreign transport may release its slot in return()),
|
|
526
|
+
* then propagate the error Result verbatim. */
|
|
527
|
+
JSValue it = JS_GetPropertyStr(ctx, st, "it");
|
|
528
|
+
hc_iterator_close(ctx, it);
|
|
529
|
+
JS_FreeValue(ctx, it);
|
|
530
|
+
JSValue resolve = JS_GetPropertyStr(ctx, st, "res");
|
|
531
|
+
hc_settle(ctx, resolve, r);
|
|
532
|
+
JS_FreeValue(ctx, resolve);
|
|
533
|
+
return JS_UNDEFINED;
|
|
534
|
+
}
|
|
535
|
+
JSValue chunk = JS_GetPropertyStr(ctx, r, "value");
|
|
536
|
+
JS_FreeValue(ctx, r);
|
|
537
|
+
if (JS_IsException(chunk)) {
|
|
538
|
+
hc_drain_reject_exception(ctx, st);
|
|
539
|
+
return JS_UNDEFINED;
|
|
540
|
+
}
|
|
541
|
+
JSValue arr = JS_GetPropertyStr(ctx, st, "arr");
|
|
542
|
+
JSValue len_v = JS_GetPropertyStr(ctx, arr, "length");
|
|
543
|
+
uint32_t len = 0;
|
|
544
|
+
JS_ToUint32(ctx, &len, len_v);
|
|
545
|
+
JS_FreeValue(ctx, len_v);
|
|
546
|
+
JS_DefinePropertyValueUint32(ctx, arr, len, chunk, JS_PROP_C_W_E);
|
|
547
|
+
JS_FreeValue(ctx, arr);
|
|
548
|
+
|
|
549
|
+
hc_drain_step(ctx, st);
|
|
550
|
+
return JS_UNDEFINED;
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
JSValue hc_drain_on_error(JSContext* ctx, JSValue this_val, int argc, JSValue* argv, int magic,
|
|
554
|
+
JSValue* func_data) {
|
|
555
|
+
(void)this_val;
|
|
556
|
+
(void)magic;
|
|
557
|
+
JSValue st = func_data[0];
|
|
558
|
+
JSValue reject = JS_GetPropertyStr(ctx, st, "rej");
|
|
559
|
+
hc_settle(ctx, reject, JS_DupValue(ctx, argc > 0 ? argv[0] : JS_UNDEFINED));
|
|
560
|
+
JS_FreeValue(ctx, reject);
|
|
561
|
+
return JS_UNDEFINED;
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
void hc_drain_step(JSContext* ctx, JSValue st) {
|
|
565
|
+
JSValue it = JS_GetPropertyStr(ctx, st, "it");
|
|
566
|
+
JSValue item_p = hc_invoke(ctx, it, "next", 0, nullptr);
|
|
567
|
+
JS_FreeValue(ctx, it);
|
|
568
|
+
if (JS_IsException(item_p)) {
|
|
569
|
+
hc_drain_reject_exception(ctx, st);
|
|
570
|
+
return;
|
|
571
|
+
}
|
|
572
|
+
JSValue wrapped = hc_promise_resolve(ctx, item_p);
|
|
573
|
+
JSValue on_item = JS_NewCFunctionData(ctx, hc_drain_on_item, 1, 0, 1, &st);
|
|
574
|
+
JSValue on_err = JS_NewCFunctionData(ctx, hc_drain_on_error, 1, 0, 1, &st);
|
|
575
|
+
hc_then2(ctx, wrapped, on_item, on_err);
|
|
576
|
+
JS_FreeValue(ctx, wrapped);
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
/* Start draining `source` (borrowed); returns a promise for the Result. */
|
|
580
|
+
JSValue hc_drain(JSContext* ctx, JSValue source, int mode) {
|
|
581
|
+
JSValue it = hc_get_async_iterator(ctx, source);
|
|
582
|
+
if (JS_IsException(it)) return it;
|
|
583
|
+
|
|
584
|
+
JSValue rfuncs[2];
|
|
585
|
+
JSValue promise = JS_NewPromiseCapability(ctx, rfuncs);
|
|
586
|
+
if (JS_IsException(promise)) {
|
|
587
|
+
JS_FreeValue(ctx, it);
|
|
588
|
+
return promise;
|
|
589
|
+
}
|
|
590
|
+
JSValue st = JS_NewObjectProto(ctx, JS_NULL);
|
|
591
|
+
JS_SetPropertyStr(ctx, st, "it", it);
|
|
592
|
+
JS_SetPropertyStr(ctx, st, "arr", JS_NewArray(ctx));
|
|
593
|
+
JS_SetPropertyStr(ctx, st, "res", rfuncs[0]);
|
|
594
|
+
JS_SetPropertyStr(ctx, st, "rej", rfuncs[1]);
|
|
595
|
+
JS_SetPropertyStr(ctx, st, "mode", JS_NewInt32(ctx, mode));
|
|
596
|
+
hc_drain_step(ctx, st);
|
|
597
|
+
JS_FreeValue(ctx, st);
|
|
598
|
+
return promise;
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
/* ── makeResponse ───────────────────────────────────────────────────────── */
|
|
602
|
+
|
|
603
|
+
/* func_data layout for response methods: [0] = claim state, [1] = raw. */
|
|
604
|
+
|
|
605
|
+
bool hc_claim(JSContext* ctx, JSValue claim_st) {
|
|
606
|
+
if (hc_get_flag(ctx, claim_st, "consumed")) {
|
|
607
|
+
hc_throw_body_consumed(ctx);
|
|
608
|
+
return false;
|
|
609
|
+
}
|
|
610
|
+
hc_set_flag(ctx, claim_st, "consumed", true);
|
|
611
|
+
return true;
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
JSValue hc_resp_body_iterator_cf(JSContext* ctx, JSValue this_val, int argc, JSValue* argv,
|
|
615
|
+
int magic, JSValue* func_data) {
|
|
616
|
+
(void)this_val;
|
|
617
|
+
(void)argc;
|
|
618
|
+
(void)argv;
|
|
619
|
+
(void)magic;
|
|
620
|
+
if (!hc_claim(ctx, func_data[0])) return JS_EXCEPTION;
|
|
621
|
+
JSValue raw_body = JS_GetPropertyStr(ctx, func_data[1], "body");
|
|
622
|
+
JSValue it = hc_get_async_iterator(ctx, raw_body);
|
|
623
|
+
JS_FreeValue(ctx, raw_body);
|
|
624
|
+
return it;
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
JSValue hc_resp_header_get_cf(JSContext* ctx, JSValue this_val, int argc, JSValue* argv,
|
|
628
|
+
int magic, JSValue* func_data) {
|
|
629
|
+
(void)this_val;
|
|
630
|
+
bool want_all = magic == 1;
|
|
631
|
+
const char* name = JS_ToCString(ctx, argc > 0 ? argv[0] : JS_UNDEFINED);
|
|
632
|
+
if (!name) return JS_EXCEPTION;
|
|
633
|
+
std::string lower(name);
|
|
634
|
+
JS_FreeCString(ctx, name);
|
|
635
|
+
for (auto& c : lower) {
|
|
636
|
+
if (c >= 'A' && c <= 'Z') c += 32;
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
JSValue headers = JS_GetPropertyStr(ctx, func_data[1], "headers");
|
|
640
|
+
JSValue len_v = JS_GetPropertyStr(ctx, headers, "length");
|
|
641
|
+
uint32_t len = 0;
|
|
642
|
+
JS_ToUint32(ctx, &len, len_v);
|
|
643
|
+
JS_FreeValue(ctx, len_v);
|
|
644
|
+
|
|
645
|
+
JSValue out = want_all ? JS_NewArray(ctx) : JS_UNDEFINED;
|
|
646
|
+
uint32_t out_idx = 0;
|
|
647
|
+
for (uint32_t i = 0; i < len; i++) {
|
|
648
|
+
JSValue pair = JS_GetPropertyUint32(ctx, headers, i);
|
|
649
|
+
JSValue k = JS_GetPropertyUint32(ctx, pair, 0);
|
|
650
|
+
const char* ks = JS_ToCString(ctx, k);
|
|
651
|
+
JS_FreeValue(ctx, k);
|
|
652
|
+
if (!ks) {
|
|
653
|
+
JS_FreeValue(ctx, pair);
|
|
654
|
+
JS_FreeValue(ctx, headers);
|
|
655
|
+
if (want_all) JS_FreeValue(ctx, out);
|
|
656
|
+
return JS_EXCEPTION;
|
|
657
|
+
}
|
|
658
|
+
std::string klower(ks);
|
|
659
|
+
JS_FreeCString(ctx, ks);
|
|
660
|
+
for (auto& c : klower) {
|
|
661
|
+
if (c >= 'A' && c <= 'Z') c += 32;
|
|
662
|
+
}
|
|
663
|
+
if (klower == lower) {
|
|
664
|
+
JSValue v = JS_GetPropertyUint32(ctx, pair, 1);
|
|
665
|
+
if (want_all) {
|
|
666
|
+
JS_DefinePropertyValueUint32(ctx, out, out_idx++, v, JS_PROP_C_W_E);
|
|
667
|
+
} else {
|
|
668
|
+
JS_FreeValue(ctx, pair);
|
|
669
|
+
JS_FreeValue(ctx, headers);
|
|
670
|
+
return v;
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
JS_FreeValue(ctx, pair);
|
|
674
|
+
}
|
|
675
|
+
JS_FreeValue(ctx, headers);
|
|
676
|
+
return out;
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
JSValue hc_resp_drain_cf(JSContext* ctx, JSValue this_val, int argc, JSValue* argv, int magic,
|
|
680
|
+
JSValue* func_data) {
|
|
681
|
+
(void)this_val;
|
|
682
|
+
(void)argc;
|
|
683
|
+
(void)argv;
|
|
684
|
+
if (!hc_claim(ctx, func_data[0])) return hc_reject_pending_exception(ctx);
|
|
685
|
+
JSValue raw_body = JS_GetPropertyStr(ctx, func_data[1], "body");
|
|
686
|
+
JSValue p = hc_drain(ctx, raw_body, magic);
|
|
687
|
+
JS_FreeValue(ctx, raw_body);
|
|
688
|
+
if (JS_IsException(p)) return hc_reject_pending_exception(ctx);
|
|
689
|
+
return p;
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
JSValue hc_resp_close_on_done(JSContext* ctx, JSValue this_val, int argc, JSValue* argv,
|
|
693
|
+
int magic, JSValue* func_data) {
|
|
694
|
+
(void)this_val;
|
|
695
|
+
(void)argc;
|
|
696
|
+
(void)argv;
|
|
697
|
+
(void)magic;
|
|
698
|
+
hc_settle(ctx, func_data[0], JS_UNDEFINED);
|
|
699
|
+
return JS_UNDEFINED;
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
JSValue hc_resp_close_cf(JSContext* ctx, JSValue this_val, int argc, JSValue* argv, int magic,
|
|
703
|
+
JSValue* func_data) {
|
|
704
|
+
(void)this_val;
|
|
705
|
+
(void)argc;
|
|
706
|
+
(void)argv;
|
|
707
|
+
(void)magic;
|
|
708
|
+
hc_set_flag(ctx, func_data[0], "consumed", true);
|
|
709
|
+
JSValue raw_body = JS_GetPropertyStr(ctx, func_data[1], "body");
|
|
710
|
+
JSValue it = hc_get_async_iterator(ctx, raw_body);
|
|
711
|
+
JS_FreeValue(ctx, raw_body);
|
|
712
|
+
if (JS_IsException(it)) return hc_reject_pending_exception(ctx);
|
|
713
|
+
JSValue ret_fn = JS_GetPropertyStr(ctx, it, "return");
|
|
714
|
+
if (!JS_IsFunction(ctx, ret_fn)) {
|
|
715
|
+
JS_FreeValue(ctx, ret_fn);
|
|
716
|
+
JS_FreeValue(ctx, it);
|
|
717
|
+
JSValue undef = JS_UNDEFINED;
|
|
718
|
+
return MIK_NewResolvedPromise(ctx, 1, &undef);
|
|
719
|
+
}
|
|
720
|
+
JSValue r = JS_Call(ctx, ret_fn, it, 0, nullptr);
|
|
721
|
+
JS_FreeValue(ctx, ret_fn);
|
|
722
|
+
JS_FreeValue(ctx, it);
|
|
723
|
+
if (JS_IsException(r)) return hc_reject_pending_exception(ctx);
|
|
724
|
+
|
|
725
|
+
JSValue rfuncs[2];
|
|
726
|
+
JSValue promise = JS_NewPromiseCapability(ctx, rfuncs);
|
|
727
|
+
if (JS_IsException(promise)) {
|
|
728
|
+
JS_FreeValue(ctx, r);
|
|
729
|
+
return promise;
|
|
730
|
+
}
|
|
731
|
+
JSValue wrapped = hc_promise_resolve(ctx, r);
|
|
732
|
+
JSValue on_done = JS_NewCFunctionData(ctx, hc_resp_close_on_done, 1, 0, 1, &rfuncs[0]);
|
|
733
|
+
hc_then2(ctx, wrapped, on_done, JS_DupValue(ctx, rfuncs[1]));
|
|
734
|
+
JS_FreeValue(ctx, wrapped);
|
|
735
|
+
JS_FreeValue(ctx, rfuncs[0]);
|
|
736
|
+
JS_FreeValue(ctx, rfuncs[1]);
|
|
737
|
+
return promise;
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
/* Build the public Response object over `raw`. Borrows `raw`. */
|
|
741
|
+
JSValue hc_make_response(JSContext* ctx, JSValue raw) {
|
|
742
|
+
JSValue claim_st = JS_NewObjectProto(ctx, JS_NULL);
|
|
743
|
+
hc_set_flag(ctx, claim_st, "consumed", false);
|
|
744
|
+
JSValue data[2] = {claim_st, raw};
|
|
745
|
+
|
|
746
|
+
JSValue resp = JS_NewObject(ctx);
|
|
747
|
+
static const char* const kCopied[] = {"status", "statusText", "url", "redirected", "headers"};
|
|
748
|
+
for (const char* prop : kCopied) {
|
|
749
|
+
JS_DefinePropertyValueStr(ctx, resp, prop, JS_GetPropertyStr(ctx, raw, prop),
|
|
750
|
+
JS_PROP_C_W_E);
|
|
751
|
+
}
|
|
752
|
+
JSValue status_v = JS_GetPropertyStr(ctx, raw, "status");
|
|
753
|
+
int32_t status = 0;
|
|
754
|
+
JS_ToInt32(ctx, &status, status_v);
|
|
755
|
+
JS_FreeValue(ctx, status_v);
|
|
756
|
+
JS_DefinePropertyValueStr(ctx, resp, "ok", JS_NewBool(ctx, status >= 200 && status < 300),
|
|
757
|
+
JS_PROP_C_W_E);
|
|
758
|
+
|
|
759
|
+
JSValue body = JS_NewObject(ctx);
|
|
760
|
+
JSAtom iter_atom = hc_async_iterator_atom(ctx);
|
|
761
|
+
JS_DefinePropertyValue(ctx, body, iter_atom,
|
|
762
|
+
JS_NewCFunctionData(ctx, hc_resp_body_iterator_cf, 0, 0, 2, data),
|
|
763
|
+
JS_PROP_C_W_E);
|
|
764
|
+
JS_FreeAtom(ctx, iter_atom);
|
|
765
|
+
JS_DefinePropertyValueStr(ctx, resp, "body", body, JS_PROP_C_W_E);
|
|
766
|
+
|
|
767
|
+
JS_DefinePropertyValueStr(ctx, resp, "get",
|
|
768
|
+
JS_NewCFunctionData(ctx, hc_resp_header_get_cf, 1, 0, 2, data),
|
|
769
|
+
JS_PROP_C_W_E);
|
|
770
|
+
JS_DefinePropertyValueStr(ctx, resp, "getAll",
|
|
771
|
+
JS_NewCFunctionData(ctx, hc_resp_header_get_cf, 1, 1, 2, data),
|
|
772
|
+
JS_PROP_C_W_E);
|
|
773
|
+
JS_DefinePropertyValueStr(ctx, resp, "text",
|
|
774
|
+
JS_NewCFunctionData(ctx, hc_resp_drain_cf, 0, DRAIN_TEXT, 2, data),
|
|
775
|
+
JS_PROP_C_W_E);
|
|
776
|
+
JS_DefinePropertyValueStr(ctx, resp, "json",
|
|
777
|
+
JS_NewCFunctionData(ctx, hc_resp_drain_cf, 0, DRAIN_JSON, 2, data),
|
|
778
|
+
JS_PROP_C_W_E);
|
|
779
|
+
JS_DefinePropertyValueStr(ctx, resp, "bytes",
|
|
780
|
+
JS_NewCFunctionData(ctx, hc_resp_drain_cf, 0, DRAIN_BYTES, 2, data),
|
|
781
|
+
JS_PROP_C_W_E);
|
|
782
|
+
JS_DefinePropertyValueStr(ctx, resp, "close",
|
|
783
|
+
JS_NewCFunctionData(ctx, hc_resp_close_cf, 0, 0, 2, data),
|
|
784
|
+
JS_PROP_C_W_E);
|
|
785
|
+
JS_FreeValue(ctx, claim_st);
|
|
786
|
+
return resp;
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
JSValue hc_make_response_cf(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) {
|
|
790
|
+
(void)this_val;
|
|
791
|
+
MIK_CHECK_ARG_RET(ctx, argc > 0 && JS_IsObject(argv[0]), 0, "a raw response object");
|
|
792
|
+
return hc_make_response(ctx, argv[0]);
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
/* ── Transport-backed request() ─────────────────────────────────────────── */
|
|
796
|
+
|
|
797
|
+
/* Request state object props:
|
|
798
|
+
* ns (transport namespace), id, url, sig (AbortSignal | absent),
|
|
799
|
+
* hnd (cancel callback | absent), tmr (timer id | absent),
|
|
800
|
+
* done, clean (booleans). */
|
|
801
|
+
|
|
802
|
+
void hc_req_cleanup(JSContext* ctx, JSValue st) {
|
|
803
|
+
if (hc_get_flag(ctx, st, "clean")) return;
|
|
804
|
+
hc_set_flag(ctx, st, "clean", true);
|
|
805
|
+
JSValue sig = JS_GetPropertyStr(ctx, st, "sig");
|
|
806
|
+
JSValue hnd = JS_GetPropertyStr(ctx, st, "hnd");
|
|
807
|
+
if (JS_IsObject(sig) && JS_IsFunction(ctx, hnd)) {
|
|
808
|
+
JSValue args[2] = {JS_NewString(ctx, "abort"), hnd};
|
|
809
|
+
JSValue r = hc_invoke(ctx, sig, "removeEventListener", 2, args);
|
|
810
|
+
JS_FreeValue(ctx, r);
|
|
811
|
+
JS_FreeValue(ctx, args[0]);
|
|
812
|
+
}
|
|
813
|
+
JS_FreeValue(ctx, sig);
|
|
814
|
+
JS_FreeValue(ctx, hnd);
|
|
815
|
+
JSValue tmr = JS_GetPropertyStr(ctx, st, "tmr");
|
|
816
|
+
if (!JS_IsUndefined(tmr)) {
|
|
817
|
+
int64_t timer_id = 0;
|
|
818
|
+
JS_ToInt64(ctx, &timer_id, tmr);
|
|
819
|
+
MIKRuntime* mik_rt = MIK_GetRuntime(ctx);
|
|
820
|
+
if (mik_rt->timers) {
|
|
821
|
+
MIK_Timer_UnSchedule(mik_rt->timers, ctx, static_cast<uint32_t>(timer_id));
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
JS_FreeValue(ctx, tmr);
|
|
825
|
+
/* Break the st <-> cancel-handler reference cycle so refcounting alone
|
|
826
|
+
* reclaims the request state (the handler's func_data holds st). */
|
|
827
|
+
JS_SetPropertyStr(ctx, st, "hnd", JS_UNDEFINED);
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
/* ns.cancel(id) — used as both the abort listener and the timeout callback. */
|
|
831
|
+
JSValue hc_req_cancel_cf(JSContext* ctx, JSValue this_val, int argc, JSValue* argv, int magic,
|
|
832
|
+
JSValue* func_data) {
|
|
833
|
+
(void)this_val;
|
|
834
|
+
(void)argc;
|
|
835
|
+
(void)argv;
|
|
836
|
+
(void)magic;
|
|
837
|
+
JSValue st = func_data[0];
|
|
838
|
+
JSValue ns = JS_GetPropertyStr(ctx, st, "ns");
|
|
839
|
+
JSValue id = JS_GetPropertyStr(ctx, st, "id");
|
|
840
|
+
JSValue r = hc_invoke(ctx, ns, "cancel", 1, &id);
|
|
841
|
+
JS_FreeValue(ctx, r);
|
|
842
|
+
JS_FreeValue(ctx, id);
|
|
843
|
+
JS_FreeValue(ctx, ns);
|
|
844
|
+
return JS_UNDEFINED;
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
/* ns.nextMessage(id); returns the message promise (or sync exception). */
|
|
848
|
+
JSValue hc_call_next_message(JSContext* ctx, JSValue st) {
|
|
849
|
+
JSValue ns = JS_GetPropertyStr(ctx, st, "ns");
|
|
850
|
+
JSValue id = JS_GetPropertyStr(ctx, st, "id");
|
|
851
|
+
JSValue p = hc_invoke(ctx, ns, "nextMessage", 1, &id);
|
|
852
|
+
JS_FreeValue(ctx, id);
|
|
853
|
+
JS_FreeValue(ctx, ns);
|
|
854
|
+
return p;
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
/* .then handlers for one body next() call. func_data: [0]=state, [1]=resolve. */
|
|
858
|
+
JSValue hc_body_on_msg(JSContext* ctx, JSValue this_val, int argc, JSValue* argv, int magic,
|
|
859
|
+
JSValue* func_data) {
|
|
860
|
+
(void)this_val;
|
|
861
|
+
(void)magic;
|
|
862
|
+
JSValue st = func_data[0];
|
|
863
|
+
JSValue resolve = func_data[1];
|
|
864
|
+
JSValue msg = argc > 0 ? argv[0] : JS_UNDEFINED;
|
|
865
|
+
|
|
866
|
+
JSValue kind_v = JS_GetPropertyStr(ctx, msg, "kind");
|
|
867
|
+
const char* kind = JS_ToCString(ctx, kind_v);
|
|
868
|
+
std::string k(kind ? kind : "");
|
|
869
|
+
JS_FreeCString(ctx, kind);
|
|
870
|
+
JS_FreeValue(ctx, kind_v);
|
|
871
|
+
|
|
872
|
+
if (k == "chunk") {
|
|
873
|
+
JSValue data = JS_GetPropertyStr(ctx, msg, "data");
|
|
874
|
+
hc_settle(ctx, resolve, hc_iter_result(ctx, false, mik__result_ok(ctx, data)));
|
|
875
|
+
return JS_UNDEFINED;
|
|
876
|
+
}
|
|
877
|
+
hc_set_flag(ctx, st, "done", true);
|
|
878
|
+
hc_req_cleanup(ctx, st);
|
|
879
|
+
if (k == "end") {
|
|
880
|
+
hc_settle(ctx, resolve, hc_iter_result(ctx, true, JS_UNDEFINED));
|
|
881
|
+
return JS_UNDEFINED;
|
|
882
|
+
}
|
|
883
|
+
JSValue cancelled_v = JS_GetPropertyStr(ctx, msg, "cancelled");
|
|
884
|
+
bool cancelled = JS_ToBool(ctx, cancelled_v) > 0;
|
|
885
|
+
JS_FreeValue(ctx, cancelled_v);
|
|
886
|
+
JSValue err;
|
|
887
|
+
if (cancelled) {
|
|
888
|
+
JSValue sig = JS_GetPropertyStr(ctx, st, "sig");
|
|
889
|
+
std::string fallback = hc_abort_reason(ctx, sig);
|
|
890
|
+
JS_FreeValue(ctx, sig);
|
|
891
|
+
std::string m = hc_message_or(ctx, msg, fallback);
|
|
892
|
+
err = mik__result_err_named(ctx, "Aborted", "%s", m.c_str());
|
|
893
|
+
} else {
|
|
894
|
+
std::string m = hc_message_or(ctx, msg, "HTTP request failed");
|
|
895
|
+
err = mik__result_err_named(ctx, "Network", "%s", m.c_str());
|
|
896
|
+
}
|
|
897
|
+
hc_settle(ctx, resolve, hc_iter_result(ctx, false, err));
|
|
898
|
+
return JS_UNDEFINED;
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
JSValue hc_body_on_msg_err(JSContext* ctx, JSValue this_val, int argc, JSValue* argv, int magic,
|
|
902
|
+
JSValue* func_data) {
|
|
903
|
+
(void)this_val;
|
|
904
|
+
(void)magic;
|
|
905
|
+
JSValue st = func_data[0];
|
|
906
|
+
JSValue resolve = func_data[1];
|
|
907
|
+
hc_set_flag(ctx, st, "done", true);
|
|
908
|
+
hc_req_cleanup(ctx, st);
|
|
909
|
+
std::string m = hc_error_message(ctx, argc > 0 ? argv[0] : JS_UNDEFINED);
|
|
910
|
+
JSValue err = mik__result_err_named(ctx, "Network", "%s", m.c_str());
|
|
911
|
+
hc_settle(ctx, resolve, hc_iter_result(ctx, false, err));
|
|
912
|
+
return JS_UNDEFINED;
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
JSValue hc_body_next_cf(JSContext* ctx, JSValue this_val, int argc, JSValue* argv, int magic,
|
|
916
|
+
JSValue* func_data) {
|
|
917
|
+
(void)this_val;
|
|
918
|
+
(void)argc;
|
|
919
|
+
(void)argv;
|
|
920
|
+
(void)magic;
|
|
921
|
+
JSValue st = func_data[0];
|
|
922
|
+
if (hc_get_flag(ctx, st, "done")) {
|
|
923
|
+
JSValue done = hc_iter_result(ctx, true, JS_UNDEFINED);
|
|
924
|
+
return MIK_NewResolvedPromise(ctx, 1, &done);
|
|
925
|
+
}
|
|
926
|
+
JSValue msg_p = hc_call_next_message(ctx, st);
|
|
927
|
+
if (JS_IsException(msg_p)) {
|
|
928
|
+
/* Sync throw from the transport maps to a Network err item. */
|
|
929
|
+
hc_set_flag(ctx, st, "done", true);
|
|
930
|
+
hc_req_cleanup(ctx, st);
|
|
931
|
+
JSValue exc = JS_GetException(ctx);
|
|
932
|
+
std::string m = hc_error_message(ctx, exc);
|
|
933
|
+
JS_FreeValue(ctx, exc);
|
|
934
|
+
JSValue item =
|
|
935
|
+
hc_iter_result(ctx, false, mik__result_err_named(ctx, "Network", "%s", m.c_str()));
|
|
936
|
+
return MIK_NewResolvedPromise(ctx, 1, &item);
|
|
937
|
+
}
|
|
938
|
+
JSValue rfuncs[2];
|
|
939
|
+
JSValue promise = JS_NewPromiseCapability(ctx, rfuncs);
|
|
940
|
+
if (JS_IsException(promise)) {
|
|
941
|
+
JS_FreeValue(ctx, msg_p);
|
|
942
|
+
return promise;
|
|
943
|
+
}
|
|
944
|
+
JSValue data[2] = {st, rfuncs[0]};
|
|
945
|
+
JSValue on_msg = JS_NewCFunctionData(ctx, hc_body_on_msg, 1, 0, 2, data);
|
|
946
|
+
JSValue on_err = JS_NewCFunctionData(ctx, hc_body_on_msg_err, 1, 0, 2, data);
|
|
947
|
+
JS_FreeValue(ctx, rfuncs[0]);
|
|
948
|
+
JS_FreeValue(ctx, rfuncs[1]);
|
|
949
|
+
JSValue wrapped = hc_promise_resolve(ctx, msg_p);
|
|
950
|
+
hc_then2(ctx, wrapped, on_msg, on_err);
|
|
951
|
+
JS_FreeValue(ctx, wrapped);
|
|
952
|
+
return promise;
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
/* return(): cancel, then drain messages until a non-chunk arrives.
|
|
956
|
+
* hc_return_drain_step consumes `resolve`. */
|
|
957
|
+
void hc_return_drain_step(JSContext* ctx, JSValue st, JSValue resolve);
|
|
958
|
+
|
|
959
|
+
JSValue hc_return_on_msg(JSContext* ctx, JSValue this_val, int argc, JSValue* argv, int magic,
|
|
960
|
+
JSValue* func_data) {
|
|
961
|
+
(void)this_val;
|
|
962
|
+
(void)magic;
|
|
963
|
+
JSValue st = func_data[0];
|
|
964
|
+
JSValue resolve = func_data[1];
|
|
965
|
+
JSValue kind_v = JS_GetPropertyStr(ctx, argc > 0 ? argv[0] : JS_UNDEFINED, "kind");
|
|
966
|
+
const char* kind = JS_ToCString(ctx, kind_v);
|
|
967
|
+
bool is_chunk = kind && strcmp(kind, "chunk") == 0;
|
|
968
|
+
JS_FreeCString(ctx, kind);
|
|
969
|
+
JS_FreeValue(ctx, kind_v);
|
|
970
|
+
if (is_chunk) {
|
|
971
|
+
hc_return_drain_step(ctx, st, JS_DupValue(ctx, resolve));
|
|
972
|
+
} else {
|
|
973
|
+
hc_settle(ctx, resolve, hc_iter_result(ctx, true, JS_UNDEFINED));
|
|
974
|
+
}
|
|
975
|
+
return JS_UNDEFINED;
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
JSValue hc_return_on_err(JSContext* ctx, JSValue this_val, int argc, JSValue* argv, int magic,
|
|
979
|
+
JSValue* func_data) {
|
|
980
|
+
(void)this_val;
|
|
981
|
+
(void)argc;
|
|
982
|
+
(void)argv;
|
|
983
|
+
(void)magic;
|
|
984
|
+
hc_settle(ctx, func_data[1], hc_iter_result(ctx, true, JS_UNDEFINED));
|
|
985
|
+
return JS_UNDEFINED;
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
void hc_return_drain_step(JSContext* ctx, JSValue st, JSValue resolve) {
|
|
989
|
+
JSValue msg_p = hc_call_next_message(ctx, st);
|
|
990
|
+
if (JS_IsException(msg_p)) {
|
|
991
|
+
JSValue exc = JS_GetException(ctx);
|
|
992
|
+
JS_FreeValue(ctx, exc);
|
|
993
|
+
hc_settle(ctx, resolve, hc_iter_result(ctx, true, JS_UNDEFINED));
|
|
994
|
+
JS_FreeValue(ctx, resolve);
|
|
995
|
+
return;
|
|
996
|
+
}
|
|
997
|
+
JSValue data[2] = {st, resolve};
|
|
998
|
+
JSValue on_msg = JS_NewCFunctionData(ctx, hc_return_on_msg, 1, 0, 2, data);
|
|
999
|
+
JSValue on_err = JS_NewCFunctionData(ctx, hc_return_on_err, 1, 0, 2, data);
|
|
1000
|
+
JS_FreeValue(ctx, resolve);
|
|
1001
|
+
JSValue wrapped = hc_promise_resolve(ctx, msg_p);
|
|
1002
|
+
hc_then2(ctx, wrapped, on_msg, on_err);
|
|
1003
|
+
JS_FreeValue(ctx, wrapped);
|
|
1004
|
+
}
|
|
1005
|
+
|
|
1006
|
+
JSValue hc_body_return_cf(JSContext* ctx, JSValue this_val, int argc, JSValue* argv, int magic,
|
|
1007
|
+
JSValue* func_data) {
|
|
1008
|
+
(void)this_val;
|
|
1009
|
+
(void)argc;
|
|
1010
|
+
(void)argv;
|
|
1011
|
+
(void)magic;
|
|
1012
|
+
JSValue st = func_data[0];
|
|
1013
|
+
if (hc_get_flag(ctx, st, "done")) {
|
|
1014
|
+
JSValue done = hc_iter_result(ctx, true, JS_UNDEFINED);
|
|
1015
|
+
return MIK_NewResolvedPromise(ctx, 1, &done);
|
|
1016
|
+
}
|
|
1017
|
+
hc_set_flag(ctx, st, "done", true);
|
|
1018
|
+
hc_req_cleanup(ctx, st);
|
|
1019
|
+
JSValue ns = JS_GetPropertyStr(ctx, st, "ns");
|
|
1020
|
+
JSValue id = JS_GetPropertyStr(ctx, st, "id");
|
|
1021
|
+
JSValue r = hc_invoke(ctx, ns, "cancel", 1, &id);
|
|
1022
|
+
JS_FreeValue(ctx, id);
|
|
1023
|
+
JS_FreeValue(ctx, ns);
|
|
1024
|
+
if (JS_IsException(r)) return r;
|
|
1025
|
+
JS_FreeValue(ctx, r);
|
|
1026
|
+
|
|
1027
|
+
JSValue rfuncs[2];
|
|
1028
|
+
JSValue promise = JS_NewPromiseCapability(ctx, rfuncs);
|
|
1029
|
+
if (JS_IsException(promise)) return promise;
|
|
1030
|
+
JS_FreeValue(ctx, rfuncs[1]);
|
|
1031
|
+
hc_return_drain_step(ctx, st, rfuncs[0]);
|
|
1032
|
+
return promise;
|
|
1033
|
+
}
|
|
1034
|
+
|
|
1035
|
+
/* Iterable body object for the transport: fresh iterators share the request
|
|
1036
|
+
* state, matching the TS closure semantics. */
|
|
1037
|
+
JSValue hc_transport_iterator_cf(JSContext* ctx, JSValue this_val, int argc, JSValue* argv,
|
|
1038
|
+
int magic, JSValue* func_data) {
|
|
1039
|
+
(void)this_val;
|
|
1040
|
+
(void)argc;
|
|
1041
|
+
(void)argv;
|
|
1042
|
+
(void)magic;
|
|
1043
|
+
JSValue it = JS_NewObject(ctx);
|
|
1044
|
+
JS_DefinePropertyValueStr(ctx, it, "next",
|
|
1045
|
+
JS_NewCFunctionData(ctx, hc_body_next_cf, 0, 0, 1, func_data),
|
|
1046
|
+
JS_PROP_C_W_E);
|
|
1047
|
+
JS_DefinePropertyValueStr(ctx, it, "return",
|
|
1048
|
+
JS_NewCFunctionData(ctx, hc_body_return_cf, 0, 0, 1, func_data),
|
|
1049
|
+
JS_PROP_C_W_E);
|
|
1050
|
+
return it;
|
|
1051
|
+
}
|
|
1052
|
+
|
|
1053
|
+
/* start.headers settle handlers. func_data: [0]=state, [1]=resolve, [2]=reject. */
|
|
1054
|
+
JSValue hc_on_headers(JSContext* ctx, JSValue this_val, int argc, JSValue* argv, int magic,
|
|
1055
|
+
JSValue* func_data) {
|
|
1056
|
+
(void)this_val;
|
|
1057
|
+
(void)magic;
|
|
1058
|
+
JSValue st = func_data[0];
|
|
1059
|
+
JSValue resolve = func_data[1];
|
|
1060
|
+
JSValue hr = argc > 0 ? argv[0] : JS_UNDEFINED;
|
|
1061
|
+
|
|
1062
|
+
JSValue ok_v = JS_GetPropertyStr(ctx, hr, "ok");
|
|
1063
|
+
if (JS_IsException(ok_v)) {
|
|
1064
|
+
/* A throwing getter on a broken transport rejects instead of leaving
|
|
1065
|
+
* the exception armed past this callback. */
|
|
1066
|
+
hc_req_cleanup(ctx, st);
|
|
1067
|
+
hc_settle(ctx, func_data[2], JS_GetException(ctx));
|
|
1068
|
+
return JS_UNDEFINED;
|
|
1069
|
+
}
|
|
1070
|
+
bool ok = JS_ToBool(ctx, ok_v) > 0;
|
|
1071
|
+
JS_FreeValue(ctx, ok_v);
|
|
1072
|
+
if (!ok) {
|
|
1073
|
+
hc_req_cleanup(ctx, st);
|
|
1074
|
+
hc_settle(ctx, resolve, JS_DupValue(ctx, hr));
|
|
1075
|
+
return JS_UNDEFINED;
|
|
1076
|
+
}
|
|
1077
|
+
|
|
1078
|
+
JSValue hv = JS_GetPropertyStr(ctx, hr, "value");
|
|
1079
|
+
if (JS_IsException(hv)) {
|
|
1080
|
+
hc_req_cleanup(ctx, st);
|
|
1081
|
+
hc_settle(ctx, func_data[2], JS_GetException(ctx));
|
|
1082
|
+
return JS_UNDEFINED;
|
|
1083
|
+
}
|
|
1084
|
+
JSValue status = JS_GetPropertyStr(ctx, hv, "status");
|
|
1085
|
+
JSValue resp_headers = JS_GetPropertyStr(ctx, hv, "headers");
|
|
1086
|
+
JS_FreeValue(ctx, hv);
|
|
1087
|
+
if (JS_IsException(status) || JS_IsException(resp_headers)) {
|
|
1088
|
+
JS_FreeValue(ctx, status);
|
|
1089
|
+
JS_FreeValue(ctx, resp_headers);
|
|
1090
|
+
hc_req_cleanup(ctx, st);
|
|
1091
|
+
hc_settle(ctx, func_data[2], JS_GetException(ctx));
|
|
1092
|
+
return JS_UNDEFINED;
|
|
1093
|
+
}
|
|
1094
|
+
|
|
1095
|
+
JSValue raw_body = JS_NewObject(ctx);
|
|
1096
|
+
JSAtom iter_atom = hc_async_iterator_atom(ctx);
|
|
1097
|
+
JS_DefinePropertyValue(ctx, raw_body, iter_atom,
|
|
1098
|
+
JS_NewCFunctionData(ctx, hc_transport_iterator_cf, 0, 0, 1, &st),
|
|
1099
|
+
JS_PROP_C_W_E);
|
|
1100
|
+
JS_FreeAtom(ctx, iter_atom);
|
|
1101
|
+
|
|
1102
|
+
JSValue raw = JS_NewObject(ctx);
|
|
1103
|
+
JS_DefinePropertyValueStr(ctx, raw, "status", status, JS_PROP_C_W_E);
|
|
1104
|
+
JS_DefinePropertyValueStr(ctx, raw, "statusText", JS_NewString(ctx, ""), JS_PROP_C_W_E);
|
|
1105
|
+
JS_DefinePropertyValueStr(ctx, raw, "url", JS_GetPropertyStr(ctx, st, "url"), JS_PROP_C_W_E);
|
|
1106
|
+
JS_DefinePropertyValueStr(ctx, raw, "redirected", JS_FALSE, JS_PROP_C_W_E);
|
|
1107
|
+
JS_DefinePropertyValueStr(ctx, raw, "headers", resp_headers, JS_PROP_C_W_E);
|
|
1108
|
+
JS_DefinePropertyValueStr(ctx, raw, "body", raw_body, JS_PROP_C_W_E);
|
|
1109
|
+
|
|
1110
|
+
JSValue resp = hc_make_response(ctx, raw);
|
|
1111
|
+
JS_FreeValue(ctx, raw);
|
|
1112
|
+
hc_settle(ctx, resolve, mik__result_ok(ctx, resp));
|
|
1113
|
+
return JS_UNDEFINED;
|
|
1114
|
+
}
|
|
1115
|
+
|
|
1116
|
+
JSValue hc_on_headers_rejected(JSContext* ctx, JSValue this_val, int argc, JSValue* argv,
|
|
1117
|
+
int magic, JSValue* func_data) {
|
|
1118
|
+
(void)this_val;
|
|
1119
|
+
(void)magic;
|
|
1120
|
+
hc_req_cleanup(ctx, func_data[0]);
|
|
1121
|
+
hc_settle(ctx, func_data[2], JS_DupValue(ctx, argc > 0 ? argv[0] : JS_UNDEFINED));
|
|
1122
|
+
return JS_UNDEFINED;
|
|
1123
|
+
}
|
|
1124
|
+
|
|
1125
|
+
/* request(url, options?) — func_data: [0]=transport namespace. */
|
|
1126
|
+
JSValue hc_request_cf(JSContext* ctx, JSValue this_val, int argc, JSValue* argv, int magic,
|
|
1127
|
+
JSValue* func_data) {
|
|
1128
|
+
(void)this_val;
|
|
1129
|
+
(void)magic;
|
|
1130
|
+
JSValue ns = func_data[0];
|
|
1131
|
+
JSValue url = argc > 0 ? argv[0] : JS_UNDEFINED;
|
|
1132
|
+
JSValue options = argc > 1 && JS_IsObject(argv[1]) ? argv[1] : JS_UNDEFINED;
|
|
1133
|
+
|
|
1134
|
+
/* Pre-aborted signal short-circuits without touching the transport. */
|
|
1135
|
+
JSValue signal =
|
|
1136
|
+
JS_IsObject(options) ? JS_GetPropertyStr(ctx, options, "signal") : JS_UNDEFINED;
|
|
1137
|
+
if (JS_IsException(signal)) {
|
|
1138
|
+
return hc_reject_pending_exception(ctx);
|
|
1139
|
+
}
|
|
1140
|
+
if (JS_IsObject(signal)) {
|
|
1141
|
+
JSValue aborted = JS_GetPropertyStr(ctx, signal, "aborted");
|
|
1142
|
+
bool is_aborted = JS_ToBool(ctx, aborted) > 0;
|
|
1143
|
+
JS_FreeValue(ctx, aborted);
|
|
1144
|
+
if (is_aborted) {
|
|
1145
|
+
std::string reason = hc_abort_reason(ctx, signal);
|
|
1146
|
+
JS_FreeValue(ctx, signal);
|
|
1147
|
+
JSValue err = mik__result_err_named(ctx, "Aborted", "%s", reason.c_str());
|
|
1148
|
+
return MIK_NewResolvedPromise(ctx, 1, &err);
|
|
1149
|
+
}
|
|
1150
|
+
}
|
|
1151
|
+
|
|
1152
|
+
JSValue prepared = hc_prepare_body(ctx, options);
|
|
1153
|
+
if (JS_IsException(prepared)) {
|
|
1154
|
+
/* Getter/iterator throws from caller options reject (async parity). */
|
|
1155
|
+
JS_FreeValue(ctx, signal);
|
|
1156
|
+
return hc_reject_pending_exception(ctx);
|
|
1157
|
+
}
|
|
1158
|
+
JSValue body = JS_GetPropertyStr(ctx, prepared, "body");
|
|
1159
|
+
JSValue headers = JS_GetPropertyStr(ctx, prepared, "headers");
|
|
1160
|
+
JS_FreeValue(ctx, prepared);
|
|
1161
|
+
|
|
1162
|
+
JSValue method_in =
|
|
1163
|
+
JS_IsObject(options) ? JS_GetPropertyStr(ctx, options, "method") : JS_UNDEFINED;
|
|
1164
|
+
JSValue method;
|
|
1165
|
+
if (JS_IsUndefined(method_in) || JS_IsNull(method_in)) {
|
|
1166
|
+
JS_FreeValue(ctx, method_in);
|
|
1167
|
+
method = JS_NewString(ctx, "GET");
|
|
1168
|
+
} else {
|
|
1169
|
+
method = hc_invoke(ctx, method_in, "toUpperCase", 0, nullptr);
|
|
1170
|
+
JS_FreeValue(ctx, method_in);
|
|
1171
|
+
if (JS_IsException(method)) {
|
|
1172
|
+
JS_FreeValue(ctx, body);
|
|
1173
|
+
JS_FreeValue(ctx, headers);
|
|
1174
|
+
JS_FreeValue(ctx, signal);
|
|
1175
|
+
JSValue exc = JS_GetException(ctx);
|
|
1176
|
+
return MIK_NewRejectedPromise(ctx, 1, &exc);
|
|
1177
|
+
}
|
|
1178
|
+
}
|
|
1179
|
+
|
|
1180
|
+
JSValue transport_opts = JS_NewObject(ctx);
|
|
1181
|
+
JS_DefinePropertyValueStr(ctx, transport_opts, "method", method, JS_PROP_C_W_E);
|
|
1182
|
+
if (!JS_IsNull(body)) {
|
|
1183
|
+
JS_DefinePropertyValueStr(ctx, transport_opts, "body", body, JS_PROP_C_W_E);
|
|
1184
|
+
} else {
|
|
1185
|
+
JS_FreeValue(ctx, body);
|
|
1186
|
+
}
|
|
1187
|
+
JS_DefinePropertyValueStr(ctx, transport_opts, "headers", headers, JS_PROP_C_W_E);
|
|
1188
|
+
|
|
1189
|
+
JSValue req_args[2] = {url, transport_opts};
|
|
1190
|
+
JSValue start = hc_invoke(ctx, ns, "request", 2, req_args);
|
|
1191
|
+
JS_FreeValue(ctx, transport_opts);
|
|
1192
|
+
if (JS_IsException(start)) {
|
|
1193
|
+
JS_FreeValue(ctx, signal);
|
|
1194
|
+
JSValue exc = JS_GetException(ctx);
|
|
1195
|
+
return MIK_NewRejectedPromise(ctx, 1, &exc);
|
|
1196
|
+
}
|
|
1197
|
+
|
|
1198
|
+
JSValue start_ok = JS_GetPropertyStr(ctx, start, "ok");
|
|
1199
|
+
bool ok = JS_ToBool(ctx, start_ok) > 0;
|
|
1200
|
+
JS_FreeValue(ctx, start_ok);
|
|
1201
|
+
if (!ok) {
|
|
1202
|
+
JS_FreeValue(ctx, signal);
|
|
1203
|
+
return MIK_NewResolvedPromise(ctx, 1, &start);
|
|
1204
|
+
}
|
|
1205
|
+
|
|
1206
|
+
JSValue st = JS_NewObjectProto(ctx, JS_NULL);
|
|
1207
|
+
JS_SetPropertyStr(ctx, st, "ns", JS_DupValue(ctx, ns));
|
|
1208
|
+
JS_SetPropertyStr(ctx, st, "id", JS_GetPropertyStr(ctx, start, "id"));
|
|
1209
|
+
JS_SetPropertyStr(ctx, st, "url", JS_DupValue(ctx, url));
|
|
1210
|
+
hc_set_flag(ctx, st, "done", false);
|
|
1211
|
+
hc_set_flag(ctx, st, "clean", false);
|
|
1212
|
+
|
|
1213
|
+
JSValue timeout_v =
|
|
1214
|
+
JS_IsObject(options) ? JS_GetPropertyStr(ctx, options, "timeoutMs") : JS_UNDEFINED;
|
|
1215
|
+
if (JS_IsException(timeout_v)) {
|
|
1216
|
+
/* TS parity: a throwing timeoutMs getter rejects the request; the
|
|
1217
|
+
* already-started transport request is not cancelled, as in TS. */
|
|
1218
|
+
JS_FreeValue(ctx, signal);
|
|
1219
|
+
JS_FreeValue(ctx, start);
|
|
1220
|
+
JS_FreeValue(ctx, st);
|
|
1221
|
+
return hc_reject_pending_exception(ctx);
|
|
1222
|
+
}
|
|
1223
|
+
/* The plain request(url) fast path never allocates the cancel closure. */
|
|
1224
|
+
if (!JS_IsUndefined(timeout_v) || JS_IsObject(signal)) {
|
|
1225
|
+
JSValue cancel_fn = JS_NewCFunctionData(ctx, hc_req_cancel_cf, 0, 0, 1, &st);
|
|
1226
|
+
if (!JS_IsUndefined(timeout_v)) {
|
|
1227
|
+
int64_t timeout_ms = 0;
|
|
1228
|
+
if (JS_ToInt64(ctx, &timeout_ms, timeout_v) < 0) {
|
|
1229
|
+
JS_FreeValue(ctx, cancel_fn);
|
|
1230
|
+
JS_FreeValue(ctx, timeout_v);
|
|
1231
|
+
JS_FreeValue(ctx, signal);
|
|
1232
|
+
JS_FreeValue(ctx, start);
|
|
1233
|
+
JS_FreeValue(ctx, st);
|
|
1234
|
+
return hc_reject_pending_exception(ctx);
|
|
1235
|
+
}
|
|
1236
|
+
MIKRuntime* mik_rt = MIK_GetRuntime(ctx);
|
|
1237
|
+
/* Null during teardown only; unreachable from the loop, guarded
|
|
1238
|
+
* for consistency with wc_connect_on_result. */
|
|
1239
|
+
if (mik_rt->timers) {
|
|
1240
|
+
uint32_t timer_id = MIK_Timer_Schedule(mik_rt->timers, ctx, cancel_fn, 0, nullptr,
|
|
1241
|
+
timeout_ms * 1000, false,
|
|
1242
|
+
MIK_GetPlatform()->get_boot_us());
|
|
1243
|
+
JS_SetPropertyStr(ctx, st, "tmr", JS_NewInt64(ctx, timer_id));
|
|
1244
|
+
}
|
|
1245
|
+
}
|
|
1246
|
+
if (JS_IsObject(signal)) {
|
|
1247
|
+
JS_SetPropertyStr(ctx, st, "sig", JS_DupValue(ctx, signal));
|
|
1248
|
+
JS_SetPropertyStr(ctx, st, "hnd", JS_DupValue(ctx, cancel_fn));
|
|
1249
|
+
JSValue add_args[2] = {JS_NewString(ctx, "abort"), cancel_fn};
|
|
1250
|
+
JSValue r = hc_invoke(ctx, signal, "addEventListener", 2, add_args);
|
|
1251
|
+
JS_FreeValue(ctx, add_args[0]);
|
|
1252
|
+
if (JS_IsException(r)) {
|
|
1253
|
+
/* TS parity: an addEventListener throw rejects the request.
|
|
1254
|
+
* An already-armed timeout timer stays armed, as in TS. */
|
|
1255
|
+
JS_FreeValue(ctx, cancel_fn);
|
|
1256
|
+
JS_FreeValue(ctx, timeout_v);
|
|
1257
|
+
JS_FreeValue(ctx, signal);
|
|
1258
|
+
JS_FreeValue(ctx, start);
|
|
1259
|
+
JS_FreeValue(ctx, st);
|
|
1260
|
+
return hc_reject_pending_exception(ctx);
|
|
1261
|
+
}
|
|
1262
|
+
JS_FreeValue(ctx, r);
|
|
1263
|
+
}
|
|
1264
|
+
JS_FreeValue(ctx, cancel_fn);
|
|
1265
|
+
}
|
|
1266
|
+
JS_FreeValue(ctx, timeout_v);
|
|
1267
|
+
JS_FreeValue(ctx, signal);
|
|
1268
|
+
|
|
1269
|
+
JSValue headers_p = JS_GetPropertyStr(ctx, start, "headers");
|
|
1270
|
+
JS_FreeValue(ctx, start);
|
|
1271
|
+
|
|
1272
|
+
JSValue rfuncs[2];
|
|
1273
|
+
JSValue promise = JS_NewPromiseCapability(ctx, rfuncs);
|
|
1274
|
+
if (JS_IsException(promise)) {
|
|
1275
|
+
JS_FreeValue(ctx, st);
|
|
1276
|
+
JS_FreeValue(ctx, headers_p);
|
|
1277
|
+
return promise;
|
|
1278
|
+
}
|
|
1279
|
+
JSValue data[3] = {st, rfuncs[0], rfuncs[1]};
|
|
1280
|
+
JSValue on_h = JS_NewCFunctionData(ctx, hc_on_headers, 1, 0, 3, data);
|
|
1281
|
+
JSValue on_h_err = JS_NewCFunctionData(ctx, hc_on_headers_rejected, 1, 0, 3, data);
|
|
1282
|
+
JS_FreeValue(ctx, st);
|
|
1283
|
+
JS_FreeValue(ctx, rfuncs[0]);
|
|
1284
|
+
JS_FreeValue(ctx, rfuncs[1]);
|
|
1285
|
+
JSValue wrapped = hc_promise_resolve(ctx, headers_p);
|
|
1286
|
+
hc_then2(ctx, wrapped, on_h, on_h_err);
|
|
1287
|
+
JS_FreeValue(ctx, wrapped);
|
|
1288
|
+
return promise;
|
|
1289
|
+
}
|
|
1290
|
+
|
|
1291
|
+
JSValue hc_pending_count_cf(JSContext* ctx, JSValue this_val, int argc, JSValue* argv, int magic,
|
|
1292
|
+
JSValue* func_data) {
|
|
1293
|
+
(void)this_val;
|
|
1294
|
+
(void)argc;
|
|
1295
|
+
(void)argv;
|
|
1296
|
+
(void)magic;
|
|
1297
|
+
return hc_invoke(ctx, func_data[0], "pendingCount", 0, nullptr);
|
|
1298
|
+
}
|
|
1299
|
+
|
|
1300
|
+
/* ── Transport module resolution ────────────────────────────────────────── */
|
|
1301
|
+
|
|
1302
|
+
/* Load `native:mikro/http` through the module loader so virtual-module
|
|
1303
|
+
* overrides (sim stubs, host fakes) win. The basename identifies the
|
|
1304
|
+
* importer to the loader's native: gate — only mikro-prefixed builtins may
|
|
1305
|
+
* import native: modules. Returns the namespace or throws. */
|
|
1306
|
+
JSValue hc_load_transport_ns(JSContext* ctx) {
|
|
1307
|
+
/* The transport namespace is only stored; its exports are read per call,
|
|
1308
|
+
* after evaluation has finished — hoisting suffices (require_evaluated=false). */
|
|
1309
|
+
return mik__load_module_ns(ctx, "mikro/http/request", "native:mikro/http", false);
|
|
1310
|
+
}
|
|
1311
|
+
|
|
1312
|
+
/* ── Module registration ────────────────────────────────────────────────── */
|
|
1313
|
+
|
|
1314
|
+
int hc_helpers_module_init(JSContext* ctx, JSModuleDef* m) {
|
|
1315
|
+
JSValue bce = hc_body_consumed_ctor(ctx);
|
|
1316
|
+
if (JS_IsException(bce)) return -1;
|
|
1317
|
+
JS_SetModuleExport(ctx, m, "BodyConsumedError", bce);
|
|
1318
|
+
JS_SetModuleExport(ctx, m, "RequestError", hc_make_request_error_obj(ctx));
|
|
1319
|
+
JS_SetModuleExport(ctx, m, "prepareBody",
|
|
1320
|
+
JS_NewCFunction(ctx, hc_prepare_body_cf, "prepareBody", 1));
|
|
1321
|
+
JS_SetModuleExport(ctx, m, "makeResponse",
|
|
1322
|
+
JS_NewCFunction(ctx, hc_make_response_cf, "makeResponse", 1));
|
|
1323
|
+
return 0;
|
|
1324
|
+
}
|
|
1325
|
+
|
|
1326
|
+
int hc_request_module_init(JSContext* ctx, JSModuleDef* m) {
|
|
1327
|
+
JSValue ns = hc_load_transport_ns(ctx);
|
|
1328
|
+
if (JS_IsException(ns)) return -1;
|
|
1329
|
+
JS_SetModuleExport(ctx, m, "request", JS_NewCFunctionData(ctx, hc_request_cf, 2, 0, 1, &ns));
|
|
1330
|
+
JS_SetModuleExport(ctx, m, "pendingCount",
|
|
1331
|
+
JS_NewCFunctionData(ctx, hc_pending_count_cf, 0, 0, 1, &ns));
|
|
1332
|
+
JS_FreeValue(ctx, ns);
|
|
1333
|
+
return 0;
|
|
1334
|
+
}
|
|
1335
|
+
|
|
1336
|
+
} // namespace
|
|
1337
|
+
|
|
1338
|
+
/* Loader hooks (see the C-module table in modules.cpp). Created lazily on
|
|
1339
|
+
* first import so MIK_RegisterVirtualModule keeps precedence for these names
|
|
1340
|
+
* and runtimes that never import http pay nothing. */
|
|
1341
|
+
JSModuleDef* mik__http_helpers_load(JSContext* ctx) {
|
|
1342
|
+
JSModuleDef* m = JS_NewCModule(ctx, "mikro/http/helpers", hc_helpers_module_init);
|
|
1343
|
+
if (!m) return nullptr;
|
|
1344
|
+
JS_AddModuleExport(ctx, m, "RequestError");
|
|
1345
|
+
JS_AddModuleExport(ctx, m, "BodyConsumedError");
|
|
1346
|
+
JS_AddModuleExport(ctx, m, "prepareBody");
|
|
1347
|
+
JS_AddModuleExport(ctx, m, "makeResponse");
|
|
1348
|
+
return m;
|
|
1349
|
+
}
|
|
1350
|
+
|
|
1351
|
+
JSModuleDef* mik__http_request_load(JSContext* ctx) {
|
|
1352
|
+
JSModuleDef* m = JS_NewCModule(ctx, "mikro/http/request", hc_request_module_init);
|
|
1353
|
+
if (!m) return nullptr;
|
|
1354
|
+
JS_AddModuleExport(ctx, m, "request");
|
|
1355
|
+
JS_AddModuleExport(ctx, m, "pendingCount");
|
|
1356
|
+
return m;
|
|
1357
|
+
}
|