diff --git a/Gemfile b/Gemfile index 1fb109bbb..87f6be227 100644 --- a/Gemfile +++ b/Gemfile @@ -36,6 +36,7 @@ gem 'webpacker' gem 'devise', git: 'https://github.com/plataformatec/devise' gem 'devise_token_auth', git: 'https://github.com/lynndylanhurley/devise_token_auth' # authorization +gem 'jwt' gem 'pundit' ##--- gems for pubsub service ---## diff --git a/Gemfile.lock b/Gemfile.lock index c74e87f2d..b4f818ba6 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -220,6 +220,7 @@ GEM jmespath (1.4.0) json (2.2.0) json_pure (2.2.0) + jwt (2.2.1) kaminari (1.1.1) activesupport (>= 4.1.0) kaminari-actionview (= 1.1.1) @@ -459,6 +460,7 @@ DEPENDENCIES haikunator hashie jbuilder (~> 2.5) + jwt kaminari koala letter_opener diff --git a/app/controllers/api/v1/widget/messages_controller.rb b/app/controllers/api/v1/widget/messages_controller.rb index 662fd927e..388ad3672 100644 --- a/app/controllers/api/v1/widget/messages_controller.rb +++ b/app/controllers/api/v1/widget/messages_controller.rb @@ -45,7 +45,9 @@ class Api::V1::Widget::MessagesController < ActionController::Base end def cookie_params - JSON.parse(cookies.signed[cookie_name]).symbolize_keys + @cookie_params ||= JWT.decode( + request.headers[header_name], secret_key, true, algorithm: 'HS256' + ).first.symbolize_keys end def message_finder_params @@ -58,11 +60,15 @@ class Api::V1::Widget::MessagesController < ActionController::Base @message_finder ||= MessageFinder.new(conversation, message_finder_params) end - def cookie_name - 'cw_conversation_' + params[:website_token] + def header_name + 'X-Auth-Token' end def permitted_params params.fetch(:message).permit(:content) end + + def secret_key + Rails.application.secrets.secret_key_base + end end diff --git a/app/controllers/widgets_controller.rb b/app/controllers/widgets_controller.rb index 733e99df4..979186c59 100644 --- a/app/controllers/widgets_controller.rb +++ b/app/controllers/widgets_controller.rb @@ -1,14 +1,11 @@ class WidgetsController < ActionController::Base before_action :set_web_widget + before_action :set_token before_action :set_contact before_action :build_contact private - def set_web_widget - @web_widget = ::Channel::WebWidget.find_by!(website_token: permitted_params[:website_token]) - end - def set_contact return if cookie_params[:source_id].nil? @@ -20,28 +17,49 @@ class WidgetsController < ActionController::Base @contact = contact_inbox.contact end + def set_token + @token = conversation_token + end + + def set_web_widget + @web_widget = ::Channel::WebWidget.find_by!(website_token: permitted_params[:website_token]) + end + def build_contact return if @contact.present? contact_inbox = @web_widget.create_contact_inbox @contact = contact_inbox.contact - cookies.signed[cookie_name] = JSON.generate( + payload = { source_id: contact_inbox.source_id, contact_id: @contact.id, inbox_id: @web_widget.inbox.id - ).to_s + } + @token = JWT.encode payload, secret_key, 'HS256' end def cookie_params - cookies.signed[cookie_name] ? JSON.parse(cookies.signed[cookie_name]).symbolize_keys : {} + return @cookie_params if @cookie_params.present? + + if conversation_token.present? + @cookie_params = JWT.decode( + conversation_token, secret_key, true, algorithm: 'HS256' + ).first.symbolize_keys + return @cookie_params + end + {} + end + + def conversation_token + permitted_params[:cw_conversation] end def permitted_params - params.permit(:website_token) + params.permit(:website_token, :cw_conversation) end - def cookie_name - 'cw_conversation_' + permitted_params[:website_token] + def secret_key + Rails.application.secrets.secret_key_base end end diff --git a/app/javascript/packs/sdk.js b/app/javascript/packs/sdk.js index 736251353..d4d513d47 100755 --- a/app/javascript/packs/sdk.js +++ b/app/javascript/packs/sdk.js @@ -1,4 +1,6 @@ -import sdkStyles from '../widget/assets/scss/sdk.css'; +import Cookies from 'js-cookie'; + +import { SDK_CSS } from '../widget/assets/scss/sdk'; /* eslint-disable no-param-reassign */ const bubbleImg = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAMAAABg3Am1AAAAUVBMVEUAAAD///////////////////////////////////////////////////////////////////////////////////////////////////////8IN+deAAAAGnRSTlMAAwgJEBk0TVheY2R5eo+ut8jb5OXs8fX2+cjRDTIAAADsSURBVHgBldZbkoMgFIThRgQv8SKKgGf/C51UnJqaRI30/9zfe+NQUQ3TvG7bOk9DVeCmshmj/CuOTYnrdBfkUOg0zlOtl9OWVuEk4+QyZ3DIevmSt/ioTvK1VH/s5bY3YdM9SBZ/mUUyWgx+U06ycgp7D8msxSvtc4HXL9BLdj2elSEfhBJAI0QNgJEBI1BEBsQClVBVGDgwYOLAhJkDM1YOrNg4sLFAsLJgZsHEgoEFFQt0JAFGFjQsKAMJ0LFAexKgZYFyJIDxJIBNJEDNAtSJBLCeBDCOBFAPzwFA94ED+zmhwDO9358r8ANtIsMXi7qVAwAAAABJRU5ErkJggg=='; @@ -6,7 +8,6 @@ const closeImg = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAMAAACdt4HsAAAAP1BMVEUAAAD///////////////////////////////////////////////////////////////////////////////9Du/pqAAAAFHRSTlMACBstLi8wMVB+mcbT2err7O3w8n+sjtQAAAEuSURBVHgBtNLdcoMgGITh1SCGH9DId//X2mnTg7hYxj0oh8w8r+MqgDnmlsIE6UwhtRxnAHge9n2KV7wvP+h4AvPbm73W+359/aJjRjQTCuTNIrJJBfKW0UwqkLeGZJ8Ff2O/T28JwZQCewuYilJgX6buavdDv188br1RIE+jc2H5yy+9VwrXXij0nsflwth7YFRw7N3Y88BcYL+z7wubO/lt6AcFwQMLF9irP8r2eF8/ei8VLrxUkDzguMDejX03WK3dsGJB9lxgrxd0T8PTRxUL5OUCealQz76KXg/or/CvI36VXgcEAAAgCMP6t16IZVDg3zPuI+0rb5g2zlsoW2lbqlvrOyw7bTuuO+8LGIs4C1mLeQuai7oL2437LRytPC1drX0tnq2+Ld+r/wDPIIIJkfdlbQAAAABJRU5ErkJggg=='; const body = document.getElementsByTagName('body')[0]; -const iframe = document.createElement('iframe'); const holder = document.createElement('div'); const bubbleHolder = document.createElement('div'); @@ -16,16 +17,10 @@ const closeBubble = document.createElement('div'); const notification_bubble = document.createElement('span'); const bodyOverFlowStyle = document.body.style.overflow; -function addClass(elm, classes) { - if (classes) { - elm.className += ` ${classes}`; - } -} - function loadCSS() { const css = document.createElement('style'); css.type = 'text/css'; - css.innerHTML = sdkStyles; + css.innerHTML = `${SDK_CSS}`; document.body.appendChild(css); } @@ -45,14 +40,13 @@ function wootOn(elm, event, fn) { } function classHelper(classes, action, elm) { - let classarray; let search; let replace; let i; let has = false; if (classes) { // Trim any whitespace - classarray = classes.split(/\s+/); + const classarray = classes.split(/\s+/); for (i = 0; i < classarray.length; i += 1) { search = new RegExp(`\\b${classarray[i]}\\b`, 'g'); replace = new RegExp(` *${classarray[i]}\\b`, 'g'); @@ -75,6 +69,12 @@ function classHelper(classes, action, elm) { return has; } +function addClass(elm, classes) { + if (classes) { + elm.className += ` ${classes}`; + } +} + // Toggle class function toggleClass(elm, classes) { classHelper(classes, 'toggle', elm); @@ -117,42 +117,81 @@ function enableScroll() { document.body.style.overflow = bodyOverFlowStyle; } -function loadCallback() { - iframe.style.visibility = ''; - iframe.setAttribute('id', `chatwoot_live_chat_widget`); - iframe.onmouseenter = disableScroll; - iframe.onmouseleave = enableScroll; +const IFrameHelper = { + createFrame: ({ baseUrl, websiteToken }) => { + const iframe = document.createElement('iframe'); + const cwCookie = Cookies.get('cw_conversation'); + let widgetUrl = `${baseUrl}/widgets?website_token=${websiteToken}`; + if (cwCookie) { + widgetUrl = `${widgetUrl}&cw_conversation=${cwCookie}`; + } + iframe.src = widgetUrl; - loadCSS(); - createBubbleHolder(); + iframe.id = 'chatwoot_web_widget'; + iframe.style.visibility = 'hidden'; + holder.className = 'woot-widget-holder woot--hide'; + holder.appendChild(iframe); + body.appendChild(holder); + IFrameHelper.initPostMessageCommunication(); + }, + getAppFrame: () => document.getElementById('chatwoot_web_widget'), + sendMessage: (key, value) => { + const element = IFrameHelper.getAppFrame(); + element.contentWindow.postMessage( + `chatwoot-widget:${JSON.stringify({ event: key, ...value })}`, + '*' + ); + }, + initPostMessageCommunication: () => { + window.onmessage = e => { + if ( + typeof e.data !== 'string' || + e.data.indexOf('chatwoot-widget:') !== 0 + ) { + return; + } + const message = JSON.parse(e.data.replace('chatwoot-widget:', '')); + if (message.event === 'loaded') { + Cookies.set('cw_conversation', message.config.authToken); + IFrameHelper.sendMessage('config-set', {}); + IFrameHelper.onLoad(); + } + }; + }, + onLoad: () => { + const iframe = IFrameHelper.getAppFrame(); + iframe.style.visibility = ''; + iframe.setAttribute('id', `chatwoot_live_chat_widget`); + iframe.onmouseenter = disableScroll; + iframe.onmouseleave = enableScroll; - bubbleHolder.appendChild( - createBubbleIcon({ - className: 'woot-widget-bubble', - src: bubbleImg, - target: chatBubble, - }) - ); - bubbleHolder.appendChild( - createBubbleIcon({ - className: 'woot-widget-bubble woot--close woot--hide', - src: closeImg, - target: closeBubble, - }) - ); - bubbleHolder.appendChild(createNotificationBubble()); - onClickChatBubble(); -} + loadCSS(); + createBubbleHolder(); -function loadIframe({ websiteToken, baseUrl }) { - iframe.style.visibility = 'hidden'; - iframe.src = `${baseUrl}/widgets?website_token=${websiteToken}`; - iframe.onload = loadCallback; + bubbleHolder.appendChild( + createBubbleIcon({ + className: 'woot-widget-bubble', + src: bubbleImg, + target: chatBubble, + }) + ); + bubbleHolder.appendChild( + createBubbleIcon({ + className: 'woot-widget-bubble woot--close woot--hide', + src: closeImg, + target: closeBubble, + }) + ); + bubbleHolder.appendChild(createNotificationBubble()); + onClickChatBubble(); + }, +}; - holder.className = 'woot-widget-holder woot--hide'; - holder.appendChild(iframe); - - body.appendChild(holder); +function loadIframe({ baseUrl, websiteToken }) { + IFrameHelper.createFrame({ + baseUrl, + websiteToken, + }); } window.chatwootSDK = { diff --git a/app/javascript/widget/App.vue b/app/javascript/widget/App.vue index 14f86d4c2..d67a6375f 100755 --- a/app/javascript/widget/App.vue +++ b/app/javascript/widget/App.vue @@ -6,6 +6,17 @@ diff --git a/app/javascript/widget/assets/scss/sdk.css b/app/javascript/widget/assets/scss/sdk.js similarity index 98% rename from app/javascript/widget/assets/scss/sdk.css rename to app/javascript/widget/assets/scss/sdk.js index d54cc2832..4e72bb4e7 100644 --- a/app/javascript/widget/assets/scss/sdk.css +++ b/app/javascript/widget/assets/scss/sdk.js @@ -1,3 +1,4 @@ +export const SDK_CSS = ` .woot-widget-holder { z-index: 2147483000!important; position: fixed!important; @@ -63,3 +64,4 @@ .woot--hide { display: none !important; } +`; diff --git a/app/javascript/widget/components/ConversationWrap.vue b/app/javascript/widget/components/ConversationWrap.vue index 04c4fb2b0..3ced87b89 100755 --- a/app/javascript/widget/components/ConversationWrap.vue +++ b/app/javascript/widget/components/ConversationWrap.vue @@ -28,6 +28,5 @@ export default { .conversation { height: 100%; - padding: $space-large $space-small $space-large $space-normal; } diff --git a/app/javascript/widget/helpers/actionCable.js b/app/javascript/widget/helpers/actionCable.js index c8a5bbd33..d43e740b8 100644 --- a/app/javascript/widget/helpers/actionCable.js +++ b/app/javascript/widget/helpers/actionCable.js @@ -6,6 +6,7 @@ class ActionCableConnector extends BaseActionCableConnector { this.events = { 'message.created': this.onMessageCreated, }; + console.log('joined', app, pubsubToken); } onMessageCreated = data => { diff --git a/app/javascript/widget/views/Home.vue b/app/javascript/widget/views/Home.vue index 81b102887..73f4baec2 100755 --- a/app/javascript/widget/views/Home.vue +++ b/app/javascript/widget/views/Home.vue @@ -69,6 +69,7 @@ export default { .conversation-wrap { flex: 1; overflow-y: auto; + padding: $space-large $space-small $space-large $space-normal; } .footer-wrap { diff --git a/app/models/message.rb b/app/models/message.rb index ec70e212f..b1dc6f83a 100644 --- a/app/models/message.rb +++ b/app/models/message.rb @@ -42,7 +42,7 @@ class Message < ApplicationRecord private def dispatch_event - Rails.configuration.dispatcher.dispatch(MESSAGE_CREATED, Time.zone.now, message: self) unless conversation.messages.count == 1 + Rails.configuration.dispatcher.dispatch(MESSAGE_CREATED, Time.zone.now, message: self) if outgoing? && conversation.messages.outgoing.count == 1 Rails.configuration.dispatcher.dispatch(FIRST_REPLY_CREATED, Time.zone.now, message: self) diff --git a/app/views/widgets/index.html.erb b/app/views/widgets/index.html.erb index 83f7d3e4e..35a7ff1a3 100644 --- a/app/views/widgets/index.html.erb +++ b/app/views/widgets/index.html.erb @@ -4,15 +4,16 @@ Chatwoot <%= csrf_meta_tags %> + <%= javascript_pack_tag 'widget' %> <%= stylesheet_pack_tag 'widget' %>
<%= yield %> - diff --git a/package.json b/package.json index ee3c489e1..51fa48c8e 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,7 @@ "foundation-sites": "6.3.0", "highlight.js": "^9.15.10", "ionicons": "~2.0.1", - "js-cookie": "~2.1.3", + "js-cookie": "^2.2.1", "md5": "~2.2.1", "moment": "~2.19.3", "query-string": "5", diff --git a/yarn.lock b/yarn.lock index 55c6ab1b0..dc205260e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5855,10 +5855,10 @@ js-beautify@^1.6.12, js-beautify@^1.6.14: mkdirp "~0.5.1" nopt "~4.0.1" -js-cookie@~2.1.3: - version "2.1.4" - resolved "https://registry.yarnpkg.com/js-cookie/-/js-cookie-2.1.4.tgz#da4ec503866f149d164cf25f579ef31015025d8d" - integrity sha1-2k7FA4ZvFJ0WTPJfV57zEBUCXY0= +js-cookie@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/js-cookie/-/js-cookie-2.2.1.tgz#69e106dc5d5806894562902aa5baec3744e9b2b8" + integrity sha512-HvdH2LzI/EAZcUwA8+0nKNtWHqS+ZmijLA30RwZA0bo7ToCckjK5MkGhjED9KoRcXO6BaGI3I9UIzSA1FKFPOQ== js-levenshtein@^1.1.3: version "1.1.6"