diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4535ba26177f..f66994722fe7 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3,6 +3,7 @@
## Unreleased
- "You miss 100 percent of the chances you don't take. — Wayne Gretzky" — Michael Scott
+- [ember] feat: Add `@sentry/ember` (#2739)
## 5.20.1
diff --git a/package.json b/package.json
index d82aef6d83cb..0ea8c01ee171 100644
--- a/package.json
+++ b/package.json
@@ -22,6 +22,7 @@
"packages/apm",
"packages/browser",
"packages/core",
+ "packages/ember",
"packages/gatsby",
"packages/hub",
"packages/integrations",
diff --git a/packages/ember/.ember-cli b/packages/ember/.ember-cli
new file mode 100644
index 000000000000..ee64cfed2a89
--- /dev/null
+++ b/packages/ember/.ember-cli
@@ -0,0 +1,9 @@
+{
+ /**
+ Ember CLI sends analytics information by default. The data is completely
+ anonymous, but there are times when you might want to disable this behavior.
+
+ Setting `disableAnalytics` to true will prevent any data from being sent.
+ */
+ "disableAnalytics": false
+}
diff --git a/packages/ember/.eslintignore b/packages/ember/.eslintignore
new file mode 100644
index 000000000000..72df37307209
--- /dev/null
+++ b/packages/ember/.eslintignore
@@ -0,0 +1,20 @@
+# unconventional js
+/blueprints/*/files/
+/vendor/
+
+# compiled output
+/dist/
+/tmp/
+
+# dependencies
+/bower_components/
+/node_modules/
+
+# misc
+/coverage/
+!.*
+
+# ember-try
+/.node_modules.ember-try/
+/bower.json.ember-try
+/package.json.ember-try
diff --git a/packages/ember/.eslintrc.js b/packages/ember/.eslintrc.js
new file mode 100644
index 000000000000..ae43746840b0
--- /dev/null
+++ b/packages/ember/.eslintrc.js
@@ -0,0 +1,57 @@
+'use strict';
+
+module.exports = {
+ root: true,
+ parser: 'babel-eslint',
+ parserOptions: {
+ ecmaVersion: 2018,
+ sourceType: 'module',
+ ecmaFeatures: {
+ legacyDecorators: true
+ }
+ },
+ plugins: [
+ 'ember'
+ ],
+ extends: [
+ 'eslint:recommended',
+ 'plugin:ember/recommended'
+ ],
+ env: {
+ browser: true
+ },
+ globals: {
+ "QUnit": true
+ },
+ rules: {},
+ overrides: [
+ // node files
+ {
+ files: [
+ '.eslintrc.js',
+ '.template-lintrc.js',
+ 'ember-cli-build.js',
+ 'index.js',
+ 'testem.js',
+ 'blueprints/*/index.js',
+ 'config/**/*.js',
+ 'tests/dummy/config/**/*.js'
+ ],
+ excludedFiles: [
+ 'addon/**',
+ 'addon-test-support/**',
+ 'app/**',
+ 'tests/dummy/app/**'
+ ],
+ parserOptions: {
+ sourceType: 'script'
+ },
+ env: {
+ browser: false,
+ node: true
+ },
+ plugins: ['node'],
+ extends: ['plugin:node/recommended']
+ }
+ ]
+};
diff --git a/packages/ember/.gitignore b/packages/ember/.gitignore
new file mode 100644
index 000000000000..c40a1b2aba3e
--- /dev/null
+++ b/packages/ember/.gitignore
@@ -0,0 +1,25 @@
+# See https://help.github.com/ignore-files/ for more about ignoring files.
+
+# compiled output
+/dist/
+/tmp/
+
+# dependencies
+/bower_components/
+/node_modules/
+
+# misc
+/.env*
+/.pnp*
+/.sass-cache
+/connect.lock
+/coverage/
+/libpeerconnection.log
+/npm-debug.log*
+/testem.log
+/yarn-error.log
+
+# ember-try
+/.node_modules.ember-try/
+/bower.json.ember-try
+/package.json.ember-try
diff --git a/packages/ember/.npmignore b/packages/ember/.npmignore
new file mode 100644
index 000000000000..bd09adff92e0
--- /dev/null
+++ b/packages/ember/.npmignore
@@ -0,0 +1,32 @@
+# compiled output
+/dist/
+/tmp/
+
+# dependencies
+/bower_components/
+
+# misc
+/.bowerrc
+/.editorconfig
+/.ember-cli
+/.env*
+/.eslintignore
+/.eslintrc.js
+/.git/
+/.gitignore
+/.template-lintrc.js
+/.travis.yml
+/.watchmanconfig
+/bower.json
+/config/ember-try.js
+/CONTRIBUTING.md
+/ember-cli-build.js
+/testem.js
+/tests/
+/yarn.lock
+.gitkeep
+
+# ember-try
+/.node_modules.ember-try/
+/bower.json.ember-try
+/package.json.ember-try
diff --git a/packages/ember/.template-lintrc.js b/packages/ember/.template-lintrc.js
new file mode 100644
index 000000000000..f38737001038
--- /dev/null
+++ b/packages/ember/.template-lintrc.js
@@ -0,0 +1,5 @@
+'use strict';
+
+module.exports = {
+ extends: 'octane'
+};
diff --git a/packages/ember/.watchmanconfig b/packages/ember/.watchmanconfig
new file mode 100644
index 000000000000..e7834e3e4f39
--- /dev/null
+++ b/packages/ember/.watchmanconfig
@@ -0,0 +1,3 @@
+{
+ "ignore_dirs": ["tmp", "dist"]
+}
diff --git a/packages/ember/LICENSE b/packages/ember/LICENSE
new file mode 100644
index 000000000000..55b2a04ccf60
--- /dev/null
+++ b/packages/ember/LICENSE
@@ -0,0 +1,9 @@
+The MIT License (MIT)
+
+Copyright (c) 2020, Sentry
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/packages/ember/README.md b/packages/ember/README.md
new file mode 100644
index 000000000000..4cfb1f3f77cf
--- /dev/null
+++ b/packages/ember/README.md
@@ -0,0 +1,82 @@
+
+
+
+
+
+
+
+# Official Sentry SDK for Ember.js
+
+## Links
+
+- [Official SDK Docs](https://docs.sentry.io/quickstart/)
+- [TypeDoc](http://getsentry.github.io/sentry-javascript/)
+
+## General
+
+This package is an Ember addon that wraps `@sentry/browser`, with added functionality related to Ember. All methods available in
+`@sentry/browser` can be imported from `@sentry/ember`.
+
+### Installation
+
+As with other Ember addons, run:
+`ember install @sentry/ember`
+
+Then add the following config to `config/environment.js`
+
+```javascript
+ ENV['@sentry/ember'] = {
+ sentry: {
+ dsn: '__DSN__' // replace __DSN__ with your DSN
+ }
+ };
+```
+
+### Usage
+
+To use this SDK, call `InitSentryForEmber` before the application is initialized, in `app.js`. This will load Sentry config from `environment.js` for you.
+
+```javascript
+import Application from '@ember/application';
+import Resolver from 'ember-resolver';
+import loadInitializers from 'ember-load-initializers';
+import config from './config/environment';
+import { InitSentryForEmber } from '@sentry/ember';
+
+InitSentryForEmber();
+
+export default class App extends Application {
+ modulePrefix = config.modulePrefix;
+ podModulePrefix = config.podModulePrefix;
+ Resolver = Resolver;
+}
+```
+
+### Additional Configuration
+
+Aside from configuration passed from this addon into `@sentry/browser` via the `sentry` property, there is also the following Ember specific configuration.
+
+```javascript
+ ENV['@sentry/ember'] = {
+ ignoreEmberOnErrorWarning: false, // Will silence Ember.onError warning without the need of using Ember debugging tools. False by default.
+ sentry: ... // See sentry-javascript configuration https://docs.sentry.io/error-reporting/configuration/?platform=javascript
+ };
+```
+
+### Supported Versions
+
+`@sentry/ember` currently supports Ember **3.8+** for error monitoring.
+
+### Previous Integration
+
+Previously we've recommended using the Ember integration from `@sentry/integrations` but moving forward we will be using
+this Ember addon to offer more Ember-specific error and performancing monitoring.
+
+## Testing
+
+You can find example instrumentation in the `dummy` application, which is also used for testing. To test with the dummy
+application, you must pass the dsn as an environment variable.
+
+```javascript
+SENTRY_DSN=__DSN__ ember serve
+```
diff --git a/packages/ember/addon/.gitkeep b/packages/ember/addon/.gitkeep
new file mode 100644
index 000000000000..e69de29bb2d1
diff --git a/packages/ember/addon/declarations.d.ts b/packages/ember/addon/declarations.d.ts
new file mode 100644
index 000000000000..fa430c829328
--- /dev/null
+++ b/packages/ember/addon/declarations.d.ts
@@ -0,0 +1,3 @@
+declare module 'ember-get-config' {
+ export default object;
+}
\ No newline at end of file
diff --git a/packages/ember/addon/index.ts b/packages/ember/addon/index.ts
new file mode 100644
index 000000000000..babe2de8fd73
--- /dev/null
+++ b/packages/ember/addon/index.ts
@@ -0,0 +1,57 @@
+import * as Sentry from '@sentry/browser';
+import { addGlobalEventProcessor, SDK_VERSION, BrowserOptions } from '@sentry/browser';
+import environmentConfig from 'ember-get-config';
+
+import { next } from '@ember/runloop';
+import { assert, warn, runInDebug } from '@ember/debug';
+import Ember from 'ember';
+
+export function InitSentryForEmber(_runtimeConfig: BrowserOptions | undefined) {
+ const config = environmentConfig['@sentry/ember'];
+ assert('Missing configuration', config);
+ assert('Missing configuration for Sentry.', config.sentry);
+
+ const initConfig = Object.assign({}, config.sentry, _runtimeConfig || {});
+
+ createEmberEventProcessor();
+
+ Sentry.init(initConfig);
+
+ runInDebug(() => {
+ if (config.ignoreEmberOnErrorWarning) {
+ return;
+ }
+ next(null, function () {
+ warn(
+ 'Ember.onerror found. Using Ember.onerror can hide some errors (such as flushed runloop errors) from Sentry. Use Sentry.captureException to capture errors within Ember.onError or remove it to have errors caught by Sentry directly. This error can be silenced via addon configuration.',
+ !Ember.onerror,
+ {
+ id: '@sentry/ember.ember-onerror-detected',
+ },
+ );
+ });
+ });
+}
+
+function createEmberEventProcessor(): void {
+ if (addGlobalEventProcessor) {
+ addGlobalEventProcessor((event) => {
+ event.sdk = {
+ ...event.sdk,
+ name: 'sentry.javascript.ember',
+ packages: [
+ ...((event.sdk && event.sdk.packages) || []),
+ {
+ name: 'npm:@sentry/ember',
+ version: SDK_VERSION,
+ },
+ ],
+ version: SDK_VERSION,
+ };
+
+ return event;
+ });
+ }
+}
+
+export * from '@sentry/browser';
diff --git a/packages/ember/config/ember-try.js b/packages/ember/config/ember-try.js
new file mode 100644
index 000000000000..91935e105971
--- /dev/null
+++ b/packages/ember/config/ember-try.js
@@ -0,0 +1,88 @@
+'use strict';
+
+const getChannelURL = require('ember-source-channel-url');
+
+module.exports = async function() {
+ return {
+ scenarios: [
+ {
+ name: 'ember-lts-3.12',
+ npm: {
+ devDependencies: {
+ 'ember-source': '~3.12.0'
+ }
+ }
+ },
+ {
+ name: 'ember-lts-3.16',
+ npm: {
+ devDependencies: {
+ 'ember-source': '~3.16.0'
+ }
+ }
+ },
+ {
+ name: 'ember-release',
+ npm: {
+ devDependencies: {
+ 'ember-source': await getChannelURL('release')
+ }
+ }
+ },
+ {
+ name: 'ember-beta',
+ npm: {
+ devDependencies: {
+ 'ember-source': await getChannelURL('beta')
+ }
+ }
+ },
+ {
+ name: 'ember-canary',
+ npm: {
+ devDependencies: {
+ 'ember-source': await getChannelURL('canary')
+ }
+ }
+ },
+ // The default `.travis.yml` runs this scenario via `npm test`,
+ // not via `ember try`. It's still included here so that running
+ // `ember try:each` manually or from a customized CI config will run it
+ // along with all the other scenarios.
+ {
+ name: 'ember-default',
+ npm: {
+ devDependencies: {}
+ }
+ },
+ {
+ name: 'ember-default-with-jquery',
+ env: {
+ EMBER_OPTIONAL_FEATURES: JSON.stringify({
+ 'jquery-integration': true
+ })
+ },
+ npm: {
+ devDependencies: {
+ '@ember/jquery': '^0.5.1'
+ }
+ }
+ },
+ {
+ name: 'ember-classic',
+ env: {
+ EMBER_OPTIONAL_FEATURES: JSON.stringify({
+ 'application-template-wrapper': true,
+ 'default-async-observers': false,
+ 'template-only-glimmer-components': false
+ })
+ },
+ npm: {
+ ember: {
+ edition: 'classic'
+ }
+ }
+ }
+ ]
+ };
+};
diff --git a/packages/ember/config/environment.js b/packages/ember/config/environment.js
new file mode 100644
index 000000000000..0dfaed4728b3
--- /dev/null
+++ b/packages/ember/config/environment.js
@@ -0,0 +1,5 @@
+'use strict';
+
+module.exports = function(/* environment, appConfig */) {
+ return { };
+};
diff --git a/packages/ember/ember-cli-build.js b/packages/ember/ember-cli-build.js
new file mode 100644
index 000000000000..dc5a39e1b796
--- /dev/null
+++ b/packages/ember/ember-cli-build.js
@@ -0,0 +1,18 @@
+'use strict';
+
+const EmberAddon = require('ember-cli/lib/broccoli/ember-addon');
+
+module.exports = function(defaults) {
+ let app = new EmberAddon(defaults, {
+ // Add options here
+ });
+
+ /*
+ This build file specifies the options for the dummy test app of this
+ addon, located in `/tests/dummy`
+ This build file does *not* influence how the addon or the app using it
+ behave. You most likely want to be modifying `./index.js` or app's build file
+ */
+
+ return app.toTree();
+};
diff --git a/packages/ember/index.js b/packages/ember/index.js
new file mode 100644
index 000000000000..2e1d1d8d5fa0
--- /dev/null
+++ b/packages/ember/index.js
@@ -0,0 +1,5 @@
+'use strict';
+
+module.exports = {
+ name: require('./package').name
+};
diff --git a/packages/ember/package.json b/packages/ember/package.json
new file mode 100644
index 000000000000..395b2fd93855
--- /dev/null
+++ b/packages/ember/package.json
@@ -0,0 +1,88 @@
+{
+ "name": "@sentry/ember",
+ "version": "5.20.1",
+ "description": "Offical Sentry SDK for Ember.js",
+ "repository": "git://github.com/getsentry/sentry-javascript.git",
+ "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/ember",
+ "author": "Sentry",
+ "license": "MIT",
+ "keywords": [
+ "ember-addon"
+ ],
+ "directories": {
+ "doc": "doc",
+ "test": "tests"
+ },
+ "scripts": {
+ "build": "ember build --environment=production",
+ "lint": "npm-run-all --aggregate-output --continue-on-error --parallel lint:*",
+ "lint:hbs": "ember-template-lint .",
+ "lint:js": "eslint .",
+ "start": "ember serve",
+ "test": "npm-run-all lint:* test:*",
+ "test:ember": "ember test",
+ "test:ember-compatibility": "ember try:each",
+ "prepublishOnly": "ember ts:precompile",
+ "postpublish": "ember ts:clean"
+ },
+ "dependencies": {
+ "@sentry/browser": "5.20.1",
+ "@sentry/types": "5.20.1",
+ "@sentry/utils": "5.20.1",
+ "ember-get-config": "^0.2.4",
+ "ember-auto-import": "^1.6.0",
+ "ember-cli-babel": "^7.20.5",
+ "ember-cli-htmlbars": "^5.1.2",
+ "ember-cli-typescript": "^3.1.4"
+ },
+ "devDependencies": {
+ "@ember/optional-features": "^1.3.0",
+ "@glimmer/component": "^1.0.0",
+ "@glimmer/tracking": "^1.0.0",
+ "@types/ember": "^3.16.0",
+ "@types/ember-qunit": "^3.4.9",
+ "@types/ember__test-helpers": "^1.7.0",
+ "@types/qunit": "^2.9.1",
+ "@types/rsvp": "^4.0.3",
+ "babel-eslint": "^10.1.0",
+ "broccoli-asset-rev": "^3.0.0",
+ "ember-cli": "~3.19.0",
+ "ember-cli-dependency-checker": "^3.2.0",
+ "ember-cli-inject-live-reload": "^2.0.2",
+ "ember-cli-sri": "^2.1.1",
+ "ember-cli-typescript-blueprints": "^3.0.0",
+ "ember-cli-uglify": "^3.0.0",
+ "ember-disable-prototype-extensions": "^1.1.3",
+ "ember-export-application-global": "^2.0.1",
+ "ember-load-initializers": "^2.1.1",
+ "ember-maybe-import-regenerator": "^0.1.6",
+ "ember-qunit": "^4.6.0",
+ "ember-resolver": "^8.0.0",
+ "ember-sinon-qunit": "^5.0.0",
+ "ember-source": "~3.19.0",
+ "ember-source-channel-url": "^2.0.1",
+ "ember-template-lint": "^2.9.1",
+ "ember-test-selectors": "^4.1.0",
+ "ember-try": "^1.4.0",
+ "eslint": "^7.5.0",
+ "eslint-plugin-ember": "^8.6.0",
+ "eslint-plugin-node": "^11.1.0",
+ "loader.js": "^4.7.0",
+ "npm-run-all": "^4.1.5",
+ "qunit-dom": "^1.2.0",
+ "typescript": "^3.9.7"
+ },
+ "engines": {
+ "node": "10.* || >= 12"
+ },
+ "ember": {
+ "edition": "octane"
+ },
+ "ember-addon": {
+ "configPath": "tests/dummy/config"
+ },
+ "volta": {
+ "node": "12.18.3",
+ "yarn": "1.22.4"
+ }
+}
diff --git a/packages/ember/testem.js b/packages/ember/testem.js
new file mode 100644
index 000000000000..49f53feed6c1
--- /dev/null
+++ b/packages/ember/testem.js
@@ -0,0 +1,27 @@
+'use strict';
+
+module.exports = {
+ test_page: 'tests/index.html?hidepassed',
+ disable_watching: true,
+ launch_in_ci: [
+ 'Chrome'
+ ],
+ launch_in_dev: [
+ 'Chrome'
+ ],
+ browser_start_timeout: 120,
+ browser_args: {
+ Chrome: {
+ ci: [
+ // --no-sandbox is needed when running Chrome inside a container
+ process.env.CI ? '--no-sandbox' : null,
+ '--headless',
+ '--disable-dev-shm-usage',
+ '--disable-software-rasterizer',
+ '--mute-audio',
+ '--remote-debugging-port=0',
+ '--window-size=1440,900'
+ ].filter(Boolean)
+ }
+ }
+};
diff --git a/packages/ember/tests/acceptance/sentry-errors-test.js b/packages/ember/tests/acceptance/sentry-errors-test.js
new file mode 100644
index 000000000000..2c60b9c8d894
--- /dev/null
+++ b/packages/ember/tests/acceptance/sentry-errors-test.js
@@ -0,0 +1,161 @@
+import { test, module } from 'qunit';
+import { setupApplicationTest } from 'ember-qunit';
+import { find, click, visit } from '@ember/test-helpers';
+import { run } from '@ember/runloop';
+import Ember from 'ember';
+import sinon from 'sinon';
+
+const defaultAssertOptions = {
+ method: 'POST',
+ errorBodyContains: [],
+};
+
+function assertSentryEventCount(assert, count) {
+ assert.equal(window._sentryTestEvents.length, count, 'Check correct number of Sentry events were sent');
+}
+
+function assertSentryCall(assert, callNumber, options) {
+ const sentryTestEvents = window._sentryTestEvents;
+ const assertOptions = Object.assign({}, defaultAssertOptions, options);
+
+ const event = sentryTestEvents[callNumber];
+
+ /**
+ * Body could be parsed here to check exact properties, but that requires too much implementation specific detail,
+ * instead this loosely matches on contents to check the correct error is being sent.
+ */
+ assert.ok(assertOptions.errorBodyContains.length, 'Must pass strings to check against error body');
+ const errorBody = JSON.stringify(event);
+ assertOptions.errorBodyContains.forEach((bodyContent) => {
+ assert.ok(errorBody.includes(bodyContent), `Checking that error body includes ${bodyContent}`);
+ });
+}
+
+module('Acceptance | Sentry Errors', function (hooks) {
+ setupApplicationTest(hooks);
+
+ hooks.beforeEach(function () {
+ window._sentryTestEvents = [];
+ const errorMessages = [];
+ this.errorMessages = errorMessages;
+
+ /**
+ * Stub out fetch function to assert on Sentry calls.
+ */
+ this.fetchStub = sinon.stub(window, 'fetch');
+
+ /**
+ * Stops global test suite failures from unhandled rejections and allows assertion on them
+ */
+ this.qunitOnUnhandledRejection = sinon.stub(QUnit, 'onUnhandledRejection');
+
+ QUnit.onError = function ({ message }) {
+ errorMessages.push(message.split('Error: ')[1]);
+ return true;
+ };
+
+ Ember.onerror = function (...args) {
+ const [error] = args;
+ errorMessages.push(error.message);
+ throw error;
+ };
+
+ this._windowOnError = window.onerror;
+ /**
+ * Will collect errors when run via testem in cli
+ */
+
+ window.onerror = function (error, ...args) {
+ errorMessages.push(error.split('Error: ')[1]);
+ if (this._windowOnError) {
+ return this._windowOnError(error, ...args);
+ }
+ };
+ });
+
+ hooks.afterEach(function () {
+ this.fetchStub.restore();
+ this.qunitOnUnhandledRejection.restore();
+ window.onerror = this._windowOnError;
+ });
+
+ test('Check "Throw Generic Javascript Error"', async function (assert) {
+ await visit('/');
+ const button = find('[data-test-button="Throw Generic Javascript Error"]');
+
+ await click(button);
+
+ assertSentryEventCount(assert, 1);
+ assertSentryCall(assert, 0, { errorBodyContains: [...this.errorMessages] });
+ });
+
+ test('Check "Throw EmberError"', async function (assert) {
+ await visit('/');
+ const button = find('[data-test-button="Throw EmberError"]');
+
+ await click(button);
+
+ assertSentryEventCount(assert, 1);
+ assertSentryCall(assert, 0, { errorBodyContains: [...this.errorMessages] });
+ });
+
+ test('Check "Caught Thrown EmberError"', async function (assert) {
+ await visit('/');
+ const button = find('[data-test-button="Caught Thrown EmberError"]');
+
+ await click(button);
+
+ assertSentryEventCount(assert, 0);
+ });
+
+ test('Check "Error From Fetch"', async function (assert) {
+ this.fetchStub.onFirstCall().callsFake((...args) => {
+ return this.fetchStub.callsThrough(args);
+ });
+ await visit('/');
+ const button = find('[data-test-button="Error From Fetch"]');
+
+ await click(button);
+
+ const done = assert.async();
+
+ run.next(() => {
+ assertSentryEventCount(assert, 1);
+ assertSentryCall(assert, 0, { errorBodyContains: [...this.errorMessages] });
+ done();
+ });
+ });
+
+ test('Check "Error in AfterRender"', async function (assert) {
+ await visit('/');
+ const button = find('[data-test-button="Error in AfterRender"]');
+
+ await click(button);
+
+ assertSentryEventCount(assert, 1);
+ assert.ok(this.qunitOnUnhandledRejection.calledOnce, 'Uncaught rejection should only be called once');
+ assertSentryCall(assert, 0, { errorBodyContains: [...this.errorMessages] });
+ });
+
+ test('Check "RSVP Rejection"', async function (assert) {
+ await visit('/');
+ const button = find('[data-test-button="RSVP Rejection"]');
+
+ await click(button);
+
+ assertSentryEventCount(assert, 1);
+ assert.ok(this.qunitOnUnhandledRejection.calledOnce, 'Uncaught rejection should only be called once');
+ assertSentryCall(assert, 0, { errorBodyContains: [this.qunitOnUnhandledRejection.getCall(0).args[0]] });
+ });
+
+ test('Check "Error inside RSVP"', async function (assert) {
+ await visit('/');
+ const button = find('[data-test-button="Error inside RSVP"]');
+
+ await click(button);
+
+ assertSentryEventCount(assert, 1);
+ assert.ok(this.qunitOnUnhandledRejection.calledOnce, 'Uncaught rejection should only be called once');
+ assertSentryCall(assert, 0, { errorBodyContains: [...this.errorMessages] });
+ });
+});
diff --git a/packages/ember/tests/dummy/app/app.js b/packages/ember/tests/dummy/app/app.js
new file mode 100644
index 000000000000..6081cb2b951f
--- /dev/null
+++ b/packages/ember/tests/dummy/app/app.js
@@ -0,0 +1,31 @@
+import Application from '@ember/application';
+import Resolver from 'ember-resolver';
+import loadInitializers from 'ember-load-initializers';
+import config from './config/environment';
+import { InitSentryForEmber } from '@sentry/ember';
+
+import { Transports } from '@sentry/browser';
+import Ember from 'ember';
+
+class TestFetchTransport extends Transports.FetchTransport {
+ sendEvent(event) {
+ if (Ember.testing) {
+ if (!window._sentryTestEvents) {
+ window._sentryTestEvents = [];
+ }
+ window._sentryTestEvents.push(event);
+ return Promise.resolve();
+ }
+ return super.sendEvent(event);
+ }
+}
+
+InitSentryForEmber({ transport: TestFetchTransport });
+
+export default class App extends Application {
+ modulePrefix = config.modulePrefix;
+ podModulePrefix = config.podModulePrefix;
+ Resolver = Resolver;
+}
+
+loadInitializers(App, config.modulePrefix);
diff --git a/packages/ember/tests/dummy/app/components/.gitkeep b/packages/ember/tests/dummy/app/components/.gitkeep
new file mode 100644
index 000000000000..e69de29bb2d1
diff --git a/packages/ember/tests/dummy/app/components/test-section.ts b/packages/ember/tests/dummy/app/components/test-section.ts
new file mode 100644
index 000000000000..55706477346c
--- /dev/null
+++ b/packages/ember/tests/dummy/app/components/test-section.ts
@@ -0,0 +1,3 @@
+import Component from '@ember/component';
+
+export default Component.extend({});
diff --git a/packages/ember/tests/dummy/app/config/environment.d.ts b/packages/ember/tests/dummy/app/config/environment.d.ts
new file mode 100644
index 000000000000..3252cc3dec43
--- /dev/null
+++ b/packages/ember/tests/dummy/app/config/environment.d.ts
@@ -0,0 +1,16 @@
+export default config;
+
+/**
+ * Type declarations for
+ * import config from './config/environment'
+ *
+ * For now these need to be managed by the developer
+ * since different ember addons can materialize new entries.
+ */
+declare const config: {
+ environment: any;
+ modulePrefix: string;
+ podModulePrefix: string;
+ locationType: string;
+ rootURL: string;
+};
diff --git a/packages/ember/tests/dummy/app/controllers/.gitkeep b/packages/ember/tests/dummy/app/controllers/.gitkeep
new file mode 100644
index 000000000000..e69de29bb2d1
diff --git a/packages/ember/tests/dummy/app/controllers/application.js b/packages/ember/tests/dummy/app/controllers/application.js
new file mode 100644
index 000000000000..49ebe3e6cf45
--- /dev/null
+++ b/packages/ember/tests/dummy/app/controllers/application.js
@@ -0,0 +1,63 @@
+import Controller from '@ember/controller';
+import { tracked } from '@glimmer/tracking';
+import { action } from '@ember/object';
+import EmberError from '@ember/error';
+import { scheduleOnce } from '@ember/runloop';
+import RSVP from 'rsvp';
+
+export default class ApplicationController extends Controller {
+ @tracked showComponents;
+
+ @action
+ createError() {
+ this.nonExistentFunction();
+ }
+
+ @action
+ createEmberError() {
+ throw new EmberError('Whoops, looks like you have an EmberError');
+ }
+
+ @action
+ createCaughtEmberError() {
+ try {
+ throw new EmberError('Looks like you have a caught EmberError');
+ } catch(e) {
+ console.log(e);
+ }
+ }
+
+ @action
+ createFetchError() {
+ fetch('http://doesntexist.example');
+ }
+
+ @action
+ createAfterRenderError() {
+ function throwAfterRender() {
+ throw new Error('After Render Error');
+ }
+ scheduleOnce('afterRender', throwAfterRender);
+ }
+
+ @action
+ createRSVPRejection() {
+ const promise = new RSVP.Promise((resolve, reject) => {
+ reject('Promise rejected');
+ });
+ return promise;
+ }
+
+ @action
+ createRSVPError() {
+ const promise = new RSVP.Promise(() => {
+ throw new Error('Error within RSVP Promise');
+ });
+ return promise;
+ }
+
+ @action
+ toggleShowComponents() {
+ this.showComponents = !this.showComponents;
+ }
+}
diff --git a/packages/ember/tests/dummy/app/helpers/.gitkeep b/packages/ember/tests/dummy/app/helpers/.gitkeep
new file mode 100644
index 000000000000..e69de29bb2d1
diff --git a/packages/ember/tests/dummy/app/index.html b/packages/ember/tests/dummy/app/index.html
new file mode 100644
index 000000000000..644ceb7179b9
--- /dev/null
+++ b/packages/ember/tests/dummy/app/index.html
@@ -0,0 +1,26 @@
+
+
+
+
+
+ Dummy
+
+
+
+ {{content-for "head"}}
+
+
+
+
+ {{content-for "head-footer"}}
+
+
+ {{content-for "body"}}
+
+
+
+
+ {{content-for "body-footer"}}
+
+
+
diff --git a/packages/ember/tests/dummy/app/models/.gitkeep b/packages/ember/tests/dummy/app/models/.gitkeep
new file mode 100644
index 000000000000..e69de29bb2d1
diff --git a/packages/ember/tests/dummy/app/router.js b/packages/ember/tests/dummy/app/router.js
new file mode 100644
index 000000000000..224ca426a85e
--- /dev/null
+++ b/packages/ember/tests/dummy/app/router.js
@@ -0,0 +1,10 @@
+import EmberRouter from '@ember/routing/router';
+import config from './config/environment';
+
+export default class Router extends EmberRouter {
+ location = config.locationType;
+ rootURL = config.rootURL;
+}
+
+Router.map(function() {
+});
diff --git a/packages/ember/tests/dummy/app/routes/.gitkeep b/packages/ember/tests/dummy/app/routes/.gitkeep
new file mode 100644
index 000000000000..e69de29bb2d1
diff --git a/packages/ember/tests/dummy/app/styles/app.css b/packages/ember/tests/dummy/app/styles/app.css
new file mode 100644
index 000000000000..1f8593255fdf
--- /dev/null
+++ b/packages/ember/tests/dummy/app/styles/app.css
@@ -0,0 +1,160 @@
+:root {
+ --primary-fg-color: #6c5fc7;
+ --button-border-color: #413496;
+ --foreground-color: #2f2936;
+ --background-color: #f2f1f3;
+ --content-border-color: #e2dee6;
+ --button-background-hover-color: #5b4cc0;
+}
+
+html {
+ height: 100vh;
+}
+
+body {
+ background: var(--background-color) url('/assets/images/sentry-pattern-transparent.png');
+ background-size: 340px;
+ background-repeat: repeat;
+ height: 100%;
+ margin: 0;
+ font-family: Rubik,Avenir Next,Helvetica Neue,sans-serif;
+ font-size: 16px;
+ line-height: 24px;
+ color: var(--foreground-color);
+}
+
+.app {
+ display: flex;
+ flex-direction: column;
+ flex-grow: 1;
+ align-items: center;
+}
+
+.container {
+ position: relative;
+ padding-left: 30px;
+ padding-right: 30px;
+ padding-top: 5vh;
+ width: 100%;
+ max-width: 740px;
+ flex: 1;
+}
+
+.box {
+ background-color: #fff;
+ border: 0;
+ box-shadow: 0 0 0 1px rgba(0,0,0,.08),0 1px 4px rgba(0,0,0,.1);
+ border-radius: 4px;
+ display: flex;
+ width: 100%;
+ margin: 0 0 20px;
+}
+
+.sidebar {
+ padding-top: 20px;
+ width: 60px;
+ background: #564f64;
+ background-image: linear-gradient(-180deg,rgba(52,44,62,0),rgba(52,44,62,.5));
+ box-shadow: 0 2px 0 0 rgba(0,0,0,.1);
+ border-radius: 4px 0 0 4px;
+ margin-top: -1px;
+ margin-bottom: -1px;
+ text-align: center;
+
+ display: flex;
+ justify-content: center;
+ padding-top: 20px;
+ padding-bottom: 20px;
+}
+
+.logo {
+ width: 24px;
+ height: 24px;
+ background-image: url('/assets/images/sentry-logo.svg');
+}
+
+section.content {
+ flex: 1;
+ padding-bottom: 40px;
+}
+
+h1, h2, h3, h4, h5, h6 {
+ font-weight: 600;
+}
+
+h3 {
+ font-size: 24px;
+ line-height: 1.2;
+}
+
+div.section {
+ margin-top: 20px;
+}
+
+.section h4 {
+ margin-bottom: 10px;
+}
+
+.content-container {
+ padding-left: 40px;
+ padding-right: 40px;
+ padding-top: 20px;
+}
+
+.content-container h3, .content-container h4 {
+ margin-top: 0px;
+}
+
+.border-bottom {
+ border-bottom: 1px solid var(--content-border-color);
+}
+
+button {
+ border-radius: 3px;
+ font-weight: 600;
+ padding: 8px 16px;
+ transition: all .1s;
+
+ border: 1px solid transparent;
+ border-radius: 3px;
+ font-weight: 600;
+ padding: 8px 16px;
+
+ -webkit-appearance: button;
+ cursor: pointer;
+}
+
+button:hover {
+ text-decoration: none;
+}
+
+button:focus {
+ outline-offset: -2px;
+}
+
+button.primary {
+ color: #fff;
+ background-color: var(--primary-fg-color);
+ border-color: var(--button-border-color);
+
+ display: inline-block;
+
+ text-shadow: 0 -1px 0 rgba(0,0,0,.15);
+ box-shadow: 0 2px 0 rgba(0,0,0,.08);
+
+ text-transform: none;
+ overflow: visible;
+}
+
+button.primary:hover {
+ background-color: var(--button-background-hover-color);
+ border-color: #204d74;
+}
+
+button.primary:focus {
+ background: #5b4cc0;
+ border-color: #3a2f87;
+ box-shadow: inset 0 2px 0 rgba(0,0,0,.12);
+ outline: none;
+}
+
diff --git a/packages/ember/tests/dummy/app/templates/application.hbs b/packages/ember/tests/dummy/app/templates/application.hbs
new file mode 100644
index 000000000000..6dac270780f8
--- /dev/null
+++ b/packages/ember/tests/dummy/app/templates/application.hbs
@@ -0,0 +1,54 @@
+
+
+
+
+
+
+
+
Sentry Instrumented Ember Application
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+{{outlet}}
diff --git a/packages/ember/tests/dummy/app/templates/components/test-section.hbs b/packages/ember/tests/dummy/app/templates/components/test-section.hbs
new file mode 100644
index 000000000000..9204d2d7571b
--- /dev/null
+++ b/packages/ember/tests/dummy/app/templates/components/test-section.hbs
@@ -0,0 +1,6 @@
+
+
{{@title}}
+
+
diff --git a/packages/ember/tests/dummy/config/environment.js b/packages/ember/tests/dummy/config/environment.js
new file mode 100644
index 000000000000..bb0f5c2608c6
--- /dev/null
+++ b/packages/ember/tests/dummy/config/environment.js
@@ -0,0 +1,61 @@
+'use strict';
+
+module.exports = function (environment) {
+ let ENV = {
+ modulePrefix: 'dummy',
+ environment,
+ rootURL: '/',
+ locationType: 'auto',
+ EmberENV: {
+ FEATURES: {
+ // Here you can enable experimental features on an ember canary build
+ // e.g. EMBER_NATIVE_DECORATOR_SUPPORT: true
+ },
+ EXTEND_PROTOTYPES: {
+ // Prevent Ember Data from overriding Date.parse.
+ Date: false,
+ },
+ },
+
+ APP: {
+ // Here you can pass flags/options to your application instance
+ // when it is created
+ },
+ };
+
+ ENV['@sentry/ember'] = {
+ sentry: {
+ dsn: process.env.SENTRY_DSN,
+ },
+ ignoreEmberOnErrorWarning: true,
+ };
+
+ if (environment === 'development') {
+ // ENV.APP.LOG_RESOLVER = true;
+ // ENV.APP.LOG_ACTIVE_GENERATION = true;
+ // ENV.APP.LOG_TRANSITIONS = true;
+ // ENV.APP.LOG_TRANSITIONS_INTERNAL = true;
+ // ENV.APP.LOG_VIEW_LOOKUPS = true;
+ }
+
+ if (environment === 'test') {
+ // Testem prefers this...
+ ENV.locationType = 'none';
+
+ // keep test console output quieter
+ ENV.APP.LOG_ACTIVE_GENERATION = false;
+ ENV.APP.LOG_VIEW_LOOKUPS = false;
+
+ ENV.APP.rootElement = '#ember-testing';
+ ENV.APP.autoboot = false;
+
+ // Include fake dsn so that instrumentation is enabled when running from cli
+ ENV['@sentry/ember'].sentry.dsn = 'https://0@0.ingest.sentry.io/0';
+ }
+
+ if (environment === 'production') {
+ // here you can enable a production-specific feature
+ }
+
+ return ENV;
+};
diff --git a/packages/ember/tests/dummy/config/optional-features.json b/packages/ember/tests/dummy/config/optional-features.json
new file mode 100644
index 000000000000..b26286e2ecdf
--- /dev/null
+++ b/packages/ember/tests/dummy/config/optional-features.json
@@ -0,0 +1,6 @@
+{
+ "application-template-wrapper": false,
+ "default-async-observers": true,
+ "jquery-integration": false,
+ "template-only-glimmer-components": true
+}
diff --git a/packages/ember/tests/dummy/config/targets.js b/packages/ember/tests/dummy/config/targets.js
new file mode 100644
index 000000000000..8ffae36361e8
--- /dev/null
+++ b/packages/ember/tests/dummy/config/targets.js
@@ -0,0 +1,18 @@
+'use strict';
+
+const browsers = [
+ 'last 1 Chrome versions',
+ 'last 1 Firefox versions',
+ 'last 1 Safari versions'
+];
+
+const isCI = !!process.env.CI;
+const isProduction = process.env.EMBER_ENV === 'production';
+
+if (isCI || isProduction) {
+ browsers.push('ie 11');
+}
+
+module.exports = {
+ browsers
+};
diff --git a/packages/ember/tests/dummy/public/assets/images/sentry-logo.svg b/packages/ember/tests/dummy/public/assets/images/sentry-logo.svg
new file mode 100644
index 000000000000..bac4e57b7790
--- /dev/null
+++ b/packages/ember/tests/dummy/public/assets/images/sentry-logo.svg
@@ -0,0 +1 @@
+
diff --git a/packages/ember/tests/dummy/public/assets/images/sentry-pattern-transparent.png b/packages/ember/tests/dummy/public/assets/images/sentry-pattern-transparent.png
new file mode 100644
index 000000000000..1f7312b5f6af
Binary files /dev/null and b/packages/ember/tests/dummy/public/assets/images/sentry-pattern-transparent.png differ
diff --git a/packages/ember/tests/dummy/public/robots.txt b/packages/ember/tests/dummy/public/robots.txt
new file mode 100644
index 000000000000..f5916452e5ff
--- /dev/null
+++ b/packages/ember/tests/dummy/public/robots.txt
@@ -0,0 +1,3 @@
+# http://www.robotstxt.org
+User-agent: *
+Disallow:
diff --git a/packages/ember/tests/helpers/.gitkeep b/packages/ember/tests/helpers/.gitkeep
new file mode 100644
index 000000000000..e69de29bb2d1
diff --git a/packages/ember/tests/index.html b/packages/ember/tests/index.html
new file mode 100644
index 000000000000..5209b8523212
--- /dev/null
+++ b/packages/ember/tests/index.html
@@ -0,0 +1,33 @@
+
+
+
+
+
+ Dummy Tests
+
+
+
+ {{content-for "head"}}
+ {{content-for "test-head"}}
+
+
+
+
+
+ {{content-for "head-footer"}}
+ {{content-for "test-head-footer"}}
+
+
+ {{content-for "body"}}
+ {{content-for "test-body"}}
+
+
+
+
+
+
+
+ {{content-for "body-footer"}}
+ {{content-for "test-body-footer"}}
+
+
diff --git a/packages/ember/tests/integration/.gitkeep b/packages/ember/tests/integration/.gitkeep
new file mode 100644
index 000000000000..e69de29bb2d1
diff --git a/packages/ember/tests/test-helper.js b/packages/ember/tests/test-helper.js
new file mode 100644
index 000000000000..2a1ea0da4641
--- /dev/null
+++ b/packages/ember/tests/test-helper.js
@@ -0,0 +1,17 @@
+import sinon from 'sinon';
+import * as Sentry from '@sentry/browser';
+
+/**
+ * Stub Sentry init function before application is imported to avoid actually setting up Sentry and needing a DSN
+ */
+sinon.stub(Sentry, 'init');
+
+import Application from '../app';
+import config from '../config/environment';
+import { setApplication } from '@ember/test-helpers';
+import { start } from 'ember-qunit';
+
+setApplication(Application.create(config.APP));
+
+start();
+QUnit.config.ignoreGlobalErrors = true;
diff --git a/packages/ember/tests/unit/.gitkeep b/packages/ember/tests/unit/.gitkeep
new file mode 100644
index 000000000000..e69de29bb2d1
diff --git a/packages/ember/tsconfig.json b/packages/ember/tsconfig.json
new file mode 100644
index 000000000000..71c52a3c72d9
--- /dev/null
+++ b/packages/ember/tsconfig.json
@@ -0,0 +1,28 @@
+{
+ "extends": "../../tsconfig.json",
+ "compilerOptions": {
+ "target": "es2017",
+ "allowJs": true,
+ "moduleResolution": "node",
+ "allowSyntheticDefaultImports": true,
+ "alwaysStrict": true,
+ "noImplicitUseStrict": false,
+ "strictNullChecks": true,
+ "strictPropertyInitialization": true,
+ "noEmitOnError": false,
+ "noEmit": true,
+ "baseUrl": ".",
+ "module": "es6",
+ "experimentalDecorators": true,
+ "paths": {
+ "dummy/tests/*": ["tests/*"],
+ "dummy/*": ["tests/dummy/app/*", "app/*"],
+ "@sentry/ember": ["addon"],
+ "@sentry/ember/*": ["addon/*"],
+ "@sentry/ember/test-support": ["addon-test-support"],
+ "@sentry/ember/test-support/*": ["addon-test-support/*"],
+ "*": ["types/*"]
+ }
+ },
+ "include": ["app/**/*", "addon/**/*", "tests/**/*", "types/**/*", "test-support/**/*", "addon-test-support/**/*"]
+}
diff --git a/packages/ember/types/dummy/index.d.ts b/packages/ember/types/dummy/index.d.ts
new file mode 100644
index 000000000000..8b137891791f
--- /dev/null
+++ b/packages/ember/types/dummy/index.d.ts
@@ -0,0 +1 @@
+
diff --git a/packages/ember/types/global.d.ts b/packages/ember/types/global.d.ts
new file mode 100644
index 000000000000..f79db55d2502
--- /dev/null
+++ b/packages/ember/types/global.d.ts
@@ -0,0 +1,6 @@
+// Types for compiled templates
+declare module '@sentry/ember/templates/*' {
+ import { TemplateFactory } from 'htmlbars-inline-precompile';
+ const tmpl: TemplateFactory;
+ export default tmpl;
+}
diff --git a/scripts/test.sh b/scripts/test.sh
index 1b0c388ee75e..1e1ca06d2383 100755
--- a/scripts/test.sh
+++ b/scripts/test.sh
@@ -6,15 +6,15 @@ source ~/.nvm/nvm.sh
if [[ "$(cut -d. -f1 <<< "$TRAVIS_NODE_VERSION")" -le 6 ]]; then
nvm use 8
yarn install --ignore-engines --ignore-scripts
- yarn build
+ yarn build --ignore="@sentry/ember"
nvm use 6
- yarn test --ignore="@sentry/browser" --ignore="@sentry/integrations" --ignore="@sentry/react" --ignore="@sentry/tracing" # latest version of karma doesn't run on node 6
+ yarn test --ignore="@sentry/browser" --ignore="@sentry/integrations" --ignore="@sentry/react" --ignore="@sentry/ember" --ignore="@sentry/tracing" # latest version of karma doesn't run on node 6
elif [[ "$(cut -d. -f1 <<< "$TRAVIS_NODE_VERSION")" -le 8 ]]; then
yarn install --ignore-engines --ignore-scripts
- yarn build
- yarn test --ignore="@sentry/tracing" --ignore="@sentry/react"
+ yarn build --ignore="@sentry/ember"
+ yarn test --ignore="@sentry/tracing" --ignore="@sentry/react" --ignore="@sentry/ember"
else
yarn install
- yarn build
- yarn test
+ yarn build --ignore="@sentry/ember"
+ yarn test --ignore="@sentry/ember"
fi