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 +4 -4
- data/Rakefile +20 -0
- data/dist/.gitignore +3 -0
- data/dist/gems.locked +29 -0
- data/dist/gems.rb +11 -0
- data/lib/wands/java_script/array_buffer.rb +48 -0
- data/lib/wands/java_script/js_error.rb +28 -0
- data/lib/wands/java_script/queue.rb +51 -0
- data/lib/wands/java_script/web_socket.rb +76 -0
- data/lib/wands/version.rb +1 -1
- data/lib/wands/web_socket.rb +2 -2
- data/node_wasi_test/.gitignore +1 -0
- data/node_wasi_test/package-lock.json +67 -0
- data/node_wasi_test/package.json +18 -0
- data/node_wasi_test/run-test-unit.mjs +49 -0
- data/node_wasi_test/start-echo-server.mjs +16 -0
- data/node_wasi_test/tests/test_unit.rb +5 -0
- data/node_wasi_test/tests/unit/test_array_buffer.rb +33 -0
- data/node_wasi_test/tests/unit/test_queue.rb +34 -0
- data/node_wasi_test/tests/unit/test_web_socket.rb +44 -0
- metadata +18 -4
- data/lib/wands/js/queue.rb +0 -43
- data/lib/wands/js/web_socket.rb +0 -48
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6e201c2b884e875383da0c4b25335479096504879e2af95cf8690b4247a3e94c
|
4
|
+
data.tar.gz: c9f0b8eba9649ed9fce87441fc4be0c8a7f2fdf12d566d47a86adb074ef1f85f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
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,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
data/lib/wands/web_socket.rb
CHANGED
@@ -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 "
|
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
|
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,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.
|
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-
|
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/
|
60
|
-
- lib/wands/
|
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:
|
data/lib/wands/js/queue.rb
DELETED
@@ -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
|
data/lib/wands/js/web_socket.rb
DELETED
@@ -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
|