-
Notifications
You must be signed in to change notification settings - Fork 138
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix: seek subdomain correctly (#888)
We relied on a regex for detecting subdomain in order to choose what cookie domain to set for PostHog storage. This worked fine for example.com or www.example.com but it didn't work for example.co.uk or example.com.au because it would grab the first two parts and so try to set on the public suffix co.uk or com.au A lot of suggested solutions on line involve keeping a list of public suffixes, or making DNS lookups to validate values which wouldn't fit here. But... browsers reject cookies set on public suffixes - despite not offering an API to directly check if something is a public suffix. So, we can split the current hostname up and then progressively try to set a cookie until the browser accepts it. For a.long.domain.someone.is.using.com.au we would check: .au rejected by browser .com.au rejected by browser .using.com.au accepted by browser We call this method a lot and the browser can't maintain in-memory variables and navigate between sub-domains, so I also cache the result I manually tested the code in the developer console of firefox, chrome, and safari
- Loading branch information
1 parent
76f058b
commit 8b289a3
Showing
5 changed files
with
159 additions
and
56 deletions.
There are no files selected for viewing
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
import { window } from '../../src/utils/globals' | ||
import { resetSessionStorageSupported, seekFirstNonPublicSubDomain, sessionStore } from '../storage' | ||
|
||
describe('sessionStore', () => { | ||
describe('seekFirstNonPublicSubDomain', () => { | ||
const mockDocumentDotCookie = { | ||
value_: '', | ||
|
||
get cookie() { | ||
return this.value_ | ||
}, | ||
|
||
set cookie(value) { | ||
//needs to refuse known public suffixes, like a browser would | ||
// value arrives like dmn_chk_1699961248575=1;domain=.uk | ||
const domain = value.split('domain=') | ||
if (['.uk', '.com', '.au', '.com.au', '.co.uk'].includes(domain[1])) return | ||
this.value_ += value + ';' | ||
}, | ||
} | ||
test.each([ | ||
{ | ||
candidate: 'www.google.co.uk', | ||
expected: 'google.co.uk', | ||
}, | ||
{ | ||
candidate: 'www.google.com', | ||
expected: 'google.com', | ||
}, | ||
{ | ||
candidate: 'www.google.com.au', | ||
expected: 'google.com.au', | ||
}, | ||
{ | ||
candidate: 'localhost', | ||
expected: '', | ||
}, | ||
])(`%s subdomain check`, ({ candidate, expected }) => { | ||
expect(seekFirstNonPublicSubDomain(candidate, mockDocumentDotCookie)).toEqual(expected) | ||
}) | ||
}) | ||
|
||
it('stores objects as strings', () => { | ||
sessionStore.set('foo', { bar: 'baz' }) | ||
expect(sessionStore.get('foo')).toEqual('{"bar":"baz"}') | ||
}) | ||
it('stores and retrieves an object untouched', () => { | ||
const obj = { bar: 'baz' } | ||
sessionStore.set('foo', obj) | ||
expect(sessionStore.parse('foo')).toEqual(obj) | ||
}) | ||
it('stores and retrieves a string untouched', () => { | ||
const str = 'hey hey' | ||
sessionStore.set('foo', str) | ||
expect(sessionStore.parse('foo')).toEqual(str) | ||
}) | ||
it('returns null if the key does not exist', () => { | ||
expect(sessionStore.parse('baz')).toEqual(null) | ||
}) | ||
it('remove deletes an item from storage', () => { | ||
const str = 'hey hey' | ||
sessionStore.set('foo', str) | ||
expect(sessionStore.parse('foo')).toEqual(str) | ||
sessionStore.remove('foo') | ||
expect(sessionStore.parse('foo')).toEqual(null) | ||
}) | ||
|
||
describe('sessionStore.is_supported', () => { | ||
beforeEach(() => { | ||
// Reset the sessionStorageSupported before each test. Otherwise, we'd just be testing the cached value. | ||
// eslint-disable-next-line no-unused-vars | ||
resetSessionStorageSupported() | ||
}) | ||
it('returns false if sessionStorage is undefined', () => { | ||
const sessionStorage = (window as any).sessionStorage | ||
delete (window as any).sessionStorage | ||
expect(sessionStore.is_supported()).toEqual(false) | ||
;(window as any).sessionStorage = sessionStorage | ||
}) | ||
it('returns true by default', () => { | ||
expect(sessionStore.is_supported()).toEqual(true) | ||
}) | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters