## Code Context **Query:** notification, notification subscription, webhook, integration hook, installation webhook, action cable listener, push notification, email notification ### Entry Points - **InstallationWebhookListener** (class) - app/listeners/installation_webhook_listener.rb:1 - **ActionCableListener** (class) - app/listeners/action_cable_listener.rb:1 - **BaseActionCableConnector** (class) - app/javascript/shared/helpers/BaseActionCableConnector.js:6 ### Related Symbols - app/listeners/base_listener.rb: BaseListener:1 - app/listeners/installation_webhook_listener.rb: account_created:2, account:12, users:16, deliver_webhook_payloads:20 - app/dispatchers/async_dispatcher.rb: listeners:11 - app/listeners/action_cable_listener.rb: notification_created:4, notification_updated:10, notification_deleted:16, account_cache_invalidated:32 ### Code #### InstallationWebhookListener (app/listeners/installation_webhook_listener.rb:1) ```ruby class InstallationWebhookListener < BaseListener def account_created(event) payload = account(event).webhook_data.merge( event: __method__.to_s, users: users(event) ) deliver_webhook_payloads(payload) end private def account(event) event.data[:account] end def users(event) account(event).administrators.map(&:webhook_data) end def deliver_webhook_payloads(payload) # Deliver the installation event webhook_url = InstallationConfig.find_by(name: 'INSTALLATION_EVENTS_WEBHOOK_URL')&.value WebhookJob.perform_later(webhook_url, payload) if webhook_url end end ``` #### ActionCableListener (app/listeners/action_cable_listener.rb:1) ```ruby class ActionCableListener < BaseListener include Events::Types def notification_created(event) notification, account, unread_count, count = extract_notification_and_account(event) tokens = [event.data[:notification].user.pubsub_token] broadcast(account, tokens, NOTIFICATION_CREATED, { notification: notification.push_event_data, unread_count: unread_count, count: count }) end def notification_updated(event) notification, account, unread_count, count = extract_notification_and_account(event) tokens = [event.data[:notification].user.pubsub_token] broadcast(account, tokens, NOTIFICATION_UPDATED, { notification: notification.push_event_data, unread_count: unread_count, count: count }) end def notification_deleted(event) notification_data = event.data[:notification_data] user = User.find_by(id: notification_data[:user_id]) account = Account.find_by(id: notification_data[:account_id]) return if user.blank? || account.blank? notification_finder = NotificationFinder.new(user, account) tokens = [user.pubsub_token] broadcast(account, tokens, NOTIFICATION_DELETED, { notification: { id: notification_data[:id] }, unread_count: notification_finder.unread_count, count: notification_finder.count }) end def account_cache_invalidated(event) account = event.data[:account] tokens = user_tokens(account, account.agents) broadcast(account, tokens, ACCOU // ... truncated ... ``` #### BaseActionCableConnector (app/javascript/shared/helpers/BaseActionCableConnector.js:6) ```javascript class BaseActionCableConnector { static isDisconnected = false; constructor( app, pubsubToken, websocketHost = '', presenceInterval = PRESENCE_INTERVAL ) { const websocketURL = websocketHost ? `${websocketHost}/cable` : undefined; this.consumer = createConsumer(websocketURL); this.subscription = this.consumer.subscriptions.create( { channel: 'RoomChannel', pubsub_token: pubsubToken, account_id: app.$store.getters.getCurrentAccountId, user_id: app.$store.getters.getCurrentUserID, }, { updatePresence() { this.perform('update_presence'); }, received: this.onReceived, disconnected: () => { BaseActionCableConnector.isDisconnected = true; this.onDisconnected(); this.initReconnectTimer(); }, } ); this.app = app; this.events = {}; this.reconnectTimer = null; this.isAValidEvent = () => true; this.triggerPresenceInterval = () => { setTimeout(() => { this.subscription.updatePresence(); this.triggerPresenceInterval(); }, presenceInterval); }; this.triggerPresenceInterval(); } checkConnection() { const isConnectionActive = this.consumer.connection.isOpen(); const isReconnected = BaseActionCableConnector.isDisconnected && isConnectionActive; if (isReconnected) { this.clearReconnectTimer(); this.onReconnect(); BaseActionCa // ... truncated ... ``` #### account_created (app/listeners/installation_webhook_listener.rb:2) ```ruby def account_created(event) payload = account(event).webhook_data.merge( event: __method__.to_s, users: users(event) ) deliver_webhook_payloads(payload) end ``` #### account (app/listeners/installation_webhook_listener.rb:12) ```ruby def account(event) event.data[:account] end ``` #### users (app/listeners/installation_webhook_listener.rb:16) ```ruby def users(event) account(event).administrators.map(&:webhook_data) end ``` #### deliver_webhook_payloads (app/listeners/installation_webhook_listener.rb:20) ```ruby def deliver_webhook_payloads(payload) # Deliver the installation event webhook_url = InstallationConfig.find_by(name: 'INSTALLATION_EVENTS_WEBHOOK_URL')&.value WebhookJob.perform_later(webhook_url, payload) if webhook_url end ``` #### listeners (app/dispatchers/async_dispatcher.rb:11) ```ruby def listeners [ AutomationRuleListener.instance, CampaignListener.instance, CsatSurveyListener.instance, HookListener.instance, InstallationWebhookListener.instance, NotificationListener.instance, ParticipationListener.instance, Conversations::UnreadCounts::Listener.instance, ReportingEventListener.instance, WebhookListener.instance ] end ``` #### notification_created (app/listeners/action_cable_listener.rb:4) ```ruby def notification_created(event) notification, account, unread_count, count = extract_notification_and_account(event) tokens = [event.data[:notification].user.pubsub_token] broadcast(account, tokens, NOTIFICATION_CREATED, { notification: notification.push_event_data, unread_count: unread_count, count: count }) end ``` #### notification_updated (app/listeners/action_cable_listener.rb:10) ```ruby def notification_updated(event) notification, account, unread_count, count = extract_notification_and_account(event) tokens = [event.data[:notification].user.pubsub_token] broadcast(account, tokens, NOTIFICATION_UPDATED, { notification: notification.push_event_data, unread_count: unread_count, count: count }) end ``` #### notification_deleted (app/listeners/action_cable_listener.rb:16) ```ruby def notification_deleted(event) notification_data = event.data[:notification_data] user = User.find_by(id: notification_data[:user_id]) account = Account.find_by(id: notification_data[:account_id]) return if user.blank? || account.blank? notification_finder = NotificationFinder.new(user, account) tokens = [user.pubsub_token] broadcast(account, tokens, NOTIFICATION_DELETED, { notification: { id: notification_data[:id] }, unread_count: notification_finder.unread_count, count: notification_finder.count }) end ``` #### account_cache_invalidated (app/listeners/action_cable_listener.rb:32) ```ruby def account_cache_invalidated(event) account = event.data[:account] tokens = user_tokens(account, account.agents) broadcast(account, tokens, ACCOUNT_CACHE_INVALIDATED, { cache_keys: event.data[:cache_keys] }) end ``` #### message_created (app/listeners/action_cable_listener.rb:41) ```ruby def message_created(event) message, account = extract_message_and_account(event) conversation = message.conversation tokens = user_tokens(account, conversation.inbox.members) + contact_tokens(conversation.contact_inbox, message) broadcast(account, tokens, MESSAGE_CREATED, message.push_event_data) end ``` #### message_updated (app/listeners/action_cable_listener.rb:49) ```ruby def message_updated(event) message, account = extract_message_and_account(event) conversation = message.conversation tokens = user_tokens(account, conversation.inbox.members) + contact_tokens(conversation.contact_inbox, message) broadcast(account, tokens, MESSAGE_UPDATED, message.push_event_data.merge(previous_changes: event.data[:previous_changes])) end ``` #### first_reply_created (app/listeners/action_cable_listener.rb:57) ```ruby def first_reply_created(event) message, account = extract_message_and_account(event) conversation = message.conversation tokens = user_tokens(account, conversation.inbox.members) broadcast(account, tokens, FIRST_REPLY_CREATED, message.push_event_data) end ``` #### conversation_created (app/listeners/action_cable_listener.rb:65) ```ruby def conversation_created(event) conversation, account = extract_conversation_and_account(event) tokens = user_tokens(account, conversation.inbox.members) + contact_inbox_tokens(conversation.contact_inbox) broadcast(account, tokens, CONVERSATION_CREATED, conversation.push_event_data) end ``` #### conversation_read (app/listeners/action_cable_listener.rb:72) ```ruby def conversation_read(event) conversation, account = extract_conversation_and_account(event) tokens = user_tokens(account, conversation.inbox.members) broadcast(account, tokens, CONVERSATION_READ, conversation.push_event_data) end ``` #### conversation_status_changed (app/listeners/action_cable_listener.rb:79) ```ruby def conversation_status_changed(event) conversation, account = extract_conversation_and_account(event) tokens = user_tokens(account, conversation.inbox.members) + contact_inbox_tokens(conversation.contact_inbox) broadcast(account, tokens, CONVERSATION_STATUS_CHANGED, conversation.push_event_data) end ``` #### conversation_updated (app/listeners/action_cable_listener.rb:86) ```ruby def conversation_updated(event) conversation, account = extract_conversation_and_account(event) tokens = user_tokens(account, conversation.inbox.members) + contact_inbox_tokens(conversation.contact_inbox) broadcast(account, tokens, CONVERSATION_UPDATED, conversation.push_event_data) end ``` #### conversation_unread_count_changed (app/listeners/action_cable_listener.rb:93) ```ruby def conversation_unread_count_changed(event) account, inbox_members = ::Conversations::UnreadCounts::BroadcastScope.new(event).perform return if account.blank? || !account.feature_enabled?('conversation_unread_counts') tokens = user_tokens(account, inbox_members) broadcast(account, tokens, CONVERSATION_UNREAD_COUNT_CHANGED, {}) end ``` #### conversation_typing_on (app/listeners/action_cable_listener.rb:102) ```ruby def conversation_typing_on(event) conversation = event.data[:conversation] account = conversation.account user = event.data[:user] tokens = typing_event_listener_tokens(account, conversation, user) broadcast( account, tokens, CONVERSATION_TYPING_ON, conversation: conversation.push_event_data, user: user.push_event_data, is_private: event.data[:is_private] || false ) end ``` #### conversation_typing_off (app/listeners/action_cable_listener.rb:118) ```ruby def conversation_typing_off(event) conversation = event.data[:conversation] account = conversation.account user = event.data[:user] tokens = typing_event_listener_tokens(account, conversation, user) broadcast( account, tokens, CONVERSATION_TYPING_OFF, conversation: conversation.push_event_data, user: user.push_event_data, is_private: event.data[:is_private] || false ) end ``` #### assignee_changed (app/listeners/action_cable_listener.rb:134) ```ruby def assignee_changed(event) conversation, account = extract_conversation_and_account(event) tokens = user_tokens(account, conversation.inbox.members) broadcast(account, tokens, ASSIGNEE_CHANGED, conversation.push_event_data) end ``` #### team_changed (app/listeners/action_cable_listener.rb:141) ```ruby def team_changed(event) conversation, account = extract_conversation_and_account(event) tokens = user_tokens(account, conversation.inbox.members) broadcast(account, tokens, TEAM_CHANGED, conversation.push_event_data) end ``` #### conversation_contact_changed (app/listeners/action_cable_listener.rb:148) ```ruby def conversation_contact_changed(event) conversation, account = extract_conversation_and_account(event) tokens = user_tokens(account, conversation.inbox.members) broadcast(account, tokens, CONVERSATION_CONTACT_CHANGED, conversation.push_event_data) end ``` #### contact_created (app/listeners/action_cable_listener.rb:155) ```ruby def contact_created(event) contact, account = extract_contact_and_account(event) broadcast(account, [account_token(account)], CONTACT_CREATED, contact.push_event_data) end ``` #### contact_updated (app/listeners/action_cable_listener.rb:160) ```ruby def contact_updated(event) contact, account = extract_contact_and_account(event) broadcast(account, [account_token(account)], CONTACT_UPDATED, contact.push_event_data) end ``` #### listeners (app/dispatchers/sync_dispatcher.rb:7) ```ruby def listeners [ActionCableListener.instance, AgentBotListener.instance] end ``` #### (app/javascript/shared/helpers/BaseActionCableConnector.js:7) ```javascript static isDisconnected = false; ``` #### constructor (app/javascript/shared/helpers/BaseActionCableConnector.js:9) ```javascript constructor( app, pubsubToken, websocketHost = '', presenceInterval = PRESENCE_INTERVAL ) { const websocketURL = websocketHost ? `${websocketHost}/cable` : undefined; this.consumer = createConsumer(websocketURL); this.subscription = this.consumer.subscriptions.create( { channel: 'RoomChannel', pubsub_token: pubsubToken, account_id: app.$store.getters.getCurrentAccountId, user_id: app.$store.getters.getCurrentUserID, }, { updatePresence() { this.perform('update_presence'); }, received: this.onReceived, disconnected: () => { BaseActionCableConnector.isDisconnected = true; this.onDisconnected(); this.initReconnectTimer(); }, } ); this.app = app; this.events = {}; this.reconnectTimer = null; this.isAValidEvent = () => true; this.triggerPresenceInterval = () => { setTimeout(() => { this.subscription.updatePresence(); this.triggerPresenceInterval(); }, presenceInterval); }; this.triggerPresenceInterval(); } ```