├── .gitattributes ├── .github └── workflows │ ├── prerelease.yml │ └── tests.yml ├── .gitignore ├── .jshintrc ├── .prettierrc ├── .vscode ├── launch.json └── tasks.json ├── CHANGELOG.md ├── CONTRIBUTING.md ├── Gruntfile.js ├── LICENSE ├── README.md ├── V1Documentation.md ├── bower.json ├── examples ├── BasicCrashReporting.html ├── BasicRUM.html ├── CustomDataCrashReporting.html ├── CustomErrorGroupingKey.html ├── IgnoreLocalhostCrashReporting.html ├── ManualSendCrashReporting.html ├── README.md ├── SettingAUserCrashReporting.html └── TagsCrashReporting.html ├── jasmine.json ├── package-lock.json ├── package.json ├── raygun4js.nuspec ├── rum-spa-test.html ├── src ├── .jshintrc ├── helpers │ └── browser-mock.js ├── polyfills.js ├── raygun.breadcrumbs.js ├── raygun.js ├── raygun.loader.js ├── raygun.network-tracking.js ├── raygun.rum │ ├── core-web-vitals.js │ ├── core-web-vitals.spec.js │ ├── index.js │ └── rum.spec.js ├── raygun.tracekit.jquery.js ├── raygun.utilities │ ├── errorUtilities.js │ ├── errorUtilities.spec.js │ ├── index.js │ └── utilities.spec.js ├── raygun.viewport.js ├── raygun.viewport.spec.js ├── snippet │ ├── minified.fetchhandler.js │ ├── minified.js │ ├── minified.nohandler.js │ ├── unminified.fetchhandler.js │ ├── unminified.js │ └── unminified.nohandler.js ├── umd.intro.js ├── umd.outro.js └── useragent.js ├── tests ├── fixtures │ ├── breadcrumbs │ │ ├── automatic.console.html │ │ ├── automatic.element.html │ │ ├── automatic.navigation.html │ │ ├── automatic.xhr.html │ │ ├── basic.html │ │ ├── basicWithObject.html │ │ └── setBreadcrumbLevel.html │ ├── common │ │ └── instrumentXHRs.js │ ├── sessions │ │ ├── crWithUser.html │ │ ├── crWithoutUser.html │ │ └── rumSession.html │ ├── v1 │ │ ├── basic.html │ │ ├── manualSend.html │ │ ├── manualSendCustomData.html │ │ ├── manualSendNoApiKey.html │ │ ├── manualSendTag.html │ │ ├── manualSendUser.html │ │ ├── manualSendVersion.html │ │ ├── trackEvent.html │ │ ├── unhandledError.html │ │ ├── unhandledErrorCustomData.html │ │ ├── unhandledErrorNoApiKey.html │ │ ├── unhandledErrorNoAttach.html │ │ ├── unhandledErrorTag.html │ │ ├── unhandledErrorUser.html │ │ └── unhandledErrorVersion.html │ └── v2 │ │ ├── UMDInfiniteLoop.html │ │ ├── basic.html │ │ ├── customTiming.html │ │ ├── endSession.html │ │ ├── legacyCustomTiming.html │ │ ├── manualSend.html │ │ ├── manualSend3rdParty.html │ │ ├── manualSendCustomData.html │ │ ├── manualSendErrorAsString.html │ │ ├── manualSendNoApiKey.html │ │ ├── manualSendTag.html │ │ ├── manualSendUser.html │ │ ├── manualSendVersion.html │ │ ├── onBeforeSendRUM.html │ │ ├── requestId.html │ │ ├── rg4jsAfterLibraryLoaded.html │ │ ├── rg4jsBeforeLibraryLoaded.html │ │ ├── rumFetchPolyfillStatusCodes.html │ │ ├── rumFetchStatusCodes.html │ │ ├── rumReferencedFetchWithFetchSnippet.html │ │ ├── rumXhrStatusCodes.html │ │ ├── syntaxErrorSnippet.html │ │ ├── trackEvent.html │ │ ├── unhandledError.html │ │ ├── unhandledErrorCustomData.html │ │ ├── unhandledErrorNoApiKey.html │ │ ├── unhandledErrorNoAttach.html │ │ ├── unhandledErrorTag.html │ │ ├── unhandledErrorTagWithString.html │ │ ├── unhandledErrorUser.html │ │ ├── unhandledErrorVersion.html │ │ ├── unhandledErrorWithUmdBuild.html │ │ ├── unhandledPromiseRejection.html │ │ ├── unhandledPromiseRejectionWithNoReason.html │ │ ├── withClientIpSet.html │ │ └── withoutClientIpSet.html └── specs │ ├── breadcrumbs │ ├── automatic.console.js │ ├── automatic.element.js │ ├── automatic.navigation.js │ ├── automatic.xhr.js │ ├── basic.js │ └── setBreadcrumbLevel.js │ ├── common.js │ ├── sessions │ ├── crashReporting.js │ └── realUserMonitoring.js │ ├── v1 │ ├── basic.js │ ├── eventsXhr.js │ ├── payloadManualSendTests.js │ └── payloadUnhandledErrorTests.js │ └── v2 │ ├── UMDInfiniteLoopTest.js │ ├── basic.js │ ├── clientIp.js │ ├── customTiming.js │ ├── endSession.js │ ├── eventsXhr.js │ ├── onBeforeSendRUM.js │ ├── payloadManualSendTests.js │ ├── payloadSyntaxError.js │ ├── payloadUnhandledErrorTests.js │ ├── requestId.js │ ├── rg4jsLoaderTests.js │ ├── rumXhrStatusTracking.js │ └── unhandledPromiseRejectionTests.js ├── tracekit ├── .travis.yml ├── README.md ├── grunt.js ├── package.json ├── tests │ ├── mocha.css │ ├── mocha.js │ └── recursion.html └── tracekit.js ├── types └── index.d.ts └── wdio.conf.js /.gitattributes: -------------------------------------------------------------------------------- 1 | dist/* linguist-generated=true 2 | -------------------------------------------------------------------------------- /.github/workflows/prerelease.yml: -------------------------------------------------------------------------------- 1 | name: Publish Raygun4JS to the pre-release environment 2 | 3 | on: 4 | workflow_dispatch: 5 | 6 | jobs: 7 | build: 8 | runs-on: ubuntu-latest 9 | 10 | strategy: 11 | matrix: 12 | node-version: [ 20.x ] 13 | 14 | steps: 15 | - uses: actions/checkout@v4 16 | 17 | - name: Use Node.js ${{ matrix.node-version }} 18 | uses: actions/setup-node@v4 19 | with: 20 | node-version: ${{ matrix.node-version }} 21 | 22 | - name: Install Grunt 23 | run: npm install -g grunt 24 | 25 | - name: Install node modules 26 | run: npm install 27 | 28 | - name: Build 29 | id: build_step 30 | run: grunt build 31 | 32 | - name: Configure AWS Credentials 33 | uses: aws-actions/configure-aws-credentials@v4 34 | with: 35 | aws-access-key-id: ${{ secrets.AWS_ACCESS_ID }} 36 | aws-secret-access-key: ${{ secrets.AWS_ACCESS_SECRET }} 37 | aws-region: ${{ secrets.AWS_REGION }} 38 | 39 | - name: Publish to S3 Pre-release 40 | id: publish_s3 41 | run: aws s3 sync ./dist ${{ secrets.AWS_PUBLISH_TARGET }} 42 | 43 | - name: Create CloudFront Cache Invalidation 44 | id: cache_invalidation 45 | run: | 46 | aws cloudfront create-invalidation \ 47 | --distribution-id ${{ secrets.AWS_CLOUDFRONT_DISTRIBUTION_ID }} \ 48 | --paths "${{ secrets.AWS_CLOUDFRONT_INVALIDATION_PATH }}" 49 | 50 | - name: Notify Slack 51 | id: slack 52 | uses: slackapi/slack-github-action@v1.26.0 53 | with: 54 | payload: | 55 | { 56 | "blocks": [ 57 | { 58 | "type": "header", 59 | "text": { 60 | "type": "plain_text", 61 | "text": "A new pre-release version of Raygun4JS has been published" 62 | } 63 | }, 64 | { 65 | "type": "section", 66 | "text": { 67 | "type": "mrkdwn", 68 | "text": "" 69 | } 70 | }, 71 | { 72 | "type": "section", 73 | "fields": [ 74 | { 75 | "type": "mrkdwn", 76 | "text": "*Branch:*\n`${{ github.ref_name }}`" 77 | }, 78 | { 79 | "type": "mrkdwn", 80 | "text": "*Node version:*\n`${{ matrix.node-version }}`" 81 | }, 82 | { 83 | "type": "mrkdwn", 84 | "text": "*Build status:*\n_${{ steps.build_step.outcome }}_" 85 | }, 86 | { 87 | "type": "mrkdwn", 88 | "text": "*S3 publish status:*\n_${{ steps.publish_s3.outcome }}_" 89 | }, 90 | { 91 | "type": "mrkdwn", 92 | "text": "*CloudFront cache invalidation status:*\n_${{ steps.cache_invalidation.outcome }}_" 93 | } 94 | ] 95 | } 96 | ] 97 | } 98 | env: 99 | SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} 100 | SLACK_WEBHOOK_TYPE: INCOMING_WEBHOOK 101 | -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | name: Continuous Integration 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | test: 11 | 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - uses: actions/checkout@v4 16 | 17 | - name: Use Node.js 20.x 18 | uses: actions/setup-node@v4 19 | with: 20 | node-version: 20.x 21 | 22 | - name: Setup Chrome 23 | id: setup-chrome 24 | uses: browser-actions/setup-chrome@latest 25 | with: 26 | chrome-version: latest 27 | install-chromedriver: true 28 | 29 | - name: Set Chrome and ChromeDriver paths 30 | run: | 31 | echo "CHROME_BIN=${{ steps.setup-chrome.outputs.chrome-path }}" >> $GITHUB_ENV 32 | echo "CHROMEDRIVER_PATH=${{ steps.setup-chrome.outputs.chromedriver-path }}" >> $GITHUB_ENV 33 | 34 | - run: npm ci 35 | - run: npx grunt 36 | - run: npm test 37 | env: 38 | CHROME_BIN: ${{ env.CHROME_BIN }} 39 | CHROMEDRIVER_PATH: ${{ env.CHROMEDRIVER_PATH }} 40 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules/ 2 | /reports/ 3 | /bower_components/ 4 | dist/snippet 5 | .idea/ 6 | index.html 7 | dist/* 8 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "curly": true, 3 | "eqeqeq": true, 4 | "immed": true, 5 | "latedef": false, 6 | "newcap": true, 7 | "noarg": true, 8 | "sub": true, 9 | "undef": true, 10 | "unused": true, 11 | "boss": true, 12 | "eqnull": true, 13 | "node": true, 14 | "esversion": 6 15 | } 16 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 100, 3 | "singleQuote": true, 4 | "trailingComma": "es5", 5 | "bracketSpacing": true, 6 | "requirePragma": true 7 | } 8 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible Node.js debug attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "node", 9 | "request": "launch", 10 | "name": "wdio", 11 | "program": "${workspaceRoot}/node_modules/grunt/bin/grunt", 12 | "cwd": "${workspaceRoot}", 13 | "args": ["test"], 14 | "port": 5859 15 | }, 16 | { 17 | "type": "node", 18 | "request": "attach", 19 | "name": "Attach to Process", 20 | "port": 5859 21 | } 22 | ] 23 | } -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=733558 3 | // for the documentation about the tasks.json format 4 | "version": "2.0.0", 5 | "command": "grunt", 6 | "tasks": [ 7 | { 8 | "label": "default", 9 | "type": "grunt", 10 | "task": "default", 11 | "problemMatcher": [ 12 | "$lessCompile", 13 | "$tsc", 14 | "$jshint" 15 | ], 16 | "group": { 17 | "_id": "build", 18 | "isDefault": false 19 | } 20 | } 21 | ] 22 | } -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | ## Important notes 4 | Please don't edit files in the `dist` subdirectory as they are generated via Grunt. You'll find source code in the `src` subdirectory! 5 | 6 | ### Code style 7 | Regarding code style like indentation and whitespace, **follow the conventions you see used in the source already.** 8 | 9 | ## Modifying the code 10 | First, ensure that you have the latest [Node.js](http://nodejs.org/) and [npm](http://npmjs.org/) installed. 11 | 12 | Test that Grunt's CLI is installed by running `grunt --version`. If the command isn't found, run `npm install -g grunt-cli`. For more information about installing Grunt, see the [getting started guide](http://gruntjs.com/getting-started). 13 | 14 | 1. Fork and clone the repo. 15 | 1. Run `npm install` to install all dependencies (including Grunt). 16 | 1. Run `grunt` to build, compile and test this project. 17 | 18 | Assuming that you don't see any red, you're ready to go. Just be sure to run `grunt` after making any changes, to ensure that nothing is broken. 19 | 20 | ## Tests 21 | 22 | The unit and E2E tests can be run with `grunt test`. If you add or modify a feature, please add a new test to `tests/specs`, and a new fixture if required (or reuse an existing one if there's one that fufills your needs). 23 | 24 | ## Submitting pull requests 25 | 26 | 1. Create a new branch, please don't work in your `master` branch directly. 27 | 1. Update the documentation to reflect any changes. 28 | 1. Push to your fork and submit a pull request. 29 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function(grunt) { 4 | 5 | // Project configuration. 6 | grunt.initConfig({ 7 | // Metadata. 8 | pkg: grunt.file.readJSON('package.json'), 9 | banner: '/*! <%= pkg.title || pkg.name %> - v<%= pkg.version %> - ' + 10 | '<%= grunt.template.today("yyyy-mm-dd") %>\n' + 11 | '<%= pkg.homepage ? "* " + pkg.homepage + "\\n" : "" %>' + 12 | '* Copyright (c) <%= grunt.template.today("yyyy") %> <%= pkg.author.name %>;' + 13 | ' Licensed MIT */\n', 14 | // Task configuration. 15 | clean: { 16 | files: ['dist'] 17 | }, 18 | browserify: { 19 | dist: { 20 | files: { 21 | 'dist/raygun.js': [ 22 | 'src/useragent.js', 23 | 'tracekit/tracekit.js', 24 | 'src/raygun.tracekit.jquery.js', 25 | 'src/polyfills.js', 26 | 'src/raygun.utilities/index.js', 27 | 'src/raygun.utilities/errorUtilities.js', 28 | 'src/raygun.network-tracking.js', 29 | 'src/raygun.viewport.js', 30 | 'src/raygun.breadcrumbs.js', 31 | 'src/raygun.rum/core-web-vitals.js', 32 | 'src/raygun.js', 33 | 'src/raygun.rum/index.js', 34 | 'src/raygun.loader.js' 35 | ], 36 | 'dist/raygun.vanilla.js': [ 37 | 'src/useragent.js', 38 | 'tracekit/tracekit.js', 39 | 'src/polyfills.js', 40 | 'src/raygun.utilities/index.js', 41 | 'src/raygun.utilities/errorUtilities.js', 42 | 'src/raygun.network-tracking.js', 43 | 'src/raygun.viewport.js', 44 | 'src/raygun.breadcrumbs.js', 45 | 'src/raygun.rum/core-web-vitals.js', 46 | 'src/raygun.js', 47 | 'src/raygun.rum/index.js', 48 | 'src/raygun.loader.js' 49 | ] 50 | } 51 | } 52 | }, 53 | concat: { 54 | options: { 55 | banner: '<%= banner %>', 56 | stripBanners: true 57 | }, 58 | dist: { 59 | src: [ 60 | 'src/umd.intro.js', 61 | 'dist/raygun.js', 62 | 'src/umd.outro.js' 63 | ], 64 | dest: 'dist/raygun.umd.js', 65 | } 66 | }, 67 | uglify: { 68 | options: { 69 | banner: '<%= banner %>', 70 | sourceMap: true 71 | }, 72 | dist: { 73 | files: { 74 | 'dist/raygun.min.js': ['dist/raygun.js'], 75 | 'dist/raygun.vanilla.min.js': ['dist/raygun.vanilla.js'], 76 | 'dist/raygun.umd.min.js': ['dist/raygun.umd.js'] 77 | } 78 | }, 79 | snippet:{ 80 | options:{ 81 | banner: '', 82 | sourceMap: false, 83 | maxLineLen: 60 84 | }, 85 | files:{ 86 | 'dist/snippet/minified.js':['src/snippet/unminified.js'], 87 | 'dist/snippet/minified.nohandler.js':['src/snippet/unminified.nohandler.js'], 88 | 'dist/snippet/minified.fetchhandler.js':['src/snippet/unminified.fetchhandler.js'] 89 | } 90 | } 91 | }, 92 | jshint: { 93 | gruntfile: { 94 | options: { 95 | jshintrc: '.jshintrc' 96 | }, 97 | src: 'Gruntfile.js' 98 | }, 99 | src: { 100 | options: { 101 | jshintrc: 'src/.jshintrc', 102 | ignores: ['src/snippet/**/*.js', 'src/umd.*', 'src/**/*.spec.js', 'src/helpers/*', 'src/raygun.rum/core-web-vitals.js'] 103 | }, 104 | src: ['src/**/*.js'] 105 | } 106 | }, 107 | watch: { 108 | gruntfile: { 109 | files: '<%= jshint.gruntfile.src %>', 110 | tasks: ['jshint:gruntfile'] 111 | }, 112 | src: { 113 | files: '<%= jshint.src.src %>', 114 | tasks: ['build'] 115 | } 116 | }, 117 | 'string-replace': { 118 | dist: { 119 | files: { 120 | 'dist/': 'dist/*.js' 121 | }, 122 | options: { 123 | replacements: [{ 124 | pattern: /({{VERSION}})/gmi, 125 | replacement: '<%= pkg.version %>' 126 | }] 127 | } 128 | }, 129 | bower: { 130 | files: { 131 | './': 'bower.json' 132 | }, 133 | options: { 134 | replacements: [{ 135 | pattern: /"version": (.*),/gmi, 136 | replacement: '"version": "<%= pkg.version %>",' 137 | }] 138 | } 139 | }, 140 | nuspec: { 141 | files: { 142 | './': 'raygun4js.nuspec' 143 | }, 144 | options: { 145 | replacements: [{ 146 | pattern: /(.*)<\/version>/gmi, 147 | replacement: "<%= pkg.version %>" 148 | }] 149 | } 150 | } 151 | } 152 | }); 153 | 154 | // These plugins provide necessary tasks. 155 | grunt.loadNpmTasks('grunt-contrib-clean'); 156 | grunt.loadNpmTasks('grunt-contrib-concat'); 157 | grunt.loadNpmTasks('grunt-contrib-uglify'); 158 | grunt.loadNpmTasks('grunt-contrib-jshint'); 159 | grunt.loadNpmTasks('grunt-contrib-watch'); 160 | grunt.loadNpmTasks('grunt-string-replace'); 161 | grunt.loadNpmTasks('grunt-browserify'); 162 | 163 | grunt.registerTask('compile', ['clean', 'browserify', 'jshint', 'concat:dist', 'uglify:dist']); 164 | 165 | grunt.registerTask('build', ['clean', 'browserify', 'jshint', 'concat:dist', 'string-replace', 'uglify']); 166 | 167 | grunt.registerTask('default', ['build']); 168 | }; 169 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013-2024 Raygun Limited 2 | 3 | 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: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | 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. 8 | 9 | ============================ 10 | Third-party library licenses 11 | ============================ 12 | 13 | js-url 14 | 15 | The MIT License (MIT) 16 | 17 | Copyright (c) 2011-2012 Websanova http://www.websanova.com 18 | 19 | Permission is hereby granted, free of charge, to any person obtaining a copy 20 | of this software and associated documentation files (the "Software"), to deal 21 | in the Software without restriction, including without limitation the rights 22 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 23 | copies of the Software, and to permit persons to whom the Software is 24 | furnished to do so, subject to the following conditions: 25 | 26 | The above copyright notice and this permission notice shall be included in 27 | all copies or substantial portions of the Software. 28 | 29 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 30 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 31 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 32 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 33 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 34 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 35 | THE SOFTWARE. -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "raygun4js", 3 | "version": "3.1.3", 4 | "homepage": "http://raygun.com", 5 | "authors": [ 6 | "Mindscape " 7 | ], 8 | "repository": { 9 | "type": "git", 10 | "url": "git://github.com/MindscapeHQ/raygun4js.git" 11 | }, 12 | "description": "Official Raygun.com automatic error tracking plugin for JavaScript", 13 | "main": "dist/raygun.js", 14 | "keywords": [ 15 | "error", 16 | "tracking", 17 | "reporting", 18 | "raygun" 19 | ], 20 | "license": "MIT", 21 | "ignore": [ 22 | "**/.*", 23 | "node_modules", 24 | "bower_components", 25 | "test", 26 | "tests" 27 | ], 28 | "dependencies": { 29 | }, 30 | "devDependencies": { 31 | "grunt-contrib-jshint": "~0.11.2", 32 | "grunt-contrib-concat": "~0.1.2", 33 | "grunt-contrib-uglify": "~0.1.1", 34 | "grunt-contrib-watch": "~0.2.0", 35 | "grunt-contrib-clean": "~0.4.0", 36 | "grunt": "~0.4.1", 37 | "grunt-contrib-jasmine": "~0.4.2" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /examples/BasicCrashReporting.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Raygun Crash Reporting - Basic Example 6 | 7 | 8 | 9 | 21 | 22 | 23 | 24 |
25 |
26 |
27 |
28 | 29 |
30 |

