1
0

java_script_agent.rb 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186
  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. You can put code in the `code` option, or put your code in a Credential and reference it from `code` with `credential:<name>` (recommended).
  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()` (the returned event objects will each have a `payload` property)
  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. cred_name = credential_referenced_by_code
  22. if cred_name
  23. errors.add(:base, "The credential '#{cred_name}' referenced by code cannot be found") unless credential(cred_name).present?
  24. else
  25. errors.add(:base, "The 'code' option is required") unless options['code'].present?
  26. end
  27. end
  28. def working?
  29. return false if recent_error_logs?
  30. if interpolated['expected_update_period_in_days'].present?
  31. return false unless event_created_within?(interpolated['expected_update_period_in_days'])
  32. end
  33. if interpolated['expected_receive_period_in_days'].present?
  34. return false unless last_receive_at && last_receive_at > interpolated['expected_receive_period_in_days'].to_i.days.ago
  35. end
  36. true
  37. end
  38. def check
  39. log_errors do
  40. execute_js("check")
  41. end
  42. end
  43. def receive(incoming_events)
  44. log_errors do
  45. execute_js("receive", incoming_events)
  46. end
  47. end
  48. def default_options
  49. js_code = <<-JS
  50. Agent.check = function() {
  51. if (this.options('make_event')) {
  52. this.createEvent({ 'message': 'I made an event!' });
  53. var callCount = this.memory('callCount') || 0;
  54. this.memory('callCount', callCount + 1);
  55. }
  56. };
  57. Agent.receive = function() {
  58. var events = this.incomingEvents();
  59. for(var i = 0; i < events.length; i++) {
  60. this.createEvent({ 'message': 'I got an event!', 'event_was': events[i].payload });
  61. }
  62. }
  63. JS
  64. {
  65. "code" => js_code.gsub(/[\n\r\t]/, '').strip,
  66. 'expected_receive_period_in_days' => "2",
  67. 'expected_update_period_in_days' => "2"
  68. }
  69. end
  70. private
  71. def execute_js(js_function, incoming_events = [])
  72. js_function = js_function == "check" ? "check" : "receive"
  73. context = V8::Context.new
  74. context.eval(setup_javascript)
  75. context["doCreateEvent"] = lambda { |a, y| create_event(payload: clean_nans(JSON.parse(y))).payload.to_json }
  76. context["getIncomingEvents"] = lambda { |a| incoming_events.to_json }
  77. context["getOptions"] = lambda { |a, x| interpolated.to_json }
  78. context["doLog"] = lambda { |a, x| log x }
  79. context["doError"] = lambda { |a, x| error x }
  80. context["getMemory"] = lambda do |a, x, y|
  81. if x && y
  82. memory[x] = clean_nans(y)
  83. else
  84. memory.to_json
  85. end
  86. end
  87. context.eval(code)
  88. context.eval("Agent.#{js_function}();")
  89. end
  90. def code
  91. cred = credential_referenced_by_code
  92. if cred
  93. credential(cred) || 'Agent.check = function() { this.error("Unable to find credential"); };'
  94. else
  95. interpolated['code']
  96. end
  97. end
  98. def credential_referenced_by_code
  99. interpolated['code'] =~ /\Acredential:(.*)\Z/ && $1
  100. end
  101. def setup_javascript
  102. <<-JS
  103. function Agent() {};
  104. Agent.createEvent = function(opts) {
  105. return JSON.parse(doCreateEvent(JSON.stringify(opts)));
  106. }
  107. Agent.incomingEvents = function() {
  108. return JSON.parse(getIncomingEvents());
  109. }
  110. Agent.memory = function(key, value) {
  111. if (typeof(key) !== "undefined" && typeof(value) !== "undefined") {
  112. getMemory(key, value);
  113. } else if (typeof(key) !== "undefined") {
  114. return JSON.parse(getMemory())[key];
  115. } else {
  116. return JSON.parse(getMemory());
  117. }
  118. }
  119. Agent.options = function(key) {
  120. if (typeof(key) !== "undefined") {
  121. return JSON.parse(getOptions())[key];
  122. } else {
  123. return JSON.parse(getOptions());
  124. }
  125. }
  126. Agent.log = function(message) {
  127. doLog(message);
  128. }
  129. Agent.error = function(message) {
  130. doError(message);
  131. }
  132. Agent.check = function(){};
  133. Agent.receive = function(){};
  134. JS
  135. end
  136. def log_errors
  137. begin
  138. yield
  139. rescue V8::Error => e
  140. error "JavaScript error: #{e.message}"
  141. end
  142. end
  143. def clean_nans(input)
  144. if input.is_a?(Array)
  145. input.map {|v| clean_nans(v) }
  146. elsif input.is_a?(Hash)
  147. input.inject({}) { |m, (k, v)| m[k] = clean_nans(v); m }
  148. elsif input.is_a?(Float) && input.nan?
  149. 'NaN'
  150. else
  151. input
  152. end
  153. end
  154. end
  155. end