diff --git a/apps/voko-server/tools/scenarios/voko-alpha/skeleton.mjs b/apps/voko-server/tools/scenarios/voko-alpha/skeleton.mjs index 8d35d50..28e8b35 100644 --- a/apps/voko-server/tools/scenarios/voko-alpha/skeleton.mjs +++ b/apps/voko-server/tools/scenarios/voko-alpha/skeleton.mjs @@ -60,7 +60,12 @@ try { const sent = await call('POST', '/api/voko/auth/send-code', { body: { email, locale: 'en' } }) check('send-code', sent.status === 200, sent) - const log = await db('email_delivery_logs').where({ to_email: email, template: 'verify-code', status: 'sent' }).orderBy('created_at', 'desc').first() + // auth.sendCode 不等待 SMTP 就返回,这里等真实投递落库(最多 30 秒)。 + let log + for (let i = 0; i < 30 && log === undefined; i += 1) { + log = await db('email_delivery_logs').where({ to_email: email, template: 'verify-code', status: 'sent' }).orderBy('created_at', 'desc').first() + if (log === undefined) await new Promise((resolve) => setTimeout(resolve, 1000)) + } check('验证码邮件已投递并记录', log !== undefined, { email }) const code = JSON.parse(log.metadata_json).code diff --git a/docs/voko/upstream.md b/docs/voko/upstream.md index 2199f89..f13c474 100644 --- a/docs/voko/upstream.md +++ b/docs/voko/upstream.md @@ -17,6 +17,7 @@ | `tools/scripts/mysql-dev.js` | 默认库 `voko`,默认 env 文件 `apps/voko-server/.env` | | `packages/llm-router/package.json` | 测试脚本加 `--passWithNoTests`(上游该包目前没有测试文件,`vitest run` 会以 1 退出) | | `pnpm-workspace.yaml` | 只保留 `apps/*`、`packages/*` | +| `packages/email/src/templates/waitlist-invite.js` | `inviteUrl` 允许 `http://localhost`(本地验收用),其余仍须 https(2026-10-10) | | `apps/voko-server/src/assembly/security.js`、`google-oauth.js`、`email-delivery-log.js` | 从 visual-game-server 原样复制(security.js 只改了一条报错文案) | ## 待裁剪(原样搬入,后续处理) diff --git a/packages/email/src/templates/waitlist-invite.js b/packages/email/src/templates/waitlist-invite.js index c7209c9..959fe9d 100644 --- a/packages/email/src/templates/waitlist-invite.js +++ b/packages/email/src/templates/waitlist-invite.js @@ -69,7 +69,9 @@ function requiredHttpsUrl(value) { if (typeof value !== 'string' || value.trim() === '') fail('missing field "inviteUrl"') const url = value.trim() try { - if (new URL(url).protocol === 'https:') return url + const parsed = new URL(url) + // 本地开发的前端是 http://localhost,其余环境必须 https。 + if (parsed.protocol === 'https:' || parsed.hostname === 'localhost') return url } catch (error) { fail(`inviteUrl is invalid: ${error instanceof Error ? error.message : String(error)}`) }