Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .changeset/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Changesets

Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works
with multi-package repos, or single-package repos to help you version and publish your code. You can
find the full documentation for it [in our repository](https://github.com/changesets/changesets).

We have a quick list of common questions to get you started engaging with this project in
[our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md).
5 changes: 5 additions & 0 deletions .changeset/client-avatar-url-align.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@rei-standard/amsg-client": minor
---

`avatarUrl` 本地预检改用 `@rei-standard/amsg-shared` 的统一校验,与 server / instant 对齐。现在非法(非 `data:`)URL —— 例如缺少协议的 `foo.com/a.png` —— 也会在客户端被 `console.warn` 并置空;此前 client 只检查 `data:` 与长度,会放行这类 URL(之后由服务端兜底置空)。软清空策略不变:装饰性字段不合法时只做清空,不会让整条请求失败。
11 changes: 11 additions & 0 deletions .changeset/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"$schema": "https://unpkg.com/@changesets/config@3.1.4/schema.json",
"changelog": "@changesets/cli/changelog",
"commit": false,
"fixed": [],
"linked": [],
"access": "public",
"baseBranch": "main",
"updateInternalDependencies": "patch",
"ignore": ["rei-standard-examples"]
}
5 changes: 5 additions & 0 deletions .changeset/instant-gzip-request-body.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@rei-standard/amsg-instant": minor
---

接收端支持 gzip 压缩的请求体。带 `X-Amsg-Request-Encoding: gzip` 头的请求会先 gunzip 再解析,不带这个头的请求按原样读取,行为不变。CORS 预检白名单里也加上了这个头。这样 `@rei-standard/amsg-client` 的 `deliver({ compressRequest })` 就能直接发到 `amsg-instant` 的 `/instant` / `/continue`,不用自己在后端解压。
5 changes: 5 additions & 0 deletions .changeset/server-vapid-subject-fix.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@rei-standard/amsg-server": patch
---

VAPID subject 规范化支持 `https:` 形式:RFC 8292 允许 subject 使用 `https:`,规范化时按原样保留,不另加 `mailto:` 前缀。reasoning 私有思考过滤、`avatarUrl` 校验、VAPID subject 规范化统一改用 `@rei-standard/amsg-shared` 的实现。
9 changes: 9 additions & 0 deletions .changeset/shared-validation-vapid-reasoning.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
"@rei-standard/amsg-shared": minor
---

新增三组共享纯函数,让 server / instant / client 复用同一份规则,不再各自维护副本:

- `validateAvatarUrl`(含 `isValidUrl` 与 `AVATAR_URL_MAX_LENGTH`)—— 头像 URL 校验
- `normalizeVapidSubject` —— VAPID subject 规范化(`mailto:` / `https:` 均保留,裸邮箱补 `mailto:`)
- `readReasoningContent` / `stripReasoningTags` —— 读取推理内容与剥离私有 `<think>` 链式思考
60 changes: 31 additions & 29 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,41 +2,25 @@ name: Release

on:
push:
tags:
# Per-package release tags only. Historical `v*` tags (v2.1.0, etc.)
# were left over from when this repo was a single package — the root
# package.json is now `private: true` with no version field, so a
# `v*` tag has no semantic meaning here. Manual sweep-all is still
# available via `workflow_dispatch`.
- 'rei-standard-amsg-*@*'
branches:
- main
workflow_dispatch:

# Per-ref concurrency group: each tag (or workflow_dispatch ref) gets its
# own queue, so coordinated multi-package releases (e.g. instant + server
# + client tags pushed together) all run in parallel without cancelling
# each other.
#
# Earlier this was `group: release` (single global queue). Combined with
# `cancel-in-progress: false`, GitHub Actions' semantics ("1 running + 1
# pending max; new arrivals evict the pending one") meant pushing N tags
# at once kept only the first running + the last pending — middle tags
# were silently cancelled. Per-ref grouping fixes this; npm-side races
# are already prevented by scripts/publish-workspaces.mjs filtering on
# GITHUB_REF (only the triggering tag's package is published), with
# `isVersionPublished()` as the idempotent backstop.
#
# `cancel-in-progress: false` is still important within a single ref:
# half-finished publishes must never be killed mid-flight.
# One release run at a time. The Changesets action either opens/updates the
# "Version Packages" PR (when changesets are pending) or publishes the
# already-versioned packages (when that PR has merged) — never both in
# parallel, so a single global queue is what we want here.
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: false

permissions:
contents: read
id-token: write
contents: write # create the Version Packages PR + push version-bump commit / git tags
pull-requests: write # open / update the Version Packages PR
id-token: write # npm provenance via OIDC trusted publishing

jobs:
publish:
release:
runs-on: ubuntu-latest

steps:
Expand Down Expand Up @@ -77,7 +61,25 @@ jobs:
- name: Build and test
run: npm run ci

