java_script_agent.rb 4.6KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170
  1. require 'date'
  2. require 'cgi'
  3. module Agents
  4. class JavaScriptAgent < Agent
  5. default_schedule "never"
  6. description <<-MD
  7. This Agent allows you to write code in JavaScript that can create and receive events. If other Agents aren't meeting your needs, try this one!
  8. At the moment, all code should be written in the `code` option. In the future, a full editor will be provided.
  9. You can implement `Agent.check` and `Agent.receive` as you see fit. The following methods will be available on Agent in the JavaScript environment:
  10. * `this.createEvent(payload)`
  11. * `this.incomingEvents()`
  12. * `this.memory()`
  13. * `this.memory(key)`
  14. * `this.memory(keyToSet, valueToSet)`
  15. * `this.options()`
  16. * `this.options(key)`
  17. * `this.log(message)`
  18. * `this.error(message)`
  19. MD
  20. def validate_options
  21. errors.add(:base, "The 'code' option is required") unless options['code'].present?
  22. end
  23. def working?
  24. return false if recent_error_logs?
  25. if options['expected_update_period_in_days'].present?
  26. return false unless event_created_within?(options['expected_update_period_in_days'])
  27. end
  28. if options['expected_receive_period_in_days'].present?
  29. return false unless last_receive_at && last_receive_at > options['expected_receive_period_in_days'].to_i.days.ago
  30. end
  31. true
  32. end
  33. def check
  34. log_errors do
  35. execute_js("check")
  36. end
  37. end
  38. def receive(incoming_events)
  39. log_errors do
  40. execute_js("receive", incoming_events)
  41. end
  42. end
  43. def default_options
  44. js_code = <<-JS
  45. Agent.check = function() {
  46. if (this.options('make_event')) {
  47. this.createEvent({ 'message': 'I made an event!' });
  48. var callCount = this.memory('callCount') || 0;
  49. this.memory('callCount', callCount + 1);
  50. }
  51. };
  52. Agent.receive = function() {
  53. var events = this.incomingEvents();
  54. for(var i = 0; i < events.length; i++) {
  55. this.createEvent({ 'message': 'I got an event!', 'event_was': events[i].payload });
  56. }
  57. }
  58. JS
  59. {
  60. "code" => js_code.gsub(/[\n\r\t]/, '').strip,
  61. 'expected_receive_period_in_days' => "2",
  62. 'expected_update_period_in_days' => "2"
  63. }
  64. end
  65. private
  66. def execute_js(js_function, incoming_events = [])
  67. js_function = js_function == "check" ? "check" : "receive"
  68. context = V8::Context.new
  69. context.eval(setup_javascript)
  70. context["doCreateEvent"] = lambda { |a, y| create_event(payload: clean_nans(JSON.parse(y))).payload.to_json }
  71. context["getIncomingEvents"] = lambda { |a| incoming_events.to_json }
  72. context["getOptions"] = lambda { |a, x| options.to_json }
  73. context["doLog"] = lambda { |a, x| log x }
  74. context["doError"] = lambda { |a, x| error x }
  75. context["getMemory"] = lambda do |a, x, y|
  76. if x && y
  77. memory[x] = clean_nans(y)
  78. else
  79. memory.to_json
  80. end
  81. end
  82. context.eval(options['code'])
  83. context.eval("Agent.#{js_function}();")
  84. end
  85. def setup_javascript
  86. <<-JS
  87. function Agent() {};
  88. Agent.createEvent = function(opts) {
  89. return JSON.parse(doCreateEvent(JSON.stringify(opts)));
  90. }
  91. Agent.incomingEvents = function() {
  92. return JSON.parse(getIncomingEvents());
  93. }
  94. Agent.memory = function(key, value) {
  95. if (typeof(key) !== "undefined" && typeof(value) !== "undefined") {
  96. getMemory(key, value);
  97. } else if (typeof(key) !== "undefined") {
  98. return JSON.parse(getMemory())[key];
  99. } else {
  100. return JSON.parse(getMemory());
  101. }
  102. }
  103. Agent.options = function(key) {
  104. if (typeof(key) !== "undefined") {
  105. return JSON.parse(getOptions())[key];
  106. } else {
  107. return JSON.parse(getOptions());
  108. }
  109. }
  110. Agent.log = function(message) {
  111. doLog(message);
  112. }
  113. Agent.error = function(message) {
  114. doError(message);
  115. }
  116. Agent.check = function(){};
  117. Agent.receive = function(){};
  118. JS
  119. end
  120. def log_errors
  121. begin
  122. yield
  123. rescue V8::Error => e
  124. error "JavaScript error: #{e.message}"
  125. end
  126. end
  127. def clean_nans(input)
  128. if input.is_a?(Array)
  129. input.map {|v| clean_nans(v) }
  130. elsif input.is_a?(Hash)
  131. input.inject({}) { |m, (k, v)| m[k] = clean_nans(v); m }
  132. elsif input.is_a?(Float) && input.nan?
  133. 'NaN'
  134. else
  135. input
  136. end
  137. end
  138. end
  139. end