Chore: Add Facebook app set up documentation (#647)
Co-authored-by: Pranav Raj S <pranavrajs@gmail.com>
@@ -26,3 +26,6 @@ exclude_patterns:
|
|||||||
- "node_modules/**/*"
|
- "node_modules/**/*"
|
||||||
- "lib/tasks/auto_annotate_models.rake"
|
- "lib/tasks/auto_annotate_models.rake"
|
||||||
- "app/test-matchers.js"
|
- "app/test-matchers.js"
|
||||||
|
- "docs/*"
|
||||||
|
- "**/*.md"
|
||||||
|
- "**/*.yml"
|
||||||
27
.env.example
@@ -22,17 +22,6 @@ POSTGRES_PASSWORD=
|
|||||||
RAILS_ENV=development
|
RAILS_ENV=development
|
||||||
RAILS_MAX_THREADS=5
|
RAILS_MAX_THREADS=5
|
||||||
|
|
||||||
#fb app
|
|
||||||
FB_VERIFY_TOKEN=
|
|
||||||
FB_APP_SECRET=
|
|
||||||
FB_APP_ID=
|
|
||||||
|
|
||||||
#twitter app
|
|
||||||
TWITTER_APP_ID=
|
|
||||||
TWITTER_CONSUMER_KEY=
|
|
||||||
TWITTER_CONSUMER_SECRET=
|
|
||||||
TWITTER_ENVIRONMENT=
|
|
||||||
|
|
||||||
#mail
|
#mail
|
||||||
MAILER_SENDER_EMAIL=accounts@chatwoot.com
|
MAILER_SENDER_EMAIL=accounts@chatwoot.com
|
||||||
SMTP_PORT=1025
|
SMTP_PORT=1025
|
||||||
@@ -59,13 +48,25 @@ AWS_REGION=
|
|||||||
SENTRY_DSN=
|
SENTRY_DSN=
|
||||||
|
|
||||||
#Log settings
|
#Log settings
|
||||||
LOG_LEVEL=
|
LOG_LEVEL=info
|
||||||
LOG_SIZE=
|
LOG_SIZE=500
|
||||||
|
|
||||||
# Credentials to access sidekiq dashboard in production
|
# Credentials to access sidekiq dashboard in production
|
||||||
SIDEKIQ_AUTH_USERNAME=
|
SIDEKIQ_AUTH_USERNAME=
|
||||||
SIDEKIQ_AUTH_PASSWORD=
|
SIDEKIQ_AUTH_PASSWORD=
|
||||||
|
|
||||||
|
### This environment variables are only required if you are setting up social media channels
|
||||||
|
#facebook
|
||||||
|
FB_VERIFY_TOKEN=
|
||||||
|
FB_APP_SECRET=
|
||||||
|
FB_APP_ID=
|
||||||
|
|
||||||
|
#twitter
|
||||||
|
TWITTER_APP_ID=
|
||||||
|
TWITTER_CONSUMER_KEY=
|
||||||
|
TWITTER_CONSUMER_SECRET=
|
||||||
|
TWITTER_ENVIRONMENT=
|
||||||
|
|
||||||
#### This environment variables are only required in hosted version which has billing
|
#### This environment variables are only required in hosted version which has billing
|
||||||
ENABLE_BILLING=
|
ENABLE_BILLING=
|
||||||
|
|
||||||
|
|||||||
@@ -59,6 +59,8 @@ class Api::V1::Accounts::CallbacksController < Api::BaseController
|
|||||||
def long_lived_token(omniauth_token)
|
def long_lived_token(omniauth_token)
|
||||||
koala = Koala::Facebook::OAuth.new(ENV['FB_APP_ID'], ENV['FB_APP_SECRET'])
|
koala = Koala::Facebook::OAuth.new(ENV['FB_APP_ID'], ENV['FB_APP_SECRET'])
|
||||||
koala.exchange_access_token_info(omniauth_token)['access_token']
|
koala.exchange_access_token_info(omniauth_token)['access_token']
|
||||||
|
rescue StandardError => e
|
||||||
|
Rails.logger e
|
||||||
end
|
end
|
||||||
|
|
||||||
def mark_already_existing_facebook_pages(data)
|
def mark_already_existing_facebook_pages(data)
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import ApiClient from '../ApiClient';
|
|||||||
|
|
||||||
class FBChannel extends ApiClient {
|
class FBChannel extends ApiClient {
|
||||||
constructor() {
|
constructor() {
|
||||||
super('facebook_indicators');
|
super('facebook_indicators', { accountScoped: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
markSeen({ inboxId, contactId }) {
|
markSeen({ inboxId, contactId }) {
|
||||||
@@ -22,7 +22,7 @@ class FBChannel extends ApiClient {
|
|||||||
|
|
||||||
create(params) {
|
create(params) {
|
||||||
return axios.post(
|
return axios.post(
|
||||||
`${this.apiVersion}/callbacks/register_facebook_page`,
|
`${this.url.replace(this.resource, '')}callbacks/register_facebook_page`,
|
||||||
params
|
params
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,9 +5,9 @@
|
|||||||
import endPoints from './endPoints';
|
import endPoints from './endPoints';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
fetchFacebookPages(token) {
|
fetchFacebookPages(token, accountId) {
|
||||||
const urlData = endPoints('fetchFacebookPages');
|
const urlData = endPoints('fetchFacebookPages');
|
||||||
urlData.params.omniauth_token = token;
|
urlData.params.omniauth_token = token;
|
||||||
return axios.post(urlData.url, urlData.params);
|
return axios.post(urlData.url(accountId), urlData.params);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -28,7 +28,9 @@ const endPoints = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
fetchFacebookPages: {
|
fetchFacebookPages: {
|
||||||
url: 'api/v1/callbacks/facebook_pages.json',
|
url(accountId) {
|
||||||
|
return `api/v1/accounts/${accountId}/callbacks/facebook_pages.json`;
|
||||||
|
},
|
||||||
params: { omniauth_token: '' },
|
params: { omniauth_token: '' },
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
15
app/javascript/dashboard/api/specs/channel/fbChannel.spec.js
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import fbChannel from '../../channel/fbChannel';
|
||||||
|
import ApiClient from '../../ApiClient';
|
||||||
|
|
||||||
|
describe('#FBChannel', () => {
|
||||||
|
it('creates correct instance', () => {
|
||||||
|
expect(fbChannel).toBeInstanceOf(ApiClient);
|
||||||
|
expect(fbChannel).toHaveProperty('get');
|
||||||
|
expect(fbChannel).toHaveProperty('show');
|
||||||
|
expect(fbChannel).toHaveProperty('create');
|
||||||
|
expect(fbChannel).toHaveProperty('update');
|
||||||
|
expect(fbChannel).toHaveProperty('delete');
|
||||||
|
expect(fbChannel).toHaveProperty('markSeen');
|
||||||
|
expect(fbChannel).toHaveProperty('toggleTyping');
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -68,6 +68,7 @@
|
|||||||
/* global FB */
|
/* global FB */
|
||||||
import { required } from 'vuelidate/lib/validators';
|
import { required } from 'vuelidate/lib/validators';
|
||||||
import LoadingState from 'dashboard/components/widgets/LoadingState';
|
import LoadingState from 'dashboard/components/widgets/LoadingState';
|
||||||
|
import { mapGetters } from 'vuex';
|
||||||
import ChannelApi from '../../../../../api/channels';
|
import ChannelApi from '../../../../../api/channels';
|
||||||
import PageHeader from '../../SettingsSubPageHeader';
|
import PageHeader from '../../SettingsSubPageHeader';
|
||||||
import router from '../../../../index';
|
import router from '../../../../index';
|
||||||
@@ -111,6 +112,12 @@ export default {
|
|||||||
getSelectablePages() {
|
getSelectablePages() {
|
||||||
return this.pageList.filter(item => !item.exists);
|
return this.pageList.filter(item => !item.exists);
|
||||||
},
|
},
|
||||||
|
...mapGetters({
|
||||||
|
currentUser: 'getCurrentUser',
|
||||||
|
}),
|
||||||
|
accountId() {
|
||||||
|
return this.currentUser.account_id;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
created() {
|
created() {
|
||||||
@@ -194,13 +201,20 @@ export default {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
fetchPages(_token) {
|
async fetchPages(_token) {
|
||||||
ChannelApi.fetchFacebookPages(_token)
|
try {
|
||||||
.then(response => {
|
const response = await ChannelApi.fetchFacebookPages(
|
||||||
this.pageList = response.data.data.page_details;
|
_token,
|
||||||
this.user_access_token = response.data.data.user_access_token;
|
this.accountId
|
||||||
})
|
);
|
||||||
.catch();
|
const {
|
||||||
|
data: { data },
|
||||||
|
} = response;
|
||||||
|
this.pageList = data.page_details;
|
||||||
|
this.user_access_token = data.user_access_token;
|
||||||
|
} catch (error) {
|
||||||
|
// Ignore error
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
channelParams() {
|
channelParams() {
|
||||||
|
|||||||
@@ -30,6 +30,18 @@ class Attachment < ApplicationRecord
|
|||||||
base_data.merge(file_metadata)
|
base_data.merge(file_metadata)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def file_url
|
||||||
|
file.attached? ? url_for(file) : ''
|
||||||
|
end
|
||||||
|
|
||||||
|
def thumb_url
|
||||||
|
if file.attached? && file.representable?
|
||||||
|
url_for(file.representation(resize: '250x250'))
|
||||||
|
else
|
||||||
|
''
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def file_metadata
|
def file_metadata
|
||||||
@@ -64,16 +76,4 @@ class Attachment < ApplicationRecord
|
|||||||
account_id: account_id
|
account_id: account_id
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
def file_url
|
|
||||||
file.attached? ? url_for(file) : ''
|
|
||||||
end
|
|
||||||
|
|
||||||
def thumb_url
|
|
||||||
if file.attached? && file.representable?
|
|
||||||
url_for(file.representation(resize: '250x250'))
|
|
||||||
else
|
|
||||||
''
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -32,13 +32,35 @@ class Facebook::SendReplyService
|
|||||||
# end
|
# end
|
||||||
# end
|
# end
|
||||||
|
|
||||||
def fb_message_params
|
def fb_text_message_params
|
||||||
{
|
{
|
||||||
recipient: { id: contact.get_source_id(inbox.id) },
|
recipient: { id: contact.get_source_id(inbox.id) },
|
||||||
message: { text: message.content }
|
message: { text: message.content }
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def fb_attachment_message_params
|
||||||
|
{
|
||||||
|
recipient: { id: contact.get_source_id(inbox.id) },
|
||||||
|
message: {
|
||||||
|
attachment: {
|
||||||
|
type: 'image',
|
||||||
|
payload: {
|
||||||
|
url: message.attachment.file_url
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
def fb_message_params
|
||||||
|
if message.attachment.blank?
|
||||||
|
fb_text_message_params
|
||||||
|
else
|
||||||
|
fb_attachment_message_params
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def delivery_params
|
def delivery_params
|
||||||
if twenty_four_hour_window_over?
|
if twenty_four_hour_window_over?
|
||||||
fb_message_params.merge(tag: 'ISSUE_RESOLUTION')
|
fb_message_params.merge(tag: 'ISSUE_RESOLUTION')
|
||||||
|
|||||||
40
docs/channels/facebook-channel.md
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
---
|
||||||
|
path: "/docs/channels/facebook"
|
||||||
|
title: "How to create Facebook channel?"
|
||||||
|
---
|
||||||
|
|
||||||
|
If you are using self-hosted Chatwoot installation, please configure the Facebook app as described in the [guide to setup Facebook app](/docs/facebook-setup)
|
||||||
|
|
||||||
|
**Step 1**. Click on "Add Inbox" button from Settings > Inboxes page.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
**Step 2**. Click on "Facebook" icon.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
**Step 3**. Click on Facebook login button. It will open a new window for you to login.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
**Step 4**. Authenticate with Facebook and select the page you want connect, enable all permissions shown in the list, otherwise the app might not work.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
**Step 5**. "Add agents" to your Facebook inbox.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
**Step 6**. Hooray! You have sucessfully created a Facebook inbox. Whenever a customer sends a message to your Facebook page, you will be able to see it here and manage it.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
**Step 7**. If you want to update the agents who have access to the inbox, you can go to Settings > Inboxes.
|
||||||
|
|
||||||
|

|
||||||
BIN
docs/channels/images/facebook/add_agents.png
Normal file
|
After Width: | Height: | Size: 842 KiB |
BIN
docs/channels/images/facebook/finish_inbox.png
Normal file
|
After Width: | Height: | Size: 800 KiB |
BIN
docs/channels/images/facebook/inbox_create.png
Normal file
|
After Width: | Height: | Size: 850 KiB |
BIN
docs/channels/images/facebook/inbox_settings.png
Normal file
|
After Width: | Height: | Size: 709 KiB |
BIN
docs/channels/images/facebook/link_account.png
Normal file
|
After Width: | Height: | Size: 492 KiB |
BIN
docs/channels/images/facebook/list_of_channels.png
Normal file
|
After Width: | Height: | Size: 873 KiB |
BIN
docs/channels/images/facebook/list_of_pages.png
Normal file
|
After Width: | Height: | Size: 348 KiB |
BIN
docs/channels/images/facebook/login_with_facebook.png
Normal file
|
After Width: | Height: | Size: 815 KiB |
BIN
docs/channels/images/facebook/permissions.png
Normal file
|
After Width: | Height: | Size: 552 KiB |
BIN
docs/channels/images/facebook/select_page.png
Normal file
|
After Width: | Height: | Size: 821 KiB |
65
docs/development/project-setup/facebook-channel-setup.md
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
---
|
||||||
|
path: "/docs/facebook-setup"
|
||||||
|
title: "Setting Up Facebook"
|
||||||
|
---
|
||||||
|
|
||||||
|
### Register A Facebook App
|
||||||
|
|
||||||
|
To use Facebook Channel, you have to create an Facebook app in developer portal. You can find more details about creating Facebook channels [here](https://developers.facebook.com/docs/apps/#register)
|
||||||
|
|
||||||
|
Once you register your Facebook App, you will have to obtain the `App Id` and `App Secret` . These values will be available in the app settings and will be required while setting up Chatwoot environment variables.
|
||||||
|
|
||||||
|
### Configure the Facebook App
|
||||||
|
|
||||||
|
1) In the app settings add your `Chatwoot installation url` as your app domain.
|
||||||
|
2) In the products section in your app settings page, Add Messenger
|
||||||
|
3) Go to the Messenger settings and configure the call Back URL with `{your_chatwoot_url}/bot`
|
||||||
|
4) Configure a `verify token`, you will need this value for configuring the chatwoot environment variables
|
||||||
|
5) You might have to add a Facebook page to your `Access Tokens` section in your Messenger settings page if your app is still in development.
|
||||||
|
6) You will also have to add your Facebook page to webhooks sections in your messenger settings with all the webhook events checked.
|
||||||
|
|
||||||
|
### Configuring the Environment Variables in Chatwoot
|
||||||
|
|
||||||
|
Configure the following Chatwoot environment variables with the values you have obtained during the facebook app setup.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
FB_VERIFY_TOKEN=
|
||||||
|
FB_APP_SECRET=
|
||||||
|
FB_APP_ID=
|
||||||
|
```
|
||||||
|
|
||||||
|
### Things to note before going into production.
|
||||||
|
|
||||||
|
Before you can start using your Facebook app in production, you will have to get it verified by Facebook. Refer the [docs](https://developers.facebook.com/docs/apps/review/) on getting your app verified.
|
||||||
|
|
||||||
|
### Developing or Testing Facebook Integration in You Local
|
||||||
|
|
||||||
|
Install [ngrok](https://ngrok.com/docs) on your machine. This will be required since Facebook Messenger API's will only communicate via https.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
brew cask install ngrok
|
||||||
|
```
|
||||||
|
|
||||||
|
Configure ngrok to route to your Rails server port.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ngrok http 3000
|
||||||
|
```
|
||||||
|
|
||||||
|
Go to Facebook developers page and navigate into your app settings. In the app settings, add `localhost` as your app domain.
|
||||||
|
In the Messenger settings page, configure the callback url with the following value.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
{your_ngrok_url}/bot
|
||||||
|
```
|
||||||
|
|
||||||
|
Update verify token in your Chatwoot environment variables.
|
||||||
|
|
||||||
|
You will also have to add a Facebook page to your `Access Tokens` section in your Messenger settings page.
|
||||||
|
Restart the Chatwoot local server. Your Chatwoot setup will be ready to receive Facebook messages.
|
||||||
|
|
||||||
|
### Test your local Setup
|
||||||
|
|
||||||
|
1. After finishing the set up above, [create a Facebook inbox](/docs/channels/facebook) after logging in to your Chatwoot Installation.
|
||||||
|
2. Send a message to your page from Facebook.
|
||||||
|
3. Wait and confirm incoming requests to `/bot` endpoint in your ngrok screen.
|
||||||
@@ -46,6 +46,15 @@ describe Facebook::SendReplyService do
|
|||||||
create(:message, message_type: 'outgoing', inbox: facebook_inbox, account: account, conversation: conversation)
|
create(:message, message_type: 'outgoing', inbox: facebook_inbox, account: account, conversation: conversation)
|
||||||
expect(bot).to have_received(:deliver)
|
expect(bot).to have_received(:deliver)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it 'if message with attachment is sent from chatwoot and is outgoing' do
|
||||||
|
create(:message, message_type: :incoming, inbox: facebook_inbox, account: account, conversation: conversation)
|
||||||
|
message = build(:message, message_type: 'outgoing', inbox: facebook_inbox, account: account, conversation: conversation)
|
||||||
|
message.attachment = Attachment.new(account_id: message.account_id, file_type: :image)
|
||||||
|
message.attachment.file.attach(io: File.open(Rails.root.join('spec/assets/avatar.png')), filename: 'avatar.png', content_type: 'image/png')
|
||||||
|
message.save!
|
||||||
|
expect(bot).to have_received(:deliver)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||