@floegence/flowersec-core 0.1.1

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 (120) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +42 -0
  3. package/YAMUX_ALIGNMENT.md +127 -0
  4. package/dist/_examples/flowersec/demo/v1.facade.gen.d.ts +12 -0
  5. package/dist/_examples/flowersec/demo/v1.facade.gen.js +15 -0
  6. package/dist/_examples/flowersec/demo/v1.gen.d.ts +16 -0
  7. package/dist/_examples/flowersec/demo/v1.gen.js +86 -0
  8. package/dist/_examples/flowersec/demo/v1.rpc.gen.d.ts +11 -0
  9. package/dist/_examples/flowersec/demo/v1.rpc.gen.js +22 -0
  10. package/dist/browser/connect.d.ts +12 -0
  11. package/dist/browser/connect.js +31 -0
  12. package/dist/browser/index.d.ts +2 -0
  13. package/dist/browser/index.js +1 -0
  14. package/dist/client-connect/common.d.ts +26 -0
  15. package/dist/client-connect/common.js +167 -0
  16. package/dist/client-connect/connectCore.d.ts +42 -0
  17. package/dist/client-connect/connectCore.js +302 -0
  18. package/dist/client-connect/tunnelAttachCloseReason.d.ts +3 -0
  19. package/dist/client-connect/tunnelAttachCloseReason.js +16 -0
  20. package/dist/client.d.ts +17 -0
  21. package/dist/client.js +1 -0
  22. package/dist/direct-client/connect.d.ts +4 -0
  23. package/dist/direct-client/connect.js +67 -0
  24. package/dist/direct-client/index.d.ts +1 -0
  25. package/dist/direct-client/index.js +1 -0
  26. package/dist/e2ee/constants.d.ts +9 -0
  27. package/dist/e2ee/constants.js +18 -0
  28. package/dist/e2ee/errors.d.ts +5 -0
  29. package/dist/e2ee/errors.js +8 -0
  30. package/dist/e2ee/framing.d.ts +12 -0
  31. package/dist/e2ee/framing.js +57 -0
  32. package/dist/e2ee/handshake.d.ts +80 -0
  33. package/dist/e2ee/handshake.js +322 -0
  34. package/dist/e2ee/index.d.ts +7 -0
  35. package/dist/e2ee/index.js +7 -0
  36. package/dist/e2ee/kdf.d.ts +15 -0
  37. package/dist/e2ee/kdf.js +39 -0
  38. package/dist/e2ee/record.d.ts +11 -0
  39. package/dist/e2ee/record.js +69 -0
  40. package/dist/e2ee/secureChannel.d.ts +82 -0
  41. package/dist/e2ee/secureChannel.js +265 -0
  42. package/dist/e2ee/transcript.d.ts +23 -0
  43. package/dist/e2ee/transcript.js +31 -0
  44. package/dist/facade.d.ts +21 -0
  45. package/dist/facade.js +61 -0
  46. package/dist/gen/flowersec/controlplane/v1.gen.d.ts +36 -0
  47. package/dist/gen/flowersec/controlplane/v1.gen.js +135 -0
  48. package/dist/gen/flowersec/direct/v1.gen.d.ts +21 -0
  49. package/dist/gen/flowersec/direct/v1.gen.js +101 -0
  50. package/dist/gen/flowersec/e2ee/v1.gen.d.ts +68 -0
  51. package/dist/gen/flowersec/e2ee/v1.gen.js +194 -0
  52. package/dist/gen/flowersec/rpc/v1.gen.d.ts +30 -0
  53. package/dist/gen/flowersec/rpc/v1.gen.js +107 -0
  54. package/dist/gen/flowersec/tunnel/v1.gen.d.ts +23 -0
  55. package/dist/gen/flowersec/tunnel/v1.gen.js +104 -0
  56. package/dist/index.d.ts +19 -0
  57. package/dist/index.js +19 -0
  58. package/dist/node/connect.d.ts +9 -0
  59. package/dist/node/connect.js +13 -0
  60. package/dist/node/index.d.ts +2 -0
  61. package/dist/node/index.js +2 -0
  62. package/dist/node/wsFactory.d.ts +2 -0
  63. package/dist/node/wsFactory.js +69 -0
  64. package/dist/observability/index.d.ts +1 -0
  65. package/dist/observability/index.js +1 -0
  66. package/dist/observability/observer.d.ts +23 -0
  67. package/dist/observability/observer.js +28 -0
  68. package/dist/rpc/callError.d.ts +5 -0
  69. package/dist/rpc/callError.js +11 -0
  70. package/dist/rpc/caller.d.ts +8 -0
  71. package/dist/rpc/caller.js +1 -0
  72. package/dist/rpc/client.d.ts +22 -0
  73. package/dist/rpc/client.js +170 -0
  74. package/dist/rpc/framing.d.ts +4 -0
  75. package/dist/rpc/framing.js +24 -0
  76. package/dist/rpc/index.d.ts +6 -0
  77. package/dist/rpc/index.js +6 -0
  78. package/dist/rpc/server.d.ts +15 -0
  79. package/dist/rpc/server.js +67 -0
  80. package/dist/rpc/typed.d.ts +5 -0
  81. package/dist/rpc/typed.js +9 -0
  82. package/dist/rpc/validate.d.ts +2 -0
  83. package/dist/rpc/validate.js +27 -0
  84. package/dist/rpc-proxy/index.d.ts +1 -0
  85. package/dist/rpc-proxy/index.js +1 -0
  86. package/dist/rpc-proxy/rpcProxy.d.ts +13 -0
  87. package/dist/rpc-proxy/rpcProxy.js +59 -0
  88. package/dist/streamhello/index.d.ts +1 -0
  89. package/dist/streamhello/index.js +1 -0
  90. package/dist/streamhello/streamHello.d.ts +3 -0
  91. package/dist/streamhello/streamHello.js +13 -0
  92. package/dist/tunnel-client/connect.d.ts +7 -0
  93. package/dist/tunnel-client/connect.js +125 -0
  94. package/dist/tunnel-client/index.d.ts +1 -0
  95. package/dist/tunnel-client/index.js +1 -0
  96. package/dist/utils/base64url.d.ts +2 -0
  97. package/dist/utils/base64url.js +40 -0
  98. package/dist/utils/bin.d.ts +6 -0
  99. package/dist/utils/bin.js +55 -0
  100. package/dist/utils/errors.d.ts +26 -0
  101. package/dist/utils/errors.js +42 -0
  102. package/dist/utils/number.d.ts +2 -0
  103. package/dist/utils/number.js +9 -0
  104. package/dist/ws/index.d.ts +1 -0
  105. package/dist/ws/index.js +1 -0
  106. package/dist/ws-client/binaryTransport.d.ts +49 -0
  107. package/dist/ws-client/binaryTransport.js +301 -0
  108. package/dist/yamux/byteReader.d.ts +10 -0
  109. package/dist/yamux/byteReader.js +50 -0
  110. package/dist/yamux/constants.d.ts +10 -0
  111. package/dist/yamux/constants.js +14 -0
  112. package/dist/yamux/header.d.ts +17 -0
  113. package/dist/yamux/header.js +26 -0
  114. package/dist/yamux/index.d.ts +5 -0
  115. package/dist/yamux/index.js +5 -0
  116. package/dist/yamux/session.d.ts +44 -0
  117. package/dist/yamux/session.js +228 -0
  118. package/dist/yamux/stream.d.ts +30 -0
  119. package/dist/yamux/stream.js +222 -0
  120. package/package.json +112 -0
