Class: JsshSocket

Inherits:
Object show all
Defined in:
lib/vapir-firefox/jssh_socket.rb

Constant Summary collapse

PROMPT =

end

"\n> "
PrototypeFile =
File.join(File.dirname(__FILE__), "prototype.functional.js")
DEFAULT_SOCKET_TIMEOUT =
64
SHORT_SOCKET_TIMEOUT =
(2**-2).to_f
@@default_jssh_ip =

IP Address of the machine where the script is to be executed. Default to localhost.

"127.0.0.1"
@@default_jssh_port =
9997

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(options = {}) ⇒ JsshSocket

Connects a new socket to jssh Takes options:

  • :jssh_ip => the ip to connect to, default 127.0.0.1

  • :jssh_port => the port to connect to, default 9997

  • :send_prototype => true|false, whether to load and send the Prototype library (the functional programming part of it anyway, and JSON bits)



91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
# File 'lib/vapir-firefox/jssh_socket.rb', line 91

def initialize(options={})
  @ip=options[:jssh_ip] || @@default_jssh_ip
  @port=options[:jssh_port] || @@default_jssh_port
  @prototype=options.key?(:send_prototype) ? options[:send_prototype] : true
  begin
    @socket = TCPSocket::new(@ip, @port)
    @socket.sync = true
    @expecting_prompt=false # initially, the welcome message comes before the prompt, so this so this is false to start with 
    @expecting_extra_maybe=false
    welcome="Welcome to the Mozilla JavaScript Shell!\n"
    read=read_value
    if !read
      @expecting_extra_maybe=true
      raise JsshUnableToStart, "Something went wrong initializing - no response" 
    elsif read != welcome
      @expecting_extra_maybe=true
      raise JsshUnableToStart, "Something went wrong initializing - message #{read.inspect} != #{welcome.inspect}" 
    end
  rescue Errno::ECONNREFUSED, Errno::ECONNRESET, Errno::ECONNABORTED, Errno::EPIPE
    err=JsshUnableToStart.new("Could not connect to JSSH sever #{@ip}:#{@port}. Ensure that Firefox is running and has JSSH configured, or try restarting firefox.\nMessage from TCPSocket:\n#{$!.message}")
    err.set_backtrace($!.backtrace)
    raise err
  end
  if @prototype
    ret=send_and_read(File.read(PrototypeFile))
    if ret != "done!"
      @expecting_extra_maybe=true
      raise JsshError, "Something went wrong loading Prototype - message #{ret.inspect}"
    end
  end
  ret=send_and_read("(function()
  { nativeJSON=Components.classes['@mozilla.org/dom/json;1'].createInstance(Components.interfaces.nsIJSON);
    nativeJSON_encode_length=function(object)
    { var encoded=nativeJSON.encode(object);
      return encoded.length.toString()+\"\\n\"+encoded;
    }
    return 'done!';
  })()")
  if ret != "done!"
    @expecting_extra_maybe=true
    raise JsshError, "Something went wrong initializing native JSON - message #{ret.inspect}"
  end
  temp_object.assign({})
end

Instance Attribute Details

#ipObject (readonly)

Returns the value of attribute ip.



84
85
86
# File 'lib/vapir-firefox/jssh_socket.rb', line 84

def ip
  @ip
end

#portObject (readonly)

Returns the value of attribute port.



84
85
86
# File 'lib/vapir-firefox/jssh_socket.rb', line 84

def port
  @port
end

#prototypeObject (readonly)

Returns the value of attribute prototype.



84
85
86
# File 'lib/vapir-firefox/jssh_socket.rb', line 84

def prototype
  @prototype
end

Instance Method Details

#assert_socketObject

raises an informative error if the socket is down for some reason



533
534
535
536
537
538
539
540
541
542
543
544
545
546
# File 'lib/vapir-firefox/jssh_socket.rb', line 533

