stealth_browser_automation 1.1.37 → 1.2.38

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,4 +1,5 @@
1
1
  require 'watir-webdriver'
2
+ require 'selenium-webdriver'
2
3
  require "pathname"
3
4
 
4
5
  # **ABSTRACT**
@@ -30,13 +31,203 @@ require "pathname"
30
31
  # Custom Selenium elements to stream the browser windows when some events happen.
31
32
  module Selenium
32
33
  module WebDriver
34
+ =begin # TODO: enable this for mouse movement biometrics simulation
35
+ module PointerActions
36
+ alias_method :old_move_to_location, :move_to_location
37
+ alias_method :old_move_to, :move_to
38
+
39
+ @@x = 0
40
+ @@y = 0
41
+
42
+ def x()
43
+ @@x
44
+ end
45
+
46
+ def y()
47
+ @@y
48
+ end
49
+
50
+ def move_to_location(x, y)
51
+ # getting noise factor
52
+ if !BlackStack::BrowserFactory.mouse_hash.nil?
53
+ actions = []
54
+
55
+ # do a noisy moveent to (x,y)
56
+ i = { :x=>@@x.to_f, :y=>@@y.to_f } # starting position
57
+ j = { :x=>x.to_f, :y=>y.to_f } # ending position
58
+ t = 0.to_f
59
+ z = BlackStack::BrowserFactory.mouse_hash[:noise].to_f
60
+
61
+ # TODO: validate the step tiene que ser un divisor de 1. O sea: 1/step da un numero entero.
62
+ step = 0.05 # 20 steps ( 1 / 0.05 = 20 )
63
+
64
+ while t<1.to_f
65
+ t += step
66
+ # noise calculation
67
+ if t<1.to_f # there is no noise for the last step
68
+ noise = {
69
+ :x => ( z.to_f + (rand(10000).to_f / 10000.to_f ) - 1.to_f ) / 10.to_f,
70
+ :y => ( z.to_f + (rand(10000).to_f / 10000.to_f ) - 1.to_f ) / 10.to_f,
71
+ }
72
+ else
73
+ noise = {
74
+ :x => 0.to_f,
75
+ :y => 0.to_f,
76
+ }
77
+ end
78
+ w = {
79
+ :x => ( i[:x] + t*(j[:x]-i[:x]) + t*(j[:x]-i[:x])*noise[:x] ).round(0).to_i,
80
+ :y => ( i[:y] + t*(j[:y]-i[:y]) + t*(j[:y]-i[:y])*noise[:y] ).round(0).to_i,
81
+ }
82
+ # TODO: fire javascript event? --> the old_move_to_location method will fire such event
83
+ # peform action
84
+ a = old_move_to_location(w[:x], w[:y])
85
+ actions << a
86
+ a.perform if t<1.to_f
87
+ end
88
+ @@x = x
89
+ @@y = y
90
+ actions.last
91
+ else
92
+ old_move_to_location(x, y)
93
+ end # if !BlackStack::BrowserFactory.mouse_hash.nil?
94
+ end # def move_to_location(x, y)
95
+
96
+ def move_to(element, right_by = nil, down_by = nil)
97
+ # getting noise factor
98
+ if !BlackStack::BrowserFactory.mouse_hash.nil?
99
+ z = BlackStack::BrowserFactory.mouse_hash[:noise].to_f
100
+
101
+ # select a random position inside the element, and call the move_to_location method.
102
+ noise = {
103
+ :x => ( z.to_f + (rand(10000).to_f / 10000.to_f ) ) / 2.to_f,
104
+ :y => ( z.to_f + (rand(10000).to_f / 10000.to_f ) ) / 2.to_f,
105
+ }
106
+
107
+ w = {
108
+ :x => ( element.location.x.to_f + element.size.width.to_f * noise[:x].to_f ).to_i,
109
+ :y => ( element.location.y.to_f + element.size.height.to_f * noise[:y].to_f ).to_i,
110
+ }
111
+ self.move_to_location(w[:x], w[:y])
112
+ else
113
+ old_move_to(x, y)
114
+ end # if !BlackStack::BrowserFactory.mouse_hash.nil?
115
+ end
116
+
117
+ end # class ActionBuilder
118
+ =end
33
119
  class Element
34
120
  alias_method :old_click, :click
121
+ alias_method :old_send_keys, :send_keys
35
122
 
36
123
  def click()
124
+ =begin # TODO: enable this for mouse movement biometrics simulation
125
+ # getting noise factor
126
+ if !BlackStack::BrowserFactory.mouse_hash.nil?
127
+ # noise move movement to the element, plus click into a random position inside the element.
128
+ BlackStack::BrowserFactory.browser.driver.action.move_to(self)
129
+ end
130
+ =end
131
+ #
37
132
  old_click
133
+ #
38
134
  BlackStack::BrowserFactory.browser.push_screenshot
39
135
  end # def click
