wands 0.7.2 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e0f4387102cd8565a88e6d29beeda55459d62afa1b6d553d3024726898ce25ff
4
- data.tar.gz: 786f17cecaf4093a46f2b7ab1d50084402a367f42cd222ec67fe33c4f6cca48e
3
+ metadata.gz: 6e201c2b884e875383da0c4b25335479096504879e2af95cf8690b4247a3e94c
4
+ data.tar.gz: c9f0b8eba9649ed9fce87441fc4be0c8a7f2fdf12d566d47a86adb074ef1f85f
5
5
  SHA512:
6
- metadata.gz: 1d53f26ba63a535f853a29d70a28f5bf8be05e820e2b0fd39519e0351091c7587b53a0cef3777fa8099fe1cd0272c1bd74aeb41269ae307a98109fbb2ef2d61f
7
- data.tar.gz: 8e7195b9106893b2bc1fea0031b2cd88affbf374840db4dc3d196b12ed0c8366051ab8ba7d3b4b76505fbe83ecfb184ca00f39d5e2dc509ea586faef6677edb2
6
+ metadata.gz: 4b10687d17cbcb7f4ca7504c4f0752f443d7608415c6413643a2ae04a8b8d8bc87c4e4e9d25125f9aa4f55bad746776e1f1d5445cb968b6581746d4060988ca6
7
+ data.tar.gz: a122263cab3fbe0c90d1be53c1282d02e228d3b753c1e2e1e06e5a983eba8d2b7feb7fe1182629d86874e6f08c5c717e8302351dd2a89a3bf69d97ae35c59a29
data/Rakefile CHANGED
@@ -10,3 +10,23 @@ require "rubocop/rake_task"
10
10
  RuboCop::RakeTask.new
11
11
 
12
12
  task default: %i[test rubocop]
