livechat 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.
Files changed (59) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +11 -0
  3. data/MIT-LICENSE +20 -0
  4. data/README.md +160 -0
  5. data/Rakefile +11 -0
  6. data/app/controllers/livechat/application_controller.rb +76 -0
  7. data/app/controllers/livechat/conversations_controller.rb +57 -0
  8. data/app/controllers/livechat/messages_controller.rb +21 -0
  9. data/app/controllers/livechat/visitor_controller.rb +108 -0
  10. data/app/controllers/livechat/widgets_controller.rb +36 -0
  11. data/app/helpers/livechat/widget_helper.rb +37 -0
  12. data/app/mailers/livechat/mailer.rb +51 -0
  13. data/app/models/livechat/application_record.rb +7 -0
  14. data/app/models/livechat/conversation.rb +108 -0
  15. data/app/models/livechat/message.rb +53 -0
  16. data/app/views/layouts/livechat/application.html.erb +97 -0
  17. data/app/views/livechat/conversations/index.html.erb +62 -0
  18. data/app/views/livechat/conversations/show.html.erb +73 -0
  19. data/app/views/livechat/mailer/new_agent_reply.text.erb +7 -0
  20. data/app/views/livechat/mailer/new_visitor_message.text.erb +7 -0
  21. data/config/locales/livechat.ar.yml +44 -0
  22. data/config/locales/livechat.bg.yml +44 -0
  23. data/config/locales/livechat.bn.yml +44 -0
  24. data/config/locales/livechat.de.yml +44 -0
  25. data/config/locales/livechat.el.yml +44 -0
  26. data/config/locales/livechat.en.yml +44 -0
  27. data/config/locales/livechat.es.yml +44 -0
  28. data/config/locales/livechat.fr.yml +44 -0
  29. data/config/locales/livechat.hi.yml +44 -0
  30. data/config/locales/livechat.hr.yml +44 -0
  31. data/config/locales/livechat.id.yml +44 -0
  32. data/config/locales/livechat.it.yml +44 -0
  33. data/config/locales/livechat.ja.yml +44 -0
  34. data/config/locales/livechat.ko.yml +44 -0
  35. data/config/locales/livechat.lb.yml +44 -0
  36. data/config/locales/livechat.nl.yml +44 -0
  37. data/config/locales/livechat.pl.yml +44 -0
  38. data/config/locales/livechat.pt.yml +44 -0
  39. data/config/locales/livechat.ro.yml +44 -0
  40. data/config/locales/livechat.ru.yml +44 -0
  41. data/config/locales/livechat.th.yml +44 -0
  42. data/config/locales/livechat.tr.yml +44 -0
  43. data/config/locales/livechat.uk.yml +44 -0
  44. data/config/locales/livechat.ur.yml +44 -0
  45. data/config/locales/livechat.vi.yml +44 -0
  46. data/config/locales/livechat.zh-CN.yml +44 -0
  47. data/config/routes.rb +27 -0
  48. data/lib/generators/livechat/install/install_generator.rb +42 -0
  49. data/lib/generators/livechat/install/templates/create_livechat_tables.rb.tt +36 -0
  50. data/lib/generators/livechat/install/templates/initializer.rb +59 -0
  51. data/lib/livechat/configuration.rb +103 -0
  52. data/lib/livechat/dashboard.js +58 -0
  53. data/lib/livechat/engine.rb +13 -0
  54. data/lib/livechat/notifications.rb +41 -0
  55. data/lib/livechat/version.rb +5 -0
  56. data/lib/livechat/widget.js +563 -0
  57. data/lib/livechat/widget.rb +115 -0
  58. data/lib/livechat.rb +56 -0
  59. metadata +125 -0
