Skip to content

Commit 08e4553

Browse files
authored
Signal improvements (#1178)
1 parent 11a943e commit 08e4553

File tree

11 files changed

+181
-26
lines changed

11 files changed

+181
-26
lines changed

.changeset/wise-coats-refuse.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
'@segment/analytics-signals': patch
3+
---
4+
5+
* Refactor disallowList logic to never allow api.segment.io
6+
* Update README
7+
* Export SignalsPlugin in umd as well as global

packages/signals/signals/README.md

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,22 +12,20 @@ See: [settings.ts](src/types/settings.ts)
1212
<head>
1313
<title>My Website</title>
1414

15-
<!-- Load SignalsPlugin -->
16-
<script src="https://cdn.jsdelivr.net/npm/@segment/analytics-signals@latest/dist/umd/analytics-signals.umd.js"></script>
17-
18-
<!-- Load Segment (copy snippet from app.segment.com) -->
15+
<!-- Load Segment (find and replace 'MY_WRITEKEY') -->
1916
<script>
20-
!function(){var i="analytics",analytics=window[i]... // etc
21-
analytics.load("<YOUR_WRITE_KEY>");
17+
!function(){var i="analytics",analytics=window[i]=window[i]||[];if(!analytics.initialize)if(analytics.invoked)window.console&&console.error&&console.error("Segment snippet included twice.");else{analytics.invoked=!0;analytics.methods=["trackSubmit","trackClick","trackLink","trackForm","pageview","identify","reset","group","track","ready","alias","debug","page","screen","once","off","on","addSourceMiddleware","addIntegrationMiddleware","setAnonymousId","addDestinationMiddleware","register"];analytics.factory=function(e){return function(){if(window[i].initialized)return window[i][e].apply(window[i],arguments);var n=Array.prototype.slice.call(arguments);if(["track","screen","alias","group","page","identify"].indexOf(e)>-1){var c=document.querySelector("link[rel='canonical']");n.push({__t:"bpc",c:c&&c.getAttribute("href")||void 0,p:location.pathname,u:location.href,s:location.search,t:document.title,r:document.referrer})}n.unshift(e);analytics.push(n);return analytics}};for(var n=0;n<analytics.methods.length;n++){var key=analytics.methods[n];analytics[key]=analytics.factory(key)}analytics.load=function(key,n){var t=document.createElement("script");t.type="text/javascript";t.async=!0;t.setAttribute("data-global-segment-analytics-key",i);t.src="https://cdn.segment.com/analytics.js/v1/" + key + "/analytics.min.js";var r=document.getElementsByTagName("script")[0];r.parentNode.insertBefore(t,r);analytics._loadOptions=n};analytics._writeKey="MY_WRITEKEY";;analytics.SNIPPET_VERSION="5.2.0";
2218
analytics.page();
23-
}()
19+
}}();
2420
</script>
25-
26-
<!-- Register SignalsPlugin -->
21+
<!-- Register SignalsPlugin -->
22+
<script src="https://cdn.jsdelivr.net/npm/@segment/analytics-signals@latest/dist/umd/analytics-signals.umd.js"></script>
2723
<script>
28-
const signalsPlugin = new SignalsPlugin()
24+
var signalsPlugin = new SignalsPlugin()
2925
analytics.register(signalsPlugin)
26+
analytics.load(analytics._writeKey)
3027
</script>
28+
3129
</head>
3230
```
3331

packages/signals/signals/package.json

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,9 @@
3131
"build:esm": "yarn tsc -p tsconfig.build.json",
3232
"build:cjs": "yarn tsc -p tsconfig.build.json --outDir ./dist/cjs --module commonjs",
3333
"build:bundle": "NODE_ENV=production yarn run webpack",
34-
"build:bundle-dev": "NODE_ENV=development yarn run webpack",
3534
"workerbox": "node scripts/build-workerbox.js",
3635
"assert-workerbox-built": "sh scripts/assert-workerbox-built.sh",
37-
"watch": "yarn concurrently 'yarn build:bundle-dev --watch' 'yarn build:esm --watch'",
36+
"watch": "yarn concurrently 'yarn build:bundle --watch' 'yarn build:esm --watch'",
3837
"version": "sh scripts/version.sh",
3938
"watch:test": "yarn test --watch",
4039
"tsc": "yarn run -T tsc",

packages/signals/signals/src/core/signal-generators/network-gen/__tests__/network-generator.test.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -323,4 +323,24 @@ describe(NetworkGenerator, () => {
323323
expect(mockEmitter.emit.mock.calls).toEqual([])
324324
unregister()
325325
})
326+
327+
it('always disallows segment api network signals', async () => {
328+
const mockEmitter = { emit: jest.fn() }
329+
const networkGenerator = new TestNetworkGenerator({
330+
networkSignalsAllowList: ['.*'],
331+
})
332+
const unregister = networkGenerator.register(
333+
mockEmitter as unknown as SignalEmitter
334+
)
335+
336+
await window.fetch(`https://api.segment.io`, {
337+
method: 'POST',
338+
headers: { 'content-type': 'application/json' },
339+
body: JSON.stringify({ key: 'value' }),
340+
})
341+
342+
await sleep(100)
343+
expect(mockEmitter.emit.mock.calls).toEqual([])
344+
unregister()
345+
})
326346
})

