Skip to content

Commit 005f40d

Browse files
Lms24mydea
andauthored
ref(core): Use versioned carrier on global object (#12206)
This PR implements a versioned Sentry carrier as described in #12188. The idea is that SDKs can from now on access their global Sentry instance and thereby no longer overwrite or interfere with potentially other SDKs (e.g. 3rd party libraries, scripts, etc). Internally, SDKs can access their carrier via the `window.__SENTRY__[SDK_VERSION]`. Externally (spotlight, loader script) via `window.__SENTRY__[window.__SENTRY__.version]`. --------- Co-authored-by: Francesco Novy <[email protected]>
1 parent fb1ed25 commit 005f40d

File tree

24 files changed

+411
-90
lines changed

24 files changed

+411
-90
lines changed
Lines changed: 137 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,139 @@
1-
!function(n,e,r,t,i,o,a,c,s){for(var u=s,f=0;f<document.scripts.length;f++)if(document.scripts[f].src.indexOf(o)>-1){u&&"no"===document.scripts[f].getAttribute("data-lazy")&&(u=!1);break}var p=[];function l(n){return"e"in n}function d(n){return"p"in n}function _(n){return"f"in n}var v=[];function y(n){u&&(l(n)||d(n)||_(n)&&n.f.indexOf("capture")>-1||_(n)&&n.f.indexOf("showReportDialog")>-1)&&m(),v.push(n)}function g(){y({e:[].slice.call(arguments)})}function h(n){y({p:n})}function E(){try{n.SENTRY_SDK_SOURCE="loader";var e=n[i],o=e.init;e.init=function(i){n.removeEventListener(r,g),n.removeEventListener(t,h);var a=c;for(var s in i)Object.prototype.hasOwnProperty.call(i,s)&&(a[s]=i[s]);!function(n,e){var r=n.integrations||[];if(!Array.isArray(r))return;var t=r.map((function(n){return n.name}));n.tracesSampleRate&&-1===t.indexOf("BrowserTracing")&&(e.BrowserTracing?r.push(new e.BrowserTracing):e.browserTracingIntegration&&r.push(e.browserTracingIntegration()));(n.replaysSessionSampleRate||n.replaysOnErrorSampleRate)&&-1===t.indexOf("Replay")&&(e.Replay?r.push(new e.Replay):e.replayIntegration&&r.push(e.replayIntegration()));n.integrations=r}(a,e),o(a)},setTimeout((function(){return function(e){try{"function"==typeof n.sentryOnLoad&&(n.sentryOnLoad(),n.sentryOnLoad=void 0);for(var r=0;r<p.length;r++)"function"==typeof p[r]&&p[r]();p.splice(0);for(r=0;r<v.length;r++){_(o=v[r])&&"init"===o.f&&e.init.apply(e,o.a)}L()||e.init();var t=n.onerror,i=n.onunhandledrejection;for(r=0;r<v.length;r++){var o;if(_(o=v[r])){if("init"===o.f)continue;e[o.f].apply(e,o.a)}else l(o)&&t?t.apply(n,o.e):d(o)&&i&&i.apply(n,[o.p])}}catch(n){console.error(n)}}(e)}))}catch(n){console.error(n)}}var O=!1;function m(){if(!O){O=!0;var n=e.scripts[0],r=e.createElement("script");r.src=a,r.crossOrigin="anonymous",r.addEventListener("load",E,{once:!0,passive:!0}),n.parentNode.insertBefore(r,n)}}function L(){var e=n.__SENTRY__;return!(void 0===e||!e.hub||!e.hub.getClient())}n[i]=n[i]||{},n[i].onLoad=function(n){L()?n():p.push(n)},n[i].forceLoad=function(){setTimeout((function(){m()}))},["init","addBreadcrumb","captureMessage","captureException","captureEvent","configureScope","withScope","showReportDialog"].forEach((function(e){n[i][e]=function(){y({f:e,a:arguments})}})),n.addEventListener(r,g),n.addEventListener(t,h),u||setTimeout((function(){m()}))}
2-
(
1+
!(function (n, e, r, t, i, o, a, c, s) {
2+
for (var u = s, f = 0; f < document.scripts.length; f++)
3+
if (document.scripts[f].src.indexOf(o) > -1) {
4+
u && 'no' === document.scripts[f].getAttribute('data-lazy') && (u = !1);
5+
break;
6+
}
7+
var p = [];
8+
function l(n) {
9+
return 'e' in n;
10+
}
11+
function d(n) {
12+
return 'p' in n;
13+
}
14+
function _(n) {
15+
return 'f' in n;
16+
}
17+
var v = [];
18+
function y(n) {
19+
u &&
20+
(l(n) || d(n) || (_(n) && n.f.indexOf('capture') > -1) || (_(n) && n.f.indexOf('showReportDialog') > -1)) &&
21+
m(),
22+
v.push(n);
23+
}
24+
function g() {
25+
y({ e: [].slice.call(arguments) });
26+
}
27+
function h(n) {
28+
y({ p: n });
29+
}
30+
function E() {
31+
try {
32+
n.SENTRY_SDK_SOURCE = 'loader';
33+
var e = n[i],
34+
o = e.init;
35+
(e.init = function (i) {
36+
n.removeEventListener(r, g), n.removeEventListener(t, h);
37+
var a = c;
38+
for (var s in i) Object.prototype.hasOwnProperty.call(i, s) && (a[s] = i[s]);
39+
!(function (n, e) {
40+
var r = n.integrations || [];
41+
if (!Array.isArray(r)) return;
42+
var t = r.map(function (n) {
43+
return n.name;
44+
});
45+
n.tracesSampleRate &&
46+
-1 === t.indexOf('BrowserTracing') &&
47+
(e.BrowserTracing
48+
? r.push(new e.BrowserTracing())
49+
: e.browserTracingIntegration && r.push(e.browserTracingIntegration()));
50+
(n.replaysSessionSampleRate || n.replaysOnErrorSampleRate) &&
51+
-1 === t.indexOf('Replay') &&
52+
(e.Replay ? r.push(new e.Replay()) : e.replayIntegration && r.push(e.replayIntegration()));
53+
n.integrations = r;
54+
})(a, e),
55+
o(a);
56+
}),
57+
setTimeout(function () {
58+
return (function (e) {
59+
try {
60+
'function' == typeof n.sentryOnLoad && (n.sentryOnLoad(), (n.sentryOnLoad = void 0));
61+
for (var r = 0; r < p.length; r++) 'function' == typeof p[r] && p[r]();
62+
p.splice(0);
63+
for (r = 0; r < v.length; r++) {
64+
_((o = v[r])) && 'init' === o.f && e.init.apply(e, o.a);
65+
}
66+
L() || e.init();
67+
var t = n.onerror,
68+
i = n.onunhandledrejection;
69+
for (r = 0; r < v.length; r++) {
70+
var o;
71+
if (_((o = v[r]))) {
72+
if ('init' === o.f) continue;
73+
e[o.f].apply(e, o.a);
74+
} else l(o) && t ? t.apply(n, o.e) : d(o) && i && i.apply(n, [o.p]);
75+
}
76+
} catch (n) {
77+
console.error(n);
78+
}
79+
})(e);
80+
});
81+
} catch (n) {
82+
console.error(n);
83+
}
84+
}
85+
var O = !1;
86+
function m() {
87+
if (!O) {
88+
O = !0;
89+
var n = e.scripts[0],
90+
r = e.createElement('script');
91+
(r.src = a),
92+
(r.crossOrigin = 'anonymous'),
93+
r.addEventListener('load', E, { once: !0, passive: !0 }),
94+
n.parentNode.insertBefore(r, n);
95+
}
96+
}
97+
function L() {
98+
var e = n.__SENTRY__;
99+
100+
// TODO: This is a temporary hack to make the loader script compatible with the versioned
101+
// carrier. This needs still needs to be added to the actual loader script before we
102+
// release the loader for v8!
103+
var v = e && e.version && e[e.version];
104+
105+
return !(void 0 === e || !e.hub || !e.hub.getClient()) || !!v;
106+
}
107+
(n[i] = n[i] || {}),
108+
(n[i].onLoad = function (n) {
109+
L() ? n() : p.push(n);
110+
}),
111+
(n[i].forceLoad = function () {
112+
setTimeout(function () {
113+
m();
114+
});
115+
}),
116+
[
117+
'init',
118+
'addBreadcrumb',
119+
'captureMessage',
120+
'captureException',
121+
'captureEvent',
122+
'configureScope',
123+
'withScope',
124+
'showReportDialog',
125+
].forEach(function (e) {
126+
n[i][e] = function () {
127+
y({ f: e, a: arguments });
128+
};
129+
}),
130+
n.addEventListener(r, g),
131+
n.addEventListener(t, h),
132+
u ||
133+
setTimeout(function () {
134+
m();
135+
});
136+
})(
3137
window,
4138
document,
5139
'error',
@@ -8,5 +142,5 @@
8142
'loader.js',
9143
__LOADER_BUNDLE__,
10144
__LOADER_OPTIONS__,
11-
__LOADER_LAZY__
145+
__LOADER_LAZY__,
12146
);

dev-packages/browser-integration-tests/loader-suites/loader/onLoad/customInit/init.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,5 +16,9 @@ window.sentryIsLoaded = () => {
1616
const __sentry = window.__SENTRY__;
1717

1818
// If there is a global __SENTRY__ that means that in any of the callbacks init() was already invoked
19-
return !!(!(typeof __sentry === 'undefined') && __sentry.hub && __sentry.hub.getClient());
19+
return !!(
20+
!(typeof __sentry === 'undefined') &&
21+
__sentry.version &&
22+
!!__sentry[__sentry.version]
23+
);
2024
};
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import * as Sentry from '@sentry/browser';
2+
3+
/**
4+
* This simulates an relatively new v7 SDK setting acs on the __SENTRY__ carrier.
5+
* see: https://github.com/getsentry/sentry-javascript/issues/12054
6+
*/
7+
window.__SENTRY__ = {
8+
acs: {
9+
getCurrentScope: () => {
10+
return 'scope';
11+
},
12+
},
13+
};
14+
15+
window.Sentry = Sentry;
16+
17+
Sentry.init({
18+
dsn: 'https://[email protected]/1337',
19+
});
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
const sentryCarrier = window && window.__SENTRY__;
2+
3+
/**
4+
* Simulate an old pre v8 SDK obtaining the hub from the global sentry carrier
5+
* and checking for the hub version.
6+
*/
7+
const res = sentryCarrier.acs && sentryCarrier.acs.getCurrentScope();
8+
9+
// Write back result into the document
10+
document.getElementById('currentScope').innerText = res && 'scope';
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<meta charset="utf-8" />
5+
</head>
6+
<body>
7+
<p id="currentScope"></p>
8+
</body>
9+
</html>
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { expect } from '@playwright/test';
2+
3+
import { sentryTest } from '../../../../utils/fixtures';
4+
5+
sentryTest(
6+
"doesn't crash if older SDKs access `acs.getCurrentScope` on the global object",
7+
async ({ getLocalTestUrl, page }) => {
8+
const url = await getLocalTestUrl({ testDir: __dirname });
9+
await page.goto(url);
10+
11+
await expect(page.locator('#currentScope')).toHaveText('scope');
12+
},
13+
);
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import * as Sentry from '@sentry/browser';
2+
3+
/**
4+
* This simulates an old, pre-v8 SDK setting itself up on the global __SENTRY__ carrier.
5+
* see: https://github.com/getsentry/sentry-javascript/issues/12155
6+
*/
7+
window.__SENTRY__ = {
8+
hub: {
9+
isOlderThan: version => {
10+
return version < 7;
11+
},
12+
},
13+
};
14+
15+
window.Sentry = Sentry;
16+
17+
Sentry.init({
18+
dsn: 'https://[email protected]/1337',
19+
});
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
const sentryCarrier = window && window.__SENTRY__;
2+
3+
/**
4+
* Simulate an old pre v8 SDK obtaining the hub from the global sentry carrier
5+
* and checking for the hub version.
6+
*/
7+
const res = sentryCarrier.hub && sentryCarrier.hub.isOlderThan(7);
8+
9+
// Write back result into the document
10+
document.getElementById('olderThan').innerText = res;
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<meta charset="utf-8" />
5+
</head>
6+
<body>
7+
<p id="olderThan"></p>
8+
</body>
9+
</html>
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { expect } from '@playwright/test';
2+
3+
import { sentryTest } from '../../../../utils/fixtures';
4+
5+
sentryTest(
6+
"doesn't crash if older SDKs access `hub.isOlderThan` on the global object",
7+
async ({ getLocalTestUrl, page }) => {
8+
const url = await getLocalTestUrl({ testDir: __dirname });
9+
await page.goto(url);
10+
11+
await expect(page.locator('#olderThan')).toHaveText('false');
12+
},
13+
);

packages/core/package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,6 @@
6363
"lint": "eslint . --format stylish",
6464
"test": "jest",
6565
"test:watch": "jest --watch",
66-
"version": "node ../../scripts/versionbump.js src/version.ts",
6766
"yalc:publish": "ts-node ../../scripts/prepack.ts && yalc publish build --push --sig"
6867
},
6968
"volta": {

packages/core/src/asyncContext/stackStrategy.ts

Lines changed: 5 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -132,29 +132,19 @@ export class AsyncContextStack {
132132
*/
133133
function getAsyncContextStack(): AsyncContextStack {
134134
const registry = getMainCarrier();
135+
const sentry = getSentryCarrier(registry);
135136

136-
// For now we continue to keep this as `hub` on the ACS,
137-
// as e.g. the Loader Script relies on this.
138-
// Eventually we may change this if/when we update the loader to not require this field anymore
139-
// Related, we also write to `hub` in {@link ./../sdk.ts registerClientOnGlobalHub}
140-
const sentry = getSentryCarrier(registry) as { hub?: AsyncContextStack };
141-
142-
if (sentry.hub) {
143-
return sentry.hub;
144-
}
145-
146-
sentry.hub = new AsyncContextStack(getDefaultCurrentScope(), getDefaultIsolationScope());
147-
return sentry.hub;
137+
return (sentry.stack = sentry.stack || new AsyncContextStack(getDefaultCurrentScope(), getDefaultIsolationScope()));
148138
}
149139

150140
function withScope<T>(callback: (scope: ScopeInterface) => T): T {
151141
return getAsyncContextStack().withScope(callback);
152142
}
153143

154144
function withSetScope<T>(scope: ScopeInterface, callback: (scope: ScopeInterface) => T): T {
155-
const hub = getAsyncContextStack() as AsyncContextStack;
156-
return hub.withScope(() => {
157-
hub.getStackTop().scope = scope;
145+
const stack = getAsyncContextStack() as AsyncContextStack;
146+
return stack.withScope(() => {
147+
stack.getStackTop().scope = scope;
158148
return callback(scope);
159149
});
160150
}

packages/core/src/carrier.ts

Lines changed: 23 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,32 @@
1-
import type { Integration } from '@sentry/types';
2-
import { GLOBAL_OBJ } from '@sentry/utils';
1+
import type { Client, Integration, MetricsAggregator, Scope } from '@sentry/types';
2+
import { GLOBAL_OBJ, SDK_VERSION } from '@sentry/utils';
3+
import type { AsyncContextStack } from './asyncContext/stackStrategy';
34
import type { AsyncContextStrategy } from './asyncContext/types';
45

56
/**
6-
* An object that contains a hub and maintains a scope stack.
7+
* An object that contains globally accessible properties and maintains a scope stack.
78
* @hidden
89
*/
910
export interface Carrier {
10-
__SENTRY__?: SentryCarrier;
11+
__SENTRY__?: VersionedCarrier;
1112
}
1213

14+
type VersionedCarrier = {
15+
version?: string;
16+
} & Record<Exclude<string, 'version'>, SentryCarrier>;
17+
1318
interface SentryCarrier {
1419
acs?: AsyncContextStrategy;
15-
}
20+
stack?: AsyncContextStack;
1621

17-
/**
18-
* An object that contains a hub and maintains a scope stack.
19-
* @hidden
20-
*/
21-
export interface Carrier {
22-
__SENTRY__?: SentryCarrier;
23-
}
22+
globalScope?: Scope;
23+
defaultIsolationScope?: Scope;
24+
defaultCurrentScope?: Scope;
25+
globalMetricsAggregators?: WeakMap<Client, MetricsAggregator> | undefined;
2426

25-
interface SentryCarrier {
26-
acs?: AsyncContextStrategy;
27-
/**
28-
* Extra Hub properties injected by various SDKs
29-
*/
27+
// TODO(v9): Remove these properties - they are no longer used and were left over in v8
3028
integrations?: Integration[];
3129
extensions?: {
32-
/** Extension methods for the hub, which are bound to the current Hub instance */
3330
// eslint-disable-next-line @typescript-eslint/ban-types
3431
[key: string]: Function;
3532
};
@@ -50,10 +47,12 @@ export function getMainCarrier(): Carrier {
5047

5148
/** Will either get the existing sentry carrier, or create a new one. */
5249
export function getSentryCarrier(carrier: Carrier): SentryCarrier {
53-
if (!carrier.__SENTRY__) {
54-
carrier.__SENTRY__ = {
55-
extensions: {},
56-
};
57-
}
58-
return carrier.__SENTRY__;
50+
const __SENTRY__ = (carrier.__SENTRY__ = carrier.__SENTRY__ || {});
51+
52+
// For now: First SDK that sets the .version property wins
53+
__SENTRY__.version = __SENTRY__.version || SDK_VERSION;
54+
55+
// Intentionally populating and returning the version of "this" SDK instance
56+
// rather than what's set in .version so that "this" SDK always gets its carrier
57+
return (__SENTRY__[SDK_VERSION] = __SENTRY__[SDK_VERSION] || {});
5958
}

packages/core/src/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,6 @@ export { initAndBind, setCurrentClient } from './sdk';
5656
export { createTransport } from './transports/base';
5757
export { makeOfflineTransport } from './transports/offline';
5858
export { makeMultiplexedTransport } from './transports/multiplexed';
59-
export { SDK_VERSION } from './version';
6059
export {
6160
getIntegrationsToSetup,
6261
addIntegration,
@@ -107,3 +106,5 @@ export { captureFeedback } from './feedback';
107106

108107
// eslint-disable-next-line deprecation/deprecation
109108
export { getCurrentHubShim, getCurrentHub } from './getCurrentHubShim';
109+
110+
export { SDK_VERSION } from '@sentry/utils';

0 commit comments

Comments
 (0)