@@ -0,0 +1,228 @@
1
+ import { ByteReader } from "./byteReader.js";
2
+ import { decodeHeader, encodeHeader, HEADER_LEN } from "./header.js";
3
+ import { DEFAULT_MAX_STREAM_WINDOW, FLAG_ACK, FLAG_RST, FLAG_SYN, TYPE_DATA, TYPE_GO_AWAY, TYPE_PING, TYPE_WINDOW_UPDATE, YAMUX_VERSION } from "./constants.js";
4
+ import { YamuxStream } from "./stream.js";
5
+ // YamuxSession multiplexes multiple streams over a single byte stream.
6
+ export class YamuxSession {
7
+ // Underlying byte stream for yamux frames.
8
+ conn;
9
+ // Reader for fixed-length header/body reads.
10
+ reader;
11
+ // Active streams keyed by stream ID.
12
+ streams = new Map();
13
+ // Callback for inbound streams created from SYN frames.
14
+ onIncomingStream;
15
+ // Maximum allowed DATA frame length.
16
+ maxFrameBytes;
17
+ // True when this side is the yamux client (odd local stream IDs).
18
+ client;
19
+ // Next stream ID to allocate (odd/even based on role).
20
+ nextStreamId;
21
+ // Closed flag for terminating read loops and streams.
22
+ closed = false;
23
+ // Writers waiting for send window credits per stream.
24
+ sendWindowWaiters = new Map();
25
+ constructor(conn, opts) {
26
+ this.conn = conn;
27
+ this.reader = new ByteReader(async () => {
28
+ try {
29
+ return await this.conn.read();
30
+ }
31
+ catch {
32
+ return null;
33
+ }
34
+ });
35
+ this.onIncomingStream = opts.onIncomingStream;
36
+ this.maxFrameBytes = Math.max(0, opts.maxFrameBytes ?? DEFAULT_MAX_STREAM_WINDOW);
37
+ this.client = opts.client;
38
+ this.nextStreamId = opts.client ? 1 : 2;
39
+ void this.readLoop();
40
+ }
41
+ // openStream allocates a new stream and performs the SYN handshake.
42
+ async openStream() {
43
+ const id = this.nextStreamId;
44
+ this.nextStreamId += 2;
45
+ const s = new YamuxStream(this, id, "init");
46
+ this.streams.set(id, s);
47
+ await s.open();
48
+ return s;
49
+ }
50
+ // getStream returns the stream for an ID, if any.
51
+ getStream(id) {
52
+ return this.streams.get(id);
53
+ }
54
+ // writeRaw writes a raw yamux frame to the underlying connection.
55
+ async writeRaw(chunk) {
56
+ await this.conn.write(chunk);
57
+ }
58
+ // sendRst sends a reset frame and removes the stream.
59
+ async sendRst(id) {
60
+ const hdr = encodeHeader({ type: TYPE_WINDOW_UPDATE, flags: FLAG_RST, streamId: id, length: 0 });
61
+ if (this.closed) {
62
+ this.onStreamClosed(id);
63
+ return;
64
+ }
65
+ try {
66
+ await this.writeRaw(hdr);
67
+ }
68
+ catch {
69
+ // Best-effort reset; ignore errors when the session is closing.
70
+ }
71
+ this.onStreamClosed(id);
72
+ }
73
+ // notifySendWindow wakes any writers waiting on window credit.
74
+ notifySendWindow(streamId) {
75
+ const ws = this.sendWindowWaiters.get(streamId);
76
+ if (ws == null)
77
+ return;
78
+ this.sendWindowWaiters.delete(streamId);
79
+ for (const w of ws)
80
+ w();
81
+ }
82
+ // waitForSendWindow blocks until send window credits are available.
83
+ waitForSendWindow(streamId) {
84
+ if (this.closed)
85
+ return Promise.reject(new Error("session closed"));
86
+ return new Promise((resolve, reject) => {
87
+ if (this.closed) {
88
+ reject(new Error("session closed"));
89
+ return;
90
+ }
91
+ const ws = this.sendWindowWaiters.get(streamId) ?? [];
92
+ ws.push(() => {
93
+ if (this.closed) {
94
+ reject(new Error("session closed"));
95
+ return;
96
+ }
97
+ resolve();
98
+ });
99
+ this.sendWindowWaiters.set(streamId, ws);
100
+ });
101
+ }
102
+ onStreamEstablished(_streamId) { }
103
+ // onStreamClosed removes the stream and wakes any per-stream waiters.
104
+ onStreamClosed(streamId) {
105
+ this.streams.delete(streamId);
106
+ this.notifySendWindow(streamId);
107
+ }
108
+ // close terminates the session and resets all streams.
109
+ close() {
110
+ if (this.closed)
111
+ return;
112
+ this.closed = true;
113
+ this.conn.close();
114
+ this.wakeSendWindowWaiters();
115
+ const streams = Array.from(this.streams.values());
116
+ this.streams.clear();
117
+ for (const s of streams)
118
+ s.reset(new Error("session closed"));
119
+ }
120
+ wakeSendWindowWaiters() {
121
+ for (const [streamId, ws] of this.sendWindowWaiters) {
122
+ this.sendWindowWaiters.delete(streamId);
123
+ for (const w of ws)
124
+ w();
125
+ }
126
+ }
127
+ async readLoop() {
128
+ try {
129
+ while (!this.closed) {
130
+ const hdrBytes = await this.reader.readExactly(HEADER_LEN);
131
+ const h = decodeHeader(hdrBytes, 0);
132
+ if (h.version !== YAMUX_VERSION) {
133
+ this.close();
134
+ return;
135
+ }
136
+ if (h.type === TYPE_DATA) {
137
+ if (this.maxFrameBytes > 0 && h.length > this.maxFrameBytes) {
138
+ this.close();
139
+ return;
140
+ }
141
+ const data = h.length > 0 ? await this.reader.readExactly(h.length) : new Uint8Array();
142
+ await this.handleDataFrame(h.streamId, h.flags, data);
143
+ continue;
144
+ }
145
+ if (h.type === TYPE_WINDOW_UPDATE) {
146
+ await this.handleWindowUpdateFrame(h.streamId, h.flags, h.length);
147
+ continue;
148
+ }
149
+ if (h.type === TYPE_PING) {
150
+ await this.handlePing(h.flags, h.length);
151
+ continue;
152
+ }
153
+ if (h.type === TYPE_GO_AWAY) {
154
+ this.close();
155
+ return;
156
+ }
157
+ this.close();
158
+ return;
159
+ }
160
+ }
161
+ catch {
162
+ this.close();
163
+ }
164
+ }
165
+ async handlePing(flags, opaque) {
166
+ if ((flags & FLAG_SYN) !== 0) {
167
+ const hdr = encodeHeader({ type: TYPE_PING, flags: FLAG_ACK, streamId: 0, length: opaque >>> 0 });
168
+ await this.writeRaw(hdr);
169
+ return;
170
+ }
171
+ }
172
+ async handleDataFrame(streamId, flags, data) {
173
+ if (streamId === 0) {
174
+ this.close();
175
+ return;
176
+ }
177
+ let s = this.streams.get(streamId);
178
+ if (s == null) {
179
+ if ((flags & FLAG_SYN) !== 0) {
180
+ if (!this.isInboundStreamIdValid(streamId)) {
181
+ await this.sendRst(streamId);
182
+ return;
183
+ }
184
+ s = new YamuxStream(this, streamId, "synReceived");
185
+ this.streams.set(streamId, s);
186
+ await s.open();
187
+ this.onIncomingStream?.(s);
188
+ }
189
+ else {
190
+ await this.sendRst(streamId);
191
+ return;
192
+ }
193
+ }
194
+ s.onData(data, flags);
195
+ }
196
+ async handleWindowUpdateFrame(streamId, flags, delta) {
197
+ if (streamId === 0) {
198
+ this.close();
199
+ return;
200
+ }
201
+ let s = this.streams.get(streamId);
202
+ if (s == null) {
203
+ if ((flags & FLAG_SYN) !== 0) {
204
+ if (!this.isInboundStreamIdValid(streamId)) {
205
+ await this.sendRst(streamId);
206
+ return;
207
+ }
208
+ s = new YamuxStream(this, streamId, "synReceived");
209
+ this.streams.set(streamId, s);
210
+ await s.open();
211
+ this.onIncomingStream?.(s);
212
+ }
213
+ else {
214
+ await this.sendRst(streamId);
215
+ return;
216
+ }
217
+ }
218
+ s.onWindowUpdate(delta, flags);
219
+ }
220
+ isInboundStreamIdValid(streamId) {
221
+ // yamux uses stream ID parity to identify the initiator:
222
+ // client-initiated streams are odd, server-initiated streams are even.
223
+ //
224
+ // When we are the client, the peer is the server and must initiate even IDs.
225
+ // When we are the server, the peer is the client and must initiate odd IDs.
226
+ return (streamId & 1) === (this.client ? 0 : 1);
227
+ }
228
+ }
@@ -0,0 +1,30 @@
1
+ import type { YamuxSession } from "./session.js";
2
+ type StreamState = "init" | "synSent" | "synReceived" | "established" | "localClose" | "remoteClose" | "closed" | "reset";
3
+ export declare class YamuxStream {
4
+ readonly id: number;
5
+ private state;
6
+ private readonly session;
7
+ private recvWindow;
8
+ private sendWindow;
9
+ private readonly recvQueue;
10
+ private recvQueueHead;
11
+ private recvQueueBytes;
12
+ private readWaiters;
13
+ private error;
14
+ constructor(session: YamuxSession, id: number, state: StreamState);
15
+ open(): Promise<void>;
16
+ onData(data: Uint8Array, flags: number): void;
17
+ onWindowUpdate(delta: number, flags: number): void;
18
+ read(): Promise<Uint8Array>;
19
+ write(data: Uint8Array): Promise<void>;
20
+ close(): Promise<void>;
21
+ reset(err: Error): void;
22
+ private processFlags;
23
+ private finalizeClosed;
24
+ private shiftRecv;
25
+ private sendFlags;
26
+ private sendWindowUpdate;
27
+ private waitForSendWindow;
28
+ private ensureWritable;
29
+ }
30
+ export {};
@@ -0,0 +1,222 @@
1
+ import { concatBytes } from "../utils/bin.js";
2
+ import { encodeHeader } from "./header.js";
3
+ import { DEFAULT_MAX_STREAM_WINDOW, FLAG_ACK, FLAG_FIN, FLAG_RST, FLAG_SYN, TYPE_DATA, TYPE_WINDOW_UPDATE } from "./constants.js";
4
+ // YamuxStream manages per-stream flow control and state transitions.
5
+ export class YamuxStream {
6
+ // Stream identifier within the session.
7
+ id;
8
+ // Current stream state in the yamux state machine.
9
+ state;
10
+ // Parent session used for frame IO and window coordination.
11
+ session;
12
+ // Remaining receive window advertised to the peer.
13
+ recvWindow = DEFAULT_MAX_STREAM_WINDOW;
14
+ // Remaining send window credit granted by the peer.
15
+ sendWindow = DEFAULT_MAX_STREAM_WINDOW;
16
+ // Buffered inbound data chunks.
17
+ recvQueue = [];
18
+ recvQueueHead = 0;
19
+ // Total buffered bytes in recvQueue.
20
+ recvQueueBytes = 0;
21
+ // Readers waiting for incoming data or EOF/reset.
22
+ readWaiters = [];
23
+ // Terminal error (reset/overflow) for the stream.
24
+ error = null;
25
+ constructor(session, id, state) {
26
+ this.session = session;
27
+ this.id = id;
28
+ this.state = state;
29
+ }
30
+ // open sends the initial window update to establish the stream.
31
+ async open() {
32
+ await this.sendWindowUpdate();
33
+ }
34
+ // onData handles inbound DATA frames and updates receive window.
35
+ onData(data, flags) {
36
+ this.processFlags(flags);
37
+ if (data.length === 0)
38
+ return;
39
+ if (data.length > this.recvWindow) {
40
+ this.reset(new Error("recv window exceeded"));
41
+ return;
42
+ }
43
+ this.recvWindow -= data.length;
44
+ this.recvQueue.push(data);
45
+ this.recvQueueBytes += data.length;
46
+ const ws = this.readWaiters;
47
+ this.readWaiters = [];
48
+ for (const w of ws)
49
+ w();
50
+ }
51
+ // onWindowUpdate applies flow-control credits from peer.
52
+ onWindowUpdate(delta, flags) {
53
+ this.processFlags(flags);
54
+ this.sendWindow += delta >>> 0;
55
+ this.session.notifySendWindow(this.id);
56
+ }
57
+ // read resolves with the next data chunk or throws on EOF/reset.
58
+ async read() {
59
+ while (true) {
60
+ if (this.error != null)
61
+ throw this.error;
62
+ const b = this.shiftRecv();
63
+ if (b != null) {
64
+ this.recvQueueBytes -= b.length;
65
+ await this.sendWindowUpdate();
66
+ return b;
67
+ }
68
+ if (this.state === "closed" || this.state === "remoteClose")
69
+ throw new Error("eof");
70
+ await new Promise((resolve) => this.readWaiters.push(resolve));
71
+ }
72
+ }
73
+ // write sends DATA frames, respecting the send window.
74
+ async write(data) {
75
+ this.ensureWritable();
76
+ let off = 0;
77
+ while (off < data.length) {
78
+ this.ensureWritable();
79
+ const chunk = data.subarray(off);
80
+ const allowed = await this.waitForSendWindow(chunk.length);
81
+ this.ensureWritable();
82
+ const sendChunk = chunk.subarray(0, allowed);
83
+ const flags = this.sendFlags();
84
+ this.sendWindow -= sendChunk.length;
85
+ const hdr = encodeHeader({
86
+ type: TYPE_DATA,
87
+ flags,
88
+ streamId: this.id,
89
+ length: sendChunk.length
90
+ });
91
+ await this.session.writeRaw(concatBytes([hdr, sendChunk]));
92
+ off += sendChunk.length;
93
+ }
94
+ }
95
+ // close sends FIN and transitions to local close.
96
+ async close() {
97
+ if (this.state === "closed")
98
+ return;
99
+ if (this.state === "reset")
100
+ return;
101
+ const wasRemoteClose = this.state === "remoteClose";
102
+ const flags = this.sendFlags() | FLAG_FIN;
103
+ this.state = wasRemoteClose ? "closed" : "localClose";
104
+ const hdr = encodeHeader({
105
+ type: TYPE_WINDOW_UPDATE,
106
+ flags,
107
+ streamId: this.id,
108
+ length: 0
109
+ });
110
+ try {
111
+ await this.session.writeRaw(hdr);
112
+ }
113
+ finally {
114
+ if (wasRemoteClose)
115
+ this.finalizeClosed();
116
+ }
117
+ }
118
+ // reset tears down the stream and notifies the peer.
119
+ reset(err) {
120
+ if (this.state === "reset")
121
+ return;
122
+ this.state = "reset";
123
+ this.error = err;
124
+ // Drop any buffered data to free memory on terminal errors.
125
+ this.recvQueue.length = 0;
126
+ this.recvQueueHead = 0;
127
+ this.recvQueueBytes = 0;
128
+ const ws = this.readWaiters;
129
+ this.readWaiters = [];
130
+ for (const w of ws)
131
+ w();
132
+ this.session.notifySendWindow(this.id);
133
+ void this.session.sendRst(this.id);
134
+ }
135
+ // processFlags updates the state machine for ACK/FIN/RST.
136
+ processFlags(flags) {
137
+ if ((flags & FLAG_ACK) !== 0) {
138
+ if (this.state === "synSent")
139
+ this.state = "established";
140
+ this.session.onStreamEstablished(this.id);
141
+ }
142
+ if ((flags & FLAG_FIN) !== 0) {
143
+ if (this.state === "localClose") {
144
+ this.state = "closed";
145
+ this.finalizeClosed();
146
+ }
147
+ else if (this.state === "established" || this.state === "synSent" || this.state === "synReceived") {
148
+ this.state = "remoteClose";
149
+ }
150
+ const ws = this.readWaiters;
151
+ this.readWaiters = [];
152
+ for (const w of ws)
153
+ w();
154
+ }
155
+ if ((flags & FLAG_RST) !== 0) {
156
+ this.reset(new Error("rst"));
157
+ }
158
+ }
159
+ finalizeClosed() {
160
+ const ws = this.readWaiters;
161
+ this.readWaiters = [];
162
+ for (const w of ws)
163
+ w();
164
+ this.session.onStreamClosed(this.id);
165
+ }
166
+ shiftRecv() {
167
+ if (this.recvQueueHead >= this.recvQueue.length)
168
+ return undefined;
169
+ const b = this.recvQueue[this.recvQueueHead];
170
+ this.recvQueueHead++;
171
+ if (this.recvQueueHead > 1024 && this.recvQueueHead * 2 > this.recvQueue.length) {
172
+ this.recvQueue.splice(0, this.recvQueueHead);
173
+ this.recvQueueHead = 0;
174
+ }
175
+ return b;
176
+ }
177
+ // sendFlags returns any SYN/ACK flags needed for the current state.
178
+ sendFlags() {
179
+ if (this.state === "init") {
180
+ this.state = "synSent";
181
+ return FLAG_SYN;
182
+ }
183
+ if (this.state === "synReceived") {
184
+ this.state = "established";
185
+ return FLAG_ACK;
186
+ }
187
+ return 0;
188
+ }
189
+ // sendWindowUpdate advertises the current receive window to the peer.
190
+ async sendWindowUpdate() {
191
+ const max = DEFAULT_MAX_STREAM_WINDOW;
192
+ const bufLen = this.recvQueueBytes >>> 0;
193
+ const delta = (max - bufLen) - this.recvWindow;
194
+ const flags = this.sendFlags();
195
+ if (delta < max / 2 && flags === 0)
196
+ return;
197
+ this.recvWindow += delta;
198
+ const hdr = encodeHeader({
199
+ type: TYPE_WINDOW_UPDATE,
200
+ flags,
201
+ streamId: this.id,
202
+ length: delta >>> 0
203
+ });
204
+ await this.session.writeRaw(hdr);
205
+ }
206
+ // waitForSendWindow blocks until credits are available.
207
+ async waitForSendWindow(want) {
208
+ if (want <= 0)
209
+ return 0;
210
+ while (this.sendWindow <= 0) {
211
+ this.ensureWritable();
212
+ await this.session.waitForSendWindow(this.id);
213
+ }
214
+ return Math.min(want, this.sendWindow);
215
+ }
216
+ ensureWritable() {
217
+ if (this.state === "reset")
218
+ throw new Error("stream reset");
219
+ if (this.state === "closed" || this.state === "localClose")
220
+ throw new Error("stream closed");
221
+ }
222
+ }
package/package.json ADDED
@@ -0,0 +1,112 @@
1
+ {
2
+ "name": "@floegence/flowersec-core",
3
+ "version": "0.1.1",
4
+ "description": "Flowersec core TypeScript library (browser-friendly E2EE + multiplexing over WebSocket).",
5
+ "license": "MIT",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/floegence/flowersec.git"
9
+ },
10
+ "bugs": {
11
+ "url": "https://github.com/floegence/flowersec/issues"
12
+ },
13
+ "homepage": "https://github.com/floegence/flowersec#readme",
14
+ "type": "module",
15
+ "main": "./dist/facade.js",
16
+ "types": "./dist/facade.d.ts",
17
+ "files": [
18
+ "dist/**",
19
+ "README.md",
20
+ "LICENSE",
21
+ "YAMUX_ALIGNMENT.md"
22
+ ],
23
+ "publishConfig": {
24
+ "access": "public"
25
+ },
26
+ "exports": {
27
+ ".": {
28
+ "types": "./dist/facade.d.ts",
29
+ "default": "./dist/facade.js"
30
+ },
31
+ "./node": {
32
+ "types": "./dist/node/index.d.ts",
33
+ "default": "./dist/node/index.js"
34
+ },
35
+ "./browser": {
36
+ "types": "./dist/browser/index.d.ts",
37
+ "default": "./dist/browser/index.js"
38
+ },
39
+ "./rpc": {
40
+ "types": "./dist/rpc/index.d.ts",
41
+ "default": "./dist/rpc/index.js"
42
+ },
43
+ "./yamux": {
44
+ "types": "./dist/yamux/index.d.ts",
45
+ "default": "./dist/yamux/index.js"
46
+ },
47
+ "./e2ee": {
48
+ "types": "./dist/e2ee/index.d.ts",
49
+ "default": "./dist/e2ee/index.js"
50
+ },
51
+ "./ws": {
52
+ "types": "./dist/ws/index.d.ts",
53
+ "default": "./dist/ws/index.js"
54
+ },
55
+ "./observability": {
56
+ "types": "./dist/observability/index.d.ts",
57
+ "default": "./dist/observability/index.js"
58
+ },
59
+ "./streamhello": {
60
+ "types": "./dist/streamhello/index.d.ts",
61
+ "default": "./dist/streamhello/index.js"
62
+ },
63
+ "./gen/flowersec/controlplane/*": {
64
+ "types": "./dist/gen/flowersec/controlplane/*.d.ts",
65
+ "default": "./dist/gen/flowersec/controlplane/*.js"
66
+ },
67
+ "./gen/flowersec/direct/*": {
68
+ "types": "./dist/gen/flowersec/direct/*.d.ts",
69
+ "default": "./dist/gen/flowersec/direct/*.js"
70
+ },
71
+ "./gen/flowersec/e2ee/*": {
72
+ "types": "./dist/gen/flowersec/e2ee/*.d.ts",
73
+ "default": "./dist/gen/flowersec/e2ee/*.js"
74
+ },
75
+ "./gen/flowersec/rpc/*": {
76
+ "types": "./dist/gen/flowersec/rpc/*.d.ts",
77
+ "default": "./dist/gen/flowersec/rpc/*.js"
78
+ },
79
+ "./gen/flowersec/tunnel/*": {
80
+ "types": "./dist/gen/flowersec/tunnel/*.d.ts",
81
+ "default": "./dist/gen/flowersec/tunnel/*.js"
82
+ },
83
+ "./internal": {
84
+ "types": "./dist/index.d.ts",
85
+ "default": "./dist/index.js"
86
+ }
87
+ },
88
+ "engines": {
89
+ "node": "^20.0.0 || ^22.0.0 || >=24.0.0"
90
+ },
91
+ "scripts": {
92
+ "build": "tsc -p tsconfig.build.json",
93
+ "bench": "vitest bench --run",
94
+ "test": "vitest run",
95
+ "lint": "eslint ."
96
+ },
97
+ "dependencies": {
98
+ "@noble/ciphers": "^0.6.0",
99
+ "@noble/curves": "^1.9.0",
100
+ "@noble/hashes": "^1.8.0",
101
+ "ws": "^8.18.0"
102
+ },
103
+ "devDependencies": {
104
+ "@types/node": "^22.10.0",
105
+ "@types/ws": "^8.5.12",
106
+ "@typescript-eslint/eslint-plugin": "^8.18.0",
107
+ "@typescript-eslint/parser": "^8.18.0",
108
+ "eslint": "^9.17.0",
109
+ "typescript": "^5.7.2",
110
+ "vitest": "^4.0.17"
111
+ }
112
+ }