packages/signals/signals/src/core/signal-generators/network-gen/helpers.ts

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -43,15 +43,7 @@ export const containsContentType = (
4343
return false
4444
}
4545
const normalizedHeaders = normalizeHeaders(headers)
46-
47-
// format the content-type header to remove charset -- this is non-standard behavior that is somewhat common
48-
// e.g. application/json;charset=utf-8 => application/json
49-
const removeCharset = (header: string | null): string | null =>
50-
header?.split(';')[0].trim() ?? null
51-
52-
return match.some((t) =>
53-
removeCharset(normalizedHeaders.get('content-type'))?.includes(t)
54-
)
46+
return match.some((t) => normalizedHeaders.get('content-type')?.includes(t))
5547
}
5648

5749
export const containsJSONContentType = (

packages/signals/signals/src/core/signal-generators/network-gen/network-signals-filter.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,17 +50,24 @@ class NetworkFilterListItem {
5050
export class NetworkSignalsFilterList {
5151
public allowed: NetworkFilterListItem
5252
public disallowed: NetworkFilterListItem
53+
private disallowedDefaults: NetworkFilterListItem
5354

5455
constructor(
5556
allowList: RegexLike[] | undefined,
5657
disallowList: RegexLike[] | undefined
5758
) {
5859
this.allowed = new NetworkFilterListItem(allowList || [])
5960
this.disallowed = new NetworkFilterListItem(disallowList || [])
61+
this.disallowedDefaults = new NetworkFilterListItem([
62+
'api.segment.io',
63+
'signals.segment.io',
64+
'cdn.segment.com',
65+
])
6066
}
6167

6268
isAllowed(url: string): boolean {
63-
const disallowed = this.disallowed.test(url)
69+
const disallowed =
70+
this.disallowed.test(url) || this.disallowedDefaults.test(url)
6471
const allowed = this.allowed.test(url)
6572
return allowed && !disallowed
6673
}
@@ -85,6 +92,7 @@ export class NetworkSignalsFilter {
8592
isAllowed(url: string): boolean {
8693
const { networkSignalsFilterList, networkSignalsAllowSameDomain } =
8794
this.settings
95+
8896
const passesNetworkFilter = networkSignalsFilterList.isAllowed(url)
8997
const allowedBecauseSameDomain =
9098
networkSignalsAllowSameDomain && isSameDomain(url)

packages/signals/signals/src/core/signals/signals.ts

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -91,9 +91,6 @@ export class Signals implements ISignals {
9191
disallowListURLs: [
9292
analyticsService.instance.settings.apiHost,
9393
analyticsService.instance.settings.cdnURL,
94-
'api.segment.io',
95-
'signals.segment.io',
96-
'cdn.segment.com',
9794
],
9895
sampleRate:
9996
analyticsService.instance.settings.cdnSettings

packages/signals/signals/src/index.umd.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,9 @@
33
*/
44
import { SignalsPlugin } from './index'
55
export { SignalsPlugin } // in case someone wants to use the umd module directly
6+
7+
// this will almost certainly be executed in the browser, but since this is UMD,
8+
// we are checking just for the sake of being thorough
9+
if (typeof window !== 'undefined') {
10+
;(window as any).SignalsPlugin = SignalsPlugin
11+
}

playgrounds/standalone-playground/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@
1212
},
1313
"dependencies": {
1414
"@segment/analytics-consent-wrapper-onetrust": "workspace:^",
15-
"@segment/analytics-next": "workspace:^"
15+
"@segment/analytics-next": "workspace:^",
16+
"@segment/analytics-signals": "workspace:^"
1617
},
1718
"devDependencies": {
1819
"http-server": "14.1.1"
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
<html>
2+
3+
<head>
4+
<style>
5+
body {
6+
font-family: monospace;
7+
}
8+
9+
#event {
10+
margin: 2em 0;
11+
min-height: 200px;
12+
min-width: 700px;
13+
}
14+
</style>
15+
16+
<form method="get">
17+
<input type="text" name="writeKey" placeholder="Writekey" />
18+
<button>Load</button>
19+
</form>
20+
</script>
21+
<script>
22+
const { searchParams } = new URL(document.location);
23+
const writeKey = searchParams.get("writeKey");
24+
document.querySelector("input").value = writeKey;
25+
26+
if (writeKey) {
27+
console.profile('snippet')
28+
console.time('snippet')
29+
!function () {
30+
var i = "analytics", analytics = window[i] = window[i] || []; if (!analytics.initialize) if (analytics.invoked) window.console && console.error && console.error("Segment snippet included twice."); else {
31+
analytics.invoked = !0; analytics.methods = ["trackSubmit", "trackClick", "trackLink", "trackForm", "pageview", "identify", "reset", "group", "track", "ready", "alias", "debug", "page", "screen", "once", "off", "on", "addSourceMiddleware", "addIntegrationMiddleware", "setAnonymousId", "addDestinationMiddleware", "register"]; analytics.factory = function (e) { return function () { if (window[i].initialized) return window[i][e].apply(window[i], arguments); var n = Array.prototype.slice.call(arguments); if (["track", "screen", "alias", "group", "page", "identify"].indexOf(e) > -1) { var c = document.querySelector("link[rel='canonical']"); n.push({ __t: "bpc", c: c && c.getAttribute("href") || void 0, p: location.pathname, u: location.href, s: location.search, t: document.title, r: document.referrer }) } n.unshift(e); analytics.push(n); return analytics } }; for (var n = 0; n < analytics.methods.length; n++) { var key = analytics.methods[n]; analytics[key] = analytics.factory(key) } analytics.load = function (key, n) { var t = document.createElement("script"); t.type = "text/javascript"; t.async = !0; t.setAttribute("data-global-segment-analytics-key", i); t.src = "https://cdn.segment.com/analytics.js/v1/" + key + "/analytics.min.js"; var r = document.getElementsByTagName("script")[0]; r.parentNode.insertBefore(t, r); analytics._loadOptions = n }; analytics._writeKey = writeKey;; analytics.SNIPPET_VERSION = "5.2.0";
32+
analytics.page();
33+
}
34+
}();
35+
}
36+
</script>
37+
<script src="/node_modules/@segment/analytics-signals/dist/umd/analytics-signals.umd.js"></script>
38+
<script>
39+
var signalsPlugin = new SignalsPlugin()
40+
analytics.register(signalsPlugin)
41+
analytics.load(analytics._writeKey)
42+
</script>
43+
44+
<body>
45+
<form>
46+
<textarea name="event" id="event">
47+
{
48+
"name": "hi",
49+
"properties": { },
50+
"traits": { },
51+
"options": { }
52+
}
53+
</textarea>
54+
<div>
55+
<button id="track">Track</button>
56+
<button id="identify">Identify</button>
57+
</div>
58+
</form>
59+
60+
<pre id="ready-logs"></pre>
61+
<pre id="logs"></pre>
62+
63+
<script type="text/javascript">
64+
if (window.analytics) {
65+
window.analytics.ready(function onReady() {
66+
console.profileEnd('snippet')
67+
console.timeEnd('snippet')
68+
document.querySelector('#ready-logs').textContent = 'ready!'
69+
})
70+
71+
document.querySelector('#track').addEventListener('click', function (e) {
72+
e.preventDefault()
73+
var contents = document.querySelector('#event').value
74+
var evt = JSON.parse(contents)
75+
console.profile('track')
76+
console.time('track')
77+
var promise = window.analytics.track(
78+
evt.name || '',
79+
evt.properties || {},
80+
evt.options || {}
81+
)
82+
83+
promise &&
84+
promise.then &&
85+
promise.then(function (ctx) {
86+
console.timeEnd('track')
87+
console.profileEnd('track')
88+
ctx.flush()
89+
document.querySelector('#logs').textContent = JSON.stringify(
90+
ctx.event,
91+
null,
92+
' '
93+
)
94+
})
95+
})
96+
97+
document
98+
.querySelector('#identify')
99+
.addEventListener('click', function (e) {
100+
e.preventDefault()
101+
var contents = document.querySelector('#event').value
102+
var evt = JSON.parse(contents)
103+
console.time('identify')
104+
var promise = window.analytics.identify(
105+
evt.name || '',
106+
evt.properties || {},
107+
evt.options || {}
108+
)
109+
110+
promise &&
111+
promise.then &&
112+
promise.then(function (ctx) {
113+
console.timeEnd('identify')
114+
ctx.flush()
115+
document.querySelector('#logs').textContent = JSON.stringify(
116+
ctx.event,
117+
null,
118+
' '
119+
)
120+
})
121+
})
122+
}
123+
</script>
124+
</body>
125+
126+
</html>

0 commit comments

Comments
 (0)