def assert_socket
  begin
    actual, expected=if prototype
      [value_json('["foo"]'), ["foo"]]
    else
      [value('"foo"'), "foo"]
    end
  rescue Errno::ECONNREFUSED, Errno::ECONNRESET, Errno::ECONNABORTED, Errno::EPIPE
    raise(JsshConnectionError, "Encountered a socket error while checking the socket.\n#{$!.class}\n#{$!.message}", $!.backtrace)
  end
  unless expected==actual
    raise JsshError, "The socket seems to have a problem: sent #{expected.inspect} but got back #{actual.inspect}"
  end
end

#assign(js_left, js_right) ⇒ Object

assigns to the javascript reference on the left the javascript expression on the right. returns the value of the expression as reported by JSSH, which will be a string, the expression’s toString. Uses #value; see its documentation.



324
325
326
# File 'lib/vapir-firefox/jssh_socket.rb', line 324

def assign(js_left, js_right)
  value("#{js_left}= #{js_right}")
end

#assign_json(js_left, rb_right) ⇒ Object

assigns to the javascript reference on the left the object on the right. Assuming the right object can be converted to JSON, the javascript value will be the equivalent javascript data type to the ruby object. Will return the assigned value, converted from its javascript value back to ruby. So, the return value won’t be exactly equivalent if you use symbols for example.

>> jssh_socket.assign_json(‘bar’, => [:baz, ‘qux’])

> “qux”]

Uses #value_json; see its documentation.



420
421
422
423
424
# File 'lib/vapir-firefox/jssh_socket.rb', line 420

def assign_json(js_left, rb_right)
  ensure_prototype
  js_right=rb_right.to_jssh
  value_json("#{js_left}=#{js_right}")
end

#call(js_function, *js_args) ⇒ Object

calls to the given function (javascript reference to a function) passing it the given arguments (javascript expressions). returns the return value of the function, a string, the toString of the javascript value. Uses #value; see its documentation.



331
332
333
# File 'lib/vapir-firefox/jssh_socket.rb', line 331

def call(js_function, *js_args)
  value("#{js_function}(#{js_args.join(', ')})")
end

#call_json(js_function, *rb_args) ⇒ Object

calls to the given function (javascript reference to a function) passing it the given arguments, each argument being converted from a ruby object to a javascript object via JSON. returns the return value of the function, of equivalent type to the javascript return value, converted from javascript to ruby via JSON. Uses #value_json; see its documentation.



431
432
433
434
435
# File 'lib/vapir-firefox/jssh_socket.rb', line 431

def call_json(js_function, *rb_args)
  ensure_prototype
  js_args=rb_args.map{|arg| arg.to_jssh}
  value_json("#{js_function}(#{js_args.join(', ')})")
end

#ComponentsObject



525
526
527
# File 'lib/vapir-firefox/jssh_socket.rb', line 525

def Components
  @components ||= object('Components')
end

#ensure_prototypeObject

raises error if the prototype library (needed for JSON stuff in javascript) has not been loaded



469
470
471
472
473
# File 'lib/vapir-firefox/jssh_socket.rb', line 469

def ensure_prototype
  unless prototype
    raise JsshError, "Cannot invoke JSON on a Jssh session that does not have the Prototype library"
  end
end

#error_or_val_json(val, js) ⇒ Object



382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
# File 'lib/vapir-firefox/jssh_socket.rb', line 382

def error_or_val_json(val, js)
  if !val || val==''
    @expecting_extra_maybe=true
    raise JsshError, "received no value! may have timed out waiting for a value that was not coming."
  end
  if val=="SyntaxError: syntax error"
    raise JsshSyntaxError, val
  end
  errord_and_val=parse_json(val)
  unless errord_and_val.is_a?(Hash) && errord_and_val.keys.sort == ['errored', 'value'].sort
    raise RuntimeError, "unexpected result: \n\t#{errord_and_val.inspect} \nencountered parsing value: \n\t#{val.inspect} \nreturned from expression: \n\t#{js.inspect}"
  end
  errord=errord_and_val['errored']
  val= errord_and_val['value']
  if errord
    case val
    when Hash
      js_error(val['name'],val['message'],js,val)
    when String
      js_error(nil, val, js)
    else
      js_error(nil, val.inspect, js)
    end
  else
    val
  end