Raygun4JS - Basic Example

31 | 32 | 33 |

34 | 35 | Throw an error 36 | 37 |

38 |
39 |
40 | 41 | 45 | 46 | 47 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /examples/BasicRUM.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Raygun Crash Reporting - Basic Example 6 | 7 | 8 | 9 | 21 | 22 | 23 | 24 |
25 |
26 |
27 |
28 | 29 |
30 |

Raygun4JS - Basic Example

31 | 32 | 33 |

34 | 35 | Throw an error 36 | 37 |

38 |
39 |
40 | 41 | 46 | 47 | 48 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /examples/CustomDataCrashReporting.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Raygun Crash Reporting - Basic Example 6 | 7 | 8 | 9 | 21 | 22 | 23 | 24 |
25 |
26 |
27 |
28 | 29 |
30 |

Raygun4JS - Custom Data

31 | 32 | 33 |

34 | 35 | Throw an error 36 | 37 |

38 |
39 |
40 | 41 | 49 | 50 | 51 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /examples/CustomErrorGroupingKey.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Raygun Crash Reporting - Basic Example 6 | 7 | 8 | 9 | 21 | 22 | 23 | 24 |
25 |
26 |
27 |
28 | 29 |
30 |

Raygun4JS - Custom Error Grouping Key Example

31 | 32 | 33 |

34 | 35 | Throw an error 36 | 37 |

38 |
39 |
40 | 41 | 48 | 49 | 50 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /examples/IgnoreLocalhostCrashReporting.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Raygun Crash Reporting - Basic Example 6 | 7 | 8 | 9 | 21 | 22 | 23 | 24 |
25 |
26 |
27 |
28 | 29 |
30 |

Raygun4JS - Custom Data

31 | 32 | 33 |

34 | 35 | Throw an error 36 | 37 |

38 |
39 |
40 | 41 | 48 | 49 | 50 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /examples/ManualSendCrashReporting.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Raygun Crash Reporting - Basic Example 6 | 7 | 8 | 9 | 21 | 22 | 23 | 24 |
25 |
26 |
27 |
28 | 29 |
30 |

Raygun4JS - Custom Data

31 | 32 | 33 |

34 | 35 | Throw an error 36 | 37 |

38 |
39 |
40 | 41 | 45 | 46 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | # Raygun Examples 2 | 3 | #### Running these examples 4 | 5 | You can either view the code here on Github or locally. Rememeber to replace the API key if you want to run them. 6 | 7 | ### Crash Reporting 8 | 9 | --- 10 | 11 | ##### [BasicCrashReporting.html](BasicCrashReporting.html) 12 | 13 | Basic Crash Reporting setup using Raygun4JS 14 | 15 | --- 16 | 17 | ##### [CustomDataCrashReporting.html](CustomDataCrashReporting.html) 18 | 19 | Send custom data through along with the crash report 20 | 21 | --- 22 | 23 | ##### [IgnoreLocalhostCrashReporting.html](IgnoreLocalhostCrashReporting.html) 24 | 25 | Ignore errors generated on localhost and *.local 26 | 27 | --- 28 | 29 | ##### [IgnoreLocalhostCrashReporting.html](IgnoreLocalhostCrashReporting.html) 30 | 31 | Ignore errors generated on localhost and *.local 32 | 33 | --- 34 | 35 | ##### [ManualSendCrashReporting.html](ManualSendCrashReporting.html) 36 | 37 | Learn how to manually send errors to Raygun 38 | 39 | --- 40 | 41 | ##### [SettingAUserCrashReporting.html](SettingAUserCrashReporting.html) 42 | 43 | Send user details through with a crash report 44 | 45 | --- 46 | 47 | ##### [TagsCrashReporting.html](TagsCrashReporting.html) 48 | 49 | Send custom tags through with the crash report 50 | 51 | --- 52 | 53 | ##### [CustomErrorGroupingKey.html](CustomErrorGroupingKey.html) 54 | 55 | Custom error grouping key 56 | 57 | --- 58 | 59 | ### RUM 60 | 61 | --- 62 | 63 | ##### [BasicRUM.html](BasicRUM.html) 64 | 65 | Basic Crash Reporting and RUM setup using Raygun4JS -------------------------------------------------------------------------------- /examples/SettingAUserCrashReporting.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Raygun Crash Reporting - Basic Example 6 | 7 | 8 | 9 | 21 | 22 | 23 | 24 |
25 |
26 |
27 |
28 | 29 |
30 |

Raygun4JS - Custom Data

31 | 32 | 33 |

34 | 35 | Throw an error 36 | 37 |

38 |
39 |
40 | 41 | 53 | 54 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /examples/TagsCrashReporting.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Raygun Crash Reporting - Basic Example 6 | 7 | 8 | 9 | 21 | 22 | 23 | 24 |
25 |
26 |
27 |
28 | 29 |
30 |

Raygun4JS - Custom Data

31 | 32 | 33 |

34 | 35 | Throw an error 36 | 37 |

