Merge branch 'develop' into feat/new-auth-screens

This commit is contained in:
Sivin Varghese 2022-05-09 10:15:40 +05:30 committed by GitHub
commit 2e50ba137b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
44 changed files with 482 additions and 78 deletions

View file

@ -161,13 +161,15 @@ USE_INBOX_AVATAR_FOR_BOT=true
## NewRelic
# https://docs.newrelic.com/docs/agents/ruby-agent/configuration/ruby-agent-configuration/
# NEW_RELIC_LICENSE_KEY=
# Set this to true to allow newrelic apm to send logs.
# This is turned off by default.
# NEW_RELIC_APPLICATION_LOGGING_ENABLED=
## Datadog
## https://github.com/DataDog/dd-trace-rb/blob/master/docs/GettingStarted.md#environment-variables
# DD_TRACE_AGENT_URL=
## IP look up configuration
## ref https://github.com/alexreisner/geocoder/blob/master/README_API_GUIDE.md
## works only on accounts with ip look up feature enabled

View file

@ -376,7 +376,7 @@ GEM
net-http-persistent (4.0.1)
connection_pool (~> 2.2)
netrc (0.11.0)
newrelic_rpm (8.4.0)
newrelic_rpm (8.7.0)
nio4r (2.5.8)
nokogiri (1.13.4)
mini_portile2 (~> 2.8.0)

View file