136
+
137
+ # Masking keystrokes biometrics
138
+ # This method is also called by Selenium::WebDriver::Element::set, and by the setter Selenium::WebDriver::Element::value
139
+ def send_keys(*args)
140
+ =begin # TODO: enable this for mouse movement biometrics simulation
141
+ # click the element before writing, that will move the cursor to the element previously
142
+ self.click()
143
+ =end
144
+ #
145
+ pevious_char = nil
146
+ if BlackStack::BrowserFactory.keystroke_signature.nil?
147
+ old_send_keys(args)
148
+ else
149
+ args.each { |s|
150
+
151
+ if s.is_a? Symbol
152
+ old_send_keys(s)
153
+ else
154
+ # convert single quotes string to double using eval() function, so I can replace escaped chars using String::escape_javascript method.
155
+ # more information here: https://www.ruby-forum.com/t/single-quoted-string-to-become-double-quoted-is-it-possible/214980
156
+ # TODO: create the method String::to_souble_quotes in blackstack_commons
157
+ eval("\"#{s.gsub('"','\\"')}\"").split('').each { |c|
158
+ # escape string to build a javascript literal string dynamically
159
+ c2 = c.escape_javascript
160
+ j = c.ord
161
+ j = j%256 # considerar caracteres no ascii, fuera del intervalo [0,255]
162
+
163
+ if !pevious_char.nil?
164
+ i = pevious_char.ord
165
+ i = i%256 # considerar caracteres no ascii, fuera del intervalo [0,255]
166
+
167
+ # d1 = delay between keys + 10% random
168
+ d = BlackStack::BrowserFactory.keystroke_matrix[i][j] # delay between 2 keys
169
+ aux1 = 1000000
170
+ rnd = ( rand((d.to_f * aux1.to_f).to_i).to_f * 0.1.to_f ) / aux1.to_f
171
+ d1 = d + rnd
172
+
173
+ # apply delay between keys
174
+ sleep(d1)
175
+ end
176
+
177
+ # d2 = delay the relative dwell and flight time
178
+ # more info here: https://www.keytrac.net/en/technology
179
+ d2 = BlackStack::BrowserFactory.keystroke_matrix[0][j] # delay between 2 keys
180
+ # TODO: agregar un random
181
+
182
+ # apply delay between dwell and flight time
183
+
184
+ # TODO: this is not working because key_down only accepts :CTRL, :ALT, :SHIFT
185
+ # BlackStack::BrowserFactory.browser.driver.action.key_down(self, c).perform
186
+
187
+ # TODO: this is working but when c.ord is outside the range [0,255]
188
+ # TODO: injected javascript is detectable by websites!
189
+ script = "arguments[0].dispatchEvent(new KeyboardEvent('keydown',{'key':\"#{c2}\"}));"
190
+ if c.ord >= 0 && c.ord <= 255
191
+ # disable pampa notifications in this case
192
+ baux = BlackStack::BrowserFactory.browser.pampa_integration
193
+ BlackStack::BrowserFactory.browser.pampa_integration = false
194
+ BlackStack::BrowserFactory.browser.execute_script(script, self)
195
+ BlackStack::BrowserFactory.browser.pampa_integration = baux
196
+ end
197
+
198
+ sleep(d2)
199
+
200
+ if c=='\n'
201
+ old_send_keys("\n") # TODO: I don't know why old_send_keys("\n") print 2 chars (\n) in the textarea, instead to do the breakline.
202
+ else
203
+ begin
204
+ old_send_keys("#{j.chr}")
205
+ rescue
206
+ # issue #112 ( https://github.com/leandrosardi/blackstack/issues/112 )
207
+ end
208
+ end
209
+
210
+ # TODO: this is not working because key_up only accepts :CTRL, :ALT, :SHIFT
211
+ # BlackStack::BrowserFactory.browser.driver.action.key_up(self, c).perform
212
+
213
+ # TODO: this is working but when c.ord is outside the range [0,255]
214
+ # TODO: injected javascript is detectable by websites!
215
+ script = "arguments[0].dispatchEvent(new KeyboardEvent('keyup',{'key':\"#{c2}\"}));"
216
+ if c.ord >= 0 && c.ord <= 255
217
+ # disable pampa notifications in this case
218
+ baux = BlackStack::BrowserFactory.browser.pampa_integration
219
+ BlackStack::BrowserFactory.browser.pampa_integration = false
220
+ BlackStack::BrowserFactory.browser.execute_script(script, self)
221
+ BlackStack::BrowserFactory.browser.pampa_integration = baux
222
+ end
223
+
224
+ # update previous char
225
+ pevious_char = c
226
+ }
227
+ end # if s.is_a? Symbol
228
+ }
229
+ end
230
+ end # def send_keys(*args)
40
231
 
41
232
  end # class Element
42
233
  end # module WebDriver
@@ -75,14 +266,14 @@ module BlackStack
75
266
  #PAMPA_BROWSER_CHANNEL_LOCK_FILENAME = './browserfactory.channel.%PROFILE_NAME%.lock' # manejo de concurrencia en la creación de browsers
76
267
  #attr_accessor :lockfile
77
268
 
78
- attr_accessor :proxy, :lnuser, :agent_name, :profile_name
269
+ attr_accessor :proxy, :lnuser, :agent_name, :profile_name, :pampa_integration, :rquery
79
270
 
80
271
  def lock_channel()
81
- self.lockfile.flock(File::LOCK_EX)
272
+ # self.lockfile.flock(File::LOCK_EX)
82
273
  end
83
274
 
84
275
  def release_channel()
85
- self.lockfile.flock(File::LOCK_UN)
276
+ # self.lockfile.flock(File::LOCK_UN)
86
277
  end
87
278
 
88
279
  def initialize(driver)
@@ -90,9 +281,16 @@ module BlackStack
90
281
  #fname = PAMPA_BROWSER_CHANNEL_LOCK_FILENAME.gsub('%PROFILE_NAME%', self.profile_name.to_s)
91
282
  #self.lockfile = File.open(fname,"w")
92
283
 
284
+ self.pampa_integration = true
285
+
93
286
  #
94
287
  super(driver)
95
288
 
289
+ # TODO: activate it when develop the RQuery (Render Query) feature
290
+ =begin
291
+ self.rquery = BlackStack::StealthBrowserAutomation::RQuery::FixedMatching(self)
292
+ =end
293
+
96
294
  # TODO: enable this when you move to the "frequency-based-streaming" model
97
295
  =begin
98
296
  # Can't operate the same browser from more than 1 thread.
@@ -134,21 +332,23 @@ module BlackStack
134
332
  end # def initialize
135
333
 
136
334
  def notify(method)
137
- url = "#{BlackStack::Pampa::api_protocol}://#{PROCESS.ws_url}:#{PROCESS.ws_port}/api1.3/pampa/browser/notify.json"
138
- res = BlackStack::Netting::call_post(url, {
139
- :api_key => BlackStack::Pampa::api_key,
140
- :filename => $0,
141
- :method => method,
142
- :worker_name => PROCESS.fullWorkerName,
143
- :id_proxy => self.proxy.nil? ? nil : self.proxy.id,
144
- :id_lnuser => self.lnuser.nil? ? nil : self.lnuser.id,
145
- :worker_assigned_process => PROCESS.worker.assigned_process,
146
- :profile_name => self.profile_name,
147
- :agent_name => self.agent_name,
148
- })
149
- parsed = JSON.parse(res.body)
150
- if parsed['status'] != "success"
151
- raise "Error Tracing BrowserActivity: #{parsed['status']}"
335
+ if self.pampa_integration
336
+ url = "#{BlackStack::Pampa::api_protocol}://#{PROCESS.ws_url}:#{PROCESS.ws_port}/api1.3/pampa/browser/notify.json"
337
+ res = BlackStack::Netting::call_post(url, {
338
+ :api_key => BlackStack::Pampa::api_key,
339
+ :filename => $0,
340
+ :method => method,
341
+ :worker_name => PROCESS.fullWorkerName,
342
+ :id_proxy => self.proxy.nil? ? nil : self.proxy.id,
343
+ :id_lnuser => self.lnuser.nil? ? nil : self.lnuser.id,
344
+ :worker_assigned_process => PROCESS.worker.assigned_process,
345
+ :profile_name => self.profile_name,
346
+ :agent_name => self.agent_name,
347
+ })
348
+ parsed = JSON.parse(res.body)
349
+ if parsed['status'] != "success"
350
+ raise "Error Tracing BrowserActivity: #{parsed['status']}"
351
+ end
152
352
  end
153
353
  end
154
354
 
@@ -201,7 +401,7 @@ module BlackStack
201
401
  BlackStack::BrowserFactory.browser.push_screenshot
202
402
  super
203
403
  end
204
-
404
+
205
405
  def execute_script(script, *args)
206
406
  self.notify("execute_script")
207
407
  BlackStack::BrowserFactory.browser.push_screenshot
@@ -331,6 +531,14 @@ module BlackStack
331
531
  # Only TYPE_CHROME or TYPE_MIMIC are supported at this moment.
332
532
  @@type = nil
333
533
 
534
+ # Masking keystroke biometric signature
535
+ @@keystroke_signature = nil
536
+ @@keystroke_matrix = nil
537
+
538
+ # Masking keystroke biometric signature
539
+ @@mouse_signature = nil
540
+ @@mouse_hash = nil
541
+
334
542
  # Returns the PampaBrowser object
335
543
  def self.browser
336
544
  @@browser
@@ -341,6 +549,148 @@ module BlackStack
341
549
  [BlackStack::BrowserFactory::TYPE_CHROME, BlackStack::BrowserFactory::TYPE_MIMIC]
342
550
  end
343
551
 
