Skip to content

Commit

Permalink
Merge pull request #1911 from tactilenews/fix_signal_onboarding
Browse files Browse the repository at this point in the history
Fix signal onboarding
  • Loading branch information
mattwr18 authored Jun 26, 2024
2 parents e347d94 + 50c40dc commit 07cb810
Show file tree
Hide file tree
Showing 38 changed files with 4,935 additions and 314 deletions.
54 changes: 36 additions & 18 deletions app/adapters/signal_adapter/inbound.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ class Inbound
UNKNOWN_CONTENT_KEYS = %w[mentions contacts sticker].freeze
SUPPORTED_ATTACHMENT_TYPES = %w[image/jpg image/jpeg image/png image/gif audio/oog audio/aac audio/mp4 audio/mpeg video/mp4].freeze

attr_reader :sender, :text, :message
attr_reader :sender, :message

def initialize
@callbacks = {}
Expand All @@ -25,12 +25,16 @@ def on(callback, &block)
def consume(signal_message)
signal_message = signal_message.with_indifferent_access

@sender = initialize_sender(signal_message)
return unless @sender
@sender = initialize_contributing_sender(signal_message)

delivery_receipt = initialize_delivery_receipt(signal_message)
return if delivery_receipt

unless @sender
initialize_onboarding_sender(signal_message)
return
end

remove_emoji = signal_message.dig(:envelope, :dataMessage, :reaction, :isRemove)
return if remove_emoji

Expand All @@ -53,31 +57,41 @@ def trigger(event, *args)
@callbacks[event].call(*args)
end

def initialize_sender(signal_message)
signal_phone_number = signal_message.dig(:envelope, :source)
sender = Contributor.find_by(signal_phone_number: signal_phone_number)

unless sender
trigger(UNKNOWN_CONTRIBUTOR, signal_phone_number)
return nil
end

if sender.signal_onboarding_completed_at.blank?
trigger(CONNECT, sender)
return nil
def initialize_contributing_sender(signal_message)
signal_phone_number = signal_message.dig(:envelope, :sourceNumber)
signal_uuid = signal_message.dig(:envelope, :sourceUuid)
if signal_phone_number
Contributor.find_by(signal_phone_number: signal_phone_number)
else
Contributor.find_by(signal_uuid: signal_uuid)
end

sender
end

def initialize_delivery_receipt(signal_message)
return nil unless delivery_receipt?(signal_message) && sender

delivery_receipt = signal_message.dig(:envelope, :receiptMessage)
return nil unless delivery_receipt

trigger(HANDLE_DELIVERY_RECEIPT, delivery_receipt, sender)
delivery_receipt
end

def initialize_onboarding_sender(signal_message)
signal_uuid = signal_message.dig(:envelope, :sourceUuid)
signal_onboarding_token = signal_message.dig(:envelope, :dataMessage, :message)
return nil unless signal_onboarding_token

sender = Contributor.find_by(signal_onboarding_token: signal_onboarding_token.strip)

unless sender
trigger(UNKNOWN_CONTRIBUTOR, signal_message.dig(:envelope, :source))
return nil
end
return unless signal_uuid

trigger(CONNECT, sender, signal_uuid)
end

def initialize_message(signal_message)
is_data_message = signal_message.dig(:envelope, :dataMessage)
return nil unless is_data_message
Expand Down Expand Up @@ -148,5 +162,9 @@ def create_message?
text = message.text
has_non_text_content || (text.present? && !unsubscribe_text?(text) && !resubscribe_text?(text))
end

def delivery_receipt?(signal_message)
signal_message.dig(:envelope, :receiptMessage)
end
end
end
2 changes: 1 addition & 1 deletion app/adapters/signal_adapter/outbound.rb
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ def send_resubscribe_error_message!(contributor)
end

def contributor_can_receive_messages?(recipient)
recipient&.signal_phone_number.present? && recipient.signal_onboarding_completed_at.present?
(recipient&.signal_phone_number.present? || recipient&.signal_uuid.present?) && recipient&.signal_onboarding_completed_at.present?
end
end
end
Expand Down
2 changes: 1 addition & 1 deletion app/adapters/signal_adapter/outbound/file.rb
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ def data
end
{
number: Setting.signal_server_phone_number,
recipients: [message.recipient.signal_phone_number],
recipients: [message.recipient.signal_attr],
message: message.text,
base64_attachments: base64_files
}
Expand Down
2 changes: 1 addition & 1 deletion app/adapters/signal_adapter/outbound/text.rb
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ def perform(contributor_id:, text:)
def data
{
number: Setting.signal_server_phone_number,
recipients: [recipient.signal_phone_number],
recipients: [recipient.signal_attr],
message: text
}
end
Expand Down
4 changes: 2 additions & 2 deletions app/adapters/signal_adapter/unknown_contributor_error.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@

module SignalAdapter
class UnknownContributorError < StandardError
def initialize(signal_phone_number:)
super("Received a message on signal from an unknown sender: #{signal_phone_number}")
def initialize(signal_attr:)
super("Received a message on signal from an unknown sender: #{signal_attr}")
end
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -4,27 +4,15 @@
<% end %>

<% if contributor.signal_onboarding_completed_at.present? %>
<p>
<%= t('.complete.text',
name: contributor.name,
phone_number: contributor.signal_phone_number.phony_formatted
) %>
</p>
<p><%= completed_onboarding_text %></p>
<% else %>
<%= c 'callout', style: :warning do %>
<%= c 'stack', space: :small do %>
<p>
<%= t('.incomplete.text',
name: contributor.name,
first_name: contributor.first_name,
date: l(contributor.created_at.to_date),
phone_number: contributor.signal_phone_number.phony_formatted,
) %>
</p>
<p><%= incomplete_onboarding_text %></p>

<%= c 'copy_button',
style: :secondary,
copy: onboarding_signal_link_url,
copy: onboarding_signal_link_url(signal_onboarding_token: contributor.signal_onboarding_token),
label: t('.incomplete.action')
%>
<% end %>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,25 @@ def initialize(contributor:, **)
private

attr_reader :contributor

def completed_onboarding_text
if contributor.signal_phone_number.present?
t('.complete.text.phone_number',
name: contributor.name,
phone_number: contributor.signal_phone_number.phony_formatted)
else

t('.complete.text.uuid',
name: contributor.name,
uuid: contributor.signal_uuid)
end
end

def incomplete_onboarding_text
t('.incomplete.text',
name: contributor.name,
first_name: contributor.first_name,
date: l(contributor.created_at.to_date))
end
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,11 @@
<%= c 'input', field.input_defaults.merge(required: true) %>
<% end %>

<%= c 'field', object: contributor, attr: :signal_phone_number do |field| %>
<%= c 'input', field.input_defaults.merge(placeholder: '', required: true, type: :tel) %>
<% end %>
<%= c 'input',
id: 'contributor[signal_onboarding_token]',
type: 'hidden',
value: contributor.signal_onboarding_token
%>

<%= c 'onboarding_consent', contributor: contributor %>

Expand Down
Original file line number Diff line number Diff line change
@@ -1,39 +1,14 @@
<%= c 'stack', **attrs do %>

<%= c 'stack', class: 'text-serif text-center', space: :xsmall do %>
<%= c 'heading', tag: :h1 do %>
<%= t('components.onboarding_signal_link.heading') %>
<%= t 'components.onboarding_signal_link.heading' %>
<% end %>

<p>
<%= t('components.onboarding_signal_link.text').html_safe %>
</p>

<p>
<%= t('components.onboarding_signal_link.action',
number: Setting.signal_server_phone_number.phony_formatted(spaces: ' ')
).html_safe %>
</p>
<% end %>

<%= c 'stack', space: :small do %>
<%= c 'button',
styles: [:block, :primary],
link: "https://signal.me/#p/#{Setting.signal_server_phone_number}",
label: t('components.onboarding_signal_link.link'),
target: '_blank',
rel: 'noopener noreferrer'
%>

<%= c 'copy_button',
styles: [:block, :secondary],
label: t('components.onboarding_signal_link.copy'),
copy: Setting.signal_server_phone_number
%>

<p class="text-small text-light text-center">
<%= t('components.onboarding_signal_link.help') %>
</p>
<p><%= t 'components.onboarding_signal_link.text' %></p>
<% end %>

<%= c 'steps_list',
class: 'text-serif',
steps: fallback_steps.map(&:html_safe)
%>
<% end %>
17 changes: 16 additions & 1 deletion app/components/onboarding_signal_link/onboarding_signal_link.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,20 @@
# frozen_string_literal: true

module OnboardingSignalLink
class OnboardingSignalLink < ApplicationComponent; end
class OnboardingSignalLink < ApplicationComponent
def initialize(signal_onboarding_token:, **)
super