@ -15,11 +15,10 @@ class ContactBuilder
end
def create_contact_inbox(contact)
::ContactInbox.create!(
::ContactInbox.create_with(hmac_verified: hmac_verified || false).find_or_create_by!(
contact_id: contact.id,
inbox_id: inbox.id,
source_id: source_id,
hmac_verified: hmac_verified || false
source_id: source_id
)
end

View file

@ -43,7 +43,7 @@ class Messages::Facebook::MessageBuilder < Messages::Messenger::MessageBuilder
return if contact.present?
@contact = Contact.create!(contact_params.except(:remote_avatar_url))
@contact_inbox = ContactInbox.create(contact: contact, inbox: @inbox, source_id: @sender_id)
@contact_inbox = ContactInbox.find_or_create_by!(contact: contact, inbox: @inbox, source_id: @sender_id)
end
def build_message

View file

@ -45,6 +45,8 @@ class RoomChannel < ApplicationCable::Channel
end
def current_account
return if current_user.blank?
@current_account ||= if @current_user.is_a? Contact
@current_user.account
else

View file

@ -7,7 +7,6 @@ class Api::V1::Accounts::Channels::TwilioChannelsController < Api::V1::Accounts:
build_inbox
setup_webhooks if @twilio_channel.sms?
rescue StandardError => e
Sentry.capture_exception(e)
render_could_not_create_error(e.message)
end
end

View file

@ -5,7 +5,7 @@ class Api::V1::Accounts::Conversations::BaseController < Api::V1::Accounts::Base
private
def conversation
@conversation ||= Current.account.conversations.find_by(display_id: params[:conversation_id])
@conversation ||= Current.account.conversations.find_by!(display_id: params[:conversation_id])
authorize @conversation.inbox, :show?
end
end

View file

@ -22,7 +22,7 @@ class MessageFinder
def current_messages
if @params[:before].present?
messages.reorder('created_at desc').where('id < ?', @params[:before]).limit(20).reverse
messages.reorder('created_at desc').where('id < ?', @params[:before].to_i).limit(20).reverse
else
messages.reorder('created_at desc').limit(20).reverse
end

View file

@ -19,6 +19,7 @@
:menu-config="activeSecondaryMenu"
:current-role="currentRole"
@add-label="showAddLabelPopup"
@toggle-accounts="toggleAccountModal"
/>
</aside>
</template>

View file

@ -1,14 +1,34 @@
<template>
<div v-if="showShowCurrentAccountContext" class="account-context--group">
<div
v-if="showShowCurrentAccountContext"
class="account-context--group"
@mouseover="setShowSwitch"
@mouseleave="resetShowSwitch"
>
{{ $t('SIDEBAR.CURRENTLY_VIEWING_ACCOUNT') }}
<p class="account-context--name text-ellipsis">
{{ account.name }}
</p>
<transition name="fade">
<div v-if="showSwitchButton" class="account-context--switch-group">
<woot-button
variant="clear"
icon="arrow-swap"
class="cursor-pointer"
@click="$emit('toggle-accounts')"
>
{{ $t('SIDEBAR.SWITCH') }}
</woot-button>
</div>
</transition>
</div>
</template>
<script>
import { mapGetters } from 'vuex';
export default {
data() {
return { showSwitchButton: false };
},
computed: {
...mapGetters({
account: 'getCurrentAccount',
@ -18,6 +38,14 @@ export default {
return this.userAccounts.length > 1 && this.account.name;
},
},
methods: {
setShowSwitch() {
this.showSwitchButton = true;
},
resetShowSwitch() {
this.showSwitchButton = false;
},
},
};
</script>
<style scoped lang="scss">
@ -27,10 +55,49 @@ export default {
font-size: var(--font-size-mini);
padding: var(--space-small);
margin-bottom: var(--space-small);
width: 100%;
position: relative;
&:hover {
background: var(--b-100);
}
.account-context--name {
font-weight: var(--font-weight-medium);
margin-bottom: 0;
}
}
.account-context--switch-group {
--overlay-shadow: linear-gradient(
to right,
rgba(255, 255, 255, 0) 0%,
rgba(255, 255, 255, 1) 50%
);
align-items: center;
background-image: var(--overlay-shadow);
border-top-left-radius: 0;
border-top-right-radius: var(--border-radius-normal);
border-bottom-left-radius: 0;
border-bottom-right-radius: var(--border-radius-normal);
display: flex;
height: 100%;
justify-content: end;
opacity: 1;
position: absolute;
right: 0;
top: 0;
width: 100%;
}
.fade-enter-active,
.fade-leave-active {
transition: opacity 300ms ease;
}
.fade-enter,
.fade-leave-to {
opacity: 0;
}
</style>

View file

@ -1,6 +1,6 @@
<template>
<div v-if="hasSecondaryMenu" class="main-nav secondary-menu">
<account-context />
<account-context @toggle-accounts="toggleAccountModal" />
<transition-group name="menu-list" tag="ul" class="menu vertical">
<secondary-nav-item
v-for="menuItem in accessibleMenuItems"
@ -224,6 +224,9 @@ export default {
showAddLabelPopup() {
this.$emit('add-label');
},
toggleAccountModal() {
this.$emit('toggle-accounts');
},
},
};
</script>

View file

@ -17,12 +17,8 @@ export default {
value: { type: Boolean, default: false },
},
methods: {
onClick(event) {
if (event.pointerId === -1) {
event.preventDefault();
} else {
this.$emit('input', !this.value);
}
onClick() {
this.$emit('input', !this.value);
},
},
};

View file

@ -147,6 +147,7 @@
},
"SIDEBAR": {
"CURRENTLY_VIEWING_ACCOUNT": "Currently viewing:",
"SWITCH": "Switch",
"CONVERSATIONS": "Conversations",
"ALL_CONVERSATIONS": "All Conversations",
"MENTIONED_CONVERSATIONS": "Mentions",

View file

@ -1,6 +1,6 @@
<template>
<draggable v-model="preChatFields" tag="tbody">
<tr v-for="(item, index) in preChatFields" :key="index">
<draggable v-model="preChatFieldOptions" tag="tbody" @end="onDragEnd">
<tr v-for="(item, index) in preChatFieldOptions" :key="index">
<td class="pre-chat-field"><fluent-icon icon="drag" /></td>
<td class="pre-chat-field">
<woot-switch
@ -51,15 +51,27 @@ export default {
type: Array,
default: () => [],
},
handlePreChatFieldOptions: {
type: Function,
default: () => {},
},
data() {
return {
preChatFieldOptions: this.preChatFields,
};
},
watch: {
preChatFields() {
this.preChatFieldOptions = this.preChatFields;
},
},
methods: {
isFieldEditable(item) {
return !!standardFieldKeys[item.name] || !item.enabled;
},
handlePreChatFieldOptions(event, type, item) {
this.$emit('update', event, type, item);
},
onDragEnd() {
this.$emit('drag-end', this.preChatFieldOptions);
},
},
};
</script>

View file

@ -26,8 +26,8 @@
"
/>
</label>
<label class="medium-8 columns">
{{ $t('INBOX_MGMT.PRE_CHAT_FORM.SET_FIELDS') }}
<div class="medium-8 columns">
<label>{{ $t('INBOX_MGMT.PRE_CHAT_FORM.SET_FIELDS') }}</label>
<table class="table table-striped w-full">
<thead class="thead-dark">
<tr>
@ -58,10 +58,11 @@
</thead>
<pre-chat-fields
:pre-chat-fields="preChatFields"
:handle-pre-chat-field-options="handlePreChatFieldOptions"
@update="handlePreChatFieldOptions"
@drag-end="changePreChatFieldFieldsOrder"
/>
</table>
</label>
</div>
</div>
<woot-submit-button
@ -138,6 +139,10 @@ export default {
});
},
changePreChatFieldFieldsOrder(updatedPreChatFieldOptions) {
this.preChatFields = updatedPreChatFieldOptions;
},
async updateInbox() {
try {
const payload = {

View file

@ -10,7 +10,8 @@ import {
getUserCookieName,
hasUserKeys,
} from '../sdk/cookieHelpers';
import { addClass, removeClass } from '../sdk/DOMHelpers';
import { SDK_SET_BUBBLE_VISIBILITY } from 'shared/constants/sharedFrameEvents';
const runSDK = ({ baseUrl, websiteToken }) => {
if (window.$chatwoot) {
return;
@ -36,6 +37,23 @@ const runSDK = ({ baseUrl, websiteToken }) => {
IFrameHelper.events.toggleBubble(state);
},
toggleBubbleVisibility(visibility) {
let widgetElm = document.querySelector('.woot--bubble-holder');
let widgetHolder = document.querySelector('.woot-widget-holder');
if (visibility === 'hide') {
addClass(widgetHolder, 'woot-widget--without-bubble');
addClass(widgetElm, 'woot-hidden');
window.$chatwoot.hideMessageBubble = true;
} else if (visibility === 'show') {
removeClass(widgetElm, 'woot-hidden');
removeClass(widgetHolder, 'woot-widget--without-bubble');
window.$chatwoot.hideMessageBubble = false;
}
IFrameHelper.sendMessage(SDK_SET_BUBBLE_VISIBILITY, {
hideMessageBubble: window.$chatwoot.hideMessageBubble,
});
},
popoutChatWindow() {
IFrameHelper.events.popoutChatWindow({
baseUrl: window.$chatwoot.baseUrl,

View file

@ -121,7 +121,7 @@ export const IFrameHelper = {
setupAudioListeners: () => {
const { baseUrl = '' } = window.$chatwoot;
getAlertAudio(baseUrl).then(() =>
getAlertAudio(baseUrl, 'widget').then(() =>
initOnEvents.forEach(event => {
document.removeEventListener(
event,
@ -175,9 +175,6 @@ export const IFrameHelper = {
},
setBubbleLabel(message) {
if (window.$chatwoot.hideMessageBubble) {
return;
}
setBubbleText(window.$chatwoot.launcherTitle || message.label);
},
@ -263,33 +260,32 @@ export const IFrameHelper = {
if (IFrameHelper.getBubbleHolder().length) {
return;
}
createBubbleHolder();
createBubbleHolder(window.$chatwoot.hideMessageBubble);
onLocationChangeListener();
if (!window.$chatwoot.hideMessageBubble) {
let className = 'woot-widget-bubble';
let closeBtnClassName = `woot-elements--${window.$chatwoot.position} woot-widget-bubble woot--close woot--hide`;
if (isFlatWidgetStyle(window.$chatwoot.widgetStyle)) {
className += ' woot-widget-bubble--flat';
closeBtnClassName += ' woot-widget-bubble--flat';
}
let className = 'woot-widget-bubble';
let closeBtnClassName = `woot-elements--${window.$chatwoot.position} woot-widget-bubble woot--close woot--hide`;
const chatIcon = createBubbleIcon({
className,
src: bubbleImg,
target: chatBubble,
});
addClass(closeBubble, closeBtnClassName);
chatIcon.style.background = widgetColor;
closeBubble.style.background = widgetColor;
bubbleHolder.appendChild(chatIcon);
bubbleHolder.appendChild(closeBubble);
bubbleHolder.appendChild(createNotificationBubble());
onClickChatBubble();
if (isFlatWidgetStyle(window.$chatwoot.widgetStyle)) {
className += ' woot-widget-bubble--flat';
closeBtnClassName += ' woot-widget-bubble--flat';
}
const chatIcon = createBubbleIcon({
className,
src: bubbleImg,
target: chatBubble,
});
addClass(closeBubble, closeBtnClassName);
chatIcon.style.background = widgetColor;
closeBubble.style.background = widgetColor;
bubbleHolder.appendChild(chatIcon);
bubbleHolder.appendChild(closeBubble);
bubbleHolder.appendChild(createNotificationBubble());
onClickChatBubble();
},
toggleCloseButton: () => {
let isMobile = false;

View file

@ -39,7 +39,10 @@ export const createBubbleIcon = ({ className, src, target }) => {
return target;
};
export const createBubbleHolder = () => {
export const createBubbleHolder = hideMessageBubble => {
if (hideMessageBubble) {
addClass(bubbleHolder, 'woot-hidden');
}
addClass(bubbleHolder, 'woot--bubble-holder');
body.appendChild(bubbleHolder);
};

View file

@ -227,4 +227,8 @@ export const SDK_CSS = `
width: 400px !important;
}
}
.woot-hidden {
display: none !important;
}
`;

View file

@ -0,0 +1 @@
export const SDK_SET_BUBBLE_VISIBILITY = 'sdk-set-bubble-visibility';

View file

@ -4,7 +4,7 @@ import { IFrameHelper } from 'widget/helpers/utils';
import { showBadgeOnFavicon } from './faviconHelper';
export const initOnEvents = ['click', 'touchstart', 'keypress', 'keydown'];
export const getAlertAudio = async (baseUrl = '') => {
export const getAlertAudio = async (baseUrl = '', type = 'dashboard') => {
const audioCtx = new (window.AudioContext || window.webkitAudioContext)();
const playsound = audioBuffer => {
window.playAudioAlert = () => {
@ -17,7 +17,7 @@ export const getAlertAudio = async (baseUrl = '') => {
};
try {
const resourceUrl = `${baseUrl}/dashboard/audios/ding.mp3`;
const resourceUrl = `${baseUrl}/audio/${type}/ding.mp3`;
const audioRequest = new Request(resourceUrl);
fetch(audioRequest)

View file

@ -38,6 +38,9 @@ import {
ON_CAMPAIGN_MESSAGE_CLICK,
ON_UNREAD_MESSAGE_CLICK,
} from './constants/widgetBusEvents';
import { SDK_SET_BUBBLE_VISIBILITY } from '../shared/constants/sharedFrameEvents';
export default {
name: 'App',
components: {
@ -103,6 +106,7 @@ export default {
'setAppConfig',
'setReferrerHost',
'setWidgetColor',
'setBubbleVisibility',
]),
...mapActions('conversation', ['fetchOldConversations', 'setUserLastSeen']),
...mapActions('campaign', [
@ -285,6 +289,8 @@ export default {
if (!message.isOpen) {
this.resetCampaign();
}
} else if (message.event === SDK_SET_BUBBLE_VISIBILITY) {
this.setBubbleVisibility(message.hideMessageBubble);
}
});
},

View file

@ -1,4 +1,5 @@
import {
SET_BUBBLE_VISIBILITY,
SET_REFERRER_HOST,
SET_WIDGET_APP_CONFIG,
SET_WIDGET_COLOR,
@ -57,6 +58,9 @@ export const actions = {
setReferrerHost({ commit }, referrerHost) {
commit(SET_REFERRER_HOST, referrerHost);
},
setBubbleVisibility({ commit }, hideMessageBubble) {
commit(SET_BUBBLE_VISIBILITY, hideMessageBubble);
},
};
export const mutations = {
@ -76,6 +80,9 @@ export const mutations = {
[SET_REFERRER_HOST]($state, referrerHost) {
$state.referrerHost = referrerHost;
},
[SET_BUBBLE_VISIBILITY]($state, hideMessageBubble) {
$state.hideMessageBubble = hideMessageBubble;
},
};
export default {

View file

@ -11,6 +11,13 @@ describe('#actions', () => {
});
});
describe('#setBubbleVisibility', () => {
it('creates actions properly', () => {
actions.setBubbleVisibility({ commit }, false);
expect(commit.mock.calls).toEqual([['SET_BUBBLE_VISIBILITY', false]]);
});
});
describe('#setWidgetColor', () => {
it('creates actions properly', () => {
actions.setWidgetColor({ commit }, '#eaeaea');

View file

@ -9,6 +9,14 @@ describe('#mutations', () => {
});
});
describe('#SET_BUBBLE_VISIBILITY', () => {
it('sets bubble visibility properly', () => {
const state = { hideMessageBubble: false };
mutations.SET_BUBBLE_VISIBILITY(state, true);
expect(state.hideMessageBubble).toEqual(true);
});
});
describe('#SET_WIDGET_COLOR', () => {
it('sets widget color properly', () => {
const state = { widgetColor: '' };

View file

@ -5,3 +5,4 @@ export const SET_WIDGET_COLOR = 'SET_WIDGET_COLOR';
export const UPDATE_CONVERSATION_ATTRIBUTES = 'UPDATE_CONVERSATION_ATTRIBUTES';
export const TOGGLE_WIDGET_OPEN = 'TOGGLE_WIDGET_OPEN';
export const SET_REFERRER_HOST = 'SET_REFERRER_HOST';
export const SET_BUBBLE_VISIBILITY = 'SET_BUBBLE_VISIBILITY';

View file

@ -9,7 +9,7 @@ class HookJob < ApplicationJob
process_dialogflow_integration(hook, event_name, event_data)
end
rescue StandardError => e
Sentry.capture_exception(e)
Rails.logger.error e
end
private

View file

@ -13,7 +13,8 @@
#
# Indexes
#
# index_channel_twilio_sms_on_account_id_and_phone_number (account_id,phone_number) UNIQUE
# index_channel_twilio_sms_on_account_sid_and_phone_number (account_sid,phone_number) UNIQUE
# index_channel_twilio_sms_on_phone_number (phone_number) UNIQUE
#
class Channel::TwilioSms < ApplicationRecord
@ -23,7 +24,9 @@ class Channel::TwilioSms < ApplicationRecord
validates :account_sid, presence: true
validates :auth_token, presence: true
validates :phone_number, uniqueness: { scope: :account_id }, presence: true
# NOTE: allowing nil for future when we suppor twilio messaging services
# https://github.com/chatwoot/chatwoot/pull/4242
validates :phone_number, uniqueness: true, allow_nil: true
enum medium: { sms: 0, whatsapp: 1 }

View file

@ -32,6 +32,6 @@ class InboxMember < ApplicationRecord
end
def remove_agent_from_round_robin
::RoundRobin::ManageService.new(inbox: inbox).remove_agent_from_queue(user_id)
::RoundRobin::ManageService.new(inbox: inbox).remove_agent_from_queue(user_id) if inbox.present?
end
end

View file

@ -82,7 +82,7 @@ Rails.application.configure do
allow do
origins '*'
resource '/packs/*', headers: :any, methods: [:get, :options]
resource '/dashboard/audios/ding.mp3', headers: :any, methods: [:get, :options]
resource '/audio/*', headers: :any, methods: [:get, :options]
resource '*', headers: :any, methods: :any, expose: ['access-token', 'client', 'uid', 'expiry']
end
end

View file

@ -121,7 +121,7 @@ Rails.application.configure do
allow do
origins '*'
resource '/packs/*', headers: :any, methods: [:get, :options]
resource '/dashboard/audios/ding.mp3', headers: :any, methods: [:get, :options]
resource '/audio/*', headers: :any, methods: [:get, :options]
if ActiveModel::Type::Boolean.new.cast(ENV.fetch('CW_API_ONLY_SERVER', false))
resource '*', headers: :any, methods: :any, expose: ['access-token', 'client', 'uid', 'expiry']
end

View file

@ -87,7 +87,7 @@ Rails.application.configure do
allow do
origins '*'
resource '/packs/*', headers: :any, methods: [:get, :options]
resource '/dashboard/audios/ding.mp3', headers: :any, methods: [:get, :options]
resource '/audio/*', headers: :any, methods: [:get, :options]
end
end
end

View file

@ -58,7 +58,7 @@ Rails.application.configure do
allow do
origins '*'
resource '/packs/*', headers: :any, methods: [:get, :options]
resource '/dashboard/audios/ding.mp3', headers: :any, methods: [:get, :options]
resource '/audio/*', headers: :any, methods: [:get, :options]
end
end
end

View file

@ -24,6 +24,24 @@ common: &default_settings
# Logging level for log/newrelic_agent.log
log_level: <%= ENV.fetch('NEW_RELIC_LOG_LEVEL', 'info') %>
application_logging:
# If `true`, all logging-related features for the agent can be enabled or disabled
# independently. If `false`, all logging-related features are disabled.
enabled: <%= ENV.fetch('NEW_RELIC_APPLICATION_LOGGING_ENABLED', false) %>
forwarding:
# If `true`, the agent captures log records emitted by this application.
enabled: true
# Defines the maximum number of log records to buffer in memory at a time.
max_samples_stored: 30000
metrics:
# If `true`, the agent captures metrics related to logging for this application.
enabled: true
local_decorating:
# If `true`, the agent decorates logs with metadata to link to entities, hosts, traces, and spans.
# This requires a log forwarder to send your log files to New Relic.
# This should not be used when forwarding is enabled.
enabled: false
# Environment-specific settings are in this section.
# RAILS_ENV or RACK_ENV (as appropriate) is used to determine the environment.

View file

@ -25,14 +25,14 @@ pidfile ENV.fetch('PIDFILE') { 'tmp/pids/server.pid' }
# Workers do not work on JRuby or Windows (both of which do not support
# processes).
#
# workers ENV.fetch("WEB_CONCURRENCY") { 2 }
workers ENV.fetch('WEB_CONCURRENCY', 1)
# Use the `preload_app!` method when specifying a `workers` number.
# This directive tells Puma to first boot the application and load code
# before forking the application. This takes advantage of Copy On Write
# process behavior so workers use less memory.
#
# preload_app!
preload_app!
# Allow puma to be restarted by `rails restart` command.
plugin :tmp_restart

View file

@ -16,6 +16,6 @@ trigger_scheduled_items_job:
# executed At every 5th minute..
trigger_imap_email_inboxes_job:
cron: '*/1 * * * *'
cron: '*/5 * * * *'
class: 'Inboxes::FetchImapEmailInboxesJob'
queue: scheduled_jobs

View file

@ -0,0 +1,39 @@
class AddAccountSidUniqueIndexToChannelTwilioSms < ActiveRecord::Migration[6.1]
def change
clear_possible_duplicates
has_duplicates = Channel::TwilioSms.select(:phone_number).group(:phone_number).having('count(*) > 1').exists?
if has_duplicates
raise <<~ERR.squish
ERROR: You have duplicate values for phone_number in "channel_twilio_sms".
This causes Twilio account lookup to behave unpredictably. Please eliminate the duplicates
and then re-run this migration.
ERR
end
remove_index :channel_twilio_sms, [:account_id, :phone_number], unique: true
add_index :channel_twilio_sms, :phone_number, unique: true
# since look ups are done via account_sid + phone_number, we need to add a unique index
add_index :channel_twilio_sms, [:account_sid, :phone_number], unique: true
end
def clear_possible_duplicates
# based on the look up in saas it seems like only the first inbox is used in case of duplicates,
# so lets try to clear our inboxes with out conversations
duplicate_phone_numbers = Channel::TwilioSms.select(:phone_number).group(:phone_number).having('count(*) > 1').collect(&:phone_number)
duplicate_phone_numbers.each do |phone_number|
# we are skipping the first inbox that was created
Channel::TwilioSms.where(phone_number: phone_number).drop(1).each do |channel|
inbox = channel.inbox
# skip inboxes with conversations
next if inbox.conversations.count.positive?
inbox.destroy
end
end
# clear the accounts created with twilio sandbox whatsapp number
# Channel::TwilioSms.where(phone_number: 'whatsapp:+14155238886').each { |channel| channel.inbox.destroy }
end
end

View file

@ -257,7 +257,8 @@ ActiveRecord::Schema.define(version: 2022_04_28_101325) do
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.integer "medium", default: 0
t.index ["account_id", "phone_number"], name: "index_channel_twilio_sms_on_account_id_and_phone_number", unique: true
t.index ["account_sid", "phone_number"], name: "index_channel_twilio_sms_on_account_sid_and_phone_number", unique: true
t.index ["phone_number"], name: "index_channel_twilio_sms_on_phone_number", unique: true
end
create_table "channel_twitter_profiles", force: :cascade do |t|

View file

@ -37,7 +37,7 @@ class Integrations::Slack::IncomingMessageBuilder
if message.present?
SUPPORTED_MESSAGE_TYPES.include?(message[:type])
else
params[:event][:files].any?
params.dig(:event, :files).any?
end
end

View file

@ -7,7 +7,7 @@ class Webhooks::Trigger
timeout: 5
)
Rails.logger.info "Performed Request: Code - #{response.code}"
rescue *ExceptionList::REST_CLIENT_EXCEPTIONS => e
rescue *ExceptionList::REST_CLIENT_EXCEPTIONS, URI::InvalidURIError => e
Rails.logger.error "Exception: invalid webhook url #{url} : #{e.message}"
rescue StandardError => e
Rails.logger.error "Exception: invalid webhook url #{url} : #{e.message}"

Binary file not shown.

View file

@ -0,0 +1,20 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe InboxMember do
include ActiveJob::TestHelper
describe '#DestroyAssociationAsyncJob' do
let(:inbox_member) { create(:inbox_member) }
# ref: https://github.com/chatwoot/chatwoot/issues/4616
context 'when parent inbox is destroyed' do
it 'enques and processes DestroyAssociationAsyncJob' do
perform_enqueued_jobs do
inbox_member.inbox.destroy!
end
end
end
end
end

199
yarn.lock
View file

@ -2794,6 +2794,47 @@
dependencies:
"@types/yargs-parser" "*"
"@videojs/http-streaming@2.13.1":
version "2.13.1"
resolved "https://registry.yarnpkg.com/@videojs/http-streaming/-/http-streaming-2.13.1.tgz#b7688d91eec969181430e00868b514b16b3b21b7"
integrity sha512-1x3fkGSPyL0+iaS3/lTvfnPTtfqzfgG+ELQtPPtTvDwqGol9Mx3TNyZwtSTdIufBrqYRn7XybB/3QNMsyjq13A==
dependencies:
"@babel/runtime" "^7.12.5"
"@videojs/vhs-utils" "3.0.4"
aes-decrypter "3.1.2"
global "^4.4.0"
m3u8-parser "4.7.0"
mpd-parser "0.21.0"
mux.js "6.0.1"
video.js "^6 || ^7"
"@videojs/vhs-utils@3.0.4":
version "3.0.4"
resolved "https://registry.yarnpkg.com/@videojs/vhs-utils/-/vhs-utils-3.0.4.tgz#e253eecd8e9318f767e752010d213587f94bb03a"
integrity sha512-hui4zOj2I1kLzDgf8QDVxD3IzrwjS/43KiS8IHQO0OeeSsb4pB/lgNt1NG7Dv0wMQfCccUpMVLGcK618s890Yg==
dependencies:
"@babel/runtime" "^7.12.5"
global "^4.4.0"
url-toolkit "^2.2.1"
"@videojs/vhs-utils@^3.0.0", "@videojs/vhs-utils@^3.0.2", "@videojs/vhs-utils@^3.0.4":
version "3.0.5"
resolved "https://registry.yarnpkg.com/@videojs/vhs-utils/-/vhs-utils-3.0.5.tgz#665ba70d78258ba1ab977364e2fe9f4d4799c46c"
integrity sha512-PKVgdo8/GReqdx512F+ombhS+Bzogiofy1LgAj4tN8PfdBx3HSS7V5WfJotKTqtOWGwVfSWsrYN/t09/DSryrw==
dependencies:
"@babel/runtime" "^7.12.5"
global "^4.4.0"
url-toolkit "^2.2.1"
"@videojs/xhr@2.6.0":
version "2.6.0"
resolved "https://registry.yarnpkg.com/@videojs/xhr/-/xhr-2.6.0.tgz#cd897e0ad54faf497961bcce3fa16dc15a26bb80"
integrity sha512-7J361GiN1tXpm+gd0xz2QWr3xNWBE+rytvo8J3KuggFaLg+U37gZQ2BuPLcnkfGffy2e+ozY70RHC8jt7zjA6Q==
dependencies:
"@babel/runtime" "^7.5.5"
global "~4.4.0"
is-function "^1.0.1"
"@vue/compiler-core@3.0.11":
version "3.0.11"
resolved "https://registry.yarnpkg.com/@vue/compiler-core/-/compiler-core-3.0.11.tgz#5ef579e46d7b336b8735228758d1c2c505aae69a"
@ -3018,6 +3059,11 @@
"@webassemblyjs/wast-parser" "1.9.0"
"@xtuc/long" "4.2.2"
"@xmldom/xmldom@^0.7.2":
version "0.7.5"
resolved "https://registry.yarnpkg.com/@xmldom/xmldom/-/xmldom-0.7.5.tgz#09fa51e356d07d0be200642b0e4f91d8e6dd408d"
integrity sha512-V3BIhmY36fXZ1OtVcI9W+FxQqxVLsPKcNjWigIaa81dLC9IolJl5Mt4Cvhmr0flUnjSpTdrbMTSbXqYqV5dT6A==
"@xtuc/ieee754@^1.2.0":
version "1.2.0"
resolved "https://registry.yarnpkg.com/@xtuc/ieee754/-/ieee754-1.2.0.tgz#eef014a3145ae477a1cbc00cd1e552336dceb790"
@ -3100,6 +3146,16 @@ address@1.1.2, address@^1.0.1:
resolved "https://registry.yarnpkg.com/address/-/address-1.1.2.tgz#bf1116c9c758c51b7a933d296b72c221ed9428b6"
integrity sha512-aT6camzM4xEA54YVJYSqxz1kv4IHnQZRtThJJHhUMRExaU5spC7jX5ugSwTaTgJliIgs4VhZOk7htClvQ/LmRA==
aes-decrypter@3.1.2:
version "3.1.2"
resolved "https://registry.yarnpkg.com/aes-decrypter/-/aes-decrypter-3.1.2.tgz#3545546f8e9f6b878640339a242efe221ba7a7cb"
integrity sha512-42nRwfQuPRj9R1zqZBdoxnaAmnIFyDi0MNyTVhjdFOd8fifXKKRfwIHIZ6AMn1or4x5WONzjwRTbTWcsIQ0O4A==
dependencies:
"@babel/runtime" "^7.12.5"
"@videojs/vhs-utils" "^3.0.0"
global "^4.4.0"
pkcs7 "^1.0.4"
aggregate-error@^3.0.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/aggregate-error/-/aggregate-error-3.1.0.tgz#92670ff50f5359bdb7a3e0d40d0ec30c5737687a"
@ -7333,7 +7389,7 @@ global-prefix@^3.0.0:
kind-of "^6.0.2"
which "^1.3.1"
global@^4.4.0:
global@^4.3.1, global@^4.4.0, global@~4.4.0:
version "4.4.0"
resolved "https://registry.yarnpkg.com/global/-/global-4.4.0.tgz#3e7b105179006a323ed71aafca3e9c57a5cc6406"
integrity sha512-wv/LAoHdRE3BeTGz53FAamhGlPLhlssK45usmGFThIi4XqnBmjKQ16u+RNbP7WvigRZDxUsM0J3gcQ5yicaL0w==
@ -7977,6 +8033,11 @@ indexes-of@^1.0.1:
resolved "https://registry.yarnpkg.com/indexes-of/-/indexes-of-1.0.1.tgz#f30f716c8e2bd346c7b67d3df3915566a7c05607"
integrity sha1-8w9xbI4r00bHtn0985FVZqfAVgc=
individual@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/individual/-/individual-2.0.0.tgz#833b097dad23294e76117a98fb38e0d9ad61bb97"
integrity sha1-gzsJfa0jKU52EXqY+zjg2a1hu5c=
infer-owner@^1.0.3, infer-owner@^1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/infer-owner/-/infer-owner-1.0.4.tgz#c4cefcaa8e51051c2a40ba2ce8a3d27295af9467"
@ -8321,7 +8382,7 @@ is-fullwidth-code-point@^3.0.0:
resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d"
integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==
is-function@^1.0.2:
is-function@^1.0.1, is-function@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/is-function/-/is-function-1.0.2.tgz#4f097f30abf6efadac9833b17ca5dc03f8144e08"
integrity sha512-lw7DUp0aWXYg+CBCN+JKkcE0Q2RayZnSvnZBlwgxHBQhqt5pZNVy4Ri7H9GmmXkdu7LUthszM+Tor1u/2iBcpQ==
@ -9347,6 +9408,11 @@ junk@^3.1.0:
resolved "https://registry.yarnpkg.com/junk/-/junk-3.1.0.tgz#31499098d902b7e98c5d9b9c80f43457a88abfa1"
integrity sha512-pBxcB3LFc8QVgdggvZWyeys+hnrNWg4OcZIU/1X59k5jQdLBlCsYGRQaz234SqoRLTCgMH00fY0xRJH+F9METQ==
keycode@^2.2.0:
version "2.2.1"
resolved "https://registry.yarnpkg.com/keycode/-/keycode-2.2.1.tgz#09c23b2be0611d26117ea2501c2c391a01f39eff"
integrity sha512-Rdgz9Hl9Iv4QKi8b0OlCRQEzp4AgVxyCtz5S/+VIHezDmrDhkp2N2TqBWOLz0/gbeREXOOiI9/4b8BY9uw2vFg==
killable@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/killable/-/killable-1.0.1.tgz#4c8ce441187a061c7474fb87ca08e2a638194892"
@ -9775,6 +9841,15 @@ lru-cache@^6.0.0:
dependencies:
yallist "^4.0.0"
m3u8-parser@4.7.0:
version "4.7.0"
resolved "https://registry.yarnpkg.com/m3u8-parser/-/m3u8-parser-4.7.0.tgz#e01e8ce136098ade1b14ee691ea20fc4dc60abf6"
integrity sha512-48l/OwRyjBm+QhNNigEEcRcgbRvnUjL7rxs597HmW9QSNbyNvt+RcZ9T/d9vxi9A9z7EZrB1POtZYhdRlwYQkQ==
dependencies:
"@babel/runtime" "^7.12.5"
"@videojs/vhs-utils" "^3.0.0"
global "^4.4.0"
magic-string@^0.25.7:
version "0.25.7"
resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.25.7.tgz#3f497d6fd34c669c6798dcb821f2ef31f5445051"
@ -10193,6 +10268,16 @@ move-concurrently@^1.0.1:
rimraf "^2.5.4"
run-queue "^1.0.3"
mpd-parser@0.21.0:
version "0.21.0"
resolved "https://registry.yarnpkg.com/mpd-parser/-/mpd-parser-0.21.0.tgz#c2036cce19522383b93c973180fdd82cd646168e"
integrity sha512-NbpMJ57qQzFmfCiP1pbL7cGMbVTD0X1hqNgL0VYP1wLlZXLf/HtmvQpNkOA1AHkPVeGQng+7/jEtSvNUzV7Gdg==
dependencies:
"@babel/runtime" "^7.12.5"
"@videojs/vhs-utils" "^3.0.2"
"@xmldom/xmldom" "^0.7.2"
global "^4.4.0"
ms@2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
@ -10231,6 +10316,14 @@ mute-stream@0.0.8:
resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.8.tgz#1630c42b2251ff81e2a283de96a5497ea92e5e0d"
integrity sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==
mux.js@6.0.1:
version "6.0.1"
resolved "https://registry.yarnpkg.com/mux.js/-/mux.js-6.0.1.tgz#65ce0f7a961d56c006829d024d772902d28c7755"
integrity sha512-22CHb59rH8pWGcPGW5Og7JngJ9s+z4XuSlYvnxhLuc58cA1WqGDQPzuG8I+sPm1/p0CdgpzVTaKW408k5DNn8w==
dependencies:
"@babel/runtime" "^7.11.2"
global "^4.4.0"
nan@^2.12.1:
version "2.14.2"
resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.2.tgz#f5376400695168f4cc694ac9393d0c9585eeea19"
@ -10683,6 +10776,11 @@ optionator@^0.8.1, optionator@^0.8.3:
type-check "~0.3.2"
word-wrap "~1.2.3"
opus-recorder@^8.0.5:
version "8.0.5"
resolved "https://registry.yarnpkg.com/opus-recorder/-/opus-recorder-8.0.5.tgz#06d3e32e15da57ebc3f57e41b93033475fcb4e3e"
integrity sha512-tBRXc9Btds7i3bVfA7d5rekAlyOcfsivt5vSIXHxRV1Oa+s6iXFW8omZ0Lm3ABWotVcEyKt96iIIUcgbV07YOw==
orderedmap@^1.1.0:
version "1.1.1"
resolved "https://registry.yarnpkg.com/orderedmap/-/orderedmap-1.1.1.tgz#c618e77611b3b21d0fe3edc92586265e0059c789"
@ -11077,6 +11175,13 @@ pirates@^4.0.0, pirates@^4.0.1:
dependencies:
node-modules-regexp "^1.0.0"
pkcs7@^1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/pkcs7/-/pkcs7-1.0.4.tgz#6090b9e71160dabf69209d719cbafa538b00a1cb"
integrity sha512-afRERtHn54AlwaF2/+LFszyAANTCggGilmcmILUzEjvs3XgFZT+xE6+QWQcAGmu4xajy+Xtj7acLOPdx5/eXWQ==
dependencies:
"@babel/runtime" "^7.5.5"
pkg-dir@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-2.0.0.tgz#f6d5d1109e19d63edf428e0bd57e12777615334b"
@ -12757,7 +12862,7 @@ recast@^0.18.1:
private "^0.1.8"
source-map "~0.6.1"
recordrtc@^5.6.2:
recordrtc@>=5.6.2:
version "5.6.2"
resolved "https://registry.yarnpkg.com/recordrtc/-/recordrtc-5.6.2.tgz#48fc214b35084973ccce82c6251198b5742bc327"
integrity sha512-1QNKKNtl7+KcwD1lyOgP3ZlbiJ1d0HtXnypUy7yq49xEERxk31PHvE9RCciDrulPCY7WJ+oz0R9hpNxgsIurGQ==
@ -13192,6 +13297,13 @@ run-queue@^1.0.0, run-queue@^1.0.3:
dependencies:
aproba "^1.1.1"
rust-result@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/rust-result/-/rust-result-1.0.0.tgz#34c75b2e6dc39fe5875e5bdec85b5e0f91536f72"
integrity sha1-NMdbLm3Dn+WHXlveyFteD5FTb3I=
dependencies:
individual "^2.0.0"
rxjs@^6.3.3, rxjs@^6.5.3, rxjs@^6.6.7:
version "6.6.7"
resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.6.7.tgz#90ac018acabf491bf65044235d5863c4dab804c9"
@ -13214,6 +13326,13 @@ safe-buffer@>=5.1.0, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1,
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6"
integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==
safe-json-parse@4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/safe-json-parse/-/safe-json-parse-4.0.0.tgz#7c0f578cfccd12d33a71c0e05413e2eca171eaac"
integrity sha1-fA9XjPzNEtM6ccDgVBPi7KFx6qw=
dependencies:
rust-result "^1.0.0"
safe-regex@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/safe-regex/-/safe-regex-1.1.0.tgz#40a3669f3b077d1e943d44629e157dd48023bf2e"
@ -13315,6 +13434,11 @@ schema-utils@^3.0.0:
ajv "^6.12.5"
ajv-keywords "^3.5.2"
sdp@^3.0.2:
version "3.0.3"
resolved "https://registry.yarnpkg.com/sdp/-/sdp-3.0.3.tgz#669958d54663ea9f4a46cc66518c9d980c52c61e"
integrity sha512-8EkfckS+XZQaPLyChu4ey7PghrdcraCVNpJe2Gfdi2ON1ylQ7OasuKX+b37R9slnRChwIAiQgt+oj8xXGD8x+A==
select-hose@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/select-hose/-/select-hose-2.0.0.tgz#625d8658f865af43ec962bfc376a37359a4994ca"
@ -14872,6 +14996,11 @@ url-parse@^1.4.3, url-parse@^1.5.1:
querystringify "^2.1.1"
requires-port "^1.0.0"
url-toolkit@^2.2.1:
version "2.2.5"
resolved "https://registry.yarnpkg.com/url-toolkit/-/url-toolkit-2.2.5.tgz#58406b18e12c58803e14624df5e374f638b0f607"
integrity sha512-mtN6xk+Nac+oyJ/PrI7tzfmomRVNFIWKUbG8jdYFt52hxbiReFAXIjYskvu64/dvuW71IcB7lV8l0HvZMac6Jg==
url@^0.11.0:
version "0.11.0"
resolved "https://registry.yarnpkg.com/url/-/url-0.11.0.tgz#3838e97cfc60521eb73c525a8e55bfdd9e2e28f1"
@ -15040,6 +15169,55 @@ vfile@^4.0.0:
unist-util-stringify-position "^2.0.0"
vfile-message "^2.0.0"
video.js@>=7.0.5, "video.js@^6 || ^7":
version "7.18.1"
resolved "https://registry.yarnpkg.com/video.js/-/video.js-7.18.1.tgz#d93cd4992710d4d95574a00e7d29a2518f9b30f7"
integrity sha512-mnXdmkVcD5qQdKMZafDjqdhrnKGettZaGSVkExjACiylSB4r2Yt5W1bchsKmjFpfuNfszsMjTUnnoIWSSqoe/Q==
dependencies:
"@babel/runtime" "^7.12.5"
"@videojs/http-streaming" "2.13.1"
"@videojs/vhs-utils" "^3.0.4"
"@videojs/xhr" "2.6.0"
aes-decrypter "3.1.2"
global "^4.4.0"
keycode "^2.2.0"
m3u8-parser "4.7.0"
mpd-parser "0.21.0"
mux.js "6.0.1"
safe-json-parse "4.0.0"
videojs-font "3.2.0"
videojs-vtt.js "^0.15.3"
videojs-font@3.2.0:
version "3.2.0"
resolved "https://registry.yarnpkg.com/videojs-font/-/videojs-font-3.2.0.tgz#212c9d3f4e4ec3fa7345167d64316add35e92232"
integrity sha512-g8vHMKK2/JGorSfqAZQUmYYNnXmfec4MLhwtEFS+mMs2IDY398GLysy6BH6K+aS1KMNu/xWZ8Sue/X/mdQPliA==
videojs-record@^4.5.0:
version "4.5.0"
resolved "https://registry.yarnpkg.com/videojs-record/-/videojs-record-4.5.0.tgz#6156a52e879fecd7b5e371e2ace0c1853133407f"
integrity sha512-p/L6UaEZxCXVqzvH0TYCyUujHbymhqrEiF63KIjOatBxOhG7OVu4RiKEbkOlniBKr2sLoVOmC3vBuk+YBD3CXw==
dependencies:
recordrtc ">=5.6.2"
video.js ">=7.0.5"
videojs-wavesurfer ">=3.7.0"
webrtc-adapter ">=8.0.0"
videojs-vtt.js@^0.15.3:
version "0.15.3"
resolved "https://registry.yarnpkg.com/videojs-vtt.js/-/videojs-vtt.js-0.15.3.tgz#84260393b79487fcf195d9372f812d7fab83a993"
integrity sha512-5FvVsICuMRx6Hd7H/Y9s9GDeEtYcXQWzGMS+sl4UX3t/zoHp3y+isSfIPRochnTH7h+Bh1ILyC639xy9Z6kPag==
dependencies:
global "^4.3.1"
videojs-wavesurfer@>=3.7.0:
version "3.8.0"
resolved "https://registry.yarnpkg.com/videojs-wavesurfer/-/videojs-wavesurfer-3.8.0.tgz#7c32ba65dc35df79681b0c4edd8b3f4ce47e0a63"
integrity sha512-qHucCBiEW+4dZ0Zp1k4R1elprUOV+QDw87UDA9QRXtO7GK/MrSdoe/TMFxP9SLnJCiX9xnYdf4OQgrmvJ9UVVw==
dependencies:
video.js ">=7.0.5"
wavesurfer.js ">=5.0.1"
vm-browserify@^1.0.1:
version "1.1.2"
resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-1.1.2.tgz#78641c488b8e6ca91a75f511e7a3b32a86e5dda0"
@ -15308,10 +15486,10 @@ watchpack@^1.7.4:
chokidar "^3.4.1"
watchpack-chokidar2 "^2.0.1"
wavesurfer.js@^5.2.0:
version "5.2.0"
resolved "https://registry.yarnpkg.com/wavesurfer.js/-/wavesurfer.js-5.2.0.tgz#efae107b5b561e9bfe3fffc50e6158136a17643e"
integrity sha512-SkPlTXfvKy+ZnEA7f7g7jn6iQg5/8mAvWpVV5vRbIS/FF9TB2ak9J7VayQfzfshOLW/CqccTiN6DDR/fZA902g==
wavesurfer.js@>=5.0.1, wavesurfer.js@^6.0.4:
version "6.1.0"
resolved "https://registry.yarnpkg.com/wavesurfer.js/-/wavesurfer.js-6.1.0.tgz#c6d4a192cbe2cb60717c88ea3f0a95bedc3dd571"
integrity sha512-Ss8d4PC8r1vSE8Qtf3UhC6o4BWXL1J/tr9DFwshFW2pSZBdkzau6FzWGJUul3aoOZSFb+azC5F/m0I9F+vU72w==
wbuf@^1.1.0, wbuf@^1.7.3:
version "1.7.3"
@ -15487,6 +15665,13 @@ webpack@4, webpack@^4.46.0:
watchpack "^1.7.4"
webpack-sources "^1.4.1"
webrtc-adapter@>=8.0.0:
version "8.1.1"
resolved "https://registry.yarnpkg.com/webrtc-adapter/-/webrtc-adapter-8.1.1.tgz#e4a4dfd1b5085d119da40c4efc0147f7d0961cba"
integrity sha512-1yXevP7TeZGmklEXkvQVrZp3fOSJlLeXNGCA7NovQokxgP3/e2T3EVGL0eKU87S9vKppWjvRWqnJeSANEspOBg==
dependencies:
sdp "^3.0.2"
websocket-driver@>=0.5.1, websocket-driver@^0.7.4:
version "0.7.4"
resolved "https://registry.yarnpkg.com/websocket-driver/-/websocket-driver-0.7.4.tgz#89ad5295bbf64b480abcba31e4953aca706f5760"