interfacets 0.1.0 → 0.9.9
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/.rubocop.yml +173 -1
- data/LICENSE +21 -0
- data/Rakefile +4 -6
- data/lib/interfacets/client/actor.rb +20 -0
- data/lib/interfacets/client/assets.rb +209 -0
- data/lib/interfacets/client/bus.rb +69 -0
- data/lib/interfacets/client/channels/api.rb +95 -0
- data/lib/interfacets/client/channels/audio.rb +101 -0
- data/lib/interfacets/client/channels/base.rb +28 -0
- data/lib/interfacets/client/channels/page_visibility.rb +21 -0
- data/lib/interfacets/client/channels/react/builder.rb +203 -0
- data/lib/interfacets/client/channels/react/channel.rb +61 -0
- data/lib/interfacets/client/channels/react/dom.rb +91 -0
- data/lib/interfacets/client/channels/react/evaluator.rb +33 -0
- data/lib/interfacets/client/channels/speech_to_text.rb +100 -0
- data/lib/interfacets/client/channels/timer.rb +51 -0
- data/lib/interfacets/client/channels/url.rb +52 -0
- data/lib/interfacets/client/config.rb +22 -0
- data/lib/interfacets/client/delegator.rb +37 -0
- data/lib/interfacets/client/facet.rb +26 -0
- data/lib/interfacets/client/facet2.rb +15 -0
- data/lib/interfacets/client/facets/attributes/accessor.rb +28 -0
- data/lib/interfacets/client/facets/attributes/association.rb +50 -0
- data/lib/interfacets/client/facets/attributes/bind.rb +25 -0
- data/lib/interfacets/client/facets/attributes/collection.rb +47 -0
- data/lib/interfacets/client/facets/attributes/readonly.rb +19 -0
- data/lib/interfacets/client/facets/deserializer.rb +30 -0
- data/lib/interfacets/client/facets/schema/deserializer.rb +63 -0
- data/lib/interfacets/client/facets/schema.rb +63 -0
- data/lib/interfacets/client/facets/serializer.rb +18 -0
- data/lib/interfacets/client/registry.rb +84 -0
- data/lib/interfacets/client/system.rb +88 -0
- data/lib/interfacets/client/utils/active_support_concern.rb +220 -0
- data/lib/interfacets/client/utils/mruby_patches.rb +81 -0
- data/lib/interfacets/client/utils/open_struct.rb +102 -0
- data/lib/interfacets/client/utils/securerandom.rb +69 -0
- data/lib/interfacets/client/view.rb +47 -0
- data/lib/interfacets/client.rb +13 -0
- data/lib/interfacets/mruby/build.dockerfile +66 -0
- data/lib/interfacets/mruby/build_config.rb +20 -0
- data/lib/interfacets/mruby/entrypoint.rb +23 -0
- data/lib/interfacets/mruby/init.c +66 -0
- data/lib/interfacets/server/api.rb +64 -0
- data/lib/interfacets/server/assets/facet.rb +61 -0
- data/lib/interfacets/server/assets.rb +210 -0
- data/lib/interfacets/server/basic_routable.rb +40 -0
- data/lib/interfacets/server/basic_router.rb +74 -0
- data/lib/interfacets/server/bus.rb +39 -0
- data/lib/interfacets/server/config.rb +87 -0
- data/lib/interfacets/server/facet.rb +51 -0
- data/lib/interfacets/server/facets/deserializer.rb +25 -0
- data/lib/interfacets/server/facets/schema/serializer.rb +54 -0
- data/lib/interfacets/server/facets/serializer.rb +50 -0
- data/lib/interfacets/server/registry.rb +212 -0
- data/lib/interfacets/shared/entities/bus.rb +218 -0
- data/lib/interfacets/shared/entities/collection_proxy.rb +190 -0
- data/lib/interfacets/shared/entities/specs/handlers.rb +117 -0
- data/lib/interfacets/shared/entities/specs.rb +124 -0
- data/lib/interfacets/shared/entity.rb +178 -0
- data/lib/interfacets/shared/entity_collection.rb +88 -0
- data/lib/interfacets/shared/generated_store.rb +145 -0
- data/lib/interfacets/shared/utils.rb +54 -0
- data/lib/interfacets/shared/validations.rb +71 -0
- data/lib/interfacets/test/browser.rb +63 -0
- data/lib/interfacets/test/js/inline_bus.rb +91 -0
- data/lib/interfacets/test/js/nodo_bus.rb +81 -0
- data/lib/interfacets/test/js/receivers/api.rb +37 -0
- data/lib/interfacets/test/js/receivers/react/node.rb +167 -0
- data/lib/interfacets/test/js/receivers/react.rb +31 -0
- data/lib/interfacets/test/js/receivers/url.rb +55 -0
- data/lib/interfacets/test.rb +17 -0
- data/lib/interfacets/version.rb +1 -1
- data/lib/interfacets.rb +26 -2
- metadata +103 -6
- data/README.md +0 -35
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Interfacets
|
|
4
|
+
module Shared
|
|
5
|
+
module Validations
|
|
6
|
+
extend ActiveSupport::Concern
|
|
7
|
+
|
|
8
|
+
class Errors
|
|
9
|
+
include Enumerable
|
|
10
|
+
|
|
11
|
+
def initialize
|
|
12
|
+
@errors = Hash.new { |h, k| h[k] = [] }
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def add(k, v)
|
|
16
|
+
@errors[k] << v
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def [](k)
|
|
20
|
+
@errors[k]
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def each(...)
|
|
24
|
+
@errors.each(...)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def empty?
|
|
28
|
+
@errors.all? { _2.empty? }
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
included do
|
|
33
|
+
def self.validators
|
|
34
|
+
@validators ||= []
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def self.validate(&block)
|
|
38
|
+
raise(ArgumentError.new("block required")) unless block_given?
|
|
39
|
+
|
|
40
|
+
validators << block
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def errors
|
|
44
|
+
@errors ||= Errors.new
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def errors_if_changed(attr)
|
|
48
|
+
errors[attr].any? ? errors[attr] : nil
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def valid?
|
|
52
|
+
errors.empty?
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def validate
|
|
56
|
+
@errors = Errors.new
|
|
57
|
+
self.class.validators.each do |block|
|
|
58
|
+
instance_exec(&block)
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# on_change do |attr, _old_val, _new_val|
|
|
63
|
+
# reset_validations
|
|
64
|
+
# end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
class_methods do
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
|
|
2
|
+
module Interfacets
|
|
3
|
+
module Test
|
|
4
|
+
class Browser
|
|
5
|
+
TYPES = {
|
|
6
|
+
inline: Js::InlineBus,
|
|
7
|
+
nodo: Js::NodoBus,
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
attr_reader :system_json, :router, :type
|
|
11
|
+
def initialize(system_json:, router:, type:)
|
|
12
|
+
unless TYPES.keys.include?(type)
|
|
13
|
+
raise ArgumentError.new(
|
|
14
|
+
"type: #{type.inspect} not allowed. Must be one of #{TYPES.keys}"
|
|
15
|
+
)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
@type = type
|
|
19
|
+
# Serialize and deserialize to ensure proper data structure for JS
|
|
20
|
+
@system_json = JSON.parse(system_json.to_json)
|
|
21
|
+
@router = router
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def visit(path)
|
|
25
|
+
js.init(
|
|
26
|
+
hydrated_facet: router.call(path).render
|
|
27
|
+
)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def c(channel_id)
|
|
31
|
+
js.handler(channel_id)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def dispatch(channel_id, event)
|
|
35
|
+
js.dispatch(channel_id, event)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def url
|
|
39
|
+
c("url")
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def dom
|
|
43
|
+
c("dom")
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
private
|
|
47
|
+
|
|
48
|
+
def js
|
|
49
|
+
@js ||= (
|
|
50
|
+
js_api = Test::Js::Receivers::Api.new(name: "interfacets:api", router:)
|
|
51
|
+
js_react = Test::Js::Receivers::React.new(name: "dom")
|
|
52
|
+
js_url = Test::Js::Receivers::Url.new(name: "url")
|
|
53
|
+
receivers = [js_api, js_react, js_url]
|
|
54
|
+
|
|
55
|
+
TYPES.fetch(type).new(
|
|
56
|
+
receiver_index: receivers.map { [_1.name, _1] }.to_h,
|
|
57
|
+
client_system_json: system_json,
|
|
58
|
+
)
|
|
59
|
+
)
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
require "nodo"
|
|
2
|
+
|
|
3
|
+
module Interfacets
|
|
4
|
+
module Test
|
|
5
|
+
module Js
|
|
6
|
+
class InlineBus
|
|
7
|
+
attr_reader :receiver_index, :client_system_json
|
|
8
|
+
def initialize(receiver_index:, client_system_json:)
|
|
9
|
+
@receiver_index = receiver_index
|
|
10
|
+
@client_system_json = client_system_json
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def handler(channel_id)
|
|
14
|
+
receiver_index.fetch(channel_id).handler
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def init(hydrated_facet:)
|
|
18
|
+
client.handle(H.j({
|
|
19
|
+
type: "interfacets:system:create_bus",
|
|
20
|
+
payload: {
|
|
21
|
+
id: "default",
|
|
22
|
+
channel_ids: ["interfacets:api", "dom", "url"],
|
|
23
|
+
hydration: {
|
|
24
|
+
destination: { bus: "default", channel: "interfacets:api" },
|
|
25
|
+
type: "interfacets:api:hydrate",
|
|
26
|
+
payload: hydrated_facet
|
|
27
|
+
},
|
|
28
|
+
config: client_system_json,
|
|
29
|
+
}
|
|
30
|
+
}))
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
private
|
|
34
|
+
|
|
35
|
+
def handle(event)
|
|
36
|
+
case event.fetch("type")
|
|
37
|
+
when "interfacets:system:render"
|
|
38
|
+
bus_event = event.fetch("payload")
|
|
39
|
+
bus_id = bus_event.fetch("id")
|
|
40
|
+
|
|
41
|
+
bus_event.fetch("payload").each do |channel_event|
|
|
42
|
+
channel_id = channel_event.fetch("id")
|
|
43
|
+
receiver_index
|
|
44
|
+
.fetch(channel_id)
|
|
45
|
+
.receive(
|
|
46
|
+
payload: channel_event.fetch("payload"),
|
|
47
|
+
dispatch: ->(ev) {
|
|
48
|
+
dispatch(bus_id:, channel_id:, event: ev)
|
|
49
|
+
},
|
|
50
|
+
)
|
|
51
|
+
end
|
|
52
|
+
else
|
|
53
|
+
raise("unhandled event type: #{event}")
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def dispatch(bus_id:, channel_id:, event:)
|
|
58
|
+
client.handle(
|
|
59
|
+
H.j(
|
|
60
|
+
{
|
|
61
|
+
destination: { bus: bus_id, channel: channel_id },
|
|
62
|
+
type: "interfacets:channel:event",
|
|
63
|
+
payload: event,
|
|
64
|
+
},
|
|
65
|
+
),
|
|
66
|
+
)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def client
|
|
70
|
+
@client ||= (
|
|
71
|
+
$asset_logger = Logger.new("/dev/null")
|
|
72
|
+
original_verbose = $VERBOSE
|
|
73
|
+
$VERBOSE = nil
|
|
74
|
+
begin
|
|
75
|
+
Client::Assets.bootstrap(client_system_json.fetch("assets"))
|
|
76
|
+
ensure
|
|
77
|
+
$VERBOSE = original_verbose
|
|
78
|
+
end
|
|
79
|
+
Client::System.logger = $asset_logger
|
|
80
|
+
|
|
81
|
+
Client.start(
|
|
82
|
+
transmit: ->(event) {
|
|
83
|
+
handle(H.j(event))
|
|
84
|
+
},
|
|
85
|
+
)
|
|
86
|
+
)
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
require "nodo"
|
|
2
|
+
|
|
3
|
+
module Interfacets
|
|
4
|
+
module Test
|
|
5
|
+
module Js
|
|
6
|
+
class NodoBus < Nodo::Core
|
|
7
|
+
attr_reader :receiver_index, :client_system_json
|
|
8
|
+
def initialize(receiver_index:, client_system_json:)
|
|
9
|
+
super()
|
|
10
|
+
@receiver_index = receiver_index
|
|
11
|
+
@client_system_json = client_system_json
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def dispatch(channel_id, event)
|
|
15
|
+
js_dispatch(channel_id, event)
|
|
16
|
+
render
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def handler(channel_id)
|
|
20
|
+
receiver_index.fetch(channel_id).handler
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def init(hydrated_facet:)
|
|
24
|
+
js_init(
|
|
25
|
+
clientSystemJson: client_system_json,
|
|
26
|
+
hydratedFacet: hydrated_facet
|
|
27
|
+
)
|
|
28
|
+
render
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def render
|
|
32
|
+
receiver_index.each do |id, ch|
|
|
33
|
+
ch.receive(
|
|
34
|
+
payload: js_get_state(id).fetch("data"),
|
|
35
|
+
dispatch: ->(e) { dispatch(id, e) }
|
|
36
|
+
)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
import app: File.join(__dir__, "../../../../test/wasm/app.mjs")
|
|
41
|
+
import :fs
|
|
42
|
+
|
|
43
|
+
function :js_init, <<~JS
|
|
44
|
+
async (config) => {
|
|
45
|
+
global.logs = []
|
|
46
|
+
global.console = {
|
|
47
|
+
log: (...msgs) => {
|
|
48
|
+
global.logs.push({
|
|
49
|
+
type: "log",
|
|
50
|
+
value: msgs.map(m => m.toString())
|
|
51
|
+
})
|
|
52
|
+
},
|
|
53
|
+
error: (...msgs) => {
|
|
54
|
+
global.logs.push({
|
|
55
|
+
type: "error",
|
|
56
|
+
value: msgs.map(m => m.toString())
|
|
57
|
+
})
|
|
58
|
+
},
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
await app.init(config)
|
|
62
|
+
}
|
|
63
|
+
JS
|
|
64
|
+
|
|
65
|
+
function :js_dispatch, <<~JS
|
|
66
|
+
(channelName, event) => {
|
|
67
|
+
app.dispatch(channelName, event)
|
|
68
|
+
}
|
|
69
|
+
JS
|
|
70
|
+
|
|
71
|
+
function :js_get_logs, <<~JS
|
|
72
|
+
() => global.logs
|
|
73
|
+
JS
|
|
74
|
+
|
|
75
|
+
function :js_get_state, <<~JS
|
|
76
|
+
(channelName) => app.getState(channelName)
|
|
77
|
+
JS
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# require_relative "./channels/react"
|
|
4
|
+
|
|
5
|
+
module Interfacets
|
|
6
|
+
module Test
|
|
7
|
+
module Js
|
|
8
|
+
module Receivers
|
|
9
|
+
class Api
|
|
10
|
+
attr_reader :router, :name, :response_queue
|
|
11
|
+
def initialize(name:, router:)
|
|
12
|
+
@name = name
|
|
13
|
+
@router = router
|
|
14
|
+
@response_queue = []
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def handler
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def receive(payload:, dispatch:)
|
|
21
|
+
@dispatch = dispatch
|
|
22
|
+
|
|
23
|
+
return if payload.nil?
|
|
24
|
+
return if payload.empty?
|
|
25
|
+
return if payload.dig("streams", "default").nil?
|
|
26
|
+
return if payload.dig("streams", "default").empty?
|
|
27
|
+
|
|
28
|
+
url = payload.dig("streams", "default", "url")
|
|
29
|
+
payload
|
|
30
|
+
.dig("streams", "default", "body", "event", "payload")
|
|
31
|
+
.then { response_queue << router.call(url).handle(_1) }
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "nokogiri"
|
|
4
|
+
|
|
5
|
+
module Interfacets
|
|
6
|
+
module Test
|
|
7
|
+
module Js
|
|
8
|
+
module Receivers
|
|
9
|
+
class React
|
|
10
|
+
class Node
|
|
11
|
+
class XmlParser
|
|
12
|
+
attr_reader :json
|
|
13
|
+
def initialize(json)
|
|
14
|
+
@json = json
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def call
|
|
18
|
+
xml = Nokogiri::XML.fragment("<Facet/>")
|
|
19
|
+
json
|
|
20
|
+
.dig("streams", "default", "dom")
|
|
21
|
+
.map { parse_element(_1) }
|
|
22
|
+
.each { xml.add_child(_1) }
|
|
23
|
+
xml
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
private
|
|
27
|
+
|
|
28
|
+
def parse_attribute(value)
|
|
29
|
+
case value
|
|
30
|
+
when Array
|
|
31
|
+
when Hash
|
|
32
|
+
value.transform_values { parse_attribute(_1) }
|
|
33
|
+
when Numeric, String, TrueClass, FalseClass, NilClass
|
|
34
|
+
value
|
|
35
|
+
else
|
|
36
|
+
raise "unknown attribute type: #{value}"
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def parse_element(el)
|
|
41
|
+
case el.fetch("type")
|
|
42
|
+
when "interfacets:string-node"
|
|
43
|
+
el.dig("attributes", "value")
|
|
44
|
+
when "interfacets:react-dom:element"
|
|
45
|
+
xml = (
|
|
46
|
+
el
|
|
47
|
+
.fetch("element")
|
|
48
|
+
.then { Nokogiri::XML.fragment("<#{_1} />").children.first }
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
el.fetch("children").each do |child|
|
|
52
|
+
xml.add_child(parse_element(child)) if child
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
el.fetch("attributes").each do |name, value|
|
|
56
|
+
xml.set_attribute(name, parse_attribute(value).to_json)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
xml
|
|
60
|
+
else
|
|
61
|
+
raise "unknown type: #{el.fetch("type")}"
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def self.parse(json:, dispatch:)
|
|
67
|
+
new(
|
|
68
|
+
xml: XmlParser.new(json).call,
|
|
69
|
+
dispatch:,
|
|
70
|
+
parent: nil,
|
|
71
|
+
)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
Error = Class.new(StandardError)
|
|
75
|
+
NoMatchesErrorError = Class.new(Error)
|
|
76
|
+
MultipleMatchesError = Class.new(Error)
|
|
77
|
+
StaleNodeError = Class.new(Error) do
|
|
78
|
+
def initialize
|
|
79
|
+
super(
|
|
80
|
+
<<~TXT
|
|
81
|
+
This node is stale and cannot be used. This occurs when the facet \
|
|
82
|
+
has been updated and you are using a reference to an out-of-date \
|
|
83
|
+
node. Re-fetch the node using `page.dom` rather than storing a \
|
|
84
|
+
reference to the node.
|
|
85
|
+
TXT
|
|
86
|
+
)
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
attr_reader :xml, :dispatch, :parent
|
|
91
|
+
def initialize(xml:, dispatch:, parent:)
|
|
92
|
+
@xml = xml
|
|
93
|
+
@dispatch = dispatch
|
|
94
|
+
@parent = parent
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def stale!
|
|
98
|
+
@stale = true
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def stale?
|
|
102
|
+
@stale || parent&.stale?
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def content
|
|
106
|
+
raise StaleNodeError if stale?
|
|
107
|
+
|
|
108
|
+
xml.content
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def one(...)
|
|
112
|
+
raise StaleNodeError if stale?
|
|
113
|
+
|
|
114
|
+
all(...)
|
|
115
|
+
.tap {
|
|
116
|
+
if _1.count == 0
|
|
117
|
+
raise NoMatchesError
|
|
118
|
+
elsif _1.count > 1
|
|
119
|
+
raise MultipleMatchesError.new(_1.map(&:content).join(", "))
|
|
120
|
+
end
|
|
121
|
+
}
|
|
122
|
+
.first
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
EMPTY_ARG = Object.new
|
|
126
|
+
|
|
127
|
+
def all(*a, content: EMPTY_ARG, **p)
|
|
128
|
+
raise StaleNodeError if stale?
|
|
129
|
+
|
|
130
|
+
xml
|
|
131
|
+
.css(*a, **p)
|
|
132
|
+
.map { Node.new(xml: _1, dispatch:, parent: self) }
|
|
133
|
+
.select {
|
|
134
|
+
(
|
|
135
|
+
content == EMPTY_ARG || (
|
|
136
|
+
if content.is_a?(Regexp)
|
|
137
|
+
_1.content.match(content)
|
|
138
|
+
else
|
|
139
|
+
_1.content == content
|
|
140
|
+
end
|
|
141
|
+
)
|
|
142
|
+
)
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def attribute(name)
|
|
148
|
+
raise StaleNodeError if stale?
|
|
149
|
+
|
|
150
|
+
xml
|
|
151
|
+
.attribute(name)
|
|
152
|
+
.then { JSON.parse(_1) }
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
def trigger(name, data = {})
|
|
156
|
+
raise StaleNodeError if stale?
|
|
157
|
+
|
|
158
|
+
value = attribute(name.to_s)
|
|
159
|
+
value["payload"]["event"] = data
|
|
160
|
+
dispatch.(value)
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "nokogiri"
|
|
4
|
+
require_relative "./react/node"
|
|
5
|
+
|
|
6
|
+
module Interfacets
|
|
7
|
+
module Test
|
|
8
|
+
module Js
|
|
9
|
+
module Receivers
|
|
10
|
+
class React
|
|
11
|
+
attr_reader :name, :node
|
|
12
|
+
def initialize(name:)
|
|
13
|
+
@name = name
|
|
14
|
+
@actions = {}
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def receive(payload:, dispatch:)
|
|
18
|
+
@dispatch = dispatch
|
|
19
|
+
@actions = nil
|
|
20
|
+
@node&.stale!
|
|
21
|
+
@node = Node.parse(json: payload, dispatch:)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def handler
|
|
25
|
+
@node
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'uri'
|
|
4
|
+
|
|
5
|
+
module Interfacets
|
|
6
|
+
module Test
|
|
7
|
+
module Js
|
|
8
|
+
module Receivers
|
|
9
|
+
class Url
|
|
10
|
+
class Handler
|
|
11
|
+
def initialize(state)
|
|
12
|
+
@state = state
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def url
|
|
16
|
+
url_spec = @state.fetch("urlSpec")
|
|
17
|
+
base_url = url_spec["url"]
|
|
18
|
+
query_params = url_spec["queryParams"]
|
|
19
|
+
|
|
20
|
+
return base_url if query_params.nil? || query_params.empty?
|
|
21
|
+
|
|
22
|
+
uri = URI(base_url)
|
|
23
|
+
uri.query = URI.encode_www_form(query_params)
|
|
24
|
+
uri.to_s
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def redirected_url
|
|
28
|
+
@state.fetch("redirectSpec")
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
attr_reader :server, :name, :response_queue
|
|
33
|
+
def initialize(name:)
|
|
34
|
+
@name = name
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def receive(payload:, dispatch:)
|
|
38
|
+
@dispatch = dispatch
|
|
39
|
+
|
|
40
|
+
return if payload.nil?
|
|
41
|
+
return if payload.empty?
|
|
42
|
+
return if payload.dig("streams", "default").nil?
|
|
43
|
+
return if payload.dig("streams", "default").empty?
|
|
44
|
+
|
|
45
|
+
@handler = Handler.new(payload.dig("streams", "default"))
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def handler
|
|
49
|
+
@handler
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# require_relative "./test/session"
|
|
4
|
+
# require_relative "./test/js"
|
|
5
|
+
# require_relative "./test/js/channels"
|
|
6
|
+
|
|
7
|
+
module Interfacets
|
|
8
|
+
module Test
|
|
9
|
+
module H
|
|
10
|
+
module_function
|
|
11
|
+
|
|
12
|
+
def j(hash)
|
|
13
|
+
JSON.parse(hash.to_json)
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
data/lib/interfacets/version.rb
CHANGED
data/lib/interfacets.rb
CHANGED
|
@@ -1,8 +1,32 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
require "zeitwerk"
|
|
4
4
|
|
|
5
5
|
module Interfacets
|
|
6
|
+
class << self
|
|
7
|
+
def reload
|
|
8
|
+
loader.reload
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def loader
|
|
12
|
+
@loader ||= (
|
|
13
|
+
Zeitwerk::Loader
|
|
14
|
+
.for_gem
|
|
15
|
+
.tap { _1.ignore("#{__dir__}/interfacets/mruby") }
|
|
16
|
+
.tap { _1.ignore("#{__dir__}/interfacets/client/utils") }
|
|
17
|
+
.tap { _1.enable_reloading if enable_reloading? }
|
|
18
|
+
.tap(&:setup)
|
|
19
|
+
)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def enable_reloading?
|
|
23
|
+
$interfacets_dev_mode
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
6
27
|
class Error < StandardError; end
|
|
7
|
-
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
unless Interfacets.enable_reloading?
|
|
31
|
+
Interfacets.loader
|
|
8
32
|
end
|