end

#getWindowsObject



528
529
530
# File 'lib/vapir-firefox/jssh_socket.rb', line 528

def getWindows
  @getwindows ||= object('getWindows()')
end

#handle(js_expr, *args) ⇒ Object

if the given javascript expression ends with an = symbol, #handle calls to #assign assuming it is given one argument; if the expression refers to a function, calls that function with the given arguments using #call; if the expression is some other value, returns that value (its javascript toString), calling #value, assuming given no arguments. Uses #value; see its documentation.



340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
# File 'lib/vapir-firefox/jssh_socket.rb', line 340

def handle(js_expr, *args)
  if js_expr=~/=\z/ # doing assignment
    js_left=$`
    if args.size != 1
      raise ArgumentError, "Assignment (#{js_expr}) must take one argument"
    end
    assign(js_left, *args)
  else
    type=typeof(js_expr)
    case type
    when "function"
      call(js_expr, *args)
    when "undefined"
      raise JsshUndefinedValueError, "undefined expression #{js_expr.inspect}"
    else
      if !args.empty?
        raise ArgumentError, "Cannot pass arguments to expression #{js_expr.inspect} of type #{type}"
      end
      value(js_expr)
    end
  end
end

#handle_json(js_expr, *args) ⇒ Object

does the same thing as #handle, but with json, calling #assign_json, #value_json, or #call_json. if the given javascript expression ends with an = symbol, #handle_json calls to #assign_json assuming it is given one argument; if the expression refers to a function, calls that function with the given arguments using #call_json; if the expression is some other value, returns that value, converted to ruby via JSON, assuming given no arguments. Uses #value_json; see its documentation.



444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
# File 'lib/vapir-firefox/jssh_socket.rb', line 444

def handle_json(js_expr, *args)
  ensure_prototype
  if js_expr=~/=\z/ # doing assignment
    js_left=$`
    if args.size != 1
      raise ArgumentError, "Assignment (#{js_expr}) must take one argument"
    end
    assign_json(js_left, *args)
  else
    type=typeof(js_expr)
    case type
    when "function"
      call_json(js_expr, *args)
    when "undefined"
      raise JsshUndefinedValueError, "undefined expression #{js_expr}"
    else
      if !args.empty?
        raise ArgumentError, "Cannot pass arguments to expression #{js_expr.inspect} of type #{type}"
      end
      value_json(js_expr)
    end
  end
end

#inspectObject



548
549
550
# File 'lib/vapir-firefox/jssh_socket.rb', line 548

def inspect
  "\#<#{self.class.name}:0x#{"%.8x"%(self.hash*2)} #{[:ip, :port, :prototype].map{|attr| aa="@#{attr}";aa+'='+instance_variable_get(aa).inspect}.join(', ')}>"
end

#instanceof(js_expression, js_interface) ⇒ Object



492
493
494
# File 'lib/vapir-firefox/jssh_socket.rb', line 492

def instanceof(js_expression, js_interface)
  value_json "(#{js_expression}) instanceof (#{js_interface})"
end

#object(ref) ⇒ Object



515
516
517
# File 'lib/vapir-firefox/jssh_socket.rb', line 515

def object(ref)
  JsshObject.new(ref, self, :debug_name => ref)
end

#object_in_temp(ref) ⇒ Object



518
519
520
# File 'lib/vapir-firefox/jssh_socket.rb', line 518

def object_in_temp(ref)
  object(ref).store_rand_temp
end

#parse_json(json) ⇒ Object

parses the given JSON string using ActiveSupport::JSON.decode Raises ActiveSupport::JSON::ParseError if given a blank string, something that is not a string, or a string that contains invalid JSON

