wat_catcher 0.6.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
+ });