Skip to content

Commit 2d89b1d

Browse files
authored
Normalize URL in network signals (#1113)
1 parent 1f68f0e commit 2d89b1d

File tree

6 files changed

+85
-13
lines changed

6 files changed

+85
-13
lines changed

.changeset/chatty-flowers-roll.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@segment/analytics-signals': patch
3+
---
4+
5+
Update network signals to include a full url

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

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
import { SignalEmitter } from '../../emitter'
88
import { Response } from 'node-fetch'
99
import { sleep } from '@segment/analytics-core'
10+
import { setLocation } from '../../../test-helpers/set-location'
1011

1112
describe(containsJSONContent, () => {
1213
it('should return true if headers contain application/json', () => {
@@ -31,18 +32,8 @@ describe(containsJSONContent, () => {
3132
})
3233

3334
describe(matchHostname, () => {
34-
const setHostname = (hostname: string) => {
35-
Object.defineProperty(window, 'location', {
36-
value: {
37-
...window.location,
38-
hostname: hostname,
39-
},
40-
writable: true,
41-
})
42-
}
43-
4435
beforeEach(() => {
45-
setHostname('example.com')
36+
setLocation({ hostname: 'example.com' })
4637
})
4738
it('should only match first party domains', () => {
4839
expect(matchHostname('https://www.example.com')).toBe(true)
@@ -54,7 +45,7 @@ describe(matchHostname, () => {
5445
})
5546

5647
it('should work with subdomains', () => {
57-
setHostname('api.example.com')
48+
setLocation({ hostname: 'api.example.com' })
5849
expect(matchHostname('https://api.example.com/foo')).toBe(true)
5950
expect(matchHostname('https://foo.com/foo')).toBe(false)
6051
expect(matchHostname('https://example.com/foo')).toBe(false)
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import { setLocation } from '../../../test-helpers/set-location'
2+
import { normalizeUrl } from '../normalize-url'
3+
4+
describe('normalizeUrl', () => {
5+
beforeEach(() => {
6+
setLocation({ origin: 'https://www.currentsite.com', protocol: 'https:' })
7+
})
8+
9+
it('should return the same URL if it starts with http', () => {
10+
const url = 'https://www.example.com'
11+
expect(normalizeUrl(url)).toBe('https://www.example.com')
12+
})
13+
14+
it('should work with subdomains', () => {
15+
const url = 'https://api.example.com'
16+
expect(normalizeUrl(url)).toBe('https://api.example.com')
17+
})
18+
19+
it('should prepend hostname to path starting with /', () => {
20+
const url = '/foo/bar'
21+
expect(normalizeUrl(url)).toBe('https://www.currentsite.com/foo/bar')
22+
})
23+
24+
it('should prepend hostname and / to path not starting with /', () => {
25+
const url = 'foo/bar'
26+
expect(normalizeUrl(url)).toBe('https://www.currentsite.com/foo/bar')
27+
})
28+
29+
it('should prepend hostname and / to a single word path', () => {
30+
const url = 'foo'
31+
expect(normalizeUrl(url)).toBe('https://www.currentsite.com/foo')
32+
})
33+
34+
it('should use the current protocol of the page if none is provided', () => {
35+
const url = 'example.com/bar'
36+
setLocation({ protocol: 'http:' })
37+
expect(normalizeUrl(url)).toBe('http://example.com/bar')
38+
})
39+
40+
it('should work if no /', () => {
41+
const url = 'example.com'
42+
setLocation({ protocol: 'http:' })
43+
expect(normalizeUrl(url)).toBe('http://example.com')
44+
})
45+
46+
it('protocols should work with subdomains', () => {
47+
const url = 'api.example.com/foo'
48+
setLocation({ protocol: 'http:' })
49+
expect(normalizeUrl(url)).toBe('http://api.example.com/foo')
50+
})
51+
})

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { logger } from '../../lib/logger'
22
import { createNetworkSignal } from '../../types'
33
import { SignalEmitter } from '../emitter'
4+
import { normalizeUrl } from './normalize-url'
45
import { SignalGenerator } from './types'
56
let origFetch: typeof window.fetch
67

@@ -68,7 +69,7 @@ export class NetworkGenerator implements SignalGenerator {
6869
emitter.emit(
6970
createNetworkSignal({
7071
action: 'Request',
71-
url: sUrl,
72+
url: normalizeUrl(sUrl),
7273
method: rq.method || '',
7374
data: JSON.parse(rq.body.toString()),
7475
})
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
/**
2+
* Normalize URL provided by the fetch API into a valid URL string that can be used with new URL.
3+
*/
4+
export const normalizeUrl = (urlOrPath: string): string => {
5+
if (urlOrPath.startsWith('http')) {
6+
return urlOrPath
7+
}
8+
if (urlOrPath.startsWith('/')) {
9+
return window.location.origin + urlOrPath
10+
}
11+
if (urlOrPath.includes('.')) {
12+
return `${location.protocol}//${urlOrPath}`
13+
}
14+
return window.location.origin + '/' + urlOrPath
15+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
export const setLocation = (location: Partial<Location> = {}) => {
2+
Object.defineProperty(window, 'location', {
3+
value: {
4+
...window.location,
5+
...location,
6+
},
7+
writable: true,
8+
})
9+
}

0 commit comments

Comments
 (0)