├── .github └── repository_metadata.yml ├── .gitignore ├── .snyk ├── .travis.yml ├── LICENSE ├── README.md ├── bower.json ├── component.json ├── cortex.yaml ├── deps ├── console-extern.js └── umd-extern.js ├── gulpfile.js ├── karma.conf-ci.js ├── karma.conf.js ├── package-lock.json ├── package.json ├── product ├── README.md ├── r7insight.js └── r7insight.min.js ├── src ├── README.md └── r7insight.js └── test └── r7insightSpec.js /.github/repository_metadata.yml: -------------------------------------------------------------------------------- 1 | 2 | repo_owners: 3 | - dublin-splinter 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | lib/* 2 | build/* 3 | node_modules/ 4 | test/live-client.html -------------------------------------------------------------------------------- /.snyk: -------------------------------------------------------------------------------- 1 | version: v1.25.0 2 | ignore: {} 3 | patch: {} 4 | exclude: {} 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - lts/carbon 4 | sudo: false 5 | deploy: 6 | provider: releases 7 | file: product/r7insight.min.js 8 | skip_cleanup: true 9 | on: 10 | repo: rapid7/r7insight_js 11 | tags: true 12 | env: 13 | global: 14 | - secure: AToXqsiOsYiCigEK0bmNBVwgQQ5fKk6+hJWWSL/A+1v+tWaa3HC7+0p3Mscy1PBqX1PeYqD7GGUUpECUGUzqLjsbXhSZQ11ECKVk1qCw18dVxE/rxKVsvTA1Ie0xOwsQvqwkxfRAVgYwN3D3smDuZXLRvASMdUfwEPAq5BXZhc0= 15 | - secure: Jk+2mrcxAzhnTtImz9hhDb/JLu4rCl2jR3KCZJUzH4OQMwYbWcy7BeZpnJFWgDoylohOTUQMpkpzLZfusbyDREJAgV+sSPEFYN1yOFIwM2t1RL1Ajl1m/eCsYWuHYcLXcRutMqVRrMW+6olA8GjUHPr/vK0gTNrZfBVXvtJL6BE= 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2019 Rapid7 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | r7insight.js 2 | ===== 3 | 4 | Client-side JavaScript logging library for [Rapid7 Insight Platform](https://www.rapid7.com/solutions/it-operations/). 5 | 6 | [![Build Status](https://travis-ci.org/rapid7/r7insight_js.png?branch=master)](https://travis-ci.org/rapid7/r7insight_js) 7 | 8 | Features 9 | -------- 10 | 11 | * Small: ~3k (minified) 12 | * Cross-browser compatible 13 | * No external dependencies 14 | * Simple API 15 | * AMD and CommonJS support 16 | 17 | Quick start 18 | ----------- 19 | 20 | Start [here](https://github.com/rapid7/r7insight_js/wiki/Getting-started), then check out the rest of the wiki. 21 | 22 | Don't have an account? Get one [for free](https://www.rapid7.com/products/insightops/try/). 23 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "r7insight_js", 3 | "version": "1.0", 4 | "homepage": "https://rapid7.com", 5 | "description": "Javascript library for Rapid7 Insight", 6 | "main": "product/r7insight.js", 7 | "license": "MIT", 8 | "ignore": [ 9 | "**/.*", 10 | "bower_components", 11 | "build", 12 | "deps", 13 | "dyn.xhr", 14 | "lib", 15 | "node_modules", 16 | "src", 17 | "test", 18 | "gulpfile.js" 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /component.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "r7insight_js", 3 | "repo": "rapid7/r7insight_js", 4 | "description": "Client-side JavaScript logging library for Rapid7 Insight", 5 | "version": "0.0.3", 6 | "keywords": [], 7 | "dependencies": {}, 8 | "development": {}, 9 | "license": "MIT", 10 | "main": "product/r7insight.common.js", 11 | "scripts": [ 12 | "product/r7insight.common.js" 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /cortex.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | info: 3 | title: R7insight Js 4 | description: Client-side JavaScript logging library for InsightOps 5 | x-cortex-git: 6 | github: 7 | alias: r7org 8 | repository: rapid7/r7insight_js 9 | x-cortex-tag: r7insight-js 10 | x-cortex-type: service 11 | x-cortex-groups: 12 | - exposure:opensource 13 | x-cortex-domain-parents: 14 | - tag: logsearch 15 | openapi: 3.0.1 16 | servers: 17 | - url: "/" 18 | -------------------------------------------------------------------------------- /deps/console-extern.js: -------------------------------------------------------------------------------- 1 | var console = {}; 2 | /** 3 | * @param {string} message 4 | */ 5 | console.warn = function(message) {}; 6 | console.error = function(message) {}; 7 | -------------------------------------------------------------------------------- /deps/umd-extern.js: -------------------------------------------------------------------------------- 1 | // CommonJS 2 | var module = { 3 | exports: null 4 | }; 5 | var exports = null; 6 | 7 | // CommonJS/Browserify 8 | var global = null; 9 | 10 | // AMD 11 | var define = function(require, module){}; 12 | define.amd = true; -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp'); 2 | var concat = require('gulp-concat'); 3 | var closureCompiler = require('google-closure-compiler').gulp(); 4 | var rename = require('gulp-rename'); 5 | var replace = require('gulp-replace'); 6 | 7 | var testFiles = [ 8 | 'src/r7insight.js', 9 | 'test/sinon*.js', 10 | 'test/*Spec.js' 11 | ]; 12 | var apiVersion = 1; 13 | var apiEndpoint = 'js.logs.insight.rapid7.com/v' + apiVersion; 14 | var webhookEndpoint = 'webhook.logs.insight.rapid7.com/noformat'; 15 | 16 | 17 | gulp.task('default', ['test', 'build']); 18 | 19 | 20 | gulp.task('watch', function() { 21 | gulp.watch('src/r7insight.js', ['test']); 22 | }); 23 | 24 | 25 | gulp.task('build', function() { 26 | return gulp.src('src/r7insight.js') 27 | .pipe(concat('r7insight.js')) // We've only got one file but still need this 28 | .pipe(replace(/localhost:8080\/v1/g, apiEndpoint)) 29 | .pipe(replace(/localhost:8080\/noformat/g, webhookEndpoint)) 30 | .pipe(gulp.dest('product')) 31 | .pipe(closureCompiler({ 32 | compilation_level: 'SIMPLE_OPTIMIZATIONS', 33 | warning_level: 'VERBOSE', 34 | debug: false, 35 | language_in: 'ECMASCRIPT5_STRICT', 36 | externs: 'deps/umd-extern.js' 37 | })) 38 | .pipe(rename('r7insight.min.js')) 39 | .pipe(gulp.dest('product')); 40 | }); 41 | -------------------------------------------------------------------------------- /karma.conf-ci.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | 3 | module.exports = function(config) { 4 | 5 | config.set({ 6 | // base path that will be used to resolve all patterns (eg. files, exclude) 7 | basePath: '', 8 | // frameworks to use 9 | // available frameworks: https://npmjs.org/browse/keyword/karma-adapter 10 | frameworks: ['jasmine', 'sinon'], 11 | 12 | // list of files / patterns to load in the browser 13 | files: [ 14 | 'src/r7insight.js', 15 | 'test/r7insightSpec.js' 16 | ], 17 | 18 | colors: true, 19 | 20 | // level of logging 21 | // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG 22 | logLevel: config.LOG_INFO, 23 | 24 | singleRun: true, 25 | browsers: ['PhantomJS'] 26 | }); 27 | }; 28 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration 2 | // Generated on Fri Apr 04 2014 16:41:37 GMT+0100 (IST) 3 | /*jslint node:true*/ 4 | 5 | module.exports = function(config) { 6 | config.set({ 7 | 8 | // base path that will be used to resolve all patterns (eg. files, exclude) 9 | basePath: '', 10 | 11 | 12 | // frameworks to use 13 | // available frameworks: https://npmjs.org/browse/keyword/karma-adapter 14 | frameworks: ['jasmine', 'sinon'], 15 | 16 | 17 | // list of files / patterns to load in the browser 18 | files: [ 19 | // Source files 20 | 'src/*.js', 21 | // Specs 22 | 'test/*Spec.js' 23 | ], 24 | 25 | 26 | // list of files to exclude 27 | exclude: [ 28 | 29 | ], 30 | 31 | 32 | // preprocess matching files before serving them to the browser 33 | // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor 34 | preprocessors: { 35 | 36 | }, 37 | 38 | 39 | // test results reporter to use 40 | // possible values: 'dots', 'progress' 41 | // available reporters: https://npmjs.org/browse/keyword/karma-reporter 42 | reporters: ['progress'], 43 | 44 | 45 | // web server port 46 | port: 9876, 47 | 48 | 49 | // enable / disable colors in the output (reporters and logs) 50 | colors: true, 51 | 52 | 53 | // level of logging 54 | // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG 55 | logLevel: config.LOG_INFO, 56 | 57 | 58 | // enable / disable watching file and executing tests whenever any file changes 59 | autoWatch: true, 60 | 61 | 62 | // start these browsers 63 | // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher 64 | browsers: ['PhantomJS'], 65 | 66 | 67 | // Continuous Integration mode 68 | // if true, Karma captures browsers, runs the tests and exits 69 | singleRun: false 70 | }); 71 | }; 72 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "r7insight_js", 3 | "version": "1.1.2", 4 | "description": "Javascript library for Rapid7 Insight", 5 | "main": "product/r7insight.js", 6 | "directories": { 7 | "test": "test" 8 | }, 9 | "scripts": { 10 | "test": "karma start karma.conf-ci.js", 11 | "build": "gulp build", 12 | "localtest": "karma start; jshint src/ test/r7insightSpec.js" 13 | }, 14 | "repository": { 15 | "type": "git", 16 | "url": "git://github.com/rapid7/r7insight_js.git" 17 | }, 18 | "author": "", 19 | "license": "MIT", 20 | "bugs": { 21 | "url": "https://github.com/rapid7/r7insight_js/issues" 22 | }, 23 | "homepage": "https://rapid7.com", 24 | "devDependencies": { 25 | "google-closure-compiler": "20161024.3.0", 26 | "gulp": "3.9.1", 27 | "gulp-concat": "2.3.5", 28 | "gulp-rename": "1.2.2", 29 | "gulp-replace": "0.2.0", 30 | "jshint": "2.5.11", 31 | "karma": "6.3.16", 32 | "karma-cli": "1.0.1", 33 | "karma-jasmine": "0.1.6", 34 | "karma-phantomjs-launcher": "1.0.4", 35 | "karma-sinon": "1.0.5", 36 | "sinon": "1.12.2" 37 | }, 38 | "engines": { 39 | "node": ">=0.10" 40 | }, 41 | "jshintConfig": { 42 | "undef": true, 43 | "es3": true 44 | }, 45 | "dependencies": {} 46 | } 47 | -------------------------------------------------------------------------------- /product/README.md: -------------------------------------------------------------------------------- 1 | r7insight.js 2 | ===== 3 | 4 | The files in this directory are auto-generated through `gulp build`. If you alter them by hand, expect them to be overwritten the next time you run the task! 5 | Last release compiled using node v10.17.0 6 | 7 | Contents 8 | -------- 9 | 10 | Several files are produced in the build. You'll probably just want to use __r7insight.min.js__, however. 11 | 12 | * __r7insight.js__: JS client library, unminified 13 | * __r7insight.min.js__: JS client library, minified 14 | -------------------------------------------------------------------------------- /product/r7insight.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license Copyright 2019 Rapid7. 3 | * Please view license at https://raw.github.com/rapid7/r7insight_js/master/LICENSE 4 | */ 5 | 6 | /*jslint browser:true*/ 7 | /*global define, module, exports, console, global */ 8 | 9 | /** @param {Object} window */ 10 | (function (root, factory) { 11 | if (typeof define === 'function' && define.amd) { 12 | // AMD. Register as an anonymous module. 13 | define(function() { 14 | return factory(root); 15 | }); 16 | } else if (typeof exports === 'object') { 17 | // Node. Does not work with strict CommonJS, but 18 | // only CommonJS-like environments that support module.exports, 19 | // like Node. 20 | if (typeof global === 'object') { 21 | // Browserify. The calling object `this` does not reference window. 22 | // `global` and `this` are equivalent in Node, preferring global 23 | // adds support for Browserify. 24 | root = global; 25 | } 26 | module.exports = factory(root); 27 | } else { 28 | // Browser globals (root is window) 29 | root.R7Insight = factory(root); 30 | } 31 | }(this, function (window) { 32 | "use strict"; 33 | // cross-browser indexOf fix 34 | var _indexOf = function (array, obj) { 35 | for (var i = 0; i < array.length; i++) { 36 | if (obj === array[i]) { 37 | return i; 38 | } 39 | } 40 | return -1; 41 | }; 42 | 43 | // Obtain a browser-specific XHR object 44 | var _getAjaxObject = function () { 45 | if (typeof XDomainRequest !== "undefined") { 46 | // We're using IE8/9 47 | return new XDomainRequest(); 48 | } 49 | return new XMLHttpRequest(); 50 | }; 51 | 52 | /** 53 | * A single log event stream. 54 | * @constructor 55 | * @param {Object} options 56 | */ 57 | function LogStream(options) { 58 | /** 59 | * @const 60 | * @type {string} */ 61 | var _traceCode = options.trace ? (Math.random() + Math.PI).toString(36).substring(2, 10) : null; 62 | /** @type {string} */ 63 | var _pageInfo = options.page_info; 64 | /** @type {string} */ 65 | var _token = options.token; 66 | /** @type {string} */ 67 | var _region = get_region_prefix(options.region); 68 | /** @type {boolean} */ 69 | var _print = options.print; 70 | /** @type {boolean} */ 71 | var _noFormat = options.no_format; 72 | /** @type {boolean} */ 73 | var _SSL = function() { 74 | if (typeof XDomainRequest === "undefined") { 75 | return options.ssl; 76 | } 77 | // If we're relying on XDomainRequest, we 78 | // must adhere to the page's encryption scheme. 79 | return window.location.protocol === "https:" ? true : false; 80 | }(); 81 | /** @type {string} */ 82 | var _endpoint; 83 | if (window.R7INSIGHTENDPOINT) { 84 | _endpoint = window.R7INSIGHTENDPOINT; 85 | } else if (_noFormat) { 86 | _endpoint = "webhook.logs.insight.rapid7.com/noformat"; 87 | } 88 | else { 89 | _endpoint = "js.logs.insight.rapid7.com/v1"; 90 | } 91 | _endpoint = (_SSL ? "https://" : "http://") + _region + _endpoint + "/logs/" + _token; 92 | 93 | /** 94 | * Flag to prevent further invocations on network err 95 | ** @type {boolean} */ 96 | var _shouldCall = true; 97 | /** @type {Array.} */ 98 | var _backlog = []; 99 | /** @type {boolean} */ 100 | var _active = false; 101 | /** @type {boolean} */ 102 | var _sentPageInfo = false; 103 | 104 | if (options.catchall) { 105 | var oldHandler = window.onerror; 106 | var newHandler = function(msg, url, line) { 107 | _rawLog({error: msg, line: line, location: url}).level('ERROR').send(); 108 | if (oldHandler) { 109 | return oldHandler(msg, url, line); 110 | } else { 111 | return false; 112 | } 113 | }; 114 | window.onerror = newHandler; 115 | } 116 | 117 | var _agentInfo = function() { 118 | var nav = window.navigator || {doNotTrack: undefined}; 119 | var screen = window.screen || {}; 120 | var location = window.location || {}; 121 | 122 | return { 123 | url: location.pathname, 124 | referrer: document.referrer, 125 | screen: { 126 | width: screen.width, 127 | height: screen.height 128 | }, 129 | window: { 130 | width: window.innerWidth, 131 | height: window.innerHeight 132 | }, 133 | browser: { 134 | name: nav.appName, 135 | user_agent: nav.userAgent, 136 | version: nav.appVersion, 137 | cookie_enabled: nav.cookieEnabled, 138 | do_not_track: nav.doNotTrack 139 | }, 140 | platform: nav.platform 141 | }; 142 | }; 143 | 144 | var _getEvent = function() { 145 | var raw = null; 146 | var args = Array.prototype.slice.call(arguments); 147 | if (args.length === 0) { 148 | throw new Error("No arguments!"); 149 | } else if (args.length === 1) { 150 | raw = args[0]; 151 | } else { 152 | // Handle a variadic overload, 153 | // e.g. _rawLog("some text ", x, " ...", 1); 154 | raw = args; 155 | } 156 | return raw; 157 | }; 158 | 159 | // Single arg stops the compiler arity warning 160 | var _rawLog = function(msg) { 161 | var event = _getEvent.apply(this, arguments); 162 | 163 | var data = {event: event}; 164 | 165 | // Add agent info if required 166 | if (_pageInfo !== 'never') { 167 | if (!_sentPageInfo || _pageInfo === 'per-entry') { 168 | _sentPageInfo = true; 169 | if (typeof event.screen === "undefined" && 170 | typeof event.browser === "undefined") 171 | _rawLog(_agentInfo()).level('PAGE').send(); 172 | } 173 | } 174 | 175 | if (_traceCode) { 176 | data.trace = _traceCode; 177 | } 178 | 179 | return {level: function(l) { 180 | // Don't log PAGE events to console 181 | // PAGE events are generated for the agentInfo function 182 | if (_print && typeof console !== "undefined" && l !== 'PAGE') { 183 | var serialized = null; 184 | if (typeof XDomainRequest !== "undefined") { 185 | // We're using IE8/9 186 | serialized = data.trace + ' ' + data.event; 187 | } 188 | try { 189 | console[l.toLowerCase()].call(console, (serialized || data)); 190 | } catch (ex) { 191 | // IE compat fix 192 | console.log((serialized || data)); 193 | } 194 | } 195 | data.level = l; 196 | 197 | return {send: function() { 198 | var cache = []; 199 | var serialized = JSON.stringify(data, function(key, value) { 200 | 201 | if (typeof value === "undefined") { 202 | return "undefined"; 203 | } else if (typeof value === "object" && value !== null) { 204 | if (_indexOf(cache, value) !== -1) { 205 | // We've seen this object before; 206 | // return a placeholder instead to prevent 207 | // cycles 208 | return ""; 209 | } 210 | cache.push(value); 211 | } 212 | return value; 213 | }); 214 | 215 | if (_active) { 216 | _backlog.push(serialized); 217 | } else { 218 | _apiCall(_token, serialized); 219 | } 220 | }}; 221 | }}; 222 | }; 223 | 224 | /** @expose */ 225 | this.log = _rawLog; 226 | 227 | var _apiCall = function(token, data) { 228 | _active = true; 229 | 230 | var request = _getAjaxObject(); 231 | 232 | if (_shouldCall) { 233 | if (request instanceof XMLHttpRequest) { 234 | // Currently we don't support fine-grained error 235 | // handling in older versions of IE 236 | request.onreadystatechange = function() { 237 | if (request.readyState === 4) { 238 | // Handle any errors 239 | if (request.status >= 400) { 240 | console.error("Couldn't submit events."); 241 | if (request.status === 410) { 242 | // This API version has been phased out 243 | console.warn("This version of r7insight_js is no longer supported!"); 244 | } 245 | } else { 246 | if (request.status === 301) { 247 | // Server issued a deprecation warning 248 | console.warn("This version of r7insight_js is deprecated! Consider upgrading."); 249 | } 250 | if (_backlog.length > 0) { 251 | // Submit the next event in the backlog 252 | _apiCall(token, _backlog.shift()); 253 | } else { 254 | _active = false; 255 | } 256 | } 257 | } 258 | 259 | }; 260 | } else { 261 | request.onload = function() { 262 | if (_backlog.length > 0) { 263 | // Submit the next event in the backlog 264 | _apiCall(token, _backlog.shift()); 265 | } else { 266 | _active = false; 267 | } 268 | }; 269 | } 270 | 271 | request.open("POST", _endpoint, true); 272 | if (request.constructor === XMLHttpRequest) { 273 | request.setRequestHeader('X-Requested-With', 'XMLHttpRequest'); 274 | request.setRequestHeader('Content-type', 'application/json'); 275 | } 276 | 277 | if (request.overrideMimeType) { 278 | request.overrideMimeType('text'); 279 | } 280 | 281 | request.send(data); 282 | } 283 | }; 284 | } 285 | 286 | /** 287 | * A single log object 288 | * @constructor 289 | * @param {Object} options 290 | */ 291 | function Logger(options) { 292 | var logger; 293 | 294 | // Default values 295 | var dict = { 296 | ssl: true, 297 | catchall: false, 298 | trace: true, 299 | page_info: 'never', 300 | print: false, 301 | endpoint: null, 302 | token: null 303 | }; 304 | 305 | if (typeof options === "object") 306 | for (var k in options) 307 | dict[k] = options[k]; 308 | else 309 | throw new Error("Invalid parameters for createLogStream()"); 310 | 311 | if (dict.token === null) { 312 | throw new Error("Token not present."); 313 | } 314 | else if (dict.region === null) { 315 | throw new Error("Region is not present"); 316 | } 317 | else { 318 | logger = new LogStream(dict); 319 | } 320 | 321 | var _log = function(msg) { 322 | if (logger) { 323 | return logger.log.apply(this, arguments); 324 | } else 325 | throw new Error("You must call R7Insight.init(...) first."); 326 | }; 327 | 328 | // The public interface 329 | return { 330 | log: function() { 331 | _log.apply(this, arguments).level('LOG').send(); 332 | }, 333 | warn: function() { 334 | _log.apply(this, arguments).level('WARN').send(); 335 | }, 336 | error: function() { 337 | _log.apply(this, arguments).level('ERROR').send(); 338 | }, 339 | info: function() { 340 | _log.apply(this, arguments).level('INFO').send(); 341 | } 342 | }; 343 | } 344 | 345 | // Array of Logger elements 346 | var loggers = {}; 347 | 348 | var _getLogger = function(name) { 349 | if (!loggers.hasOwnProperty(name)) 350 | throw new Error("Invalid name for logStream"); 351 | 352 | return loggers[name]; 353 | }; 354 | 355 | var _createLogStream = function(options) { 356 | if (typeof options.name !== "string") 357 | throw new Error("Name not present."); 358 | else if (loggers.hasOwnProperty(options.name)) 359 | return true; // logger already exists 360 | loggers[options.name] = new Logger(options); 361 | 362 | return true; 363 | }; 364 | 365 | var _deprecatedInit = function(options) { 366 | var dict = { 367 | name : "default" 368 | }; 369 | 370 | if (typeof options === "object") 371 | for (var k in options) 372 | dict[k] = options[k]; 373 | else if (typeof options === "string") 374 | dict.token = options; 375 | else 376 | throw new Error("Invalid parameters for init()"); 377 | 378 | return _createLogStream(dict); 379 | }; 380 | 381 | var _destroyLogStream = function(name) { 382 | if (typeof name === 'undefined'){ 383 | name = 'default'; 384 | } 385 | 386 | delete loggers[name]; 387 | }; 388 | 389 | // The public interface 390 | return { 391 | init: _deprecatedInit, 392 | createLogStream: _createLogStream, 393 | to: _getLogger, 394 | destroy: _destroyLogStream, 395 | log: function() { 396 | for (var k in loggers) 397 | loggers[k].log.apply(this, arguments); 398 | }, 399 | warn: function() { 400 | for (var k in loggers) 401 | loggers[k].warn.apply(this, arguments); 402 | }, 403 | error: function() { 404 | for (var k in loggers) 405 | loggers[k].error.apply(this, arguments); 406 | }, 407 | info: function() { 408 | for (var k in loggers) 409 | loggers[k].info.apply(this, arguments); 410 | } 411 | }; 412 | 413 | function get_region_prefix(region) { 414 | var allowed_regions = ['eu', 'us', 'us2', 'us3', 'ca', 'au', 'ap','custom']; 415 | if (region) { 416 | if(region === 'custom') return ''; 417 | if (allowed_regions.indexOf(region) > -1) { 418 | return region + '.'; 419 | } else { 420 | throw "Unrecognised region"; 421 | } 422 | } 423 | throw "No region defined"; 424 | } 425 | 426 | })); 427 | -------------------------------------------------------------------------------- /product/r7insight.min.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 Rapid7. 3 | Please view license at https://raw.github.com/rapid7/r7insight_js/master/LICENSE 4 | */ 5 | (function(b,e){"function"===typeof define&&define.amd?define(function(){return e(b)}):"object"===typeof exports?("object"===typeof global&&(b=global),module.exports=e(b)):b.R7Insight=e(b)})(this,function(b){function e(a){var c=a.trace?(Math.random()+Math.PI).toString(36).substring(2,10):null,q=a.page_info,e=a.token,g=v(a.region),f=a.print,p=a.no_format,r;r="undefined"===typeof XDomainRequest?a.ssl:"https:"===b.location.protocol?!0:!1;var k;k=b.R7INSIGHTENDPOINT?b.R7INSIGHTENDPOINT:p?"webhook.logs.insight.rapid7.com/noformat": 6 | "js.logs.insight.rapid7.com/v1";k=(r?"https://":"http://")+g+k+"/logs/"+e;var h=[],l=!1,t=!1;if(a.catchall){var u=b.onerror;b.onerror=function(a,b,d){m({error:a,line:d,location:b}).level("ERROR").send();return u?u(a,b,d):!1}}var w=function(){var a=b.navigator||{doNotTrack:void 0},c=b.screen||{};return{url:(b.location||{}).pathname,referrer:document.referrer,screen:{width:c.width,height:c.height},window:{width:b.innerWidth,height:b.innerHeight},browser:{name:a.appName,user_agent:a.userAgent,version:a.appVersion, 7 | cookie_enabled:a.cookieEnabled,do_not_track:a.doNotTrack},platform:a.platform}},x=function(){var a=Array.prototype.slice.call(arguments);if(0===a.length)throw Error("No arguments!");return 1===a.length?a[0]:a},m=function(a){var b=x.apply(this,arguments),d={event:b};"never"===q||t&&"per-entry"!==q||(t=!0,"undefined"===typeof b.screen&&"undefined"===typeof b.browser&&m(w()).level("PAGE").send());c&&(d.trace=c);return{level:function(a){if(f&&"undefined"!==typeof console&&"PAGE"!==a){var b=null;"undefined"!== 8 | typeof XDomainRequest&&(b=d.trace+" "+d.event);try{console[a.toLowerCase()].call(console,b||d)}catch(y){console.log(b||d)}}d.level=a;return{send:function(){var a=[],b=JSON.stringify(d,function(b,d){if("undefined"===typeof d)return"undefined";if("object"===typeof d&&null!==d){var c;a:{for(c=0;c";a.push(d)}return d});l?h.push(b):n(e,b)}}}}};this.log=m;var n=function(a,b){l=!0;var d;d="undefined"!==typeof XDomainRequest?new XDomainRequest:new XMLHttpRequest; 9 | d instanceof XMLHttpRequest?d.onreadystatechange=function(){4===d.readyState&&(400<=d.status?(console.error("Couldn't submit events."),410===d.status&&console.warn("This version of r7insight_js is no longer supported!")):(301===d.status&&console.warn("This version of r7insight_js is deprecated! Consider upgrading."),0} */ 98 | var _backlog = []; 99 | /** @type {boolean} */ 100 | var _active = false; 101 | /** @type {boolean} */ 102 | var _sentPageInfo = false; 103 | 104 | if (options.catchall) { 105 | var oldHandler = window.onerror; 106 | var newHandler = function(msg, url, line) { 107 | _rawLog({error: msg, line: line, location: url}).level('ERROR').send(); 108 | if (oldHandler) { 109 | return oldHandler(msg, url, line); 110 | } else { 111 | return false; 112 | } 113 | }; 114 | window.onerror = newHandler; 115 | } 116 | 117 | var _agentInfo = function() { 118 | var nav = window.navigator || {doNotTrack: undefined}; 119 | var screen = window.screen || {}; 120 | var location = window.location || {}; 121 | 122 | return { 123 | url: location.pathname, 124 | referrer: document.referrer, 125 | screen: { 126 | width: screen.width, 127 | height: screen.height 128 | }, 129 | window: { 130 | width: window.innerWidth, 131 | height: window.innerHeight 132 | }, 133 | browser: { 134 | name: nav.appName, 135 | user_agent: nav.userAgent, 136 | version: nav.appVersion, 137 | cookie_enabled: nav.cookieEnabled, 138 | do_not_track: nav.doNotTrack 139 | }, 140 | platform: nav.platform 141 | }; 142 | }; 143 | 144 | var _getEvent = function() { 145 | var raw = null; 146 | var args = Array.prototype.slice.call(arguments); 147 | if (args.length === 0) { 148 | throw new Error("No arguments!"); 149 | } else if (args.length === 1) { 150 | raw = args[0]; 151 | } else { 152 | // Handle a variadic overload, 153 | // e.g. _rawLog("some text ", x, " ...", 1); 154 | raw = args; 155 | } 156 | return raw; 157 | }; 158 | 159 | // Single arg stops the compiler arity warning 160 | var _rawLog = function(msg) { 161 | var event = _getEvent.apply(this, arguments); 162 | 163 | var data = {event: event}; 164 | 165 | // Add agent info if required 166 | if (_pageInfo !== 'never') { 167 | if (!_sentPageInfo || _pageInfo === 'per-entry') { 168 | _sentPageInfo = true; 169 | if (typeof event.screen === "undefined" && 170 | typeof event.browser === "undefined") 171 | _rawLog(_agentInfo()).level('PAGE').send(); 172 | } 173 | } 174 | 175 | if (_traceCode) { 176 | data.trace = _traceCode; 177 | } 178 | 179 | return {level: function(l) { 180 | // Don't log PAGE events to console 181 | // PAGE events are generated for the agentInfo function 182 | if (_print && typeof console !== "undefined" && l !== 'PAGE') { 183 | var serialized = null; 184 | if (typeof XDomainRequest !== "undefined") { 185 | // We're using IE8/9 186 | serialized = data.trace + ' ' + data.event; 187 | } 188 | try { 189 | console[l.toLowerCase()].call(console, (serialized || data)); 190 | } catch (ex) { 191 | // IE compat fix 192 | console.log((serialized || data)); 193 | } 194 | } 195 | data.level = l; 196 | 197 | return {send: function() { 198 | var cache = []; 199 | var serialized = JSON.stringify(data, function(key, value) { 200 | 201 | if (typeof value === "undefined") { 202 | return "undefined"; 203 | } else if (typeof value === "object" && value !== null) { 204 | if (_indexOf(cache, value) !== -1) { 205 | // We've seen this object before; 206 | // return a placeholder instead to prevent 207 | // cycles 208 | return ""; 209 | } 210 | cache.push(value); 211 | } 212 | return value; 213 | }); 214 | 215 | if (_active) { 216 | _backlog.push(serialized); 217 | } else { 218 | _apiCall(_token, serialized); 219 | } 220 | }}; 221 | }}; 222 | }; 223 | 224 | /** @expose */ 225 | this.log = _rawLog; 226 | 227 | var _apiCall = function(token, data) { 228 | _active = true; 229 | 230 | var request = _getAjaxObject(); 231 | 232 | if (_shouldCall) { 233 | if (request instanceof XMLHttpRequest) { 234 | // Currently we don't support fine-grained error 235 | // handling in older versions of IE 236 | request.onreadystatechange = function() { 237 | if (request.readyState === 4) { 238 | // Handle any errors 239 | if (request.status >= 400) { 240 | console.error("Couldn't submit events."); 241 | if (request.status === 410) { 242 | // This API version has been phased out 243 | console.warn("This version of r7insight_js is no longer supported!"); 244 | } 245 | } else { 246 | if (request.status === 301) { 247 | // Server issued a deprecation warning 248 | console.warn("This version of r7insight_js is deprecated! Consider upgrading."); 249 | } 250 | if (_backlog.length > 0) { 251 | // Submit the next event in the backlog 252 | _apiCall(token, _backlog.shift()); 253 | } else { 254 | _active = false; 255 | } 256 | } 257 | } 258 | 259 | }; 260 | } else { 261 | request.onload = function() { 262 | if (_backlog.length > 0) { 263 | // Submit the next event in the backlog 264 | _apiCall(token, _backlog.shift()); 265 | } else { 266 | _active = false; 267 | } 268 | }; 269 | } 270 | 271 | request.open("POST", _endpoint, true); 272 | if (request.constructor === XMLHttpRequest) { 273 | request.setRequestHeader('X-Requested-With', 'XMLHttpRequest'); 274 | request.setRequestHeader('Content-type', 'application/json'); 275 | } 276 | 277 | if (request.overrideMimeType) { 278 | request.overrideMimeType('text'); 279 | } 280 | 281 | request.send(data); 282 | } 283 | }; 284 | } 285 | 286 | /** 287 | * A single log object 288 | * @constructor 289 | * @param {Object} options 290 | */ 291 | function Logger(options) { 292 | var logger; 293 | 294 | // Default values 295 | var dict = { 296 | ssl: true, 297 | catchall: false, 298 | trace: true, 299 | page_info: 'never', 300 | print: false, 301 | endpoint: null, 302 | token: null 303 | }; 304 | 305 | if (typeof options === "object") 306 | for (var k in options) 307 | dict[k] = options[k]; 308 | else 309 | throw new Error("Invalid parameters for createLogStream()"); 310 | 311 | if (dict.token === null) { 312 | throw new Error("Token not present."); 313 | } 314 | else if (dict.region === null) { 315 | throw new Error("Region is not present"); 316 | } 317 | else { 318 | logger = new LogStream(dict); 319 | } 320 | 321 | var _log = function(msg) { 322 | if (logger) { 323 | return logger.log.apply(this, arguments); 324 | } else 325 | throw new Error("You must call R7Insight.init(...) first."); 326 | }; 327 | 328 | // The public interface 329 | return { 330 | log: function() { 331 | _log.apply(this, arguments).level('LOG').send(); 332 | }, 333 | warn: function() { 334 | _log.apply(this, arguments).level('WARN').send(); 335 | }, 336 | error: function() { 337 | _log.apply(this, arguments).level('ERROR').send(); 338 | }, 339 | info: function() { 340 | _log.apply(this, arguments).level('INFO').send(); 341 | } 342 | }; 343 | } 344 | 345 | // Array of Logger elements 346 | var loggers = {}; 347 | 348 | var _getLogger = function(name) { 349 | if (!loggers.hasOwnProperty(name)) 350 | throw new Error("Invalid name for logStream"); 351 | 352 | return loggers[name]; 353 | }; 354 | 355 | var _createLogStream = function(options) { 356 | if (typeof options.name !== "string") 357 | throw new Error("Name not present."); 358 | else if (loggers.hasOwnProperty(options.name)) 359 | return true; // logger already exists 360 | loggers[options.name] = new Logger(options); 361 | 362 | return true; 363 | }; 364 | 365 | var _deprecatedInit = function(options) { 366 | var dict = { 367 | name : "default" 368 | }; 369 | 370 | if (typeof options === "object") 371 | for (var k in options) 372 | dict[k] = options[k]; 373 | else if (typeof options === "string") 374 | dict.token = options; 375 | else 376 | throw new Error("Invalid parameters for init()"); 377 | 378 | return _createLogStream(dict); 379 | }; 380 | 381 | var _destroyLogStream = function(name) { 382 | if (typeof name === 'undefined'){ 383 | name = 'default'; 384 | } 385 | 386 | delete loggers[name]; 387 | }; 388 | 389 | // The public interface 390 | return { 391 | init: _deprecatedInit, 392 | createLogStream: _createLogStream, 393 | to: _getLogger, 394 | destroy: _destroyLogStream, 395 | log: function() { 396 | for (var k in loggers) 397 | loggers[k].log.apply(this, arguments); 398 | }, 399 | warn: function() { 400 | for (var k in loggers) 401 | loggers[k].warn.apply(this, arguments); 402 | }, 403 | error: function() { 404 | for (var k in loggers) 405 | loggers[k].error.apply(this, arguments); 406 | }, 407 | info: function() { 408 | for (var k in loggers) 409 | loggers[k].info.apply(this, arguments); 410 | } 411 | }; 412 | 413 | function get_region_prefix(region) { 414 | var allowed_regions = ['eu', 'us', 'us2', 'us3', 'ca', 'au', 'ap','custom']; 415 | if (region) { 416 | if(region === 'custom') return ''; 417 | if (allowed_regions.indexOf(region) > -1) { 418 | return region + '.'; 419 | } else { 420 | throw "Unrecognised region"; 421 | } 422 | } 423 | throw "No region defined"; 424 | } 425 | 426 | })); 427 | -------------------------------------------------------------------------------- /test/r7insightSpec.js: -------------------------------------------------------------------------------- 1 | /*jshint loopfunc:true*/ 2 | /*globals describe, it, expect, R7Insight, sinon, afterEach, beforeEach, jasmine, window, console, spyOn, XDomainRequest, XMLHttpRequest*/ 3 | var GLOBAL = this; 4 | var TOKEN = 'test_token'; 5 | 6 | function destroy() { 7 | R7Insight.destroy('default'); 8 | R7Insight.destroy(TOKEN); 9 | } 10 | 11 | function mockXMLHttpRequests() { 12 | // Prevent requests 13 | this.xhr = sinon.useFakeXMLHttpRequest(); 14 | 15 | // List requests 16 | var requestList = this.requestList = []; 17 | this.xhr.onCreate = function (request) { 18 | requestList.push(request); 19 | }; 20 | } 21 | 22 | function addGetJson() { 23 | this.getXhrJson = function (xhrRequestId) { 24 | return JSON.parse(this.requestList[xhrRequestId].requestBody); 25 | }; 26 | } 27 | 28 | function restoreXMLHttpRequests() { 29 | if (this.xhr) { 30 | this.xhr.restore(); 31 | } 32 | } 33 | 34 | describe('construction', function () { 35 | it('with string', function () { 36 | expect(R7Insight.init({ 37 | token: TOKEN, 38 | region: 'eu' 39 | })).toBe(true); 40 | }); 41 | 42 | it('with object', function () { 43 | expect(R7Insight.init({ 44 | token: TOKEN, 45 | region: 'eu' 46 | })).toBe(true); 47 | }); 48 | 49 | // TODO: Test Raul's multi logger 50 | 51 | describe('fails', function () { 52 | it('without token', function () { 53 | expect(R7Insight.init).toThrow("Invalid parameters for init()"); 54 | }); 55 | 56 | it('without token (object)', function () { 57 | expect(function () { 58 | R7Insight.init({}); 59 | }).toThrow("Token not present."); 60 | }); 61 | }); 62 | 63 | afterEach(destroy); 64 | }); 65 | 66 | describe('sending messages', function () { 67 | beforeEach(mockXMLHttpRequests); 68 | beforeEach(addGetJson); 69 | beforeEach(function () { 70 | R7Insight.init({ 71 | token: TOKEN, 72 | trace: true, 73 | region: 'eu' 74 | }); 75 | }); 76 | 77 | it('logs null values', function () { 78 | R7Insight.log(null); 79 | 80 | expect(this.getXhrJson(0).event).toBe(null); 81 | }); 82 | 83 | it('logs undefined values', function () { 84 | R7Insight.log(undefined); 85 | 86 | expect(this.getXhrJson(0).event).toBe('undefined'); 87 | }); 88 | 89 | it('logs object with nullish properties', function () { 90 | R7Insight.log({ 91 | undef: undefined, 92 | nullVal: null 93 | }); 94 | 95 | var event = this.getXhrJson(0).event; 96 | expect(event.undef).toBe('undefined'); 97 | expect(event.nullVal).toBe(null); 98 | }); 99 | 100 | it('logs array with nullish values', function () { 101 | R7Insight.log([ 102 | undefined, 103 | null 104 | ]); 105 | 106 | var event = this.getXhrJson(0).event; 107 | expect(event[0]).toBe('undefined'); 108 | expect(event[1]).toBe(null); 109 | }); 110 | 111 | it('sends trace code', function () { 112 | R7Insight.log('test'); 113 | 114 | var trace = this.getXhrJson(0).trace; 115 | expect(trace).toEqual(jasmine.any(String)); 116 | expect(trace.length).toBe(8); 117 | }); 118 | 119 | it('accepts multiple arguments', function () { 120 | var args = ['test', 1, undefined]; 121 | 122 | R7Insight.log.apply(R7Insight, args); 123 | 124 | var event = this.getXhrJson(0).event; 125 | expect(event.length).toBe(3); 126 | expect(event[0]).toBe(args[0]); 127 | expect(event[1]).toBe(args[1]); 128 | expect(event[2]).toBe('undefined'); 129 | }); 130 | 131 | afterEach(destroy); 132 | }); 133 | 134 | describe('sends log level', function () { 135 | beforeEach(mockXMLHttpRequests); 136 | beforeEach(addGetJson); 137 | beforeEach(function () { 138 | R7Insight.init({ 139 | token: TOKEN, 140 | region: 'eu' 141 | }); 142 | }); 143 | 144 | var methods = [ 145 | 'log', 146 | 'info', 147 | 'warn', 148 | 'error' 149 | ]; 150 | 151 | for (var i = 0; i < methods.length; i++) { 152 | var method = methods[i]; 153 | var level = method.toUpperCase(); 154 | 155 | it(level, function (method, level) { 156 | return function () { 157 | R7Insight[method]('test'); 158 | expect(this.getXhrJson(0).level).toBe(level); 159 | }; 160 | }(method, level)); 161 | } 162 | 163 | it('excludes cyclic values', function () { 164 | var a = {}; 165 | a.b = a; 166 | 167 | R7Insight.log(a); 168 | 169 | expect(this.getXhrJson(0).event.b).toBe(''); 170 | }); 171 | 172 | afterEach(restoreXMLHttpRequests); 173 | afterEach(destroy); 174 | }); 175 | 176 | describe('sending user agent data', function () { 177 | beforeEach(mockXMLHttpRequests); 178 | beforeEach(addGetJson); 179 | 180 | function checkAgentInfo(agent) { 181 | expect(agent).toBeDefined(); 182 | 183 | // Perhaps these could be filled in since we're running in a 184 | // real browser now? 185 | expect(agent.url).toBeDefined(); 186 | expect(agent.referrer).toBeDefined(); 187 | expect(agent.screen).toBeDefined(); 188 | expect(agent.window).toBeDefined(); 189 | expect(agent.browser).toBeDefined(); 190 | expect(agent.platform).toBeDefined(); 191 | } 192 | 193 | it('page_info: never - never sends log data', function () { 194 | R7Insight.init({ 195 | token: TOKEN, 196 | page_info: 'never', 197 | region: 'eu' 198 | }); 199 | 200 | R7Insight.log('hi'); 201 | 202 | var data = this.getXhrJson(0); 203 | 204 | expect(data.event).toBe('hi'); 205 | expect(this.getXhrJson(0).agent).toBeUndefined(); 206 | }); 207 | 208 | it('page_info: per-entry - sends log data for each log', function () { 209 | R7Insight.init({ 210 | token: TOKEN, 211 | page_info: 'per-entry', 212 | region: 'eu' 213 | }); 214 | 215 | R7Insight.log('hi'); 216 | 217 | // Check data is sent the first time 218 | checkAgentInfo(this.getXhrJson(0).event); 219 | 220 | // Respond to first request so that the 2nd request will be made 221 | this.requestList[0].respond(); 222 | 223 | expect(this.getXhrJson(1).event).toBe('hi'); 224 | 225 | R7Insight.log('hi again'); 226 | this.requestList[1].respond(); 227 | 228 | // Check that page info is sent subsequent times 229 | checkAgentInfo(this.getXhrJson(2).event); 230 | 231 | this.requestList[2].respond(); 232 | 233 | expect(this.getXhrJson(3).event).toBe('hi again'); 234 | }); 235 | 236 | it('page_info: per-page - always sends data for each log', function () { 237 | R7Insight.init({ 238 | token: TOKEN, 239 | page_info: 'per-page', 240 | region: 'eu' 241 | }); 242 | 243 | R7Insight.log('hi'); 244 | 245 | // Check data is sent the first time 246 | checkAgentInfo(this.getXhrJson(0).event); 247 | 248 | // Respond to first request so that the 2nd request will be made 249 | this.requestList[0].respond(); 250 | 251 | expect(this.getXhrJson(1).event).toBe('hi'); 252 | 253 | R7Insight.log('hi again'); 254 | this.requestList[1].respond(); 255 | 256 | // Check that no data is sent subsequent times 257 | expect(this.getXhrJson(2).event).toBe('hi again'); 258 | }); 259 | 260 | afterEach(destroy); 261 | }); 262 | 263 | describe('catch all option', function () { 264 | beforeEach(mockXMLHttpRequests); 265 | beforeEach(function () { 266 | this.oldErrorHandler = sinon.stub(GLOBAL, 'onerror') 267 | .returns(true); 268 | }); 269 | 270 | it('assigns onerror handler', function () { 271 | R7Insight.init({ 272 | token: TOKEN, 273 | catchall: true, 274 | region: 'eu' 275 | }); 276 | // Don't test if onerror is set because #1 we've got a stub 277 | // and 2nd, karma has its handler. 278 | expect(GLOBAL.onerror).not.toBe(this.oldErrorHandler); 279 | }); 280 | 281 | it('sends errors', function () { 282 | // Don't care what happens to this, just ignore the error 283 | R7Insight.init({ 284 | token: TOKEN, 285 | catchall: true, 286 | region: 'eu' 287 | }); 288 | 289 | // Check if onerror handler is not the stub from above 290 | expect(GLOBAL.onerror).not.toBe(this.oldErrorHandler); 291 | 292 | expect(this.requestList.length).toBe(0); 293 | 294 | // Pretend to trigger an error like the browser might 295 | GLOBAL.onerror('Script error', 'http://example.com', 0); 296 | 297 | expect(this.requestList.length).toBe(1); 298 | }); 299 | 300 | it('bubbles onerror calls', function () { 301 | R7Insight.init({ 302 | token: TOKEN, 303 | catchall: true, 304 | region: 'eu' 305 | }); 306 | 307 | // Pretend to trigger an error like the browser might 308 | GLOBAL.onerror('Script error', 'http://example.com', 0); 309 | 310 | expect(this.oldErrorHandler.calledOnce).toBe(true); 311 | }); 312 | 313 | afterEach(function () { 314 | if (this.oldErrorHandler.restore) { 315 | this.oldErrorHandler.restore(); 316 | } 317 | }); 318 | afterEach(restoreXMLHttpRequests); 319 | afterEach(destroy); 320 | }); 321 | 322 | describe('destroys log streams', function () { 323 | it('default', function () { 324 | R7Insight.init({ 325 | token: TOKEN, 326 | region: 'eu' 327 | }); 328 | R7Insight.destroy(); 329 | 330 | expect(function () { 331 | R7Insight.init({ 332 | token: TOKEN, 333 | region: 'eu' 334 | }); 335 | }).not.toThrow(); 336 | }); 337 | 338 | it('custom name', function () { 339 | R7Insight.init({ 340 | token: TOKEN, 341 | name: 'test', 342 | region: 'eu' 343 | }); 344 | R7Insight.destroy('test'); 345 | 346 | expect(function () { 347 | R7Insight.init({ 348 | token: TOKEN, 349 | name: 'test', 350 | region: 'eu' 351 | }); 352 | }).not.toThrow(); 353 | R7Insight.destroy('test'); 354 | }); 355 | 356 | afterEach(destroy); 357 | }); 358 | 359 | describe('tests for SSL', function () { 360 | beforeEach(mockXMLHttpRequests); 361 | beforeEach(addGetJson); 362 | 363 | it('SSL option set to true leads to "https"', function () { 364 | R7Insight.init({ 365 | token: TOKEN, 366 | name: 'test', 367 | ssl: true, 368 | region: 'eu' 369 | }); 370 | R7Insight.log("Test"); 371 | var url = this.requestList[0].url; 372 | expect(url.indexOf('https')).toBe(0); 373 | R7Insight.destroy('test'); 374 | }); 375 | 376 | it('SSL option set to false leads to "http"', function () { 377 | R7Insight.init({ 378 | token: TOKEN, 379 | name: 'test', 380 | ssl: false, 381 | region: 'eu' 382 | }); 383 | R7Insight.log("Test"); 384 | var url = this.requestList[0].url; 385 | expect(url.indexOf("https")).toBe(-1); 386 | expect(url.indexOf("http")).toBe(0); 387 | R7Insight.destroy('test'); 388 | }); 389 | 390 | it('SSL option not set leads to "https"', function () { 391 | R7Insight.init({ 392 | token: TOKEN, 393 | name: 'test', 394 | region: 'eu' 395 | }); 396 | R7Insight.log("Test"); 397 | var url = this.requestList[0].url; 398 | expect(url.indexOf("https")).toBe(0); 399 | R7Insight.destroy('test'); 400 | }); 401 | }); 402 | 403 | describe('tests for region', function () { 404 | beforeEach(mockXMLHttpRequests); 405 | beforeEach(addGetJson); 406 | 407 | it('Setting the region to each of the allowed values adds that region to the url', function () { 408 | var regions = ["eu", "us", "ca", "ap", "au"]; 409 | regions.forEach(function (region) { 410 | mockXMLHttpRequests(); 411 | R7Insight.init({ 412 | token: TOKEN, 413 | name: 'test' + region, 414 | region: region 415 | }); 416 | R7Insight.log("Test"); 417 | var url = this.requestList[0].url; 418 | expect(url.indexOf('/' + region + '.')).toBe(7, "Expected " + region + 419 | " in the url, got: " + url.substring(7,9)); 420 | R7Insight.destroy('test' + region); 421 | }); 422 | }); 423 | 424 | it('Not setting the region throws error "No region defined"', function () { 425 | try { 426 | R7Insight.init({ 427 | token: TOKEN, 428 | name: 'test' 429 | }); 430 | jasmine.done.fail(); 431 | } catch(e) { 432 | expect(e).toBe("No region defined"); 433 | } 434 | R7Insight.destroy('test'); 435 | }); 436 | 437 | it('Setting the region to an unrecognised value throws error "Unrecognised region"', function () { 438 | try { 439 | R7Insight.init({ 440 | token: TOKEN, 441 | name: 'test', 442 | region: 'random_region' 443 | }); 444 | jasmine.done.fail(); 445 | } catch(e) { 446 | expect(e).toBe("Unrecognised region"); 447 | } 448 | R7Insight.destroy('test'); 449 | }); 450 | }); 451 | 452 | describe('no_format option', function () { 453 | beforeEach(mockXMLHttpRequests); 454 | beforeEach(addGetJson); 455 | 456 | it('Should send data to noformat if no format is enabled', function () { 457 | R7Insight.init({ 458 | token: TOKEN, 459 | no_format: true, 460 | region: 'eu' 461 | }); 462 | R7Insight.log('some message'); 463 | var url = this.requestList[0].url; 464 | expect(url).toContain("noformat"); 465 | }); 466 | 467 | it('Should send data to js if no format is disabled', function () { 468 | R7Insight.init({ 469 | token: TOKEN, 470 | no_format: false, 471 | region: 'eu' 472 | }); 473 | R7Insight.log('some message'); 474 | var url = this.requestList[0].url; 475 | expect(url).toContain("v1"); 476 | }); 477 | 478 | afterEach(restoreXMLHttpRequests); 479 | afterEach(destroy); 480 | }); 481 | 482 | 483 | describe('custom endpoint', function () { 484 | beforeEach(mockXMLHttpRequests); 485 | beforeEach(addGetJson); 486 | beforeEach(function () { 487 | window.R7INSIGHTENDPOINT = 'somewhere1.com/custom-logging'; 488 | R7Insight.init({ 489 | token: TOKEN, 490 | region: 'eu' 491 | }); 492 | }); 493 | 494 | it('can be set', function () { 495 | R7Insight.log('some message'); 496 | var lastReq = this.requestList[0]; 497 | expect(lastReq.url).toBe('https://eu.somewhere1.com/custom-logging/logs/test_token'); 498 | }); 499 | 500 | afterEach(restoreXMLHttpRequests); 501 | afterEach(destroy); 502 | }); 503 | 504 | describe('full custom endpoint', function () { 505 | beforeEach(mockXMLHttpRequests); 506 | beforeEach(addGetJson); 507 | beforeEach(function () { 508 | window.R7INSIGHTENDPOINT = 'somewhere1.com/custom-logging'; 509 | R7Insight.init({ 510 | token: TOKEN, 511 | region: 'custom' 512 | }); 513 | }); 514 | 515 | it('can be set', function () { 516 | R7Insight.log('some message'); 517 | var lastReq = this.requestList[0]; 518 | expect(lastReq.url).toBe('https://somewhere1.com/custom-logging/logs/test_token'); 519 | }); 520 | 521 | afterEach(restoreXMLHttpRequests); 522 | afterEach(destroy); 523 | }); 524 | 525 | 526 | describe('Multiple initialisations', function () { 527 | beforeEach(mockXMLHttpRequests); 528 | beforeEach(addGetJson); 529 | beforeEach(function () { 530 | R7Insight.init({ 531 | token: TOKEN, 532 | region: 'eu' 533 | }); 534 | }); 535 | 536 | it('should allow calling init on already set up logger', function () { 537 | R7Insight.init({ 538 | token: TOKEN, 539 | region: 'eu' 540 | }); 541 | R7Insight.log('some message'); 542 | var lastReq = this.requestList[0]; 543 | expect(this.getXhrJson(0).event).toBe('some message'); 544 | }); 545 | 546 | afterEach(restoreXMLHttpRequests); 547 | afterEach(destroy); 548 | }); 549 | 550 | describe('Multiple initialisations', function () { 551 | beforeEach(mockXMLHttpRequests); 552 | beforeEach(addGetJson); 553 | beforeEach(function () { 554 | R7Insight.init({ 555 | name: 'test', 556 | token: TOKEN, 557 | region: 'eu' 558 | }); 559 | }); 560 | 561 | it('should allow calling init on already set up logger with name', function () { 562 | R7Insight.init({ 563 | name: 'test', 564 | token: TOKEN, 565 | region: 'eu' 566 | }); 567 | R7Insight.log('some message'); 568 | expect(this.getXhrJson(0).event).toBe('some message'); 569 | }); 570 | 571 | afterEach(restoreXMLHttpRequests); 572 | afterEach(destroy); 573 | }); 574 | 575 | describe('print option', function () { 576 | beforeEach(mockXMLHttpRequests); 577 | beforeEach(function () { 578 | spyOn(console, 'log'); 579 | spyOn(console, 'info'); 580 | spyOn(console, 'warn'); 581 | spyOn(console, 'error'); 582 | R7Insight.init({ 583 | token: TOKEN, 584 | print: true, 585 | region: 'eu' 586 | }); 587 | }); 588 | 589 | it('should log to console also', function () { 590 | R7Insight.log('some message'); 591 | expect(console.log.mostRecentCall.args[0].trace).toMatch(/[0-9a-z]{8}/); 592 | expect(console.log.mostRecentCall.args[0].event).toEqual('some message'); 593 | expect(console.log.mostRecentCall.args[0].level).toEqual('LOG'); 594 | }); 595 | 596 | it('below IE9 should stringify console messages', function () { 597 | /*jshint -W020 */ 598 | XDomainRequest = XMLHttpRequest; //trick into thinking we are in IE8/9 browser 599 | /*jshint +W020 */ 600 | R7Insight.log('some message'); 601 | expect(console.log.mostRecentCall.args[0]).toMatch(/[0-9a-z]{8} some message/); 602 | }); 603 | 604 | afterEach(restoreXMLHttpRequests); 605 | afterEach(destroy); 606 | }); 607 | --------------------------------------------------------------------------------