wat_catcher 0.6.0 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/wat_catcher/bugsnag_controller.rb +26 -0
  3. data/app/controllers/wat_catcher/wats_controller.rb +1 -1
  4. data/config/routes.rb +4 -1
  5. data/lib/wat_catcher.rb +1 -0
  6. data/lib/wat_catcher/version.rb +1 -1
  7. data/lib/wat_catcher/wattle_helper.rb +10 -1
  8. data/spec/ci.sh +8 -0
  9. data/spec/controllers/bugsnag_controller_spec.rb +33 -0
  10. data/spec/dummy/README.rdoc +28 -0
  11. data/spec/dummy/Rakefile +6 -0
  12. data/spec/dummy/app/assets/images/.keep +0 -0
  13. data/spec/dummy/app/assets/javascripts/application.js +13 -0
  14. data/spec/dummy/app/assets/stylesheets/application.css +13 -0
  15. data/spec/dummy/app/controllers/application_controller.rb +5 -0
  16. data/spec/dummy/app/controllers/concerns/.keep +0 -0
  17. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  18. data/spec/dummy/app/mailers/.keep +0 -0
  19. data/spec/dummy/app/models/.keep +0 -0
  20. data/spec/dummy/app/models/concerns/.keep +0 -0
  21. data/spec/dummy/app/views/layouts/application.html.erb +14 -0
  22. data/spec/dummy/bin/bundle +3 -0
  23. data/spec/dummy/bin/rails +4 -0
  24. data/spec/dummy/bin/rake +4 -0
  25. data/spec/dummy/config.ru +4 -0
  26. data/spec/dummy/config/application.rb +23 -0
  27. data/spec/dummy/config/boot.rb +5 -0
  28. data/spec/dummy/config/database.yml +25 -0
  29. data/spec/dummy/config/environment.rb +5 -0
  30. data/spec/dummy/config/environments/development.rb +29 -0
  31. data/spec/dummy/config/environments/production.rb +80 -0
  32. data/spec/dummy/config/environments/test.rb +36 -0
  33. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  34. data/spec/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  35. data/spec/dummy/config/initializers/inflections.rb +16 -0
  36. data/spec/dummy/config/initializers/mime_types.rb +5 -0
  37. data/spec/dummy/config/initializers/secret_token.rb +12 -0
  38. data/spec/dummy/config/initializers/session_store.rb +3 -0
  39. data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
  40. data/spec/dummy/config/locales/en.yml +23 -0
  41. data/spec/dummy/config/routes.rb +3 -0
  42. data/spec/dummy/lib/assets/.keep +0 -0
  43. data/spec/dummy/log/.keep +0 -0
  44. data/spec/dummy/public/404.html +58 -0
  45. data/spec/dummy/public/422.html +58 -0
  46. data/spec/dummy/public/500.html +57 -0
  47. data/spec/dummy/public/favicon.ico +0 -0
  48. data/spec/spec_helper.rb +63 -0
  49. data/vendor/assets/javascripts/bugsnag.js +623 -0
  50. data/wat_catcher.gemspec +5 -0
  51. metadata +142 -2
