-
Notifications
You must be signed in to change notification settings - Fork 7
feat(amsg): 单用户 Worker 暴露 VAPID 公钥端点,供前端跨源订阅 #16
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| --- | ||
| "@rei-standard/amsg-server": minor | ||
| "@rei-standard/amsg-client": minor | ||
| --- | ||
|
|
||
| 单用户 worker 暴露 VAPID 公钥端点,供前端跨源订阅。 | ||
|
|
||
| - amsg-server:单用户 Worker 新增 `GET /vapid-public-key`,返回本 Worker 自己的 `VAPID_PUBLIC_KEY`(未配置时返回 503 `VAPID_NOT_CONFIGURED`)。和其它端点共用同一套 CORS 与 `serverToken` 校验。前端拿它作为 `applicationServerKey` 来创建 Web Push 订阅——各自部署的 worker 各有各的 VAPID,公钥在运行时从 worker 拉取。 | ||
| - amsg-client:新增 `ReiClient.getVapidPublicKey()`,GET 该端点并返回公钥字符串(配了 `serverToken` 时带上 `X-Client-Token`)。 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
60 changes: 60 additions & 0 deletions
60
packages/rei-standard-amsg/client/test/vapid-public-key.test.mjs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,60 @@ | ||
| import { test } from 'node:test'; | ||
| import assert from 'node:assert/strict'; | ||
| import { ReiClient } from '../src/index.js'; | ||
|
|
||
| const USER = '550e8400-e29b-41d4-a716-446655440000'; | ||
|
|
||
| test('getVapidPublicKey() GETs /vapid-public-key with X-Client-Token and returns the key string', async () => { | ||
| const captured = []; | ||
| const original = globalThis.fetch; | ||
| globalThis.fetch = async (url, init) => { | ||
| captured.push({ url: String(url), method: init && init.method, headers: (init && init.headers) || {} }); | ||
| return new Response(JSON.stringify({ success: true, publicKey: 'BPUB123' }), { | ||
| status: 200, headers: { 'Content-Type': 'application/json' } | ||
| }); | ||
| }; | ||
| let key; | ||
| try { | ||
| const client = new ReiClient({ baseUrl: 'https://w.dev', userId: USER, serverToken: 's3cret' }); | ||
| key = await client.getVapidPublicKey(); | ||
| } finally { | ||
| globalThis.fetch = original; | ||
| } | ||
| assert.equal(captured.length, 1); | ||
| assert.equal(captured[0].url, 'https://w.dev/vapid-public-key'); | ||
| assert.equal(captured[0].method, 'GET'); | ||
| assert.equal(captured[0].headers['X-Client-Token'], 's3cret'); | ||
| assert.equal(key, 'BPUB123'); | ||
| }); | ||
|
|
||
| test('getVapidPublicKey() without serverToken sends no X-Client-Token', async () => { | ||
| const captured = []; | ||
| const original = globalThis.fetch; | ||
| globalThis.fetch = async (url, init) => { | ||
| captured.push({ headers: (init && init.headers) || {} }); | ||
| return new Response(JSON.stringify({ success: true, publicKey: 'BPUB123' }), { | ||
| status: 200, headers: { 'Content-Type': 'application/json' } | ||
| }); | ||
| }; | ||
| try { | ||
| const client = new ReiClient({ baseUrl: 'https://w.dev', userId: USER }); | ||
| await client.getVapidPublicKey(); | ||
| } finally { | ||
| globalThis.fetch = original; | ||
| } | ||
| assert.equal(captured[0].headers['X-Client-Token'], undefined); | ||
| }); | ||
|
|
||
| test('getVapidPublicKey() throws on a non-success response', async () => { | ||
| const original = globalThis.fetch; | ||
| globalThis.fetch = async () => new Response( | ||
| JSON.stringify({ success: false, error: { code: 'VAPID_NOT_CONFIGURED', message: 'VAPID 未配置' } }), | ||
| { status: 503, headers: { 'Content-Type': 'application/json' } } | ||
| ); | ||
| try { | ||
| const client = new ReiClient({ baseUrl: 'https://w.dev', userId: USER }); | ||
| await assert.rejects(() => client.getVapidPublicKey(), /VAPID 未配置/); | ||
| } finally { | ||
| globalThis.fetch = original; | ||
| } | ||
| }); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
47 changes: 47 additions & 0 deletions
47
packages/rei-standard-amsg/server/src/server/handlers/vapid-public-key.js
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,47 @@ | ||
| /** | ||
| * Handler: vapid-public-key | ||
| * | ||
| * Exposes this worker's own VAPID public key so a browser frontend can build a | ||
| * Web Push subscription (`applicationServerKey`) at runtime. Each self-hosted | ||
| * worker owns its keypair, so the key can't be baked into the frontend — it | ||
| * pulls it from here. | ||
| * | ||
| * Auth funnels through the same resolveTenant as every other endpoint, so with | ||
| * a serverToken configured this route requires `X-Client-Token` too (the | ||
| * all-or-nothing contract). The public key itself is not a secret; gating it | ||
| * just keeps every endpoint consistent. | ||
| * | ||
| * @param {Object} ctx - Server context ({ vapid, tenantManager, ... }). | ||
| * @returns {{ GET: function }} | ||
| */ | ||
|
|
||
| export function createVapidPublicKeyHandler(ctx) { | ||
| async function GET(url, headers) { | ||
| const effectiveHeaders = headers || url || {}; | ||
| const tenantResult = await ctx.tenantManager.resolveTenant(effectiveHeaders); | ||
| if (!tenantResult.ok) { | ||
| return tenantResult.error; | ||
| } | ||
|
|
||
| const publicKey = ctx.vapid && ctx.vapid.publicKey; | ||
| if (!publicKey) { | ||
| return { | ||
| status: 503, | ||
| body: { | ||
| success: false, | ||
| error: { | ||
| code: 'VAPID_NOT_CONFIGURED', | ||
| message: 'VAPID 公钥未配置:请为本 Worker 设置 VAPID_PUBLIC_KEY' | ||
| } | ||
| } | ||
| }; | ||
| } | ||
|
|
||
| return { | ||
| status: 200, | ||
| body: { success: true, publicKey } | ||
| }; | ||
| } | ||
|
|
||
| return { GET }; | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.