552
+ # Returns the @@keystroke_signature attribute
553
+ def self.keystroke_signature
554
+ @@keystroke_signature
555
+ end
556
+
557
+ # Returns the @@keystroke_matrix attribute
558
+ def self.keystroke_matrix
559
+ @@keystroke_matrix
560
+ end
561
+
562
+ # Returns the @@mouse_signature attribute
563
+ def self.mouse_signature
564
+ @@mouse_signature
565
+ end
566
+
567
+ # Returns the @@keystroke_matrix attribute
568
+ def self.mouse_hash
569
+ @@mouse_hash
570
+ end
571
+
572
+ # Set the @@keystroke_signature attribute
573
+ def self.set_keystroke_signature(s)
574
+ return if s.nil?
575
+
576
+ raise 'The keystroke signature must be a GUID' if !s.guid?
577
+
578
+ # guid normalization
579
+ s = s.to_guid
580
+
581
+ # initializing class attributes
582
+ @@keystroke_signature = s
583
+ @@keystroke_matrix = []
584
+
585
+ # calculating the average writing speed
586
+ writing_speed = 0
587
+ s.split('').each { |c|
588
+ writing_speed += c.ord if c.ord != '-'.ord
589
+ }
590
+ writing_speed = writing_speed.to_f / 100000.to_f
591
+
592
+ # calculating average delay between each pair of keys
593
+ delays = s.split('') #.delete('-')
594
+ i = 0
595
+ while i<256
596
+ j = 0
597
+ k = 0
598
+ @@keystroke_matrix[i] = []
599
+ while j<256
600
+ k = 0 if k>=delays.size
601
+ @@keystroke_matrix[i][j] = writing_speed.to_f
602
+ @@keystroke_matrix[i][j] += delays[k-0].to_f / 100.to_f
603
+ @@keystroke_matrix[i][j] += delays[k-1].to_f / 100.to_f if k>0
604
+ @@keystroke_matrix[i][j] += delays[k-2].to_f / 100.to_f if k>1
605
+ @@keystroke_matrix[i][j] += delays[k-3].to_f / 100.to_f if k>2
606
+ @@keystroke_matrix[i][j] += delays[k-4].to_f / 100.to_f if k>3
607
+ k+=1
608
+ j+=1
609
+ end
610
+ i+=1
611
+ end
612
+ end
613
+
614
+ # Set the @@mouse_signature attribute
615
+ def self.set_mouse_signature(s)
616
+ return if s.nil?
617
+
618
+ raise 'The mouse signature must be a GUID' if !s.guid?
619
+
620
+ # guid normalization
621
+ s = s.to_guid
622
+
623
+ # initializing class attributes
624
+ @@mouse_signature = s
625
+
626
+ # initialize mouse hash
627
+ max = 16*32-1 # it is the sum of all the hex number from the max GUID: FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFFF
628
+ sum = 0 # it is the sum of all the hex number from the GUID in the `s` parameter
629
+ s.gsub('-', '').upcase.split('').each { |c|
630
+ i = "0x#{c}".to_i(16)
631
+ sum += i
632
+ }
633
+ rate = sum.to_f / max.to_f
634
+ @@mouse_hash = {
635
+ :noise => rate,
636
+ }
637
+ end
638
+
639
+ # kill both multilogin.exe and headless.exe processes.
640
+ def self.mla_kill()
641
+ # this is to run many mlalisteners in paralell
642
+ # for more information refer to #166:doc:
643
+ # https://github.com/leandrosardi/blackstack/issues/166
644
+ f = File.open('mlalistener.lock', File::CREAT)
645
+
646
+ # kill tasks
647
+ system("taskkill /F /T /IM headless.exe 1>nul 2>nul")
648
+ system("taskkill /F /T /IM multilogin.exe 1>nul 2>nul")
649
+
650
+ # this is to run many mlalisteners in paralell
651
+ # for more information refer to #166:doc:
652
+ # https://github.com/leandrosardi/blackstack/issues/166
653
+ f.flock(File::LOCK_UN)
654
+ end
655
+
656
+ # start headliness.exe, and relaunch it if it shuts down.
657
+ def self.mla_listener(h)
658
+ port = h[:port]
659
+ verbose = h[:verbose]
660
+
661
+ port = 45000 if port.nil?
662
+ verbose = true if verbose.nil?
663
+ begin
664
+ while (true)
665
+ puts 'verbose mode is active.' if verbose
666
+ puts "listening port #{port.to_s}." if verbose
667
+ start_time = Time.now
668
+ BlackStack::BrowserFactory::mla_kill
669
+ if (/cygwin|mswin|mingw|bccwin|wince|emx/ =~ RUBY_PLATFORM) != nil
670
+ system("headless.exe -port #{port} 1>nul 2>nul")
671
+ else
672
+ # TODO: parametrizar la ruta de headless.sh
673
+ system("~/code/multilogin/headless.sh -port #{port}") # multilogin folder is uncompressed in the ~/code/ folder
674
+ end
675
+ #system("headless.exe -port #{port} 1>nul 2>nul")
676
+ end_time = Time.now
677
+ enlapsed_seconds = end_time - start_time
678
+ poll_seconds = 5 # parser.value('min_cycle_seconds')
679
+ sleep( poll_seconds - enlapsed_seconds ) if poll_seconds > enlapsed_seconds
680
+ end
681
+ rescue Interrupt => e
682
+ puts 'process interruption' if verbose
683
+ # stop this while(true) loop
684
+ rescue => e
685
+ puts "unexpected error: #{e.to_s}" if verbose
686
+ # nothing to do here.
687
+ # just restart the while
688
+ ensure
689
+ BlackStack::BrowserFactory::mla_kill
690
+ end
691
+ end
692
+
693
+
344
694
  # Returns the description string from a browser type code
345
695
  def self.typeDesc(type)
346
696
  return 'Chrome' if @@type == BlackStack::BrowserFactory::TYPE_CHROME
@@ -386,23 +736,29 @@ module BlackStack
386
736
  # For more information, read documentations about PROFILE_PID_LIST_FILENAME.
387
737
  # This method is used for chrome browsers only.
388
738
  def self.readPidToProfileList(profile_name)
389
- if profile_name != nil
390
- fname = PROFILE_PID_LIST_FILENAME.gsub('%PROFILE_NAME%', profile_name)
391
- s = File.read(fname)
392
- return s.split(",")
739
+ if @@browser.pampa_integration
740
+ if profile_name != nil
741
+ fname = PROFILE_PID_LIST_FILENAME.gsub('%PROFILE_NAME%', profile_name)
742
+ s = File.read(fname)
743
+ return s.split(",")
744
+ else
745
+ return []
746
+ end
393
747
  else
394
748
  return []
395
- end
749
+ end # if @@browser.pampa_integration
396
750
  end
397
751
 
398
752
  # Add the PID of this browser; and the PID of all the child processes of this, with name like /chrome\.exe/.
399
753
  # For more information, read documentations about PROFILE_PID_LIST_FILENAME.
400
754
  # This method is used for chrome browsers only.
401
755
  def self.updateProfileList
402
- addPidToProfileList(@@profile_name, @@pid)
403
- PROCESS.list().select { |p| p[:ppid] == @@pid && p[:executablepath] =~ /chrome\.exe/ }.each { |p2|
404
- addPidToProfileList(@@profile_name, p2[:pid])
405
- }
756
+ if @@browser.pampa_integration
757
+ addPidToProfileList(@@profile_name, @@pid)
758
+ PROCESS.list().select { |p| p[:ppid] == @@pid && p[:executablepath] =~ /chrome\.exe/ }.each { |p2|
759
+ addPidToProfileList(@@profile_name, p2[:pid])
760
+ }
761
+ end # if @@browser.pampa_integration
406
762
  end