@@ -0,0 +1,36 @@
1
+ Dummy::Application.configure do
2
+ # Settings specified here will take precedence over those in config/application.rb.
3
+
4
+ # The test environment is used exclusively to run your application's
5
+ # test suite. You never need to work with it otherwise. Remember that
6
+ # your test database is "scratch space" for the test suite and is wiped
7
+ # and recreated between test runs. Don't rely on the data there!
8
+ config.cache_classes = true
9
+
10
+ # Do not eager load code on boot. This avoids loading your whole application
11
+ # just for the purpose of running a single test. If you are using a tool that
12
+ # preloads Rails for running tests, you may have to set it to true.
13
+ config.eager_load = false
14
+
15
+ # Configure static asset server for tests with Cache-Control for performance.
16
+ config.serve_static_assets = true
17
+ config.static_cache_control = "public, max-age=3600"
18
+
19
+ # Show full error reports and disable caching.
20
+ config.consider_all_requests_local = true
21
+ config.action_controller.perform_caching = false
22
+
23
+ # Raise exceptions instead of rendering exception templates.
24
+ config.action_dispatch.show_exceptions = false
25
+
26
+ # Disable request forgery protection in test environment.
27
+ config.action_controller.allow_forgery_protection = false
28
+
29
+ # Tell Action Mailer not to deliver emails to the real world.
30
+ # The :test delivery method accumulates sent emails in the
31
+ # ActionMailer::Base.deliveries array.
32
+ config.action_mailer.delivery_method = :test
33
+
34
+ # Print deprecation notices to the stderr.
35
+ config.active_support.deprecation = :stderr
36
+ end
@@ -0,0 +1,7 @@
1
+ # Be sure to restart your server when you modify this file.
2
+
3
+ # You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces.
4
+ # Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ }
5
+
6
+ # You can also remove all the silencers if you're trying to debug a problem that might stem from framework code.
7
+ # Rails.backtrace_cleaner.remove_silencers!
@@ -0,0 +1,4 @@
1
+ # Be sure to restart your server when you modify this file.
2
+
3
+ # Configure sensitive parameters which will be filtered from the log file.
4
+ Rails.application.config.filter_parameters += [:password]
@@ -0,0 +1,16 @@
1
+ # Be sure to restart your server when you modify this file.
2
+
3
+ # Add new inflection rules using the following format. Inflections
4
+ # are locale specific, and you may define rules for as many different
5
+ # locales as you wish. All of these examples are active by default:
6
+ # ActiveSupport::Inflector.inflections(:en) do |inflect|
7
+ # inflect.plural /^(ox)$/i, '\1en'
8
+ # inflect.singular /^(ox)en/i, '\1'
9
+ # inflect.irregular 'person', 'people'
10
+ # inflect.uncountable %w( fish sheep )
11
+ # end
12
+
13
+ # These inflection rules are supported but not enabled by default:
14
+ # ActiveSupport::Inflector.inflections(:en) do |inflect|
15
+ # inflect.acronym 'RESTful'
16
+ # end
@@ -0,0 +1,5 @@
1
+ # Be sure to restart your server when you modify this file.
2
+
3
+ # Add new mime types for use in respond_to blocks:
4
+ # Mime::Type.register "text/richtext", :rtf
5
+ # Mime::Type.register_alias "text/html", :iphone
@@ -0,0 +1,12 @@
1
+ # Be sure to restart your server when you modify this file.
2
+
3
+ # Your secret key is used for verifying the integrity of signed cookies.
4
+ # If you change this key, all old signed cookies will become invalid!
5
+
6
+ # Make sure the secret is at least 30 characters and all random,
7
+ # no regular words or you'll be exposed to dictionary attacks.
8
+ # You can use `rake secret` to generate a secure secret key.
9
+
10
+ # Make sure your secret_key_base is kept private
11
+ # if you're sharing your code publicly.
12
+ Dummy::Application.config.secret_key_base = 'ef82b8f94b465ceaa4347d1b02383a96b23cceb8676384a0012dd23c8d81ead2a396adcc47773088fd089674c03ad7828835318f2826699d3f12686d343a75e8'
@@ -0,0 +1,3 @@
1
+ # Be sure to restart your server when you modify this file.
2
+
3
+ Dummy::Application.config.session_store :cookie_store, key: '_dummy_session'
@@ -0,0 +1,14 @@
1
+ # Be sure to restart your server when you modify this file.
2
+
3
+ # This file contains settings for ActionController::ParamsWrapper which
4
+ # is enabled by default.
5
+
6
+ # Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array.
7
+ ActiveSupport.on_load(:action_controller) do
8
+ wrap_parameters format: [:json] if respond_to?(:wrap_parameters)
9
+ end
10
+
11
+ # To enable root element in JSON for ActiveRecord objects.
12
+ # ActiveSupport.on_load(:active_record) do
13
+ # self.include_root_in_json = true
14
+ # end
@@ -0,0 +1,23 @@
1
+ # Files in the config/locales directory are used for internationalization
2
+ # and are automatically loaded by Rails. If you want to use locales other
3
+ # than English, add the necessary files in this directory.
4
+ #
5
+ # To use the locales, use `I18n.t`:
6
+ #
7
+ # I18n.t 'hello'
8
+ #
9
+ # In views, this is aliased to just `t`:
10
+ #
11
+ # <%= t('hello') %>
12
+ #
13
+ # To use a different locale, set it with `I18n.locale`:
14
+ #
15
+ # I18n.locale = :es
16
+ #
17
+ # This would use the information in config/locales/es.yml.
18
+ #
19
+ # To learn more, please read the Rails Internationalization guide
20
+ # available at http://guides.rubyonrails.org/i18n.html.
21
+
22
+ en:
23
+ hello: "Hello world"
@@ -0,0 +1,3 @@
1
+ Dummy::Application.routes.draw do
2
+ mount WatCatcher::Engine => '/wat_catcher'
3
+ end
File without changes
File without changes
@@ -0,0 +1,58 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>The page you were looking for doesn't exist (404)</title>
5
+ <style>
6
+ body {
7
+ background-color: #EFEFEF;
8
+ color: #2E2F30;
9
+ text-align: center;
10
+ font-family: arial, sans-serif;
11
+ }
12
+
13
+ div.dialog {
14
+ width: 25em;
15
+ margin: 4em auto 0 auto;
16
+ border: 1px solid #CCC;
17
+ border-right-color: #999;
18
+ border-left-color: #999;
19
+ border-bottom-color: #BBB;
20
+ border-top: #B00100 solid 4px;
21
+ border-top-left-radius: 9px;
22
+ border-top-right-radius: 9px;
23
+ background-color: white;
24
+ padding: 7px 4em 0 4em;
25
+ }
26
+
27
+ h1 {
28
+ font-size: 100%;
29
+ color: #730E15;
30
+ line-height: 1.5em;
31
+ }
32
+
33
+ body > p {
34
+ width: 33em;
35
+ margin: 0 auto 1em;
36
+ padding: 1em 0;
37
+ background-color: #F7F7F7;
38
+ border: 1px solid #CCC;
39
+ border-right-color: #999;
40
+ border-bottom-color: #999;
41
+ border-bottom-left-radius: 4px;
42
+ border-bottom-right-radius: 4px;
43
+ border-top-color: #DADADA;
44
+ color: #666;
45
+ box-shadow:0 3px 8px rgba(50, 50, 50, 0.17);
46
+ }
47
+ </style>
48
+ </head>
49
+
50
+ <body>
51
+ <!-- This file lives in public/404.html -->
52
+ <div class="dialog">
53
+ <h1>The page you were looking for doesn't exist.</h1>
54
+ <p>You may have mistyped the address or the page may have moved.</p>
55
+ </div>
56
+ <p>If you are the application owner check the logs for more information.</p>
57
+ </body>
58
+ </html>
@@ -0,0 +1,58 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>The change you wanted was rejected (422)</title>
5
+ <style>
6
+ body {
7
+ background-color: #EFEFEF;
8
+ color: #2E2F30;
9
+ text-align: center;
10
+ font-family: arial, sans-serif;
11
+ }
12
+
13
+ div.dialog {
14
+ width: 25em;
15
+ margin: 4em auto 0 auto;
16
+ border: 1px solid #CCC;
17
+ border-right-color: #999;
18
+ border-left-color: #999;
19
+ border-bottom-color: #BBB;
20
+ border-top: #B00100 solid 4px;
21
+ border-top-left-radius: 9px;
22
+ border-top-right-radius: 9px;
23
+ background-color: white;
24
+ padding: 7px 4em 0 4em;
25
+ }
26
+
27
+ h1 {
28
+ font-size: 100%;
29
+ color: #730E15;
30
+ line-height: 1.5em;
31
+ }
32
+
33
+ body > p {
34
+ width: 33em;
35
+ margin: 0 auto 1em;
36
+ padding: 1em 0;
37
+ background-color: #F7F7F7;
38
+ border: 1px solid #CCC;
39
+ border-right-color: #999;
40
+ border-bottom-color: #999;
41
+ border-bottom-left-radius: 4px;
42
+ border-bottom-right-radius: 4px;
43
+ border-top-color: #DADADA;
44
+ color: #666;
45
+ box-shadow:0 3px 8px rgba(50, 50, 50, 0.17);
46
+ }
47
+ </style>
48
+ </head>
49
+
50
+ <body>
51
+ <!-- This file lives in public/422.html -->
52
+ <div class="dialog">
53
+ <h1>The change you wanted was rejected.</h1>
54
+ <p>Maybe you tried to change something you didn't have access to.</p>
55
+ </div>
56
+ <p>If you are the application owner check the logs for more information.</p>
57
+ </body>
58
+ </html>
@@ -0,0 +1,57 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>We're sorry, but something went wrong (500)</title>
5
+ <style>
6
+ body {
7
+ background-color: #EFEFEF;
8
+ color: #2E2F30;
9
+ text-align: center;
10
+ font-family: arial, sans-serif;
11
+ }
12
+
13
+ div.dialog {
14
+ width: 25em;
15
+ margin: 4em auto 0 auto;
16
+ border: 1px solid #CCC;
17
+ border-right-color: #999;
18
+ border-left-color: #999;
19
+ border-bottom-color: #BBB;
20
+ border-top: #B00100 solid 4px;
21
+ border-top-left-radius: 9px;
22
+ border-top-right-radius: 9px;
23
+ background-color: white;
24
+ padding: 7px 4em 0 4em;
25
+ }
26
+
27
+ h1 {
28
+ font-size: 100%;
29
+ color: #730E15;
30
+ line-height: 1.5em;
31
+ }
32
+
33
+ body > p {
34
+ width: 33em;
35
+ margin: 0 auto 1em;
36
+ padding: 1em 0;
37
+ background-color: #F7F7F7;
38
+ border: 1px solid #CCC;
39
+ border-right-color: #999;
40
+ border-bottom-color: #999;
41
+ border-bottom-left-radius: 4px;
42
+ border-bottom-right-radius: 4px;
43
+ border-top-color: #DADADA;
44
+ color: #666;
45
+ box-shadow:0 3px 8px rgba(50, 50, 50, 0.17);
46
+ }
47
+ </style>
48
+ </head>
49
+
50
+ <body>
51
+ <!-- This file lives in public/500.html -->
52
+ <div class="dialog">
53
+ <h1>We're sorry, but something went wrong.</h1>
54
+ </div>
55
+ <p>If you are the application owner check the logs for more information.</p>
56
+ </body>
57
+ </html>
File without changes
@@ -0,0 +1,63 @@
1
+ # This file is copied to spec/ when you run 'rails generate rspec:install'
2
+ ENV["RAILS_ENV"] ||= 'test'
3
+
4
+ require File.expand_path("../dummy/config/environment", __FILE__)
5
+
6
+ require 'rspec/rails'
7
+ require 'rspec/autorun'
8
+ #require 'rr'
9
+ require 'sidekiq/testing/inline'
10
+
11
+ # Requires supporting ruby files with custom matchers and macros, etc,
12
+ # in spec/support/ and its subdirectories.
13
+ Dir[Rails.root.join("spec/support/**/*.rb")].each {|f| require f}
14
+
15
+ Sidekiq::Testing.fake!
16
+
17
+ RSpec.configure do |config|
18
+
19
+ # If you're not using ActiveRecord, or you'd prefer not to run each of your
20
+ # examples within a transaction, remove the following line or assign false
21
+ # instead of true.
22
+ #config.use_transactional_fixtures = false
23
+
24
+ #config.before(:suite) do
25
+ # DatabaseCleaner.strategy = :transaction
26
+ #end
27
+ #
28
+ #config.before(:each) do
29
+ # DatabaseCleaner.start
30
+ #end
31
+ #
32
+ #config.after(:each) do
33
+ # DatabaseCleaner.clean
34
+ #end
35
+
36
+ # ## Mock Framework
37
+ #
38
+ # If you prefer to use mocha, flexmock or RR, uncomment the appropriate line:
39
+ #
40
+ # config.mock_with :mocha
41
+ # config.mock_with :flexmock
42
+ # config.mock_with :rr
43
+
44
+ # Remove this line if you're not using ActiveRecord or ActiveRecord fixtures
45
+ config.fixture_path = "#{::Rails.root}/spec/fixtures"
46
+
47
+
48
+ # If true, the base class of anonymous controllers will be inferred
49
+ # automatically. This will be the default behavior in future versions of
50
+ # rspec-rails.
51
+ config.infer_base_class_for_anonymous_controllers = false
52
+ config.global_fixtures = :all
53
+
54
+ # Run specs in random order to surface order dependencies. If you find an
55
+ # order dependency and want to debug it, you can fix the order by providing
56
+ # the seed, which is printed after each run.
57
+ # --seed 1234
58
+
59
+
60
+
61
+ config.order = "random"
62
+
63
+ end
@@ -0,0 +1,623 @@
1
+ //
2
+ // **Bugsnag.js** is the official JavaScript notifier for
3
+ // [Bugsnag](https://bugsnag.com).
4
+ //
5
+ // Bugsnag gives you instant notification of errors and
6
+ // exceptions in your website's JavaScript code.
7
+ //
8
+ // Bugsnag.js is incredibly small, and has no external dependencies (not even
9
+ // jQuery!) so you can safely use it on any website.
10
+ //
11
+
12
+ // The `Bugsnag` object is the only globally exported variable
13
+ (function(definition) {
14
+ var old = window.Bugsnag;
15
+ window.Bugsnag = definition(window, document, navigator, old);
16
+ })(function (window, document, navigator, old) {
17
+ var self = {},
18
+ lastEvent,
19
+ lastScript,
20
+ previousNotification,
21
+ shouldCatch = true,
22
+ ignoreOnError = 0,
23
+
24
+ // We've seen cases where individual clients can infinite loop sending us errors
25
+ // (in some cases 10,000+ errors per page). This limit is at the point where
26
+ // you've probably learned everything useful there is to debug the problem,
27
+ // and we're happy to under-estimate the count to save the client (and Bugsnag's) resources.
28
+ eventsRemaining = 10;
29
+
30
+ self.noConflict = function() {
31
+ window.Bugsnag = old;
32
+ return self;
33
+ };
34
+
35
+ //
36
+ // ### Manual error notification (public methods)
37
+ //
38
+
39
+ // #### Bugsnag.notifyException
40
+ //
41
+ // Notify Bugsnag about a given `exception`, typically that you've caught
42
+ // with a `try/catch` statement or that you've generated yourself.
43
+ //
44
+ // It's almost always better to let an exception bubble rather than catching
45
+ // it, as that gives more consistent behaviour across browsers. Consider
46
+ // re-throwing instead of calling .notifyException.
47
+ //
48
+ // Since most JavaScript exceptions use the `Error` class, we also allow
49
+ // you to provide a custom error name when calling `notifyException`.
50
+ //
51
+ // You should never pass severity. It's there for internal use, so we
52
+ // can mark uncaught exceptions as "fatal". The default value is "error"
53
+ // and "warning" is also supported by the backend, all other values cause
54
+ // the notification to be dropped; and you will not see it in your dashboard.
55
+ self.notifyException = function (exception, name, metaData, severity) {
56
+ if (name && typeof name !== "string") {
57
+ metaData = name;
58
+ }
59
+ if (!metaData) {
60
+ metaData = {};
61
+ }
62
+ addScriptToMetaData(metaData);
63
+
64
+ sendToBugsnag({
65
+ name: name || exception.name,
66
+ message: exception.message || exception.description,
67
+ stacktrace: stacktraceFromException(exception) || generateStacktrace(),
68
+ file: exception.fileName || exception.sourceURL,
69
+ lineNumber: exception.lineNumber || exception.line,
70
+ columnNumber: exception.columnNumber ? exception.columnNumber + 1 : undefined,
71
+ severity: severity || "error"
72
+ }, metaData);
73
+ };
74
+
75
+ // #### Bugsnag.notify
76
+ //
77
+ // Notify Bugsnag about an error by passing in a `name` and `message`,
78
+ // without requiring an exception.
79
+ self.notify = function (name, message, metaData) {
80
+ sendToBugsnag({
81
+ name: name,
82
+ message: message,
83
+ stacktrace: generateStacktrace(),
84
+ severity: "error"
85
+ }, metaData);
86
+ };
87
+
88
+ // Return a function acts like the given function, but reports
89
+ // any exceptions to Bugsnag before re-throwing them.
90
+ //
91
+ // This is not a public function because it can only be used if
92
+ // the exception is not caught after being thrown out of this function.
93
+ //
94
+ // If you call wrap twice on the same function, it'll give you back the
95
+ // same wrapped function. This lets removeEventListener to continue to
96
+ // work.
97
+ function wrap(_super, options) {
98
+ try {
99
+ if (typeof _super !== "function") {
100
+ return _super;
101
+ }
102
+ if (!_super.bugsnag) {
103
+ var currentScript = getCurrentScript();
104
+ _super.bugsnag = function (event) {
105
+ if (options && options.eventHandler) {
106
+ lastEvent = event;
107
+ }
108
+ lastScript = currentScript;
109
+
110
+ // We set shouldCatch to false on IE < 10 because catching the error ruins the file/line as reported in window.onerror,
111
+ // We set shouldCatch to false on Chrome/Safari because it interferes with "break on unhandled exception"
112
+ // All other browsers need shouldCatch to be true, as they don't pass the exception object to window.onerror
113
+ if (shouldCatch) {
114
+ try {
115
+ return _super.apply(this, arguments);
116
+ } catch (e) {
117
+ // We do this rather than stashing treating the error like lastEvent
118
+ // because in FF 26 onerror is not called for synthesized event handlers.
119
+ if (getSetting("autoNotify", true)) {
120
+ self.notifyException(e, null, null, "fatal");
121
+ ignoreNextOnError();
122
+ }
123
+ throw e;
124
+ } finally {
125
+ lastScript = null;
126
+ }
127
+ } else {
128
+ var ret = _super.apply(this, arguments);
129
+ // in case of error, this is set to null in window.onerror
130
+ lastScript = null;
131
+ return ret;
132
+ }
133
+ };
134
+ _super.bugsnag.bugsnag = _super.bugsnag;
135
+ }
136
+ return _super.bugsnag;
137
+
138
+ // This can happen if _super is not a normal javascript function.
139
+ // For example, see https://github.com/bugsnag/bugsnag-js/issues/28
140
+ } catch (e) {
141
+ return _super;
142
+ }
143
+ }
144
+
145
+ //
146
+ // ### Script tag tracking
147
+ //
148
+
149
+ // To emulate document.currentScript we use document.scripts.last.
150
+ // This only works while synchronous scripts are running, so we track
151
+ // that here.
152
+ var synchronousScriptsRunning = document.readyState !== "complete";
153
+ function loadCompleted() {
154
+ synchronousScriptsRunning = false;
155
+ }
156
+
157
+ // from jQuery. We don't have quite such tight bounds as they do if
158
+ // we end up on the window.onload event as we don't try and hack
159
+ // the .scrollLeft() fix in because it doesn't work in frames so
160
+ // we'd need these fallbacks anyway.
161
+ // The worst that can happen is we group an event handler that fires
162
+ // before us into the last script tag.
163
+ if (document.addEventListener) {
164
+ document.addEventListener("DOMContentLoaded", loadCompleted, true);
165
+ window.addEventListener("load", loadCompleted, true);
166
+ } else {
167
+ window.attachEvent("onload", loadCompleted);
168
+ }
169
+
170
+ function getCurrentScript() {
171
+ var script = document.currentScript || lastScript;
172
+
173
+ if (!script && synchronousScriptsRunning) {
174
+ var scripts = document.getElementsByTagName("script");
175
+ script = scripts[scripts.length - 1];
176
+ }
177
+
178
+ return script;
179
+ }
180
+
181
+ function addScriptToMetaData(metaData) {
182
+ var script = getCurrentScript();
183
+
184
+ if (script) {
185
+ metaData.script = {
186
+ src: script.src,
187
+ content: getSetting("inlineScript", true) ? script.innerHTML : ""
188
+ };
189
+ }
190
+ }
191
+
192
+ //
193
+ // ### Helpers & Setup
194
+ //
195
+
196
+ // Compile regular expressions upfront.
197
+ var API_KEY_REGEX = /^[0-9a-f]{32}$/i;
198
+ var FUNCTION_REGEX = /function\s*([\w\-$]+)?\s*\(/i;
199
+
200
+ // Set up default notifier settings.
201
+ var DEFAULT_BASE_ENDPOINT = "https://notify.bugsnag.com/";
202
+ var DEFAULT_NOTIFIER_ENDPOINT = DEFAULT_BASE_ENDPOINT + "js";
203
+ var NOTIFIER_VERSION = "2.3.2";
204
+
205
+ // Keep a reference to the currently executing script in the DOM.
206
+ // We'll use this later to extract settings from attributes.
207
+ var scripts = document.getElementsByTagName("script");
208
+ var thisScript = scripts[scripts.length - 1];
209
+
210
+ // Simple logging function that wraps `console.log` if available.
211
+ // This is useful for warning about configuration issues
212
+ // eg. forgetting to set an API key.
213
+ function log(msg) {
214
+ var console = window.console;
215
+ if (console !== undefined && console.log !== undefined) {
216
+ console.log("[Bugsnag] " + msg);
217
+ }
218
+ }
219
+
220
+ // Deeply serialize an object into a query string. We use the PHP-style
221
+ // nested object syntax, `nested[keys]=val`, to support heirachical
222
+ // objects. Similar to jQuery's `$.param` method.
223
+ function serialize(obj, prefix) {
224
+ var str = [];
225
+ for (var p in obj) {
226
+ if (obj.hasOwnProperty(p) && p != null && obj[p] != null) {
227
+ var k = prefix ? prefix + "[" + p + "]" : p, v = obj[p];
228
+ str.push(typeof v === "object" ? serialize(v, k) : encodeURIComponent(k) + "=" + encodeURIComponent(v));
229
+ }
230
+ }
231
+ return str.join("&");
232
+ }
233
+
234
+ // Deep-merge the `source` object into the `target` object and return
235
+ // the `target`. Properties in source that will overwrite those in target.
236
+ // Similar to jQuery's `$.extend` method.
237
+ function merge(target, source) {
238
+ if (source == null) {
239
+ return target;
240
+ }
241
+
242
+ target = target || {};
243
+ for (var key in source) {
244
+ if (source.hasOwnProperty(key)) {
245
+ try {
246
+ if (source[key].constructor === Object) {
247
+ target[key] = merge(target[key], source[key]);
248
+ } else {
249
+ target[key] = source[key];
250
+ }
251
+ } catch (e) {
252
+ target[key] = source[key];
253
+ }
254
+ }
255
+ }
256
+
257
+ return target;
258
+ }
259
+
260
+ // Make a HTTP request with given `url` and `params` object.
261
+ // For maximum browser compatibility and cross-domain support, requests are
262
+ // made by creating a temporary JavaScript `Image` object.
263
+ function request(url, params) {
264
+ if (typeof BUGSNAG_TESTING !== "undefined" && self.testRequest) {
265
+ self.testRequest(url, params);
266
+ } else {
267
+ var img = new Image();
268
+ img.src = url + "?" + serialize(params) + "&ct=img&cb=" + new Date().getTime();
269
+ }
270
+ }
271
+
272
+ // Extract all `data-*` attributes from a DOM element and return them as an
273
+ // object. This is used to allow Bugsnag settings to be set via attributes
274
+ // on the `script` tag, eg. `<script data-apikey="xyz">`.
275
+ // Similar to jQuery's `$(el).data()` method.
276
+ function getData(node) {
277
+ var dataAttrs = {};
278
+ var dataRegex = /^data\-([\w\-]+)$/;
279
+ var attrs = node.attributes;
280
+ for (var i = 0; i < attrs.length; i++) {
281
+ var attr = attrs[i];
282
+ if (dataRegex.test(attr.nodeName)) {
283
+ var key = attr.nodeName.match(dataRegex)[1];
284
+ dataAttrs[key] = attr.nodeValue;
285
+ }
286
+ }
287
+
288
+ return dataAttrs;
289
+ }
290
+
291
+ // Get configuration settings from either `self` (the `Bugsnag` object)
292
+ // or `data` (the `data-*` attributes).
293
+ var data;
294
+ function getSetting(name, fallback) {
295
+ data = data || getData(thisScript);
296
+ var setting = self[name] !== undefined ? self[name] : data[name.toLowerCase()];
297
+ if (setting === "false") {
298
+ setting = false;
299
+ }
300
+ return setting !== undefined ? setting : fallback;
301
+ }
302
+
303
+ // Validate a Bugsnag API key exists and is of the correct format.
304
+ function validateApiKey(apiKey) {
305
+ if (apiKey == null || !apiKey.match(API_KEY_REGEX)) {
306
+ log("Invalid API key '" + apiKey + "'");
307
+ return false;
308
+ }
309
+
310
+ return true;
311
+ }
312
+
313
+ // Send an error to Bugsnag.
314
+ function sendToBugsnag(details, metaData) {
315
+ // Validate the configured API key.
316
+ var apiKey = getSetting("apiKey");
317
+ if (!validateApiKey(apiKey) || !eventsRemaining) {
318
+ return;
319
+ }
320
+ eventsRemaining -= 1;
321
+
322
+ // Check if we should notify for this release stage.
323
+ var releaseStage = getSetting("releaseStage");
324
+ var notifyReleaseStages = getSetting("notifyReleaseStages");
325
+ if (notifyReleaseStages) {
326
+ var shouldNotify = false;
327
+ for (var i = 0; i < notifyReleaseStages.length; i++) {
328
+ if (releaseStage === notifyReleaseStages[i]) {
329
+ shouldNotify = true;
330
+ break;
331
+ }
332
+ }
333
+
334
+ if (!shouldNotify) {
335
+ return;
336
+ }
337
+ }
338
+
339
+ // Don't send multiple copies of the same error.
340
+ // This fixes a problem when a client goes into an infinite loop,
341
+ // and starts wasting all their bandwidth sending messages to bugsnag.
342
+ var deduplicate = [details.name, details.message, details.stacktrace].join("|");
343
+ if (deduplicate === previousNotification) {
344
+ return;
345
+ } else {
346
+ previousNotification = deduplicate;
347
+ }
348
+
349
+ if (lastEvent) {
350
+ metaData = metaData || {};
351
+ metaData["Last Event"] = eventToMetaData(lastEvent);
352
+ }
353
+
354
+ // Merge the local and global `metaData`.
355
+ var mergedMetaData = merge(merge({}, getSetting("metaData")), metaData);
356
+
357
+ // Run any `beforeNotify` function
358
+ var beforeNotify = self.beforeNotify;
359
+ if (typeof(beforeNotify) === "function") {
360
+ var retVal = beforeNotify(details, mergedMetaData);
361
+ if (retVal === false) {
362
+ return;
363
+ }
364
+ }
365
+
366
+ // Make the request:
367
+ //
368
+ // - Work out which endpoint to send to.
369
+ // - Combine error information with other data such as
370
+ // user-agent and locale, `metaData` and settings.
371
+ // - Make the HTTP request.
372
+ var location = window.location;
373
+ console.log(getSetting("endpoint"));
374
+ request(getSetting("endpoint") || DEFAULT_NOTIFIER_ENDPOINT, {
375
+ notifierVersion: NOTIFIER_VERSION,
376
+
377
+ apiKey: apiKey,
378
+ projectRoot: getSetting("projectRoot") || location.protocol + "//" + location.host,
379
+ context: getSetting("context") || location.pathname,
380
+ userId: getSetting("userId"), // Deprecated, remove in v3
381
+ user: getSetting("user"),
382
+ metaData: mergedMetaData,
383
+ releaseStage: releaseStage,
384
+ appVersion: getSetting("appVersion"),
385
+
386
+ url: window.location.href,
387
+ userAgent: navigator.userAgent,
388
+ language: navigator.language || navigator.userLanguage,
389
+
390
+ severity: details.severity,
391
+
392
+ name: details.name,
393
+ message: details.message,
394
+ stacktrace: details.stacktrace,
395
+ file: details.file,
396
+ lineNumber: details.lineNumber,
397
+ columnNumber: details.columnNumber
398
+ });
399
+ }
400
+
401
+ // Generate a browser stacktrace (or approximation) from the current stack.
402
+ // This is used to add a stacktrace to `Bugsnag.notify` calls, and to add a
403
+ // stacktrace approximation where we can't get one from an exception.
404
+ function generateStacktrace() {
405
+ var stacktrace;
406
+ var MAX_FAKE_STACK_SIZE = 10;
407
+ var ANONYMOUS_FUNCTION_PLACEHOLDER = "[anonymous]";
408
+
409
+ // Try to generate a real stacktrace (most browsers, except IE9 and below).
410
+ try {
411
+ throw new Error("");
412
+ } catch (exception) {
413
+ stacktrace = stacktraceFromException(exception);
414
+ }
415
+
416
+ // Otherwise, build a fake stacktrace from the list of method names by
417
+ // looping through the list of functions that called this one (and skip
418
+ // whoever called us).
419
+ if (!stacktrace) {
420
+ var functionStack = [];
421
+ try {
422
+ var curr = arguments.callee.caller.caller;
423
+ while (curr && functionStack.length < MAX_FAKE_STACK_SIZE) {
424
+ var fn = FUNCTION_REGEX.test(curr.toString()) ? RegExp.$1 || ANONYMOUS_FUNCTION_PLACEHOLDER : ANONYMOUS_FUNCTION_PLACEHOLDER;
425
+ functionStack.push(fn);
426
+ curr = curr.caller;
427
+ }
428
+ } catch (e) {
429
+ log(e);
430
+
431
+ }
432
+
433
+ stacktrace = functionStack.join("\n");
434
+ }
435
+
436
+ // Tell the backend to ignore the first two lines in the stack-trace.
437
+ // generateStacktrace() + window.onerror,
438
+ // generateStacktrace() + notify,
439
+ // generateStacktrace() + notifyException
440
+ return "<generated>\n" + stacktrace;
441
+ }
442
+
443
+ // Get the stacktrace string from an exception
444
+ function stacktraceFromException(exception) {
445
+ return exception.stack || exception.backtrace || exception.stacktrace;
446
+ }
447
+
448
+ // Populate the event tab of meta-data.
449
+ function eventToMetaData(event) {
450
+ var tab = {
451
+ millisecondsAgo: new Date() - event.timeStamp,
452
+ type: event.type,
453
+ which: event.which,
454
+ target: targetToString(event.target)
455
+ };
456
+
457
+ return tab;
458
+ }
459
+
460
+ // Convert a DOM element into a string suitable for passing to Bugsnag.
461
+ function targetToString(target) {
462
+ if (target) {
463
+ var attrs = target.attributes;
464
+
465
+ if (attrs) {
466
+ var ret = "<" + target.nodeName.toLowerCase();
467
+ for (var i = 0; i < attrs.length; i++) {
468
+ if (attrs[i].value && attrs[i].value.toString() != "null") {
469
+ ret += " " + attrs[i].name + "=\"" + attrs[i].value + "\"";
470
+ }
471
+ }
472
+ return ret + ">";
473
+ } else {
474
+ // e.g. #document
475
+ return target.nodeName;
476
+ }
477
+ }
478
+ }
479
+
480
+ // If we've notified bugsnag of an exception in wrap, we don't want to
481
+ // re-notify when it hits window.onerror after we re-throw it.
482
+ function ignoreNextOnError() {
483
+ ignoreOnError += 1;
484
+ window.setTimeout(function () {
485
+ ignoreOnError -= 1;
486
+ });
487
+ }
488
+
489
+ // Disable catching on IE < 10 as it destroys stack-traces from generateStackTrace()
490
+ if (!window.atob) {
491
+ shouldCatch = false;
492
+
493
+ // Disable catching on browsers that support HTML5 ErrorEvents properly.
494
+ // This lets debug on unhandled exceptions work.
495
+ } else if (window.ErrorEvent) {
496
+ try {
497
+ if (new window.ErrorEvent("test").colno === 0) {
498
+ shouldCatch = false;
499
+ }
500
+ } catch(e){ }
501
+ }
502
+
503
+
504
+ //
505
+ // ### Polyfilling
506
+ //
507
+
508
+ // Add a polyFill to an object
509
+ function polyFill(obj, name, makeReplacement) {
510
+ var original = obj[name];
511
+ var replacement = makeReplacement(original);
512
+ obj[name] = replacement;
513
+
514
+ if (typeof BUGSNAG_TESTING !== "undefined" && window.undo) {
515
+ window.undo.push(function () {
516
+ obj[name] = original;
517
+ });
518
+ }
519
+ }
520
+
521
+ if (getSetting("autoNotify", true)) {
522
+ //
523
+ // ### Automatic error notification
524
+ //
525
+ // Attach to `window.onerror` events and notify Bugsnag when they happen.
526
+ // These are mostly js compile/parse errors, but on some browsers all
527
+ // "uncaught" exceptions will fire this event.
528
+ //
529
+ polyFill(window, "onerror", function (_super) {
530
+ // Keep a reference to any existing `window.onerror` handler
531
+ if (typeof BUGSNAG_TESTING !== "undefined") {
532
+ self._onerror = _super;
533
+ }
534
+
535
+ return function bugsnag(message, url, lineNo, charNo, exception) {
536
+ var shouldNotify = getSetting("autoNotify", true);
537
+ var metaData = {};
538
+
539
+ // IE 6+ support.
540
+ if (!charNo && window.event) {
541
+ charNo = window.event.errorCharacter;
542
+ }
543
+
544
+ addScriptToMetaData(metaData);
545
+ lastScript = null;
546
+
547
+ if (shouldNotify && !ignoreOnError) {
548
+
549
+ sendToBugsnag({
550
+ name: exception && exception.name || "window.onerror",
551
+ message: message,
552
+ file: url,
553
+ lineNumber: lineNo,
554
+ columnNumber: charNo,
555
+ stacktrace: (exception && stacktraceFromException(exception)) || generateStacktrace(),
556
+ severity: "fatal"
557
+ }, metaData);
558
+ }
559
+
560
+ if (typeof BUGSNAG_TESTING !== "undefined") {
561
+ _super = self._onerror;
562
+ }
563
+
564
+ // Fire the existing `window.onerror` handler, if one exists
565
+ if (_super) {
566
+ _super(message, url, lineNo, charNo, exception);
567
+ }
568
+ };
569
+ });
570
+
571
+ var hijackTimeFunc = function (_super) {
572
+ // Note, we don't do `_super.call` because that doesn't work on IE 8,
573
+ // luckily this is implicitly window so it just works everywhere.
574
+ //
575
+ // setTimout in all browsers except IE <9 allows additional parameters
576
+ // to be passed, so in order to support these without resorting to call/apply
577
+ // we need an extra layer of wrapping.
578
+ return function (f, t) {
579
+ if (typeof f === "function") {
580
+ f = wrap(f);
581
+ var args = Array.prototype.slice.call(arguments, 2);
582
+ return _super(function () {
583
+ f.apply(this, args);
584
+ }, t);
585
+ } else {
586
+ return _super(f, t);
587
+ }
588
+ };
589
+ };
590
+
591
+ polyFill(window, "setTimeout", hijackTimeFunc);
592
+ polyFill(window, "setInterval", hijackTimeFunc);
593
+ if (window.requestAnimationFrame) {
594
+ polyFill(window, "requestAnimationFrame", hijackTimeFunc);
595
+ }
596
+
597
+ var hijackEventFunc = function (_super) {
598
+ return function (e, f, capture, secure) {
599
+ // HTML lets event-handlers be objects with a handlEvent function,
600
+ // we need to change f.handleEvent here, as self.wrap will ignore f.
601
+ if (f && f.handleEvent) {
602
+ f.handleEvent = wrap(f.handleEvent, {eventHandler: true});
603
+ }
604
+ return _super.call(this, e, wrap(f, {eventHandler: true}), capture, secure);
605
+ };
606
+ };
607
+
608
+ // EventTarget is all that's required in modern chrome/opera
609
+ // EventTarget + Window + ModalWindow is all that's required in modern FF (there are a few Moz prefixed ones that we're ignoring)
610
+ // The rest is a collection of stuff for Safari and IE 11. (Again ignoring a few MS and WebKit prefixed things)
611
+ "EventTarget Window Node ApplicationCache AudioTrackList ChannelMergerNode CryptoOperation EventSource FileReader HTMLUnknownElement IDBDatabase IDBRequest IDBTransaction KeyOperation MediaController MessagePort ModalWindow Notification SVGElementInstance Screen TextTrack TextTrackCue TextTrackList WebSocket WebSocketWorker Worker XMLHttpRequest XMLHttpRequestEventTarget XMLHttpRequestUpload".replace(/\w+/g, function (global) {
612
+ var prototype = window[global] && window[global].prototype;
613
+ if (prototype && prototype.hasOwnProperty && prototype.hasOwnProperty("addEventListener")) {
614
+ polyFill(prototype, "addEventListener", hijackEventFunc);
615
+ // We also need to hack removeEventListener so that you can remove any
616
+ // event listeners.
617
+ polyFill(prototype, "removeEventListener", hijackEventFunc);
618
+ }
619
+ });
620
+ }
621
+
622
+ return self;
623
+ });