13
+
14
+ namespace :wasm do
15
+ desc "Build Ruby WASM and install gems"
16
+ task :build do
17
+ Dir.chdir("dist") do
18
+ ENV["BUNDLE_GEMFILE"] = File.expand_path("gems.rb")
19
+ sh "bundle install"
20
+ sh "bundle exec rbwasm build --ruby-version 3.4 -o ruby+wands.wasm"
21
+ sh "cp ruby+wands.wasm ../examples/ruby_wasm/dist"
22
+ end
23
+ end
24
+
25
+ desc "Test Ruby WASM in Node.js"
26
+ task :test do
27
+ Dir.chdir("node_wasi_test") do
28
+ sh "npm install"
29
+ sh "npm test"
30
+ end
31
+ end
32
+ end
data/dist/.gitignore ADDED
@@ -0,0 +1,3 @@
1
+ build
2
+ rubies
3
+ ruby+wands.wasm
data/dist/gems.locked ADDED
@@ -0,0 +1,29 @@
1
+ PATH
2
+ remote: ..
3
+ specs:
4
+ wands (0.8.0)
5
+ protocol-websocket (~> 0.20)
6
+ webrick (~> 1.9)
7
+
8
+ GEM
9
+ remote: https://rubygems.org/
10
+ specs:
11
+ js (2.7.1)
12
+ logger (1.6.6)
13
+ protocol-http (0.49.0)
14
+ protocol-websocket (0.20.1)
15
+ protocol-http (~> 0.2)
16
+ ruby_wasm (2.7.1-x86_64-linux)
17
+ webrick (1.9.1)
18
+
19
+ PLATFORMS
20
+ x86_64-linux
21
+
22
+ DEPENDENCIES
23
+ js (~> 2.7)
24
+ logger (~> 1.6)
25
+ ruby_wasm (~> 2.7)
26
+ wands!
27
+
28
+ BUNDLED WITH
29
+ 2.6.2
data/dist/gems.rb ADDED
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "wands", path: "../"
6
+
7
+ gem "ruby_wasm", "~> 2.7"
8
+
9
+ gem "js", "~> 2.7"
10
+
11
+ gem "logger", "~> 1.6"
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "js"
4
+
5
+ module Wands
6
+ module JavaScript
7
+ # A wrapper around JavaScript ArrayBuffer
8
+ class ArrayBuffer
9
+ def initialize(js_array_buffer = nil)
10
+ @data = "".b
11
+
12
+ return unless js_array_buffer
13
+ raise ArgumentError, "Expected a JavaScript ArrayBuffer" unless JS.is_a?(js_array_buffer,
14
+ JS.global[:ArrayBuffer])
15
+
16
+ uint8_array = JS.global[:Uint8Array].new(js_array_buffer)
17
+
18
+ (0...js_array_buffer[:byteLength].to_i).each do |index|
19
+ @data << uint8_array[index].to_i.chr
20
+ end
21
+ end
22
+
23
+ def write(binary_string)
24
+ raise ArgumentError, "must be ASCII-8BIT encoded" unless binary_string.encoding == Encoding::ASCII_8BIT
25
+
26
+ @data << binary_string
27
+ end
28
+
29
+ def to_js_array_buffer
30
+ uint8_array = JS.global[:Uint8Array].new(@data.bytesize)
31
+
32
+ @data.bytes.each_with_index do |byte, index|
33
+ uint8_array[index] = byte
34
+ end
35
+
36
+ uint8_array[:buffer]
37
+ end
38
+
39
+ def read
40
+ @data.dup
41
+ end
42
+
43
+ def size
44
+ @data.bytesize
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Wands
4
+ module JavaScript
5
+ # Represents a JavaScript error event, inheriting from Ruby's Error class.
6
+ class JSError < StandardError
7
+ attr_reader :stack
8
+
9
+ def initialize(js_error_event, message = nil)
10
+ super("#{message}#{dig_js_values(js_error_event, :message)}")
11
+ @stack = dig_js_values(js_error_event, :error, :stack)
12
+ end
13
+
14
+ private
15
+
16
+ # Recursively digs into a nested object using the provided keys, returning nil if any key is not found
17
+ # or if the value is `JS::Undefined`.
18
+ def dig_js_values(object, *keys)
19
+ keys.reduce(object) do |value, key|
20
+ return nil if value.nil?
21
+
22
+ next_value = value[key]
23
+ next_value == JS::Undefined ? nil : next_value
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Wands
4
+ module JavaScript
5
+ # Asynchronous event queuing mechanism utilizing JavaScript's Promise;
6
+ # since it uses JavaScript's API, only objects that can be converted to
7
+ # JavaScript objects can pass through this queue.
8
+ class Queue
9
+ def self.wait_once
10
+ queue = new
11
+ yield queue
12
+ queue.pop
13
+ end
14
+
15
+ def initialize
16
+ @waiter = nil
17
+ @buffer = []
18
+ end
19
+
20
+ # Only objects that can be converted to JavaScript object can be pushed.
21
+ def push(message)
22
+ js_object = JS.try_convert(message)
23
+ raise TypeError, "#{message.class} is not a JS::Object like object" unless js_object
24
+
25
+ if @waiter
26
+ @waiter.apply message
27
+ @waiter = nil
28
+ else
29
+ @buffer << js_object
30
+ end
31
+ end
32
+ alias << push
33
+
34
+ # The popped object is a JavaScript object.
35
+ # For example, when you push Ruby's false,
36
+ # the pop method will return JS::True.
37
+ def pop
38
+ # message is received
39
+ # return message
40
+ return @buffer.shift unless @buffer.empty?
41
+
42
+ # message is not received
43
+ # set promise and wait
44
+ resolve = nil
45
+ promise = JS.global[:Promise].new { resolve = it }
46
+ @waiter = resolve
47
+ promise.await
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "uri"
4
+ require_relative "queue"
5
+ require_relative "js_error"
6
+ require_relative "array_buffer"
7
+
8
+ module Wands
9
+ module JavaScript
10
+ # WebSocket class for when JavaScript is available in the browser.
11
+ # All methods are synchronous and blocking, compatible with the Socket class.
12
+ module WebSocket
13
+ def self.prepended(base) = base.singleton_class.prepend(ClassMethods)
14
+
15
+ # The class methods module
16
+ module ClassMethods
17
+ def open(host, port)
18
+ uri = URI::HTTP.build(host:, port:)
19
+ ws = JS.global[:WebSocket].new(uri.to_s)
20
+ ws[:binaryType] = "arraybuffer"
21
+
22
+ instance = new(ws)
23
+ ws.addEventListener("message") { instance << it }
24
+ wait_until_ready ws
25
+ instance
26
+ end
27
+
28
+ private
29
+
30
+ def wait_until_ready(js_ws)
31
+ return if js_ws[:readyState] == 1
32
+
33
+ js_event = Queue.wait_once do |queue|
34
+ js_ws.addEventListener("open") { queue.push it }
35
+ js_ws.addEventListener("error") { queue.push it }
36
+ end
37
+
38
+ return unless js_event[:type].to_s == "error"
39
+
40
+ raise JSError.new js_event, "WebSocket connection to #{js_ws[:url]} failed: "
41
+ end
42
+ end
43
+
44
+ def initialize(web_socket)
45
+ @socket = web_socket
46
+ @buffer = Queue.new
47
+ @binary_message = "".b
48
+ end
49
+
50
+ def gets = @buffer.pop.to_s
51
+
52
+ def puts(str)
53
+ raise "socket is closed" unless @socket[:readyState] == 1
54
+
55
+ @socket.send(str)
56
+ end
57
+
58
+ def write(str)
59
+ raise "socket is closed" unless @socket[:readyState] == 1
60
+
61
+ array_buffer = ArrayBuffer.new
62
+ array_buffer.write(str)
63
+ @socket.send(array_buffer.to_js_array_buffer, binary: true)
64
+ end
65
+
66
+ # Add an text flame into the buffer
67
+ def <<(event) = @buffer << event[:data]
68
+
69
+ private
70
+
71
+ def read_next_binary_frame
72
+ ArrayBuffer.new(@buffer.pop).read
73
+ end
74
+ end
75
+ end
76
+ end
data/lib/wands/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Wands
4
- VERSION = "0.7.2"
4
+ VERSION = "0.8.0"
5
5
  end