407
763
 
408
764
  # Returns true if @@browser is not nil, or @@profile_name is not nil.
@@ -426,21 +782,25 @@ module BlackStack
426
782
 
427
783
  # Retorna true si el perfil en @@profile_name esta siendo trabajado por este proceso
428
784
  def self.itIsMe?
429
- # el archivo tiene una unica primera linea de ancho fijo
430
- line_chars = 100
431
- # obtengo el id de este proceso que esta corriendo
432
- my_pid = PROCESS.pid
433
- # bloqueo el archivo de este perfil
434
- @@fd_profile_lock.flock(File::LOCK_EX) if !@@fd_profile_lock.nil?
435
- # valido que my_pid no tenga mas caracteres que al ancho fijo
436
- raise 'Cannot work browser profile lockfile because the lenght if the id of this process is larger than the max number of chars allowed (#{line_chars.to_s})' if line_chars < my_pid.size
437
- # obtengo el id del proceso que se reservo este perfil
438
- @@fd_profile_lock.seek(-line_chars, IO::SEEK_CUR) if !@@fd_profile_lock.nil?
439
- lock_pid = @@fd_profile_lock.read.to_s
440
- # libero el archivo de este perfil
441
- @@fd_profile_lock.flock(File::LOCK_UN) if !@@fd_profile_lock.nil?
442
- # return
443
- my_pid.strip.upcase == lock_pid.strip.upcase
785
+ if @@browser.pampa_integration
786
+ # el archivo tiene una unica primera linea de ancho fijo
787
+ line_chars = 100
788
+ # obtengo el id de este proceso que esta corriendo
789
+ my_pid = PROCESS.pid
790
+ # bloqueo el archivo de este perfil
791
+ @@fd_profile_lock.flock(File::LOCK_EX) if !@@fd_profile_lock.nil?
792
+ # valido que my_pid no tenga mas caracteres que al ancho fijo
793
+ raise 'Cannot work browser profile lockfile because the lenght if the id of this process is larger than the max number of chars allowed (#{line_chars.to_s})' if line_chars < my_pid.size
794
+ # obtengo el id del proceso que se reservo este perfil
795
+ @@fd_profile_lock.seek(-line_chars, IO::SEEK_CUR) if !@@fd_profile_lock.nil?
796
+ lock_pid = @@fd_profile_lock.read.to_s
797
+ # libero el archivo de este perfil
798
+ @@fd_profile_lock.flock(File::LOCK_UN) if !@@fd_profile_lock.nil?
799
+ # return
800
+ return my_pid.strip.upcase == lock_pid.strip.upcase
801
+ else
802
+ return false
803
+ end
444
804
  end
445
805
 
446
806
  # This method is to open no more than one browser of the same profile at time.
@@ -449,41 +809,43 @@ module BlackStack
449
809
  # If the content of the file is different than the PID of this process, but such content is not equal to the PID of any other process alive, so this process is good to start the browser.
450
810
  # If the content of the file is different than the PID of this process, and such content is equal to the PID of another process alive, this method unlock the file and will raise an exception.
451
811
  def self.lockProfile
452
- # el archivo tiene una unica primera linea de ancho fijo
453
- line_chars = 100
454
- # bloqueo el archivo de este perfil
455
- @@fd_profile_lock.flock(File::LOCK_EX)
456
- # obtengo el id de este proceso que esta corriendo
457
- my_pid = PROCESS.pid
458
- # valido que my_pid no tenga mas caracteres que al ancho fijo
459
- raise 'Cannot work browser profile lockfile because the lenght if the id of this process is larger than the max number of chars allowed (#{line_chars.to_s})' if line_chars < my_pid.size
460
- # obtengo el id del proceso que se reservo este perfil
461
- lock_pid = @@fd_profile_lock.read.to_s
462
- #
463
- if lock_pid.size == 0
464
- @@fd_profile_lock.write my_pid
465
- @@fd_profile_lock.write ' ' * (line_chars - my_pid.size)
466
- @@fd_profile_lock.flush
467
- elsif lock_pid.strip.upcase != my_pid.strip.upcase
468
- # verifico si el proceso que reservo este perfil esta activo
469
- p = PROCESS.list().select { |h| h[:pid].strip.upcase == lock_pid.strip.upcase }.first
470
- lock_process_is_alive = !p.nil?
471
- # levanto una excepcion de bloqueo si este perfil fue reservado por otro proceso y el proceso sigue activo
472
- if lock_process_is_alive
473
- # libero el archivo de este perfil
474
- @@fd_profile_lock.flock(File::LOCK_UN)
475
- #
476
- raise 'Profile is locked'
477
- # me reservo este perfil si fue reservado por un proceso que ya no esta activo
478
- else # if lock_process_is_alive
479
- @@fd_profile_lock.seek(-line_chars, IO::SEEK_CUR)
812
+ if @@browser.pampa_integration
813
+ # el archivo tiene una unica primera linea de ancho fijo
814
+ line_chars = 100
815
+ # bloqueo el archivo de este perfil
816
+ @@fd_profile_lock.flock(File::LOCK_EX)
817
+ # obtengo el id de este proceso que esta corriendo
818
+ my_pid = PROCESS.pid
819
+ # valido que my_pid no tenga mas caracteres que al ancho fijo
820
+ raise 'Cannot work browser profile lockfile because the lenght if the id of this process is larger than the max number of chars allowed (#{line_chars.to_s})' if line_chars < my_pid.size
821
+ # obtengo el id del proceso que se reservo este perfil
822
+ lock_pid = @@fd_profile_lock.read.to_s
823
+ #
824
+ if lock_pid.size == 0
480
825
  @@fd_profile_lock.write my_pid