38 |
39 |
40 | 41 | 46 | 47 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /jasmine.json: -------------------------------------------------------------------------------- 1 | { 2 | "spec_dir": "src", 3 | "spec_files": [ 4 | "**/*.spec.js" 5 | ], 6 | "helpers": [ 7 | "helpers/**/*.js" 8 | ], 9 | "stopSpecOnExpectationFailure": false, 10 | "random": true 11 | } 12 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "raygun4js", 3 | "main": "dist/raygun.umd.js", 4 | "files": [ 5 | "dist/*", 6 | "types/*" 7 | ], 8 | "title": "Raygun4js", 9 | "description": "Raygun.com plugin for JavaScript", 10 | "version": "3.1.3", 11 | "homepage": "https://github.com/MindscapeHQ/raygun4js", 12 | "author": { 13 | "name": "MindscapeHQ", 14 | "email": "hello@raygun.com" 15 | }, 16 | "repository": { 17 | "type": "git", 18 | "url": "https://github.com/MindscapeHQ/raygun4js.git" 19 | }, 20 | "types": "./types/index.d.ts", 21 | "bugs": "https://github.com/MindscapeHQ/raygun4js/issues", 22 | "license": "SEE LICENSE IN https://github.com/MindscapeHQ/raygun4js/blob/master/LICENSE", 23 | "devDependencies": { 24 | "@wdio/cli": "^8.36.1", 25 | "@wdio/dot-reporter": "^8.36.1", 26 | "@wdio/jasmine-framework": "^8.36.1", 27 | "@wdio/local-runner": "^8.36.1", 28 | "@wdio/selenium-standalone-service": "^8.14.0", 29 | "@wdio/spec-reporter": "^8.36.1", 30 | "@wdio/static-server-service": "^8.36.1", 31 | "chromedriver": "^124.0.3", 32 | "cross-env": "^7.0.3", 33 | "grunt": "^1.6.1", 34 | "grunt-browserify": "^6.0.0", 35 | "grunt-contrib-clean": "^2.0.1", 36 | "grunt-contrib-concat": "^2.1.0", 37 | "grunt-contrib-jshint": "^3.2.0", 38 | "grunt-contrib-uglify": "^5.2.2", 39 | "grunt-contrib-watch": "^1.1.0", 40 | "grunt-string-replace": "^1.3.1", 41 | "jasmine": "^5.1.0", 42 | "underscore": "^1.13.6", 43 | "wdio-chromedriver-service": "^8.1.1" 44 | }, 45 | "scripts": { 46 | "test": "npm run jasmine && npm run wdio", 47 | "jasmine": "jasmine --config=jasmine.json", 48 | "wdio": "cross-env node_modules/.bin/wdio wdio.conf.js" 49 | }, 50 | "keywords": [ 51 | "error", 52 | "tracking", 53 | "raygun", 54 | "clientside" 55 | ], 56 | "dependencies": { 57 | "web-vitals": "^3.5.0" 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /raygun4js.nuspec: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | raygun4js 5 | 3.1.3 6 | Raygun4js 7 | Raygun Limited 8 | Raygun Limited 9 | https://raw2.github.com/MindscapeHQ/raygun4js/master/LICENSE 10 | http://raygun.com/raygun-providers/javascript 11 | false 12 | Official Raygun JavaScript module - automatic client-side error tracking and Real User Monitoring for your web project 13 | Raygun4js is a tiny library that you can easily add to your website or web application, which will then let your site automatically transmit all Errors to your Raygun.com dashboard, where you can see the stack trace, environment data, custom data and more. Installation is painless, and configuring your site to transmit errors takes just a couple of minutes. 14 | en-US 15 | raygun error tracking reporting logging monitoring 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /rum-spa-test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 35 | Raygun4JS Test Website 36 | 37 | 38 | 39 |

das da stuff

40 | 109 | 110 | 111 | 112 | 113 | -------------------------------------------------------------------------------- /src/.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "curly": true, 3 | "eqeqeq": true, 4 | "immed": true, 5 | "latedef": false, 6 | "newcap": true, 7 | "noarg": true, 8 | "sub": true, 9 | "undef": true, 10 | "unused": true, 11 | "boss": true, 12 | "eqnull": true, 13 | "browser": true, 14 | "predef": ["TraceKit", "jQuery"], 15 | "laxbreak": true 16 | } 17 | -------------------------------------------------------------------------------- /src/helpers/browser-mock.js: -------------------------------------------------------------------------------- 1 | /*jshint esversion: 6 */ 2 | 3 | // Mock for Raygun object 4 | var RaygunObject = {}; 5 | global.Raygun = RaygunObject; 6 | 7 | // Function to store event handlers 8 | function storeEventHandler(eventType, handler) { 9 | if (!global._eventHandlers[eventType]) { 10 | global._eventHandlers[eventType] = []; 11 | } 12 | global._eventHandlers[eventType].push(handler); 13 | } 14 | 15 | // Function to simulate an event 16 | function simulateEvent(eventType) { 17 | if (global._eventHandlers[eventType]) { 18 | global._eventHandlers[eventType].forEach(handler => handler()); 19 | } 20 | } 21 | 22 | // Initializing global event handlers storage 23 | global._eventHandlers = {}; 24 | 25 | // Mock implementations 26 | global.addEventListener = function(eventType, handler, useCapture) { 27 | storeEventHandler(eventType, handler); 28 | }; 29 | 30 | global._simulateEvent = simulateEvent; 31 | 32 | // Mock for global window object 33 | global.window = Object.defineProperties({}, { 34 | 'localStorage': { 35 | get: () => null, 36 | configurable: true, 37 | enumerable: true 38 | }, 39 | 'sessionStorage': { 40 | get: () => null, 41 | configurable: true, 42 | enumerable: true 43 | }, 44 | '__instantiatedRaygun': { 45 | get: () => RaygunObject, 46 | configurable: true, 47 | enumerable: true 48 | } 49 | }); 50 | 51 | // Mock for global document object 52 | global.document = {}; -------------------------------------------------------------------------------- /src/polyfills.js: -------------------------------------------------------------------------------- 1 | // Mozilla's toISOString() shim for IE8 2 | if (!Date.prototype.toISOString) { 3 | (function () { 4 | function pad(number) { 5 | var r = String(number); 6 | if (r.length === 1) { 7 | r = '0' + r; 8 | } 9 | return r; 10 | } 11 | 12 | Date.prototype.toISOString = function () { 13 | return this.getUTCFullYear() + '-' + pad(this.getUTCMonth() + 1) + '-' + pad(this.getUTCDate()) + 'T' + pad(this.getUTCHours()) + ':' + pad(this.getUTCMinutes()) + ':' + pad(this.getUTCSeconds()) + '.' + String((this.getUTCMilliseconds() / 1000).toFixed(3)).slice(2, 5) + 'Z'; 14 | }; 15 | }()); 16 | } 17 | 18 | // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/indexOf 19 | if (!Array.prototype.indexOf) { 20 | Array.prototype.indexOf = function(searchElement, fromIndex) { 21 | var k; 22 | if (this == null) { 23 | throw new TypeError('"this" is null or not defined'); 24 | } 25 | var o = Object(this); 26 | var len = o.length >>> 0; 27 | 28 | if (len === 0) { 29 | return -1; 30 | } 31 | var n = fromIndex | 0; 32 | 33 | if (n >= len) { 34 | return -1; 35 | } 36 | k = Math.max(n >= 0 ? n : len - Math.abs(n), 0); 37 | 38 | while (k < len) { 39 | if (k in o && o[k] === searchElement) { 40 | return k; 41 | } 42 | k++; 43 | } 44 | return -1; 45 | }; 46 | } 47 | 48 | // Production steps of ECMA-262, Edition 5, 15.4.4.19 49 | // Reference: http://es5.github.io/#x15.4.4.19 50 | if (!Array.prototype.map) { 51 | Array.prototype.map = function(callback/*, thisArg*/) { 52 | var T, A, k; 53 | 54 | if (this == null) { 55 | throw new TypeError('this is null or not defined'); 56 | } 57 | 58 | var O = Object(this); 59 | var len = O.length >>> 0; 60 | 61 | if (typeof callback !== 'function') { 62 | throw new TypeError(callback + ' is not a function'); 63 | } 64 | 65 | if (arguments.length > 1) { 66 | T = arguments[1]; 67 | } 68 | 69 | A = new Array(len); 70 | k = 0; 71 | 72 | while (k < len) { 73 | var kValue, mappedValue; 74 | 75 | if (k in O) { 76 | kValue = O[k]; 77 | 78 | mappedValue = callback.call(T, kValue, k, O); 79 | A[k] = mappedValue; 80 | } 81 | k++; 82 | } 83 | 84 | return A; 85 | }; 86 | } 87 | 88 | // Production steps of ECMA-262, Edition 5, 15.4.4.18 89 | // Reference: http://es5.github.io/#x15.4.4.18 90 | if (!Array.prototype.forEach) { 91 | Array.prototype.forEach = function(callback/*, thisArg*/) { 92 | var T, k; 93 | 94 | if (this == null) { 95 | throw new TypeError('this is null or not defined'); 96 | } 97 | 98 | var O = Object(this); 99 | var len = O.length >>> 0; 100 | 101 | if (typeof callback !== 'function') { 102 | throw new TypeError(callback + ' is not a function'); 103 | } 104 | 105 | if (arguments.length > 1) { 106 | T = arguments[1]; 107 | } 108 | 109 | k = 0; 110 | while (k < len) { 111 | var kValue; 112 | 113 | if (k in O) { 114 | kValue = O[k]; 115 | 116 | callback.call(T, kValue, k, O); 117 | } 118 | k++; 119 | } 120 | }; 121 | } 122 | 123 | // Mozilla's bind() shim for IE8 124 | if (!Function.prototype.bind) { 125 | Function.prototype.bind = function (oThis) { 126 | if (typeof this !== 'function') { 127 | throw new TypeError('Function.prototype.bind - what is trying to be bound is not callable'); 128 | } 129 | 130 | var aArgs = Array.prototype.slice.call(arguments, 1), 131 | fToBind = this, 132 | FNOP = function () { 133 | }, 134 | fBound = function () { 135 | return fToBind.apply(this instanceof FNOP && oThis ? this : oThis, 136 | aArgs.concat(Array.prototype.slice.call(arguments))); 137 | }; 138 | 139 | FNOP.prototype = this.prototype; 140 | fBound.prototype = new FNOP(); 141 | 142 | return fBound; 143 | }; 144 | } 145 | -------------------------------------------------------------------------------- /src/raygun.rum/core-web-vitals.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @prettier 3 | */ 4 | 5 | /* 6 | * raygun4js 7 | * https://github.com/MindscapeHQ/raygun4js 8 | * 9 | * Copyright (c) 2024 MindscapeHQ 10 | * Licensed under the MIT license. 11 | */ 12 | 13 | var webVitals = require('web-vitals'); 14 | 15 | function raygunCoreWebVitalFactory() { 16 | var WebVitalTimingType = "w"; 17 | var queueTimings = null; 18 | var _parentResource = null; 19 | 20 | var CoreWebVitals = function(){ 21 | this.cleanWebVitalData = function (event) { 22 | var res = event; 23 | 24 | if(res.value && res.value.toFixed) { 25 | res.value = res.value.toFixed(3); 26 | } 27 | 28 | return res; 29 | }; 30 | }; 31 | 32 | CoreWebVitals.prototype.attach = function(queueHandler, parentResource) { 33 | queueTimings = queueHandler; 34 | _parentResource = parentResource; 35 | 36 | webVitals.onLCP(this.handler); 37 | webVitals.onFID(this.handler); 38 | webVitals.onCLS(this.handler); 39 | webVitals.onINP(this.handler); 40 | webVitals.onFCP(this.handler); 41 | webVitals.onTTFB(this.handler); 42 | }; 43 | 44 | CoreWebVitals.prototype.handler = function(event) { 45 | if(event.value && event.value.toFixed) { 46 | event.value = event.value.toFixed(3); 47 | } 48 | 49 | var webVitalEvent = { 50 | url: event.name, 51 | timing: { 52 | t: WebVitalTimingType, 53 | du: event.value 54 | }, 55 | parentResource: _parentResource 56 | }; 57 | 58 | queueTimings(webVitalEvent); 59 | }; 60 | 61 | return new CoreWebVitals(); 62 | } 63 | 64 | window.raygunCoreWebVitalFactory = raygunCoreWebVitalFactory; -------------------------------------------------------------------------------- /src/raygun.rum/core-web-vitals.spec.js: -------------------------------------------------------------------------------- 1 | /*jshint esversion: 6 */ 2 | 3 | require('./core-web-vitals'); 4 | 5 | describe("core-web-vitals", () => { 6 | let CoreWebVitals = window.raygunCoreWebVitalFactory({ webVitals: null }), queue = []; 7 | CoreWebVitals.attach(e => queue.push(e)); 8 | 9 | beforeEach(() => { 10 | queue = []; 11 | }); 12 | 13 | describe("handler is called", () => { 14 | it("creates the appropriate payload", () => { 15 | CoreWebVitals.handler({ name: "FID", value: "1" }); 16 | 17 | expect(queue.pop()).toEqual({ 18 | url: "FID", 19 | timing: { 20 | t: "w", 21 | du: "1" 22 | }, 23 | parentResource: undefined 24 | }); 25 | }); 26 | }); 27 | 28 | describe("event reports long metric value", () => { 29 | 30 | it('value is rounded to 3dp', () => { 31 | CoreWebVitals.handler({ name: "FID", value: 0.14589 }); 32 | 33 | var res = queue.pop(); 34 | expect(res.timing.du).toBe('0.146'); 35 | }); 36 | }); 37 | }); 38 | -------------------------------------------------------------------------------- /src/raygun.rum/rum.spec.js: -------------------------------------------------------------------------------- 1 | /*jshint esversion: 6 */ 2 | 3 | require('./index'); 4 | 5 | describe("raygun.rum", () => { 6 | let RUM; 7 | let utils; 8 | 9 | beforeEach(() => { 10 | RUM = new Raygun.RealUserMonitoring( 11 | "API-KEY", 12 | "API-URL", 13 | () => {}, 14 | "user", 15 | "version", 16 | ["rum-unit-tests"], 17 | [], 18 | [], 19 | false, //debugMode, 20 | undefined, //maxVirtualPageDuration, 21 | true, //ignoreUrlCasing, 22 | true, 23 | undefined, //beforeSendRumCallback, 24 | false, //setCookieAsSecure, 25 | false, //captureMissingRequests, 26 | false // automaticPerformanceCustomTimings 27 | ); 28 | utils = RUM.Utilities; 29 | }); 30 | 31 | describe("isCustomTimingMeasurement", () => { 32 | it("returns true when entryType is 'measure'", () => { 33 | const resource = { 34 | entryType: 'measure' 35 | }; 36 | expect(utils.isCustomTimingMeasurement(resource)).toBe(true); 37 | }); 38 | it("returns false when entryType is not 'measure'", () => { 39 | const resource = { 40 | entryType: 'mark' 41 | }; 42 | expect(utils.isCustomTimingMeasurement(resource)).toBe(false); 43 | }); 44 | it("returns false when undefined is passed", () => { 45 | expect(utils.isCustomTimingMeasurement(undefined)).toBe(false); 46 | }); 47 | }); 48 | 49 | describe("createCustomTimingMeasurement", () => { 50 | it('returns a custom timing entry', () => { 51 | expect(utils.createCustomTimingMeasurement("test", 100, 200)).toEqual({ 52 | url: 'test', 53 | timing: { 54 | t: 't', 55 | du: '100.00', 56 | a: '200.00' 57 | } 58 | }); 59 | }); 60 | 61 | describe('with floating point numbers passed', () => { 62 | it('all numbers are fixed to 2 decimal places', () => { 63 | expect(utils.createCustomTimingMeasurement("test", 100.123, 200.123)).toEqual({ 64 | url: 'test', 65 | timing: { 66 | t: 't', 67 | du: '100.12', 68 | a: '200.12' 69 | } 70 | }); 71 | }); 72 | }); 73 | 74 | describe('when no offset passed', () => { 75 | it('returns a object with the offset equal to 0.00', () => { 76 | expect(utils.createCustomTimingMeasurement("test", 100)).toEqual({ 77 | url: 'test', 78 | timing: { 79 | t: 't', 80 | du: '100.00', 81 | a: '0.00' 82 | } 83 | }); 84 | }); 85 | }); 86 | }); 87 | 88 | describe("getCustomTimingMeasurement", () => { 89 | it('returns a custom timing entry', () => { 90 | const resource = { 91 | name: 'test-resource', 92 | startTime: 1000, 93 | duration: 2000, 94 | }; 95 | 96 | expect(utils.getCustomTimingMeasurement(resource)).toEqual({ 97 | url: 'test-resource', 98 | timing: { 99 | t: 't', 100 | du: '2000.00', 101 | a: '1000.00' 102 | } 103 | }); 104 | }); 105 | }); 106 | 107 | describe("getTimingDuration", () => { 108 | it('returns the duration when it is not 0', () => { 109 | const resource = { 110 | name: 'test-resource', 111 | startTime: 1000, 112 | responseEnd: 2000, 113 | duration: 500, 114 | }; 115 | expect(utils.getTimingDuration(resource)).toEqual(500); 116 | }); 117 | 118 | it('returns startTime - responseEnd when the duration is 0', () => { 119 | const resource = { 120 | name: 'test-resource', 121 | startTime: 1200, 122 | responseEnd: 2000, 123 | duration: 0, 124 | }; 125 | expect(utils.getTimingDuration(resource)).toEqual(800); 126 | }); 127 | }); 128 | }); -------------------------------------------------------------------------------- /src/raygun.tracekit.jquery.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Extended support for backtraces and global error handling for most 3 | * asynchronous jQuery functions. 4 | */ 5 | (function traceKitAsyncForjQuery($, TraceKit) { 6 | 'use strict'; 7 | // quit if jQuery isn't on the page 8 | if (!$ || !$.event || !$.event.add) { 9 | return; 10 | } 11 | 12 | var _oldEventAdd = $.event.add; 13 | $.event.add = function traceKitEventAdd(elem, types, handler, data, selector) { 14 | if (typeof handler !== 'function' && typeof handler.handler !== 'function') { 15 | return _oldEventAdd.call(this, elem, types, handler, data, selector); 16 | } 17 | 18 | var _handler; 19 | 20 | if (handler.handler) { 21 | _handler = handler.handler; 22 | handler.handler = TraceKit.wrap(handler.handler); 23 | } else { 24 | _handler = handler; 25 | handler = TraceKit.wrap(handler); 26 | } 27 | 28 | // If the handler we are attaching doesn’t have the same guid as 29 | // the original, it will never be removed when someone tries to 30 | // unbind the original function later. Technically as a result of 31 | // this our guids are no longer globally unique, but whatever, that 32 | // never hurt anybody RIGHT?! 33 | if (_handler.guid) { 34 | handler.guid = _handler.guid; 35 | } else { 36 | handler.guid = _handler.guid = $.guid++; 37 | } 38 | 39 | return _oldEventAdd.call(this, elem, types, handler, data, selector); 40 | }; 41 | 42 | var _oldReady = $.fn.ready; 43 | $.fn.ready = function traceKitjQueryReadyWrapper(fn) { 44 | return _oldReady.call(this, TraceKit.wrap(fn)); 45 | }; 46 | 47 | var _oldAjax = $.ajax; 48 | $.ajax = function traceKitAjaxWrapper(url, options) { 49 | if (typeof url === "object") { 50 | options = url; 51 | url = undefined; 52 | } 53 | 54 | options = options || {}; 55 | 56 | var keys = ['complete', 'error', 'success'], key; 57 | while(key = keys.pop()) { 58 | if (typeof options[key] === "function") { 59 | options[key] = TraceKit.wrap(options[key]); 60 | } 61 | } 62 | 63 | try { 64 | return (url) ? _oldAjax.call(this, url, options) : _oldAjax.call(this, options); 65 | } catch (e) { 66 | TraceKit.report(e); 67 | throw e; 68 | } 69 | }; 70 | 71 | }(window.jQuery, window.TraceKit)); 72 | -------------------------------------------------------------------------------- /src/raygun.utilities/errorUtilities.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @prettier 3 | */ 4 | 5 | window.raygunErrorUtilitiesFactory = function (window, Raygun) { 6 | var scriptError = 'Script error'; 7 | var currentLocation = !!window && !!window.location ? window.location : { 8 | host: null, 9 | toString: function toString() { 10 | return null; 11 | } 12 | }; 13 | var currentUrl = currentLocation.toString(); 14 | var utils = Raygun.Utilities; 15 | 16 | var isBrowserExtensionUrl = function isBrowserExtensionUrl(url) { 17 | return url.indexOf('chrome-extension://') === 0 || 18 | url.indexOf('moz-extension://') === 0 || 19 | url.indexOf('safari-extension://') === 0 || 20 | url.indexOf('safari-web-extension://') === 0; 21 | }; 22 | 23 | // The stack line is deemed invalid if all of the following conditions are met: 24 | // 1. The line and column numbers are nil *or* zero 25 | // 2. The url is nil *or* the same as the current location *and* the function is '?' 26 | var isValidStackLine = function isValidStackLine(stackLine) { 27 | if (!utils.isNil(stackLine.line) && stackLine.line > 0) { 28 | return true; 29 | } 30 | 31 | if (!utils.isNil(stackLine.column) && stackLine.column > 0) { 32 | return true; 33 | } 34 | 35 | if (utils.isNil(stackLine.url) || currentUrl.indexOf(stackLine.url) !== -1 && stackLine.func === '?') { 36 | return false; 37 | } 38 | 39 | return true; 40 | }; 41 | 42 | return { 43 | /** 44 | * Check if the current stacktrace is a Script error from an external domain. 45 | * 46 | * @param stackTrace 47 | * @param options 48 | * @returns {boolean} 49 | */ 50 | isScriptError: function isScriptError(stackTrace, options) { 51 | var msg = scriptError; 52 | 53 | if (stackTrace.message) { 54 | msg = stackTrace.message; 55 | } else if (options && options.status) { 56 | msg = options.status; 57 | } 58 | 59 | if (utils.isNil(msg)) { 60 | msg = scriptError; 61 | } 62 | 63 | return !utils.isReactNative() && 64 | typeof msg.substring === 'function' && 65 | msg.substring(0, scriptError.length) === scriptError && 66 | !utils.isNil(stackTrace.stack[0].url) && 67 | stackTrace.stack[0].url.indexOf(currentLocation.host) === -1 && 68 | (stackTrace.stack[0].line === 0 || stackTrace.stack[0].func === '?'); 69 | }, 70 | 71 | /** 72 | * Check if the stacktrace from a browser extension - if any stack line has a url that starts with a browser 73 | * extension protocol (e.g. "chrome-extension://"), then this will return true. 74 | * 75 | * @param stackTrace 76 | * @returns {boolean} 77 | */ 78 | isBrowserExtensionError: function isBrowserExtensionError(stackTrace) { 79 | var stack = stackTrace.stack; 80 | 81 | if (utils.isEmpty(stack)) { 82 | return false; 83 | } 84 | 85 | return utils.any(stack, function (stackLine) { 86 | var url = stackLine.url; 87 | 88 | return !utils.isNil(url) && isBrowserExtensionUrl(url); 89 | }); 90 | }, 91 | 92 | /** 93 | * Check if any lines in the stack are valid, i.e. they do not match the criteria of having a null/zero line and 94 | * column number and do not have a url equal to the current url with a function name of '?'. 95 | * 96 | * This is to filter out a common pattern of errors triggered in browser extensions or by bots/crawlers. 97 | * 98 | * @param stackTrace 99 | * @returns {boolean} 100 | */ 101 | isValidStackTrace: function isValidStackTrace(stackTrace) { 102 | var stack = stackTrace.stack; 103 | 104 | if (utils.isNil(stackTrace.message) || utils.isEmpty(stack)) { 105 | return false; 106 | } 107 | 108 | return utils.any(stack, isValidStackLine); 109 | }, 110 | 111 | /** 112 | * Check if the current stacktrace has any lines that have a url that matches the current url. This function can be 113 | * passed a list of whitelisted domains that will be checked against. 114 | * 115 | * @param stackTrace 116 | * @param whitelistedScriptDomains string[] 117 | * @returns {boolean} 118 | */ 119 | stackTraceHasValidDomain: function stackTraceHasValidDomain(stackTrace, whitelistedScriptDomains) { 120 | var foundValidDomain = false; 121 | 122 | for (var i = 0; !foundValidDomain && stackTrace.stack && i < stackTrace.stack.length; i++) { 123 | var stackLine = stackTrace.stack[i]; 124 | 125 | if (!utils.isNil(stackLine) && !utils.isNil(stackLine.url)) { 126 | for (var j in whitelistedScriptDomains) { 127 | if (whitelistedScriptDomains.hasOwnProperty(j)) { 128 | if (stackLine.url.indexOf(whitelistedScriptDomains[j]) > -1) { 129 | foundValidDomain = true; 130 | } 131 | } 132 | } 133 | 134 | if (stackLine.url.indexOf(currentLocation.host) > -1) { 135 | foundValidDomain = true; 136 | } 137 | } 138 | } 139 | 140 | return foundValidDomain; 141 | } 142 | }; 143 | }; 144 | -------------------------------------------------------------------------------- /src/raygun.viewport.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @prettier 3 | */ 4 | 5 | window.raygunViewportFactory = function raygunViewportFactory(window, document, Raygun) { 6 | 'use strict'; 7 | 8 | var utils = Raygun.Utilities; 9 | var nullResult = { 10 | width: null, 11 | height: null 12 | }; 13 | 14 | var getViewportWidth = function getViewportWidth() { 15 | return Math.max(document.documentElement.clientWidth || 0, window.innerWidth || 0); 16 | }; 17 | 18 | var getViewportHeight = function getViewportHeight() { 19 | return Math.max(document.documentElement.clientHeight || 0, window.innerHeight || 0); 20 | }; 21 | 22 | var isValidDimension = function isValidDimension(dimensionValue) { 23 | return !utils.isNil(dimensionValue) && !isNaN(dimensionValue) && dimensionValue > 0; 24 | }; 25 | 26 | return { 27 | /** 28 | * Get the width and height values of the current browser viewport. 29 | * 30 | * Notes: 31 | * - This will return an object with null width and height values if window or document are unavailable, or the 32 | * dimension values are invalid. 33 | * - This will use the window object's innerWidth and innerHeight functions to get the dimensions, with a fallback 34 | * to document.documentElement clientWidth and clientHeight, if both are available, it will return the largest of 35 | * the values 36 | * 37 | * @returns {{width: number, height: number}|{width: null, height: null}} 38 | */ 39 | getViewportDimensions: function getViewportDimensions() { 40 | if (utils.isNil(document) || utils.isNil(window)) { 41 | return nullResult; 42 | } 43 | 44 | var viewportWidth = getViewportWidth(); 45 | var viewportHeight = getViewportHeight(); 46 | 47 | if (!isValidDimension(viewportWidth) && !isValidDimension(viewportHeight)) { 48 | return nullResult; 49 | } 50 | 51 | return { 52 | width: viewportWidth, 53 | height: viewportHeight, 54 | }; 55 | } 56 | }; 57 | }; 58 | -------------------------------------------------------------------------------- /src/raygun.viewport.spec.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @prettier 3 | */ 4 | 5 | require('./raygun.viewport'); 6 | 7 | const fakeWidth = 1920; 8 | const fakeHeight = 1080; 9 | 10 | const FakeWindow = { 11 | innerWidth: fakeWidth, 12 | innerHeight: fakeHeight 13 | }; 14 | 15 | const FakeDocument = { 16 | documentElement: { 17 | clientWidth: fakeWidth, 18 | clientHeight: fakeHeight 19 | } 20 | }; 21 | 22 | const FakeRaygun = { 23 | Utilities: window.raygunUtilityFactory(FakeWindow, {}) 24 | }; 25 | 26 | function getViewportClass(windowObject, documentObject) { 27 | return window.raygunViewportFactory(windowObject, documentObject, FakeRaygun); 28 | } 29 | 30 | describe('Viewport', () => { 31 | describe('getViewportDimensions', () => { 32 | describe('with undefined window object', () => { 33 | it('returns null result', () => { 34 | const viewport = getViewportClass(undefined, FakeDocument); 35 | const result = viewport.getViewportDimensions(); 36 | 37 | expect(result.width).toBeNull(); 38 | expect(result.height).toBeNull(); 39 | }); 40 | }); 41 | 42 | describe('with undefined document object', () => { 43 | it('returns null result', () => { 44 | const viewport = getViewportClass(FakeWindow, undefined); 45 | const result = viewport.getViewportDimensions(); 46 | 47 | expect(result.width).toBeNull(); 48 | expect(result.height).toBeNull(); 49 | }); 50 | }); 51 | 52 | describe('with zero width and height values', () => { 53 | it('returns null result', () => { 54 | const viewport = getViewportClass({ 55 | innerWidth: 0, 56 | innerHeight: 0 57 | }, { 58 | documentElement: { 59 | clientWidth: 0, 60 | clientHeight: 0 61 | } 62 | }); 63 | 64 | const result = viewport.getViewportDimensions(); 65 | 66 | expect(result.width).toBeNull(); 67 | expect(result.height).toBeNull(); 68 | }); 69 | }); 70 | 71 | describe('with non-numeric width and height values', () => { 72 | it('returns null result', () => { 73 | const viewport = getViewportClass({ 74 | innerWidth: 'a', 75 | innerHeight: 'b' 76 | }, { 77 | documentElement: { 78 | clientWidth: 'a', 79 | clientHeight: 'b' 80 | } 81 | }); 82 | 83 | const result = viewport.getViewportDimensions(); 84 | 85 | expect(result.width).toBeNull(); 86 | expect(result.height).toBeNull(); 87 | }); 88 | }); 89 | 90 | describe('with a window object that does not support innerWidth and innerHeight', () => { 91 | it('will fallback to document dimensions', () => { 92 | const viewport = getViewportClass({}, { 93 | documentElement: { 94 | clientWidth: 4096, 95 | clientHeight: 2160 96 | } 97 | }); 98 | const result = viewport.getViewportDimensions(); 99 | 100 | expect(result.width).toEqual(4096); 101 | expect(result.height).toEqual(2160); 102 | }); 103 | }); 104 | 105 | describe('with window and document object that contain valid width and height', () => { 106 | describe('with equal width and height values', () => { 107 | it('will return the inner width and height from the window', () => { 108 | const viewport = getViewportClass(FakeWindow, FakeDocument); 109 | const result = viewport.getViewportDimensions(); 110 | 111 | expect(result.width).toEqual(fakeWidth); 112 | expect(result.height).toEqual(fakeHeight); 113 | }); 114 | }); 115 | 116 | describe('with window values that include the scrollbar width and height', () => { 117 | it('will take the largest dimensions to include the scrollbar', () => { 118 | const viewport = getViewportClass({ 119 | innerWidth: 1920, 120 | innerHeight: 1080 121 | }, { 122 | documentElement: { 123 | clientWidth: 1904, 124 | clientHeight: 1064 125 | } 126 | }); 127 | const result = viewport.getViewportDimensions(); 128 | 129 | expect(result.width).toEqual(1920); 130 | expect(result.height).toEqual(1080); 131 | }); 132 | }); 133 | }); 134 | }); 135 | }); 136 | -------------------------------------------------------------------------------- /src/snippet/minified.fetchhandler.js: -------------------------------------------------------------------------------- 1 | !function(a,b,c,d,e,f,g,h,i,j){a.RaygunObject=e,a[e]=a[e]||function(){ 2 | (a[e].o=a[e].o||[]).push(arguments)},g=b.createElement(c),h=b.getElementsByTagName(c)[0], 3 | g.async=1,g.src=d,a.__raygunNoConflict=!!f,h.parentNode.insertBefore(g,h),i=a.onerror, 4 | a.onerror=function(b,c,d,f,g){i&&i(b,c,d,f,g),g||(g=new Error(b)),a[e].q=a[e].q||[], 5 | a[e].q.push({e:g})},j=a.fetch,j&&(a.__raygunOriginalFetch=j,a.fetch=function(){ 6 | return a.__raygunFetchCallback?a.__raygunFetchCallback.apply(null,arguments):j.apply(null,arguments); 7 | })}(window,document,"script","//cdn.raygun.io/raygun4js/raygun.min.js","rg4js"); -------------------------------------------------------------------------------- /src/snippet/minified.js: -------------------------------------------------------------------------------- 1 | !function(a,b,c,d,e,f,g,h,i){a.RaygunObject=e,a[e]=a[e]||function(){ 2 | (a[e].o=a[e].o||[]).push(arguments)},g=b.createElement(c),h=b.getElementsByTagName(c)[0], 3 | g.async=1,g.src=d,a.__raygunNoConflict=!!f,h.parentNode.insertBefore(g,h),i=a.onerror, 4 | a.onerror=function(b,c,d,f,g){i&&i(b,c,d,f,g),g||(g=new Error(b)),a[e].q=a[e].q||[], 5 | a[e].q.push({e:g})}}(window,document,"script","//cdn.raygun.io/raygun4js/raygun.min.js","rg4js"); -------------------------------------------------------------------------------- /src/snippet/minified.nohandler.js: -------------------------------------------------------------------------------- 1 | !function(a,b,c,d,e,f,g,h){a.RaygunObject=e,a[e]=a[e]||function(){ 2 | (a[e].o=a[e].o||[]).push(arguments)},g=b.createElement(c),h=b.getElementsByTagName(c)[0], 3 | g.async=1,g.src=d,a.__raygunNoConflict=!!f,h.parentNode.insertBefore(g,h); 4 | }(window,document,"script","//cdn.raygun.io/raygun4js/raygun.min.js","rg4js"); -------------------------------------------------------------------------------- /src/snippet/unminified.fetchhandler.js: -------------------------------------------------------------------------------- 1 | (function(wind, doc, scriptTag, url, obj, noConflict, script, firstScriptElement, onErrorHandler, fetchObject) { 2 | wind['RaygunObject'] = obj; 3 | wind[obj] = wind[obj] || function() { 4 | (wind[obj].o = wind[obj].o || []).push(arguments) 5 | }, 6 | script = doc.createElement(scriptTag), 7 | firstScriptElement = doc.getElementsByTagName(scriptTag)[0]; 8 | script.async = 1; 9 | script.src = url; 10 | 11 | wind.__raygunNoConflict = !!noConflict; 12 | 13 | firstScriptElement.parentNode.insertBefore(script, firstScriptElement); 14 | 15 | onErrorHandler = wind.onerror; 16 | wind.onerror = function (msg, url, line, col, err) { 17 | if (onErrorHandler) { 18 | onErrorHandler(msg, url, line, col, err); 19 | } 20 | 21 | if (!err) { 22 | err = new Error(msg); 23 | } 24 | 25 | wind[obj].q = wind[obj].q || []; 26 | wind[obj].q.push({e: err}); 27 | }; 28 | 29 | fetchObject = wind.fetch; 30 | 31 | if(!!fetchObject) { 32 | wind.__raygunOriginalFetch = fetchObject; 33 | wind.fetch = function() { 34 | if(!!wind.__raygunFetchCallback) { 35 | return wind.__raygunFetchCallback.apply(null, arguments); 36 | } 37 | 38 | return fetchObject.apply(null, arguments); 39 | }; 40 | } 41 | 42 | })(window, document, 'script', '//cdn.raygun.io/raygun4js/raygun.min.js', 'rg4js'); 43 | -------------------------------------------------------------------------------- /src/snippet/unminified.js: -------------------------------------------------------------------------------- 1 | (function(wind, doc, scriptTag, url, obj, noConflict, s, n, o) { 2 | wind['RaygunObject'] = obj; 3 | wind[obj] = wind[obj] || function() { 4 | (wind[obj].o = wind[obj].o || []).push(arguments) 5 | }, 6 | s = doc.createElement(scriptTag), 7 | n = doc.getElementsByTagName(scriptTag)[0]; 8 | s.async = 1; 9 | s.src = url; 10 | 11 | wind.__raygunNoConflict = !!noConflict; 12 | 13 | n.parentNode.insertBefore(s, n); 14 | 15 | o = wind.onerror; 16 | wind.onerror = function (msg, url, line, col, err) { 17 | if (o) { 18 | o(msg, url, line, col, err); 19 | } 20 | 21 | if (!err) { 22 | err = new Error(msg); 23 | } 24 | 25 | wind[obj].q = wind[obj].q || []; 26 | wind[obj].q.push({e: err}); 27 | }; 28 | 29 | })(window, document, 'script', '//cdn.raygun.io/raygun4js/raygun.min.js', 'rg4js'); 30 | -------------------------------------------------------------------------------- /src/snippet/unminified.nohandler.js: -------------------------------------------------------------------------------- 1 | (function(wind, doc, scriptTag, url, obj, noConflict, s, n) { 2 | wind['RaygunObject'] = obj; 3 | wind[obj] = wind[obj] || function() { 4 | (wind[obj].o = wind[obj].o || []).push(arguments) 5 | }, 6 | s = doc.createElement(scriptTag), 7 | n = doc.getElementsByTagName(scriptTag)[0]; 8 | s.async = 1; 9 | s.src = url; 10 | 11 | wind.__raygunNoConflict = !!noConflict; 12 | 13 | n.parentNode.insertBefore(s, n); 14 | })(window, document, 'script', '//cdn.raygun.io/raygun4js/raygun.min.js', 'rg4js'); 15 | -------------------------------------------------------------------------------- /src/umd.intro.js: -------------------------------------------------------------------------------- 1 | // https://github.com/umdjs/umd/blob/master/templates/returnExportsGlobal.js 2 | 3 | (function(root, factory) { 4 | if (typeof define === 'function' && define.amd) { 5 | // AMD. Register as an anonymous module. 6 | define('raygun4js', function() { 7 | return (root.Raygun = factory()); 8 | }); 9 | } else if (typeof module === 'object' && module.exports) { 10 | // Node. Does not work with strict CommonJS, but 11 | // only CommonJS-like environments that support module.exports, 12 | // like Node. 13 | module.exports = factory(); 14 | } else { 15 | // Browser globals 16 | root.Raygun = factory(); 17 | } 18 | }(this, function() { 19 | 20 | var windw = this || window || global; 21 | var originalOnError = windw.onerror; 22 | windw.onerror = function(msg, url, line, col, err) { 23 | if (originalOnError) { 24 | originalOnError(msg, url, line, col, err); 25 | } 26 | 27 | if (!err) { 28 | err = new Error(msg); 29 | } 30 | 31 | windw['rg4js'].q = windw['rg4js'].q || []; 32 | windw['rg4js'].q.push({ e: err }); 33 | }; 34 | // Similar approach as the snippet, creates the rg4js proxy function, which is exported in umd.outro.js once the 35 | // script is executed, and later overwritten by the loader once it's finished 36 | (function(wind) { 37 | wind['RaygunObject'] = 'rg4js'; 38 | wind[wind['RaygunObject']] = wind[wind['RaygunObject']] || function() { 39 | if (wind && typeof wind['Raygun'] === 'undefined' || 40 | (typeof document === 'undefined' || document.readyState !== 'complete') || (!wind['RaygunInitialized'])) { 41 | // onload hasn't been called, cache the commands just like the snippet 42 | (wind[wind['RaygunObject']].o = wind[wind['RaygunObject']].o || []).push(arguments) 43 | } else { 44 | // onload has been called and provider has executed, call the executor proxy function 45 | return wind[wind['RaygunObject']](arguments[0], arguments[1]); 46 | } 47 | 48 | } 49 | })(windw); 50 | -------------------------------------------------------------------------------- /src/umd.outro.js: -------------------------------------------------------------------------------- 1 | 2 | return window.rg4js; 3 | })); -------------------------------------------------------------------------------- /src/useragent.js: -------------------------------------------------------------------------------- 1 | /* 2 | * raygun4js 3 | * https://github.com/MindscapeHQ/raygun4js 4 | * 5 | * Copyright (c) 2013-2024 Raygun Limited 6 | * Licensed under the MIT license. 7 | */ 8 | 9 | 10 | window.raygunUserAgent = navigator.userAgent; 11 | 12 | window.raygunUserAgentData = window.navigator.userAgentData || null; 13 | window.raygunUserAgentDataStatus = 1; // 1: Start, 2: high entropy success, 3: timed-out 14 | 15 | //Run this asap so that the High Entropy user agent data will be available, when we send data to the server 16 | (function () { 17 | setTimeout(function () { if (window.raygunUserAgentDataStatus === 1) { window.raygunUserAgentDataStatus = 3; } }, 200); 18 | 19 | if (!(window && window.navigator && window.navigator.userAgentData)) { return; } 20 | 21 | if (!!window.navigator.userAgentData.getHighEntropyValues) { 22 | var hints = ["platformVersion", "fullVersionList" /* ,"model" //We may want model (device) info in the future */]; 23 | 24 | window.navigator.userAgentData 25 | .getHighEntropyValues(hints) 26 | .then( 27 | function (highEntropyUserAgentData) { 28 | window.raygunUserAgentData = highEntropyUserAgentData; 29 | window.raygunUserAgent = getHighFidelityUAString(window.raygunUserAgent); 30 | 31 | window.raygunUserAgentDataStatus = 2; 32 | }, 33 | function (e) { 34 | window.console.error('Error calling getHighEntropyValues: ', e); 35 | }); 36 | } 37 | 38 | })(); 39 | 40 | 41 | function getHighFidelityUAString(userAgentString) { 42 | 43 | if (!window.raygunUserAgentData) { 44 | return userAgentString; 45 | } 46 | if (window.raygunUserAgentData.platform === "Windows") { 47 | var platformVersion = (window.raygunUserAgentData.platformVersion || '').split("."); 48 | var majorVersion = parseInt(platformVersion[0], 10) || 0; 49 | if (majorVersion >= 13) { 50 | userAgentString = userAgentString.replace('Windows NT 10.0', 'Windows NT 11.0'); 51 | } 52 | } 53 | var fullVersionList = window.raygunUserAgentData.fullVersionList; 54 | 55 | if (!fullVersionList) { 56 | return userAgentString; 57 | } 58 | 59 | var regexChrome = /Chrome\/(\d+)\.(\d+)\.(\d+)\.(\d+)/i; 60 | var regexEdge = /Edg\/(\d+)\.(\d+)\.(\d+)\.(\d+)/i; 61 | // var regexOpera = /OPR\/(\d+)\.(\d+)\.(\d+)\.(\d+)/i; // Not used below, yet??? 62 | 63 | for (var n = 0; n < fullVersionList.length; n++) { 64 | 65 | var version = fullVersionList[n].version; 66 | var brand = fullVersionList[n].brand; 67 | 68 | if (brand === "Chromium") { 69 | userAgentString = userAgentString.replace(regexChrome, 'Chrome\/' + version); 70 | } 71 | if (brand === "Microsoft Edge") { 72 | userAgentString = userAgentString.replace(regexEdge, 'Edg\/' + version); 73 | } 74 | //Opera (version 88) behaves differently, it correctly populates the UserAgent string; but not the high entropy UserAgentData. This may change in the future?! 75 | /* 76 | if (brand === "Opera") { 77 | userAgentString = userAgentString.replace(regexOpera, 'OPR\/' + version); 78 | } 79 | */ 80 | } 81 | 82 | return userAgentString; 83 | } -------------------------------------------------------------------------------- /tests/fixtures/breadcrumbs/automatic.console.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Raygun4JS with V2 API 5 | 6 | 13 | 14 | 15 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /tests/fixtures/breadcrumbs/automatic.element.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Raygun4JS with V2 API 5 | 6 | 13 | 14 | 15 | Click me 16 | 17 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /tests/fixtures/breadcrumbs/automatic.navigation.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Raygun4JS with V2 API 5 | 6 | 13 | 14 | 15 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /tests/fixtures/breadcrumbs/automatic.xhr.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Raygun4JS with V2 API 5 | 9 | 10 | 17 | 18 | 19 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /tests/fixtures/breadcrumbs/basic.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Raygun4JS with V2 API 5 | 6 | 13 | 14 | 15 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /tests/fixtures/breadcrumbs/basicWithObject.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Raygun4JS with V2 API 5 | 6 | 13 | 14 | 15 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /tests/fixtures/breadcrumbs/setBreadcrumbLevel.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Raygun4JS with V2 API 5 | 6 | 13 | 14 | 15 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /tests/fixtures/common/instrumentXHRs.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | window.__requestPayloads = []; 3 | window.__inFlightXHRs = []; 4 | window.__completedXHRs = []; 5 | window.__sentXHRs = []; 6 | 7 | var origOpen = XMLHttpRequest.prototype.open; 8 | var origSend = XMLHttpRequest.prototype.send; 9 | 10 | var origSetRequestHeader = XMLHttpRequest.prototype.setRequestHeader; 11 | 12 | XMLHttpRequest.prototype.setRequestHeader = function() { 13 | if(!this.__headers) { 14 | this.__headers = {}; 15 | } 16 | this.__headers[arguments[0]] = arguments[1]; 17 | 18 | origSetRequestHeader.apply(this, arguments); 19 | }; 20 | 21 | XMLHttpRequest.prototype.getRequestHeader = function() { 22 | if(!this.__headers) { 23 | this.__headers = {}; 24 | } 25 | 26 | var header = arguments[0]; 27 | return this.__headers[header] || null; 28 | }; 29 | 30 | XMLHttpRequest.prototype.open = function() { 31 | 32 | window.__inFlightXHRs.push({ 33 | xhr: this, 34 | orig: origOpen, 35 | method: arguments[0], 36 | url: arguments[1] 37 | }); 38 | 39 | this.addEventListener('load', function() { 40 | window.__completedXHRs.push(this); 41 | }); 42 | 43 | origOpen.apply(this, arguments); 44 | }; 45 | 46 | XMLHttpRequest.prototype.send = function() { 47 | if (arguments[0]) { 48 | var json = JSON.parse(arguments[0]); 49 | if (json && json.foo == undefined) 50 | window.__requestPayloads.push(json); 51 | } 52 | 53 | window.__sentXHRs.push({ 54 | xhr: this, 55 | clientIp: this.getRequestHeader('X-Remote-Address') 56 | }); 57 | 58 | origSend.apply(this, arguments); 59 | }; 60 | 61 | if (window.XDomainRequest) { 62 | var origXOpen = XDomainRequest.prototype.open; 63 | var origXSend = XDomainRequest.prototype.send; 64 | 65 | XDomainRequest.prototype.open = function() { 66 | 67 | window.__inFlightXHRs.push({ 68 | xhr: this, 69 | orig: origXOpen, 70 | method: arguments[0], 71 | // XDomainRequest doesn't support https so it gets trimmed off 72 | // Add it back in to be consistent with other tests 73 | url: 'https:' + arguments[1] 74 | }); 75 | 76 | this.onload = function() { 77 | window.__completedXHRs.push(this); 78 | }; 79 | 80 | origXOpen.apply(this, arguments); 81 | }; 82 | 83 | XDomainRequest.prototype.send = function() { 84 | window.__requestPayloads.push(JSON.parse(arguments[0])); 85 | 86 | origXSend.apply(this, arguments); 87 | }; 88 | } 89 | })() 90 | -------------------------------------------------------------------------------- /tests/fixtures/sessions/crWithUser.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Raygun4JS with User 5 | 12 | 13 | 14 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /tests/fixtures/sessions/crWithoutUser.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Raygun4JS with Anonymous user 5 | 12 | 13 | 14 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /tests/fixtures/sessions/rumSession.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Raygun4JS with V2 API 5 | 12 | 13 | 14 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /tests/fixtures/v1/basic.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Raygun4JS with V1 API 5 | 6 | 7 | 8 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /tests/fixtures/v1/manualSend.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Raygun4JS with V1 API 5 | 6 | 7 | 8 | 9 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /tests/fixtures/v1/manualSendCustomData.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Raygun4JS with V1 API 5 | 6 | 7 | 8 | 9 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /tests/fixtures/v1/manualSendNoApiKey.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Raygun4JS with V1 API 5 | 6 | 7 | 8 | 9 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /tests/fixtures/v1/manualSendTag.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Raygun4JS with V1 API 5 | 6 | 7 | 8 | 9 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /tests/fixtures/v1/manualSendUser.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Raygun4JS with V1 API 5 | 6 | 7 | 8 | 9 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /tests/fixtures/v1/manualSendVersion.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Raygun4JS with V1 API 5 | 6 | 7 | 8 | 9 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /tests/fixtures/v1/trackEvent.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Raygun4JS with V1 API 5 | 6 | 7 | 8 | 9 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /tests/fixtures/v1/unhandledError.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Raygun4JS with V1 API 5 | 6 | 7 | 8 | 9 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /tests/fixtures/v1/unhandledErrorCustomData.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Raygun4JS with V1 API 5 | 6 | 7 | 8 | 9 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /tests/fixtures/v1/unhandledErrorNoApiKey.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Raygun4JS with V1 API 5 | 6 | 7 | 8 | 9 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /tests/fixtures/v1/unhandledErrorNoAttach.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Raygun4JS with V1 API 5 | 6 | 7 | 8 | 9 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /tests/fixtures/v1/unhandledErrorTag.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Raygun4JS with V1 API 5 | 6 | 7 | 8 | 9 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /tests/fixtures/v1/unhandledErrorUser.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Raygun4JS with V1 API 5 | 6 | 7 | 8 | 9 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /tests/fixtures/v1/unhandledErrorVersion.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Raygun4JS with V1 API 5 | 6 | 7 | 8 | 9 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /tests/fixtures/v2/UMDInfiniteLoop.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Raygun4JS with V2 API 6 | 7 | 16 | 17 | 18 | 19 | 20 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /tests/fixtures/v2/basic.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Raygun4JS with V2 API 5 | 12 | 13 | 14 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /tests/fixtures/v2/customTiming.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Raygun4JS with V2 API 5 | 6 | 13 | 14 | 15 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /tests/fixtures/v2/endSession.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Raygun4JS with V2 API 5 | 6 | 13 | 14 | 15 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /tests/fixtures/v2/legacyCustomTiming.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Raygun4JS with V2 API 5 | 6 | 13 | 14 | 15 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /tests/fixtures/v2/manualSend.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Raygun4JS with V2 API 5 | 6 | 13 | 14 | 15 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /tests/fixtures/v2/manualSend3rdParty.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Raygun4JS with V2 API 5 | 6 | 13 | 14 | 15 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /tests/fixtures/v2/manualSendCustomData.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Raygun4JS with V2 API 5 | 6 | 13 | 14 | 15 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /tests/fixtures/v2/manualSendErrorAsString.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Raygun4JS with V2 API 5 | 6 | 13 | 14 | 15 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /tests/fixtures/v2/manualSendNoApiKey.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Raygun4JS with V2 API 5 | 6 | 13 | 14 | 15 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /tests/fixtures/v2/manualSendTag.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Raygun4JS with V2 API 5 | 6 | 13 | 14 | 15 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /tests/fixtures/v2/manualSendUser.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Raygun4JS with V2 API 5 | 6 | 13 | 14 | 15 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /tests/fixtures/v2/manualSendVersion.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Raygun4JS with V2 API 5 | 6 | 13 | 14 | 15 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /tests/fixtures/v2/onBeforeSendRUM.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Raygun4JS with V2 API 5 | 6 | 13 | 14 | 15 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /tests/fixtures/v2/requestId.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Raygun4JS with V2 API 5 | 6 | 13 | 14 | 15 | 20 | 21 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /tests/fixtures/v2/rg4jsAfterLibraryLoaded.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Raygun4JS with V2 API 5 | 6 | 7 | 42 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /tests/fixtures/v2/rg4jsBeforeLibraryLoaded.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Raygun4JS with V2 API 5 | 6 | 7 | 42 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /tests/fixtures/v2/rumFetchPolyfillStatusCodes.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Raygun4JS with V2 API 6 | 7 | 8 | 9 | 16 | 17 | 18 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /tests/fixtures/v2/rumFetchStatusCodes.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Raygun4JS with V2 API 6 | 7 | 14 | 15 | 16 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /tests/fixtures/v2/rumReferencedFetchWithFetchSnippet.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Raygun4JS with V2 API 6 | 7 | 70 | 71 | 72 | 73 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /tests/fixtures/v2/rumXhrStatusCodes.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Raygun4JS with V2 API 6 | 10 | 11 | 18 | 19 | 20 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /tests/fixtures/v2/syntaxErrorSnippet.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Raygun4JS with V2 API 5 | 6 | 13 | 24 | 25 | 26 | 27 | 28 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /tests/fixtures/v2/trackEvent.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Raygun4JS with V2 API 5 | 6 | 13 | 14 | 15 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /tests/fixtures/v2/unhandledError.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Raygun4JS with V2 API 5 | 6 | 13 | 14 | 15 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /tests/fixtures/v2/unhandledErrorCustomData.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Raygun4JS with V2 API 5 | 6 | 13 | 14 | 15 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /tests/fixtures/v2/unhandledErrorNoApiKey.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Raygun4JS with V2 API 5 | 6 | 13 | 14 | 15 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /tests/fixtures/v2/unhandledErrorNoAttach.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Raygun4JS with V2 API 5 | 6 | 13 | 14 | 15 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /tests/fixtures/v2/unhandledErrorTag.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Raygun4JS with V2 API 5 | 6 | 13 | 14 | 15 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /tests/fixtures/v2/unhandledErrorTagWithString.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Raygun4JS with V2 API 5 | 6 | 13 | 14 | 15 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /tests/fixtures/v2/unhandledErrorUser.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Raygun4JS with V2 API 5 | 6 | 13 | 14 | 15 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /tests/fixtures/v2/unhandledErrorVersion.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Raygun4JS with V2 API 5 | 6 | 13 | 14 | 15 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /tests/fixtures/v2/unhandledErrorWithUmdBuild.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Raygun4JS with V2 API 5 | 6 | 13 | 14 | 15 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /tests/fixtures/v2/unhandledPromiseRejection.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Raygun4JS with V2 API 5 | 6 | 13 | 14 | 15 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /tests/fixtures/v2/unhandledPromiseRejectionWithNoReason.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Raygun4JS with V2 API 5 | 6 | 13 | 14 | 15 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /tests/fixtures/v2/withClientIpSet.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Raygun4JS with Client IP 6 | 7 | 14 | 15 | 16 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /tests/fixtures/v2/withoutClientIpSet.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Raygun4JS without Client IP 6 | 7 | 14 | 15 | 16 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /tests/specs/breadcrumbs/automatic.console.js: -------------------------------------------------------------------------------- 1 | /* globals describe, beforeEach, it, expect, browser */ 2 | 3 | var common = require("../common"); 4 | 5 | describe("Console logging", () => { 6 | beforeEach(async function () { 7 | await browser.url("http://localhost:4567/fixtures/breadcrumbs/automatic.console.html"); 8 | await browser.pause(4000); 9 | }); 10 | 11 | // Don't work in IE9 for some reason 12 | // console object gets correctly enhanced ¯\_(ツ)_/¯ 13 | if (!( common.isOldIE())) { 14 | it("records log message", async function () { 15 | var breadcrumb = (await common.getBreadcrumbs())[0]; 16 | 17 | expect(breadcrumb.type).toBe("console"); 18 | expect(breadcrumb.message).toBe("log"); 19 | }); 20 | 21 | it("records warn message", async function () { 22 | var breadcrumb = (await common.getBreadcrumbs())[1]; 23 | 24 | expect(breadcrumb.type).toBe("console"); 25 | expect(breadcrumb.message).toBe("warn"); 26 | }); 27 | 28 | it("records error message", async function () { 29 | var breadcrumb = (await common.getBreadcrumbs())[2]; 30 | 31 | expect(breadcrumb.type).toBe("console"); 32 | expect(breadcrumb.message).toBe("error"); 33 | }); 34 | } 35 | }); 36 | -------------------------------------------------------------------------------- /tests/specs/breadcrumbs/automatic.element.js: -------------------------------------------------------------------------------- 1 | /* globals describe, beforeEach, it, expect, browser */ 2 | 3 | var common = require("../common"); 4 | 5 | describe("Element clicking", function() { 6 | beforeEach(async function() { 7 | await browser.url("http://localhost:4567/fixtures/breadcrumbs/automatic.element.html"); 8 | await browser.pause(4000); 9 | }); 10 | 11 | it("logs the element that was clicked", async function() { 12 | var breadcrumb = (await common.getBreadcrumbs())[0]; 13 | 14 | expect(breadcrumb.type).toBe("click-event"); 15 | expect(breadcrumb.message).toBe("UI Click"); 16 | }); 17 | 18 | it("logs the element selector that was clicked", async function() { 19 | var breadcrumb = (await common.getBreadcrumbs())[0]; 20 | 21 | expect(await breadcrumb.CustomData.selector).toBe("A#foo"); 22 | }); 23 | 24 | it("logs the element text that was clicked", async function() { 25 | var breadcrumb = (await common.getBreadcrumbs())[0]; 26 | 27 | expect(breadcrumb.CustomData.text).toBe("Click me"); 28 | }); 29 | }); 30 | -------------------------------------------------------------------------------- /tests/specs/breadcrumbs/automatic.navigation.js: -------------------------------------------------------------------------------- 1 | /* globals describe, beforeEach, it, expect, browser */ 2 | 3 | var _ = require('underscore'); 4 | var common = require("../common"); 5 | 6 | async function breadcrumbExists(type, message) { 7 | return _.any(await common.getBreadcrumbs(), function(breadcrumb) { 8 | return breadcrumb.type === type && breadcrumb.message.indexOf(message) !== -1; 9 | }); 10 | } 11 | 12 | describe("Navigation events", function() { 13 | beforeEach(async function() { 14 | await browser.url("http://localhost:4567/fixtures/breadcrumbs/automatic.navigation.html"); 15 | await browser.pause(1000); 16 | }); 17 | 18 | it("records a page load breadcrumb first", async function() { 19 | 20 | var payload = await common.sentPayloads(); 21 | var breadcrumb = _.first(payload[0].Details.Breadcrumbs); 22 | 23 | if (!common.isOldIE()) { 24 | expect(breadcrumb.type).toBe("navigation"); 25 | expect(breadcrumb.message).toBe("Page loaded"); 26 | } 27 | }); 28 | 29 | it("records pageShown events", async function() { 30 | if (!common.isOldIE() && !common.isIEVersion('10')) { 31 | expect(await breadcrumbExists("navigation", "Page shown")).toBe(true); 32 | } 33 | }); 34 | 35 | it("records replaceState events", async function() { 36 | if (!common.isOldIE()) { 37 | expect(await breadcrumbExists("navigation", "replaceState")).toBe(true); 38 | } 39 | }); 40 | 41 | it("records pushState events", async function() { 42 | if (!common.isOldIE()) { 43 | expect(await breadcrumbExists("navigation", "pushState")).toBe(true); 44 | } 45 | }); 46 | 47 | it("records popState events", async function() { 48 | if (!common.isOldIE()) { 49 | expect(await breadcrumbExists("navigation", "Navigated back")).toBe(true); 50 | } 51 | }); 52 | 53 | it("records hashChange events", async function() { 54 | // Despite hashchange event being supported by ie9 and tested working 55 | // the test doesn't pick up the hash change event 56 | if (!common.isOldIE()) { 57 | expect(await breadcrumbExists("navigation", "Hash change")).toBe(true); 58 | } 59 | }); 60 | }); 61 | -------------------------------------------------------------------------------- /tests/specs/breadcrumbs/automatic.xhr.js: -------------------------------------------------------------------------------- 1 | /* globals describe, beforeEach, it, expect, browser, window */ 2 | 3 | var _ = require('underscore'); 4 | var common = require("../common"); 5 | 6 | describe("XHR tracking", function() { 7 | beforeEach(async function() { 8 | await browser.url("http://localhost:4567/fixtures/breadcrumbs/automatic.xhr.html"); 9 | await browser.pause(2000); 10 | }); 11 | 12 | it("tracks XHR start and end events", async function() { 13 | var breadcrumbs = await common.getBreadcrumbs(true); 14 | 15 | expect(breadcrumbs[0].type).toBe("request"); 16 | expect(breadcrumbs[0].message).toContain("Opening request"); 17 | 18 | expect(breadcrumbs[1].type).toBe("request"); 19 | expect(breadcrumbs[1].message).toContain("Finished request"); 20 | }); 21 | 22 | it("works when the responseType is non text", async function() { 23 | var breadcrumbs = await common.getBreadcrumbs(true); 24 | 25 | expect(breadcrumbs[2].type).toBe("request"); 26 | expect(breadcrumbs[2].message).toContain("Opening request"); 27 | 28 | expect(breadcrumbs[4].type).toBe("request"); 29 | expect(breadcrumbs[4].message).toContain("Finished request"); 30 | }); 31 | 32 | it("records the correct message with the URL", async function() { 33 | var breadcrumbs = await common.getBreadcrumbs(true); 34 | 35 | expect(breadcrumbs[0].message).toBe("Opening request to http://localhost:4567/fixtures/breadcrumbs/automatic.console.html"); 36 | }); 37 | 38 | it("records the correct requestURL", async function() { 39 | var breadcrumbs = await common.getBreadcrumbs(true); 40 | 41 | expect(breadcrumbs[0].CustomData.requestURL).toBe("http://localhost:4567/fixtures/breadcrumbs/automatic.console.html"); 42 | }); 43 | 44 | it("records the correct requestURL for absolute paths", async function() { 45 | var breadcrumbs = await common.getBreadcrumbs(true); 46 | 47 | expect(breadcrumbs[3].CustomData.requestURL).toBe("http://localhost:4567/fixtures/breadcrumbs/automatic.xhr.html"); 48 | }); 49 | 50 | it('does not record requests to raygun domains', async function() { 51 | var breadcrumbs = await common.getBreadcrumbs(true); 52 | 53 | var doesNotContainRaygun = _.every(breadcrumbs, function (crumb) { 54 | return crumb.CustomData.requestURL.indexOf('raygun') === -1 55 | }); 56 | 57 | expect(doesNotContainRaygun).toBe(true); 58 | }); 59 | 60 | it("does not log bodies when logXhrContents is false", async function() { 61 | var breadcrumbs = await browser.execute(function() { 62 | window.rg4js('logContentsOfXhrCalls', true); 63 | 64 | return window.rg4js('getRaygunInstance').getBreadcrumbs(); 65 | }); 66 | 67 | expect(breadcrumbs[1].CustomData.body).toContain("Disabled"); 68 | }); 69 | }); 70 | -------------------------------------------------------------------------------- /tests/specs/breadcrumbs/basic.js: -------------------------------------------------------------------------------- 1 | /* globals describe, beforeEach, it, expect, browser */ 2 | 3 | var _ = require('underscore'); 4 | var common = require("../common"); 5 | 6 | describe("Recording a basic breadcrumb", function() { 7 | beforeEach(async function() { 8 | await browser.url("http://localhost:4567/fixtures/breadcrumbs/basic.html"); 9 | await browser.pause(2000); 10 | }); 11 | 12 | it("adds the breadcrumb to the payload", async function() { 13 | var sentPayloads = await common.sentPayloads(); 14 | 15 | expect(_.any(sentPayloads, function (payload) { 16 | return !!payload.Details.Breadcrumbs; 17 | })).toBe(true); 18 | }); 19 | 20 | it("has the correct message", async function() { 21 | expect((await common.firstBreadcrumb()).message).toBe("a message"); 22 | }); 23 | 24 | it("can take metadata", async function() { 25 | expect((await common.firstBreadcrumb()).CustomData).toEqual({ customData: true }); 26 | }); 27 | }); 28 | 29 | describe("Recording a basic breadcrumb with an object", function() { 30 | beforeEach(async function() { 31 | await browser.url("http://localhost:4567/fixtures/breadcrumbs/basicWithObject.html"); 32 | await browser.pause(4000); 33 | }); 34 | 35 | it("merges the passed object with the default crumb", async function() { 36 | var breadcrumb = await common.firstBreadcrumb(); 37 | 38 | var keys = Object.keys(breadcrumb).sort(); 39 | var expectedKeys = [ 40 | 'level', 41 | 'timestamp', 42 | 'message', 43 | 'CustomData', 44 | 'category', 45 | 'type' 46 | ].sort(); 47 | 48 | await expect(keys).toEqual(expectedKeys); 49 | }); 50 | }); 51 | -------------------------------------------------------------------------------- /tests/specs/breadcrumbs/setBreadcrumbLevel.js: -------------------------------------------------------------------------------- 1 | /* globals describe, beforeEach, it, expect, browser */ 2 | 3 | var common = require("../common"); 4 | 5 | describe("Changing breadcrumbLevel", function() { 6 | beforeEach(async function() { 7 | await browser.url("http://localhost:4567/fixtures/breadcrumbs/setBreadcrumbLevel.html"); 8 | await browser.pause(8000); 9 | }); 10 | 11 | it("Only records the right breadcrumbs for the current level", async function() { 12 | var breadcrumbs = await common.getBreadcrumbs(); 13 | 14 | expect(breadcrumbs[0].level).toBe("debug"); 15 | expect(breadcrumbs[0].message).toContain("foo"); 16 | 17 | expect(breadcrumbs[1].level).toBe("error"); 18 | expect(breadcrumbs[1].message).toContain("foo"); 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /tests/specs/common.js: -------------------------------------------------------------------------------- 1 | /* globals browser, window */ 2 | var _ = require('underscore'); 3 | 4 | module.exports = { 5 | isIEVersion: function(version) { 6 | return browser.capabilities.browserName === 'internet explorer' && 7 | browser.capabilities.browserVersion === version; 8 | }, 9 | isOldIE: function() { 10 | return (this.isIEVersion('9')) || (this.isIEVersion('10')); 11 | }, 12 | inFlightXHRs: function() { 13 | return browser.execute(function() { 14 | return window.__inFlightXHRs; 15 | }); 16 | }, 17 | sentPayloads: function() { 18 | return browser.execute(function() { 19 | return window.__requestPayloads; 20 | }); 21 | }, 22 | getBreadcrumbs: async function(pulseEnabled = false) { 23 | var crumbs = await browser.execute(function(pulseEnabled) { 24 | return pulseEnabled ? 25 | window.__requestPayloads[(window.__requestPayloads.length - 1)].Details.Breadcrumbs : 26 | window.__requestPayloads[0].Details.Breadcrumbs ; 27 | }, pulseEnabled); 28 | 29 | return crumbs; 30 | }, 31 | firstBreadcrumb: async function() { 32 | return (await this.getBreadcrumbs())[0]; 33 | }, 34 | getLocalStorageValue: async function(key) { 35 | return await browser.execute(function (name) { 36 | return localStorage.getItem(name); 37 | }, key); 38 | }, 39 | getSessionStorageValue: function(key) { 40 | return browser.execute(function (name) { 41 | return localStorage.getItem(name); 42 | }, key); 43 | }, 44 | setCookieValue: async function(key, value) { 45 | await browser.execute(function(cookieName, cookieValue) { 46 | document.cookie = cookieName + '=' + cookieValue + '; path=/'; 47 | }, key, value); 48 | }, 49 | getCookieValue: async function(key) { 50 | var cookieResult = await browser.execute(function(name) { 51 | var nameEQ = name + '='; 52 | var ca = document.cookie.split(';'); 53 | for (var i = 0; i < ca.length; i++) { 54 | var c = ca[i]; 55 | while (c.charAt(0) === ' ') { 56 | c = c.substring(1, c.length); 57 | } 58 | if (c.indexOf(nameEQ) === 0) { 59 | return c.substring(nameEQ.length, c.length); 60 | } 61 | } 62 | return null; 63 | }, key); 64 | 65 | return cookieResult; 66 | } 67 | }; 68 | -------------------------------------------------------------------------------- /tests/specs/sessions/crashReporting.js: -------------------------------------------------------------------------------- 1 | var common = require("../common"); 2 | 3 | describe("Crash Reporting Anonymous User Tracking", function() { 4 | afterEach(async function() { 5 | await browser.execute(function() { 6 | localStorage.clear(); 7 | }); 8 | }); 9 | 10 | describe("with anonymous user tracking", function() { 11 | it("creates a user id and persists it in localStorage when one doesn't exist", async function() { 12 | await browser.url("http://localhost:4567/fixtures/sessions/crWithoutUser.html"); 13 | 14 | var result = await common.getLocalStorageValue("raygun4js-userid"); 15 | 16 | expect(result !== null).toBeTruthy(); 17 | }); 18 | 19 | it("retrieves user id from localStorage when one exists", async function() { 20 | await browser.execute(function() { 21 | localStorage.setItem('raygun4js-userid', 'abc123'); 22 | }); 23 | 24 | await browser.url("http://localhost:4567/fixtures/sessions/crWithoutUser.html"); 25 | 26 | var result = await common.getLocalStorageValue("raygun4js-userid"); 27 | expect(result).toBe('abc123'); 28 | }); 29 | 30 | it("retrieves user id from a cookie and sets it in localStorage", async function() { 31 | await common.setCookieValue('raygun4js-userid', 'xyz789'); 32 | 33 | await browser.url("http://localhost:4567/fixtures/sessions/crWithoutUser.html"); 34 | 35 | var result = await common.getLocalStorageValue("raygun4js-userid"); 36 | 37 | expect(result).toBe('xyz789'); 38 | expect(await common.getCookieValue('raygun4js-userid')).toBeFalsy(); 39 | }); 40 | }); 41 | 42 | describe("with a user set", function() { 43 | it("doesn't set a user id", async function() { 44 | await browser.url("http://localhost:4567/fixtures/sessions/crWithUser.html"); 45 | 46 | var result = await common.getLocalStorageValue("raygun4js-userid"); 47 | expect(result).toBe(null); 48 | }); 49 | }); 50 | }); -------------------------------------------------------------------------------- /tests/specs/sessions/realUserMonitoring.js: -------------------------------------------------------------------------------- 1 | var common = require("../common"); 2 | 3 | describe("RUM Session Tracking", function() { 4 | 5 | // Tests 6 | 7 | // rum 8 | // - persists session id into storage when it doesn't exist 9 | // - retrieves session id from storage when it exists 10 | // - creates new session id if it has expired 11 | // - retrieved session id timestamp is updated when it already exists 12 | // retrieves identifier from cookie and sets it in sessionStorage 13 | 14 | afterEach(async function() { 15 | await browser.execute(function() { 16 | localStorage.clear(); 17 | }); 18 | }); 19 | 20 | it("persists a session id into storage when one doesn't exist", async function() { 21 | await browser.url("http://localhost:4567/fixtures/sessions/rumSession.html"); 22 | 23 | var result = await common.getLocalStorageValue("raygun4js-sid"); 24 | expect(result !== null).toBeTrue(); 25 | }); 26 | 27 | it("uses the session id and updates the timestamp when it is found and is less than 30 minutes old", async function() { 28 | var oneMinuteAgoTimestamp = new Date(new Date() - 60000).toISOString(); 29 | 30 | await browser.execute(function(timestamp) { 31 | var sessionValue = 'abc123'; 32 | 33 | var sessionString = 'id|' + sessionValue + '×tamp|' + timestamp; 34 | localStorage.setItem('raygun4js-sid', sessionString); 35 | }, oneMinuteAgoTimestamp); 36 | 37 | await browser.url("http://localhost:4567/fixtures/sessions/rumSession.html"); 38 | 39 | var result = await common.getLocalStorageValue("raygun4js-sid") 40 | 41 | const set = result.split(/[|&]/); 42 | 43 | expect(set.length).toBe(4); 44 | expect(set[0]).toBe('id'); 45 | expect(set[1]).toBe('abc123'); 46 | expect(set[2]).toBe('timestamp'); 47 | 48 | var newTimestampIsGreater = new Date(set[3]) > new Date(oneMinuteAgoTimestamp); 49 | expect(newTimestampIsGreater).toBe(true); 50 | }); 51 | 52 | it("retrieves session id from a cookie and sets it in localStorage", async function() { 53 | var sessionValue = 'cookieId'; 54 | var timestamp = new Date(new Date() - 60000).toISOString(); 55 | var cookieValue = 'id|' + sessionValue + '×tamp|' + timestamp; 56 | 57 | await common.setCookieValue('raygun4js-sid', cookieValue); 58 | 59 | await browser.url("http://localhost:4567/fixtures/sessions/rumSession.html"); 60 | 61 | var result = await common.getLocalStorageValue("raygun4js-sid") 62 | 63 | const set = result.split(/[|&]/); 64 | 65 | expect(set.length).toBe(4); 66 | expect(set[0]).toBe('id'); 67 | expect(set[1]).toBe('cookieId'); 68 | 69 | expect(await common.getCookieValue('raygun4js-sid')).toBeFalsy(); 70 | }); 71 | 72 | describe('creates a new session id', function() { 73 | it("creates a new session id if the session id is older than 30 minutes", async function() { 74 | await browser.execute(function() { 75 | var sessionValue = 'expiredId'; 76 | var oneHourAgoTimestamp = new Date(new Date() - 60 * 60000).toISOString(); 77 | var sessionString = 'id|' + sessionValue + '×tamp|' + oneHourAgoTimestamp; 78 | localStorage.setItem('raygun4js-sid', sessionString); 79 | }); 80 | 81 | await browser.url("http://localhost:4567/fixtures/sessions/rumSession.html"); 82 | 83 | var result = await common.getLocalStorageValue("raygun4js-sid") 84 | 85 | expect(result.indexOf('id|expiredId')).toBe(-1); 86 | expect(result.split('&')[0] !== 'id|expiredId').toBeTrue(); 87 | }); 88 | 89 | it("creates a new session id if value stored is null", async function() { 90 | await browser.execute(function() { 91 | localStorage.setItem('raygun4js-sid', null); 92 | }); 93 | 94 | await browser.url("http://localhost:4567/fixtures/sessions/rumSession.html"); 95 | 96 | var result = await common.getLocalStorageValue("raygun4js-sid") 97 | expect(result !== null).toBeTrue(); 98 | }); 99 | }); 100 | }); -------------------------------------------------------------------------------- /tests/specs/v1/basic.js: -------------------------------------------------------------------------------- 1 | var webdriverio = require('webdriverio'); 2 | 3 | describe("Basic Raygun4JS V1 API tests", function() { 4 | 5 | // Setup 6 | 7 | beforeEach(async function() { 8 | await browser.url('http://localhost:4567/fixtures/v1/basic.html'); 9 | }); 10 | 11 | // Tests 12 | 13 | it('has global Raygun object present', async function () { 14 | var result = await browser.execute(function () { 15 | return typeof Raygun; 16 | }); 17 | 18 | expect(result).toBe('object'); 19 | }); 20 | 21 | it('has CR sending function', async function () { 22 | var result = await browser.execute(function () { 23 | return typeof Raygun.send === 'function'; 24 | }); 25 | 26 | expect(result).toBe(true); 27 | }); 28 | 29 | it('has RUM trackEvent function', async function () { 30 | var result = await browser.execute(function () { 31 | return typeof Raygun.trackEvent === 'function'; 32 | }); 33 | 34 | expect(result).toBe(true); 35 | }); 36 | }); 37 | -------------------------------------------------------------------------------- /tests/specs/v1/eventsXhr.js: -------------------------------------------------------------------------------- 1 | /* globals describe, it, expect, browser, window */ 2 | var _ = require('underscore'); 3 | var common = require('../common'); 4 | 5 | var _eventsEndpoint = 'https://api.raygun.io/events'; 6 | 7 | describe("XHR functional tests for /events with V1", function() { 8 | 9 | // Tests 10 | 11 | it("performs an XHR to /events when Raygun.trackEvent() is called", async function () { 12 | await browser.url('http://localhost:4567/fixtures/v1/trackEvent.html'); 13 | 14 | await browser.pause(6000); 15 | 16 | var inFlightXhrs = await browser.execute(function () { 17 | return window.__inFlightXHRs; 18 | }); 19 | 20 | var didPerformRequest = _.any(inFlightXhrs, function (req) { 21 | return req.url.indexOf(_eventsEndpoint) === 0; 22 | }); 23 | 24 | if (!(await common.isIEVersion('8'))) { 25 | await expect(didPerformRequest).toBe(true); 26 | } 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /tests/specs/v1/payloadManualSendTests.js: -------------------------------------------------------------------------------- 1 | /* globals describe, beforeEach, it, expect, browser, window */ 2 | 3 | var _ = require('underscore'); 4 | var common = require('../common'); 5 | 6 | var _entriesEndpoint = 'https://api.raygun.io/entries'; 7 | 8 | describe("Payload functional validation tests for V1 manual send", function() { 9 | 10 | it("performs an XHR to /entries when Raygun.send() is called", async function () { 11 | await browser.url('http://localhost:4567/fixtures/v1/manualSend.html'); 12 | 13 | await browser.pause(6000); 14 | 15 | var inFlightXhrs = await browser.execute(function () { 16 | return window.__inFlightXHRs; 17 | }); 18 | 19 | var didPerformRequest = _.any(inFlightXhrs, function (req) { 20 | return req.url.indexOf(_entriesEndpoint) === 0; 21 | }); 22 | 23 | expect(didPerformRequest).toBeTrue(); 24 | }); 25 | 26 | it("doesn't performs an XHR to /entries when the API key isn't set",async function () { 27 | await browser.url('http://localhost:4567/fixtures/v1/manualSendNoApiKey.html'); 28 | 29 | await browser.pause(4000); 30 | 31 | var inFlightXhrs = browser.execute(function () { 32 | return window.__inFlightXHRs; 33 | }); 34 | 35 | var didPerformRequest = _.any(inFlightXhrs, function (req) { 36 | return req.url.indexOf(_entriesEndpoint) === 0; 37 | }); 38 | 39 | expect(didPerformRequest).toBeFalse(); 40 | }); 41 | 42 | it("has the error message in the payload set", async function () { 43 | await browser.url('http://localhost:4567/fixtures/v1/manualSend.html'); 44 | 45 | await browser.pause(1000); 46 | 47 | var requestPayloads = await browser.execute(function () { 48 | return window.__requestPayloads; 49 | }); 50 | 51 | var passes = _.any(requestPayloads, function (payload) { 52 | return payload.Details.Error.Message === 'Manual send' || payload.Details.Error.Message === 'Script error'; 53 | }); 54 | 55 | expect(passes).toBeTrue(); 56 | }); 57 | 58 | it("has the classname in the payload set",async function () { 59 | await browser.url('http://localhost:4567/fixtures/v1/manualSend.html'); 60 | 61 | await browser.pause(4000); 62 | 63 | var requestPayloads = await browser.execute(function () { 64 | return window.__requestPayloads; 65 | }); 66 | 67 | var passes = _.any(requestPayloads, function (payload) { 68 | return payload.Details.Error.ClassName === 'Error'; 69 | }); 70 | 71 | if (!( common.isOldIE())) { 72 | expect(passes).toBeTrue(); 73 | } 74 | }); 75 | 76 | it("has the filename in the stacktrace payload set",async function () { 77 | var pageUrl = 'http://localhost:4567/fixtures/v1/manualSend.html'; 78 | 79 | await browser.url(pageUrl); 80 | 81 | await browser.pause(4000); 82 | 83 | var requestPayloads = await browser.execute(function () { 84 | return window.__requestPayloads; 85 | }); 86 | 87 | var passes = _.any(requestPayloads, function (payload) { 88 | var stackTrace = payload.Details.Error.StackTrace[0]; 89 | return stackTrace && stackTrace.FileName === pageUrl; 90 | }); 91 | 92 | if (!( common.isOldIE())) { 93 | expect(passes).toBeTrue(); 94 | } 95 | }); 96 | 97 | it("has tags in the payload when tags are passed in",async function () { 98 | var pageUrl = 'http://localhost:4567/fixtures/v1/manualSendTag.html'; 99 | 100 | await browser.url(pageUrl); 101 | 102 | await browser.pause(4000); 103 | 104 | var requestPayloads = await browser.execute(function () { 105 | return window.__requestPayloads; 106 | }); 107 | 108 | var passes = _.any(requestPayloads, function (payload) { 109 | return payload.Details.Tags[0] === 'my_tag'; 110 | }); 111 | 112 | expect(passes).toBeTrue(); 113 | }); 114 | 115 | it("has custom data in the payload when custom data is passed in",async function () { 116 | var pageUrl = 'http://localhost:4567/fixtures/v1/manualSendCustomData.html'; 117 | 118 | await browser.url(pageUrl); 119 | 120 | await browser.pause(4000); 121 | 122 | var requestPayloads = await browser.execute(function () { 123 | return window.__requestPayloads; 124 | }); 125 | 126 | var passes = _.any(requestPayloads, function (payload) { 127 | return payload.Details.UserCustomData.myCustomKey === 'myCustomValue'; 128 | }); 129 | 130 | expect(passes).toBeTrue(); 131 | }); 132 | 133 | it("has correct user payload when Raygun.setUser() is called", async function () { 134 | var pageUrl = 'http://localhost:4567/fixtures/v1/manualSendUser.html'; 135 | 136 | await browser.url(pageUrl); 137 | 138 | await browser.pause(4000); 139 | 140 | var requestPayloads = await browser.execute(function () { 141 | return window.__requestPayloads; 142 | }); 143 | 144 | var passes = _.any(requestPayloads, function (payload) { 145 | return payload.Details.User.Identifier === 'user_email_address@localhost.local' && 146 | payload.Details.User.IsAnonymous === false && 147 | payload.Details.User.FirstName === 'Foo' && 148 | payload.Details.User.FullName === 'Foo Bar' && 149 | payload.Details.User.UUID === 'BAE62917-ACE8-ab3D-9287-B6A33B8E8C55'; 150 | }); 151 | 152 | expect(passes).toBeTrue(); 153 | }); 154 | 155 | it("has correct version in payload when Raygun.setVersion() is called",async function () { 156 | var pageUrl = 'http://localhost:4567/fixtures/v1/manualSendVersion.html'; 157 | 158 | await browser.url(pageUrl); 159 | 160 | await browser.pause(4000); 161 | 162 | var requestPayloads = await browser.execute(function () { 163 | return window.__requestPayloads; 164 | }); 165 | 166 | var passes = _.any(requestPayloads, function (payload) { 167 | return payload.Details.Version === '1.0.0.0'; 168 | }); 169 | 170 | expect(passes).toBeTrue(); 171 | }); 172 | 173 | }); 174 | 175 | -------------------------------------------------------------------------------- /tests/specs/v1/payloadUnhandledErrorTests.js: -------------------------------------------------------------------------------- 1 | var webdriverio = require('webdriverio'); 2 | var _ = require('underscore'); 3 | 4 | var _entriesEndpoint = 'https://api.raygun.io/entries'; 5 | 6 | describe("Payload functional validation tests for V1 automatic unhandled error sending", function() { 7 | 8 | it("performs an XHR to /entries when Raygun.send() is called", async function () { 9 | await browser.url('http://localhost:4567/fixtures/v1/unhandledError.html'); 10 | 11 | await browser.pause(6000); 12 | 13 | var inFlightXhrs = await browser.execute(function () { 14 | return window.__inFlightXHRs; 15 | }); 16 | 17 | var didPerformRequest = _.any(inFlightXhrs, function (req) { 18 | return req.url.indexOf(_entriesEndpoint) === 0; 19 | }); 20 | 21 | expect(didPerformRequest).toBe(true); 22 | }); 23 | 24 | it("doesn't performs an XHR to /entries when the API key isn't set", async function () { 25 | await browser.url('http://localhost:4567/fixtures/v1/unhandledErrorNoApiKey.html'); 26 | 27 | await browser.pause(4000); 28 | 29 | var inFlightXhrs = await browser.execute(function () { 30 | return window.__inFlightXHRs; 31 | }); 32 | 33 | var didPerformRequest = _.any(inFlightXhrs, function (req) { 34 | return req.url.indexOf(_entriesEndpoint) === 0; 35 | }); 36 | 37 | expect(didPerformRequest).toBe(false); 38 | }); 39 | 40 | it("doesn't performs an XHR to /entries when attach() isn't called", async function () { 41 | await browser.url('http://localhost:4567/fixtures/v1/unhandledErrorNoAttach.html'); 42 | 43 | await browser.pause(4000); 44 | 45 | var inFlightXhrs = await browser.execute(function () { 46 | return window.__inFlightXHRs; 47 | }); 48 | 49 | var didPerformRequest = _.any(inFlightXhrs, function (req) { 50 | return req.url.indexOf(_entriesEndpoint) === 0; 51 | }); 52 | 53 | expect(didPerformRequest).toBe(false); 54 | }); 55 | 56 | 57 | it("has the error message in the payload set", async function () { 58 | await browser.url('http://localhost:4567/fixtures/v1/unhandledError.html'); 59 | 60 | await browser.pause(4000); 61 | 62 | var requestPayloads = await browser.execute(function () { 63 | return window.__requestPayloads; 64 | }); 65 | 66 | var passes = _.any(requestPayloads, function (payload) { 67 | return payload.Details.Error.Message.indexOf('Unhandled error') > -1; 68 | }); 69 | 70 | expect(passes).toBe(true); 71 | }); 72 | 73 | it("has the filename in the stacktrace payload set", async function () { 74 | var pageUrl = 'http://localhost:4567/fixtures/v1/unhandledError.html'; 75 | 76 | await browser.url(pageUrl); 77 | 78 | await browser.pause(4000); 79 | 80 | var requestPayloads = await browser.execute(function () { 81 | return window.__requestPayloads; 82 | }); 83 | 84 | var passes = _.any(requestPayloads, function (payload) { 85 | return payload.Details.Error.StackTrace[0].FileName === pageUrl; 86 | }); 87 | 88 | expect(passes).toBe(true); 89 | }); 90 | 91 | it("has tags in the payload when tags are passed in", async function () { 92 | var pageUrl = 'http://localhost:4567/fixtures/v1/unhandledErrorTag.html'; 93 | 94 | await browser.url(pageUrl); 95 | 96 | await browser.pause(4000); 97 | 98 | var requestPayloads = await browser.execute(function () { 99 | return window.__requestPayloads; 100 | }); 101 | 102 | var passes = _.any(requestPayloads, function (payload) { 103 | return payload.Details.Tags[0] === 'my_tag'; 104 | }); 105 | 106 | expect(passes).toBe(true); 107 | }); 108 | 109 | it("has custom data in the payload when custom data is passed in", async function () { 110 | var pageUrl = 'http://localhost:4567/fixtures/v1/unhandledErrorCustomData.html'; 111 | 112 | await browser.url(pageUrl); 113 | 114 | await browser.pause(4000); 115 | 116 | var requestPayloads = await browser.execute(function () { 117 | return window.__requestPayloads; 118 | }); 119 | 120 | var passes = _.any(requestPayloads, function (payload) { 121 | return payload.Details.UserCustomData.myCustomKey === 'myCustomValue'; 122 | }); 123 | 124 | expect(passes).toBe(true); 125 | }); 126 | 127 | it("has correct user payload when Raygun.setUser() is called", async function () { 128 | var pageUrl = 'http://localhost:4567/fixtures/v1/unhandledErrorUser.html'; 129 | 130 | await browser.url(pageUrl); 131 | 132 | await browser.pause(4000); 133 | 134 | var requestPayloads = await browser.execute(function () { 135 | return window.__requestPayloads; 136 | }); 137 | 138 | var passes = _.any(requestPayloads, function (payload) { 139 | return payload.Details.User.Identifier === 'user_email_address@localhost.local' && 140 | payload.Details.User.IsAnonymous === false && 141 | payload.Details.User.FirstName === 'Foo' && 142 | payload.Details.User.FullName === 'Foo Bar' && 143 | payload.Details.User.UUID === 'BAE62917-ACE8-ab3D-9287-B6A33B8E8C55'; 144 | }); 145 | 146 | expect(passes).toBe(true); 147 | }); 148 | 149 | it("has correct version in payload when Raygun.setVersion() is called", async function () { 150 | var pageUrl = 'http://localhost:4567/fixtures/v1/unhandledErrorVersion.html'; 151 | 152 | await browser.url(pageUrl); 153 | 154 | await browser.pause(6000); 155 | 156 | var requestPayloads = await browser.execute(function () { 157 | return window.__requestPayloads; 158 | }); 159 | 160 | var passes = _.any(requestPayloads, function (payload) { 161 | return payload.Details.Version === '1.0.0.0'; 162 | }); 163 | 164 | expect(passes).toBe(true); 165 | }); 166 | 167 | it("has a UnhandleException tag for crash vs. error support", async function () { 168 | var pageUrl = 'http://localhost:4567/fixtures/v1/unhandledError.html'; 169 | 170 | await browser.url(pageUrl); 171 | 172 | await browser.pause(4000); 173 | 174 | var requestPayloads = await browser.execute(function () { 175 | return window.__requestPayloads; 176 | }); 177 | 178 | var passes = _.any(requestPayloads, function (payload) { 179 | return _.any(payload.Details.Tags, function (tag) { 180 | return tag === 'UnhandledException'; 181 | }); 182 | }); 183 | 184 | expect(passes).toBe(true); 185 | }); 186 | 187 | it("has existing tags and an UnhandleException tag for crash vs. error support", async function () { 188 | var pageUrl = 'http://localhost:4567/fixtures/v1/unhandledErrorTag.html'; 189 | 190 | await browser.url(pageUrl); 191 | 192 | await browser.pause(4000); 193 | 194 | var requestPayloads = await browser.execute(function () { 195 | return window.__requestPayloads; 196 | }); 197 | 198 | var passes = _.any(requestPayloads, function (payload) { 199 | return _.any(payload.Details.Tags, function (tag) { 200 | return tag === 'UnhandledException'; 201 | }); 202 | }); 203 | 204 | expect(passes).toBe(true); 205 | }); 206 | 207 | }); 208 | 209 | -------------------------------------------------------------------------------- /tests/specs/v2/UMDInfiniteLoopTest.js: -------------------------------------------------------------------------------- 1 | var webdriverio = require('webdriverio'); 2 | 3 | 4 | 5 | 6 | /** 7 | * What does this test do? 8 | * When using the UMD module, if raygun events are fired (e.g. rg4js('send', ...)) 9 | * before raygun is fully loaded they are stored in an object on the window. 10 | * When Raygun loads these are then processed. 11 | * However a bug was found where we were assuming raygun was loaded when 12 | * `document.readyState === complete` but it is not loaded until slightly 13 | * after when the `load` event is fired. This means that if a rayugn event 14 | * is processed in this gap an infinite loop can be caused. 15 | * This test ensures graceful handling of this situation 16 | */ 17 | describe("UMD Infinite loop test", function() { 18 | beforeEach(async function() { 19 | /** 20 | * Clears the session between tests to ensure 21 | * that the sessionstart event is always fired 22 | */ 23 | await browser.reloadSession(); 24 | }); 25 | 26 | describe('test infinite loop is not caused', function() { 27 | beforeEach(async function() { 28 | await browser.url('http://localhost:4567/fixtures/v2/UMDInfiniteLoop.html'); 29 | await browser.pause(1000); 30 | }); 31 | 32 | it('succesfully sends the event', async function() { 33 | var customTimingData = await browser.execute(function() { 34 | console.log(window.__requestPayloads) 35 | return window.__requestPayloads[2]; 36 | }); 37 | console.log(customTimingData.eventData[0].data); 38 | expect(JSON.parse(customTimingData.eventData[0].data)[0]).toEqual({ 39 | timing: { 40 | a: "0.00", 41 | du: "100.00", 42 | t: "t" 43 | }, 44 | url: "timingName", 45 | parentResource: { url: 'http://localhost:4567/fixtures/v2/UMDInfiniteLoop.html', type: 'p' } 46 | }); 47 | }); 48 | }); 49 | }); 50 | -------------------------------------------------------------------------------- /tests/specs/v2/basic.js: -------------------------------------------------------------------------------- 1 | var webdriverio = require('webdriverio'); 2 | 3 | describe("Basic Raygun4JS V2 API tests", function() { 4 | 5 | // Setup 6 | 7 | beforeEach(async function() { 8 | await browser.url('http://localhost:4567/fixtures/v2/basic.html'); 9 | }); 10 | 11 | // Tests 12 | 13 | it('has global rg4js object present', async function () { 14 | var result = await browser.execute(function () { 15 | return typeof rg4js === 'function'; 16 | }); 17 | 18 | expect(result).toBe(true); 19 | }); 20 | 21 | it('has global Raygun object present', async function () { 22 | var result = await browser.execute(function () { 23 | return typeof Raygun === 'object'; 24 | }); 25 | 26 | expect(result).toBe(true); 27 | }); 28 | 29 | it('has CR sending function', async function () { 30 | var result = await browser.execute(function () { 31 | return typeof Raygun.send === 'function'; 32 | }); 33 | 34 | expect(result).toBe(true); 35 | }); 36 | 37 | it('has RUM trackEvent function', async function () { 38 | var result = await browser.execute(function () { 39 | return typeof Raygun.trackEvent === 'function'; 40 | }); 41 | 42 | expect(result).toBe(true); 43 | }); 44 | }); -------------------------------------------------------------------------------- /tests/specs/v2/clientIp.js: -------------------------------------------------------------------------------- 1 | /* globals describe, beforeEach, it, expect, browser, window */ 2 | 3 | var _ = require('underscore'); 4 | 5 | describe("ClientIp", function() { 6 | 7 | it("X-Remote-Address is null when not set", async function () { 8 | await browser.url('http://localhost:4567/fixtures/v2/withoutClientIpSet.html'); 9 | 10 | await browser.pause(4000); 11 | 12 | var sentXhrs = await browser.execute(function () { 13 | return window.__sentXHRs; 14 | }); 15 | 16 | var remoteAddressIsUndefined = _.every(sentXhrs, function (req) { 17 | return req.clientIp === null; 18 | }); 19 | 20 | await expect(remoteAddressIsUndefined).toBe(true); 21 | }); 22 | 23 | it("X-Remote-Address is equal to '192.168.0.12'", async function () { 24 | await browser.url('http://localhost:4567/fixtures/v2/withClientIpSet.html'); 25 | 26 | await browser.pause(9000); 27 | 28 | var sentXhrs = await browser.execute(function () { 29 | return window.__sentXHRs; 30 | }); 31 | 32 | var remoteAddressIsSet = _.every(sentXhrs, function (req) { 33 | return req.clientIp === "192.168.0.12"; 34 | }); 35 | 36 | expect(remoteAddressIsSet).toBeTrue(); 37 | }); 38 | }); 39 | -------------------------------------------------------------------------------- /tests/specs/v2/customTiming.js: -------------------------------------------------------------------------------- 1 | var webdriverio = require('webdriverio'); 2 | 3 | describe("Custom Timing tests", function() { 4 | beforeEach(async function() { 5 | /** 6 | * Clears the session between tests to ensure 7 | * that the sessionstart event is always fired 8 | */ 9 | await browser.reloadSession(); 10 | }); 11 | 12 | describe('legacy custom timings', function() { 13 | beforeEach(async function() { 14 | await browser.url('http://localhost:4567/fixtures/v2/legacyCustomTiming.html'); 15 | await browser.pause(1000); 16 | }); 17 | 18 | it('sends custom timing events', async function () { 19 | var customTimingData = await browser.execute(function () { 20 | return window.__requestPayloads[1]; 21 | }); 22 | 23 | expect(JSON.parse(customTimingData.eventData[0].data)[0].customTiming).toEqual({ 24 | custom1: 100, 25 | custom2: 50 26 | }); 27 | }); 28 | }); 29 | 30 | describe('new custom timings', function() { 31 | beforeEach(async function() { 32 | await browser.url('http://localhost:4567/fixtures/v2/customTiming.html'); 33 | await browser.pause(1000); 34 | }); 35 | 36 | it('sends custom timing events', async function () { 37 | var customTimingData = await browser.execute(function () { 38 | return window.__requestPayloads[2]; 39 | }); 40 | 41 | expect(JSON.parse(customTimingData.eventData[0].data)[0]).toEqual({ 42 | timing: { 43 | a: "0.00", 44 | du: "100.00", 45 | t: "t" 46 | }, 47 | url: "timingName", 48 | parentResource: { url: 'http://localhost:4567/fixtures/v2/customTiming.html', type: 'p' } 49 | }); 50 | }); 51 | }); 52 | }); 53 | -------------------------------------------------------------------------------- /tests/specs/v2/endSession.js: -------------------------------------------------------------------------------- 1 | var webdriverio = require('webdriverio'); 2 | 3 | describe('endSession', function() { 4 | 5 | //Setup 6 | beforeEach(async function() { 7 | await browser.reloadSession(); 8 | await browser.url('http://localhost:4567/fixtures/v2/customTiming.html'); 9 | }); 10 | 11 | 12 | //Tests 13 | it('generates a new session id and saves to storage', async function() { 14 | 15 | var sessionId = await browser.execute(function () { 16 | return localStorage.getItem("raygun4js-sid"); 17 | }); 18 | 19 | var newSessionId = await browser.execute(function () { 20 | rg4js('endSession'); 21 | return localStorage.getItem("raygun4js-sid"); 22 | }); 23 | 24 | expect(newSessionId).toBeTruthy; 25 | expect(sessionId !== newSessionId).toBeTrue(); 26 | }); 27 | 28 | it('sends a session_end and session_start event', async function() { 29 | 30 | await browser.execute(function () { 31 | rg4js('endSession'); 32 | }); 33 | 34 | var endSessionPayload = await browser.execute(function () { 35 | var timings = window.__requestPayloads[2]; 36 | return timings.eventData[0].type; 37 | }); 38 | 39 | var startSessionPayload = await browser.execute(function () { 40 | var timings = window.__requestPayloads[3]; 41 | return timings.eventData[0].type; 42 | }); 43 | 44 | expect(endSessionPayload).toEqual("session_end"); 45 | expect(startSessionPayload).toEqual("session_start"); 46 | }); 47 | }); 48 | -------------------------------------------------------------------------------- /tests/specs/v2/eventsXhr.js: -------------------------------------------------------------------------------- 1 | var webdriverio = require('webdriverio'); 2 | var _ = require('underscore'); 3 | 4 | var _eventsEndpoint = 'https://api.raygun.io/events'; 5 | 6 | describe("XHR functional tests for /events with V2", function() { 7 | 8 | // Tests 9 | 10 | it("performs an XHR to /events when rg4js('trackEvent') is called", async function () { 11 | await browser.url('http://localhost:4567/fixtures/v2/trackEvent.html'); 12 | 13 | await browser.pause(4000); 14 | 15 | var inFlightXhrs = await browser.execute(function () { 16 | return window.__inFlightXHRs; 17 | }); 18 | 19 | var didPerformRequest = await _.any(inFlightXhrs, function (req) { 20 | return req.url.indexOf(_eventsEndpoint) === 0; 21 | }); 22 | 23 | expect(didPerformRequest).toBe(true); 24 | }); 25 | 26 | }); -------------------------------------------------------------------------------- /tests/specs/v2/onBeforeSendRUM.js: -------------------------------------------------------------------------------- 1 | var webdriverio = require('webdriverio'); 2 | var _ = require('underscore'); 3 | 4 | var _eventsEndpoint = 'https://api.raygun.io/events'; 5 | 6 | describe('onBeforeSendRUM callback', function() { 7 | it('lets you modify the payload before sending', async function() { 8 | await browser.url('http://localhost:4567/fixtures/v2/onBeforeSendRUM.html'); 9 | 10 | await browser.pause(4000); 11 | 12 | var modifiedPayload = await browser.execute(function () { 13 | return window.__requestPayloads[0]; 14 | }); 15 | 16 | expect(modifiedPayload.eventData[0].version).toBe('1.0.0'); 17 | }); 18 | 19 | it('serializes the eventData.data to a string', async function() { 20 | await browser.url('http://localhost:4567/fixtures/v2/onBeforeSendRUM.html'); 21 | 22 | await browser.pause(4000); 23 | 24 | var firstPayload = await browser.execute(function () { 25 | return window.__requestPayloads[0]; 26 | }); 27 | 28 | var typeOfData = typeof firstPayload.eventData[0].data; 29 | expect(typeOfData === "string").toBe(true); 30 | }); 31 | 32 | it('allows you to cancel sending a payload', async function() { 33 | await browser.url('http://localhost:4567/fixtures/v2/onBeforeSendRUM.html'); 34 | 35 | await browser.pause(4000); 36 | 37 | var allPayloads = await browser.execute(function () { 38 | return window.__requestPayloads; 39 | }); 40 | 41 | expect(allPayloads.length).toBe(1); 42 | }); 43 | }); 44 | -------------------------------------------------------------------------------- /tests/specs/v2/payloadSyntaxError.js: -------------------------------------------------------------------------------- 1 | var webdriverio = require('webdriverio'); 2 | var _ = require('underscore'); 3 | 4 | var _entriesEndpoint = 'https://api.raygun.io/entries'; 5 | 6 | describe("Payload functional validation tests for V2 syntax error caught with the Snippet", function() { 7 | 8 | it("performs an XHR to /entries when a syntax error is present", async function () { 9 | await browser.url('http://localhost:4567/fixtures/v2/syntaxErrorSnippet.html'); 10 | 11 | await browser.pause(6000); 12 | 13 | var requestPayloads = await browser.execute(function () { 14 | return window.__requestPayloads; 15 | }); 16 | 17 | var doesHaveLineNumbersAndColumnNumbers = _.any(requestPayloads, function (req) { 18 | console.log('[stacktrace]'); 19 | var stackTracesToCheck = req.Details.Error.StackTrace.slice(0, 2); 20 | return _.every(stackTracesToCheck, function (trace) { 21 | if (trace.MethodName === 'eval') { 22 | return true; 23 | } 24 | 25 | return typeof trace.LineNumber === 'number' && typeof trace.ColumnNumber === 'number'; 26 | }); 27 | }); 28 | 29 | expect(doesHaveLineNumbersAndColumnNumbers).toBe(true); 30 | }); 31 | 32 | }); 33 | -------------------------------------------------------------------------------- /tests/specs/v2/requestId.js: -------------------------------------------------------------------------------- 1 | var webdriverio = require('webdriverio'); 2 | 3 | describe("Request ID tests", function() { 4 | 5 | // Tests 6 | 7 | it('has a unique request ID for each virtual page request', async function() { 8 | await browser.url('http://localhost:4567/fixtures/v2/requestId.html'); 9 | 10 | await browser.pause(1000); 11 | 12 | var requestPayloads = await browser.execute(function () { 13 | return window.__requestPayloads; 14 | }); 15 | 16 | // If this fails because eventData is undefined it's probably 17 | // because the virtual pages aren't being sent 18 | var requestId1 = requestPayloads[1].eventData[0].requestId; 19 | var requestId2 = requestPayloads[2].eventData[0].requestId; 20 | 21 | expect(requestId1 !== requestId2).toBeTrue(requestId2); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /tests/specs/v2/rg4jsLoaderTests.js: -------------------------------------------------------------------------------- 1 | var webdriverio = require('webdriverio'); 2 | var _ = require('underscore'); 3 | 4 | var _entriesEndpoint = 'https://api.raygun.io/entries'; 5 | 6 | describe("Functional tests for rg4js() calls to ensure they are executed by the loader", function() { 7 | 8 | it("sets onBeforeSend when it is called before library is loaded", async function () { 9 | await browser.url('http://localhost:4567/fixtures/v2/rg4jsBeforeLibraryLoaded.html'); 10 | 11 | await browser.pause(5000); 12 | 13 | var didPerformOnBeforeSend = await browser.execute(function () { 14 | return window.didPerformOnBeforeSend; 15 | }); 16 | 17 | expect(didPerformOnBeforeSend).toEqual(['beforesendcalled', 'the_scope', 'object']); 18 | }); 19 | 20 | it("sets onBeforeSend when it is called after library is loaded", async function () { 21 | await browser.url('http://localhost:4567/fixtures/v2/rg4jsAfterLibraryLoaded.html'); 22 | 23 | await browser.pause(5000); 24 | 25 | var didPerformOnBeforeSend = await browser.execute(function () { 26 | return window.didPerformOnBeforeSend; 27 | }); 28 | 29 | expect(didPerformOnBeforeSend).toEqual(['beforesendcalled', 'the_scope', 'object']); 30 | }); 31 | 32 | }); -------------------------------------------------------------------------------- /tests/specs/v2/rumXhrStatusTracking.js: -------------------------------------------------------------------------------- 1 | /* globals describe, it, expect, browser, window, afterEach, fail */ 2 | 3 | var _ = require('underscore'); 4 | 5 | describe("RUM status code tracking", function () { 6 | 7 | 8 | beforeEach(function() { 9 | originalTimeout = jasmine.DEFAULT_TIMEOUT_INTERVAL; 10 | jasmine.DEFAULT_TIMEOUT_INTERVAL = 60000; 11 | }); 12 | afterEach(async function() { 13 | jasmine.DEFAULT_TIMEOUT_INTERVAL = originalTimeout; 14 | await browser.reloadSession(); 15 | }); 16 | 17 | function payloadsWithoutRaygunApi(payloads) { 18 | return _.sortBy(_.filter(payloads, function (payload) { 19 | return payload.url.indexOf('raygun') === -1; 20 | }), function (payload) { return payload.url; }); 21 | } 22 | 23 | async function checkStatusCodes() { 24 | 25 | var requestPayloads = await waitForRequestPayloads(); 26 | 27 | if (requestPayloads.length < 3) { 28 | fail("test did not wait long enough for ajax requests to be sent to Raygun"); 29 | } 30 | 31 | var timingPayload = payloadsWithoutRaygunApi(JSON.parse(requestPayloads[2].eventData[0].data)); 32 | 33 | var expectedPairs = [ 34 | { url: 'http://localhost:4567/fixtures/v2/foo.html', statusCode: 404, type: 'relative url that does not exist' }, 35 | { url: 'http://localhost:4567/fixtures/v2/rumXhrStatusCodes.html', statusCode: 200, type: 'plain relative url' }, 36 | { url: 'http://localhost:4567/fixtures/v2/rumXhrStatusCodes.html', statusCode: 200, type: 'relative url with query string' }, 37 | { url: 'http://localhost:4567/fixtures/v2/rumXhrStatusCodes.html', statusCode: 200, type: 'absolute url' }, 38 | ]; 39 | 40 | for (var i = 0; i < expectedPairs.length; i++) { 41 | var payloadUrl = timingPayload[i].url; 42 | var payloadStatus = timingPayload[i].statusCode; 43 | var payloadDataType = timingPayload[i].timing.t; 44 | 45 | var pairUrl = expectedPairs[i].url; 46 | var pairStatus = expectedPairs[i].statusCode; 47 | var pairType = expectedPairs[i].type; 48 | 49 | expect(payloadUrl === pairUrl).toBeTrue("failed for type: " + pairType); 50 | expect(payloadStatus === pairStatus).toBeTrue("failed for type: " + pairType); 51 | expect(payloadDataType === payloadDataType).toBeTrue("XHR data type missing for: " + pairType); 52 | } 53 | } 54 | 55 | 56 | it("attaches the status codes to xhr calls for XmlHttpRequest", async function () { 57 | await browser.url('http://localhost:4567/fixtures/v2/rumXhrStatusCodes.html'); 58 | await checkStatusCodes(); 59 | }); 60 | 61 | it("attaches status codes to requests for fetch requests", async function () { 62 | await browser.url('http://localhost:4567/fixtures/v2/rumFetchStatusCodes.html'); 63 | 64 | await browser.pause(1000); 65 | 66 | var supportsFetch = await browser.execute(function () { 67 | return window.supportsFetch; 68 | }); 69 | 70 | if (!supportsFetch) { 71 | return; 72 | } 73 | 74 | 75 | 76 | await checkStatusCodes(); 77 | }); 78 | 79 | describe('with the global window.fetch objects saved as a reference', () => { 80 | describe('and using the minified.fetchhandler.js code snippet', () => { 81 | it('attaches status codes to requests', async () => { 82 | await browser.url('http://localhost:4567/fixtures/v2/rumReferencedFetchWithFetchSnippet.html'); 83 | 84 | await browser.pause(1000); 85 | 86 | var supportsFetch = await browser.execute(function () { 87 | return window.supportsFetch; 88 | }); 89 | 90 | if (!supportsFetch) { 91 | return; 92 | } 93 | 94 | await checkStatusCodes(); 95 | }); 96 | 97 | it('overriden fetch methods are stilled called', async () => { 98 | await browser.url('http://localhost:4567/fixtures/v2/rumReferencedFetchWithFetchSnippet.html'); 99 | 100 | await browser.pause(1000); 101 | 102 | var supportsFetch = await browser.execute(function () { 103 | return window.supportsFetch; 104 | }); 105 | 106 | if (!supportsFetch) { 107 | return; 108 | } 109 | 110 | var completedCalls = await browser.execute(function () { 111 | return window.__completedCalls; 112 | }); 113 | 114 | if (completedCalls.length < 4) { 115 | fail("test did not wait long enough for ajax requests to be sent to Raygun"); 116 | } 117 | 118 | var expectedCalls = [ 119 | 'foo.html', 120 | 'rumXhrStatusCodes.html', 121 | 'rumXhrStatusCodes.html?foo=bar', 122 | 'http://localhost:4567/fixtures/v2/rumXhrStatusCodes.html' 123 | ]; 124 | 125 | for (var i = 0; i < expectedCalls.length; i++) { 126 | var url = expectedCalls[i]; 127 | expect(completedCalls.indexOf(url) !== -1).toBeTrue(); 128 | } 129 | }); 130 | }); 131 | }); 132 | 133 | it("attaches the status codes for polyfilled fetch requests", async function () { 134 | await browser.url('http://localhost:4567/fixtures/v2/rumFetchPolyfillStatusCodes.html'); 135 | 136 | await checkStatusCodes(); 137 | }); 138 | 139 | async function waitForRequestPayloads() { 140 | var n = 0; 141 | while (n < 40) { 142 | 143 | var requestPayloads = await browser.execute(function () { 144 | return window.__requestPayloads; 145 | }); 146 | 147 | if (requestPayloads.length >= 3) { 148 | break; 149 | } 150 | await browser.pause(1000); 151 | } 152 | 153 | var requestPayloads = await browser.execute(function () { 154 | return window.__requestPayloads; 155 | }); 156 | return requestPayloads; 157 | } 158 | }); 159 | -------------------------------------------------------------------------------- /tests/specs/v2/unhandledPromiseRejectionTests.js: -------------------------------------------------------------------------------- 1 | var webdriverio = require('webdriverio'); 2 | var _ = require('underscore'); 3 | 4 | describe("Unhandled promise rejection", function() { 5 | // Tests 6 | 7 | it('sends error on unhandled promise rejection', async function() { 8 | await browser.url('http://localhost:4567/fixtures/v2/unhandledPromiseRejection.html'); 9 | await browser.pause(1000); 10 | 11 | var supportsUnHandledRejections = await browser.execute(function() { 12 | return window.supportsOnunhandledrejection; 13 | }); 14 | 15 | if(supportsUnHandledRejections) { 16 | await browser.pause(1000); 17 | 18 | var requestPayloads = await browser.execute(function () { 19 | return window.__requestPayloads; 20 | }); 21 | var unhandledPromise = requestPayloads[0].Details.Error.Message.indexOf('rejected promise') > -1; 22 | 23 | expect(unhandledPromise).toBeTrue(); 24 | } 25 | }); 26 | 27 | describe('with no reason provided for rejection', function() { 28 | it('sends an error with a relevant message and no stacktrace data', async function() { 29 | await browser.url('http://localhost:4567/fixtures/v2/unhandledPromiseRejectionWithNoReason.html'); 30 | await browser.pause(1000); 31 | 32 | var supportsUnHandledRejections = await browser.execute(function() { 33 | return window.supportsOnunhandledrejection; 34 | }); 35 | 36 | if (supportsUnHandledRejections) { 37 | await browser.pause(1000); 38 | 39 | var requestPayloads = await browser.execute(function () { 40 | return window.__requestPayloads; 41 | }); 42 | 43 | var errorPayload = requestPayloads[0].Details.Error; 44 | 45 | expect(errorPayload.Message).toEqual('Unhandled promise rejection'); 46 | expect(errorPayload.StackTrace.length).toEqual(1); 47 | expect(errorPayload.StackTrace[0].LineNumber).toBeNull(); 48 | expect(errorPayload.StackTrace[0].ColumnNumber).toBeNull(); 49 | } 50 | }); 51 | }); 52 | }); 53 | -------------------------------------------------------------------------------- /tracekit/.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 0.8 -------------------------------------------------------------------------------- /tracekit/README.md: -------------------------------------------------------------------------------- 1 | TraceKit - Cross browser stack traces. 2 | ===================================== 3 | 4 | [![Build Status](https://travis-ci.org/occ/TraceKit.png?branch=master)](https://travis-ci.org/occ/TraceKit) 5 | 6 | ### Supports all major browsers, from IE6 to Opera, the Andriod webview and everywhere in between. 7 | 8 | Not all browsers support stack traces on error objects, but TraceKit squeezes 9 | out as much useful information as possible and normalizes it. 3kB minified + gzipped 10 | 11 | 12 | ## Install 13 | 14 | ``` 15 | bower install tracekit 16 | ``` 17 | This places TraceKit at `components/tracekit/tracekit.js`. Install [bower](http://twitter.github.com/bower/): `npm install bower -g`, download npm with Node: http://nodejs.org 18 | 19 | Then include the ` 10 | 11 | 12 | 50 | 51 | 60 | 61 | 62 |

TraceKit Recursion Test

63 |
64 | 67 | 68 | --------------------------------------------------------------------------------