tuix_rails 0.1.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: dd2c3676824936348b36faf27edba0191067f31a2d2ecce7c5d5e4f33b05178d
4
+ data.tar.gz: 1be22f93ab08113a150736b9a2d7726076a36481c756c8edbbf0af2e2e87e190
5
+ SHA512:
6
+ metadata.gz: 6a58fd4728e42ef4be71e8bec5e623245319d819e716e81778d2eca5a8ae4c17da31cfcc28c8c6f484285f46093008d6d71f82e8cf077630618544905ebcb576
7
+ data.tar.gz: ca64d02cb52fce897c1f9610976d4b92ec0f1c4f9408aaec4b18b1978d8ecb0f82d1a4061e44a95ca44c22debd1dd4328766418aceb862b9ed0a3c40ecf53b78
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Maciek Wciślik
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,151 @@
1
+ # tuix_rails
2
+
3
+ Rails integration for the existing Tuix widget: server-signed identities, hosted
4
+ widget delivery, CSP nonces and Turbo navigation. Requires Ruby 3.2+ and Rails
5
+ 7.2–8.x (Rails itself may require a newer Ruby). No database, npm installation,
6
+ asset precompile or separate Tuix login is needed by the host application.
7
+
8
+ MIT licensed. Until the gem is on RubyGems, install it from a checkout of this
9
+ repository or from a built gem, as below.
10
+
11
+ ## Install
12
+
13
+ Add the package to the host application's Gemfile, using a checkout of this
14
+ repository or an unpacked gem artifact:
15
+
16
+ ```ruby
17
+ gem "tuix_rails", path: "/absolute/path/to/tuix/packages/rails"
18
+ ```
19
+
20
+ For the built artifact, run `gem unpack output/releases/tuix_rails-0.1.0.gem`
21
+ and point `path:` at the resulting `tuix_rails-0.1.0` directory.
22
+
23
+ ```sh
24
+ bundle install
25
+ bin/rails generate tuix:install
26
+ ```
27
+
28
+ The generator creates `config/initializers/tuix.rb`. It prints the layout steps
29
+ instead of guessing your authentication library or rewriting custom layouts.
30
+ Re-running it uses Rails' normal file conflict handling; `--skip` preserves a
31
+ custom initializer.
32
+
33
+ Set `TUIX_PROJECT_KEY`, `TUIX_SECRET_KEY`, `TUIX_API_URL` (default
34
+ `https://tuix.app`) and `TUIX_ENVIRONMENT` (default `uat`) on the host server.
35
+ Allow the host's exact origin in Tuix project settings and keep signed identity
36
+ enabled. HTTP API origins are accepted only for localhost/127.0.0.1 development.
37
+
38
+ Add the asset helper inside your layout's `<head>`:
39
+
40
+ ```erb
41
+ <%= tuix_assets %>
42
+ ```
43
+
44
+ Add one identity helper inside `<body>`, outside any fragment shared across users:
45
+
46
+ ```erb
47
+ <%= tuix_widget(user: current_user && {
48
+ id: current_user.id,
49
+ name: current_user.name,
50
+ email: current_user.email
51
+ }) %>
52
+ ```
53
+
54
+ Adapt `current_user` to your authentication system. Pass `nil` for signed-out
55
+ users or users who should not have feedback access. Supply only trusted server
56
+ identity data, never request parameters. Include email only when your application
57
+ has verified it. Tuix uses the signed email for dashboard access and notifications;
58
+ omit it if you cannot vouch for the address. IDs must be stable strings or integers.
59
+
60
+ The gem signs the exact ID with HMAC-SHA256, or the ID, a newline and the trimmed,
61
+ lowercased email when present (Tuix identity v2). The signing secret never goes
62
+ into HTML. `TuixRails.identity(user)` is also available to sign identities for a
63
+ separate JavaScript frontend. `config.release` optionally identifies a deployed
64
+ build. Missing project/secret or an anonymous user leaves the widget inactive;
65
+ invalid supplied configuration or identity raises an actionable argument error.
66
+
67
+ ## Navigation and delivery
68
+
69
+ The gem serves its small bootstrap at a content-addressed `/tuix-rails/` URL.
70
+ Authenticated pages fetch the configured Tuix origin's public
71
+ `/widget/channels/stable.json` without cookies, validate the manifest, then load
72
+ the immutable widget with SHA-384 integrity. One runtime is selected per document.
73
+ Widget updates arrive on a subsequent full page load without upgrading the gem.
74
+ Bootstrap changes require upgrading the gem and deploying the host.
75
+
76
+ Turbo renders, frames and streams can replace the identity element. The bridge
77
+ destroys the old widget before identifying the new user. Removing the element or
78
+ rendering `user: nil` clears it. Pending loads cannot re-identify after logout.
79
+ The bridge has bounded manifest/script timeouts and retries after navigation.
80
+ Anonymous pages do not request the manifest or widget runtime.
81
+
82
+ Before Turbo caches a page, the bridge removes its widget and signed identity.
83
+ Cached previews stay inactive; a fresh server render restores feedback. A history
84
+ restoration served entirely from Turbo's cache therefore has no feedback identity.
85
+ For authenticated pages that need feedback on every back/forward visit, add this
86
+ to the layout's `<head>` so Turbo fetches fresh server identity:
87
+
88
+ ```html
89
+ <meta name="turbo-cache-control" content="no-cache">
90
+ ```
91
+
92
+ Clear your application's Turbo cache on logout as part of its normal session
93
+ handling. Do not mark the identity element `data-turbo-permanent` or put it in a
94
+ shared full-page cache. Browser back/forward cache follows the host's existing
95
+ session policy; the bridge destroys the widget on `pagehide` and re-reads the
96
+ current page on `pageshow`. See the [Turbo lifecycle documentation](https://turbo.hotwired.dev/handbook/building).
97
+
98
+ ## Content security policy
99
+
100
+ Helpers use Rails' `content_security_policy_nonce` by default; an explicit
101
+ `nonce:` can be passed to both. The bootstrap passes it to the widget loader and
102
+ styles. Keep the nonce consistent with the document's policy across Turbo visits.
103
+ Keep Rails' `<%= csp_meta_tag %>` in the layout head so Turbo can also use that
104
+ nonce for its own scripts and progress styles. The gem does not replace the host's policy.
105
+
106
+ Allow the local bootstrap and the Tuix origin in applicable script directives,
107
+ Tuix fonts, Tuix HTTP/WebSocket connections (including manifest fetches), and
108
+ the storage origin shown in Project settings for uploads and images. Image
109
+ previews need `blob:` and `data:`. Capture requires `style-src-attr 'unsafe-inline'`
110
+ and the empty stylesheet hash
111
+ `'sha256-47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU='` in the applicable style
112
+ directive. Account for `script-src-elem`/`style-src-elem` when present. Test capture
113
+ against the real host policy; Tuix → Project settings → Install tuix → Content
114
+ Security Policy lists the exact sources for your project.
115
+
116
+ ## Development
117
+
118
+ ```sh
119
+ cd packages/rails
120
+ bundle install
121
+ bundle exec ruby -Ilib test/integration_test.rb
122
+ bundle exec ruby ../../scripts/package-rails.rb
123
+ # From the repository root, after npm ci:
124
+ npm test -w @tuix/rails
125
+ npm run build -w @tuix/widget
126
+ npm run test:browser -w @tuix/rails
127
+ ```
128
+
129
+ `RAILS_VERSION='~> 7.2.0' bundle update` selects a different supported Rails line.
130
+ CI covers Rails 7.2/Ruby 3.2, Rails 8.0/Ruby 3.3 and Rails 8.1/Ruby 3.4.
131
+ Integration tests boot a real Rails application, render helpers, fetch the asset
132
+ through middleware, check identity signing/escaping, and exercise the generator.
133
+ The package command builds `output/releases/tuix_rails-0.1.0.gem`, verifies its
134
+ contents and reruns integration tests against a temporary gem installation before copying
135
+ the artifact into place. It never publishes. CI uploads the same local artifact, and the
136
+ release workflow publishes that artifact.
137
+
138
+ Browser regression tests need Playwright Chromium (`npx playwright install chromium`).
139
+ They boot Rails on loopback and exercise the actual Turbo library and built Tuix
140
+ widget: Drive, frames, streams, logout, back navigation, CSP nonces and SRI rejection.
141
+ The remote Tuix API is a deterministic local fixture that verifies Ruby signatures
142
+ with an independent Node HMAC implementation. jsdom tests cover pending-load races
143
+ and recovery. No store/account, Docker or production keys are needed.
144
+
145
+ Locally verified on 11 October 2026 with Ruby 3.2.4: all 9 integration tests passed
146
+ on Rails 7.2.4, 8.0.5.1 and 8.1.4; all 8 bootstrap tests and the Chromium/Turbo
147
+ 8.0.23 browser smoke passed. The CI matrix also exercises newer Ruby versions;
148
+ its remote run is separate from these local results.
149
+
150
+ Before rolling out to your users, try report creation, screenshots and notifications
151
+ against a real Tuix project using the host's authentication, origin and CSP.
@@ -0,0 +1,143 @@
1
+ /* A page selects one runtime. Signed identity follows Rails renders and Turbo navigation. */
2
+ (() => {
3
+ const root = document.documentElement;
4
+ if (root.hasAttribute('data-tuix-rails-bound')) return;
5
+ root.setAttribute('data-tuix-rails-bound', '');
6
+ const sri = /^sha384-[A-Za-z0-9+/]{64}$/;
7
+ const immutable = /^\/widget\/(\d+\.\d+\.\d+(?:-[a-zA-Z0-9.-]+)?)\/[a-f0-9]{64}\/tuix\.js$/;
8
+ let previous, failed, selected, loading, controller;
9
+ let generation = 0;
10
+ let suspended = false;
11
+
12
+ function clear() {
13
+ generation++;
14
+ controller?.abort();
15
+ controller = undefined;
16
+ if (previous !== undefined && typeof window.tuix === 'function') window.tuix('destroy');
17
+ previous = undefined;
18
+ }
19
+
20
+ function apiOrigin(value) {
21
+ const url = new URL(value);
22
+ if (url.username || url.password || url.search || url.hash ||
23
+ !['', '/'].includes(url.pathname) ||
24
+ !(url.protocol === 'https:' || (url.protocol === 'http:' && ['localhost', '127.0.0.1'].includes(url.hostname)))) {
25
+ throw Error('Invalid Tuix API origin');
26
+ }
27
+ return url.origin;
28
+ }
29
+
30
+ function runtime(manifest, origin) {
31
+ const match = typeof manifest?.loader?.url === 'string' && manifest.loader.url.match(immutable);
32
+ if (manifest?.protocol !== 1 || !match || manifest.version !== match[1] ||
33
+ !sri.test(manifest.loader.integrity) ||
34
+ (manifest.directory !== undefined && manifest.directory !== manifest.loader.url.slice(8, -8))) {
35
+ throw Error('Invalid Tuix runtime manifest');
36
+ }
37
+ return { url: origin + manifest.loader.url, integrity: manifest.loader.integrity };
38
+ }
39
+
40
+ async function select(payload, token) {
41
+ const origin = apiOrigin(payload.options.api);
42
+ if (selected) {
43
+ if (selected.origin !== origin || selected.delivery !== payload.delivery) throw Error('Runtime already selected');
44
+ return selected;
45
+ }
46
+ let entry;
47
+ if (payload.delivery === 'hosted') {
48
+ const abort = new AbortController();
49
+ controller = abort;
50
+ const timeout = setTimeout(() => abort.abort(), 8000);
51
+ try {
52
+ const response = await fetch(origin + '/widget/channels/stable.json', {
53
+ mode: 'cors', credentials: 'omit', redirect: 'error', signal: abort.signal
54
+ });
55
+ if (!response.ok) throw Error('Runtime unavailable');
56
+ entry = runtime(await response.json(), origin);
57
+ } finally {
58
+ clearTimeout(timeout);
59
+ if (controller === abort) controller = undefined;
60
+ }
61
+ } else {
62
+ throw Error('Invalid delivery mode');
63
+ }
64
+ if (token !== generation) throw Error('Identity changed');
65
+ selected = { ...entry, origin, delivery: payload.delivery };
66
+ return selected;
67
+ }
68
+
69
+ function load(entry, nonce) {
70
+ if (loading) return loading;
71
+ loading = new Promise((resolve, reject) => {
72
+ const script = document.createElement('script');
73
+ script.src = entry.url;
74
+ script.integrity = entry.integrity;
75
+ script.crossOrigin = 'anonymous';
76
+ if (nonce) script.nonce = nonce;
77
+ const timeout = setTimeout(() => finish(false), 15000);
78
+ function finish(ok) {
79
+ clearTimeout(timeout);
80
+ script.onload = script.onerror = null;
81
+ if (ok) resolve();
82
+ else { script.remove(); loading = undefined; reject(Error('Runtime failed')); }
83
+ }
84
+ script.onload = () => finish(true);
85
+ script.onerror = () => finish(false);
86
+ document.head.append(script);
87
+ });
88
+ return loading;
89
+ }
90
+
91
+ async function synchronize() {
92
+ if (suspended || root.hasAttribute('data-turbo-preview')) return;
93
+ const encoded = document.getElementById('tuix-rails')?.getAttribute('data-tuix-rails');
94
+ if (!encoded) { if (previous !== undefined) clear(); return; }
95
+ if (encoded === previous || encoded === failed) return;
96
+ clear();
97
+ let payload;
98
+ try {
99
+ payload = JSON.parse(encoded);
100
+ if (!payload?.user?.id || !payload.user.name || !/^[a-f0-9]{64}$/.test(payload.user.hash) ||
101
+ !payload.options?.project) return;
102
+ } catch { return; }
103
+ previous = encoded;
104
+ const token = generation;
105
+ try {
106
+ const entry = await select(payload, token);
107
+ if (token !== generation) return;
108
+ await load(entry, payload.options.nonce);
109
+ if (token !== generation || suspended) return;
110
+ if (typeof window.tuix !== 'function') throw Error('Runtime missing');
111
+ window.tuix('destroy');
112
+ window.tuix('init', payload.options);
113
+ window.tuix('identify', payload.user);
114
+ } catch {
115
+ if (token !== generation) return;
116
+ failed = encoded;
117
+ clear();
118
+ }
119
+ }
120
+
121
+ let pending = false;
122
+ function schedule() {
123
+ if (pending) return;
124
+ pending = true;
125
+ queueMicrotask(() => { pending = false; synchronize(); });
126
+ }
127
+ new MutationObserver(schedule).observe(root, {
128
+ childList: true, subtree: true, attributes: true, attributeFilter: ['data-tuix-rails']
129
+ });
130
+ function navigate() { failed = undefined; schedule(); }
131
+ // Stop before Turbo clones the page. Signed identity must not survive in its cache.
132
+ document.addEventListener('turbo:before-cache', () => {
133
+ suspended = true;
134
+ clear();
135
+ document.getElementById('tuix-rails')?.removeAttribute('data-tuix-rails');
136
+ });
137
+ document.addEventListener('turbo:before-render', () => { suspended = true; clear(); });
138
+ document.addEventListener('turbo:load', () => { suspended = false; navigate(); });
139
+ document.addEventListener('turbo:render', () => { suspended = false; navigate(); });
140
+ window.addEventListener('pagehide', () => { suspended = true; clear(); });
141
+ window.addEventListener('pageshow', () => { suspended = false; navigate(); });
142
+ synchronize();
143
+ })();
@@ -0,0 +1,26 @@
1
+ require "rails/generators"
2
+
3
+ module Tuix
4
+ class InstallGenerator < Rails::Generators::Base
5
+ desc "Configure Tuix and print the layout integration steps"
6
+
7
+ def create_configuration
8
+ initializer "tuix.rb", <<~RUBY
9
+ TuixRails.configure do |config|
10
+ config.project = ENV["TUIX_PROJECT_KEY"]
11
+ config.secret = ENV["TUIX_SECRET_KEY"]
12
+ config.api = ENV.fetch("TUIX_API_URL", "https://tuix.app")
13
+ config.environment = ENV.fetch("TUIX_ENVIRONMENT", "uat")
14
+ # config.release = ENV["APP_REVISION"]
15
+ end
16
+ RUBY
17
+ end
18
+
19
+ def installation_steps
20
+ say "Add <%= tuix_assets %> to your layout's <head>."
21
+ say "Add <%= tuix_widget(user: your_authenticated_identity_or_nil) %> inside <body>."
22
+ say "Use a hash with :id, :name and optional verified :email. Never use request parameters."
23
+ say "See the gem README for Turbo cache, CSP and allowed-origin configuration."
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,29 @@
1
+ require "digest"
2
+ require "base64"
3
+
4
+ module TuixRails
5
+ module Assets
6
+ BODY = File.binread(File.expand_path("../../assets/bootstrap.js", __dir__)).freeze
7
+ PATH = "/tuix-rails/#{Digest::SHA256.hexdigest(BODY)}/bootstrap.js".freeze
8
+ INTEGRITY = "sha384-#{Base64.strict_encode64(Digest::SHA384.digest(BODY))}".freeze
9
+ end
10
+
11
+ # Serve exactly one public asset; no filesystem path comes from the request.
12
+ class Middleware
13
+ def initialize(app)
14
+ @app = app
15
+ end
16
+
17
+ def call(env)
18
+ return @app.call(env) unless env["PATH_INFO"] == Assets::PATH && %w[GET HEAD].include?(env["REQUEST_METHOD"])
19
+ headers = {
20
+ "content-type" => "text/javascript; charset=utf-8",
21
+ "content-length" => Assets::BODY.bytesize.to_s,
22
+ "cache-control" => "public, max-age=31536000, immutable",
23
+ "x-content-type-options" => "nosniff",
24
+ "cross-origin-resource-policy" => "same-origin"
25
+ }
26
+ [200, headers, env["REQUEST_METHOD"] == "HEAD" ? [] : [Assets::BODY]]
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,18 @@
1
+ require_relative "assets"
2
+
3
+ module TuixRails
4
+ module Helper
5
+ def tuix_assets(nonce: content_security_policy_nonce)
6
+ path = "#{request.script_name}#{Assets::PATH}"
7
+ content_tag(:script, "", src: path, defer: true, integrity: Assets::INTEGRITY,
8
+ crossorigin: "anonymous", nonce: nonce, data: { turbo_track: "reload" })
9
+ end
10
+
11
+ def tuix_widget(user:, nonce: content_security_policy_nonce)
12
+ payload = TuixRails.payload(user, nonce: nonce)
13
+ # Action View escapes JSON as attribute text, including quotes and HTML.
14
+ content_tag(:div, "", id: "tuix-rails", hidden: true,
15
+ data: { tuix_rails: payload && JSON.generate(payload), turbo_temporary: true })
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,13 @@
1
+ require_relative "helper"
2
+
3
+ module TuixRails
4
+ class Railtie < Rails::Railtie
5
+ initializer "tuix_rails.assets" do |app|
6
+ app.middleware.use TuixRails::Middleware
7
+ end
8
+
9
+ initializer "tuix_rails.helpers" do
10
+ ActiveSupport.on_load(:action_view) { include TuixRails::Helper }
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,3 @@
1
+ module TuixRails
2
+ VERSION = "0.1.0"
3
+ end
data/lib/tuix_rails.rb ADDED
@@ -0,0 +1,81 @@
1
+ require "json"
2
+ require "openssl"
3
+ require "uri"
4
+ require_relative "tuix_rails/version"
5
+
6
+ module TuixRails
7
+ class Configuration
8
+ attr_accessor :project, :secret, :api, :environment, :release
9
+
10
+ def initialize
11
+ @project = ENV["TUIX_PROJECT_KEY"]
12
+ @secret = ENV["TUIX_SECRET_KEY"]
13
+ @api = ENV.fetch("TUIX_API_URL", "https://tuix.app")
14
+ @environment = ENV.fetch("TUIX_ENVIRONMENT", "uat")
15
+ end
16
+ end
17
+
18
+ def self.configuration
19
+ @configuration ||= Configuration.new
20
+ end
21
+
22
+ def self.configure
23
+ yield configuration
24
+ end
25
+
26
+ def self.validate_text!(value, field, limit)
27
+ unless value.is_a?(String) && value.valid_encoding? && value.bytesize.between?(1, limit) && !value.strip.empty?
28
+ raise ArgumentError, "invalid Tuix #{field}"
29
+ end
30
+ value
31
+ end
32
+
33
+ # Only pass trusted, authenticated host identities, with verified emails.
34
+ def self.identity(user, secret: configuration.secret)
35
+ return nil if user.nil?
36
+ raise ArgumentError, "Tuix user must be a hash with :id and :name" unless user.is_a?(Hash)
37
+ id = user[:id].is_a?(Integer) ? user[:id].to_s : user[:id]
38
+ validate_text!(id, :id, 200)
39
+ validate_text!(user[:name], :name, 200)
40
+ unless secret.is_a?(String) && secret.bytesize >= 32
41
+ raise ArgumentError, "Tuix project secret must be at least 32 bytes"
42
+ end
43
+ email = user[:email]
44
+ unless email.nil?
45
+ validate_text!(email, :email, 254)
46
+ email = email.strip.downcase
47
+ raise ArgumentError, "invalid Tuix email" unless email.match?(/\A[^\s@]+@[^\s@]+\.[^\s@]+\z/)
48
+ end
49
+ message = email ? "#{id}\n#{email}" : id
50
+ result = { id: id, name: user[:name], hash: OpenSSL::HMAC.hexdigest("SHA256", secret, message) }
51
+ result[:email] = email if email
52
+ result
53
+ end
54
+
55
+ def self.api_origin(value)
56
+ validate_text!(value, :api, 2048)
57
+ url = URI.parse(value)
58
+ unless url.is_a?(URI::HTTP) && url.host && !url.host.empty? && !url.userinfo && !url.query && !url.fragment &&
59
+ ["", "/"].include?(url.path) && url.port.between?(1, 65_535) &&
60
+ (url.scheme == "https" || %w[localhost 127.0.0.1].include?(url.host))
61
+ raise ArgumentError, "Tuix API must be an HTTPS origin (HTTP is allowed on localhost)"
62
+ end
63
+ value.delete_suffix("/")
64
+ rescue URI::InvalidURIError
65
+ raise ArgumentError, "invalid Tuix API origin"
66
+ end
67
+
68
+ def self.payload(user, nonce: nil)
69
+ config = configuration
70
+ return nil if user.nil? || [config.project, config.secret, config.api].any? { |value| value.nil? || value == "" }
71
+ validate_text!(config.project, :project, 128)
72
+ unless config.environment.is_a?(String) && config.environment.match?(/\A[a-z][a-z0-9_-]{0,39}\z/)
73
+ raise ArgumentError, "invalid Tuix environment"
74
+ end
75
+ options = { project: config.project, api: api_origin(config.api), environment: config.environment, nonce: nonce }
76
+ options[:release] = validate_text!(config.release, :release, 200) if config.release
77
+ { options: options, user: identity(user), delivery: "hosted" }
78
+ end
79
+ end
80
+
81
+ require_relative "tuix_rails/railtie" if defined?(Rails::Railtie)
metadata ADDED
@@ -0,0 +1,93 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: tuix_rails
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Tuix
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: railties
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: '7.2'
19
+ - - "<"
20
+ - !ruby/object:Gem::Version
21
+ version: '9'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ requirements:
26
+ - - ">="
27
+ - !ruby/object:Gem::Version
28
+ version: '7.2'
29
+ - - "<"
30
+ - !ruby/object:Gem::Version
31
+ version: '9'
32
+ - !ruby/object:Gem::Dependency
33
+ name: actionview
34
+ requirement: !ruby/object:Gem::Requirement
35
+ requirements:
36
+ - - ">="
37
+ - !ruby/object:Gem::Version
38
+ version: '7.2'
39
+ - - "<"
40
+ - !ruby/object:Gem::Version
41
+ version: '9'
42
+ type: :runtime
43
+ prerelease: false
44
+ version_requirements: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - ">="
47
+ - !ruby/object:Gem::Version
48
+ version: '7.2'
49
+ - - "<"
50
+ - !ruby/object:Gem::Version
51
+ version: '9'
52
+ description: Server-signed Tuix identities, hosted widget delivery and Turbo lifecycle
53
+ integration for Rails applications.
54
+ executables: []
55
+ extensions: []
56
+ extra_rdoc_files: []
57
+ files:
58
+ - LICENSE
59
+ - README.md
60
+ - assets/bootstrap.js
61
+ - lib/generators/tuix/install_generator.rb
62
+ - lib/tuix_rails.rb
63
+ - lib/tuix_rails/assets.rb
64
+ - lib/tuix_rails/helper.rb
65
+ - lib/tuix_rails/railtie.rb
66
+ - lib/tuix_rails/version.rb
67
+ homepage: https://tuix.app
68
+ licenses:
69
+ - MIT
70
+ metadata:
71
+ homepage_uri: https://tuix.app
72
+ source_code_uri: https://github.com/tuix-app/tuix/tree/main/packages/rails
73
+ documentation_uri: https://github.com/tuix-app/tuix/tree/main/packages/rails#readme
74
+ bug_tracker_uri: https://github.com/tuix-app/tuix/issues
75
+ rubygems_mfa_required: 'true'
76
+ rdoc_options: []
77
+ require_paths:
78
+ - lib
79
+ required_ruby_version: !ruby/object:Gem::Requirement
80
+ requirements:
81
+ - - ">="
82
+ - !ruby/object:Gem::Version
83
+ version: '3.2'
84
+ required_rubygems_version: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - ">="
87
+ - !ruby/object:Gem::Version
88
+ version: '0'
89
+ requirements: []
90
+ rubygems_version: 3.6.9
91
+ specification_version: 4
92
+ summary: Signed Tuix feedback widget integration for Rails
93
+ test_files: []