mirror of
https://github.com/hanzo-apps/hanzo.ai.git
synced 2026-10-29 07:20:43 +00:00
The app's connect popup (@hanzo/app Setup) returns to hanzo.ai/?at=-/settings/integrations with connected= or error= on the query. MOVED moved it on to Settings with the answer dropped, and the popup stayed open. RETURNED runs first: in a popup it posts the answer to the opener (this origin only), a BroadcastChannel and storage, then closes the window.
71 lines
2.9 KiB
TypeScript
71 lines
2.9 KiB
TypeScript
import { test, expect } from '@playwright/test'
|
|
import { RETURNED } from '../../lib/auth/returned'
|
|
|
|
/**
|
|
* AN OAUTH POPUP CLOSES ON ITS RETURN, AND ITS OPENER HEARS HOW IT WENT.
|
|
*
|
|
* The API's callback sends the popup to `https://hanzo.ai/?at=-/settings/integrations`
|
|
* with the answer on the query (cloud apps/provider back.go `returnURL`); `/`'s first
|
|
* script hands it on and closes the window (lib/auth/returned). This runs that
|
|
* script against a stand-in window, each way the answer travels.
|
|
*/
|
|
|
|
const ORIGIN = 'https://hanzo.ai'
|
|
const KEY = 'hanzo-oauth-return'
|
|
|
|
function run(search: string, { opener = true, name = 'hanzo-oauth-github' } = {}) {
|
|
const posted: [unknown, string][] = []
|
|
const broadcast: unknown[] = []
|
|
const stored: [string, string | null][] = []
|
|
let closed = false
|
|
const window = {
|
|
name,
|
|
opener: opener ? { postMessage: (m: unknown, to: string) => posted.push([m, to]) } : null,
|
|
close: () => void (closed = true),
|
|
}
|
|
const location = { search, origin: ORIGIN }
|
|
const localStorage = {
|
|
setItem: (k: string, v: string) => stored.push([k, v]),
|
|
removeItem: (k: string) => stored.push([k, null]),
|
|
}
|
|
class BroadcastChannel {
|
|
constructor(readonly name: string) {}
|
|
postMessage(m: unknown) {
|
|
if (this.name === KEY) broadcast.push(m)
|
|
}
|
|
close() {}
|
|
}
|
|
new Function('window', 'location', 'localStorage', 'BroadcastChannel', RETURNED)(window, location, localStorage, BroadcastChannel)
|
|
return { posted, broadcast, stored, closed }
|
|
}
|
|
|
|
test.describe('an OAuth popup returning to /', () => {
|
|
test('a connection is told to the opener, on this origin only, and the popup closes', () => {
|
|
const r = run('?at=-/settings/integrations&connected=github&account=acme')
|
|
const answer = { type: KEY, provider: 'github', ok: true, account: 'acme' }
|
|
expect(r.posted).toEqual([[answer, ORIGIN]])
|
|
expect(r.broadcast).toEqual([answer])
|
|
expect(r.stored[0]).toEqual([KEY, JSON.stringify(answer)])
|
|
expect(r.closed).toBe(true)
|
|
})
|
|
|
|
test('a refusal is told the same way, with its reason', () => {
|
|
const r = run('?at=-/settings/integrations&error=github&reason=invalid+state')
|
|
expect(r.posted).toEqual([[{ type: KEY, provider: 'github', ok: false, reason: 'invalid state' }, ORIGIN]])
|
|
expect(r.closed).toBe(true)
|
|
})
|
|
|
|
test('a popup that lost its opener still answers by channel and storage, and closes', () => {
|
|
const r = run('?at=-/settings/integrations&connected=github&account=acme', { opener: false })
|
|
expect(r.posted).toEqual([])
|
|
expect(r.broadcast).toHaveLength(1)
|
|
expect(r.closed).toBe(true)
|
|
})
|
|
|
|
test('a tab of its own, or a page with no answer, is left to move on to Settings', () => {
|
|
expect(run('?at=-/settings/integrations&connected=github', { opener: false, name: '' }).closed).toBe(false)
|
|
expect(run('?at=-/settings/integrations').closed).toBe(false)
|
|
expect(run('?error=access_denied').closed).toBe(false)
|
|
})
|
|
})
|