Raises:

  • (err_class)


499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
# File 'lib/vapir-firefox/jssh_socket.rb', line 499

def parse_json(json)
  err_class=JSON::ParserError
  decoder=JSON.method(:parse)
  # err_class=ActiveSupport::JSON::ParseError
  # decoder=ActiveSupport::JSON.method(:decode)
  raise err_class, "Not a string! got: #{json.inspect}" unless json.is_a?(String)
  raise err_class, "Blank string!" if json==''
  begin
    return decoder.call(json)
  rescue err_class
    err=$!.class.new($!.message+"\nParsing: #{json.inspect}")
    err.set_backtrace($!.backtrace)
    raise err
  end
end

#send_and_read(js_expr, options = {}) ⇒ Object



283
284
285
286
287
288
289
290
# File 'lib/vapir-firefox/jssh_socket.rb', line 283

def send_and_read(js_expr, options={})
#    logger.add(-1) { "SEND_AND_READ is starting. options=#{options.inspect}" }
  @last_expression=js_expr
  js_expr=js_expr+"\n" unless js_expr =~ /\n\z/
#    logger.debug { "SEND_AND_READ sending #{js_expr.inspect}" }
  @socket.send(js_expr, 0)
  return read_value(options)
end

#temp_objectObject



522
523
524
# File 'lib/vapir-firefox/jssh_socket.rb', line 522

def temp_object
  @temp_object ||= object('JsshTemp')
end

#typeof(expression) ⇒ Object

returns the type of the given expression using javascript typeof operator, with the exception that if the expression is null, returns ‘null’ - whereas typeof(null) in javascript returns ‘object’



477
478
479
480
481
482
483
484
485
486
487
488
489
490
# File 'lib/vapir-firefox/jssh_socket.rb', line 477

def typeof(expression)
  ensure_prototype
  js="try
{ nativeJSON_encode_length({errored: false, value: (function(object){ return (object===null) ? 'null' : (typeof object); })(#{expression})});
} catch(e)
{ if(e.name=='ReferenceError')
{ nativeJSON_encode_length({errored: false, value: 'undefined'});
}
else
{ nativeJSON_encode_length({errored: true, value: Object.extend({}, e)});
}
}"
  error_or_val_json(send_and_read(js, :length_before_value => true),js)
end

#value(js) ⇒ Object

returns the value of the given javascript expression, as reported by JSSH. This will be a string, the given expression’s toString.



315
316
317
318
319
# File 'lib/vapir-firefox/jssh_socket.rb', line 315

def value(js)
  # this is wrapped in a function so that ...
  # dang, now I can't remember. I'm sure I had a good reason at the time. 
  send_and_read("(function(){return #{js}})()")
end

#value_json(js, options = {}) ⇒ Object

returns the value of the given javascript expression. Assuming that it can be converted to JSON, will return the equivalent ruby data type to the javascript value. Will raise an error if the javascript errors.

Raises:

  • (ArgumentError)


366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
# File 'lib/vapir-firefox/jssh_socket.rb', line 366

def value_json(js, options={})
  options={:error_on_undefined => true}.merge(options)
  raise ArgumentError, "Expected a string containing a javascript expression! received #{js.inspect} (#{js.class})" unless js.is_a?(String)
  ensure_prototype
  ref_error=options[:error_on_undefined] ? "typeof(result)=='undefined' ? {errored: true, value: {'name': 'ReferenceError', 'message': 'undefined expression in: '+result_f.toString()}} : " : ""
  wrapped_js=
    "try
     { var result_f=(function(){return #{js}});
       var result=result_f();
       nativeJSON_encode_length(#{ref_error} {errored: false, value: result});
     }catch(e)
     { nativeJSON_encode_length({errored: true, value: Object.extend({}, e)});
     }"
  val=send_and_read(wrapped_js, options.merge(:length_before_value => true))
  error_or_val_json(val, js)
end