481
826
  @@fd_profile_lock.write ' ' * (line_chars - my_pid.size)
482
827
  @@fd_profile_lock.flush
483
- end # if lock_process_is_alive
484
- else # lock_pid.strip.upcase == my_pid.strip.upcase
485
- # go ahead
486
- end # if lock_pid.size == 0
828
+ elsif lock_pid.strip.upcase != my_pid.strip.upcase
829
+ # verifico si el proceso que reservo este perfil esta activo
830
+ p = PROCESS.list().select { |h| h[:pid].strip.upcase == lock_pid.strip.upcase }.first
831
+ lock_process_is_alive = !p.nil?
832
+ # levanto una excepcion de bloqueo si este perfil fue reservado por otro proceso y el proceso sigue activo
833
+ if lock_process_is_alive
834
+ # libero el archivo de este perfil
835
+ @@fd_profile_lock.flock(File::LOCK_UN)
836
+ #
837
+ raise 'Profile is locked'
838
+ # me reservo este perfil si fue reservado por un proceso que ya no esta activo
839
+ else # if lock_process_is_alive
840
+ @@fd_profile_lock.seek(-line_chars, IO::SEEK_CUR)
841
+ @@fd_profile_lock.write my_pid
842
+ @@fd_profile_lock.write ' ' * (line_chars - my_pid.size)
843
+ @@fd_profile_lock.flush
844
+ end # if lock_process_is_alive
845
+ else # lock_pid.strip.upcase == my_pid.strip.upcase
846
+ # go ahead
847
+ end # if lock_pid.size == 0
848
+ end # if @@browser.pampa_integration
487
849
  end # def self.lockProfile()
488
850
 
489
851
  # This method is to open no more than one browser of the same profile at time.
@@ -541,101 +903,82 @@ module BlackStack
541
903
 
542
904
  # Close the browser only if itIsMe? returns true.
543
905
  # Set nil to @@browser, @@pid, @@profile_name, @@driver, @@type.
544
- def self.destroy
545
- #if self.itIsMe?
546
- begin
547
- # borro el contenido de PROFILE_LOCK_FILENAME
548
- #if !@@profile_name.nil?
549
- # fname = PROFILE_LOCK_FILENAME.gsub('%PROFILE_NAME%', @@profile_name)
550
- # File.open(fname,'w')
551
- #end
906
+ def self.destroy(clear_variables=true)
907
+ begin
908
+ # borro el contenido de PROFILE_LOCK_FILENAME
909
+ #if !@@profile_name.nil?
910
+ # fname = PROFILE_LOCK_FILENAME.gsub('%PROFILE_NAME%', @@profile_name)
911
+ # File.open(fname,'w')
912
+ #end
552
913
 
553
- #
554
- if @@type == BlackStack::BrowserFactory::TYPE_CHROME
555
- # kill all child process
556
- self.lockProfileList()
557
- self.readPidToProfileList(@@profile_name).each { |pid|
558
- MyProcess.kill(pid)
559
- self.removePidToProfileList(@@profile_name, pid)
560
- }
561
- self.releaseProfileList()
914
+ #
915
+ if @@type == BlackStack::BrowserFactory::TYPE_CHROME
916
+ # kill all child process
917
+ self.lockProfileList()
918
+ self.readPidToProfileList(@@profile_name).each { |pid|
919
+ MyProcess.kill(pid)
920
+ self.removePidToProfileList(@@profile_name, pid)
921
+ }
922
+ self.releaseProfileList()
562
923
 
563
- begin
564
- @@browser.close if !@@browser.nil?
565
- rescue
566
- end
924
+ begin
925
+ @@browser.close if !@@browser.nil?
926
+ rescue
927
+ end
567
928
 
568
- elsif @@type == BlackStack::BrowserFactory::TYPE_MIMIC
569
- =begin
570
- puts ''
571
- begin
572
- print 'Close browser?... '
573
- if !@@browser.nil?
574
- puts 'yes'
575
- print 'Close browser... '
576
- @@browser.close
577
- puts 'done'
578
- else
579
- puts 'no'
580
- end
581
- rescue => e
582
- puts "error: #{e.to_s}"
929
+ elsif @@type == BlackStack::BrowserFactory::TYPE_MIMIC
930
+ i = 0
931
+ body = nil
932
+ max = 90
933
+ success = 0 # I have to call this access point many times to get the browser closed.
934
+ max_success_required = 30
935
+ while i<max && success<max_success_required
936
+ i+=1
937
+ # cierro el perfil por si estaba corriendo
938
+ if BlackStack::StealthBrowserAutomation::Multilogin::mla_version == BlackStack::StealthBrowserAutomation::Multilogin::VERSION_4
939
+ url = "http://127.0.0.1:#{BlackStack::StealthBrowserAutomation::Multilogin::mla_local_port}/api/v1/profile/stop?profileId=#{@@profile_name}"
940
+ ssl = true
941
+ elsif BlackStack::StealthBrowserAutomation::Multilogin::mla_version == BlackStack::StealthBrowserAutomation::Multilogin::VERSION_5
942
+ url = "http://localhost.multiloginapp.com:#{BlackStack::StealthBrowserAutomation::Multilogin::mla_local_port}/api/v1/profile/stop?profileId=#{@@profile_name}"
943
+ ssl = false
944
+ else
945
+ raise 'Invalid Multilogin Version'
583
946
  end