@@ -7,7 +7,7 @@ require "protocol/websocket/text_frame"
7
7
  require_relative "upgrade_request"
8
8
  require_relative "http_response"
9
9
  require_relative "response_exception"
10
- require_relative "js/web_socket" if defined? JS
10
+ require_relative "java_script/web_socket" if defined? JS
11
11
 
12
12
  module Wands
13
13
  # This is a class that represents WebSocket, which has the same interface as TCPSocket.
@@ -26,7 +26,7 @@ module Wands
26
26
  class WebSocket
27
27
  include Protocol::WebSocket::Headers
28
28
  extend Forwardable
29
- prepend JS::WebSocket if defined? JS
29
+ prepend JavaScript::WebSocket if defined? JS
30
30
 
31
31
  def_delegators :@socket, :addr, :remote_address, :close, :to_io, :eof?
32
32
 
@@ -0,0 +1 @@
1
+ node_modules
@@ -0,0 +1,67 @@
1
+ {
2
+ "name": "node_wasi_test",
3
+ "version": "1.0.0",
4
+ "lockfileVersion": 3,
5
+ "requires": true,
6
+ "packages": {
7
+ "": {
8
+ "name": "node_wasi_test",
9
+ "version": "1.0.0",
10
+ "license": "ISC",
11
+ "devDependencies": {
12
+ "@ruby/wasm-wasi": "^2.7.1",
13
+ "ws": "^8.18.1"
14
+ },
15
+ "engines": {
16
+ "node": ">=22.0.0"
17
+ }
18
+ },
19
+ "node_modules/@bjorn3/browser_wasi_shim": {
20
+ "version": "0.3.0",
21
+ "resolved": "https://registry.npmjs.org/@bjorn3/browser_wasi_shim/-/browser_wasi_shim-0.3.0.tgz",
22
+ "integrity": "sha512-FlRBYttPRLcWORzBe6g8nmYTafBkOEFeOqMYM4tAHJzFsQy4+xJA94z85a9BCs8S+Uzfh9LrkpII7DXr2iUVFg==",
23
+ "dev": true,
24
+ "license": "MIT OR Apache-2.0"
25
+ },
26
+ "node_modules/@ruby/wasm-wasi": {
27
+ "version": "2.7.1",
28
+ "resolved": "https://registry.npmjs.org/@ruby/wasm-wasi/-/wasm-wasi-2.7.1.tgz",
29
+ "integrity": "sha512-2f4NqiJuFoeYiXNr60PH3TbH5c+z/xP2Hq36Av2yahp05AaLDyJxWZwr9EGmfoFsmVTmeDdEW2KjPTfpue6xeg==",
30
+ "dev": true,
31
+ "license": "MIT",
32
+ "dependencies": {
33
+ "@bjorn3/browser_wasi_shim": "^0.3.0",
34
+ "tslib": "^2.8.1"
35
+ }
36
+ },
37
+ "node_modules/tslib": {
38
+ "version": "2.8.1",
39
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
40
+ "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
41
+ "dev": true,
42
+ "license": "0BSD"
43
+ },
44
+ "node_modules/ws": {
45
+ "version": "8.18.1",
46
+ "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.1.tgz",
47
+ "integrity": "sha512-RKW2aJZMXeMxVpnZ6bck+RswznaxmzdULiBr6KY7XkTnW8uvt0iT9H5DkHUChXrc+uurzwa0rVI16n/Xzjdz1w==",
48
+ "dev": true,
49
+ "license": "MIT",
50
+ "engines": {
51
+ "node": ">=10.0.0"
52
+ },
53
+ "peerDependencies": {
54
+ "bufferutil": "^4.0.1",
55
+ "utf-8-validate": ">=5.0.2"
56
+ },
57
+ "peerDependenciesMeta": {
58
+ "bufferutil": {
59
+ "optional": true
60
+ },
61
+ "utf-8-validate": {
62
+ "optional": true
63
+ }
64
+ }
65
+ }
66
+ }
67
+ }
@@ -0,0 +1,18 @@
1
+ {
2
+ "name": "node_wasi_test",
3
+ "version": "1.0.0",
4
+ "main": "index.js",
5
+ "engines": {
6
+ "node": ">=22.0.0"
7
+ },
8
+ "scripts": {
9
+ "test": "node --experimental-wasi-unstable-preview1 ./run-test-unit.mjs"
10
+ },
11
+ "author": "",
12
+ "license": "ISC",
13
+ "description": "",
14
+ "devDependencies": {
15
+ "@ruby/wasm-wasi": "^2.7.1",
16
+ "ws": "^8.18.1"
17
+ }
18
+ }
@@ -0,0 +1,49 @@
1
+ import fs from "fs/promises";
2
+ import path from "path";
3
+ import * as nodeWasi from "wasi";
4
+ import { RubyVM } from "@ruby/wasm-wasi";
5
+ import startEchoServer from "./start-echo-server.mjs";
6
+
7
+ async function instantiateNodeWasi() {
8
+ const dirname = path.dirname(new URL(import.meta.url).pathname);
9
+ const binaryPath = "../dist/ruby+wands.wasm";
10
+ const binary = await fs.readFile(binaryPath);
11
+ const rubyModule = await WebAssembly.compile(binary);
12
+
13
+ const wasi = new nodeWasi.WASI({
14
+ preopens: { "__root__": path.join(dirname, ".") },
15
+ version: "preview1",
16
+ });
17
+
18
+ const { vm } = await RubyVM.instantiateModule({
19
+ module: rubyModule,
20
+ wasip1: wasi,
21
+ });
22
+ return vm;
23
+ }
24
+
25
+ async function test() {
26
+ const vm = await instantiateNodeWasi();
27
+
28
+ await vm.evalAsync(`
29
+ require 'test/unit'
30
+
31
+ require_relative '/__root__/tests/test_unit.rb'
32
+ ok = Test::Unit::AutoRunner.run
33
+ exit(1) unless ok
34
+ `);
35
+ }
36
+
37
+ async function main() {
38
+ const stopServer = startEchoServer(8080);
39
+
40
+ try{
41
+ Error.stackTraceLimit = Infinity;
42
+ await test();
43
+ } finally {
44
+ stopServer();
45
+ }
46
+ }
47
+
48
+ main();
49
+
@@ -0,0 +1,16 @@
1
+ import { WebSocketServer } from 'ws';
2
+
3
+ export default function startTextEchoServer(port) {
4
+ const wss = new WebSocketServer({ port });
5
+
6
+ console.log(`Echo server starts at port:${port}`);
7
+
8
+ wss.on('connection', (ws) => {
9
+ ws.on('message', (message, isBinary) => {
10
+ ws.send(message, { binary: isBinary });
11
+ });
12
+ });
13
+
14
+ // return a function to stop the server
15
+ return () => wss.close();
16
+ }
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "unit/test_queue"
4
+ require_relative "unit/test_array_buffer"
5
+ require_relative "unit/test_web_socket"
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "test-unit"
4
+ require "js"
5
+ require "wands/java_script/array_buffer"
6
+
7
+ module Wands
8
+ module JavaScript
9
+ # Test Wands::JavaScript::ArrayBuffer
10
+ class TestArrayBuffer < Test::Unit::TestCase
11
+ # rubocop:disable Naming/MethodName
12
+ def test_to_js_array_buffer_returns_ArrayBuffer
13
+ @buffer = ArrayBuffer.new
14
+ @buffer.write("\x10\x20\x30".b)
15
+ js_buffer = @buffer.to_js_array_buffer
16
+
17
+ assert_true(JS.is_a?(js_buffer, JS.global[:ArrayBuffer]))
18
+ assert_equal(3, JS.global[:Uint8Array].new(js_buffer)[:length])
19
+ end
20
+ # rubocop:enable Naming/MethodName
21
+
22
+ def test_initialize_with_js_array_buffer
23
+ original = "\xaa\xbb\xcc".b
24
+ @buffer = ArrayBuffer.new
25
+ @buffer.write(original)
26
+ js_buf = @buffer.to_js_array_buffer
27
+
28
+ new_buffer = ArrayBuffer.new(js_buf)
29
+ assert_equal(original, new_buffer.read)
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "test-unit"
4
+ require "js"
5
+ require "wands/java_script/queue"
6
+
7
+ module Wands
8
+ module JavaScript
9
+ # Test Wands::JavaScirpt::Queue
10
+ class TestQueue < Test::Unit::TestCase
11
+ def test_push_and_pop
12
+ queue = Queue.new
13
+ queue.push("Hello World!")
14
+ message = queue.pop
15
+
16
+ assert_true message.is_a?(JS::Object)
17
+ assert_equal "Hello World!", message.to_s
18
+ end
19
+
20
+ def test_wait_once
21
+ start = Time.now
22
+ Queue.wait_once do |queue|
23
+ JS.global.setTimeout(-> { queue.push(nil) }, 100)
24
+ end
25
+ assert_in_delta 0.1, Time.now - start, 0.05
26
+ end
27
+
28
+ def test_push_no_js_like_object
29
+ queue = Queue.new
30
+ assert_raise(TypeError) { queue.push(Object.new) }
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "test-unit"
4
+ require "js"
5
+ require "wands/web_socket"
6
+
7
+ module Wands
8
+ # Test Wands::WebSocket
9
+ class TestWebSocket < Test::Unit::TestCase
10
+ def test_open_failure
11
+ assert_raise(JavaScript::JSError) { WebSocket.open("localhost", 23_456) }
12
+ end
13
+
14
+ def test_open_success
15
+ web_socket = WebSocket.open("localhost", 8080)
16
+ assert_instance_of(WebSocket, web_socket)
17
+ web_socket.close
18
+ end
19
+
20
+ def test_puts_and_gets
21
+ web_socket = WebSocket.open("localhost", 8080)
22
+ web_socket.puts("Hello World!")
23
+
24
+ received = web_socket.gets
25
+ assert_instance_of(String, received)
26
+ assert_equal("Hello World!", received)
27
+
28
+ web_socket.close
29
+ end
30
+
31
+ def test_write_and_read
32
+ web_socket = WebSocket.open("localhost", 8080)
33
+ binary_data = "\x48\x65\x6C\x6C\x6F\x20\x57\x6F\x72\x6C\x64\x21".b # "Hello World!" as binary
34
+ web_socket.write(binary_data)
35
+
36
+ received = web_socket.read(12)
37
+ assert_instance_of(String, received)
38
+ assert_equal(received.encoding, Encoding::ASCII_8BIT)
39
+ assert_equal(binary_data, received)
40
+
41
+ web_socket.close
42
+ end
43
+ end
44
+ end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: wands
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.2
4
+ version: 0.8.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - shigeru.nakajima
8
8
  bindir: exe
