diff --git a/app/controllers/api/v3/notices_controller.rb b/app/controllers/api/v3/notices_controller.rb new file mode 100644 index 0000000..a893f65 --- /dev/null +++ b/app/controllers/api/v3/notices_controller.rb @@ -0,0 +1,34 @@ +class Api::V3::NoticesController < ApplicationController + skip_before_action :verify_authenticity_token + skip_before_action :authenticate_user! + + respond_to :json + + def create + response.headers['Access-Control-Allow-Origin'] = '*' + response.headers['Access-Control-Allow-Headers'] = 'origin, content-type, accept' + + if !request.options? + report = JsonParser.new(params).report + + if report.valid? + if report.should_keep? + report.generate_notice! + render json: { + notice: { + id: report.notice.id + } + } + else + render text: 'Notice for old app version ignored' + end + else + render text: 'Your API key is unknown', :status => 422 + end + else + render nothing: true + end + rescue JsonParser::ParamsError + render text: 'Invalid request' + end +end diff --git a/config/routes.rb b/config/routes.rb index 3305bc0..c52e06b 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -58,9 +58,10 @@ Rails.application.routes.draw do end end end + + match '/v3/projects/:project_id/notices' => 'v3/notices#create', via: [:post, :options] end root :to => 'apps#index' - end diff --git a/lib/json_parser.rb b/lib/json_parser.rb new file mode 100644 index 0000000..b1d24b9 --- /dev/null +++ b/lib/json_parser.rb @@ -0,0 +1,86 @@ +class JsonParser + class ParamsError < StandardError; end + + attr_reader :params, :error + + def initialize(params) + @params = params || {} + end + + def report + attributes = { + error_class: error['type'], + message: error['message'], + backtrace: backtrace, + request: request, + server_environment: server_environment, + api_key: params['key'], + notifier: params['notifier'], + user_attributes: user_attributes, + framework: 'Javascript' + } + + ErrorReport.new(attributes) + end + + private + + def error + raise ParamsError unless params.has_key?('errors') && params['errors'].any? + @error ||= params['errors'].first + end + + def backtrace + error['backtrace'].map do |backtrace_line| + { + method: backtrace_line['function'], + file: backtrace_line['file'], + number: backtrace_line['line'], + column: backtrace_line['column'] + } + end + end + + def server_environment + { + 'environment-name' => context['environment'], + 'hostname' => hostname, + 'project-root' => context['rootDirectory'], + 'app-version' => context['version'] + } + end + + def request + environment = params['environment'].merge( + 'HTTP_USER_AGENT' => context['userAgent'] + ) + + { + 'cgi-data' => environment, + 'session' => params['session'], + 'params' => params['params'], + 'url' => url, + 'component' => context['component'], + 'action' => context['action'] + } + end + + def user_attributes + hash = context.slice('userId', 'userUsername', 'userName', 'userEmail') + Hash[hash.map { |key, value| [key.sub(/^user/, ''), value] }] + end + + def url + context['url'] + end + + def hostname + URI.parse(url).hostname + rescue URI::InvalidURIError + '' + end + + def context + @context = params['context'] || {} + end +end \ No newline at end of file diff --git a/spec/lib/json_parser_spec.rb b/spec/lib/json_parser_spec.rb new file mode 100644 index 0000000..cd85aec --- /dev/null +++ b/spec/lib/json_parser_spec.rb @@ -0,0 +1,69 @@ +describe JsonParser do + let(:app) { Fabricate(:app) } + + it 'raises error when errors attribute is missing' do + expect { + JsonParser.new({}).report + }.to raise_error(JsonParser::ParamsError) + + expect { + JsonParser.new({'errors' => []}).report + }.to raise_error(JsonParser::ParamsError) + end + + it 'parses JSON payload and returns ErrorReport' do + params = JSON.parse(<<-EOL) + { + "notifier":{"name":"airbrake-js-v8","version":"0.3.10","url":"https://github.com/airbrake/airbrake-js"}, + "errors":[ + { + "type":"Error", + "message":"Error: TestError", + "backtrace":[ + {"function":"d","file":"http://localhost:3000/assets/application.js","line":11234,"column":24}, + {"function":"c","file":"http://localhost:3000/assets/application.js","line":11233,"column":18}, + {"function":"b","file":"http://localhost:3000/assets/application.js","line":11232,"column":18}, + {"function":"a","file":"http://localhost:3000/assets/application.js","line":11231,"column":18}, + {"function":"HTMLDocument.","file":"http://localhost:3000/assets/application.js","line":11236,"column":3}, + {"function":"fire","file":"http://localhost:3000/assets/application.js","line":1018,"column":34}, + {"function":"Object.self.fireWith [as resolveWith]","file":"http://localhost:3000/assets/application.js","line":1128,"column":13}, + {"function":"Function.jQuery.extend.ready","file":"http://localhost:3000/assets/application.js","line":417,"column":15}, + {"function":"HTMLDocument.DOMContentLoaded","file":"http://localhost:3000/assets/application.js","line":93,"column":14} + ] + } + ], + "context":{ + "language":"JavaScript", + "sourceMapEnabled":true, + "userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.99 Safari/537.36", + "url":"http://localhost:3000/kontakt", + "userId":1,"userUsername":"john", + "userName":"John Doe", + "userUsername": "john", + "userEmail":"john.doe@example.org", + "version":"1.0", + "component":"ContactsController", + "action":"show" + }, + "params":{"returnTo":"dashboard"}, + "environment":{"navigator_vendor":"Google Inc."}, + "session":{"isAdmin":true}, + "key":"#{app.api_key}" + } + EOL + + report = JsonParser.new(params).report + notice = report.generate_notice! + + expect(report.error_class).to eq('Error') + expect(report.message).to eq('Error: TestError') + expect(report.backtrace.lines.size).to eq(9) + expect(notice.user_attributes).to include({'Id' => 1, 'Name' => 'John Doe', 'Email' => 'john.doe@example.org', 'Username' => 'john'}) + expect(notice.session).to include('isAdmin' => true) + expect(notice.params).to include('returnTo' => 'dashboard') + expect(notice.env_vars).to include( + 'navigator_vendor' => 'Google Inc.', + 'HTTP_USER_AGENT' => 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.99 Safari/537.36' + ) + end +end \ No newline at end of file -- libgit2 0.21.2