584
- =end
947
+ uri = URI.parse(url)
585
948
  =begin
586
- begin
587
- # TODO: entender porque debo hacer esta llamada a start para que el perfil se cierre cuando ya estaba iniciado
588
- puts ''
589
- print 'Close browser?... '
590
- if !@@profile_name.nil?
591
- puts 'yes'
592
- print 'Close browser... '
593
- BlackStack::BrowserFactory.mimic(@@profile_name)
594
- puts 'done'
595
- print 'Close browser (2)... '
596
- @@browser.close
597
- puts 'done'
598
- else
599
- puts 'no'
600
- end
601
- rescue
602
- # nothing here
949
+ Net::HTTP.start(uri.host, uri.port, :use_ssl => ssl, :verify_mode => OpenSSL::SSL::VERIFY_NONE) do |http|
950
+ req = Net::HTTP::Get.new(uri)
951
+ req['Content-Type'] = 'application/json'
952
+ if http.request(req).body
953
+ body = http.request(req).body
954
+ end
603
955
  end
604
956
  =end
605
-
606
- i = 0
607
- max = 90
608
- success = 0 # I have to call this access point many times to get the browser closed.
609
- max_success_required = 30
610
- while i<max && success<max_success_required
611
- #puts ''
612
- i+=1
613
- url = "http://127.0.0.1:#{BlackStack::StealthBrowserAutomation::Multilogin::mla_local_port}/api/v1/profile/stop?profileId=#{@@profile_name}"
614
- #print "Api Call ##{i.to_s} to: #{url}... "
615
- uri = URI.parse(url)
616
- body = Net::HTTP.get(uri)
617
- res = JSON.parse(body)
618
- success += 1 if res['status'].to_s == 'OK'
619
- #puts "response: #{res['status'].to_s}"
620
- end # while i<max && && success<max_success_required
621
- raise "Error requesting Mimic to stop (tried #{i.to_s} times)" if success<max_success_required
622
-
623
- else
624
- raise 'Cannot destroy browser due unknown browser type'
625
- end
626
- rescue => e
627
- self.release
628
- raise e
957
+ body = Net::HTTP.get(uri)
958
+ res = JSON.parse(body)
959
+ success += 1 if res['status'].to_s == 'OK'
960
+ #puts "i:#{i.to_s}:."
961
+ #puts "body:#{body.to_s}:."
962
+ #puts "res.status:#{res['status'].to_s}:."
963
+ end # while i<max && && success<max_success_required
964
+ raise "Error requesting Mimic to stop (tried #{i.to_s} times)" if success<max_success_required
965
+ else
966
+ # unknown browser type
629
967
  end
630
- #end # if self.isItMe?
968
+ rescue => e
969
+ self.release
970
+ raise e
971
+ end
631
972
 
632
- # reseteo las variables
633
- @@browser = nil
634
- @@pid = nil
635
- @@profile_name = nil
636
- @@driver = nil
637
- @@type = nil
638
- end
973
+ # reseteo las variables
974
+ if clear_variables
975
+ @@browser = nil
976
+ @@pid = nil
977
+ @@profile_name = nil
978
+ @@driver = nil
979
+ @@type = nil
980
+ end
981
+ end # if self.destroy
639
982
 
640
983
  # Returns the version of the availalble chrome browser.
641
984
  def self.chrome_version
@@ -684,13 +1027,29 @@ puts 'no'
684
1027
  i += 1
685
1028
 
686
1029
  # cierro el perfil por si estaba corriendo
687
- url = "http://127.0.0.1:#{BlackStack::StealthBrowserAutomation::Multilogin::mla_local_port}/api/v1/profile/stop?profileId=#{@@profile_name}"
688
- uri = URI.parse(url)
1030
+ if BlackStack::StealthBrowserAutomation::Multilogin::mla_version == BlackStack::StealthBrowserAutomation::Multilogin::VERSION_4
1031
+ url = "http://127.0.0.1:#{BlackStack::StealthBrowserAutomation::Multilogin::mla_local_port}/api/v1/profile/stop?profileId=#{@@profile_name}"
1032
+ ssl = true
1033
+ elsif BlackStack::StealthBrowserAutomation::Multilogin::mla_version == BlackStack::StealthBrowserAutomation::Multilogin::VERSION_5
1034
+ url = "http://localhost.multiloginapp.com:#{BlackStack::StealthBrowserAutomation::Multilogin::mla_local_port}/api/v1/profile/stop?profileId=#{@@profile_name}"
1035
+ ssl = false
1036
+ else
1037
+ raise 'Invalid Multilogin Version'
1038
+ end
1039
+ uri = URI.parse(url)
689
1040
  body = Net::HTTP.get(uri)
690
1041
  res = JSON.parse(body)
691
1042
 