9
9
  cert_chain: []
10
- date: 2025-03-20 00:00:00.000000000 Z
10
+ date: 2025-03-23 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: protocol-websocket
@@ -54,15 +54,29 @@ files:
54
54
  - LICENSE
55
55
  - README.md
56
56
  - Rakefile
57
+ - dist/.gitignore
58
+ - dist/gems.locked
59
+ - dist/gems.rb
57
60
  - lib/wands.rb
58
61
  - lib/wands/http_response.rb
59
- - lib/wands/js/queue.rb
60
- - lib/wands/js/web_socket.rb
62
+ - lib/wands/java_script/array_buffer.rb
63
+ - lib/wands/java_script/js_error.rb
64
+ - lib/wands/java_script/queue.rb
65
+ - lib/wands/java_script/web_socket.rb
61
66
  - lib/wands/response_exception.rb
62
67
  - lib/wands/upgrade_request.rb
63
68
  - lib/wands/version.rb
64
69
  - lib/wands/web_socket.rb
65
70
  - lib/wands/web_socket_server.rb
71
+ - node_wasi_test/.gitignore
72
+ - node_wasi_test/package-lock.json
73
+ - node_wasi_test/package.json
74
+ - node_wasi_test/run-test-unit.mjs
75
+ - node_wasi_test/start-echo-server.mjs
76
+ - node_wasi_test/tests/test_unit.rb
77
+ - node_wasi_test/tests/unit/test_array_buffer.rb
78
+ - node_wasi_test/tests/unit/test_queue.rb
79
+ - node_wasi_test/tests/unit/test_web_socket.rb
66
80
  - sig/wands.rbs
