deskcrew-rails 1.0.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: 948e98697c70fa23d2d3867beac3ef8b9b818ff6945f613529201d9493e68605
4
+ data.tar.gz: 8598b9154df830408ff02afdaeac34993aa7cee6433261620efae04245a2a376
5
+ SHA512:
6
+ metadata.gz: b7c3a60b6d1a2be32b59bde4f114904805fc8bde9cff2492d84f50409cff7b7b9ac995fa929a487e1044e9acc604e0e9bb6ae96ee1ade61233151b6a481bfcee
7
+ data.tar.gz: 41b3b24b5bc2166e8375c9c51dab28037cac571b1049fe6cb1105c0eb4c1379deff7c3210c3bf4ee8de9b351796252d8856a452c6dc9e4cfc863b9b20713c518
data/CHANGELOG.md ADDED
@@ -0,0 +1,5 @@
1
+ # Changelog
2
+
3
+ ## 1.0.0
4
+
5
+ - First release: `deskcrew_widget_tag` view helper, opt-in `Deskcrew::Middleware` that injects the widget before `</body>`, `Deskcrew::Client` for `register_origin` and `create_ticket`, `rails g deskcrew:install`, and the `deskcrew:register_origin` rake task.
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 DeskCrew
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,82 @@
1
+ # deskcrew-rails
2
+
3
+ ![DeskCrew widget on every Rails page](https://deskcrew.io/packages/deskcrew-rails.gif)
4
+
5
+ Add the [DeskCrew](https://deskcrew.io) AI support widget to a Ruby on Rails app, and create support tickets from your own code. DeskCrew is an AI-powered helpdesk: the widget answers visitors from your knowledge base, anything it cannot answer becomes a ticket, and you approve every reply before it sends.
6
+
7
+ You need a free DeskCrew account to get a widget key: https://deskcrew.io/signup
8
+
9
+ ## Install
10
+
11
+ ```ruby
12
+ # Gemfile
13
+ gem "deskcrew-rails"
14
+ ```
15
+
16
+ ```bash
17
+ bundle install
18
+ bin/rails generate deskcrew:install
19
+ ```
20
+
21
+ The generator writes `config/initializers/deskcrew.rb`:
22
+
23
+ ```ruby
24
+ Deskcrew.configure do |config|
25
+ config.widget_key = ENV.fetch("DESKCREW_WIDGET_KEY", nil) # "pub_..." from your dashboard's Install page
26
+ config.board = ENV.fetch("DESKCREW_BOARD", nil) # your board slug
27
+ config.site_url = "https://www.example.com" # this site's public origin
28
+ # config.color = "#4f46e5"
29
+ # config.position = "right" # or "left"
30
+ # config.greeting = "Hi! How can we help?"
31
+ # config.auto_inject = true # see below
32
+ # config.enabled = !Rails.env.test?
33
+ end
34
+ ```
35
+
36
+ ## Show the widget
37
+
38
+ Either add the helper to your layout, just before `</body>`:
39
+
40
+ ```erb
41
+ <%= deskcrew_widget_tag %>
42
+ ```
43
+
44
+ or set `config.auto_inject = true` and a middleware adds the same tag to every successful HTML response. JSON, redirects, streams and error pages are left alone, and a page that already carries the tag is never given a second one.
45
+
46
+ ## Allow your domain
47
+
48
+ The widget only runs on origins you have registered. Once per environment:
49
+
50
+ ```bash
51
+ bin/rails deskcrew:register_origin
52
+ ```
53
+
54
+ This sends your `site_url` to DeskCrew together with your public widget key. The same thing happens when you paste the key on the dashboard's Install page, so run it only if you configured the key here first.
55
+
56
+ ## Create tickets from your code
57
+
58
+ ```ruby
59
+ Deskcrew::Client.create_ticket(
60
+ name: current_user.name,
61
+ email: current_user.email,
62
+ message: params[:message]
63
+ )
64
+ ```
65
+
66
+ Returns `true` when DeskCrew accepted the ticket, `false` otherwise. It never raises into your request cycle, and the call takes at most ten seconds, so wrap it in a background job if you create tickets in bulk.
67
+
68
+ ## What the gem sends to DeskCrew
69
+
70
+ - The widget script tag points at `https://deskcrew.io/desk.js` with your public widget key and the optional board, colour, position and greeting. Every value is validated and HTML-escaped before it reaches the page.
71
+ - `deskcrew:register_origin` posts your `site_url` and widget key to `https://deskcrew.io/api/widget/register-origin`.
72
+ - `Deskcrew::Client.create_ticket` posts the name, email and message to `https://deskcrew.io/api/widget/submit` with your `site_url` as the Origin.
73
+
74
+ Nothing else leaves your app. Terms: https://deskcrew.io/terms. Privacy: https://deskcrew.io/privacy.
75
+
76
+ ## Requirements
77
+
78
+ Ruby 3.0 or newer, Rails 6.1 or newer.
79
+
80
+ ## License
81
+
82
+ MIT. See LICENSE.txt.
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+ require "net/http"
5
+ require "uri"
6
+
7
+ module Deskcrew
8
+ # Two calls, both plain HTTPS to DeskCrew:
9
+ # Deskcrew::Client.register_origin -> lets the widget run on config.site_url
10
+ # Deskcrew::Client.create_ticket(...) -> a support ticket from your own code
11
+ # Both are best-effort: a network failure returns false, never raises into your app.
12
+ class Client
13
+ TIMEOUT = 10
14
+
15
+ class << self
16
+ def register_origin(config = Deskcrew.config, http: nil)
17
+ return false unless config.widget_ready? && config.site_url.to_s.start_with?("https://")
18
+
19
+ post(config, "/api/widget/register-origin", { key: config.widget_key, origin: config.site_url }, http: http)
20
+ end
21
+
22
+ def create_ticket(name: nil, email:, message:, config: Deskcrew.config, http: nil)
23
+ return false unless config.widget_ready?
24
+ return false if email.to_s.strip.empty? || message.to_s.strip.empty?
25
+
26
+ post(
27
+ config,
28
+ "/api/widget/submit",
29
+ { key: config.widget_key, name: name.to_s, email: email.to_s, message: message.to_s },
30
+ origin: config.site_url,
31
+ http: http
32
+ )
33
+ end
34
+
35
+ private
36
+
37
+ def post(config, path, payload, origin: nil, http: nil)
38
+ uri = URI.join(config.app_url, path)
39
+ request = Net::HTTP::Post.new(uri)
40
+ request["Content-Type"] = "application/json"
41
+ request["Origin"] = origin if origin
42
+ request.body = JSON.generate(payload)
43
+
44
+ client = http || Net::HTTP.new(uri.host, uri.port).tap do |c|
45
+ c.use_ssl = uri.scheme == "https"
46
+ c.open_timeout = TIMEOUT
47
+ c.read_timeout = TIMEOUT
48
+ end
49
+ response = client.request(request)
50
+ response.code.to_i.between?(200, 299)
51
+ rescue StandardError
52
+ false
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Deskcrew
4
+ # Everything the widget and the client need. Set it once in
5
+ # config/initializers/deskcrew.rb (rails g deskcrew:install writes one).
6
+ class Configuration
7
+ # Public widget key from your DeskCrew dashboard (Install page). Starts with "pub_".
8
+ attr_accessor :widget_key
9
+ # Your board slug, e.g. "acme" for deskcrew.io/board/acme.
10
+ attr_accessor :board
11
+ # Widget accent colour as a hex string, e.g. "#4f46e5".
12
+ attr_accessor :color
13
+ # "right" (default) or "left".
14
+ attr_accessor :position
15
+ # First line the widget shows, up to 120 characters.
16
+ attr_accessor :greeting
17
+ # Your site's public origin, e.g. "https://www.example.com". Used to register the
18
+ # widget for your domain and as the Origin of tickets created by the client.
19
+ attr_accessor :site_url
20
+ # Set false to keep the widget off without removing the gem.
21
+ attr_accessor :enabled
22
+ # When true, a middleware injects the widget before </body> on HTML responses.
23
+ # Leave false and call deskcrew_widget_tag in your layout instead.
24
+ attr_accessor :auto_inject
25
+ # Where DeskCrew lives. Only change this if DeskCrew tells you to.
26
+ attr_accessor :app_url
27
+
28
+ KEY_FORMAT = /\Apub_[A-Za-z0-9]+\z/
29
+ BOARD_FORMAT = /\A[a-z0-9-]+\z/
30
+ COLOR_FORMAT = /\A#[0-9a-fA-F]{6}\z/
31
+ GREETING_MAX = 120
32
+
33
+ def initialize
34
+ @widget_key = ENV.fetch("DESKCREW_WIDGET_KEY", nil)
35
+ @board = ENV.fetch("DESKCREW_BOARD", nil)
36
+ @color = nil
37
+ @position = "right"
38
+ @greeting = nil
39
+ @site_url = nil
40
+ @enabled = true
41
+ @auto_inject = false
42
+ @app_url = "https://deskcrew.io"
43
+ end
44
+
45
+ # True when there is a usable key and the widget is switched on.
46
+ def widget_ready?
47
+ enabled && KEY_FORMAT.match?(widget_key.to_s)
48
+ end
49
+
50
+ # Values the way the widget accepts them; bad input is dropped, never passed through.
51
+ def sanitized
52
+ {
53
+ key: (KEY_FORMAT.match?(widget_key.to_s) ? widget_key : nil),
54
+ board: (BOARD_FORMAT.match?(board.to_s) ? board : nil),
55
+ color: (COLOR_FORMAT.match?(color.to_s) ? color : nil),
56
+ position: (%w[left right].include?(position.to_s) ? position.to_s : nil),
57
+ greeting: (greeting.to_s.empty? ? nil : greeting.to_s[0, GREETING_MAX])
58
+ }
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Deskcrew
4
+ # View helper. In your layout, just before </body>:
5
+ # <%= deskcrew_widget_tag %>
6
+ module Helper
7
+ def deskcrew_widget_tag
8
+ html = Deskcrew::Widget.tag
9
+ html.respond_to?(:html_safe) ? html.html_safe : html # rubocop:disable Rails/OutputSafety
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Deskcrew
4
+ # Opt-in (config.auto_inject = true): adds the widget tag before </body> on successful
5
+ # HTML responses. Skips responses that already carry the tag, non-HTML bodies, and
6
+ # anything that is not a 200, so JSON, redirects, streams and errors are untouched.
7
+ class Middleware
8
+ MARKER = "desk.js"
9
+
10
+ def initialize(app)
11
+ @app = app
12
+ end
13
+
14
+ def call(env)
15
+ status, headers, body = @app.call(env)
16
+ return [status, headers, body] unless inject?(status, headers)
17
+
18
+ tag = Deskcrew::Widget.tag
19
+ return [status, headers, body] if tag.empty?
20
+
21
+ html = +""
22
+ body.each { |part| html << part.to_s }
23
+ body.close if body.respond_to?(:close)
24
+ return [status, headers, [html]] if html.include?(MARKER)
25
+
26
+ injected = html.sub(%r{</body>}i) { "#{tag}\n</body>" }
27
+ set_length(headers, injected)
28
+ [status, headers, [injected]]
29
+ end
30
+
31
+ private
32
+
33
+ def inject?(status, headers)
34
+ return false unless status.to_i == 200
35
+ return false unless Deskcrew.config.widget_ready?
36
+
37
+ type = header(headers, "Content-Type").to_s
38
+ type.include?("text/html")
39
+ end
40
+
41
+ def header(headers, name)
42
+ headers[name] || headers[name.downcase]
43
+ end
44
+
45
+ def set_length(headers, html)
46
+ key = headers.key?("content-length") ? "content-length" : "Content-Length"
47
+ headers[key] = html.bytesize.to_s if headers.key?("Content-Length") || headers.key?("content-length")
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails/railtie"
4
+
5
+ module Deskcrew
6
+ class Railtie < Rails::Railtie
7
+ initializer "deskcrew.helper" do
8
+ ActiveSupport.on_load(:action_view) { include Deskcrew::Helper }
9
+ end
10
+
11
+ initializer "deskcrew.middleware" do |app|
12
+ app.middleware.use Deskcrew::Middleware if Deskcrew.config.auto_inject
13
+ end
14
+
15
+ rake_tasks do
16
+ load File.expand_path("../tasks/deskcrew.rake", __dir__)
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Deskcrew
4
+ VERSION = "1.0.0"
5
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "erb"
4
+
5
+ module Deskcrew
6
+ # Builds the one script tag the widget needs. Every attribute value is HTML-escaped
7
+ # and only known-good values make it into the tag (see Configuration#sanitized).
8
+ module Widget
9
+ module_function
10
+
11
+ def tag(config = Deskcrew.config)
12
+ return "" unless config.widget_ready?
13
+
14
+ values = config.sanitized
15
+ attrs = { "src" => "#{config.app_url}/desk.js", "data-key" => values[:key] }
16
+ attrs["data-board"] = values[:board] if values[:board]
17
+ attrs["data-color"] = values[:color] if values[:color]
18
+ attrs["data-position"] = values[:position] if values[:position]
19
+ attrs["data-greeting"] = values[:greeting] if values[:greeting]
20
+
21
+ pairs = attrs.map { |k, v| %(#{k}="#{ERB::Util.html_escape(v)}") }
22
+ "<script #{pairs.join(' ')} defer></script>"
23
+ end
24
+ end
25
+ end
data/lib/deskcrew.rb ADDED
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "deskcrew/version"
4
+ require "deskcrew/configuration"
5
+ require "deskcrew/widget"
6
+ require "deskcrew/helper"
7
+ require "deskcrew/middleware"
8
+ require "deskcrew/client"
9
+ require "deskcrew/railtie" if defined?(Rails::Railtie)
10
+
11
+ module Deskcrew
12
+ class << self
13
+ def config
14
+ @config ||= Configuration.new
15
+ end
16
+
17
+ def configure
18
+ yield config
19
+ config
20
+ end
21
+
22
+ def reset!
23
+ @config = Configuration.new
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails/generators"
4
+
5
+ module Deskcrew
6
+ module Generators
7
+ class InstallGenerator < Rails::Generators::Base
8
+ source_root File.expand_path("templates", __dir__)
9
+ desc "Writes config/initializers/deskcrew.rb"
10
+
11
+ def create_initializer
12
+ template "deskcrew.rb.tt", "config/initializers/deskcrew.rb"
13
+ end
14
+
15
+ def show_next_steps
16
+ say ""
17
+ say "DeskCrew installed. Next:", :green
18
+ say " 1. Put your widget key in config/initializers/deskcrew.rb (or DESKCREW_WIDGET_KEY)."
19
+ say " 2. Add <%= deskcrew_widget_tag %> before </body> in your layout, or set auto_inject = true."
20
+ say " 3. Run bin/rails deskcrew:register_origin once per environment."
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ # DeskCrew support widget. Get the key from your DeskCrew dashboard, Install page.
4
+ Deskcrew.configure do |config|
5
+ config.widget_key = ENV.fetch("DESKCREW_WIDGET_KEY", nil) # "pub_..."
6
+ config.board = ENV.fetch("DESKCREW_BOARD", nil) # your board slug
7
+ config.site_url = "https://www.example.com" # this site's public origin
8
+ # config.color = "#4f46e5"
9
+ # config.position = "right"
10
+ # config.greeting = "Hi! How can we help?"
11
+ # config.auto_inject = true # or call deskcrew_widget_tag in your layout
12
+ # config.enabled = !Rails.env.test?
13
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ namespace :deskcrew do
4
+ desc "Register this site's origin with DeskCrew so the widget is allowed on your domain"
5
+ task register_origin: :environment do
6
+ config = Deskcrew.config
7
+ abort "DeskCrew: set config.widget_key and config.site_url (https://...) first" unless config.widget_ready? && config.site_url.to_s.start_with?("https://")
8
+ if Deskcrew::Client.register_origin
9
+ puts "DeskCrew: #{config.site_url} registered for widget key #{config.widget_key[0, 8]}…"
10
+ else
11
+ abort "DeskCrew: registration failed; check the key on your dashboard's Install page"
12
+ end
13
+ end
14
+ end
metadata ADDED
@@ -0,0 +1,91 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: deskcrew-rails
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - DeskCrew
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: '6.1'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - ">="
24
+ - !ruby/object:Gem::Version
25
+ version: '6.1'
26
+ - !ruby/object:Gem::Dependency
27
+ name: rack
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: '2.2'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: '2.2'
40
+ description: DeskCrew is an AI-powered helpdesk. This gem injects the DeskCrew support
41
+ widget into your Rails views (a helper or an opt-in middleware), registers your
42
+ site with DeskCrew so the widget works on your domain, and gives you a small client
43
+ to create support tickets from your own code.
44
+ email:
45
+ - support@deskcrew.io
46
+ executables: []
47
+ extensions: []
48
+ extra_rdoc_files: []
49
+ files:
50
+ - CHANGELOG.md
51
+ - LICENSE.txt
52
+ - README.md
53
+ - lib/deskcrew.rb
54
+ - lib/deskcrew/client.rb
55
+ - lib/deskcrew/configuration.rb
56
+ - lib/deskcrew/helper.rb
57
+ - lib/deskcrew/middleware.rb
58
+ - lib/deskcrew/railtie.rb
59
+ - lib/deskcrew/version.rb
60
+ - lib/deskcrew/widget.rb
61
+ - lib/generators/deskcrew/install/install_generator.rb
62
+ - lib/generators/deskcrew/install/templates/deskcrew.rb.tt
63
+ - lib/tasks/deskcrew.rake
64
+ homepage: https://deskcrew.io/integrations/rails
65
+ licenses:
66
+ - MIT
67
+ metadata:
68
+ homepage_uri: https://deskcrew.io/integrations/rails
69
+ documentation_uri: https://deskcrew.io/board/deskcrew/kb
70
+ source_code_uri: https://github.com/webmilmind1/support-tickets/tree/main/rails-gem
71
+ changelog_uri: https://github.com/webmilmind1/support-tickets/blob/main/rails-gem/CHANGELOG.md
72
+ rubygems_mfa_required: 'true'
73
+ rdoc_options: []
74
+ require_paths:
75
+ - lib
76
+ required_ruby_version: !ruby/object:Gem::Requirement
77
+ requirements:
78
+ - - ">="
79
+ - !ruby/object:Gem::Version
80
+ version: '3.0'
81
+ required_rubygems_version: !ruby/object:Gem::Requirement
82
+ requirements:
83
+ - - ">="
84
+ - !ruby/object:Gem::Version
85
+ version: '0'
86
+ requirements: []
87
+ rubygems_version: 3.6.9
88
+ specification_version: 4
89
+ summary: Add the DeskCrew AI support widget to a Rails app and turn form submissions
90
+ into tickets.
91
+ test_files: []