firefly-compiler 0.4.36 → 0.4.46

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 (116) hide show
  1. package/.hintrc +4 -4
  2. package/.vscode/settings.json +4 -4
  3. package/bin/Release.ff +99 -49
  4. package/bin/firefly.mjs +1 -1
  5. package/compiler/Builder.ff +257 -257
  6. package/compiler/Compiler.ff +227 -227
  7. package/compiler/Dependencies.ff +186 -186
  8. package/compiler/DependencyLock.ff +17 -17
  9. package/compiler/JsEmitter.ff +946 -946
  10. package/compiler/LspHook.ff +202 -202
  11. package/compiler/ModuleCache.ff +178 -178
  12. package/compiler/Workspace.ff +88 -88
  13. package/core/.firefly/include/package-lock.json +394 -394
  14. package/core/.firefly/include/package.json +5 -5
  15. package/core/.firefly/include/prepare.sh +1 -1
  16. package/core/.firefly/package.ff +2 -2
  17. package/core/Array.ff +265 -265
  18. package/core/Atomic.ff +64 -64
  19. package/core/Box.ff +7 -7
  20. package/core/BrowserSystem.ff +40 -37
  21. package/core/Buffer.ff +3 -3
  22. package/core/BuildSystem.ff +148 -145
  23. package/core/Crypto.ff +96 -95
  24. package/core/Equal.ff +36 -36
  25. package/core/HttpClient.ff +87 -87
  26. package/core/Instant.ff +17 -0
  27. package/core/JsSystem.ff +69 -69
  28. package/core/Json.ff +434 -434
  29. package/core/List.ff +415 -415
  30. package/core/Lock.ff +144 -144
  31. package/core/NodeSystem.ff +189 -189
  32. package/core/Ordering.ff +161 -161
  33. package/core/Path.ff +401 -401
  34. package/core/Random.ff +134 -134
  35. package/core/RbMap.ff +216 -216
  36. package/core/Show.ff +43 -43
  37. package/core/SourceLocation.ff +68 -68
  38. package/core/Stream.ff +1 -1
  39. package/core/Task.ff +141 -141
  40. package/experimental/benchmarks/ListGrab.ff +23 -23
  41. package/experimental/benchmarks/ListGrab.java +55 -55
  42. package/experimental/benchmarks/Pyrotek45.ff +30 -30
  43. package/experimental/benchmarks/Pyrotek45.java +64 -64
  44. package/experimental/bidirectional/Bidi.ff +88 -88
  45. package/experimental/random/Index.ff +53 -53
  46. package/experimental/random/Process.ff +120 -120
  47. package/experimental/random/RunLength.ff +3 -3
  48. package/experimental/random/Scrape.ff +51 -51
  49. package/experimental/random/Symbols.ff +73 -73
  50. package/experimental/random/Tensor.ff +52 -52
  51. package/experimental/random/Units.ff +36 -36
  52. package/experimental/s3/S3TestAuthorizationHeader.ff +38 -38
  53. package/experimental/s3/S3TestPut.ff +15 -15
  54. package/experimental/tests/TestJson.ff +26 -26
  55. package/firefly.sh +0 -0
  56. package/fireflysite/Main.ff +13 -13
  57. package/lsp/.firefly/package.ff +1 -1
  58. package/lsp/CompletionHandler.ff +811 -811
  59. package/lsp/Handler.ff +714 -714
  60. package/lsp/HoverHandler.ff +79 -79
  61. package/lsp/LanguageServer.ff +272 -272
  62. package/lsp/SignatureHelpHandler.ff +55 -55
  63. package/lsp/SymbolHandler.ff +181 -181
  64. package/lsp/TestReferences.ff +16 -16
  65. package/lsp/TestReferencesCase.ff +7 -7
  66. package/lsp/stderr.txt +1 -1
  67. package/lsp/stdout.txt +34 -34
  68. package/lux/.firefly/package.ff +1 -1
  69. package/lux/Css.ff +648 -648
  70. package/lux/CssTest.ff +48 -48
  71. package/lux/Lux.ff +487 -487
  72. package/lux/LuxEvent.ff +116 -116
  73. package/lux/Main.ff +128 -128
  74. package/lux/Main2.ff +144 -144
  75. package/output/js/ff/compiler/Builder.mjs +43 -43
  76. package/output/js/ff/compiler/Dependencies.mjs +3 -3
  77. package/output/js/ff/core/Array.mjs +59 -59
  78. package/output/js/ff/core/Atomic.mjs +36 -36
  79. package/output/js/ff/core/BrowserSystem.mjs +19 -11
  80. package/output/js/ff/core/Buffer.mjs +7 -7
  81. package/output/js/ff/core/BuildSystem.mjs +38 -30
  82. package/output/js/ff/core/Crypto.mjs +67 -68
  83. package/output/js/ff/core/HttpClient.mjs +24 -24
  84. package/output/js/ff/core/Instant.mjs +38 -0
  85. package/output/js/ff/core/Json.mjs +147 -147
  86. package/output/js/ff/core/List.mjs +50 -50
  87. package/output/js/ff/core/Lock.mjs +97 -97
  88. package/output/js/ff/core/NodeSystem.mjs +77 -77
  89. package/output/js/ff/core/Ordering.mjs +8 -8
  90. package/output/js/ff/core/Path.mjs +231 -231
  91. package/output/js/ff/core/Random.mjs +56 -56
  92. package/output/js/ff/core/Stream.mjs +2 -2
  93. package/output/js/ff/core/Task.mjs +31 -31
  94. package/package.json +29 -29
  95. package/rpc/.firefly/package.ff +1 -1
  96. package/rpc/Rpc.ff +69 -69
  97. package/s3/.firefly/package.ff +1 -0
  98. package/{experimental/s3 → s3}/S3.ff +92 -92
  99. package/unsafejs/UnsafeJs.ff +19 -19
  100. package/vscode/LICENSE.txt +21 -21
  101. package/vscode/Prepublish.ff +15 -15
  102. package/vscode/README.md +16 -16
  103. package/vscode/client/package.json +22 -22
  104. package/vscode/client/src/extension.ts +104 -104
  105. package/vscode/icons/firefly-icon.svg +10 -10
  106. package/vscode/language-configuration.json +61 -61
  107. package/vscode/package-lock.json +3623 -3623
  108. package/vscode/package.json +160 -160
  109. package/vscode/snippets.json +241 -241
  110. package/webserver/.firefly/include/package-lock.json +16 -16
  111. package/webserver/.firefly/include/package.json +5 -5
  112. package/webserver/.firefly/package.ff +2 -2
  113. package/webserver/WebServer.ff +685 -685
  114. package/websocket/.firefly/package.ff +1 -1
  115. package/websocket/WebSocket.ff +131 -131
  116. package/crypto/SubtleCrypto.ff +0 -149