- name: Publish public workspaces
- name: Create Release PR or publish
uses: changesets/action@v1
with:
# When changesets are pending: open/refresh the "Version Packages" PR.
# `npm run version` runs `changeset version` (bump versions + write
# CHANGELOGs) AND `npm install --package-lock-only`. The lockfile step
# is required: `changeset version` only touches package.json/CHANGELOG,
# but this repo commits package-lock.json with workspace versions and
# internal dep ranges. Without the refresh, the merged Version PR would
# leave the lockfile stale and the next `npm ci` (above) would fail
# before publishing.
# When that PR has merged (no pending changesets, versions ahead of
# npm): run `npm run release` → `changeset publish` to publish each
# bumped package and push its git tag.
version: npm run version
publish: npm run release
env:
NPM_PUBLISH_PROVENANCE: 'true'
run: npm run publish:workspaces
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# Keep npm provenance on publish (changeset publish honours this
# the same way `npm publish --provenance` does; needs npm >= 9 and
# the id-token: write permission above).
NPM_CONFIG_PROVENANCE: 'true'
32 changes: 12 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,39 +4,31 @@

## 📦 包

| 包 | 版本 | 用途 |
|---|---|---|
| [`@rei-standard/amsg-shared`](./packages/rei-standard-amsg/shared/README.md) | `0.2.0` | 三轴推送契约(`AmsgPush` 判别联合 + builders + 类型守卫) |
| [`@rei-standard/amsg-instant`](./packages/rei-standard-amsg/instant/README.md) | `0.9.0` | 一次性即时推送(SSE 默认传输、always-on Web Push backup) |
| [`@rei-standard/amsg-server`](./packages/rei-standard-amsg/server/README.md) | `2.5.0` | 定时 / 周期消息,多租户 Blob 配置 + token 鉴权 |
| [`@rei-standard/amsg-client`](./packages/rei-standard-amsg/client/README.md) | `2.4.0` | 浏览器 SDK:加密、请求封装、Push 订阅、SSE consumer |
| [`@rei-standard/amsg-sw`](./packages/rei-standard-amsg/sw/README.md) | `2.2.0` | Service Worker:推送展示、离线队列、delivery dedupe |
| 包 | 用途 |
|---|---|
| [`@rei-standard/amsg-shared`](./packages/rei-standard-amsg/shared/README.md) | 推送 schema(`AmsgPush` 判别联合 + builders + 类型守卫) |
| [`@rei-standard/amsg-instant`](./packages/rei-standard-amsg/instant/README.md) | 一次性即时推送(SSE 默认传输、always-on Web Push backup) |
| [`@rei-standard/amsg-server`](./packages/rei-standard-amsg/server/README.md) | 定时 / 周期消息,多租户 Blob 配置 + token 鉴权 |
| [`@rei-standard/amsg-client`](./packages/rei-standard-amsg/client/README.md) | 浏览器 SDK:加密、请求封装、Push 订阅、deliver() 送达裁决 / SSE consumer |
| [`@rei-standard/amsg-sw`](./packages/rei-standard-amsg/sw/README.md) | Service Worker:推送展示、离线队列、delivery dedupe |

`amsg-shared` 是依赖图最底层:其他四个包都依赖它,反过来不行;它本身零运行时依赖。

**怎么挑服务端包**:只发"按钮点了就立刻推一条" → `amsg-instant`;要定时或周期任务 → `amsg-server`;两种都要就都装,共用同一套 VAPID 与 masterKey。

### 协调发布说明:稳定版发布(shared 0.2.0 / instant 0.9.0 / sw 2.2.0 / client 2.4.0 / server 2.5.0)
### 版本与发布

本轮补上 SSE + Web Push backup 的同 key 去重链路,并将相关包作为稳定版发布。`amsg-server` 没有运行时行为改动,只做 shared 依赖协调发版。

- `@rei-standard/amsg-shared`:`0.1.0` → `0.2.0`
- `@rei-standard/amsg-instant`:`0.8.2` → `0.9.0`
- `@rei-standard/amsg-server`:`2.4.1` → `2.5.0`
- `@rei-standard/amsg-sw`:`2.1.1` → `2.2.0`
- `@rei-standard/amsg-client`:`2.3.0` → `2.4.0`