@@ -0,0 +1,115 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+ require 'digest'
5
+
6
+ module Livechat
7
+ # Serves the self-contained browser widget. The JavaScript is plain ES (no
8
+ # framework, no build step) and styles itself inline, so it drops into any
9
+ # Rails app regardless of its CSS or JS setup. It lives under lib/ (not
10
+ # app/assets/) so a host that runs an asset pipeline never ingests it.
11
+ module Widget
12
+ SOURCE = File.expand_path('widget.js', __dir__)
13
+ DASHBOARD_SOURCE = File.expand_path('dashboard.js', __dir__)
14
+
15
+ # Right-to-left scripts, so the chat renders mirrored for those locales.
16
+ # Matched on the language subtag, so region variants ("ar-EG") count too.
17
+ RTL_LANGUAGES = %w[ar arc ckb dv fa ha he ks ku ps sd ug ur yi].freeze
18
+
19
+ class << self
20
+ def javascript
21
+ @javascript ||= File.read(SOURCE)
22
+ end
23
+
24
+ def dashboard_javascript
25
+ @dashboard_javascript ||= File.read(DASHBOARD_SOURCE)
26
+ end
27
+
28
+ # Content fingerprints for cache-busting script URLs: a changed file is
29
+ # a changed URL, so no browser can ever run stale widget code — Safari
30
+ # has been caught ignoring must-revalidate on same-URL scripts.
31
+ def fingerprint
32
+ @fingerprint ||= Digest::MD5.hexdigest(javascript)
33
+ end
34
+
35
+ def dashboard_fingerprint
36
+ @dashboard_fingerprint ||= Digest::MD5.hexdigest(dashboard_javascript)
37
+ end
38
+
39
+ # The two <script> tags the helper renders.
40
+ #
41
+ # The config rides in a `type="application/json"` block: it is *data*,
42
+ # not code, so the browser never executes it and Turbo never tries to
43
+ # re-run it on a soft visit — which means it needs no CSP nonce and the
44
+ # widget can re-read the *current* page's config on every `turbo:load`.
45
+ #
46
+ # The code is a same-origin `src` script served by the engine — NOT
47
+ # inlined. Under a nonce-based CSP, Turbo Drive body swaps re-run body
48
+ # scripts against the *original* page's CSP header, so a fresh inline
49
+ # nonce gets refused; a same-origin src is covered by `'self'` on every
50
+ # visit. `nonce:` is still stamped for hosts whose script-src has no
51
+ # 'self'; pass nil when the app has no nonce.
52
+ def snippet(locale:, authenticated:, nonce: nil)
53
+ json = config_json(locale:, authenticated:)
54
+ nonce_attr = nonce ? %( nonce="#{nonce}") : ''
55
+ src = "#{Livechat.config.mount_path.chomp('/')}/widget.js?v=#{fingerprint}"
56
+
57
+ %(<script type="application/json" data-livechat-config>#{json}</script>) +
58
+ %(<script src="#{src}" defer#{nonce_attr} data-livechat-widget></script>)
59
+ end
60
+
61
+ def config_json(locale:, authenticated:)
62
+ config = Livechat.config
63
+ payload = {
64
+ endpoint: config.widget_endpoint,
65
+ locale: locale.to_s,
66
+ rtl: rtl?(locale),
67
+ authenticated: authenticated ? true : false,
68
+ launcher: config.show_launcher ? true : false,
69
+ appName: Livechat.app_name,
70
+ accentColor: config.accent_color,
71
+ labels: labels
72
+ }
73
+ # Escape "</" so a value can't close the <script> block early.
74
+ payload.to_json.gsub('</', '<\/')
75
+ end
76
+
77
+ private
78
+
79
+ # Every user-facing string in the widget, resolved through Rails I18n so
80
+ # the chat follows the app's current locale. Each lookup carries an
81
+ # English default, so the widget stays fully worded even when a key is
82
+ # missing for the active locale.
83
+ def labels
84
+ config = Livechat.config
85
+ {
86
+ launcher: config.launcher_label.presence || t(:launcher, 'Chat with us'),
87
+ greeting: config.greeting.presence || t(:greeting, 'Hi! How can we help?'),
88
+ replyTime: config.reply_time_text.presence ||
89
+ t(:reply_time, 'We usually reply within a few hours.'),
90
+ placeholder: t(:placeholder, 'Write a message…'),
91
+ send: t(:send, 'Send'),
92
+ close: t(:close, 'Close'),
93
+ you: t(:you, 'You'),
94
+ team: t(:team, 'Support'),
95
+ emailPrompt: t(:email_prompt, 'Get a copy of our reply by email:'),
96
+ emailPlaceholder: t(:email_placeholder, 'you@example.com'),
97
+ emailSave: t(:email_save, 'Save'),
98
+ emailSaved: t(:email_saved, 'We will also reply by email.'),
99
+ eventResolved: t(:event_resolved, 'Conversation resolved'),
100
+ eventReopened: t(:event_reopened, 'Conversation reopened'),
101
+ errorSend: t(:error_send, 'Could not send. Please try again.'),
102
+ unreadAria: t(:unread_aria, 'unread messages')
103
+ }
104
+ end
105
+
106
+ def t(key, default, **args)
107
+ I18n.t(key, scope: :livechat, default: default, **args)
108
+ end
109
+
110
+ def rtl?(locale)
111
+ RTL_LANGUAGES.include?(locale.to_s.downcase.split(/[-_]/).first)
112
+ end
113
+ end
114
+ end
115
+ end
data/lib/livechat.rb ADDED
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'livechat/version'
4
+ require 'livechat/configuration'
5
+ require 'livechat/widget'
6
+ require 'livechat/notifications'
7
+ require 'livechat/engine'
8
+
9
+ # Live chat for Rails. A floating widget lets visitors — signed-in or
10
+ # anonymous — message your team; your team answers from a built-in inbox at
11
+ # the mount path, every reply signed with its author. Conversations live in
12
+ # your own database. When nobody is around, visitors leave an email and the
13
+ # thread continues there. No third-party script, no separate service.
14
+ module Livechat
15
+ class << self
16
+ def config
17
+ @config ||= Configuration.new
18
+ end
19
+
20
+ def configure
21
+ yield config
22
+ end
23
+
24
+ # Can this request see the widget and write messages? Checked on the
25
+ # server for every widget endpoint and by the helper before rendering.
26
+ def enabled?(request)
27
+ !!config.enabled.call(request)
28
+ end
29
+
30
+ # Can this request work the inbox? Checked by every inbox action.
31
+ def agent?(request)
32
+ !!config.authorize_agent.call(request)
33
+ end
34
+
35
+ def app_name
36
+ config.app_name.presence || rails_app_name
37
+ end
38
+
39
+ # The public face of an agent message. Blank display names fall back to
40
+ # a localized "Support", so visitors always see a sender.
41
+ def display_name_for(agent_label)
42
+ name = config.agent_display_name.call(agent_label).presence
43
+ name || I18n.t(:team, scope: :livechat, default: 'Support')
44
+ end
45
+
46
+ private
47
+
48
+ # The application's module name, verbatim ("EthicsPortal", "SupeRails") —
49
+ # no inflection games. Set config.app_name for anything fancier.
50
+ def rails_app_name
51
+ Rails.application.class.module_parent_name
52
+ rescue StandardError
53
+ 'this app'
54
+ end
55
+ end
56
+ end
metadata ADDED
@@ -0,0 +1,125 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: livechat
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Yaroslav Shmarov
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.1'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - ">="
24
+ - !ruby/object:Gem::Version
25
+ version: '7.1'
26
+ description: |
27
+ A mountable Rails engine that adds support chat to your app — the thing
28
+ you'd otherwise pay Crisp or Intercom for, or deploy Chatwoot to get. A
29
+ floating widget lets visitors (signed-in or anonymous) message your team;
30
+ your team answers from a built-in inbox where several teammates can work
31
+ the same thread, every reply signed with its author. When nobody is
32
+ around, visitors leave an email and the conversation continues there.
33
+ Conversations never leave your database. Framework-agnostic: no CSS or JS
34
+ framework required, works with Turbo Drive and strict CSPs.
35
+ email:
36
+ - yaroslav.shmarov@clickfunnels.com
37
+ executables: []
38
+ extensions: []
39
+ extra_rdoc_files: []
40
+ files:
41
+ - CHANGELOG.md
42
+ - MIT-LICENSE
43
+ - README.md
44
+ - Rakefile
45
+ - app/controllers/livechat/application_controller.rb
46
+ - app/controllers/livechat/conversations_controller.rb
47
+ - app/controllers/livechat/messages_controller.rb
48
+ - app/controllers/livechat/visitor_controller.rb
49
+ - app/controllers/livechat/widgets_controller.rb
50
+ - app/helpers/livechat/widget_helper.rb
51
+ - app/mailers/livechat/mailer.rb
52
+ - app/models/livechat/application_record.rb
53
+ - app/models/livechat/conversation.rb
54
+ - app/models/livechat/message.rb
55
+ - app/views/layouts/livechat/application.html.erb
56
+ - app/views/livechat/conversations/index.html.erb
57
+ - app/views/livechat/conversations/show.html.erb
58
+ - app/views/livechat/mailer/new_agent_reply.text.erb
59
+ - app/views/livechat/mailer/new_visitor_message.text.erb
60
+ - config/locales/livechat.ar.yml
61
+ - config/locales/livechat.bg.yml
62
+ - config/locales/livechat.bn.yml
63
+ - config/locales/livechat.de.yml
64
+ - config/locales/livechat.el.yml
65
+ - config/locales/livechat.en.yml
66
+ - config/locales/livechat.es.yml
67
+ - config/locales/livechat.fr.yml
68
+ - config/locales/livechat.hi.yml
69
+ - config/locales/livechat.hr.yml
70
+ - config/locales/livechat.id.yml
71
+ - config/locales/livechat.it.yml
72
+ - config/locales/livechat.ja.yml
73
+ - config/locales/livechat.ko.yml
74
+ - config/locales/livechat.lb.yml
75
+ - config/locales/livechat.nl.yml
76
+ - config/locales/livechat.pl.yml
77
+ - config/locales/livechat.pt.yml
78
+ - config/locales/livechat.ro.yml
79
+ - config/locales/livechat.ru.yml
80
+ - config/locales/livechat.th.yml
81
+ - config/locales/livechat.tr.yml
82
+ - config/locales/livechat.uk.yml
83
+ - config/locales/livechat.ur.yml
84
+ - config/locales/livechat.vi.yml
85
+ - config/locales/livechat.zh-CN.yml
86
+ - config/routes.rb
87
+ - lib/generators/livechat/install/install_generator.rb
88
+ - lib/generators/livechat/install/templates/create_livechat_tables.rb.tt
89
+ - lib/generators/livechat/install/templates/initializer.rb
90
+ - lib/livechat.rb
91
+ - lib/livechat/configuration.rb
92
+ - lib/livechat/dashboard.js
93
+ - lib/livechat/engine.rb
94
+ - lib/livechat/notifications.rb
95
+ - lib/livechat/version.rb
96
+ - lib/livechat/widget.js
97
+ - lib/livechat/widget.rb
98
+ homepage: https://github.com/yshmarov/livechat
99
+ licenses:
100
+ - MIT
101
+ metadata:
102
+ homepage_uri: https://github.com/yshmarov/livechat
103
+ source_code_uri: https://github.com/yshmarov/livechat
104
+ changelog_uri: https://github.com/yshmarov/livechat/blob/main/CHANGELOG.md
105
+ bug_tracker_uri: https://github.com/yshmarov/livechat/issues
106
+ rubygems_mfa_required: 'true'
107
+ rdoc_options: []
108
+ require_paths:
109
+ - lib
110
+ required_ruby_version: !ruby/object:Gem::Requirement
111
+ requirements:
112
+ - - ">="
113
+ - !ruby/object:Gem::Version
114
+ version: '3.2'
115
+ required_rubygems_version: !ruby/object:Gem::Requirement
116
+ requirements:
117
+ - - ">="
118
+ - !ruby/object:Gem::Version
119
+ version: '0'
120
+ requirements: []
121
+ rubygems_version: 4.0.6
122
+ specification_version: 4
123
+ summary: 'Open-source live chat for Rails: a drop-in support widget plus a team inbox,
124
+ in your own database.'
125
+ test_files: []