ruflet_rails 0.0.1

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: e4e545e3193b769addf80f3ec9f7a902fefed5ecaab529bc34b718d66d683679
4
+ data.tar.gz: f9011791ffdff4236321fd0ea0df09acd688a999a3d8612c06aa5acdddcbf0af
5
+ SHA512:
6
+ metadata.gz: 8dcaf1e929c4707fd9ca238332f558408d7016900de561fb8cbcbeaefcddae07fa0808b64861bc3ef5efdecbaed208f9153390ab59db0910788f7f231556a0ff
7
+ data.tar.gz: 8a2218ad72419c96bc5b03dca4e41938aac900c7dd70afb7f639159682e38ded805e81aa0f8dd3d3488d3cd8d76fafb58ebcd4ea07ff85fed2827762d1bdd89c
data/README.md ADDED
@@ -0,0 +1,36 @@
1
+ # Ruflet Rails
2
+
3
+ `ruflet_rails` is the Rails-first integration package for Ruflet.
4
+
5
+ Internal Rails transport/protocol code is bundled inside this gem as `Ruflet::Rails::Protocol`.
6
+ No separate protocol gem is required.
7
+
8
+ ## Usage
9
+
10
+ ```ruby
11
+ # Gemfile
12
+ path "/path/to/ruflet/packages" do
13
+ gem "ruflet_rails"
14
+ end
15
+ ```
16
+
17
+ ```ruby
18
+ # app/mobile/main.rb
19
+ require "ruflet"
20
+
21
+ class MyApp < Ruflet::App
22
+ def view(page)
23
+ page.title = "Hello"
24
+ page.add(page.text(value: "Hello Ruflet"))
25
+ end
26
+ end
27
+
28
+ MyApp.new.run
29
+ ```
30
+
31
+ ```ruby
32
+ # config/routes.rb
33
+ mount Ruflet::Rails.mobile(Rails.root.join("app/mobile/main.rb")), at: "/ws"
34
+ ```
35
+
36
+ Connect mobile to Rails base URL, e.g. `http://10.0.2.2:3000`.
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ruflet
4
+ module Rails
5
+ module Protocol
6
+ module Context
7
+ THREAD_KEY = :ruflet_rails_env
8
+
9
+ module_function
10
+
11
+ def current_env
12
+ Thread.current[THREAD_KEY]
13
+ end
14
+
15
+ def with_env(env)
16
+ previous = Thread.current[THREAD_KEY]
17
+ Thread.current[THREAD_KEY] = env
18
+ yield
19
+ ensure
20
+ Thread.current[THREAD_KEY] = previous
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "digest/sha1"
4
+
5
+ module Ruflet
6
+ module Rails
7
+ module Protocol
8
+ class Endpoint
9
+ WEBSOCKET_GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
10
+
11
+ def initialize(server:, path: "/ws")
12
+ @server = server
13
+ @path = path
14
+ end
15
+
16
+ def call(env)
17
+ return not_found unless env["PATH_INFO"] == @path
18
+ return bad_request("Expected WebSocket upgrade") unless websocket_upgrade_request?(env)
19
+
20
+ hijack = env["rack.hijack"]
21
+ return bad_request("Rack hijack is required by Ruflet::Rails::Protocol::Endpoint") unless hijack.respond_to?(:call)
22
+
23
+ hijack.call
24
+ io = env["rack.hijack_io"]
25
+ return bad_request("Rack did not provide hijacked IO") unless io
26
+
27
+ io.sync = true if io.respond_to?(:sync=)
28
+ write_handshake(io, env["HTTP_SEC_WEBSOCKET_KEY"])
29
+
30
+ captured_env = env.dup
31
+ Thread.new(io, captured_env) do |socket, ws_env|
32
+ Thread.current.report_on_exception = false if Thread.current.respond_to?(:report_on_exception=)
33
+ Context.with_env(ws_env) do
34
+ @server.handle_upgraded_socket(socket)
35
+ end
36
+ end
37
+
38
+ [-1, {}, []]
39
+ rescue StandardError => e
40
+ [500, { "content-type" => "text/plain" }, ["Ruflet Rails protocol error: #{e.message}"]]
41
+ end
42
+
43
+ private
44
+
45
+ def websocket_upgrade_request?(env)
46
+ return false unless env["REQUEST_METHOD"] == "GET"
47
+
48
+ upgrade = env["HTTP_UPGRADE"].to_s.downcase
49
+ connection = env["HTTP_CONNECTION"].to_s.downcase
50
+ key = env["HTTP_SEC_WEBSOCKET_KEY"].to_s
51
+
52
+ upgrade == "websocket" && connection.include?("upgrade") && !key.empty?
53
+ end
54
+
55
+ def write_handshake(io, key)
56
+ accept = [Digest::SHA1.digest("#{key}#{WEBSOCKET_GUID}")].pack("m0")
57
+
58
+ io.write("HTTP/1.1 101 Switching Protocols\r\n")
59
+ io.write("Upgrade: websocket\r\n")
60
+ io.write("Connection: Upgrade\r\n")
61
+ io.write("Sec-WebSocket-Accept: #{accept}\r\n")
62
+ io.write("\r\n")
63
+ end
64
+
65
+ def not_found
66
+ [404, { "content-type" => "text/plain" }, ["Not Found"]]
67
+ end
68
+
69
+ def bad_request(message)
70
+ [400, { "content-type" => "text/plain" }, [message]]
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ruflet
4
+ module Rails
5
+ module Protocol
6
+ class Middleware
7
+ def initialize(app)
8
+ @app = app
9
+ end
10
+
11
+ def call(env)
12
+ Context.with_env(env) { @app.call(env) }
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ruflet
4
+ module Rails
5
+ module Protocol
6
+ class MobileLoader
7
+ def initialize(file_path)
8
+ @file_path = file_path
9
+ end
10
+
11
+ def load!
12
+ absolute = File.expand_path(@file_path)
13
+ raise ArgumentError, "Mobile file not found: #{absolute}" unless File.file?(absolute)
14
+
15
+ captured_entrypoint = nil
16
+ interceptor = lambda do |entrypoint:, **_kwargs|
17
+ captured_entrypoint = entrypoint
18
+ :captured
19
+ end
20
+
21
+ Ruflet.with_run_interceptor(interceptor) do
22
+ Kernel.load(absolute)
23
+ end
24
+
25
+ raise ArgumentError, "No Ruflet app boot found in #{absolute}. Expected MyApp.new.run" unless captured_entrypoint
26
+
27
+ { entrypoint: captured_entrypoint }
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ruflet
4
+ module Rails
5
+ module Protocol
6
+ class Mount
7
+ def initialize(app, file_path:, path: "/ws")
8
+ @app = app
9
+ @path = path
10
+ @endpoint = Runner.new.build_mobile_endpoint(file_path: file_path, path: "/")
11
+ end
12
+
13
+ def call(env)
14
+ return @app.call(env) unless env["PATH_INFO"] == @path
15
+
16
+ mounted_env = env.dup
17
+ mounted_env["PATH_INFO"] = "/"
18
+ @endpoint.call(mounted_env)
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ruflet
4
+ module Rails
5
+ module Protocol
6
+ class Runner
7
+ def initialize(&entrypoint)
8
+ @entrypoint = entrypoint
9
+ end
10
+
11
+ def build_endpoint(path: "/")
12
+ raise ArgumentError, "Ruflet::Rails::Protocol endpoint requires a block" unless @entrypoint.respond_to?(:call)
13
+
14
+ Endpoint.new(server: build_server(@entrypoint), path: path)
15
+ end
16
+
17
+ def build_mobile_endpoint(file_path:, path: "/")
18
+ loaded = MobileLoader.new(file_path).load!
19
+ Endpoint.new(server: build_server(loaded[:entrypoint]), path: path)
20
+ end
21
+
22
+ private
23
+
24
+ def build_server(entrypoint)
25
+ Ruflet::Server.new do |page|
26
+ env = Context.current_env
27
+ if entrypoint.arity == 1
28
+ entrypoint.call(page)
29
+ else
30
+ entrypoint.call(page, env)
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "protocol/context"
4
+ require_relative "protocol/middleware"
5
+ require_relative "protocol/endpoint"
6
+ require_relative "protocol/mobile_loader"
7
+ require_relative "protocol/runner"
8
+ require_relative "protocol/mount"
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ruflet
4
+ module Rails
5
+ class Railtie < ::Rails::Railtie
6
+ initializer "ruflet_rails.middleware" do |app|
7
+ app.middleware.use Ruflet::Rails::Protocol::Middleware
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ruflet
4
+ module Rails
5
+ module_function
6
+
7
+ # Mount inside Rails routes; route "at:" controls URL path.
8
+ def endpoint(&block)
9
+ Protocol::Runner.new(&block).build_endpoint
10
+ end
11
+
12
+ # Load app/mobile/main.rb (MyApp.new.run) and mount it in Rails routes.
13
+ def mobile(file_path)
14
+ Protocol::Runner.new.build_mobile_endpoint(file_path: file_path)
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ruflet
4
+ VERSION = "0.0.1" unless const_defined?(:VERSION)
5
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "ruflet"
4
+ require_relative "ruflet/rails/protocol"
5
+ require_relative "ruflet/rails"
6
+
7
+ if defined?(::Rails::Railtie)
8
+ require_relative "ruflet/rails/railtie"
9
+ end
metadata ADDED
@@ -0,0 +1,80 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ruflet_rails
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - AdamMusa
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: rails
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: '7.0'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - ">="
24
+ - !ruby/object:Gem::Version
25
+ version: '7.0'
26
+ - !ruby/object:Gem::Dependency
27
+ name: ruflet
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - '='
31
+ - !ruby/object:Gem::Version
32
+ version: 0.0.1
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - '='
38
+ - !ruby/object:Gem::Version
39
+ version: 0.0.1
40
+ description: Rails-first integration package for mounting Ruflet mobile apps in Rails
41
+ routes.
42
+ email:
43
+ - adammusa2222@gmail.com
44
+ executables: []
45
+ extensions: []
46
+ extra_rdoc_files: []
47
+ files:
48
+ - README.md
49
+ - lib/ruflet/rails.rb
50
+ - lib/ruflet/rails/protocol.rb
51
+ - lib/ruflet/rails/protocol/context.rb
52
+ - lib/ruflet/rails/protocol/endpoint.rb
53
+ - lib/ruflet/rails/protocol/middleware.rb
54
+ - lib/ruflet/rails/protocol/mobile_loader.rb
55
+ - lib/ruflet/rails/protocol/mount.rb
56
+ - lib/ruflet/rails/protocol/runner.rb
57
+ - lib/ruflet/rails/railtie.rb
58
+ - lib/ruflet/version.rb
59
+ - lib/ruflet_rails.rb
60
+ homepage: https://github.com/AdamMusa/Ruflet
61
+ licenses: []
62
+ metadata: {}
63
+ rdoc_options: []
64
+ require_paths:
65
+ - lib
66
+ required_ruby_version: !ruby/object:Gem::Requirement
67
+ requirements:
68
+ - - ">="
69
+ - !ruby/object:Gem::Version
70
+ version: '3.1'
71
+ required_rubygems_version: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ requirements: []
77
+ rubygems_version: 3.7.2
78
+ specification_version: 4
79
+ summary: Rails integration for Ruflet.
80
+ test_files: []