mirror of
https://github.com/hanzo-apps/hanzo.ai.git
synced 2026-10-29 07:20:43 +00:00
components/consent/Consent.tsx and Choices.tsx are gone. app/providers.tsx mounts Consent from @hanzo/ui/consent 8.29.14: a bar with Manage preferences, Accept all and a × that stores essential cookies only, and a Cookie Preferences sheet that opens from any link to #choices, the footer's Privacy choices included. Its switches start off until chosen. @hanzo/event 0.3.61 makes the stream first party: every page view and event goes for every visitor, stating the grants it went under, so the provider no longer takes `enabled` from consent. Consent gates Google and Meta alone, through startTags. The conversion and forward gates now hold that no platform hears of a visit before Accept or after a refusal, while our stream counts it under no grant. Help's corner words gain the Font and Arabic font rows @hanzo/ui 8.29.12 added to Words.
109 lines
5.0 KiB
TypeScript
109 lines
5.0 KiB
TypeScript
import { expect, test, type Page } from '@playwright/test'
|
|
import { serveExport } from './export'
|
|
import { bare, person, stream, type Wire } from './heard'
|
|
import { asked, issuer, type Seen as Issued } from './issuer'
|
|
|
|
/**
|
|
* HANZO.CHAT COUNTS THE VISIT, THEN OPENS THE CHAT AT HANZO.AI.
|
|
*
|
|
* Its root is this export's /forward (universe infra/aws/routes/sites.yaml,
|
|
* router `hanzo-chat`). For a visitor who accepted, the page reports to
|
|
* hanzo.chat's own GA4 stream, the Meta Pixel and our stream, and leaves for
|
|
* hanzo.ai/chat once they have taken the page view — carrying the query, and
|
|
* never through hanzo.id. A visitor who has not answered is counted nowhere and
|
|
* is not held: every visitor is asked before anything is measured.
|
|
*
|
|
* hanzo.chat is answered from the export on this machine, `/` as /forward;
|
|
* gtag.js and fbevents.js are stubs, the second one sending the page view the
|
|
* way Meta's library does, so what is measured is what left the page.
|
|
*/
|
|
|
|
const HANZO_CHAT = 'G-GRQBMNDTT6'
|
|
const PIXEL = '1790308611893001'
|
|
|
|
/** Meta's library, as far as this gate needs it: drain the stub's queue into /tr hits. */
|
|
const FBEVENTS = `(function(){var f=window.fbq,id='';f.queue.splice(0).forEach(function(a){
|
|
if(a[0]==='init')id=a[1];if(a[0]==='track')new Image().src='https://www.facebook.com/tr?id='+id+'&ev='+a[1]});
|
|
f.callMethod=function(){}})()`
|
|
|
|
let base: string
|
|
let stop: () => Promise<void>
|
|
|
|
test.beforeAll(async () => {
|
|
const served = await serveExport()
|
|
base = served.url
|
|
stop = served.close
|
|
})
|
|
test.afterAll(async () => stop())
|
|
|
|
type Seen = { ga: string[]; meta: string[]; ai: string[]; iam: Issued; events: Wire[] }
|
|
|
|
async function edge(page: Page): Promise<Seen> {
|
|
await person(page)
|
|
await page.route('**/*', (route) => (new URL(route.request().url()).origin === base ? route.continue() : route.abort()))
|
|
const seen: Seen = { ga: [], meta: [], ai: [], iam: null, events: [] }
|
|
await page.route('https://www.googletagmanager.com/**', (route) => {
|
|
seen.ga.push(new URL(route.request().url()).searchParams.get('id') ?? '')
|
|
return route.fulfill({ status: 200, contentType: 'text/javascript', body: '' })
|
|
})
|
|
await page.route('https://connect.facebook.net/**', (route) => route.fulfill({ status: 200, contentType: 'text/javascript', body: FBEVENTS }))
|
|
await page.route('https://www.facebook.com/tr**', (route) => {
|
|
const q = new URL(route.request().url()).searchParams
|
|
seen.meta.push(`${q.get('id')} ${q.get('ev')}`)
|
|
return route.fulfill({ status: 200, contentType: 'image/gif', body: '' })
|
|
})
|
|
await page.route('https://hanzo.chat/**', async (route) => {
|
|
const u = new URL(route.request().url())
|
|
const path = u.pathname === '/' ? '/forward' : u.pathname
|
|
return route.fulfill({ response: await route.fetch({ url: `${base}${path}${u.search}` }) })
|
|
})
|
|
await page.route('https://hanzo.ai/**', (route) => {
|
|
seen.ai.push(route.request().url())
|
|
return route.fulfill({ status: 200, contentType: 'text/html', body: '<!doctype html><title>Hanzo</title>' })
|
|
})
|
|
seen.iam = await issuer(page)
|
|
seen.events = await stream(page)
|
|
return seen
|
|
}
|
|
|
|
test('hanzo.chat reports to its own stream, the Pixel and ours, then opens the chat at hanzo.ai', async ({ page }) => {
|
|
const seen = await edge(page)
|
|
await page.goto('https://hanzo.chat/?q=hello')
|
|
await page.waitForURL((u) => u.hostname === 'hanzo.ai', { timeout: 20_000 })
|
|
expect(bare(page.url())).toBe('https://hanzo.ai/chat?q=hello')
|
|
expect(seen.ga).toEqual([HANZO_CHAT])
|
|
expect(seen.meta).toEqual([`${PIXEL} PageView`])
|
|
expect(seen.events.map((e) => e.event)).toContain('$pageview')
|
|
expect(seen.ai.map(bare)).toEqual(['https://hanzo.ai/chat?q=hello'])
|
|
expect(asked(seen.iam)).toEqual([])
|
|
})
|
|
|
|
test.describe('a visitor who has not answered the cookie question', () => {
|
|
// A browser that holds no choice for hanzo.chat (playwright.config ANSWERED holds one).
|
|
test.use({ storageState: { cookies: [], origins: [] } })
|
|
|
|
test('is counted by no platform on hanzo.chat, and is not held there', async ({ page }) => {
|
|
const seen = await edge(page)
|
|
const start = Date.now()
|
|
await page.goto('https://hanzo.chat/?q=hello')
|
|
await page.waitForURL((u) => u.hostname === 'hanzo.ai', { timeout: 20_000 })
|
|
expect(Date.now() - start).toBeLessThan(12_000)
|
|
expect(bare(page.url())).toBe('https://hanzo.ai/chat?q=hello')
|
|
expect(seen.ga).toEqual([])
|
|
expect(seen.meta).toEqual([])
|
|
expect(seen.events.filter((e) => String(e.properties?.consent ?? '') !== ''), 'our own stream only, under no grant').toEqual([])
|
|
})
|
|
})
|
|
|
|
test('a blocked tag holds no reader on hanzo.chat', async ({ page }) => {
|
|
const seen = await edge(page)
|
|
// Meta's host dropped, as a blocker drops it: the library never arrives.
|
|
await page.route('https://connect.facebook.net/**', (route) => route.abort())
|
|
const start = Date.now()
|
|
await page.goto('https://hanzo.chat/')
|
|
await page.waitForURL((u) => u.hostname === 'hanzo.ai', { timeout: 20_000 })
|
|
expect(Date.now() - start).toBeLessThan(12_000)
|
|
expect(bare(page.url())).toBe('https://hanzo.ai/chat')
|
|
expect(seen.ga).toEqual([HANZO_CHAT])
|
|
})
|