67
81
  homepage: https://github.com/ledsun/wands
68
82
  licenses:
@@ -1,43 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Wands
4
- module JS
5
- # A blocking queue that can be used for waiting for asynchronous events.
6
- class Queue
7
-
8
- def self.wait_once
9
- queue = new
10
- yield queue
11
- queue.pop
12
- end
13
-
14
- def initialize
15
- @waiter = nil
16
- @buffer = []
17
- end
18
-
19
- def push(message)
20
- if @waiter
21
- @waiter.apply message
22
- @waiter = nil
23
- else
24
- @buffer << message
25
- end
26
- end
27
- alias << push
28
-
29
- def pop
30
- # message is received
31
- # return message
32
- return @buffer.shift unless @buffer.empty?
33
-
34
- # message is not received
35
- # set promise and wait
36
- resolve = nil
37
- promise = ::JS.global[:Promise].new { resolve = it }
38
- @waiter = resolve
39
- promise.await
40
- end
41
- end
42
- end
43
- end
@@ -1,48 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "uri"
4
- require_relative "queue"
5
-
6
- module Wands
7
- module JS
8
- # WebSocket class for when JavaScript is available in the browser.
9
- # All methods are synchronous and blocking, compatible with the Socket class.
10
- module WebSocket
11
- def self.prepended(base) = base.singleton_class.prepend(ClassMethods)
12
-
13
- # The class methods module
14
- module ClassMethods
15
- def open(host, port)
16
- uri = URI::HTTP.build(host:, port:)
17
- ws = ::JS.global[:WebSocket].new(uri.to_s)
18
-
19
- instance = new(ws)
20
- ws.addEventListener("message") do |event|
21
- instance << event
22
- end
23
-
24
- Queue.wait_once do |queue|
25
- ws.addEventListener("open") { queue.push(nil) }
26
- end
27
-
28
- instance
29
- end
30
- end
31
-
32
- def initialize(web_socket)
33
- @ws = web_socket
34
- @queue = Queue.new
35
- end
36
-
37
- def puts(str)
38
- raise "socket is closed" unless @ws[:readyState] == 1
39
-
40
- @ws.send(str)
41
- end
42
-
43
- def gets = @queue.pop
44
-
45
- def <<(event) = @queue << event[:data]
46
- end
47
- end
48
- end