55 lines
1.4 KiB
TypeScript
55 lines
1.4 KiB
TypeScript
const MAILPIT_URL = 'http://127.0.0.1:54324'
|
|
|
|
interface MailpitAddress {
|
|
Address: string
|
|
Name: string
|
|
}
|
|
|
|
interface MailpitMessage {
|
|
ID: string
|
|
To: MailpitAddress[]
|
|
Subject: string
|
|
Date: string
|
|
}
|
|
|
|
interface MailpitListResponse {
|
|
messages: MailpitMessage[] | null
|
|
total: number
|
|
}
|
|
|
|
interface MailpitMessageDetail {
|
|
Text: string
|
|
HTML: string
|
|
}
|
|
|
|
export async function deleteAllMail(): Promise<void> {
|
|
await fetch(`${MAILPIT_URL}/api/v1/messages`, { method: 'DELETE' })
|
|
}
|
|
|
|
export async function getMagicLink(toEmail: string, timeoutMs = 15_000): Promise<string> {
|
|
const deadline = Date.now() + timeoutMs
|
|
|
|
while (Date.now() < deadline) {
|
|
const res = await fetch(`${MAILPIT_URL}/api/v1/messages`)
|
|
if (res.ok) {
|
|
const data: MailpitListResponse = await res.json()
|
|
const msg = (data.messages ?? []).find(m =>
|
|
m.To.some(t => t.Address === toEmail),
|
|
)
|
|
if (msg) {
|
|
const detail: MailpitMessageDetail = await fetch(
|
|
`${MAILPIT_URL}/api/v1/message/${msg.ID}`,
|
|
).then(r => r.json())
|
|
|
|
const body = detail.Text || detail.HTML
|
|
// Supabase local auth confirmation URL
|
|
const match = body.match(/https?:\/\/127\.0\.0\.1:54321\/auth\/v1\/verify\?[^\s"<]+/)
|
|
if (match) return match[0]
|
|
}
|
|
}
|
|
await new Promise(r => setTimeout(r, 500))
|
|
}
|
|
|
|
throw new Error(`No magic link email for ${toEmail} within ${timeoutMs}ms`)
|
|
}
|