-
Notifications
You must be signed in to change notification settings - Fork 570
/
Copy pathscouter-all.js
1023 lines (899 loc) · 35.3 KB
/
scouter-all.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/*!
* Copyright 2009-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*!
Copyright (c) 2013 Bugsnag, https://bugsnag.com/
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the Software
is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.*/
/**
* @author <a href="mailto:gunlee01@gmail.com">Gun Lee</a>
*/
/**
* catch browser's errors and send it to the collection service of scouter APM.
* modified from Bugsnag // https://github.com/bugsnag/bugsnag-js
* -- browser support --
* -- all browsers (IE9, IOS6, Chrome any, Safari any, FF any)
*/
// The `Scouter` object is the only globally exported variable
(function (window, old) {
var self = {},
lastEvent,
lastScript,
previousNotification,
shouldCatch = true,
ignoreOnError = 0,
// We've seen cases where individual clients can infinite loop sending us errors
// (in some cases 10,000+ errors per page). This limit is at the point where
// you've probably learned everything useful there is to debug the problem,
// and we're happy to under-estimate the count to save the client (and Scouter's) resources.
eventsRemaining = 10;
// #### Scouter.noConflict
//
// This is obsolete with UMD, as we cannot assume the global scope is polluted with
// the Scouter object anyway. In this case, it's up to the host Javascript file to
// correctly utilise this functionality.
//
// Maybe it's worth removing all together, if we're loading via any UMD method.
self.noConflict = function() {
window.Scouter = old;
return self;
};
// ### Scouter.refresh
//
// Resets the Scouter rate limit. If you have a large single-page app, you may
// wish to call this in your router to avoid exception reports being thrown
// away.
//
// By default Scouter aggressively limits the number of exception reports from
// one page load. This protects both the client's browser and our servers in
// cases where exceptions are thrown in tight loops or scroll handlers.
self.refresh = function() {
eventsRemaining = 10;
};
//
// ### Manual error notification (public methods)
//
// #### Scouter.notifyException
//
// Notify Scouter about a given `exception`, typically that you've caught
// with a `try/catch` statement or that you've generated yourself.
//
// It's almost always better to let an exception bubble rather than catching
// it, as that gives more consistent behaviour across browsers. Consider
// re-throwing instead of calling .notifyException.
//
// Since most JavaScript exceptions use the `Error` class, we also allow
// you to provide a custom error name when calling `notifyException`.
//
// The default value is "warning" and "error" and "info" are also supported by the
// backend, all other values cause the notification to be dropped; and you
// will not see it in your dashboard.
self.notifyException = function (exception, name, metaData, severity) {
if (name && typeof name !== "string") {
metaData = name;
name = undefined;
}
if (!metaData) {
metaData = {};
}
addScriptToMetaData(metaData);
sendToScouter({
name: name || exception.name,
message: exception.message || exception.description,
stacktrace: stacktraceFromException(exception) || generateStacktrace(),
file: exception.fileName || exception.sourceURL,
lineNumber: exception.lineNumber || exception.line,
columnNumber: exception.columnNumber ? exception.columnNumber + 1 : undefined,
severity: severity || "warning"
}, metaData);
};
// #### Scouter.notify
//
// Notify Scouter about an error by passing in a `name` and `message`,
// without requiring an exception.
self.notify = function (name, message, metaData, severity) {
sendToScouter({
name: name,
message: message,
stacktrace: generateStacktrace(),
// These are defaults so that 'scouter.notify()' calls show up in old IE,
// newer browsers get a legit stacktrace from generateStacktrace().
file: window.location.toString(),
lineNumber: 1,
severity: severity || "warning"
}, metaData);
};
// Return a function acts like the given function, but reports
// any exceptions to Scouter before re-throwing them.
//
// This is not a public function because it can only be used if
// the exception is not caught after being thrown out of this function.
//
// If you call wrap twice on the same function, it'll give you back the
// same wrapped function. This lets removeEventListener to continue to
// work.
function wrap(_super, options) {
try {
if (typeof _super !== "function") {
return _super;
}
if (!_super.scouter) {
var currentScript = getCurrentScript();
_super.scouter = function (event) {
if (options && options.eventHandler) {
lastEvent = event;
}
lastScript = currentScript;
// We set shouldCatch to false on IE < 10 because catching the error ruins the file/line as reported in window.onerror,
// We set shouldCatch to false on Chrome/Safari because it interferes with "break on unhandled exception"
// All other browsers need shouldCatch to be true, as they don't pass the exception object to window.onerror
if (shouldCatch) {
try {
return _super.apply(this, arguments);
} catch (e) {
// We do this rather than stashing treating the error like lastEvent
// because in FF 26 onerror is not called for synthesized event handlers.
if (getSetting("autoNotify", true)) {
self.notifyException(e, null, null, "error");
ignoreNextOnError();
}
throw e;
} finally {
lastScript = null;
}
} else {
var ret = _super.apply(this, arguments);
// in case of error, this is set to null in window.onerror
lastScript = null;
return ret;
}
};
_super.scouter.scouter = _super.scouter;
}
return _super.scouter;
// This can happen if _super is not a normal javascript function.
// For example, see https://github.com/bugsnag/bugsnag-js/issues/28
} catch (e) {
return _super;
}
}
//
// ### Script tag tracking
//
// To emulate document.currentScript we use document.scripts.last.
// This only works while synchronous scripts are running, so we track
// that here.
var synchronousScriptsRunning = document.readyState !== "complete";
function loadCompleted() {
synchronousScriptsRunning = false;
}
// from jQuery. We don't have quite such tight bounds as they do if
// we end up on the window.onload event as we don't try and hack
// the .scrollLeft() fix in because it doesn't work in frames so
// we'd need these fallbacks anyway.
// The worst that can happen is we group an event handler that fires
// before us into the last script tag.
if (document.addEventListener) {
document.addEventListener("DOMContentLoaded", loadCompleted, true);
window.addEventListener("load", loadCompleted, true);
} else {
window.attachEvent("onload", loadCompleted);
}
function getCurrentScript() {
var script = document.currentScript || lastScript;
if (!script && synchronousScriptsRunning) {
var scripts = document.scripts || document.getElementsByTagName("script");
script = scripts[scripts.length - 1];
}
return script;
}
function addScriptToMetaData(metaData) {
var script = getCurrentScript();
if (script) {
metaData.script = {
src: script.src,
content: getSetting("inlineScript", true) ? script.innerHTML : ""
};
}
}
//
// ### Helpers & Setup
//
// Compile regular expressions upfront.
var API_KEY_REGEX = /^[0-9a-f]{32}$/i;
var FUNCTION_REGEX = /function\s*([\w\-$]+)?\s*\(/i;
// Set up default notifier settings.
var DEFAULT_NOTIFIER_ENDPOINT = "/_scouter_browser.jsp";
var NOTIFIER_VERSION = "1.0.0";
// Keep a reference to the currently executing script in the DOM.
// We'll use this later to extract settings from attributes.
var scripts = document.getElementsByTagName("script");
var thisScript = scripts[scripts.length - 1];
// Simple logging function that wraps `console.log` if available.
// This is useful for warning about configuration issues
// eg. forgetting to set an API key.
function log(msg) {
var disableLog = getSetting("disableLog");
var console = window.console;
if (console !== undefined && console.log !== undefined && !disableLog) {
console.log("[Scouter] " + msg);
}
}
// Deeply serialize an object into a query string. We use the PHP-style
// nested object syntax, `nested[keys]=val`, to support heirachical
// objects. Similar to jQuery's `$.param` method.
function serialize(obj, prefix, depth) {
if (depth >= 5) {
return encodeURIComponent(prefix) + "=[RECURSIVE]";
}
depth = depth + 1 || 1;
try {
if (window.Node && obj instanceof window.Node) {
return encodeURIComponent(prefix) + "=" + encodeURIComponent(targetToString(obj));
}
var str = [];
for (var p in obj) {
if (obj.hasOwnProperty(p) && p != null && obj[p] != null) {
var k = prefix ? prefix + "[" + p + "]" : p, v = obj[p];
str.push(typeof v === "object" ? serialize(v, k, depth) : encodeURIComponent(k) + "=" + encodeURIComponent(v));
}
}
return str.join("&");
} catch (e) {
return encodeURIComponent(prefix) + "=" + encodeURIComponent("" + e);
}
}
// Deep-merge the `source` object into the `target` object and return
// the `target`. Properties in source that will overwrite those in target.
// Similar to jQuery's `$.extend` method.
function merge(target, source) {
if (source == null) {
return target;
}
target = target || {};
for (var key in source) {
if (source.hasOwnProperty(key)) {
try {
if (source[key].constructor === Object) {
target[key] = merge(target[key], source[key]);
} else {
target[key] = source[key];
}
} catch (e) {
target[key] = source[key];
}
}
}
return target;
}
// Make a HTTP request with given `url` and `params` object.
// For maximum browser compatibility and cross-domain support, requests are
// made by creating a temporary JavaScript `Image` object.
function request(url, params) {
url += "?" + serialize(params) + "&ct=img&cb=" + new Date().getTime();
if (typeof SCOUTER_TESTING !== "undefined" && self.testRequest) {
self.testRequest(url, params);
} else {
var img = new Image();
img.src = url;
}
}
// Extract all `data-*` attributes from a DOM element and return them as an
// object. This is used to allow Scouter settings to be set via attributes
// on the `script` tag, eg. `<script data-apikey="xyz">`.
// Similar to jQuery's `$(el).data()` method.
function getData(node) {
var dataAttrs = {};
var dataRegex = /^data\-([\w\-]+)$/;
// If the node doesn't exist due to being loaded as a commonjs module,
// then return an empty object and fallback to self[].
if (node) {
var attrs = node.attributes;
for (var i = 0; i < attrs.length; i++) {
var attr = attrs[i];
if (dataRegex.test(attr.nodeName)) {
var key = attr.nodeName.match(dataRegex)[1];
dataAttrs[key] = attr.value || attr.nodeValue;
}
}
}
return dataAttrs;
}
// Get configuration settings from either `self` (the `Scouter` object)
// or `data` (the `data-*` attributes).
var data;
function getSetting(name, fallback) {
data = data || getData(thisScript);
var setting = self[name] !== undefined ? self[name] : data[name.toLowerCase()];
if (setting === "false") {
setting = false;
}
return setting !== undefined ? setting : fallback;
}
// Validate a Scouter API key exists and is of the correct format.
function validateApiKey(apiKey) {
if (!apiKey || !apiKey.match(API_KEY_REGEX)) {
log("Invalid API key '" + apiKey + "'");
return false;
}
return true;
}
// Send an error to Scouter.
function sendToScouter(details, metaData) {
// Validate the configured API key.
var apiKey = getSetting("apiKey");
if (!validateApiKey(apiKey) || !eventsRemaining) {
return;
}
eventsRemaining -= 1;
// Check if we should notify for this release stage.
var releaseStage = getSetting("releaseStage", "production");
var notifyReleaseStages = getSetting("notifyReleaseStages");
if (notifyReleaseStages) {
var shouldNotify = false;
for (var i = 0; i < notifyReleaseStages.length; i++) {
if (releaseStage === notifyReleaseStages[i]) {
shouldNotify = true;
break;
}
}
if (!shouldNotify) {
return;
}
}
// Don't send multiple copies of the same error.
// This fixes a problem when a client goes into an infinite loop,
// and starts wasting all their bandwidth sending messages to scouter.
var deduplicate = [details.name, details.message, details.stacktrace].join("|");
if (deduplicate === previousNotification) {
return;
} else {
previousNotification = deduplicate;
}
if (lastEvent) {
metaData = metaData || {};
metaData["Last Event"] = eventToMetaData(lastEvent);
}
// Build the request payload by combining error information with other data
// such as user-agent and locale, `metaData` and settings.
var payload = {
notifierVersion: NOTIFIER_VERSION,
apiKey: apiKey,
projectRoot: getSetting("projectRoot") || window.location.protocol + "//" + window.location.host,
context: getSetting("context") || window.location.pathname,
userId: getSetting("userId"), // Deprecated, remove in v3
user: getSetting("user"),
metaData: merge(merge({}, getSetting("metaData")), metaData),
releaseStage: releaseStage,
appVersion: getSetting("appVersion"),
url: window.location.href,
userAgent: navigator.userAgent,
language: navigator.language || navigator.userLanguage,
severity: details.severity,
name: details.name,
message: details.message,
stacktrace: details.stacktrace,
file: details.file,
lineNumber: details.lineNumber,
columnNumber: details.columnNumber,
payloadVersion: "2"
};
// Run any `beforeNotify` function
var beforeNotify = self.beforeNotify;
if (typeof(beforeNotify) === "function") {
var retVal = beforeNotify(payload, payload.metaData);
if (retVal === false) {
return;
}
}
if (payload.lineNumber === 0 && (/Script error\.?/).test(payload.message)) {
return log("Ignoring cross-domain script error. See https://bugsnag.com/docs/notifiers/js/cors");
}
// Make the HTTP request
request(getSetting("endpoint") || DEFAULT_NOTIFIER_ENDPOINT, payload);
}
// Generate a browser stacktrace (or approximation) from the current stack.
// This is used to add a stacktrace to `Scouter.notify` calls, and to add a
// stacktrace approximation where we can't get one from an exception.
function generateStacktrace() {
var generated, stacktrace;
var MAX_FAKE_STACK_SIZE = 10;
var ANONYMOUS_FUNCTION_PLACEHOLDER = "[anonymous]";
// Try to generate a real stacktrace (most browsers, except IE9 and below).
try {
throw new Error("");
} catch (exception) {
generated = "<generated>\n";
stacktrace = stacktraceFromException(exception);
}
// Otherwise, build a fake stacktrace from the list of method names by
// looping through the list of functions that called this one (and skip
// whoever called us).
if (!stacktrace) {
generated = "<generated-ie>\n";
var functionStack = [];
try {
var curr = arguments.callee.caller.caller;
while (curr && functionStack.length < MAX_FAKE_STACK_SIZE) {
var fn = FUNCTION_REGEX.test(curr.toString()) ? RegExp.$1 || ANONYMOUS_FUNCTION_PLACEHOLDER : ANONYMOUS_FUNCTION_PLACEHOLDER;
functionStack.push(fn);
curr = curr.caller;
}
} catch (e) {
log(e);
}
stacktrace = functionStack.join("\n");
}
// Tell the backend to ignore the first two lines in the stack-trace.
// generateStacktrace() + window.onerror,
// generateStacktrace() + notify,
// generateStacktrace() + notifyException
return generated + stacktrace;
}
// Get the stacktrace string from an exception
function stacktraceFromException(exception) {
return exception.stack || exception.backtrace || exception.stacktrace;
}
// Populate the event tab of meta-data.
function eventToMetaData(event) {
var tab = {
millisecondsAgo: new Date() - event.timeStamp,
type: event.type,
which: event.which,
target: targetToString(event.target)
};
return tab;
}
// Convert a DOM element into a string suitable for passing to Scouter.
function targetToString(target) {
if (target) {
var attrs = target.attributes;
if (attrs) {
var ret = "<" + target.nodeName.toLowerCase();
for (var i = 0; i < attrs.length; i++) {
if (attrs[i].value && attrs[i].value.toString() != "null") {
ret += " " + attrs[i].name + "=\"" + attrs[i].value + "\"";
}
}
return ret + ">";
} else {
// e.g. #document
return target.nodeName;
}
}
}
// If we've notified scouter of an exception in wrap, we don't want to
// re-notify when it hits window.onerror after we re-throw it.
function ignoreNextOnError() {
ignoreOnError += 1;
window.setTimeout(function () {
ignoreOnError -= 1;
});
}
// Disable catching on IE < 10 as it destroys stack-traces from generateStackTrace()
if (!window.atob) {
shouldCatch = false;
// Disable catching on browsers that support HTML5 ErrorEvents properly.
// This lets debug on unhandled exceptions work.
} else if (window.ErrorEvent) {
try {
if (new window.ErrorEvent("test").colno === 0) {
shouldCatch = false;
}
} catch(e){ }
}
//
// ### Polyfilling
//
// Add a polyFill to an object
function polyFill(obj, name, makeReplacement) {
var original = obj[name];
var replacement = makeReplacement(original);
obj[name] = replacement;
if (typeof SCOUTER_TESTING !== "undefined" && window.undo) {
window.undo.push(function () {
obj[name] = original;
});
}
}
if (getSetting("autoNotify", true)) {
//
// ### Automatic error notification
//
// Attach to `window.onerror` events and notify Scouter when they happen.
// These are mostly js compile/parse errors, but on some browsers all
// "uncaught" exceptions will fire this event.
//
polyFill(window, "onerror", function (_super) {
// Keep a reference to any existing `window.onerror` handler
if (typeof SCOUTER_TESTING !== "undefined") {
self._onerror = _super;
}
return function scouter(message, url, lineNo, charNo, exception) {
var shouldNotify = getSetting("autoNotify", true);
var metaData = {};
// IE 6+ support.
if (!charNo && window.event) {
charNo = window.event.errorCharacter;
}
addScriptToMetaData(metaData);
lastScript = null;
if (shouldNotify && !ignoreOnError) {
sendToScouter({
name: exception && exception.name || "window.onerror",
message: message,
file: url,
lineNumber: lineNo,
columnNumber: charNo,
stacktrace: (exception && stacktraceFromException(exception)) || generateStacktrace(),
severity: "error"
}, metaData);
}
if (typeof SCOUTER_TESTING !== "undefined") {
_super = self._onerror;
}
// Fire the existing `window.onerror` handler, if one exists
if (_super) {
_super(message, url, lineNo, charNo, exception);
}
};
});
var hijackTimeFunc = function (_super) {
// Note, we don't do `_super.call` because that doesn't work on IE 8,
// luckily this is implicitly window so it just works everywhere.
//
// setTimout in all browsers except IE <9 allows additional parameters
// to be passed, so in order to support these without resorting to call/apply
// we need an extra layer of wrapping.
return function (f, t) {
if (typeof f === "function") {
f = wrap(f);
var args = Array.prototype.slice.call(arguments, 2);
return _super(function () {
f.apply(this, args);
}, t);
} else {
return _super(f, t);
}
};
};
polyFill(window, "setTimeout", hijackTimeFunc);
polyFill(window, "setInterval", hijackTimeFunc);
if (window.requestAnimationFrame) {
polyFill(window, "requestAnimationFrame", function (_super) {
return function (callback) {
return _super(wrap(callback));
};
});
}
if (window.setImmediate) {
polyFill(window, "setImmediate", function (_super) {
return function (f) {
var args = Array.prototype.slice.call(arguments);
args[0] = wrap(args[0]);
return _super.apply(this, args);
};
});
}
// EventTarget is all that's required in modern chrome/opera
// EventTarget + Window + ModalWindow is all that's required in modern FF (there are a few Moz prefixed ones that we're ignoring)
// The rest is a collection of stuff for Safari and IE 11. (Again ignoring a few MS and WebKit prefixed things)
"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) {
var prototype = window[global] && window[global].prototype;
if (prototype && prototype.hasOwnProperty && prototype.hasOwnProperty("addEventListener")) {
polyFill(prototype, "addEventListener", function (_super) {
return function (e, f, capture, secure) {
// HTML lets event-handlers be objects with a handlEvent function,
// we need to change f.handleEvent here, as self.wrap will ignore f.
try {
if (f && f.handleEvent) {
f.handleEvent = wrap(f.handleEvent, {eventHandler: true});
}
} catch (err) {
// When selenium is installed, we sometimes get 'Permission denied to access property "handleEvent"'
// Because this catch is around Scouter library code, it won't catch any user errors
log(err);
}
return _super.call(this, e, wrap(f, {eventHandler: true}), capture, secure);
};
});
// We also need to hack removeEventListener so that you can remove any
// event listeners.
polyFill(prototype, "removeEventListener", function (_super) {
return function (e, f, capture, secure) {
_super.call(this, e, f, capture, secure);
return _super.call(this, e, wrap(f), capture, secure);
};
});
}
});
}
window.Scouter = self;
// If people are using a javascript loader, we should integrate with it.
// We don't want to defer instrumenting their code with callbacks however,
// so we slightly abuse the intent and continue with our plan of polyfilling
// the browser whether or not they ever actually require the module.
// This has the nice side-effect of continuing to work when people are using
// AMD but loading Scouter via a CDN.
// It has the not-so-nice side-effect of polluting the global namespace, but
// you can easily call Scouter.noConflict() to fix that.
if (typeof define === "function" && define.amd) {
// AMD
define([], function () {
return self;
});
} else if (typeof module === "object" && typeof module.exports === "object") {
// CommonJS/Browserify
module.exports = self;
}
})(window, window.Scouter);
/*!
* Copyright 2009-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @author <a href="mailto:gunlee01@gmail.com">Gun Lee</a>
*/
/**
* measure browser's ajax timing and send it to the collection service of scouter APM.
* -- browser support --
* -- all modern browsers ( IE9+, IOS6+, Chrome any, Safari any, FF any)
*/
(function(XHR) {
"use strict";
var _p = window.scouter || {};
var DEFAULT_END_POINT = '/_scouter_browser.jsp';
var DEFAULT_GXID_HEADER = 'scouter_gxid';
var DEFAULT_GATHER_RATIO = 100.0; //unit:% - default:100.0%
//options
_p.endPoint = _p.endPoint || DEFAULT_END_POINT;
_p.debug = _p.debug || false;
_p.gxidHeader = _p.gxidHeader || DEFAULT_GXID_HEADER;
_p.gatherRatio = _p.gatherRatio || DEFAULT_GATHER_RATIO;
var stats = [];
var timeoutId = null;
var open = XHR.prototype.open;
var send = XHR.prototype.send;
XHR.prototype.open = function(method, url, async, user, pass) {
this._url = url;
open.call(this, method, url, async, user, pass);
};
XHR.prototype.send = function(data) {
var self = this;
var start;
var oldOnReadyStateChange;
var url = this._url;
function onReadyStateChange() {
var random1000 = Math.floor(Math.random()*1000);
if(self.readyState == 4 && (random1000 <= Math.floor(_p.gatherRatio * 10))) {
var resGxid = self.getResponseHeader(_p.gxid_header);
var time = new Date() - start;
stats.push({
url: url,
duration: time,
gxid: resGxid,
userAgent: navigator.userAgent
});
if(!timeoutId) {
timeoutId = window.setTimeout(function() {
var queryString = JSON.stringify({stats:stats}, undefined, 0);
var xhr = new XHR();
xhr.noIntercept = true;
var fullQuery = _p.endPoint + '?p=ax&q=' + encodeURIComponent(queryString);
if(_p.debug) {
console.log('fullQuery = ' + fullQuery);
}
xhr.open("GET", fullQuery, true);
xhr.send();
timeoutId = null;
stats = [];
}, 1000);
}
}
if(oldOnReadyStateChange) {
oldOnReadyStateChange();
}
}
if(!this.noIntercept) {
start = new Date();
if(this.addEventListener) {
this.addEventListener("readystatechange", onReadyStateChange, false);
} else {
oldOnReadyStateChange = this.onreadystatechange;
this.onreadystatechange = onReadyStateChange;
}
}
send.call(this, data);
}
})(XMLHttpRequest);
/*!
* Copyright 2009-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @author <a href="mailto:gunlee01@gmail.com">Gun Lee</a>
*/
/**
* measure browser's performance timing and send it to the collection service of scouter APM.
* -- browser support --
* -- all modern browsers ( IE9+, IOS6+, Chrome any, Safari any, FF any)
*/
(function() {
var _p = window.scouter || {};
var DEFAULT_END_POINT = "/_scouter_browser.jsp";
var DEFAULT_GXID_HEADER = 'scouter_gxid';
var DEFAULT_GATHER_RATIO = 100.0; //unit:% - default:100.0%
_p.endPoint = _p.endPoint || DEFAULT_END_POINT;
_p.debug = _p.debug || false;
_p.gxid_header = _p.gxid_header || DEFAULT_GXID_HEADER;
_p.gatherRatio = _p.gatherRatio || DEFAULT_GATHER_RATIO;
if(!document.addEventListener) return; //Not support IE8-
//gather ratio condition
var random1000 = Math.floor(Math.random()*1000);
if(random1000 <= Math.floor(_p.gatherRatio * 10)) {
window.addEventListener("load", function() {
document.removeEventListener("load", arguments.callee, false);
navtiming();
}, false);
}
var navtiming = function(){
//TODO - read gxid from cookie(how to ensure on the case of multiple requests-ex. with browser frames?)
var resGxid = getCookie(_p.gxid_header);
setTimeout(function(){
var performance = window.performance || window.webkitPerformance || window.msPerformance || window.mozPerformance;
if(performance === undefined) {
console.log('Unfortunately, your browser does not support the Navigation Timing API');
return;
}
var t = performance.timing;
var navtiming = {
gxid: resGxId,
navigationStart: t.navigationStart,
unloadEventStart: t.unloadEventStart,
unloadEventEnd: t.unloadEventEnd,
redirectStart: t.redirectStart,
redirectEnd: t.redirectEnd,
fetchStart: t.fetchStart,
domainLookupStart: t.domainLookupStart,
domainLookupEnd: t.domainLookupEnd,
connectStart: t.connectStart,
connectEnd: t.connectEnd,
secureConnectionStart: t.secureConnectionStart,
requestStart: t.requestStart,
responseStart: t.responseStart,
responseEnd: t.responseEnd,
domLoading: t.domLoading,
domInteractive: t.domInteractive,
domContentLoadedEventStart: t.domContentLoadedEventStart,
domContentLoadedEventEnd: t.domContentLoadedEventEnd,
domComplete: t.domComplete,
loadEventStart: t.loadEventStart,
loadEventEnd: t.loadEventEnd
};
if(_p.debug) {
console.log(navtiming);
console.log('resGxid = ' + resGxid);
console.log('1st client:%d', navtiming.domainLookupStart - navtiming.navigationStart);
console.log('1st n/w-req:%d', navtiming.requestStart - navtiming.domainLookupStart);
console.log('1st server:%d', navtiming.responseStart - navtiming.requestStart);
console.log('2nd n/w-res:%d', navtiming.responseEnd - navtiming.responseStart);
console.log('after response, total time:%d', navtiming.loadEventEnd - navtiming.responseEnd);
console.log('responseEnd to domInteractive:%d', navtiming.domLoading - navtiming.responseEnd);
console.log('domInteractive to domContentLoadedEventEnd:%d', navtiming.domContentLoadedEventEnd - navtiming.domInteractive);
console.log('domContentLoadedEventEnd to domComplete:%d', navtiming.domComplete - navtiming.domContentLoadedEventEnd);
console.log('domComplete to loadEventEnd:%d', navtiming.loadEventEnd - navtiming.domComplete);
console.log('loadEventStart to loadEventEnd:%d', navtiming.loadEventEnd - navtiming.loadEventStart);
}
sendToScouter(navtiming);
}, 0);
};
// Deeply serialize an object into a query string. We use the PHP-style
// nested object syntax, `nested[keys]=val`, to support hierarchical
// objects. Similar to jQuery's `$.param` method.
function serialize(obj, prefix) {
var str = [];
for (var p in obj) {
if (obj.hasOwnProperty(p) && p != null && obj[p] != null) {
var k = prefix ? prefix + "[" + p + "]" : p, v = obj[p];
str.push(typeof v === "object" ? serialize(v, k) : encodeURIComponent(k) + "=" + encodeURIComponent(v));
}
}
return str.join("&");
}
function sendToScouter(t) {
var location = window.location;
var sendObj = {
host: location.protocol + "//" + location.host,
uri: location.pathname,
url: window.location.href,
userAgent: navigator.userAgent,
gxid: resGxId,
navigationStart: t.navigationStart,
unloadEventStart: t.unloadEventStart,
unloadEventEnd: t.unloadEventEnd,
redirectStart: t.redirectStart,
redirectEnd: t.redirectEnd,
fetchStart: t.fetchStart,
domainLookupStart: t.domainLookupStart,
domainLookupEnd: t.domainLookupEnd,
connectStart: t.connectStart,
connectEnd: t.connectEnd,
secureConnectionStart: t.secureConnectionStart,
requestStart: t.requestStart,
responseStart: t.responseStart,
responseEnd: t.responseEnd,
domLoading: t.domLoading,
domInteractive: t.domInteractive,
domContentLoadedEventStart: t.domContentLoadedEventStart,
domContentLoadedEventEnd: t.domContentLoadedEventEnd,
domComplete: t.domComplete,
loadEventStart: t.loadEventStart,
loadEventEnd: t.loadEventEnd
};