mirror of
https://github.com/hanzo-apps/hanzo.ai.git
synced 2026-10-30 14:58:08 +00:00
Every import, the catalog pin and the docs name @hanzo/app and github.com/hanzoai/app. lib/rooms.ts is lib/app.ts and components/rooms.tsx is components/app.tsx.
83 lines
3.1 KiB
TypeScript
83 lines
3.1 KiB
TypeScript
import { test, expect } from '@playwright/test'
|
|
import { SEAL } from '@hanzo/app/lib/share'
|
|
|
|
/**
|
|
* THE SHARE TOKEN LEAVES THE ADDRESS BEFORE ANYTHING READS IT.
|
|
*
|
|
* `SEAL` is the first script in the document head. It runs as the page is
|
|
* parsed and again on every fragment change, with its listeners registered
|
|
* before any tag's, so an ad tag's own history listener reads an address that
|
|
* no longer carries the token. These run it against a stand-in window.
|
|
*/
|
|
|
|
const TOKEN = 'Zq3LwX0p-Tf9_aB7kQmN2rS8vY1cD4eF6gH5jK0lMnO'
|
|
|
|
function page(href: string) {
|
|
const u = new URL(href)
|
|
const listeners: Record<string, (() => void)[]> = {}
|
|
const stored: Record<string, string> = {}
|
|
const win: Record<string, unknown> = {}
|
|
const location = { pathname: u.pathname, search: u.search, hash: u.hash }
|
|
const history = {
|
|
replaceState: (_s: unknown, _t: string, url: string) => {
|
|
const next = new URL(url, 'https://hanzo.ai')
|
|
location.pathname = next.pathname
|
|
location.search = next.search
|
|
location.hash = next.hash
|
|
},
|
|
}
|
|
const sessionStorage = { setItem: (k: string, v: string) => (stored[k] = v) }
|
|
const addEventListener = (type: string, fn: () => void) => (listeners[type] ??= []).push(fn)
|
|
const run = new Function('location', 'history', 'sessionStorage', 'addEventListener', 'window', SEAL)
|
|
run(location, history, sessionStorage, addEventListener, win)
|
|
return {
|
|
location,
|
|
stored,
|
|
win,
|
|
fire(type: string, hash: string) {
|
|
location.hash = hash
|
|
for (const fn of listeners[type] ?? []) fn()
|
|
},
|
|
}
|
|
}
|
|
|
|
test.describe('the share token leaves the address', () => {
|
|
for (const path of ['/chat/shared', '/chat/shared/', '/chat/shared?utm_source=x', '/chat/shared/index.html', '/chat/shared.html', '/Chat/Shared', '/chat//shared']) {
|
|
test(`on ${path}`, () => {
|
|
const p = page(`https://hanzo.ai${path}#${TOKEN}`)
|
|
expect(p.location.hash).toBe('')
|
|
expect(p.stored['hanzo:share:open']).toBe(TOKEN)
|
|
expect(p.win.hzShare).toBe(TOKEN)
|
|
})
|
|
}
|
|
|
|
test('on any page whose fragment has a token shape, the not-found page included', () => {
|
|
const p = page(`https://hanzo.ai/no/such/page#${TOKEN}`)
|
|
expect(p.location.hash).toBe('')
|
|
expect(p.stored['hanzo:share:open']).toBe(TOKEN)
|
|
})
|
|
|
|
test('an ordinary fragment elsewhere is left alone', () => {
|
|
const p = page('https://hanzo.ai/pricing#choices')
|
|
expect(p.location.hash).toBe('#choices')
|
|
expect(p.stored['hanzo:share:open']).toBeUndefined()
|
|
})
|
|
|
|
test('a fragment that does not decode is taken as written', () => {
|
|
const p = page('https://hanzo.ai/chat/shared#%E0%A4%A')
|
|
expect(p.location.hash).toBe('')
|
|
expect(p.stored['hanzo:share:open']).toBe('%E0%A4%A')
|
|
})
|
|
|
|
test('a link pasted into an open tab is taken on hashchange and on popstate', () => {
|
|
const p = page('https://hanzo.ai/chat/shared')
|
|
const second = TOKEN.replace('Z', 'Y')
|
|
p.fire('hashchange', `#${TOKEN}`)
|
|
expect(p.location.hash).toBe('')
|
|
expect(p.stored['hanzo:share:open']).toBe(TOKEN)
|
|
p.fire('popstate', `#${second}`)
|
|
expect(p.location.hash).toBe('')
|
|
expect(p.stored['hanzo:share:open']).toBe(second)
|
|
})
|
|
})
|