@signal_onboarding_token = signal_onboarding_token
end

private

attr_reader :signal_onboarding_token

def fallback_steps
data = { signal_server_phone_number: Setting.signal_server_phone_number, token: signal_onboarding_token }
I18n.t('components.onboarding_signal_link.steps', **data)
end
end
end
22 changes: 15 additions & 7 deletions app/controllers/onboarding/signal_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,28 @@ module Onboarding
class SignalController < OnboardingController
skip_before_action :verify_jwt, only: :link

def link; end
def show
super
@contributor.signal_onboarding_token = SecureRandom.alphanumeric(8).upcase
end

def link
@signal_onboarding_token = signal_onboarding_token
end

private

def attr_name
:signal_phone_number
def redirect_to_success
signal_onboarding_token = @contributor.signal_onboarding_token
redirect_to onboarding_signal_link_path(signal_onboarding_token: signal_onboarding_token, jwt: nil)
end

def redirect_to_success
redirect_to onboarding_signal_link_path(jwt: nil)
def attr_name
:signal_onboarding_token
end

def complete_onboarding(contributor)
SignalAdapter::CreateContactJob.perform_later(contributor)
def signal_onboarding_token
params.require(:signal_onboarding_token)
end
end
end
18 changes: 14 additions & 4 deletions app/controllers/onboarding_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ class OnboardingController < ApplicationController
skip_before_action :require_login
before_action :verify_jwt, except: :success
before_action :resume_telegram_onboarding, only: %i[index show]
before_action :resume_signal_onboarding, only: %i[index show]
before_action :redirect_if_contributor_exists, only: :create

rescue_from ActionController::BadRequest, with: :render_unauthorized
Expand All @@ -28,7 +29,6 @@ def create
@contributor.tag_list = tag_list_from_jwt

if @contributor.save
complete_onboarding(@contributor)
@contributor.send_welcome_message!
redirect_to_success
else
Expand Down Expand Up @@ -84,11 +84,9 @@ def render_unauthorized

def verify_jwt
return unless jwt
raise ActionController::BadRequest unless resume_telegram_onboarding?
raise ActionController::BadRequest unless resume_telegram_onboarding? || resume_signal_onboarding?
end

def complete_onboarding(contributor); end

def resume_telegram_onboarding
return unless resume_telegram_onboarding?

Expand All @@ -101,6 +99,18 @@ def resume_telegram_onboarding?
contributor&.telegram_id.blank? && contributor&.telegram_onboarding_token.present?
end

def resume_signal_onboarding
return unless resume_signal_onboarding?

token = jwt.contributor.signal_onboarding_token
redirect_to onboarding_signal_link_path(signal_onboarding_token: token)
end

def resume_signal_onboarding?
contributor = jwt&.contributor
contributor&.signal_uuid.blank? && contributor&.signal_onboarding_token.present?
end

def jwt
decoded_token = JsonWebToken.decode(jwt_param)
action = decoded_token.first['data']['action']
Expand Down
7 changes: 5 additions & 2 deletions app/jobs/signal_adapter/attach_contributors_avatar_job.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,11 @@ module SignalAdapter
class AttachContributorsAvatarJob < ApplicationJob
queue_as :attach_signal_avatar

def perform(contributor)
avatar = "/app/signal-cli-config/avatars/profile-#{contributor.signal_phone_number}"
def perform(contributor_id:)
contributor = Contributor.find_by(id: contributor_id)
return unless contributor

avatar = "/app/signal-cli-config/avatars/profile-#{contributor.signal_uuid}"
return unless File.file?(avatar)

contributor.avatar.attach(io: File.open(avatar), filename: contributor.id)
Expand Down
7 changes: 5 additions & 2 deletions app/jobs/signal_adapter/create_contact_job.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,17 @@

module SignalAdapter
class CreateContactJob < ApplicationJob
def perform(contributor)
def perform(contributor_id:)
contributor = Contributor.find_by(id: contributor_id)
return unless contributor

url = URI.parse("#{Setting.signal_cli_rest_api_endpoint}/v1/contacts/#{Setting.signal_server_phone_number}")
header = {
Accept: 'application/json',
'Content-Type': 'application/json'
}
data = {
recipient: contributor.signal_phone_number,
recipient: contributor.signal_uuid,
name: contributor.name
}
req = Net::HTTP::Put.new(url.to_s, header)
Expand Down
Loading

0 comments on commit 07cb810

Please sign in to comment.