@@ -1,685 +1,685 @@
1
- capability WebServer(
2
- intenalSystem: NodeSystem
3
- internalHost: String
4
- internalPort: Int
5
- mutable internalJsWebSockets: JsValue
6
- mutable internalJsWildcard: JsValue
7
- mutable internalJsHosts: Array[JsValue]
8
- mutable internalJsApp: JsValue
9
- )
10
-
11
- class WebRequest[T](
12
- internalJsApp: JsValue
13
- internalJsContext: JsValue
14
- internalMethod: String
15
- internalPath: String
16
- internalQuery: String
17
- internalHeaders: StringMap[String]
18
- internalJsResponse: JsValue
19
- internalResponse: T
20
- internalResponseHeaders: StringMap[String]
21
- internalResponseChunks: Array[JsValue]
22
- mutable internalResponseStatus: String
23
- mutable internalCloseConnection: Bool
24
- mutable internalResolveFlush: JsValue
25
- mutable internalContentSize: Int
26
- mutable internalResponded: Bool
27
- )
28
-
29
- capability WebResponse {}
30
-
31
- data MultipartField {}
32
-
33
- capability WebSocketConnection {}
34
-
35
- new(
36
- system: NodeSystem
37
- host: String
38
- port: Int
39
- ): WebServer {
40
- WebServer(system, host, port, system.js().null(), system.js().null(), [].toArray(), system.js().null())
41
- }
42
-
43
- extend self: WebServer {
44
-
45
- enableWebSockets(
46
- maxPayloadBytes: Int = 16384
47
- maxBackpressureBytes: Int = 65536
48
- closeDueToMaxBackpressureBytes: Bool = False
49
- maxLifetime: Option[Duration] = None
50
- idleTimeout: Option[Duration] = None
51
- ) {
52
- self.internalJsWebSockets = self.intenalSystem.js().object()
53
- self.internalJsWebSockets.set("maxPayloadLength", maxPayloadBytes)
54
- self.internalJsWebSockets.set("maxBackpressureBytes", maxBackpressureBytes)
55
- self.internalJsWebSockets.set("closeOnBackpressureLimit", closeDueToMaxBackpressureBytes)
56
- maxLifetime.each {self.internalJsWebSockets.set("maxLifetime", (_.seconds / 60).round().toInt())}
57
- idleTimeout.each {self.internalJsWebSockets.set("idleTimeout", _.seconds.round().toInt())}
58
- }
59
-
60
- addHost(
61
- hostname: String
62
- key: Option[Path] = None
63
- certificate: Option[Path] = None
64
- certificateAuthority: Option[Path] = None
65
- passphrase: Option[String] = None
66
- dhParameters: Option[Path] = None
67
- ciphers: Option[String] = None
68
- preferLowMemoryUsage: Bool = False
69
- ) {
70
- let object = self.intenalSystem.js().object().with("hostname", hostname)
71
- key.each {object.set("key_file_name", _.absolute())}
72
- certificate.each {object.set("cert_file_name", _.absolute())}
73
- certificateAuthority.each {object.set("ca_file_name", _.absolute())}
74
- passphrase.each {object.set("passphrase", _)}
75
- dhParameters.each {object.set("dh_params_file_name", _.absolute())}
76
- ciphers.each {object.set("ssl_ciphers", _)}
77
- if(preferLowMemoryUsage) {object.set("ssl_prefer_low_memory_usage", True)}
78
- self.internalJsHosts.push(object)
79
- }
80
-
81
- setWildcard(
82
- key: Option[Path] = None
83
- certificate: Option[Path] = None
84
- certificateAuthority: Option[Path] = None
85
- passphrase: Option[String] = None
86
- dhParameters: Option[Path] = None
87
- ciphers: Option[String] = None
88
- preferLowMemoryUsage: Bool = False
89
- ) {
90
- let object = self.intenalSystem.js().object()
91
- key.each {object.set("key_file_name", _.absolute())}
92
- certificate.each {object.set("cert_file_name", _.absolute())}
93
- certificateAuthority.each {object.set("ca_file_name", _.absolute())}
94
- passphrase.each {object.set("passphrase", _)}
95
- dhParameters.each {object.set("dh_params_file_name", _.absolute())}
96
- ciphers.each {object.set("ssl_ciphers", _)}
97
- if(preferLowMemoryUsage) {object.set("ssl_prefer_low_memory_usage", True)}
98
- self.internalJsWildcard = object
99
- }
100
-
101
- listen(handler: WebRequest[WebResponse] => Unit)
102
- target node async """
103
- import * as uws from 'uWebSockets.js'
104
- ff_core_Task.Task_throwIfAborted($task);
105
- await new Promise((resolveServer, rejectServer) => {
106
- self_.internalJsApp_ = self_.internalJsWilcard_ ? uws.SSLApp(self_.internalJsWilcard_)
107
- : self_.internalJsHosts_.array.some(h => h.key_file_name) ? uws.SSLApp() : uws.App();
108
- self_.internalJsHosts_.array.forEach(h => self_.internalJsApp_.addServerName(h.hostname, h));
109
- const handleRequest = (res, req, context) => {
110
- let abortedByUws = false;
111
- const method = req.getMethod();
112
- const path = req.getUrl();
113
- const query = req.getQuery() || '';
114
- const headers = new Map();
115
- req.forEach((key, value) => headers.set(key, value));
116
- const chunks = internalArray_();
117
- const task = ff_core_Task.Task_spawn($task, async task => {
118
- try {
119
- ff_core_Task.Task_throwIfAborted(task);
120
- const webRequest = WebRequest(
121
- self_.internalJsApp_, context,
122
- method, path, query, headers,
123
- res, null,
124
- new Map(), internalArray_(),
125
- "200 OK", false, null, -1, false
126
- );
127
- await handler_(webRequest, task);
128
- ff_core_Task.Task_throwIfAborted(task);
129
- if(context == null && !webRequest.internalResponded_ && webRequest.internalContentSize_ >= 0) {
130
- await WebRequest_flush$(webRequest, task);
131
- ff_core_Task.Task_throwIfAborted(task);
132
- }
133
- if(context == null && !webRequest.internalResponded_) await new Promise((resolve, reject) => {
134
- res.cork(() => {
135
- try {
136
- ff_core_Task.Task_throwIfAborted(task);
137
- if(!webRequest.internalResolveFlush_) {
138
- res.writeStatus(webRequest.internalResponseStatus_);
139
- webRequest.internalResponseHeaders_.forEach((value, key) => res.writeHeader(key, value));
140
- }
141
- const chunk = internalChunksToChunk_(webRequest.internalResponseChunks_);
142
- res.end(chunk, webRequest.internalCloseConnection_);
143
- resolve();
144
- } catch(e) {
145
- reject(e);
146
- }
147
- });
148
- });
149
- } catch(e) {
150
- if(!abortedByUws) console.error(e);
151
- if(!abortedByUws && context == null) res.cork(() => {
152
- if(!abortedByUws) res.writeStatus("500 Internal Server Error");
153
- if(!abortedByUws) res.end();
154
- });
155
- }
156
- });
157
- res.onAborted(() => {
158
- abortedByUws = true;
159
- ff_core_Task.Task_abort(task);
160
- });
161
- }
162
- self_.internalJsApp_.any('/*', handleRequest);
163
- if(self_.internalJsWebSockets_) self_.internalJsApp_.ws("/*", {
164
- ...self_.internalJsWebSockets_,
165
- upgrade: handleRequest,
166
- open: ws => ws.getUserData().onOpen(ws),
167
- message: (ws, m, b) => ws.getUserData().onMessage(m, b),
168
- close: ws => ws.getUserData().onClose(),
169
- });
170
- self_.internalJsApp_.listen(self_.internalHost_, self_.internalPort_, listenSocket => {
171
- if(listenSocket) {
172
- const abort = () => {
173
- $task.controller.signal.removeEventListener('abort', abort);
174
- uws.us_listen_socket_close(listenSocket);
175
- self_.internalJsApp_.close();
176
- }
177
- $task.controller.signal.addEventListener('abort', abort);
178
- } else {
179
- rejectServer();
180
- }
181
- });
182
- });
183
- """
184
-
185
- publishBuffer(topic: String, message: Buffer) // Should this be corked?
186
- target js async """
187
- if(self_.internalJsApp_) self_.internalJsApp_.publish(topic_, message_.buffer, true);
188
- """
189
-
190
- publishText(topic: String, message: String) // Should this be corked?
191
- target js async """
192
- if(self_.internalJsApp_) self_.internalJsApp_.publish(topic_, message_, false);
193
- """
194
-
195
- }
196
-
197
- extend self[T]: WebRequest[T] {
198
-
199
- readMethod(): String {
200
- self.internalMethod
201
- }
202
-
203
- readPath(): String {
204
- self.internalPath
205
- }
206
-
207
- readRawQueryString(): String {
208
- self.internalQuery
209
- }
210
-
211
- readQuery(name: String): Option[String]
212
- target node sync """
213
- if(self_.internalQuery_.length === 0) return ff_core_Option.None();
214
- const ps = self_.internalQuery_.split('&');
215
- const n = name_ + '=';
216
- const r = ps.find(p => p === name_ || p.startsWith(n));
217
- if(r == null) return ff_core_Option.None();
218
- return ff_core_Option.Some(decodeURIComponent(r.slice(name_.length + 1)));
219
- """
220
-
221
- readHeader(lowerCaseName: String): Option[String] {
222
- self.internalHeaders.get(lowerCaseName)
223
- }
224
-
225
- eachHeader(body: (String, String) => Unit) {
226
- self.internalHeaders.each {body(_, _)}
227
- }
228
-
229
- }
230
-
231
- extend self[T]: WebRequest[T] {
232
-
233
- writeStatus(codeAndMessage: String) {
234
- self.internalResponseStatus = codeAndMessage
235
- }
236
-
237
- writeHeader(name: String, value: String) {
238
- self.internalResponseHeaders.set(name, value)
239
- }
240
-
241
- writeContentSize(contentSize: Int) {
242
- if(contentSize >= 0) {
243
- self.internalContentSize = contentSize
244
- }
245
- }
246
-
247
- writeText(data: String) {
248
- self.internalResponseChunks.push(internalTextChunk(data))
249
- }
250
-
251
- writeLine(data: String) {
252
- self.writeText(data + "\n")
253
- }
254
-
255
- writeBuffer(data: Buffer) {
256
- self.internalResponseChunks.push(internalBufferChunk(data))
257
- }
258
-
259
- writeStream(data: Stream[Buffer]) {
260
- data.each {self.writeBuffer(_)}
261
- }
262
-
263
- }
264
-
265
- extend self[T]: WebRequest[T] {
266
-
267
- publishBuffer(topic: String, message: Buffer) // Should this be corked?
268
- target js sync """
269
- self_.internalJsApp_.publish(topic_, message_.buffer, true);
270
- """
271
-
272
- publishText(topic: String, message: String) // Should this be corked?
273
- target js sync """
274
- self_.internalJsApp_.publish(topic_, message_, false);
275
- """
276
-
277
- }
278
-
279
- extend self[T]: WebRequest[T] {
280
-
281
- closeConnection() {
282
- self.internalCloseConnection = True
283
- }
284
-
285
- readRemoteAddress(): String
286
- target js sync """
287
- return new TextDecoder().decode(self_.internalJsResponse_.getRemoteAddressAsText())
288
- """
289
-
290
- readProxiedRemoteAddress(): String
291
- target js sync """
292
- return new TextDecoder().decode(self_.internalJsResponse_.getProxiedRemoteAddressAsText())
293
- """
294
-
295
- }
296
-
297
- extend self: WebRequest[WebResponse] {
298
-
299
- flush()
300
- target js async """
301
- if(!self_.internalResolveFlush_) {
302
- self_.internalResolveFlush_ = () => {};
303
- self_.internalJsResponse_.cork(() => {
304
- self_.internalJsResponse_.writeStatus(self_.internalResponseStatus_);
305
- self_.internalResponseHeaders_.forEach((value, key) =>
306
- self_.internalJsResponse_.writeHeader(key, value)
307
- );
308
- self_.internalJsResponse_.onWritable(() => {
309
- self_.internalResolveFlush_();
310
- return true;
311
- });
312
- });
313
- }
314
- while(self_.internalResponseChunks_.array.length !== 0) {
315
- await new Promise((resolve, reject) => {
316
- self_.internalJsResponse_.cork(() => {
317
- let backpressure = false;
318
- let i = 0;
319
- for(i = 0; i < self_.internalResponseChunks_.array.length && !backpressure; i++) {
320
- let chunk = self_.internalResponseChunks_.array[i];
321
- if(chunk && (chunk.byteLength || chunk.length)) {
322
- if(typeof chunk !== 'string') {
323
- chunk = new Uint8Array(chunk.buffer, chunk.byteOffset, chunk.byteLength);
324
- } else if(self_.internalContentSize_ >= 0) {
325
- chunk = new TextEncoder().encode(chunk);
326
- }
327
- if(self_.internalContentSize_ >= 0) {
328
- const lastOffset = self_.internalJsResponse_.getWriteOffset();
329
- const [ok, responded] = self_.internalJsResponse_.tryEnd(chunk, self_.internalContentSize_);
330
- if(responded) self_.internalResponded_ = true;
331
- if(!ok) {
332
- const written = self_.internalJsResponse_.getWriteOffset() - lastOffset;
333
- backpressure = true;
334
- self_.internalResponseChunks_.array[i] =
335
- new Uint8Array(chunk.buffer, chunk.byteOffset + written, chunk.byteLength - written);
336
- i -= 1;
337
- }
338
- } else {
339
- backpressure = !self_.internalJsResponse_.write(chunk);
340
- }
341
- }
342
- }
343
- if(i > 0) self_.internalResponseChunks_.array.splice(0, i);
344
- if(!backpressure) {
345
- resolve();
346
- } else {
347
- self_.internalResolveFlush_ = resolve;
348
- }
349
- });
350
- });
351
- }
352
- """
353
-
354
- flushText(data: String) {
355
- self.writeText(data)
356
- self.flush()
357
- }
358
-
359
- flushBuffer(data: Buffer) {
360
- self.writeBuffer(data)
361
- self.flush()
362
- }
363
-
364
- flushStream(data: Stream[Buffer]) {
365
- data.each {self.flushBuffer(_)}
366
- }
367
-
368
- }
369
-
370
- extend self: WebRequest[WebResponse] {
371
-
372
- readText(encoding: String = "utf8"): String {
373
- self.readStream(1073741823).toString(encoding)
374
- }
375
-
376
- readBuffer(): Buffer {
377
- self.readStream(1073741823).toBuffer()
378
- }
379
-
380
- readStream(maxPendingBytes: Int = 65536): Stream[Buffer]
381
- target js async """
382
- const front = [];
383
- const back = [];
384
- let pendingBytes = 0
385
- let closed = false;
386
- let callResolve = null;
387
- let callReject = null;
388
- self_.internalJsResponse_.onData((chunk, isLast) => {
389
- if(closed) return;
390
- if(isLast) closed = true;
391
- else chunk = cunk.slice();
392
- pendingBytes += chunk.byteLength;
393
- back.push(new DataView(chunk));
394
- if(pendingBytes >= maxPendingBytes_) self_.internalJsResponse_.pause();
395
- if(callResolve) callResolve();
396
- });
397
- const abort = () => {
398
- $task.controller.signal.removeEventListener('abort', abort);
399
- if(callReject) callReject();
400
- }
401
- $task.controller.signal.addEventListener('abort', abort);
402
- return ff_core_Stream.Stream(
403
- async () => {
404
- while(true) {
405
- ff_core_Task.Task_throwIfAborted($task);
406
- if(front.length === 0) {
407
- while(back.length !== 0) front.push(back.pop());
408
- }
409
- if(front.length !== 0) {
410
- const chunk = front.pop();
411
- const paused = pendingBytes >= maxPendingBytes_;
412
- pendingBytes -= chunk.byteLength;
413
- if(!closed && paused && pendingBytes < maxPendingBytes_) self_.internalJsResponse_.unpause();
414
- return ff_core_Option.Some(chunk);
415
- } else if(closed) {
416
- return ff_core_Option.None();
417
- } else {
418
- await new Promise((resolve, reject) => {
419
- callResolve = resolve;
420
- callReject = reject;
421
- });
422
- callResolve = null;
423
- callReject = null;
424
- }
425
- }
426
- },
427
- async () => {
428
- closed = true;
429
- ff_core_Task.Task_throwIfAborted($task);
430
- if(callResolve) callResolve();
431
- }
432
- )
433
- """
434
-
435
- }
436
-
437
- extend self[T]: WebRequest[T] {
438
-
439
- needsWebSocket(): Bool
440
- target js sync """
441
- return self_.internalJsContext_ != null;
442
- """
443
-
444
- }
445
-
446
- extend self: WebRequest[WebResponse] {
447
-
448
- openWebSocket(): WebSocketConnection
449
- target js async """
450
- ff_core_Task.Task_throwIfAborted($task);
451
- const userData = {};
452
- const wrapper = {front: [], back: [], listeners: [], task: $task};
453
- const promise = new Promise((resolve, reject) => {
454
- const abort = () => {wrapper.webSocket = null; reject()};
455
- userData.onOpen = ws => {wrapper.webSocket = ws; resolve(wrapper)};
456
- userData.onClose = () => {
457
- $task.controller.signal.removeEventListener('abort', abort);
458
- abort();
459
- for(let i = 0; i < wrapper.listeners.length; i++) wrapper.listeners[i]();
460
- wrapper.listeners.length = 0;
461
- };
462
- userData.onMessage = (message, isBinary) => {
463
- wrapper.back.push(isBinary ? new DataView(message) : new TextDecoder().decode(message));
464
- for(let i = 0; i < wrapper.listeners.length; i++) wrapper.listeners[i]();
465
- wrapper.listeners.length = 0;
466
- };
467
- $task.controller.signal.addEventListener('abort', abort);
468
- });
469
- self_.internalJsResponse_.cork(() => {
470
- ff_core_Task.Task_throwIfAborted($task);
471
- self_.internalJsResponse_.upgrade(
472
- userData,
473
- self_.internalHeaders_.get('sec-websocket-key'),
474
- self_.internalHeaders_.get('sec-websocket-protocol'),
475
- self_.internalHeaders_.get('sec-websocket-extensions'),
476
- self_.internalJsContext_
477
- )
478
- });
479
- return await promise;
480
- """
481
-
482
- }
483
-
484
- extend self: WebSocketConnection {
485
-
486
- readText(encoding: String = "utf8"): Option[String] {
487
- self.readAny {_} {_.toString(encoding)}
488
- }
489
-
490
- readBuffer(): Option[Buffer] {
491
- self.readAny {_.toBuffer()} {_}
492
- }
493
-
494
- readAny[T](fromText: String => T, fromBuffer: Buffer => T): Option[T]
495
- target node async """
496
- ff_core_Task.Task_throwIfAborted($task);
497
- while(self_.front.length === 0) {
498
- while(self_.back.length !== 0) {
499
- self_.front.push(self_.back.pop());
500
- }
501
- if(self_.front.length !== 0) break;
502
- if(self_.webSocket == null) return ff_core_Option.None();
503
- let abort = null;
504
- try {
505
- await new Promise((resolve, reject) => {
506
- self_.listeners.push(resolve);
507
- if($task !== self_.task) {
508
- abort = () => {
509
- self_.listeners = self_.listeners.filter(l => l != resolve);
510
- reject();
511
- };
512
- $task.controller.signal.addEventListener('abort', abort);
513
- }
514
- });
515
- } finally {
516
- if($task !== self_.task) {
517
- $task.controller.signal.removeEventListener('abort', abort);
518
- }
519
- }
520
- }
521
- const data = self_.front.pop();
522
- if(typeof data === 'string') return ff_core_Option.Some(await fromText_(data));
523
- return ff_core_Option.Some(await fromBuffer_(data));
524
- """
525
-
526
- readStream(): Stream[Buffer] {
527
- Stream {self.readBuffer()} {self.close()}
528
- }
529
-
530
- writeBuffer(message: Buffer)
531
- target node async """
532
- await internalSend_$(self_, () => self_.webSocket.send(message_.buffer, true) !== 2, $task);
533
- """
534
-
535
- writeText(message: String)
536
- target node async """
537
- await internalSend_$(self_, () => self_.webSocket.send(message_, false) !== 2, $task);
538
- """
539
-
540
- publishBuffer(topic: String, message: Buffer)
541
- target node async """
542
- await internalSend_$(self_, () => self_.webSocket.publish(topic_, message_.buffer, true), $task);
543
- """
544
-
545
- publishText(topic: String, message: String)
546
- target node async """
547
- await internalSend_$(self_, () => self_.webSocket.publish(topic_, message_, false), $task);
548
- """
549
-
550
- subscribe(topic: String)
551
- target node async """
552
- if(self_.webSocket == null) throw new Error("WebSocket is closed");
553
- self_.webSocket.subscribe(topic_);
554
- """
555
-
556
- unsubscribe(topic: String)
557
- target node async """
558
- if(self_.webSocket == null) throw new Error("WebSocket is closed");
559
- self_.webSocket.unsubscribe(topic_);
560
- """
561
-
562
- isSubscribedTo(topic: String): Bool
563
- target node async """
564
- if(self_.webSocket == null) throw new Error("WebSocket is closed");
565
- return self_.webSocket.isSubscribed(topic_);
566
- """
567
-
568
- subscriptions(): Array[String]
569
- target node async """
570
- if(self_.webSocket == null) throw new Error("WebSocket is closed");
571
- return self_.webSocket.getTopics();
572
- """
573
-
574
- close(): Unit
575
- target node async """
576
- if(self_.webSocket != null) {
577
- self_.webSocket.close();
578
- self_.webSocket = null;
579
- }
580
- """
581
-
582
- }
583
-
584
- internalSend[T](self: WebSocketConnection, send: () => Bool): JsValue
585
- target node async """
586
- let abort = null;
587
- try {
588
- await new Promise((resolve, reject) => {
589
- if(self_.webSocket == null) new Error("WebSocket is closed");
590
- else self_.webSocket.cork(() => {
591
- if($task !== self_.task) {
592
- abort = () => {
593
- self_.listeners = self_.listeners.filter(l => l != resolve);
594
- reject();
595
- };
596
- $task.controller.signal.addEventListener('abort', abort);
597
- }
598
- if(self_.webSocket == null) {
599
- reject(new Error("WebSocket is closed"));
600
- } else if(!send_()) {
601
- reject(new Error("Dropped message due to backpressure"));
602
- } else {
603
- resolve();
604
- }
605
- })
606
- });
607
- } finally {
608
- if($task !== self_.task) {
609
- $task.controller.signal.removeEventListener('abort', abort);
610
- }
611
- }
612
- """
613
-
614
- internalArray[T](): Array[T] {
615
- Array.new()
616
- }
617
-
618
- internalTextChunk(data: String): JsValue
619
- target js sync """
620
- return data_;
621
- """
622
-
623
- internalBufferChunk(data: Buffer): JsValue
624
- target js sync """
625
- return data_;
626
- """
627
-
628
- internalChunksToChunk(chunks: Array[JsValue]): JsValue
629
- target js sync """
630
- const chunks = chunks_.array;
631
- if(chunks.length === 0) return;
632
- const firstIsString = typeof chunks[0] === 'string';
633
- if(firstIsString && chunks.length === 1) return chunks[0];
634
- if(firstIsString && chunks.every(c => typeof c === 'string')) {
635
- let result = chunks[0];
636
- for(let i = 1; i < chunks.length; i++) {
637
- result += chunks[i];
638
- }
639
- return result;
640
- }
641
- let totalLength = 0;
642
- for(let i = 0; i < chunks.length; i++) {
643
- if(typeof chunks[i] === 'string') chunks[i] = new TextEncoder().encode(chunks[i]);
644
- totalLength += chunks[i].byteLength;
645
- }
646
- const result = new Uint8Array(totalLength);
647
- let offset = 0;
648
- for(let i = 0; i < chunks.length; i++) {
649
- const chunk = chunks[i];
650
- const uint8Array = new Uint8Array(chunk.buffer, chunk.byteOffset, chunk.byteLength);
651
- result.set(uint8Array, offset);
652
- offset += chunk.byteLength;
653
- }
654
- return result;
655
- """
656
-
657
- parseMultipartFields(data: Buffer, contentType: String): Option[Array[MultipartField]]
658
- target js sync """
659
- const parts = getParts(data_, contentType_);
660
- return parts == null ? ff_core_Option.None() : ff_core_Option.Some(parts);
661
- """
662
-
663
- extend self: MultipartField {
664
-
665
- data(): Buffer
666
- target js sync """
667
- return new DataView(self_.data);
668
- """
669
-
670
- name(): String
671
- target js sync """
672
- return self_.name;
673
- """
674
-
675
- type(): Option[String]
676
- target js sync """
677
- return self_.type == null ? ff_core_Option.None() : ff_core_Option.Some(self_.type);
678
- """
679
-
680
- filename(): Option[String]
681
- target js sync """
682
- return self_.filename == null ? ff_core_Option.None() : ff_core_Option.Some(self_.filename);
683
- """
684
-
685
- }
1
+ capability WebServer(
2
+ intenalSystem: NodeSystem
3
+ internalHost: String
4
+ internalPort: Int
5
+ mutable internalJsWebSockets: JsValue
6
+ mutable internalJsWildcard: JsValue
7
+ mutable internalJsHosts: Array[JsValue]
8
+ mutable internalJsApp: JsValue
9
+ )
10
+
11
+ class WebRequest[T](
12
+ internalJsApp: JsValue
13
+ internalJsContext: JsValue
14
+ internalMethod: String
15
+ internalPath: String
16
+ internalQuery: String
17
+ internalHeaders: StringMap[String]
18
+ internalJsResponse: JsValue
19
+ internalResponse: T
20
+ internalResponseHeaders: StringMap[String]
21
+ internalResponseChunks: Array[JsValue]
22
+ mutable internalResponseStatus: String
23
+ mutable internalCloseConnection: Bool
24
+ mutable internalResolveFlush: JsValue
25
+ mutable internalContentSize: Int
26
+ mutable internalResponded: Bool
27
+ )
28
+
29
+ capability WebResponse {}
30
+
31
+ data MultipartField {}
32
+
33
+ capability WebSocketConnection {}
34
+
35
+ new(
36
+ system: NodeSystem
37
+ host: String
38
+ port: Int
39
+ ): WebServer {
40
+ WebServer(system, host, port, system.js().null(), system.js().null(), [].toArray(), system.js().null())
41
+ }
42
+
43
+ extend self: WebServer {
44
+
45
+ enableWebSockets(
46
+ maxPayloadBytes: Int = 16384
47
+ maxBackpressureBytes: Int = 65536
48
+ closeDueToMaxBackpressureBytes: Bool = False
49
+ maxLifetime: Option[Duration] = None
50
+ idleTimeout: Option[Duration] = None
51
+ ) {
52
+ self.internalJsWebSockets = self.intenalSystem.js().object()
53
+ self.internalJsWebSockets.set("maxPayloadLength", maxPayloadBytes)
54
+ self.internalJsWebSockets.set("maxBackpressureBytes", maxBackpressureBytes)
55
+ self.internalJsWebSockets.set("closeOnBackpressureLimit", closeDueToMaxBackpressureBytes)
56
+ maxLifetime.each {self.internalJsWebSockets.set("maxLifetime", (_.seconds / 60).round().toInt())}
57
+ idleTimeout.each {self.internalJsWebSockets.set("idleTimeout", _.seconds.round().toInt())}
58
+ }
59
+
60
+ addHost(
61
+ hostname: String
62
+ key: Option[Path] = None
63
+ certificate: Option[Path] = None
64
+ certificateAuthority: Option[Path] = None
65
+ passphrase: Option[String] = None
66
+ dhParameters: Option[Path] = None
67
+ ciphers: Option[String] = None
68
+ preferLowMemoryUsage: Bool = False
69
+ ) {
70
+ let object = self.intenalSystem.js().object().with("hostname", hostname)
71
+ key.each {object.set("key_file_name", _.absolute())}
72
+ certificate.each {object.set("cert_file_name", _.absolute())}
73
+ certificateAuthority.each {object.set("ca_file_name", _.absolute())}
74
+ passphrase.each {object.set("passphrase", _)}
75
+ dhParameters.each {object.set("dh_params_file_name", _.absolute())}
76
+ ciphers.each {object.set("ssl_ciphers", _)}
77
+ if(preferLowMemoryUsage) {object.set("ssl_prefer_low_memory_usage", True)}
78
+ self.internalJsHosts.push(object)
79
+ }
80
+
81
+ setWildcard(
82
+ key: Option[Path] = None
83
+ certificate: Option[Path] = None
84
+ certificateAuthority: Option[Path] = None
85
+ passphrase: Option[String] = None
86
+ dhParameters: Option[Path] = None
87
+ ciphers: Option[String] = None
88
+ preferLowMemoryUsage: Bool = False
89
+ ) {
90
+ let object = self.intenalSystem.js().object()
91
+ key.each {object.set("key_file_name", _.absolute())}
92
+ certificate.each {object.set("cert_file_name", _.absolute())}
93
+ certificateAuthority.each {object.set("ca_file_name", _.absolute())}
94
+ passphrase.each {object.set("passphrase", _)}
95
+ dhParameters.each {object.set("dh_params_file_name", _.absolute())}
96
+ ciphers.each {object.set("ssl_ciphers", _)}
97
+ if(preferLowMemoryUsage) {object.set("ssl_prefer_low_memory_usage", True)}
98
+ self.internalJsWildcard = object
99
+ }
100
+
101
+ listen(handler: WebRequest[WebResponse] => Unit)
102
+ target node async """
103
+ import * as uws from 'uWebSockets.js'
104
+ ff_core_Task.Task_throwIfAborted($task);
105
+ await new Promise((resolveServer, rejectServer) => {
106
+ self_.internalJsApp_ = self_.internalJsWilcard_ ? uws.SSLApp(self_.internalJsWilcard_)
107
+ : self_.internalJsHosts_.array.some(h => h.key_file_name) ? uws.SSLApp() : uws.App();
108
+ self_.internalJsHosts_.array.forEach(h => self_.internalJsApp_.addServerName(h.hostname, h));
109
+ const handleRequest = (res, req, context) => {
110
+ let abortedByUws = false;
111
+ const method = req.getMethod();
112
+ const path = req.getUrl();
113
+ const query = req.getQuery() || '';
114
+ const headers = new Map();
115
+ req.forEach((key, value) => headers.set(key, value));
116
+ const chunks = internalArray_();
117
+ const task = ff_core_Task.Task_spawn($task, async task => {
118
+ try {
119
+ ff_core_Task.Task_throwIfAborted(task);
120
+ const webRequest = WebRequest(
121
+ self_.internalJsApp_, context,
122
+ method, path, query, headers,
123
+ res, null,
124
+ new Map(), internalArray_(),
125
+ "200 OK", false, null, -1, false
126
+ );
127
+ await handler_(webRequest, task);
128
+ ff_core_Task.Task_throwIfAborted(task);
129
+ if(context == null && !webRequest.internalResponded_ && webRequest.internalContentSize_ >= 0) {
130
+ await WebRequest_flush$(webRequest, task);
131
+ ff_core_Task.Task_throwIfAborted(task);
132
+ }
133
+ if(context == null && !webRequest.internalResponded_) await new Promise((resolve, reject) => {
134
+ res.cork(() => {
135
+ try {
136
+ ff_core_Task.Task_throwIfAborted(task);
137
+ if(!webRequest.internalResolveFlush_) {
138
+ res.writeStatus(webRequest.internalResponseStatus_);
139
+ webRequest.internalResponseHeaders_.forEach((value, key) => res.writeHeader(key, value));
140
+ }
141
+ const chunk = internalChunksToChunk_(webRequest.internalResponseChunks_);
142
+ res.end(chunk, webRequest.internalCloseConnection_);
143
+ resolve();
144
+ } catch(e) {
145
+ reject(e);
146
+ }
147
+ });
148
+ });
149
+ } catch(e) {
150
+ if(!abortedByUws) console.error(e);
151
+ if(!abortedByUws && context == null) res.cork(() => {
152
+ if(!abortedByUws) res.writeStatus("500 Internal Server Error");
153
+ if(!abortedByUws) res.end();
154
+ });
155
+ }
156
+ });
157
+ res.onAborted(() => {
158
+ abortedByUws = true;
159
+ ff_core_Task.Task_abort(task);
160
+ });
161
+ }
162
+ self_.internalJsApp_.any('/*', handleRequest);
163
+ if(self_.internalJsWebSockets_) self_.internalJsApp_.ws("/*", {
164
+ ...self_.internalJsWebSockets_,
165
+ upgrade: handleRequest,
166
+ open: ws => ws.getUserData().onOpen(ws),
167
+ message: (ws, m, b) => ws.getUserData().onMessage(m, b),
168
+ close: ws => ws.getUserData().onClose(),
169
+ });
170
+ self_.internalJsApp_.listen(self_.internalHost_, self_.internalPort_, listenSocket => {
171
+ if(listenSocket) {
172
+ const abort = () => {
173
+ $task.controller.signal.removeEventListener('abort', abort);
174
+ uws.us_listen_socket_close(listenSocket);
175
+ self_.internalJsApp_.close();
176
+ }
177
+ $task.controller.signal.addEventListener('abort', abort);
178
+ } else {
179
+ rejectServer();
180
+ }
181
+ });
182
+ });
183
+ """
184
+
185
+ publishBuffer(topic: String, message: Buffer) // Should this be corked?
186
+ target js async """
187
+ if(self_.internalJsApp_) self_.internalJsApp_.publish(topic_, message_.buffer, true);
188
+ """
189
+
190
+ publishText(topic: String, message: String) // Should this be corked?
191
+ target js async """
192
+ if(self_.internalJsApp_) self_.internalJsApp_.publish(topic_, message_, false);
193
+ """
194
+
195
+ }
196
+
197
+ extend self[T]: WebRequest[T] {
198
+
199
+ readMethod(): String {
200
+ self.internalMethod
201
+ }
202
+
203
+ readPath(): String {
204
+ self.internalPath
205
+ }
206
+
207
+ readRawQueryString(): String {
208
+ self.internalQuery
209
+ }
210
+
211
+ readQuery(name: String): Option[String]
212
+ target node sync """
213
+ if(self_.internalQuery_.length === 0) return ff_core_Option.None();
214
+ const ps = self_.internalQuery_.split('&');
215
+ const n = name_ + '=';
216
+ const r = ps.find(p => p === name_ || p.startsWith(n));
217
+ if(r == null) return ff_core_Option.None();
218
+ return ff_core_Option.Some(decodeURIComponent(r.slice(name_.length + 1)));
219
+ """
220
+
221
+ readHeader(lowerCaseName: String): Option[String] {
222
+ self.internalHeaders.get(lowerCaseName)
223
+ }
224
+
225
+ eachHeader(body: (String, String) => Unit) {
226
+ self.internalHeaders.each {body(_, _)}
227
+ }
228
+
229
+ }
230
+
231
+ extend self[T]: WebRequest[T] {
232
+
233
+ writeStatus(codeAndMessage: String) {
234
+ self.internalResponseStatus = codeAndMessage
235
+ }
236
+
237
+ writeHeader(name: String, value: String) {
238
+ self.internalResponseHeaders.set(name, value)
239
+ }
240
+
241
+ writeContentSize(contentSize: Int) {
242
+ if(contentSize >= 0) {
243
+ self.internalContentSize = contentSize
244
+ }
245
+ }
246
+
247
+ writeText(data: String) {
248
+ self.internalResponseChunks.push(internalTextChunk(data))
249
+ }
250
+
251
+ writeLine(data: String) {
252
+ self.writeText(data + "\n")
253
+ }
254
+
255
+ writeBuffer(data: Buffer) {
256
+ self.internalResponseChunks.push(internalBufferChunk(data))
257
+ }
258
+
259
+ writeStream(data: Stream[Buffer]) {
260
+ data.each {self.writeBuffer(_)}
261
+ }
262
+
263
+ }
264
+
265
+ extend self[T]: WebRequest[T] {
266
+
267
+ publishBuffer(topic: String, message: Buffer) // Should this be corked?
268
+ target js sync """
269
+ self_.internalJsApp_.publish(topic_, message_.buffer, true);
270
+ """
271
+
272
+ publishText(topic: String, message: String) // Should this be corked?
273
+ target js sync """
274
+ self_.internalJsApp_.publish(topic_, message_, false);
275
+ """
276
+
277
+ }
278
+
279
+ extend self[T]: WebRequest[T] {
280
+
281
+ closeConnection() {
282
+ self.internalCloseConnection = True
283
+ }
284
+
285
+ readRemoteAddress(): String
286
+ target js sync """
287
+ return new TextDecoder().decode(self_.internalJsResponse_.getRemoteAddressAsText())
288
+ """
289
+
290
+ readProxiedRemoteAddress(): String
291
+ target js sync """
292
+ return new TextDecoder().decode(self_.internalJsResponse_.getProxiedRemoteAddressAsText())
293
+ """
294
+
295
+ }
296
+
297
+ extend self: WebRequest[WebResponse] {
298
+
299
+ flush()
300
+ target js async """
301
+ if(!self_.internalResolveFlush_) {
302
+ self_.internalResolveFlush_ = () => {};
303
+ self_.internalJsResponse_.cork(() => {
304
+ self_.internalJsResponse_.writeStatus(self_.internalResponseStatus_);
305
+ self_.internalResponseHeaders_.forEach((value, key) =>
306
+ self_.internalJsResponse_.writeHeader(key, value)
307
+ );
308
+ self_.internalJsResponse_.onWritable(() => {
309
+ self_.internalResolveFlush_();
310
+ return true;
311
+ });
312
+ });
313
+ }
314
+ while(self_.internalResponseChunks_.array.length !== 0) {
315
+ await new Promise((resolve, reject) => {
316
+ self_.internalJsResponse_.cork(() => {
317
+ let backpressure = false;
318
+ let i = 0;
319
+ for(i = 0; i < self_.internalResponseChunks_.array.length && !backpressure; i++) {
320
+ let chunk = self_.internalResponseChunks_.array[i];
321
+ if(chunk && (chunk.byteLength || chunk.length)) {
322
+ if(typeof chunk !== 'string') {
323
+ chunk = new Uint8Array(chunk.buffer, chunk.byteOffset, chunk.byteLength);
324
+ } else if(self_.internalContentSize_ >= 0) {
325
+ chunk = new TextEncoder().encode(chunk);
326
+ }
327
+ if(self_.internalContentSize_ >= 0) {
328
+ const lastOffset = self_.internalJsResponse_.getWriteOffset();
329
+ const [ok, responded] = self_.internalJsResponse_.tryEnd(chunk, self_.internalContentSize_);
330
+ if(responded) self_.internalResponded_ = true;
331
+ if(!ok) {
332
+ const written = self_.internalJsResponse_.getWriteOffset() - lastOffset;
333
+ backpressure = true;
334
+ self_.internalResponseChunks_.array[i] =
335
+ new Uint8Array(chunk.buffer, chunk.byteOffset + written, chunk.byteLength - written);
336
+ i -= 1;
337
+ }
338
+ } else {
339
+ backpressure = !self_.internalJsResponse_.write(chunk);
340
+ }
341
+ }
342
+ }
343
+ if(i > 0) self_.internalResponseChunks_.array.splice(0, i);
344
+ if(!backpressure) {
345
+ resolve();
346
+ } else {
347
+ self_.internalResolveFlush_ = resolve;
348
+ }
349
+ });
350
+ });
351
+ }
352
+ """
353
+
354
+ flushText(data: String) {
355
+ self.writeText(data)
356
+ self.flush()
357
+ }
358
+
359
+ flushBuffer(data: Buffer) {
360
+ self.writeBuffer(data)
361
+ self.flush()
362
+ }
363
+
364
+ flushStream(data: Stream[Buffer]) {
365
+ data.each {self.flushBuffer(_)}
366
+ }
367
+
368
+ }
369
+
370
+ extend self: WebRequest[WebResponse] {
371
+
372
+ readText(encoding: String = "utf8"): String {
373
+ self.readStream(1073741823).toString(encoding)
374
+ }
375
+
376
+ readBuffer(): Buffer {
377
+ self.readStream(1073741823).toBuffer()
378
+ }
379
+
380
+ readStream(maxPendingBytes: Int = 65536): Stream[Buffer]
381
+ target js async """
382
+ const front = [];
383
+ const back = [];
384
+ let pendingBytes = 0
385
+ let closed = false;
386
+ let callResolve = null;
387
+ let callReject = null;
388
+ self_.internalJsResponse_.onData((chunk, isLast) => {
389
+ if(closed) return;
390
+ if(isLast) closed = true;
391
+ else chunk = cunk.slice();
392
+ pendingBytes += chunk.byteLength;
393
+ back.push(new DataView(chunk));
394
+ if(pendingBytes >= maxPendingBytes_) self_.internalJsResponse_.pause();
395
+ if(callResolve) callResolve();
396
+ });
397
+ const abort = () => {
398
+ $task.controller.signal.removeEventListener('abort', abort);
399
+ if(callReject) callReject();
400
+ }
401
+ $task.controller.signal.addEventListener('abort', abort);
402
+ return ff_core_Stream.Stream(
403
+ async () => {
404
+ while(true) {
405
+ ff_core_Task.Task_throwIfAborted($task);
406
+ if(front.length === 0) {
407
+ while(back.length !== 0) front.push(back.pop());
408
+ }
409
+ if(front.length !== 0) {
410
+ const chunk = front.pop();
411
+ const paused = pendingBytes >= maxPendingBytes_;
412
+ pendingBytes -= chunk.byteLength;
413
+ if(!closed && paused && pendingBytes < maxPendingBytes_) self_.internalJsResponse_.unpause();
414
+ return ff_core_Option.Some(chunk);
415
+ } else if(closed) {
416
+ return ff_core_Option.None();
417
+ } else {
418
+ await new Promise((resolve, reject) => {
419
+ callResolve = resolve;
420
+ callReject = reject;
421
+ });
422
+ callResolve = null;
423
+ callReject = null;
424
+ }
425
+ }
426
+ },
427
+ async () => {
428
+ closed = true;
429
+ ff_core_Task.Task_throwIfAborted($task);
430
+ if(callResolve) callResolve();
431
+ }
432
+ )
433
+ """
434
+
435
+ }
436
+
437
+ extend self[T]: WebRequest[T] {
438
+
439
+ needsWebSocket(): Bool
440
+ target js sync """
441
+ return self_.internalJsContext_ != null;
442
+ """
443
+
444
+ }
445
+
446
+ extend self: WebRequest[WebResponse] {
447
+
448
+ openWebSocket(): WebSocketConnection
449
+ target js async """
450
+ ff_core_Task.Task_throwIfAborted($task);
451
+ const userData = {};
452
+ const wrapper = {front: [], back: [], listeners: [], task: $task};
453
+ const promise = new Promise((resolve, reject) => {
454
+ const abort = () => {wrapper.webSocket = null; reject()};
455
+ userData.onOpen = ws => {wrapper.webSocket = ws; resolve(wrapper)};
456
+ userData.onClose = () => {
457
+ $task.controller.signal.removeEventListener('abort', abort);
458
+ abort();
459
+ for(let i = 0; i < wrapper.listeners.length; i++) wrapper.listeners[i]();
460
+ wrapper.listeners.length = 0;
461
+ };
462
+ userData.onMessage = (message, isBinary) => {
463
+ wrapper.back.push(isBinary ? new DataView(message) : new TextDecoder().decode(message));
464
+ for(let i = 0; i < wrapper.listeners.length; i++) wrapper.listeners[i]();
465
+ wrapper.listeners.length = 0;
466
+ };
467
+ $task.controller.signal.addEventListener('abort', abort);
468
+ });
469
+ self_.internalJsResponse_.cork(() => {
470
+ ff_core_Task.Task_throwIfAborted($task);
471
+ self_.internalJsResponse_.upgrade(
472
+ userData,
473
+ self_.internalHeaders_.get('sec-websocket-key'),
474
+ self_.internalHeaders_.get('sec-websocket-protocol'),
475
+ self_.internalHeaders_.get('sec-websocket-extensions'),
476
+ self_.internalJsContext_
477
+ )
478
+ });
479
+ return await promise;
480
+ """
481
+
482
+ }
483
+
484
+ extend self: WebSocketConnection {
485
+
486
+ readText(encoding: String = "utf8"): Option[String] {
487
+ self.readAny {_} {_.toString(encoding)}
488
+ }
489
+
490
+ readBuffer(): Option[Buffer] {
491
+ self.readAny {_.toBuffer()} {_}
492
+ }
493
+
494
+ readAny[T](fromText: String => T, fromBuffer: Buffer => T): Option[T]
495
+ target node async """
496
+ ff_core_Task.Task_throwIfAborted($task);
497
+ while(self_.front.length === 0) {
498
+ while(self_.back.length !== 0) {
499
+ self_.front.push(self_.back.pop());
500
+ }
501
+ if(self_.front.length !== 0) break;
502
+ if(self_.webSocket == null) return ff_core_Option.None();
503
+ let abort = null;
504
+ try {
505
+ await new Promise((resolve, reject) => {
506
+ self_.listeners.push(resolve);
507
+ if($task !== self_.task) {
508
+ abort = () => {
509
+ self_.listeners = self_.listeners.filter(l => l != resolve);
510
+ reject();
511
+ };
512
+ $task.controller.signal.addEventListener('abort', abort);
513
+ }
514
+ });
515
+ } finally {
516
+ if($task !== self_.task) {
517
+ $task.controller.signal.removeEventListener('abort', abort);
518
+ }
519
+ }
520
+ }
521
+ const data = self_.front.pop();
522
+ if(typeof data === 'string') return ff_core_Option.Some(await fromText_(data));
523
+ return ff_core_Option.Some(await fromBuffer_(data));
524
+ """
525
+
526
+ readStream(): Stream[Buffer] {
527
+ Stream {self.readBuffer()} {self.close()}
528
+ }
529
+
530
+ writeBuffer(message: Buffer)
531
+ target node async """
532
+ await internalSend_$(self_, () => self_.webSocket.send(message_.buffer, true) !== 2, $task);
533
+ """
534
+
535
+ writeText(message: String)
536
+ target node async """
537
+ await internalSend_$(self_, () => self_.webSocket.send(message_, false) !== 2, $task);
538
+ """
539
+
540
+ publishBuffer(topic: String, message: Buffer)
541
+ target node async """
542
+ await internalSend_$(self_, () => self_.webSocket.publish(topic_, message_.buffer, true), $task);
543
+ """
544
+
545
+ publishText(topic: String, message: String)
546
+ target node async """
547
+ await internalSend_$(self_, () => self_.webSocket.publish(topic_, message_, false), $task);
548
+ """
549
+
550
+ subscribe(topic: String)
551
+ target node async """
552
+ if(self_.webSocket == null) throw new Error("WebSocket is closed");
553
+ self_.webSocket.subscribe(topic_);
554
+ """
555
+
556
+ unsubscribe(topic: String)
557
+ target node async """
558
+ if(self_.webSocket == null) throw new Error("WebSocket is closed");
559
+ self_.webSocket.unsubscribe(topic_);
560
+ """
561
+
562
+ isSubscribedTo(topic: String): Bool
563
+ target node async """
564
+ if(self_.webSocket == null) throw new Error("WebSocket is closed");
565
+ return self_.webSocket.isSubscribed(topic_);
566
+ """
567
+
568
+ subscriptions(): Array[String]
569
+ target node async """
570
+ if(self_.webSocket == null) throw new Error("WebSocket is closed");
571
+ return self_.webSocket.getTopics();
572
+ """
573
+
574
+ close(): Unit
575
+ target node async """
576
+ if(self_.webSocket != null) {
577
+ self_.webSocket.close();
578
+ self_.webSocket = null;
579
+ }
580
+ """
581
+
582
+ }
583
+
584
+ internalSend[T](self: WebSocketConnection, send: () => Bool): JsValue
585
+ target node async """
586
+ let abort = null;
587
+ try {
588
+ await new Promise((resolve, reject) => {
589
+ if(self_.webSocket == null) new Error("WebSocket is closed");
590
+ else self_.webSocket.cork(() => {
591
+ if($task !== self_.task) {
592
+ abort = () => {
593
+ self_.listeners = self_.listeners.filter(l => l != resolve);
594
+ reject();
595
+ };
596
+ $task.controller.signal.addEventListener('abort', abort);
597
+ }
598
+ if(self_.webSocket == null) {
599
+ reject(new Error("WebSocket is closed"));
600
+ } else if(!send_()) {
601
+ reject(new Error("Dropped message due to backpressure"));
602
+ } else {
603
+ resolve();
604
+ }
605
+ })
606
+ });
607
+ } finally {
608
+ if($task !== self_.task) {
609
+ $task.controller.signal.removeEventListener('abort', abort);
610
+ }
611
+ }
612
+ """
613
+
614
+ internalArray[T](): Array[T] {
615
+ Array.new()
616
+ }
617
+
618
+ internalTextChunk(data: String): JsValue
619
+ target js sync """
620
+ return data_;
621
+ """
622
+
623
+ internalBufferChunk(data: Buffer): JsValue
624
+ target js sync """
625
+ return data_;
626
+ """
627
+
628
+ internalChunksToChunk(chunks: Array[JsValue]): JsValue
629
+ target js sync """
630
+ const chunks = chunks_.array;
631
+ if(chunks.length === 0) return;
632
+ const firstIsString = typeof chunks[0] === 'string';
633
+ if(firstIsString && chunks.length === 1) return chunks[0];
634
+ if(firstIsString && chunks.every(c => typeof c === 'string')) {
635
+ let result = chunks[0];
636
+ for(let i = 1; i < chunks.length; i++) {
637
+ result += chunks[i];
638
+ }
639
+ return result;
640
+ }
641
+ let totalLength = 0;
642
+ for(let i = 0; i < chunks.length; i++) {
643
+ if(typeof chunks[i] === 'string') chunks[i] = new TextEncoder().encode(chunks[i]);
644
+ totalLength += chunks[i].byteLength;
645
+ }
646
+ const result = new Uint8Array(totalLength);
647
+ let offset = 0;
648
+ for(let i = 0; i < chunks.length; i++) {
649
+ const chunk = chunks[i];
650
+ const uint8Array = new Uint8Array(chunk.buffer, chunk.byteOffset, chunk.byteLength);
651
+ result.set(uint8Array, offset);
652
+ offset += chunk.byteLength;
653
+ }
654
+ return result;
655
+ """
656
+
657
+ parseMultipartFields(data: Buffer, contentType: String): Option[Array[MultipartField]]
658
+ target js sync """
659
+ const parts = getParts(data_, contentType_);
660
+ return parts == null ? ff_core_Option.None() : ff_core_Option.Some(parts);
661
+ """
662
+
663
+ extend self: MultipartField {
664
+
665
+ data(): Buffer
666
+ target js sync """
667
+ return new DataView(self_.data);
668
+ """
669
+
670
+ name(): String
671
+ target js sync """
672
+ return self_.name;
673
+ """
674
+
675
+ type(): Option[String]
676
+ target js sync """
677
+ return self_.type == null ? ff_core_Option.None() : ff_core_Option.Some(self_.type);
678
+ """
679
+
680
+ filename(): Option[String]
681
+ target js sync """
682
+ return self_.filename == null ? ff_core_Option.None() : ff_core_Option.Some(self_.filename);
683
+ """
684
+
685
+ }