firefly-compiler 0.5.39 → 0.5.40

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