diff --git a/.env.example b/.env.example index 110bd4292..281cd64d1 100644 --- a/.env.example +++ b/.env.example @@ -4,6 +4,9 @@ SECRET_KEY_BASE=replace_with_lengthy_secure_hex # Replace with the URL you are planning to use for your app FRONTEND_URL=http://0.0.0.0:3000 +# If you plan to use CDN for your assets, set Asset CDN Host +ASSET_CDN_HOST= + # Force all access to the app over SSL, default is set to false FORCE_SSL=false @@ -103,7 +106,7 @@ IOS_APP_ID=6C953F3RX2.com.chatwoot.app # VAPID_PUBLIC_KEY= # VAPID_PRIVATE_KEY= # -# for mobile apps +# for mobile apps # FCM_SERVER_KEY= ## Bot Customizations diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index dd84ea782..66e142ad6 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -2,15 +2,16 @@ name: Bug report about: Create a report to help us improve title: '' -labels: '' +labels: 'Bug' assignees: '' --- - **Describe the bug** + A clear and concise description of what the bug is. **To Reproduce** + Steps to reproduce the behavior: 1. Go to '...' 2. Click on '....' @@ -18,11 +19,17 @@ Steps to reproduce the behavior: 4. See error **Expected behavior** + A clear and concise description of what you expected to happen. **Screenshots** + If applicable, add screenshots to help explain your problem. +**Environment** + +Describe whether you are using Chatwoot Cloud (app.chatwoot.com) or a self hosted installation of Chatwoot. If you are using a self hosted installation of Chatwoot describe the type of deployment (Docker/Linux VM installation/Heroku) + **Desktop (please complete the following information):** - OS: [e.g. iOS] - Browser [e.g. chrome, safari] @@ -35,4 +42,5 @@ If applicable, add screenshots to help explain your problem. - Version [e.g. 22] **Additional context** + Add any other context about the problem here. diff --git a/.rubocop.yml b/.rubocop.yml index ab5efd6c8..c27b9651b 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -4,20 +4,8 @@ require: - rubocop-rspec inherit_from: .rubocop_todo.yml -Lint/RaiseException: - Enabled: true -Lint/StructNewOverride: - Enabled: true -Lint/DeprecatedOpenSSLConstant: - Enabled: true -Lint/MixedRegexpCaptureTypes: - Enabled: true Layout/LineLength: Max: 150 -Layout/EmptyLinesAroundAttributeAccessor: - Enabled: true -Layout/SpaceAroundMethodCallOperator: - Enabled: true Metrics/ClassLength: Max: 125 @@ -33,25 +21,21 @@ Style/FrozenStringLiteralComment: Enabled: false Style/SymbolArray: Enabled: false -Style/HashEachMethods: - Enabled: true -Style/HashTransformKeys: - Enabled: true -Style/HashTransformValues: - Enabled: true -Style/RedundantFetchBlock: - Enabled: true -Style/RedundantRegexpCharacterClass: - Enabled: true -Style/RedundantRegexpEscape: - Enabled: true -Style/SlicingWithRange: - Enabled: true +Style/OptionalBooleanParameter: + Exclude: + - 'app/services/email_templates/db_resolver_service.rb' + - 'app/dispatchers/dispatcher.rb' Style/GlobalVars: Exclude: - 'config/initializers/redis.rb' - 'lib/redis/alfred.rb' - 'lib/global_config.rb' +Style/ClassVars: + Exclude: + - 'app/services/email_templates/db_resolver_service.rb' +Lint/MissingSuper: + Exclude: + - 'app/drops/base_drop.rb' Metrics/BlockLength: Exclude: - spec/**/* @@ -87,8 +71,11 @@ Style/GuardClause: - 'db/migrate/20190819005836_add_missing_indexes_on_taggings.acts_as_taggable_on_engine.rb' Metrics/AbcSize: Exclude: + - 'app/controllers/concerns/auth_helper.rb' - 'db/migrate/20190819005836_add_missing_indexes_on_taggings.acts_as_taggable_on_engine.rb' + - 'db/migrate/20161123131628_devise_token_auth_create_users.rb' Metrics/CyclomaticComplexity: + Max: 7 Exclude: - 'db/migrate/20190819005836_add_missing_indexes_on_taggings.acts_as_taggable_on_engine.rb' Rails/ReversibleMigration: @@ -115,9 +102,16 @@ Rails/UniqueValidationWithoutIndex: Exclude: - 'app/models/channel/twitter_profile.rb' - 'app/models/webhook.rb' +Rails/RenderInline: + Exclude: + - 'app/controllers/swagger_controller.rb' RSpec/NamedSubject: Enabled: false +# we should bring this down +RSpec/MultipleMemoizedHelpers: + Max: 12 AllCops: + NewCops: enable Exclude: - 'bin/**/*' - 'db/schema.rb' @@ -129,3 +123,4 @@ AllCops: - 'config/environments/**/*' - 'tmp/**/*' - 'storage/**/*' + - 'db/migrate/20200225162150_init_schema.rb' diff --git a/Gemfile b/Gemfile index 591630a82..32dab102c 100644 --- a/Gemfile +++ b/Gemfile @@ -48,6 +48,8 @@ gem 'dotenv-rails' gem 'foreman' gem 'puma' gem 'webpacker', '~> 5.x' +# metrics on heroku +gem 'barnes' ##--- gems for authentication & authorization ---## gem 'devise' @@ -63,7 +65,8 @@ gem 'administrate' gem 'wisper', '2.0.0' ##--- gems for channels ---## -gem 'facebook-messenger' +# TODO: bump up gem to 2.0 +gem 'facebook-messenger', '1.5.0' gem 'telegram-bot-ruby' gem 'twilio-ruby', '~> 5.32.0' # twitty will handle subscription of twitter account events @@ -95,6 +98,9 @@ group :development do # used in swagger build gem 'json_refs', git: 'https://github.com/tzmfreedom/json_refs', ref: 'e32deb0' + + # When we want to squash migrations + gem 'squasher' end group :test do diff --git a/Gemfile.lock b/Gemfile.lock index fc5c613bb..0c9cda2ec 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -18,56 +18,56 @@ GEM specs: action-cable-testing (0.6.1) actioncable (>= 5.0) - actioncable (6.0.3.2) - actionpack (= 6.0.3.2) + actioncable (6.0.3.3) + actionpack (= 6.0.3.3) nio4r (~> 2.0) websocket-driver (>= 0.6.1) - actionmailbox (6.0.3.2) - actionpack (= 6.0.3.2) - activejob (= 6.0.3.2) - activerecord (= 6.0.3.2) - activestorage (= 6.0.3.2) - activesupport (= 6.0.3.2) + actionmailbox (6.0.3.3) + actionpack (= 6.0.3.3) + activejob (= 6.0.3.3) + activerecord (= 6.0.3.3) + activestorage (= 6.0.3.3) + activesupport (= 6.0.3.3) mail (>= 2.7.1) - actionmailer (6.0.3.2) - actionpack (= 6.0.3.2) - actionview (= 6.0.3.2) - activejob (= 6.0.3.2) + actionmailer (6.0.3.3) + actionpack (= 6.0.3.3) + actionview (= 6.0.3.3) + activejob (= 6.0.3.3) mail (~> 2.5, >= 2.5.4) rails-dom-testing (~> 2.0) - actionpack (6.0.3.2) - actionview (= 6.0.3.2) - activesupport (= 6.0.3.2) + actionpack (6.0.3.3) + actionview (= 6.0.3.3) + activesupport (= 6.0.3.3) rack (~> 2.0, >= 2.0.8) rack-test (>= 0.6.3) rails-dom-testing (~> 2.0) rails-html-sanitizer (~> 1.0, >= 1.2.0) - actiontext (6.0.3.2) - actionpack (= 6.0.3.2) - activerecord (= 6.0.3.2) - activestorage (= 6.0.3.2) - activesupport (= 6.0.3.2) + actiontext (6.0.3.3) + actionpack (= 6.0.3.3) + activerecord (= 6.0.3.3) + activestorage (= 6.0.3.3) + activesupport (= 6.0.3.3) nokogiri (>= 1.8.5) - actionview (6.0.3.2) - activesupport (= 6.0.3.2) + actionview (6.0.3.3) + activesupport (= 6.0.3.3) builder (~> 3.1) erubi (~> 1.4) rails-dom-testing (~> 2.0) rails-html-sanitizer (~> 1.1, >= 1.2.0) - activejob (6.0.3.2) - activesupport (= 6.0.3.2) + activejob (6.0.3.3) + activesupport (= 6.0.3.3) globalid (>= 0.3.6) - activemodel (6.0.3.2) - activesupport (= 6.0.3.2) - activerecord (6.0.3.2) - activemodel (= 6.0.3.2) - activesupport (= 6.0.3.2) - activestorage (6.0.3.2) - actionpack (= 6.0.3.2) - activejob (= 6.0.3.2) - activerecord (= 6.0.3.2) + activemodel (6.0.3.3) + activesupport (= 6.0.3.3) + activerecord (6.0.3.3) + activemodel (= 6.0.3.3) + activesupport (= 6.0.3.3) + activestorage (6.0.3.3) + actionpack (= 6.0.3.3) + activejob (= 6.0.3.3) + activerecord (= 6.0.3.3) marcel (~> 0.3.1) - activesupport (6.0.3.2) + activesupport (6.0.3.3) concurrent-ruby (~> 1.0, >= 1.0.2) i18n (>= 0.7, < 2) minitest (~> 5.1) @@ -77,7 +77,7 @@ GEM activerecord (>= 5.0, < 6.1) addressable (2.7.0) public_suffix (>= 2.0.2, < 5.0) - administrate (0.13.0) + administrate (0.14.0) actionpack (>= 4.2) actionview (>= 4.2) activerecord (>= 4.2) @@ -93,23 +93,23 @@ GEM rake (>= 10.4, < 14.0) ast (2.4.1) attr_extras (6.2.4) - autoprefixer-rails (9.8.2) + autoprefixer-rails (9.8.6.3) execjs aws-eventstream (1.1.0) - aws-partitions (1.332.0) - aws-sdk-core (3.100.0) + aws-partitions (1.360.0) + aws-sdk-core (3.105.0) aws-eventstream (~> 1, >= 1.0.2) aws-partitions (~> 1, >= 1.239.0) aws-sigv4 (~> 1.1) jmespath (~> 1.0) - aws-sdk-kms (1.34.1) + aws-sdk-kms (1.37.0) aws-sdk-core (~> 3, >= 3.99.0) aws-sigv4 (~> 1.1) - aws-sdk-s3 (1.69.1) - aws-sdk-core (~> 3, >= 3.99.0) + aws-sdk-s3 (1.79.1) + aws-sdk-core (~> 3, >= 3.104.3) aws-sdk-kms (~> 1) aws-sigv4 (~> 1.1) - aws-sigv4 (1.2.0) + aws-sigv4 (1.2.2) aws-eventstream (~> 1, >= 1.0.2) axiom-types (0.1.1) descendants_tracker (~> 0.0.4) @@ -122,11 +122,14 @@ GEM faraday (~> 1.0) faraday_middleware (~> 1.0.0.rc1) nokogiri (~> 1.10.4) - bcrypt (3.1.13) + barnes (0.0.8) + multi_json (~> 1) + statsd-ruby (~> 1.1) + bcrypt (3.1.15) bindex (0.8.1) - bootsnap (1.4.6) + bootsnap (1.4.8) msgpack (~> 1.0) - brakeman (4.8.2) + brakeman (4.9.0) browser (4.2.0) builder (3.2.4) bullet (6.1.0) @@ -146,12 +149,12 @@ GEM crack (0.4.3) safe_yaml (~> 1.0.0) crass (1.0.6) - cypress-on-rails (1.7.0) + cypress-on-rails (1.8.0) rack database_cleaner (1.8.5) datetime_picker_rails (0.0.7) momentjs-rails (>= 2.8.1) - declarative (0.0.10) + declarative (0.0.20) declarative-option (0.1.0) descendants_tracker (0.0.4) thread_safe (~> 0.3, >= 0.3.1) @@ -166,41 +169,42 @@ GEM devise (> 3.5.2, < 5) rails (>= 4.2.0, < 6.1) sprockets (= 3.7.2) - diff-lcs (1.4) - digest-crc (0.5.1) + diff-lcs (1.4.4) + digest-crc (0.6.1) + rake (~> 13.0) docile (1.3.2) domain_name (0.5.20190701) unf (>= 0.0.5, < 1.0.0) - dotenv (2.7.5) - dotenv-rails (2.7.5) - dotenv (= 2.7.5) - railties (>= 3.2, < 6.1) + dotenv (2.7.6) + dotenv-rails (2.7.6) + dotenv (= 2.7.6) + railties (>= 3.2) equalizer (0.0.11) erubi (1.9.0) execjs (2.7.0) facebook-messenger (1.5.0) httparty (~> 0.13, >= 0.13.7) rack (>= 1.4.5) - factory_bot (6.0.2) + factory_bot (6.1.0) activesupport (>= 5.0.0) - factory_bot_rails (6.0.0) - factory_bot (~> 6.0.0) + factory_bot_rails (6.1.0) + factory_bot (~> 6.1.0) railties (>= 5.0.0) - faker (2.12.0) + faker (2.13.0) i18n (>= 1.6, < 2) faraday (1.0.1) multipart-post (>= 1.2, < 3) faraday_middleware (1.0.0) faraday (~> 1.0) - fcm (1.0.1) + fcm (1.0.2) faraday (~> 1.0.0) ffi (1.13.1) flag_shih_tzu (0.3.23) - foreman (0.87.1) - gli (2.19.1) + foreman (0.87.2) + gli (2.19.2) globalid (0.4.2) activesupport (>= 4.2.0) - google-api-client (0.41.0) + google-api-client (0.43.0) addressable (~> 2.5, >= 2.5.1) googleauth (~> 0.9) httpclient (>= 2.8.1, < 3.0) @@ -211,24 +215,24 @@ GEM google-cloud-core (1.5.0) google-cloud-env (~> 1.0) google-cloud-errors (~> 1.0) - google-cloud-env (1.3.2) + google-cloud-env (1.3.3) faraday (>= 0.17.3, < 2.0) google-cloud-errors (1.0.1) - google-cloud-storage (1.26.2) + google-cloud-storage (1.28.0) addressable (~> 2.5) digest-crc (~> 0.4) google-api-client (~> 0.33) google-cloud-core (~> 1.2) googleauth (~> 0.9) mini_mime (~> 1.0) - googleauth (0.13.0) + googleauth (0.13.1) faraday (>= 0.17.3, < 2.0) jwt (>= 1.4, < 3.0) memoist (~> 0.16) multi_json (~> 1.11) os (>= 0.9, < 2.0) signet (~> 0.14) - groupdate (5.0.0) + groupdate (5.1.0) activesupport (>= 5) haikunator (1.1.0) hana (1.3.6) @@ -253,8 +257,8 @@ GEM rails-dom-testing (>= 1, < 3) railties (>= 4.2.0) thor (>= 0.14, < 2.0) - json (2.3.0) - jwt (2.2.1) + json (2.3.1) + jwt (2.2.2) kaminari (1.2.1) activesupport (>= 4.1.0) kaminari-actionview (= 1.2.1) @@ -279,7 +283,7 @@ GEM listen (3.2.1) rb-fsevent (~> 0.10, >= 0.10.3) rb-inotify (~> 0.9, >= 0.9.10) - loofah (2.6.0) + loofah (2.7.0) crass (~> 1.0.2) nokogiri (>= 1.5.9) mail (2.7.1) @@ -295,20 +299,20 @@ GEM mini_magick (4.10.1) mini_mime (1.0.2) mini_portile2 (2.4.0) - minitest (5.14.1) + minitest (5.14.2) momentjs-rails (2.20.1) railties (>= 3.1) msgpack (1.3.3) - multi_json (1.14.1) + multi_json (1.15.0) multi_xml (0.6.0) multipart-post (2.1.1) netrc (0.11.0) - nio4r (2.5.2) + nio4r (2.5.3) nokogiri (1.10.10) mini_portile2 (~> 2.4.0) oauth (0.5.4) orm_adapter (0.5.0) - os (1.1.0) + os (1.1.1) parallel (1.19.2) parser (2.7.1.4) ast (~> 2.4.1) @@ -328,35 +332,33 @@ GEM rack (>= 0.4) rack-cors (1.1.1) rack (>= 2.0.0) - rack-protection (2.0.8.1) - rack rack-proxy (0.6.5) rack rack-test (1.1.0) rack (>= 1.0, < 3) - rails (6.0.3.2) - actioncable (= 6.0.3.2) - actionmailbox (= 6.0.3.2) - actionmailer (= 6.0.3.2) - actionpack (= 6.0.3.2) - actiontext (= 6.0.3.2) - actionview (= 6.0.3.2) - activejob (= 6.0.3.2) - activemodel (= 6.0.3.2) - activerecord (= 6.0.3.2) - activestorage (= 6.0.3.2) - activesupport (= 6.0.3.2) + rails (6.0.3.3) + actioncable (= 6.0.3.3) + actionmailbox (= 6.0.3.3) + actionmailer (= 6.0.3.3) + actionpack (= 6.0.3.3) + actiontext (= 6.0.3.3) + actionview (= 6.0.3.3) + activejob (= 6.0.3.3) + activemodel (= 6.0.3.3) + activerecord (= 6.0.3.3) + activestorage (= 6.0.3.3) + activesupport (= 6.0.3.3) bundler (>= 1.3.0) - railties (= 6.0.3.2) + railties (= 6.0.3.3) sprockets-rails (>= 2.0.0) rails-dom-testing (2.0.3) activesupport (>= 4.2.0) nokogiri (>= 1.6) rails-html-sanitizer (1.3.0) loofah (~> 2.3) - railties (6.0.3.2) - actionpack (= 6.0.3.2) - activesupport (= 6.0.3.2) + railties (6.0.3.3) + actionpack (= 6.0.3.3) + activesupport (= 6.0.3.3) method_source rake (>= 0.8.7) thor (>= 0.20.3, < 2.0) @@ -366,7 +368,7 @@ GEM rb-inotify (0.10.1) ffi (~> 1.0) redis (4.2.1) - redis-namespace (1.7.0) + redis-namespace (1.8.0) redis (>= 3.0.4) redis-rack-cache (2.2.1) rack-cache (>= 1.10, < 2) @@ -405,25 +407,25 @@ GEM rspec-mocks (~> 3.9) rspec-support (~> 3.9) rspec-support (3.9.3) - rubocop (0.86.0) + rubocop (0.89.1) parallel (~> 1.10) - parser (>= 2.7.0.1) + parser (>= 2.7.1.1) rainbow (>= 2.2.2, < 4.0) regexp_parser (>= 1.7) rexml - rubocop-ast (>= 0.0.3, < 1.0) + rubocop-ast (>= 0.3.0, < 1.0) ruby-progressbar (~> 1.7) unicode-display_width (>= 1.4.0, < 2.0) - rubocop-ast (0.0.3) - parser (>= 2.7.0.1) - rubocop-performance (1.6.1) - rubocop (>= 0.71.0) - rubocop-rails (2.6.0) + rubocop-ast (0.3.0) + parser (>= 2.7.1.4) + rubocop-performance (1.7.1) + rubocop (>= 0.82.0) + rubocop-rails (2.7.1) activesupport (>= 4.2.0) rack (>= 1.1) - rubocop (>= 0.82.0) - rubocop-rspec (1.40.0) - rubocop (>= 0.68.1) + rubocop (>= 0.87.0) + rubocop-rspec (1.43.2) + rubocop (~> 0.87) ruby-progressbar (1.10.1) safe_yaml (1.0.5) sass (3.7.4) @@ -439,7 +441,7 @@ GEM sprockets (> 3.0) sprockets-rails tilt - scout_apm (2.6.7) + scout_apm (2.6.9) parser scss_lint (0.59.0) sass (~> 3.5, >= 3.5.5) @@ -448,15 +450,14 @@ GEM activesupport (>= 4) selectize-rails (0.12.6) semantic_range (2.3.0) - sentry-raven (3.0.0) + sentry-raven (3.0.3) faraday (>= 1.0) - shoulda-matchers (4.3.0) + shoulda-matchers (4.4.1) activesupport (>= 4.2.0) - sidekiq (6.0.7) + sidekiq (6.1.1) connection_pool (>= 2.2.2) rack (~> 2.0) - rack-protection (>= 2.0.0) - redis (>= 4.1.0) + redis (>= 4.2.0) signet (0.14.0) addressable (~> 2.3) faraday (>= 0.17.3, < 2.0) @@ -467,14 +468,13 @@ GEM json (>= 1.8, < 3) simplecov-html (~> 0.10.0) simplecov-html (0.10.2) - slack-ruby-client (0.14.6) - activesupport - faraday (>= 0.9) + slack-ruby-client (0.15.0) + faraday (>= 1.0) faraday_middleware gli hashie websocket-driver - spring (2.1.0) + spring (2.1.1) spring-watcher-listen (2.0.1) listen (>= 2.7, < 4.0) spring (>= 1.2, < 3.0) @@ -485,11 +485,13 @@ GEM actionpack (>= 4.0) activesupport (>= 4.0) sprockets (>= 3.0.0) + squasher (0.6.2) + statsd-ruby (1.4.0) telegram-bot-ruby (0.12.0) faraday inflecto virtus - telephone_number (1.4.7) + telephone_number (1.4.9) thor (1.0.1) thread_safe (0.3.6) tilt (2.0.10) @@ -514,7 +516,7 @@ GEM unf_ext (0.0.7.7) unicode-display_width (1.7.0) uniform_notifier (1.13.0) - valid_email2 (3.2.2) + valid_email2 (3.3.1) activemodel (>= 3.2) mail (~> 2.5) virtus (1.0.5) @@ -524,7 +526,7 @@ GEM equalizer (~> 0.0, >= 0.0.9) warden (1.2.8) rack (>= 2.0.6) - web-console (4.0.3) + web-console (4.0.4) actionview (>= 6.0.0) activemodel (>= 6.0.0) bindex (>= 0.4.0) @@ -541,7 +543,7 @@ GEM webpush (1.0.0) hkdf (~> 0.2) jwt (~> 2.0) - websocket-driver (0.7.2) + websocket-driver (0.7.3) websocket-extensions (>= 0.1.0) websocket-extensions (0.1.5) wisper (2.0.0) @@ -558,6 +560,7 @@ DEPENDENCIES attr_extras aws-sdk-s3 azure-storage-blob + barnes bootsnap brakeman browser @@ -569,7 +572,7 @@ DEPENDENCIES devise devise_token_auth dotenv-rails - facebook-messenger + facebook-messenger (= 1.5.0) factory_bot_rails faker fcm @@ -615,6 +618,7 @@ DEPENDENCIES slack-ruby-client spring spring-watcher-listen + squasher telegram-bot-ruby telephone_number time_diff diff --git a/app/bot/facebook_bot.rb b/app/bot/facebook_bot.rb index f0e04ebc2..f3f87220d 100644 --- a/app/bot/facebook_bot.rb +++ b/app/bot/facebook_bot.rb @@ -19,4 +19,10 @@ class FacebookBot updater.perform Rails.logger.info "Human was online at #{delivery.at}" end + + Bot.on :message_echo do |message| + Rails.logger.info "MESSAGE_ECHO #{message}" + response = ::Integrations::Facebook::MessageParser.new(message) + ::Integrations::Facebook::MessageCreator.new(response).perform + end end diff --git a/app/builders/messages/facebook/message_builder.rb b/app/builders/messages/facebook/message_builder.rb index 0095b9e1b..40b0aebc2 100644 --- a/app/builders/messages/facebook/message_builder.rb +++ b/app/builders/messages/facebook/message_builder.rb @@ -7,11 +7,12 @@ class Messages::Facebook::MessageBuilder attr_reader :response - def initialize(response, inbox, outgoing_echo = false) + def initialize(response, inbox, outgoing_echo: false) @response = response @inbox = inbox - @sender_id = (outgoing_echo ? @response.recipient_id : @response.sender_id) - @message_type = (outgoing_echo ? :outgoing : :incoming) + @outgoing_echo = outgoing_echo + @sender_id = (@outgoing_echo ? @response.recipient_id : @response.sender_id) + @message_type = (@outgoing_echo ? :outgoing : :incoming) end def perform @@ -50,6 +51,8 @@ class Messages::Facebook::MessageBuilder def attach_file(attachment, file_url) file_resource = LocalResource.new(file_url) attachment.file.attach(io: file_resource.file, filename: file_resource.tmp_filename, content_type: file_resource.encoding) + rescue Errno::ETIMEDOUT, Errno::ECONNREFUSED, SocketError => e + Rails.logger.info "invalid url #{file_url} : #{e.message}" end def conversation @@ -118,7 +121,7 @@ class Messages::Facebook::MessageBuilder message_type: @message_type, content: response.content, source_id: response.identifier, - sender: contact + sender: @outgoing_echo ? nil : contact } end diff --git a/app/builders/messages/message_builder.rb b/app/builders/messages/message_builder.rb index 24f2f3aa6..6fc3bab5d 100644 --- a/app/builders/messages/message_builder.rb +++ b/app/builders/messages/message_builder.rb @@ -3,12 +3,11 @@ class Messages::MessageBuilder attr_reader :message def initialize(user, conversation, params) - @content = params[:content] + @params = params @private = params[:private] || false @conversation = conversation @user = user @message_type = params[:message_type] || 'outgoing' - @content_type = params[:content_type] @items = params.to_unsafe_h&.dig(:content_attributes, :items) @attachments = params[:attachments] @in_reply_to = params.to_unsafe_h&.dig(:content_attributes, :in_reply_to) @@ -48,12 +47,13 @@ class Messages::MessageBuilder account_id: @conversation.account_id, inbox_id: @conversation.inbox_id, message_type: message_type, - content: @content, + content: @params[:content], private: @private, sender: sender, - content_type: @content_type, + content_type: @params[:content_type], items: @items, - in_reply_to: @in_reply_to + in_reply_to: @in_reply_to, + echo_id: @params[:echo_id] } end end diff --git a/app/controllers/api/v1/accounts/callbacks_controller.rb b/app/controllers/api/v1/accounts/callbacks_controller.rb index 77e46d669..37116d8d0 100644 --- a/app/controllers/api/v1/accounts/callbacks_controller.rb +++ b/app/controllers/api/v1/accounts/callbacks_controller.rb @@ -81,6 +81,8 @@ class Api::V1::Accounts::CallbacksController < Api::V1::Accounts::BaseController avatar_resource = LocalResource.new(uri) facebook_inbox.avatar.attach(io: avatar_resource.file, filename: avatar_resource.tmp_filename, content_type: avatar_resource.encoding) + rescue Errno::ETIMEDOUT, Errno::ECONNREFUSED, SocketError => e + Rails.logger.info "invalid url #{file_url} : #{e.message}" end def get_avatar_url(page_id) diff --git a/app/controllers/api/v1/accounts/conversations_controller.rb b/app/controllers/api/v1/accounts/conversations_controller.rb index 51b6aa079..2f1bdcbfb 100644 --- a/app/controllers/api/v1/accounts/conversations_controller.rb +++ b/app/controllers/api/v1/accounts/conversations_controller.rb @@ -1,5 +1,6 @@ class Api::V1::Accounts::ConversationsController < Api::V1::Accounts::BaseController include Events::Types + before_action :conversation, except: [:index] before_action :contact_inbox, only: [:create] @@ -40,9 +41,10 @@ class Api::V1::Accounts::ConversationsController < Api::V1::Accounts::BaseContro end def toggle_typing_status - if params[:typing_status] == 'on' + case params[:typing_status] + when 'on' trigger_typing_event(CONVERSATION_TYPING_ON) - elsif params[:typing_status] == 'off' + when 'off' trigger_typing_event(CONVERSATION_TYPING_OFF) end head :ok diff --git a/app/controllers/api/v1/accounts/inbox_members_controller.rb b/app/controllers/api/v1/accounts/inbox_members_controller.rb index 9ad2ef93d..fc960d9ec 100644 --- a/app/controllers/api/v1/accounts/inbox_members_controller.rb +++ b/app/controllers/api/v1/accounts/inbox_members_controller.rb @@ -12,7 +12,7 @@ class Api::V1::Accounts::InboxMembersController < Api::V1::Accounts::BaseControl end def show - @agents = Current.account.users.where(id: @inbox.members.pluck(:user_id)) + @agents = Current.account.users.where(id: @inbox.members.select(:user_id)) end private diff --git a/app/controllers/api/v1/accounts/inboxes_controller.rb b/app/controllers/api/v1/accounts/inboxes_controller.rb index d2b0dcb60..6c4a3cf7d 100644 --- a/app/controllers/api/v1/accounts/inboxes_controller.rb +++ b/app/controllers/api/v1/accounts/inboxes_controller.rb @@ -91,7 +91,7 @@ class Api::V1::Accounts::InboxesController < Api::V1::Accounts::BaseController :welcome_tagline, :webhook_url, :email, - selected_feature_flags: [] + { selected_feature_flags: [] } ]) end end diff --git a/app/controllers/api/v1/accounts/integrations/slack_controller.rb b/app/controllers/api/v1/accounts/integrations/slack_controller.rb index 77cf05ade..1002f5752 100644 --- a/app/controllers/api/v1/accounts/integrations/slack_controller.rb +++ b/app/controllers/api/v1/accounts/integrations/slack_controller.rb @@ -3,7 +3,7 @@ class Api::V1::Accounts::Integrations::SlackController < Api::V1::Accounts::Base def create builder = Integrations::Slack::HookBuilder.new( - account: current_account, + account: Current.account, code: params[:code], inbox_id: params[:inbox_id] ) @@ -25,7 +25,7 @@ class Api::V1::Accounts::Integrations::SlackController < Api::V1::Accounts::Base private def fetch_hook - @hook = Integrations::Hook.find_by(app_id: 'slack') + @hook = Integrations::Hook.where(account: Current.account).find_by(app_id: 'slack') end def create_chatwoot_slack_channel diff --git a/app/controllers/api/v1/widget/conversations_controller.rb b/app/controllers/api/v1/widget/conversations_controller.rb index 66b54d4b9..bafd008f9 100644 --- a/app/controllers/api/v1/widget/conversations_controller.rb +++ b/app/controllers/api/v1/widget/conversations_controller.rb @@ -8,7 +8,7 @@ class Api::V1::Widget::ConversationsController < Api::V1::Widget::BaseController def update_last_seen head :ok && return if conversation.nil? - conversation.user_last_seen_at = DateTime.now.utc + conversation.contact_last_seen_at = DateTime.now.utc conversation.save! head :ok end @@ -26,9 +26,10 @@ class Api::V1::Widget::ConversationsController < Api::V1::Widget::BaseController def toggle_typing head :ok && return if conversation.nil? - if permitted_params[:typing_status] == 'on' + case permitted_params[:typing_status] + when 'on' trigger_typing_event(CONVERSATION_TYPING_ON) - elsif permitted_params[:typing_status] == 'off' + when 'off' trigger_typing_event(CONVERSATION_TYPING_OFF) end diff --git a/app/controllers/api/v1/widget/messages_controller.rb b/app/controllers/api/v1/widget/messages_controller.rb index afc0d71b2..a3fed9fb4 100644 --- a/app/controllers/api/v1/widget/messages_controller.rb +++ b/app/controllers/api/v1/widget/messages_controller.rb @@ -48,6 +48,7 @@ class Api::V1::Widget::MessagesController < Api::V1::Widget::BaseController sender: @contact, content: permitted_params[:message][:content], inbox_id: conversation.inbox_id, + echo_id: permitted_params[:message][:echo_id], message_type: :incoming } end @@ -112,11 +113,11 @@ class Api::V1::Widget::MessagesController < Api::V1::Widget::BaseController end def message_update_params - params.permit(message: [submitted_values: [:name, :title, :value]]) + params.permit(message: [{ submitted_values: [:name, :title, :value] }]) end def permitted_params - params.permit(:id, :before, :website_token, contact: [:email], message: [:content, :referer_url, :timestamp]) + params.permit(:id, :before, :website_token, contact: [:email], message: [:content, :referer_url, :timestamp, :echo_id]) end def set_message diff --git a/app/controllers/devise_overrides/confirmations_controller.rb b/app/controllers/devise_overrides/confirmations_controller.rb index 96d16eafe..057e77b12 100644 --- a/app/controllers/devise_overrides/confirmations_controller.rb +++ b/app/controllers/devise_overrides/confirmations_controller.rb @@ -25,6 +25,6 @@ class DeviseOverrides::ConfirmationsController < Devise::ConfirmationsController user.reset_password_token = enc user.reset_password_sent_at = Time.now.utc user.save(validate: false) - '/app/auth/password/edit?config=default&redirect_url=&reset_password_token=' + raw + "/app/auth/password/edit?config=default&redirect_url=&reset_password_token=#{raw}" end end diff --git a/app/controllers/twitter/callbacks_controller.rb b/app/controllers/twitter/callbacks_controller.rb index f317f3ffa..b3cafbe12 100644 --- a/app/controllers/twitter/callbacks_controller.rb +++ b/app/controllers/twitter/callbacks_controller.rb @@ -27,7 +27,7 @@ class Twitter::CallbacksController < Twitter::BaseController end def account - @account ||= Account.find_by!(id: account_id) + @account ||= Account.find(account_id) end def twitter_app_redirect_url diff --git a/app/dispatchers/async_dispatcher.rb b/app/dispatchers/async_dispatcher.rb index c24b633aa..e5fbdd7f7 100644 --- a/app/dispatchers/async_dispatcher.rb +++ b/app/dispatchers/async_dispatcher.rb @@ -9,7 +9,10 @@ class AsyncDispatcher < BaseDispatcher end def listeners - listeners = [EventListener.instance, WebhookListener.instance, HookListener.instance] - listeners + [ + EventListener.instance, + WebhookListener.instance, + InstallationWebhookListener.instance, HookListener.instance + ] end end diff --git a/app/finders/conversation_finder.rb b/app/finders/conversation_finder.rb index 495bddf63..5ca1e12f6 100644 --- a/app/finders/conversation_finder.rb +++ b/app/finders/conversation_finder.rb @@ -67,9 +67,10 @@ class ConversationFinder end def filter_by_assignee_type - if @assignee_type == 'me' + case @assignee_type + when 'me' @conversations = @conversations.assigned_to(current_user) - elsif @assignee_type == 'unassigned' + when 'unassigned' @conversations = @conversations.unassigned end @conversations diff --git a/app/javascript/dashboard/assets/scss/_helper-classes.scss b/app/javascript/dashboard/assets/scss/_helper-classes.scss index cbcc31e42..a1bfa1462 100644 --- a/app/javascript/dashboard/assets/scss/_helper-classes.scss +++ b/app/javascript/dashboard/assets/scss/_helper-classes.scss @@ -69,3 +69,11 @@ select { border-top-left-radius: 0 !important; } } + +.justify-space-between { + justify-content: space-between; +} + +.w-100 { + width: 100%; +} diff --git a/app/javascript/dashboard/assets/scss/_mixins.scss b/app/javascript/dashboard/assets/scss/_mixins.scss index 36a87a4f9..a20a32ffe 100644 --- a/app/javascript/dashboard/assets/scss/_mixins.scss +++ b/app/javascript/dashboard/assets/scss/_mixins.scss @@ -1,3 +1,4 @@ +@import '~dashboard/assets/scss/variables'; @import '~widget/assets/scss/mixins'; $spinner-before-border-color: rgba(255, 255, 255, 0.7); @@ -235,12 +236,3 @@ $spinner-before-border-color: rgba(255, 255, 255, 0.7); text-overflow: ellipsis; white-space: nowrap; } - - -.justify-space-between { - justify-content: space-between; -} - -.w-100 { - width: 100%; -} diff --git a/app/javascript/dashboard/assets/scss/widgets/_conv-header.scss b/app/javascript/dashboard/assets/scss/widgets/_conv-header.scss index 878e5a788..20919a73a 100644 --- a/app/javascript/dashboard/assets/scss/widgets/_conv-header.scss +++ b/app/javascript/dashboard/assets/scss/widgets/_conv-header.scss @@ -41,6 +41,10 @@ @include flex; @include flex-align($x: center, $y: middle); + &.hide { + visibility: hidden; + } + .user--name { @include margin(0); font-size: $font-size-medium; @@ -83,3 +87,15 @@ } } } + + +.header-actions-wrap { + display: flex; + flex-direction: row; + flex-grow: 1; + justify-content: flex-end; + + &.has-open-sidebar { + justify-content: flex-end; + } +} diff --git a/app/javascript/dashboard/assets/scss/widgets/_conversation-view.scss b/app/javascript/dashboard/assets/scss/widgets/_conversation-view.scss index 257ad16d7..dde956eaa 100644 --- a/app/javascript/dashboard/assets/scss/widgets/_conversation-view.scss +++ b/app/javascript/dashboard/assets/scss/widgets/_conversation-view.scss @@ -103,11 +103,6 @@ } } -.emojione { - height: $font-size-medium; - width: $font-size-medium; -} - .conversation-wrap { @include background-gray; @include margin(0); diff --git a/app/javascript/dashboard/assets/scss/widgets/_emojiinput.scss b/app/javascript/dashboard/assets/scss/widgets/_emojiinput.scss deleted file mode 100644 index f4bc48e7c..000000000 --- a/app/javascript/dashboard/assets/scss/widgets/_emojiinput.scss +++ /dev/null @@ -1,107 +0,0 @@ -@import '../variables'; -@import '../mixins'; - -.emoji-dialog { - @include elegant-card; - background: $color-white; - box-sizing: content-box; - position: absolute; - right: 0; - top: -22rem; - width: 28.6rem; - - &::before { - @include arrow(bottom, $color-white, $space-slab); - bottom: -$space-slab; - position: absolute; - right: $space-two; - } - - .emojione { - font-size: $font-size-default; - margin: $zero; - } - - .emoji-row { - box-sizing: border-box; - height: 180px; - overflow-y: auto; - padding: $space-small; - - .emoji { - border-radius: 4px; - display: inline-block; - padding: 5px; - } - - .emojione { - cursor: pointer; - float: left; - margin: .6rem; - } - } - - .emoji-category-title { - color: $color-heading; - font-size: $font-size-small; - font-weight: $font-weight-medium; - margin: 0; - text-transform: capitalize; - } - - .emoji-category-heading-decoration { - text-align: right; - } -} - -.emoji-dialog-header { - background-color: $color-body; - border-top-left-radius: $space-small; - border-top-right-radius: $space-small; - padding: $zero $space-smaller; - - ul { - display: flex; - list-style: none; - margin: 0; - padding: $space-smaller 0 0; - - >li { - align-items: center; - cursor: pointer; - display: flex; - height: $space-medium; - justify-content: center; - padding: $space-smaller $space-small; - } - - .emojione { - height: $space-two; - width: $space-normal; - } - - >.active { - background: $color-white; - border-top-left-radius: $space-small; - border-top-right-radius: $space-small; - } - - img, - svg { - filter: grayscale(100%); - } - } - - .active { - - img, - svg { - filter: grayscale(0); - } - } - - >* { - display: table-cell; - vertical-align: middle; - } -} diff --git a/app/javascript/dashboard/components/widgets/conversation/ConversationHeader.vue b/app/javascript/dashboard/components/widgets/conversation/ConversationHeader.vue index 65567ae26..10860a2bd 100644 --- a/app/javascript/dashboard/components/widgets/conversation/ConversationHeader.vue +++ b/app/javascript/dashboard/components/widgets/conversation/ConversationHeader.vue @@ -1,6 +1,6 @@ @@ -21,6 +22,7 @@ import { setHeader } from 'widget/helpers/axios'; import { IFrameHelper } from 'widget/helpers/utils'; import Router from './views/Router'; +import { getLocale } from './helpers/urlParamsHelper'; export default { name: 'App', @@ -33,6 +35,7 @@ export default { isMobile: false, hideMessageBubble: false, widgetPosition: 'right', + showPopoutButton: false, }; }, computed: { @@ -49,74 +52,25 @@ export default { const isLeft = this.widgetPosition === 'left'; return isLeft; }, + isIFrame() { + return IFrameHelper.isIFrame(); + }, }, mounted() { const { websiteToken, locale } = window.chatwootWebChannel; this.setLocale(locale); - - if (IFrameHelper.isIFrame()) { - IFrameHelper.sendMessage({ - event: 'loaded', - config: { - authToken: window.authToken, - channelConfig: window.chatwootWebChannel, - }, - }); + if (this.isIFrame) { + this.registerListeners(); + this.sendLoadedEvent(); setHeader('X-Auth-Token', window.authToken); + } else { + setHeader('X-Auth-Token', window.authToken); + this.fetchOldConversations(); + this.fetchAvailableAgents(websiteToken); + this.setLocale(getLocale(window.location.search)); } - this.setWidgetColor(window.chatwootWebChannel); - - window.addEventListener('message', e => { - const wootPrefix = 'chatwoot-widget:'; - const isDataNotString = typeof e.data !== 'string'; - const isNotFromWoot = isDataNotString || e.data.indexOf(wootPrefix) !== 0; - - if (isNotFromWoot) return; - - const message = JSON.parse(e.data.replace(wootPrefix, '')); - if (message.event === 'config-set') { - this.setLocale(message.locale); - this.setBubbleLabel(); - this.setPosition(message.position); - this.fetchOldConversations().then(() => { - this.setUnreadView(); - }); - this.fetchAvailableAgents(websiteToken); - this.setHideMessageBubble(message.hideMessageBubble); - } else if (message.event === 'widget-visible') { - this.scrollConversationToBottom(); - } else if (message.event === 'set-current-url') { - window.refererURL = message.refererURL; - } else if (message.event === 'toggle-close-button') { - this.isMobile = message.showClose; - } else if (message.event === 'push-event') { - this.createWidgetEvents(message); - } else if (message.event === 'set-label') { - this.$store.dispatch('conversationLabels/create', message.label); - } else if (message.event === 'remove-label') { - this.$store.dispatch('conversationLabels/destroy', message.label); - } else if (message.event === 'set-user') { - this.$store.dispatch('contacts/update', message); - } else if (message.event === 'set-custom-attributes') { - this.$store.dispatch( - 'contacts/setCustomAttributes', - message.customAttributes - ); - } else if (message.event === 'delete-custom-attribute') { - this.$store.dispatch('contacts/setCustomAttributes', { - [message.customAttribute]: null, - }); - } else if (message.event === 'set-locale') { - this.setLocale(message.locale); - this.setBubbleLabel(); - } else if (message.event === 'set-unread-view') { - this.showUnreadView = true; - } else if (message.event === 'unset-unread-view') { - this.showUnreadView = false; - } - }); - this.$store.dispatch('conversationAttributes/get'); + this.setWidgetColor(window.chatwootWebChannel); this.registerUnreadEvents(); }, methods: { @@ -147,15 +101,23 @@ export default { this.hideMessageBubble = !!hideBubble; }, registerUnreadEvents() { - bus.$on('on-agent-message-recieved', () => this.setUnreadView()); + bus.$on('on-agent-message-recieved', () => { + if (!this.isIFrame) { + this.setUserLastSeen(); + } + this.setUnreadView(); + }); bus.$on('on-unread-view-clicked', () => { this.unsetUnreadView(); this.setUserLastSeen(); }); }, + setPopoutDisplay(showPopoutButton) { + this.showPopoutButton = showPopoutButton; + }, setUnreadView() { const { unreadMessageCount } = this; - if (IFrameHelper.isIFrame() && unreadMessageCount > 0) { + if (this.isIFrame && unreadMessageCount > 0) { IFrameHelper.sendMessage({ event: 'setUnreadMode', unreadMessageCount, @@ -163,7 +125,7 @@ export default { } }, unsetUnreadView() { - if (IFrameHelper.isIFrame()) { + if (this.isIFrame) { IFrameHelper.sendMessage({ event: 'resetUnreadMode' }); } }, @@ -176,6 +138,63 @@ export default { this.setUserLastSeen(); this.$store.dispatch('events/create', { name: eventName }); }, + registerListeners() { + const { websiteToken } = window.chatwootWebChannel; + window.addEventListener('message', e => { + if (!IFrameHelper.isAValidEvent(e)) { + return; + } + const message = IFrameHelper.getMessage(e); + if (message.event === 'config-set') { + this.setLocale(message.locale); + this.setBubbleLabel(); + this.setPosition(message.position); + this.fetchOldConversations().then(() => this.setUnreadView()); + this.setPopoutDisplay(message.showPopoutButton); + this.fetchAvailableAgents(websiteToken); + this.setHideMessageBubble(message.hideMessageBubble); + } else if (message.event === 'widget-visible') { + this.scrollConversationToBottom(); + } else if (message.event === 'set-current-url') { + window.refererURL = message.refererURL; + } else if (message.event === 'toggle-close-button') { + this.isMobile = message.showClose; + } else if (message.event === 'push-event') { + this.createWidgetEvents(message); + } else if (message.event === 'set-label') { + this.$store.dispatch('conversationLabels/create', message.label); + } else if (message.event === 'remove-label') { + this.$store.dispatch('conversationLabels/destroy', message.label); + } else if (message.event === 'set-user') { + this.$store.dispatch('contacts/update', message); + } else if (message.event === 'set-custom-attributes') { + this.$store.dispatch( + 'contacts/setCustomAttributes', + message.customAttributes + ); + } else if (message.event === 'delete-custom-attribute') { + this.$store.dispatch('contacts/setCustomAttributes', { + [message.customAttribute]: null, + }); + } else if (message.event === 'set-locale') { + this.setLocale(message.locale); + this.setBubbleLabel(); + } else if (message.event === 'set-unread-view') { + this.showUnreadView = true; + } else if (message.event === 'unset-unread-view') { + this.showUnreadView = false; + } + }); + }, + sendLoadedEvent() { + IFrameHelper.sendMessage({ + event: 'loaded', + config: { + authToken: window.authToken, + channelConfig: window.chatwootWebChannel, + }, + }); + }, }, }; diff --git a/app/javascript/widget/api/conversation.js b/app/javascript/widget/api/conversation.js index 9933e529a..0901fbcb7 100755 --- a/app/javascript/widget/api/conversation.js +++ b/app/javascript/widget/api/conversation.js @@ -33,7 +33,7 @@ const toggleTyping = async ({ typingStatus }) => { const setUserLastSeenAt = async ({ lastSeen }) => { return API.post( `/api/v1/widget/conversations/update_last_seen${window.location.search}`, - { user_last_seen_at: lastSeen } + { contact_last_seen_at: lastSeen } ); }; diff --git a/app/javascript/widget/assets/scss/_buttons.scss b/app/javascript/widget/assets/scss/_buttons.scss index 441908c8f..840b0b40e 100755 --- a/app/javascript/widget/assets/scss/_buttons.scss +++ b/app/javascript/widget/assets/scss/_buttons.scss @@ -58,4 +58,14 @@ $button-border-width: 1px; &.block { width: 100%; } + + &.transparent { + background: transparent; + border: 0; + height: auto; + } + + &.compact { + padding: 0; + } } diff --git a/app/javascript/widget/assets/scss/woot.scss b/app/javascript/widget/assets/scss/woot.scss index ba6d93e23..7c69f2a55 100755 --- a/app/javascript/widget/assets/scss/woot.scss +++ b/app/javascript/widget/assets/scss/woot.scss @@ -3,7 +3,7 @@ @import 'buttons'; @import 'mixins'; @import 'forms'; -@import 'shared/assets/fonts/inter'; +@import 'shared/assets/fonts/widget_fonts'; @import '~ionicons/scss/ionicons'; @import '~spinkit/scss/spinners/7-three-bounce'; @@ -11,6 +11,8 @@ html, body { font-family: $font-family; font-size: 10px; + -moz-osx-font-smoothing: grayscale; + -webkit-font-smoothing: antialiased; height: 100%; } @@ -18,36 +20,15 @@ body { height: 100%; } -.close-button { - cursor: pointer; - position: relative; - width: $space-two; - - &::before, - &::after { - background-color: $color-heading; - content: ' '; - height: $space-normal; - left: $space-small; - position: absolute; - top: $space-micro; - width: 2px; - } - - &::before { - transform: rotate(45deg); - } - - &::after { - transform: rotate(-45deg); - } -} - .is-mobile { - .header-wrap { + .actions { .close-button { display: block !important; } + + .new-window--button { + display: none !important; + } } } diff --git a/app/javascript/widget/components/ChatHeader.vue b/app/javascript/widget/components/ChatHeader.vue index 5143635a5..c84d05326 100644 --- a/app/javascript/widget/components/ChatHeader.vue +++ b/app/javascript/widget/components/ChatHeader.vue @@ -4,16 +4,18 @@ avatar

- + @@ -73,9 +70,5 @@ export default { width: 24px; margin-right: $space-small; } - - .close-button { - display: none; - } } diff --git a/app/javascript/widget/components/ChatHeaderExpanded.vue b/app/javascript/widget/components/ChatHeaderExpanded.vue index 5ef50129d..a31f6669e 100755 --- a/app/javascript/widget/components/ChatHeaderExpanded.vue +++ b/app/javascript/widget/components/ChatHeaderExpanded.vue @@ -1,7 +1,9 @@