692
- url = "http://127.0.0.1:#{BlackStack::StealthBrowserAutomation::Multilogin::mla_local_port}/api/v1/profile/start?automation=true&profileId=#{@@profile_name}"
693
- uri = URI.parse(url)
1043
+ if BlackStack::StealthBrowserAutomation::Multilogin::mla_version == BlackStack::StealthBrowserAutomation::Multilogin::VERSION_4
1044
+ url = "http://127.0.0.1:#{BlackStack::StealthBrowserAutomation::Multilogin::mla_local_port}/api/v1/profile/start?automation=true&profileId=#{@@profile_name}"
1045
+ ssl = true
1046
+ elsif BlackStack::StealthBrowserAutomation::Multilogin::mla_version == BlackStack::StealthBrowserAutomation::Multilogin::VERSION_5
1047
+ url = "http://localhost.multiloginapp.com:#{BlackStack::StealthBrowserAutomation::Multilogin::mla_local_port}/api/v1/profile/start?automation=true&profileId=#{@@profile_name}"
1048
+ ssl = false
1049
+ else
1050
+ raise 'Invalid Multilogin Version'
1051
+ end
1052
+ uri = URI.parse(url)
694
1053
  body = Net::HTTP.get(uri)
695
1054
  res = JSON.parse(body)
696
1055
 
@@ -762,17 +1121,22 @@ puts 'no'
762
1121
  h[:load_timeout].nil? ? DEFAULT_LOAD_TIMEOUT : h[:load_timeout],
763
1122
  h[:user_agent],
764
1123
  h[:profile_name],
765
- h[:chromedriver_path]
1124
+ h[:chromedriver_path],
1125
+ h[:pampa_integration],
1126
+ h[:keystroke_signature],
1127
+ h[:mouse_signature]
766
1128
  )
767
1129
  end
768
1130
 
769
1131
  # Launch a chrome browser.
770
- def self.launch_chrome(proxy=nil, load_timeout=DEFAULT_LOAD_TIMEOUT, user_agent=nil, profile_name=nil, chromedriver_path=nil)
1132
+ def self.launch_chrome(proxy=nil, load_timeout=DEFAULT_LOAD_TIMEOUT, user_agent=nil, profile_name=nil, chromedriver_path=nil, pampa_integration=true, keystroke_signature=nil, mouse_signature=nil)
771
1133
  #
772
1134
  @@browser = nil
773
1135
  @@driver = nil
774
1136
  @@type = BlackStack::BrowserFactory::TYPE_CHROME
775
1137
  @@profile_name = profile_name
1138
+ self.set_keystroke_signature(keystroke_signature)
1139
+ self.set_mouse_signature(mouse_signature)
776
1140
 
777
1141
  #
778
1142
  #fname = PROFILE_LOCK_FILENAME.gsub('%PROFILE_NAME%', @@profile_name)
@@ -790,14 +1154,16 @@ puts 'no'
790
1154
  while (@@browser == nil)
791
1155
 
792
1156
  begin
793
- # Levantar el flag de reserva a mi favor
794
- self.lock()
795
-
796
- #
797
- #self.lockProfile()
798
-
799
- #
800
- self.lockProfileList()
1157
+ if pampa_integration
1158
+ # Levantar el flag de reserva a mi favor
1159
+ self.lock()
1160
+
1161
+ #
1162
+ #self.lockProfile()
1163
+
1164
+ #
1165
+ self.lockProfileList()
1166
+ end # pampa_integration
801
1167
 
802
1168
  #
803
1169
  client = Selenium::WebDriver::Remote::Http::Default.new
@@ -839,29 +1205,34 @@ puts 'no'
839
1205
 
840
1206
  Selenium::WebDriver::Chrome.driver_path = chromedriver_path if !chromedriver_path.nil?
841
1207
  Selenium::WebDriver::Chrome.driver_path = DEFAULT_CHROMEDRIVER_PATH if chromedriver_path.nil?
842
-
1208
+
843
1209
  @@driver = Selenium::WebDriver.for :chrome, :switches => switches
844
1210
  bridge = @@driver.instance_variable_get(:@bridge)
845
1211
  service = bridge.instance_variable_get(:@service)
846
1212
  process = service.instance_variable_get(:@process)
847
- @@pid = process.pid.to_s # es el PID del chromedriver
1213
+ # @@pid = process.pid.to_s # es el PID del chromedriver # TODO: hacer andar esto con selenium 3.14.1
848
1214
  @@driver.manage.window().maximize()
849
1215
  @@browser = PampaBrowser.new(@@driver)
850
1216
  @@browser.proxy = proxy.clone if !proxy.nil?
851
1217
  @@browser.agent_name = agent
852
1218
  @@browser.profile_name = profile_name
1219
+ @@browser.pampa_integration = pampa_integration
853
1220
  self.updateProfileList
854
-
1221
+
855
1222
  #
856
- rescue => e # TODO: Se atrapa una expecion porque sigue ocurreiendo el error reportado en el issue #134 y #129
857
- #
858
- p1 = PROCESS.list().select { |p| p[:ppid] == PROCESS.pid && p[:executablepath] =~ /chromedriver\.exe/ }.first
859
- if (p1 != nil)
860
- @@pid = p1[:pid]
861
- self.updateProfileList
862
- end
863
- self.releaseProfileList()
864
-
1223
+ rescue => e
1224
+ # TODO: Se atrapa una expecion porque sigue ocurreiendo el error reportado en el issue #134 y #129
1225
+ if !@@browser.nil?
1226
+ if @@browser.pampa_integration
1227
+ p1 = PROCESS.list().select { |p| p[:ppid] == PROCESS.pid && p[:executablepath] =~ /chromedriver\.exe/ }.first
1228
+ if (p1 != nil)
1229
+ @@pid = p1[:pid]
1230
+ self.updateProfileList
1231
+ end
1232
+ self.releaseProfileList()
1233
+ end
1234
+ end
1235
+
865
1236
  #
866
1237
  #self.releaseProfile()
867
1238