@sproutboat/runtime 0.41.0 → 0.42.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/CHANGELOG.md +6 -0
- package/package.json +1 -1
- package/src/native-fetch-prelude.js +287 -2
- package/src/sockets.test.ts +42 -0
- package/src/source.ts +15 -0
- package/src/transport-broker.js +471 -0
- package/src/transport-embedded.js +175 -1
- package/src/wrap.ts +14 -0
- package/test/native-sockets-broker.ts +460 -0
|
@@ -3302,6 +3302,10 @@ typedef struct sb_wsc {
|
|
|
3302
3302
|
uint64_t rng;
|
|
3303
3303
|
char *host, *path, *headers, *protocols;
|
|
3304
3304
|
int port, tls;
|
|
3305
|
+
// Raw TCP (cloudflare:sockets connect()): no WebSocket framing, bytes both ways.
|
|
3306
|
+
int raw, starttls_req, shutdown_req, want_drain;
|
|
3307
|
+
size_t unsent, window;
|
|
3308
|
+
char sni[256];
|
|
3305
3309
|
} sb_wsc;
|
|
3306
3310
|
static pthread_mutex_t sb_wsc_lock = PTHREAD_MUTEX_INITIALIZER;
|
|
3307
3311
|
static sb_wsc* sb_wscs = 0;
|
|
@@ -3496,6 +3500,7 @@ static long sb_wsio_pump(sb_wsc* c, sb_wsio* io, unsigned char* buf, size_t cap,
|
|
|
3496
3500
|
}
|
|
3497
3501
|
}
|
|
3498
3502
|
|
|
3503
|
+
static int sb_wsio_start_tls(sb_wsio* io, const char* host, char* why, size_t whylen);
|
|
3499
3504
|
static int sb_wsio_open(sb_wsio* io, const char* host, int port, int tls, char* why, size_t whylen) {
|
|
3500
3505
|
memset(io, 0, sizeof(*io));
|
|
3501
3506
|
io->fd = sb_tcp_connect(host, port, why, whylen);
|
|
@@ -3506,6 +3511,11 @@ static int sb_wsio_open(sb_wsio* io, const char* host, int port, int tls, char*
|
|
|
3506
3511
|
fcntl(io->fd, F_SETFL, fcntl(io->fd, F_GETFL) | O_NONBLOCK);
|
|
3507
3512
|
io->tls = tls;
|
|
3508
3513
|
if (!tls) return 0;
|
|
3514
|
+
return sb_wsio_start_tls(io, host, why, whylen);
|
|
3515
|
+
}
|
|
3516
|
+
// Starts TLS on an open fd (connect with secureTransport "on", or startTls()).
|
|
3517
|
+
static int sb_wsio_start_tls(sb_wsio* io, const char* host, char* why, size_t whylen) {
|
|
3518
|
+
io->tls = 1;
|
|
3509
3519
|
io->sc = (br_ssl_client_context*)malloc(sizeof(br_ssl_client_context));
|
|
3510
3520
|
io->xc = (br_x509_minimal_context*)malloc(sizeof(br_x509_minimal_context));
|
|
3511
3521
|
io->iobuf = (unsigned char*)malloc(BR_SSL_BUFSIZE_BIDI);
|
|
@@ -3759,6 +3769,91 @@ finish:
|
|
|
3759
3769
|
return 0;
|
|
3760
3770
|
}
|
|
3761
3771
|
|
|
3772
|
+
// Raw TCP: the thread for connect(). Events: 1 data, 3 error (reason in data), 4 connected (plain),
|
|
3773
|
+
// 6 end of input, 8 send queue drained (after a write left more than the high-water mark queued),
|
|
3774
|
+
// 9 TLS handshake finished.
|
|
3775
|
+
#define SB_TCP_READ_WINDOW (256u << 10)
|
|
3776
|
+
static void* sb_tcp_thread(void* arg) {
|
|
3777
|
+
sb_wsc* c = (sb_wsc*)arg;
|
|
3778
|
+
sb_wsio io;
|
|
3779
|
+
memset(&io, 0, sizeof(io));
|
|
3780
|
+
io.fd = -1;
|
|
3781
|
+
char why[512] = "";
|
|
3782
|
+
unsigned char buf[16384];
|
|
3783
|
+
unsigned char* wq = 0; size_t wq_len = 0, wq_cap = 0;
|
|
3784
|
+
int tls_waiting = 0, eof = 0, shut = 0;
|
|
3785
|
+
if (sb_wsio_open(&io, c->host, c->port, c->tls, why, sizeof(why)) != 0) { sb_wsc_push(c, 3, 0, why, strlen(why)); goto finish; }
|
|
3786
|
+
if (c->tls) tls_waiting = 1; else sb_wsc_push(c, 4, 0, "", 0);
|
|
3787
|
+
for (;;) {
|
|
3788
|
+
pthread_mutex_lock(&c->lock);
|
|
3789
|
+
int rel = c->released;
|
|
3790
|
+
size_t window = c->unacked;
|
|
3791
|
+
int st = c->starttls_req; c->starttls_req = 0;
|
|
3792
|
+
int sh = c->shutdown_req;
|
|
3793
|
+
char sni[256]; memcpy(sni, c->sni, sizeof(sni));
|
|
3794
|
+
if (c->outq_len) {
|
|
3795
|
+
if (wq_len + c->outq_len > wq_cap) {
|
|
3796
|
+
size_t cap = wq_cap ? wq_cap : 4096;
|
|
3797
|
+
while (cap < wq_len + c->outq_len) cap *= 2;
|
|
3798
|
+
unsigned char* grown = (unsigned char*)realloc(wq, cap);
|
|
3799
|
+
if (grown) { wq = grown; wq_cap = cap; }
|
|
3800
|
+
}
|
|
3801
|
+
if (wq_len + c->outq_len <= wq_cap) { memcpy(wq + wq_len, c->outq, c->outq_len); wq_len += c->outq_len; c->outq_len = 0; }
|
|
3802
|
+
}
|
|
3803
|
+
c->unsent = wq_len;
|
|
3804
|
+
pthread_mutex_unlock(&c->lock);
|
|
3805
|
+
if (rel) {
|
|
3806
|
+
if (wq_len) sb_wsio_pump(c, &io, buf, sizeof(buf), wq, &wq_len, 0, 0);
|
|
3807
|
+
goto finish;
|
|
3808
|
+
}
|
|
3809
|
+
if (st && !io.tls) {
|
|
3810
|
+
if (wq_len) sb_wsio_pump(c, &io, buf, sizeof(buf), wq, &wq_len, 0, 0);
|
|
3811
|
+
if (sb_wsio_start_tls(&io, sni[0] ? sni : c->host, why, sizeof(why)) != 0) { sb_wsc_push(c, 3, 0, why, strlen(why)); goto finish; }
|
|
3812
|
+
tls_waiting = 1;
|
|
3813
|
+
}
|
|
3814
|
+
long n = sb_wsio_pump(c, &io, buf, sizeof(buf), wq, &wq_len, !eof && window < SB_TCP_READ_WINDOW, tls_waiting ? 20 : -1);
|
|
3815
|
+
if (tls_waiting && io.tls && (br_ssl_engine_current_state(&io.sc->eng) & (BR_SSL_SENDAPP | BR_SSL_RECVAPP))) {
|
|
3816
|
+
tls_waiting = 0;
|
|
3817
|
+
sb_wsc_push(c, 9, 0, "", 0);
|
|
3818
|
+
}
|
|
3819
|
+
if (n == -2) continue;
|
|
3820
|
+
if (n < 0) {
|
|
3821
|
+
int err = io.tls ? br_ssl_engine_last_error(&io.sc->eng) : 0;
|
|
3822
|
+
if (err != 0 && err != BR_ERR_IO) { snprintf(why, sizeof(why), "TLS error %d", err); sb_wsc_push(c, 3, 0, why, strlen(why)); goto finish; }
|
|
3823
|
+
if (tls_waiting) { snprintf(why, sizeof(why), "connection closed during the TLS handshake"); sb_wsc_push(c, 3, 0, why, strlen(why)); goto finish; }
|
|
3824
|
+
if (!eof) { eof = 1; sb_wsc_push(c, 6, 0, "", 0); }
|
|
3825
|
+
// keep serving writes until released; with no reader the pump just waits for the wake pipe
|
|
3826
|
+
n = 0;
|
|
3827
|
+
}
|
|
3828
|
+
if (n == 0) sb_wsc_drain_wake(c);
|
|
3829
|
+
else sb_wsc_push(c, 1, 0, (const char*)buf, (size_t)n);
|
|
3830
|
+
if (sh && wq_len == 0 && !shut) {
|
|
3831
|
+
shut = 1;
|
|
3832
|
+
if (io.tls) br_ssl_engine_close(&io.sc->eng); else shutdown(io.fd, SHUT_WR);
|
|
3833
|
+
}
|
|
3834
|
+
int drained = 0;
|
|
3835
|
+
pthread_mutex_lock(&c->lock);
|
|
3836
|
+
c->unsent = wq_len;
|
|
3837
|
+
if (c->want_drain && c->outq_len + wq_len == 0) { c->want_drain = 0; drained = 1; }
|
|
3838
|
+
pthread_mutex_unlock(&c->lock);
|
|
3839
|
+
if (drained) sb_wsc_push(c, 8, 0, "", 0);
|
|
3840
|
+
}
|
|
3841
|
+
finish:
|
|
3842
|
+
sb_wsio_close(&io);
|
|
3843
|
+
free(wq);
|
|
3844
|
+
pthread_mutex_lock(&c->lock);
|
|
3845
|
+
c->finished = 1;
|
|
3846
|
+
int rel2 = c->released;
|
|
3847
|
+
pthread_mutex_unlock(&c->lock);
|
|
3848
|
+
if (rel2) {
|
|
3849
|
+
pthread_mutex_lock(&sb_wsc_lock);
|
|
3850
|
+
for (sb_wsc** at = &sb_wscs; *at; at = &(*at)->next) if (*at == c) { *at = c->next; break; }
|
|
3851
|
+
pthread_mutex_unlock(&sb_wsc_lock);
|
|
3852
|
+
sb_wsc_free(c);
|
|
3853
|
+
}
|
|
3854
|
+
return 0;
|
|
3855
|
+
}
|
|
3856
|
+
|
|
3762
3857
|
static sb_wsc* sb_wsc_find(unsigned id) {
|
|
3763
3858
|
pthread_mutex_lock(&sb_wsc_lock);
|
|
3764
3859
|
sb_wsc* c = sb_wscs;
|
|
@@ -3787,6 +3882,52 @@ static void sb_wsc_drain(void) {
|
|
|
3787
3882
|
|
|
3788
3883
|
// The JS side's ops. data is borrowed for the call.
|
|
3789
3884
|
static long long sb_wsc_op(int op, unsigned id, const char* data, size_t len, size_t n, char* out, size_t cap) {
|
|
3885
|
+
if (op == 'T') {
|
|
3886
|
+
// data: host \n port \n mode (0 off, 1 on, 2 starttls) \n servername
|
|
3887
|
+
sb_wsc* c = (sb_wsc*)calloc(1, sizeof(sb_wsc));
|
|
3888
|
+
if (!c) return 0;
|
|
3889
|
+
char* copy = (char*)malloc(len + 1);
|
|
3890
|
+
if (!copy) { free(c); return 0; }
|
|
3891
|
+
memcpy(copy, data, len); copy[len] = 0;
|
|
3892
|
+
char* parts[4]; char* cur = copy;
|
|
3893
|
+
for (int i = 0; i < 4; i++) {
|
|
3894
|
+
parts[i] = cur;
|
|
3895
|
+
char* nl = strchr(cur, '\n');
|
|
3896
|
+
if (nl) { *nl = 0; cur = nl + 1; } else cur += strlen(cur);
|
|
3897
|
+
}
|
|
3898
|
+
c->raw = 1; c->window = SB_TCP_READ_WINDOW;
|
|
3899
|
+
c->host = strdup(parts[0]); c->port = atoi(parts[1]); c->tls = atoi(parts[2]) == 1;
|
|
3900
|
+
snprintf(c->sni, sizeof(c->sni), "%s", parts[3]);
|
|
3901
|
+
c->path = strdup(""); c->protocols = strdup(""); c->headers = strdup("");
|
|
3902
|
+
free(copy);
|
|
3903
|
+
if (!c->host || !c->path || !c->protocols || !c->headers || pipe(c->wake) != 0) { free(c->host); free(c->path); free(c->protocols); free(c->headers); free(c); return 0; }
|
|
3904
|
+
for (int k = 0; k < 2; k++) { fcntl(c->wake[k], F_SETFD, FD_CLOEXEC); fcntl(c->wake[k], F_SETFL, fcntl(c->wake[k], F_GETFL) | O_NONBLOCK); }
|
|
3905
|
+
pthread_mutex_init(&c->lock, 0);
|
|
3906
|
+
pthread_mutex_lock(&sb_wsc_lock);
|
|
3907
|
+
int live = 0;
|
|
3908
|
+
for (sb_wsc* k = sb_wscs; k; k = k->next) live++;
|
|
3909
|
+
if (live >= SB_WSC_MAX) { pthread_mutex_unlock(&sb_wsc_lock); sb_wsc_free(c); return 0; }
|
|
3910
|
+
sb_wsc_next = (sb_wsc_next + 1) & 0x7fffffffu;
|
|
3911
|
+
if (!sb_wsc_next) sb_wsc_next = 1;
|
|
3912
|
+
c->id = sb_wsc_next | SB_WSC_BIT;
|
|
3913
|
+
c->next = sb_wscs; sb_wscs = c;
|
|
3914
|
+
pthread_mutex_unlock(&sb_wsc_lock);
|
|
3915
|
+
pthread_attr_t attr;
|
|
3916
|
+
pthread_attr_init(&attr);
|
|
3917
|
+
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
|
|
3918
|
+
pthread_attr_setstacksize(&attr, 512 * 1024);
|
|
3919
|
+
pthread_t thread;
|
|
3920
|
+
int failed = pthread_create(&thread, &attr, sb_tcp_thread, c);
|
|
3921
|
+
pthread_attr_destroy(&attr);
|
|
3922
|
+
if (failed) {
|
|
3923
|
+
pthread_mutex_lock(&sb_wsc_lock);
|
|
3924
|
+
for (sb_wsc** at = &sb_wscs; *at; at = &(*at)->next) if (*at == c) { *at = c->next; break; }
|
|
3925
|
+
pthread_mutex_unlock(&sb_wsc_lock);
|
|
3926
|
+
sb_wsc_free(c);
|
|
3927
|
+
return 0;
|
|
3928
|
+
}
|
|
3929
|
+
return (long long)c->id;
|
|
3930
|
+
}
|
|
3790
3931
|
if (op == 'u') {
|
|
3791
3932
|
// data: host \n port \n tls(0|1) \n path \n protocols \n header lines (to the end)
|
|
3792
3933
|
sb_wsc* c = (sb_wsc*)calloc(1, sizeof(sb_wsc));
|
|
@@ -3836,7 +3977,40 @@ static long long sb_wsc_op(int op, unsigned id, const char* data, size_t len, si
|
|
|
3836
3977
|
sb_wsc* c = sb_wsc_find(id);
|
|
3837
3978
|
if (!c) return op == 'p' || op == 'k' || op == 'w' || op == 'r' ? 0 : -1;
|
|
3838
3979
|
switch (op) {
|
|
3839
|
-
case '
|
|
3980
|
+
case 'b': if (c->raw) {
|
|
3981
|
+
// raw bytes: queue them, say how many are still unsent, and ask for a drain event past the mark
|
|
3982
|
+
pthread_mutex_lock(&c->lock);
|
|
3983
|
+
size_t need = c->outq_len + len;
|
|
3984
|
+
if (need > c->outq_cap) {
|
|
3985
|
+
size_t cap = c->outq_cap ? c->outq_cap : 4096;
|
|
3986
|
+
while (cap < need) cap *= 2;
|
|
3987
|
+
unsigned char* grown = (unsigned char*)realloc(c->outq, cap);
|
|
3988
|
+
if (!grown) { pthread_mutex_unlock(&c->lock); return -1; }
|
|
3989
|
+
c->outq = grown; c->outq_cap = cap;
|
|
3990
|
+
}
|
|
3991
|
+
memcpy(c->outq + c->outq_len, data, len);
|
|
3992
|
+
c->outq_len += len;
|
|
3993
|
+
size_t queued = c->outq_len + c->unsent;
|
|
3994
|
+
if (queued > (256u << 10)) c->want_drain = 1;
|
|
3995
|
+
pthread_mutex_unlock(&c->lock);
|
|
3996
|
+
sb_wsc_wake_thread(c);
|
|
3997
|
+
return (long long)queued;
|
|
3998
|
+
}
|
|
3999
|
+
/* fall through */
|
|
4000
|
+
case 't': return sb_wsc_frame(c, op == 't' ? 1 : 2, data, len, 0, 0);
|
|
4001
|
+
case 'S':
|
|
4002
|
+
pthread_mutex_lock(&c->lock);
|
|
4003
|
+
snprintf(c->sni, sizeof(c->sni), "%.*s", (int)(len < sizeof(c->sni) - 1 ? len : sizeof(c->sni) - 1), data ? data : "");
|
|
4004
|
+
c->starttls_req = 1;
|
|
4005
|
+
pthread_mutex_unlock(&c->lock);
|
|
4006
|
+
sb_wsc_wake_thread(c);
|
|
4007
|
+
return 0;
|
|
4008
|
+
case 'H':
|
|
4009
|
+
pthread_mutex_lock(&c->lock);
|
|
4010
|
+
c->shutdown_req = 1;
|
|
4011
|
+
pthread_mutex_unlock(&c->lock);
|
|
4012
|
+
sb_wsc_wake_thread(c);
|
|
4013
|
+
return 0;
|
|
3840
4014
|
case 'c': {
|
|
3841
4015
|
pthread_mutex_lock(&c->lock);
|
|
3842
4016
|
int already = c->close_queued;
|
package/src/wrap.ts
CHANGED
|
@@ -137,6 +137,20 @@ function hasBindings(b: Bindings): boolean {
|
|
|
137
137
|
* leaves the specifier external for this.
|
|
138
138
|
*/
|
|
139
139
|
export function inlineWorkersImports(source: string): string {
|
|
140
|
+
// `cloudflare:sockets` (compatibility date 2026-11-17): connect() lives on a prelude global.
|
|
141
|
+
// postgres/cf loads it lazily: `await import("cloudflare:sockets")`.
|
|
142
|
+
source = source.replace(/\bimport\(\s*["']cloudflare:sockets["']\s*\)/g, "Promise.resolve(globalThis.__sbSockets)");
|
|
143
|
+
source = source.replace(
|
|
144
|
+
/^import\s*\{([^}]*)\}\s*from\s*["']cloudflare:sockets["'];?[ \t]*$/gm,
|
|
145
|
+
(_match, names: string) => {
|
|
146
|
+
const pairs = names
|
|
147
|
+
.split(",")
|
|
148
|
+
.map((part) => part.trim())
|
|
149
|
+
.filter(Boolean)
|
|
150
|
+
.map((part) => part.replace(/^(\S+)\s+as\s+(\S+)$/, "$1: $2"));
|
|
151
|
+
return `const { ${pairs.join(", ")} } = globalThis.__sbSockets;`;
|
|
152
|
+
},
|
|
153
|
+
);
|
|
140
154
|
return source.replace(
|
|
141
155
|
/^import\s*\{([^}]*)\}\s*from\s*["']cloudflare:workers["'];?[ \t]*$/gm,
|
|
142
156
|
(_match, names: string) => {
|