包间依赖一律使用**精确版本**(不带 `^`),避免 npm 在生态系统里解析出混版本图。本轮重点是:`amsg-instant` 默认 SSE 传输与 always-on Web Push backup、`amsg-client` 的 SSE consumer、`amsg-sw` 的 delivery dedupe / `REI_AMSG_DELIVER` bridge,以及 shared 的 `notification.silent` 类型补齐。
版本号、CHANGELOG 与发布由 [Changesets](https://github.com/changesets/changesets) 管理。五个包各自独立版本(不绑成同一个号)。发布流程见 [`RELEASING.md`](./RELEASING.md):写 changeset → 合到 `main` → CI 开「Version Packages」PR,合并该 PR 即发版。

**安装最新版(`latest` dist-tag)**:

```bash
npm install @rei-standard/amsg-shared @rei-standard/amsg-instant @rei-standard/amsg-server @rei-standard/amsg-sw @rei-standard/amsg-client
```

## 三轴推送语义(Three-axis push schema
## 推送 schema

每一条推送都由三个**正交**的维度描述。把"用什么方式发出去"(dispatch)、"业务命名空间"(business)、"载荷里装的是什么"(content)拆开,让一个 axis 加值的时候不需要动另外两个 axis
每条推送用三个互不影响的维度描述:"用什么方式发出去"(dispatch)、"属于哪个业务"(business)、"载荷里装的是什么"(content)。三者拆开,给某一个维度加新值时,另外两个不用动

| 轴 | 字段 | 取值 | 由谁定 |
|---|---|---|---|
Expand Down Expand Up @@ -90,7 +82,7 @@ npm install @rei-standard/amsg-client @rei-standard/amsg-sw
ReiStandard/
├── standards/ # 权威规范文本(端点、字段、错误码)
├── packages/rei-standard-amsg/ # 5 个发布到 npm 的 SDK 包
│ ├── shared/ # 三轴推送契约(最底层,其他包都依赖)
│ ├── shared/ # 推送 schema(最底层,其他包都依赖)
│ ├── server/ # 定时 / 周期消息(多租户 Blob + token)
│ ├── instant/ # 一次性即时推送(无 DB / 无 cron)
│ ├── client/ # 浏览器 SDK(加密、请求封装、Push 订阅)
Expand Down
29 changes: 29 additions & 0 deletions RELEASING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# 发布流程

本仓库用 [Changesets](https://github.com/changesets/changesets) 管理版本号、CHANGELOG 和发布。五个包(`shared` / `client` / `instant` / `server` / `sw`)各自独立版本。

## 怎么发版

1. **写 changeset**:在你的功能分支上跑

```bash
npx changeset
```

交互里勾选这次改动涉及哪些包、各自选 bump 级别(`patch` / `minor` / `major`),再写一句面向用户的变更摘要。命令会在 `.changeset/` 下生成一个 Markdown 文件,跟代码一起提交进 PR。

> 只改文档、测试、CI 这类不影响发布产物的,可以不写 changeset。

2. **合并到 `main`**:你的功能 PR 正常评审、合并。

3. **「Version Packages」PR**:`main` 上一旦有待处理的 changeset,Release workflow 会自动开(或刷新)一个标题为 *Version Packages* 的 PR。这个 PR 会把 changeset 应用掉——按 bump 级别抬版本号、写进各包的 `CHANGELOG.md`、删掉已消费的 changeset 文件。`updateInternalDependencies: patch` 让被依赖包升版时,依赖方的内部依赖区间也跟着对齐。version 命令在 `changeset version` 之后还会跑 `npm install --package-lock-only` 刷新 `package-lock.json`,让锁文件里的 workspace 版本号与抬版后的 package.json 对齐,否则合并该 PR 后下一次 `npm ci` 会因锁文件过时而失败。

4. **合并「Version Packages」PR 即发版**:合并后,同一个 workflow 跑 `changeset publish`,把版本号领先于 npm 的包逐个发布(带 npm provenance),并推对应的 git tag。

## 内部依赖区间

四个上层包对 `@rei-standard/amsg-shared` 用 `^0.2.0`。在 0.x 上脱字号只放行同一 minor 内的补丁(`0.2.x`),所以 shared 出补丁时消费者自动跟随、不必协调重发;shared 升 minor(如 `0.3.0`)不会被自动选中,要消费者在自己的 changeset 里显式升级区间。

## 权限与密钥

发布走 npm 的 OIDC trusted publishing,不需要在仓库里配 `NPM_TOKEN`。Release workflow 申请了 `id-token: write` 权限并把 npm 升到 `>= 11.5.1`,发布时带 `--provenance`。前提是 npm 侧已为这些包配好 trusted publisher(指向本仓库的 Release workflow)。`changesets/action` 开 PR / 推 tag 用的是 GitHub 自带的 `GITHUB_TOKEN`。
18 changes: 0 additions & 18 deletions bump.mjs

This file was deleted.

8 changes: 3 additions & 5 deletions docs/VERCEL_TEST_DEPLOY.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,9 @@
## 环境变量

- 必需:`TENANT_DATABASE_URL`
- 可选:`INIT_SECRET`(服务端启用初始化鉴权时再配置)

可选:

- `TEST_USER_ID`
- 可选:
- `INIT_SECRET`(服务端启用初始化鉴权时再配置)
- `TEST_USER_ID`

说明:测试端点会先调用 `init-tenant`,然后自动完成 `get-user-key`、`schedule-message`、`send-notifications` 验证。

Expand Down
Loading
Loading