Module: SecondStep::MacOSNotify

Defined in:
lib/second_step/mac_os_notify.rb

Class Method Summary collapse

Class Method Details

._notify(message, options = {}, verbose = false) ⇒ Object



17
18
19
20
# File 'lib/second_step/mac_os_notify.rb', line 17

def self._notify(message, options = {}, verbose = false)
  result = TerminalNotifier.execute(verbose, options.merge(:message => message))
  $? && $?.success? && _notify_result(result, options)
end

._notify_result(result, options = {}) ⇒ Object



9
10
11
12
13
14
15
16
# File 'lib/second_step/mac_os_notify.rb', line 9

def self._notify_result(result, options = {})
  reply_option = options[:reply] || options['reply']
  if reply_option && !result.start_with?('@')
    result
  else
    result.length == 0 || result.downcase.gsub(/\W+/,'_').to_sym
  end
end

.hash_to_array_with_only(hash, keys = nil) ⇒ Object



21
22
23
# File 'lib/second_step/mac_os_notify.rb', line 21

def self.hash_to_array_with_only(hash, keys = nil)
  keys.zip(hash.values_at(*keys))
end

.notify_login(from: 'Unknown', actions: %i[approve deny],, action_text: 'Approve or Deny:', timeout: 5, ignore_label: 'Ignore', show_details: nil, **details) ⇒ Object

Returns an action from actions, or :ignore Action must be an array of symbols containing only word characters and non-neighboring underscores.



50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
# File 'lib/second_step/mac_os_notify.rb', line 50

def self.(from: 'Unknown', actions: %i[approve deny], action_text: 'Approve or Deny:', timeout: 5, ignore_label: 'Ignore', show_details: nil, **details)
  if show_details
    show_details &= details.keys
  else
    show_details = details.keys
  end
  body_text = hash_to_array_with_only(details, show_details).map{|(k,v)| "#{k.to_s.capitalize}: #{v}"}.join(', ')
  subprocess = fork do
    begin
      if TerminalNotifier.available?
        result = _notify(body_text,
                         actions: actions.map{|s| s.to_s.capitalize}.join(','),
                         closeLabel: ignore_label,
                         dropdownLabel: action_text,
                         title: 'SecondStep',
                         subtitle: "Login Request from #{from}",
                         group: Process.pid,
                         timeout: timeout)
        if result
          result = notify_with_dialog(details.merge(from: from, actions: actions, action_text: action_text, ignore_label: ignore_label, show_details: show_details)) if result == :_contentclicked
          yield actions.include?(result) ? result : :ignore
        else
          raise NotificationError.new(result)
        end
      else
        result = notify_with_dialog(details.merge(from: from, actions: actions, action_text: action_text, ignore_label: ignore_label, show_details: show_details))
        yield actions.include?(result) ? result : :ignore
      end
    ensure
      TerminalNotifier.remove Process.pid
    end
  end
  at_exit {
    begin
      unless Process.waitpid subprocess, Process::WNOHANG
        puts "Waiting for notification in process #{subprocess} to finish..."
        begin
          Timeout.timeout(180) do
            Process.wait subprocess
          end
        rescue Timeout::Error
          puts "Process #{subprocess} not finishing... killing"
          begin
            Process.kill('HUP', subprocess)
            Timeout.timeout(20) do
              Process.wait subprocess
            end
          rescue Timeout::Error
            puts "Process #{subprocess} not killing... terminating"
            Process.kill(9, subprocess)
          end
        end
      end
    rescue Errno::ECHILD
      true
    end
  }
end

.notify_with_dialog(details) ⇒ Object



24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
# File 'lib/second_step/mac_os_notify.rb', line 24

def self.notify_with_dialog(details)
  buttons = details[:actions].map{ |action| "\"#{Shellwords.shellescape(action.to_s.capitalize)}\"" }
  escaped_from = Shellwords.shellescape details[:from]
  body_text = hash_to_array_with_only(details, details[:show_details]).map do |(k,v)|
    body_line = k.to_s.capitalize.split(' ')
    body_line.last << ':'
    Shellwords.join(body_line + v.to_s.split(' '))
  end.join('\n')
  icon_path = File.expand_path('../logo.png.icns', __FILE__)
  osascript = <<-osascript
try
  set dialogResult to display dialog "Login Request from #{escaped_from}\\n\\n#{body_text}" buttons {"Ignore",#{buttons.reverse.join(',')}} cancel button "Ignore" default button #{buttons.first} with title "SecondStep Login Request" with icon POSIX file "#{icon_path}"
  return the button returned of dialogResult
on error number -128
  return "ignore"
end try
  osascript
  result = IO.popen('osascript', 'r+') do |f|
    f.puts osascript
    f.close_write
    f.readlines
  end
  _notify_result result.first.chomp
end