@rei-standard/amsg-shared 0.4.0-next.6 → 0.4.0-next.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +34 -2
- package/dist/index.cjs +12 -0
- package/dist/index.d.cts +108 -0
- package/dist/index.d.ts +108 -0
- package/dist/index.mjs +12 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -70,7 +70,7 @@ omit all four.
|
|
|
70
70
|
|
|
71
71
|
| Field | Type | Notes |
|
|
72
72
|
|----------------------|-------------------------------------------|-------|
|
|
73
|
-
| `show` | `'auto' \| 'always' \| 'when-hidden' \| false` | Display policy
|
|
73
|
+
| `show` | `'auto' \| 'always' \| 'when-hidden' \| false` | Display policy — see [选哪个 `show`](#选哪个-show). |
|
|
74
74
|
| `title` | `string?` | Notification title override. |
|
|
75
75
|
| `body` | `string?` | Notification body override. |
|
|
76
76
|
| `icon` | `string?` | Notification icon URL. |
|
|
@@ -84,6 +84,38 @@ omit all four.
|
|
|
84
84
|
Unknown fields are preserved for forward compatibility, but the known
|
|
85
85
|
fields above are validated by the builders when present.
|
|
86
86
|
|
|
87
|
+
### 选哪个 `show`
|
|
88
|
+
|
|
89
|
+
订阅是按 `userVisibleOnly: true` 建的,收到 push 却不弹通知就是违约:Chrome 替你弹一条通用横幅,Firefox 有配额、超了退订,iOS 给新订阅几天宽限期(跟条数无关)、过期后一条不弹就吊销订阅。所以口径只有一条,跟机型无关——**要推就一定弹,不想弹就别推。**
|
|
90
|
+
|
|
91
|
+
| 值 | SW 那边 | 什么时候用 |
|
|
92
|
+
|---|---|---|
|
|
93
|
+
| 不配 / `'auto'` | 按 `messageKind` 走默认(`content` / `result` 弹,其余不弹) | 默认,多数 payload 不用管 |
|
|
94
|
+
| `'always'` | 一定弹 | 要推的一律用它。嫌打扰配 `tag` 折叠加 `silent`,而不是不弹 |
|
|
95
|
+
| `false` | 一定不弹 | 明说这条不弹。有收件箱的发送端据此**根本不发这条 push**,内容落收件箱等客户端补拉 |
|
|
96
|
+
| `'when-hidden'` | 有可见窗口就不弹 | 兼容档,新代码不选——应用在前台时它就是一条不弹的 push,那笔账照记 |
|
|
97
|
+
|
|
98
|
+
所以 `show: false` 在发送端和接收端不是同一件事:有服务端收件箱的发送端(`@rei-standard/amsg-server` 单用户线)见到它压根不发,SW 那边收不到;没有收件箱的发送端才是「推过去、SW 不弹」。
|
|
99
|
+
|
|
100
|
+
想要「立刻推到、但页面自己渲染」没有专门的档:用 `'always'` + `tag` + `silent`,页面自绘不受影响(`postMessage` 跟弹不弹通知无关)。
|
|
101
|
+
|
|
102
|
+
完整取舍见 [`@rei-standard/amsg-sw` README 的「不展示通知的代价」](https://github.com/Tosd0/ReiStandard/blob/main/packages/rei-standard-amsg/sw/README.md#不展示通知的代价)。
|
|
103
|
+
|
|
104
|
+
### `notificationIntent(payload)`
|
|
105
|
+
|
|
106
|
+
把上面这套规则算成一个值,`'always'` / `'when-hidden'` / `'never'`:`notification.show` 说了算,没说才按 `messageKind` 走默认。两端读同一份——SW 拿它决定要不要 `showNotification`,发送端拿它决定这条值不值得占用推送通道(`'never'` 的 payload 推过去不会有任何可见反馈,`@rei-standard/amsg-server` 只把它落进收件箱,等客户端上线 `GET /outbox?since=` 补拉)。
|
|
107
|
+
|
|
108
|
+
```js
|
|
109
|
+
import { notificationIntent } from '@rei-standard/amsg-shared';
|
|
110
|
+
|
|
111
|
+
notificationIntent({ messageKind: 'content' }); // 'always'
|
|
112
|
+
notificationIntent({ messageKind: 'reasoning' }); // 'never'
|
|
113
|
+
notificationIntent({ messageKind: 'reasoning', notification: { show: 'always' } }); // 'always'
|
|
114
|
+
notificationIntent({ messageKind: 'content', notification: { show: false } }); // 'never'
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
`'when-hidden'` 单独占一档,因为它到底弹不弹要看当下有没有可见窗口,那只有 SW 知道;发送端把它当「可能会弹」照发。
|
|
118
|
+
|
|
87
119
|
---
|
|
88
120
|
|
|
89
121
|
## Per-kind fields
|
|
@@ -168,7 +200,7 @@ The legacy `type` field is **gone** — do not look for it on
|
|
|
168
200
|
两处与别的 kind 不同:
|
|
169
201
|
|
|
170
202
|
- **投递路径**:产出方(`@rei-standard/amsg-server` 的 `ctx.emitResult()`)除了推送,还把它落进服务端收件箱,客户端下次 `GET /outbox?since=` 一定拿得到。
|
|
171
|
-
- **通知**:SW 侧默认弹(与 `content` 同待遇,其余三种是静默送给页面)——结果往往正是「跑完了,回来看看」那句话。不想弹就带 `notification: { show: false }
|
|
203
|
+
- **通知**:SW 侧默认弹(与 `content` 同待遇,其余三种是静默送给页面)——结果往往正是「跑完了,回来看看」那句话。不想弹就带 `notification: { show: false }`:这一档 `amsg-server` 不发推送、只落收件箱,客户端补拉时拿到(见[选哪个 `show`](#选哪个-show))。
|
|
172
204
|
|
|
173
205
|
---
|
|
174
206
|
|
package/dist/index.cjs
CHANGED
|
@@ -66,6 +66,7 @@ __export(src_exports, {
|
|
|
66
66
|
jsonToBase64Url: () => jsonToBase64Url,
|
|
67
67
|
normalizeAiApiUrl: () => normalizeAiApiUrl,
|
|
68
68
|
normalizeVapidSubject: () => normalizeVapidSubject,
|
|
69
|
+
notificationIntent: () => notificationIntent,
|
|
69
70
|
randomBytes: () => randomBytes,
|
|
70
71
|
randomUUID: () => randomUUID,
|
|
71
72
|
readReasoningContent: () => readReasoningContent,
|
|
@@ -845,6 +846,17 @@ var PUSH_SOURCE = Object.freeze({
|
|
|
845
846
|
INSTANT: "instant",
|
|
846
847
|
SCHEDULED: "scheduled"
|
|
847
848
|
});
|
|
849
|
+
function notificationIntent(payload) {
|
|
850
|
+
const notification = payload && typeof payload === "object" && payload.notification;
|
|
851
|
+
const show = notification && typeof notification === "object" ? notification.show : void 0;
|
|
852
|
+
if (show === "always") return "always";
|
|
853
|
+
if (show === "when-hidden") return "when-hidden";
|
|
854
|
+
if (show === false) return "never";
|
|
855
|
+
if (!payload || typeof payload !== "object") return "never";
|
|
856
|
+
const kind = payload.messageKind;
|
|
857
|
+
if (kind === void 0 || kind === null) return "always";
|
|
858
|
+
return kind === MESSAGE_KIND.CONTENT || kind === MESSAGE_KIND.RESULT ? "always" : "never";
|
|
859
|
+
}
|
|
848
860
|
function requireField(kind, field, value) {
|
|
849
861
|
if (value === void 0 || value === null || value === "") {
|
|
850
862
|
throw new Error(`[amsg-shared] ${kind}: '${field}' is required`);
|
package/dist/index.d.cts
CHANGED
|
@@ -1,3 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fields present on every push, regardless of kind. Discriminator
|
|
3
|
+
* fields (`messageKind`) and kind-specific fields live on the kind
|
|
4
|
+
* interfaces below.
|
|
5
|
+
*
|
|
6
|
+
* `metadata` is a passthrough namespace owned by the caller. Packages
|
|
7
|
+
* are forbidden from writing their own fields into `metadata` — any
|
|
8
|
+
* protocol-level data goes on top-level fields.
|
|
9
|
+
*
|
|
10
|
+
* Scheduling identity (`taskId` / `taskUuid` / `recurrenceType` /
|
|
11
|
+
* `occurrenceMs`) is stamped by `@rei-standard/amsg-server` on every push
|
|
12
|
+
* that came out of a scheduled task row. It tells the client which task
|
|
13
|
+
* this is, whether the task will come back, and which nominal fire time
|
|
14
|
+
* produced this burst — so a task the client never created (one the
|
|
15
|
+
* character scheduled for itself at fire time) still arrives fully
|
|
16
|
+
* identified. Pushes with no task behind them (amsg-instant) omit all four.
|
|
17
|
+
*
|
|
18
|
+
* @typedef {Object} AmsgPushCommon
|
|
19
|
+
* @property {MessageType} messageType - How the push was produced.
|
|
20
|
+
* @property {PushSource} source - Which sub-package routed it.
|
|
21
|
+
* @property {string} messageId - Unique per push. Format owned by the producer.
|
|
22
|
+
* @property {string} sessionId - Shared across all pushes from one LLM round (reasoning + content) and across iterations of a single agentic-loop request. Opaque id — do not parse it for task identity, read the fields below.
|
|
23
|
+
* @property {string} timestamp - ISO 8601 timestamp at producer.
|
|
24
|
+
* @property {string} [messageSubtype] - Caller-defined business namespace. Defaults to 'chat' at producers.
|
|
25
|
+
* @property {Object} [metadata] - Caller passthrough. Packages MUST NOT write here.
|
|
26
|
+
* @property {NotificationDirective} [notification] - SW notification strategy.
|
|
27
|
+
* @property {number | string | null} [taskId] - Scheduled task row id.
|
|
28
|
+
* @property {string | null} [taskUuid] - Scheduled task uuid (the id the scheduling side chose).
|
|
29
|
+
* @property {'none' | 'daily' | 'weekly'} [recurrenceType] - Whether that task fires again.
|
|
30
|
+
* @property {number | null} [occurrenceMs] - Nominal fire time of this occurrence (epoch ms).
|
|
31
|
+
*/
|
|
32
|
+
/**
|
|
33
|
+
* SW-rendering directive. Mirrors the fields that `amsg-sw`'s
|
|
34
|
+
* `createNotificationFromPayload` consumes (`notification.{show,title,body,icon,badge,tag,renotify,requireInteraction,silent,data}`)
|
|
35
|
+
* so producers get builder validation for the fields the SW actually reads.
|
|
36
|
+
*
|
|
37
|
+
* Routing in SW:
|
|
38
|
+
* - By default (`show: "auto"` or omitted), `messageKind: 'content'` / `'result'` (and legacy
|
|
39
|
+
* un-kinded payloads) will display a system notification. `reasoning` / `tool_request` /
|
|
40
|
+
* `error` will dispatch silently.
|
|
41
|
+
* - `show: "always"`, `"when-hidden"`, or `false` overrides this default.
|
|
42
|
+
* - When rendering, `notification.*` is consulted first, with per-field
|
|
43
|
+
* fallback to the matching top-level payload fields (`title`,
|
|
44
|
+
* `body`/`message`, `icon`/`avatarUrl`, `badge`, `tag`/`messageId`,
|
|
45
|
+
* `renotify`, `requireInteraction`, `silent`, `data`), and finally to
|
|
46
|
+
* the SW's `defaultIcon` / `defaultBadge` options (boolean knobs
|
|
47
|
+
* default to `false` at the SW). Prefer setting overrides under
|
|
48
|
+
* `notification` for explicitness; top-level fallback exists so that
|
|
49
|
+
* legacy un-namespaced payloads keep working byte-for-byte.
|
|
50
|
+
*
|
|
51
|
+
* 选 `show` 的口径只有一条,跟机型无关:**要推就一定弹**(`"always"`,嫌打扰
|
|
52
|
+
* 配 `tag` 折叠 + `silent`),**不想弹就别推**(不配或配 `false`,内容落服务端
|
|
53
|
+
* 收件箱,等客户端上线补拉)。收到 push 却不弹通知是违约:Firefox 按配额退
|
|
54
|
+
* 订,iOS 过了订阅宽限期直接吊销。`"when-hidden"` 是给老部署留的兼容档,新代
|
|
55
|
+
* 码不选。`show: false` 在两端也不是同一件事——有收件箱的发送端见到它根本不发
|
|
56
|
+
* 这条 push,SW 压根收不到。
|
|
57
|
+
*
|
|
58
|
+
* 各档取舍见本包 README 的「选哪个 `show`」与 `@rei-standard/amsg-sw` README
|
|
59
|
+
* 的「不展示通知的代价」。
|
|
60
|
+
*
|
|
61
|
+
* @typedef {Object} NotificationDirective
|
|
62
|
+
* @property {"auto" | "always" | "when-hidden" | false} [show] - Rendering strategy. Defaults to "auto" (render only if messageKind is content).
|
|
63
|
+
* @property {string} [title] - Notification title override (falls back to top-level `title`, then `来自 {contactName}`).
|
|
64
|
+
* @property {string} [body] - Notification body override (falls back to top-level `body`, then `message`).
|
|
65
|
+
* @property {string} [icon] - Icon URL override (falls back to top-level `icon`/`avatarUrl`, then SW `defaultIcon`).
|
|
66
|
+
* @property {string} [badge] - Badge URL override (falls back to top-level `badge`, then SW `defaultBadge`).
|
|
67
|
+
* @property {string} [tag] - Notification grouping tag; matching tag replaces the prior notification (falls back to top-level `tag`, then `messageId`, then a generated unique tag).
|
|
68
|
+
* @property {boolean} [renotify] - When tag matches, still vibrate/sound (falls back to top-level `renotify`, default false at SW).
|
|
69
|
+
* @property {boolean} [requireInteraction] - Notification stays until user dismisses (falls back to top-level `requireInteraction`, default false at SW).
|
|
70
|
+
* @property {boolean} [silent] - Suppress sound and vibration (falls back to top-level `silent`, default false at SW).
|
|
71
|
+
* @property {Record<string, unknown>} [data] - Custom payload data to attach to the notification (falls back to top-level `data`).
|
|
72
|
+
*/
|
|
73
|
+
/**
|
|
74
|
+
* 这条 payload 到了客户端会不会弹系统通知——发送端与 SW 共用的这一份判定。
|
|
75
|
+
*
|
|
76
|
+
* 三个取值:
|
|
77
|
+
*
|
|
78
|
+
* | 返回值 | 意思 |
|
|
79
|
+
* |----------------|------|
|
|
80
|
+
* | `'always'` | 一定弹 |
|
|
81
|
+
* | `'when-hidden'`| 看有没有可见窗口,由 SW 当场定(发送端无从知道)。兼容档,新代码用 `'always'` |
|
|
82
|
+
* | `'never'` | 一定不弹 |
|
|
83
|
+
*
|
|
84
|
+
* 判定顺序跟 SW 里一模一样:`notification.show` 说了算,没说才按 `messageKind`
|
|
85
|
+
* 走默认(`content` / `result` 与缺 kind 的 2.0.x 老 payload 弹,`reasoning` /
|
|
86
|
+
* `tool_request` / `error` 不弹)。
|
|
87
|
+
*
|
|
88
|
+
* 两端各拿它做一件事:
|
|
89
|
+
*
|
|
90
|
+
* - **SW**:决定要不要 `showNotification`。
|
|
91
|
+
* - **发送端**:决定这条值不值得占用推送通道。`'never'` 的 payload 推过去就是
|
|
92
|
+
* 一次「收了 push 却不弹通知」,浏览器那边要记账;有收件箱兜底的发送端
|
|
93
|
+
* (`@rei-standard/amsg-server`)因此只落行、不推,等客户端上线补拉。
|
|
94
|
+
*
|
|
95
|
+
* @param {Record<string, unknown>|null|undefined} payload
|
|
96
|
+
* @returns {'always' | 'when-hidden' | 'never'}
|
|
97
|
+
*/
|
|
98
|
+
export function notificationIntent(payload: Record<string, unknown> | null | undefined): "always" | "when-hidden" | "never";
|
|
1
99
|
/**
|
|
2
100
|
* Build a {@link ContentPush}. Use this for legacy sentence-split
|
|
3
101
|
* bursts (set `messageIndex` 1-based + `totalMessages`) or for a
|
|
@@ -584,6 +682,16 @@ export type AmsgPushCommon = {
|
|
|
584
682
|
* default to `false` at the SW). Prefer setting overrides under
|
|
585
683
|
* `notification` for explicitness; top-level fallback exists so that
|
|
586
684
|
* legacy un-namespaced payloads keep working byte-for-byte.
|
|
685
|
+
*
|
|
686
|
+
* 选 `show` 的口径只有一条,跟机型无关:**要推就一定弹**(`"always"`,嫌打扰
|
|
687
|
+
* 配 `tag` 折叠 + `silent`),**不想弹就别推**(不配或配 `false`,内容落服务端
|
|
688
|
+
* 收件箱,等客户端上线补拉)。收到 push 却不弹通知是违约:Firefox 按配额退
|
|
689
|
+
* 订,iOS 过了订阅宽限期直接吊销。`"when-hidden"` 是给老部署留的兼容档,新代
|
|
690
|
+
* 码不选。`show: false` 在两端也不是同一件事——有收件箱的发送端见到它根本不发
|
|
691
|
+
* 这条 push,SW 压根收不到。
|
|
692
|
+
*
|
|
693
|
+
* 各档取舍见本包 README 的「选哪个 `show`」与 `@rei-standard/amsg-sw` README
|
|
694
|
+
* 的「不展示通知的代价」。
|
|
587
695
|
*/
|
|
588
696
|
export type NotificationDirective = {
|
|
589
697
|
/**
|
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fields present on every push, regardless of kind. Discriminator
|
|
3
|
+
* fields (`messageKind`) and kind-specific fields live on the kind
|
|
4
|
+
* interfaces below.
|
|
5
|
+
*
|
|
6
|
+
* `metadata` is a passthrough namespace owned by the caller. Packages
|
|
7
|
+
* are forbidden from writing their own fields into `metadata` — any
|
|
8
|
+
* protocol-level data goes on top-level fields.
|
|
9
|
+
*
|
|
10
|
+
* Scheduling identity (`taskId` / `taskUuid` / `recurrenceType` /
|
|
11
|
+
* `occurrenceMs`) is stamped by `@rei-standard/amsg-server` on every push
|
|
12
|
+
* that came out of a scheduled task row. It tells the client which task
|
|
13
|
+
* this is, whether the task will come back, and which nominal fire time
|
|
14
|
+
* produced this burst — so a task the client never created (one the
|
|
15
|
+
* character scheduled for itself at fire time) still arrives fully
|
|
16
|
+
* identified. Pushes with no task behind them (amsg-instant) omit all four.
|
|
17
|
+
*
|
|
18
|
+
* @typedef {Object} AmsgPushCommon
|
|
19
|
+
* @property {MessageType} messageType - How the push was produced.
|
|
20
|
+
* @property {PushSource} source - Which sub-package routed it.
|
|
21
|
+
* @property {string} messageId - Unique per push. Format owned by the producer.
|
|
22
|
+
* @property {string} sessionId - Shared across all pushes from one LLM round (reasoning + content) and across iterations of a single agentic-loop request. Opaque id — do not parse it for task identity, read the fields below.
|
|
23
|
+
* @property {string} timestamp - ISO 8601 timestamp at producer.
|
|
24
|
+
* @property {string} [messageSubtype] - Caller-defined business namespace. Defaults to 'chat' at producers.
|
|
25
|
+
* @property {Object} [metadata] - Caller passthrough. Packages MUST NOT write here.
|
|
26
|
+
* @property {NotificationDirective} [notification] - SW notification strategy.
|
|
27
|
+
* @property {number | string | null} [taskId] - Scheduled task row id.
|
|
28
|
+
* @property {string | null} [taskUuid] - Scheduled task uuid (the id the scheduling side chose).
|
|
29
|
+
* @property {'none' | 'daily' | 'weekly'} [recurrenceType] - Whether that task fires again.
|
|
30
|
+
* @property {number | null} [occurrenceMs] - Nominal fire time of this occurrence (epoch ms).
|
|
31
|
+
*/
|
|
32
|
+
/**
|
|
33
|
+
* SW-rendering directive. Mirrors the fields that `amsg-sw`'s
|
|
34
|
+
* `createNotificationFromPayload` consumes (`notification.{show,title,body,icon,badge,tag,renotify,requireInteraction,silent,data}`)
|
|
35
|
+
* so producers get builder validation for the fields the SW actually reads.
|
|
36
|
+
*
|
|
37
|
+
* Routing in SW:
|
|
38
|
+
* - By default (`show: "auto"` or omitted), `messageKind: 'content'` / `'result'` (and legacy
|
|
39
|
+
* un-kinded payloads) will display a system notification. `reasoning` / `tool_request` /
|
|
40
|
+
* `error` will dispatch silently.
|
|
41
|
+
* - `show: "always"`, `"when-hidden"`, or `false` overrides this default.
|
|
42
|
+
* - When rendering, `notification.*` is consulted first, with per-field
|
|
43
|
+
* fallback to the matching top-level payload fields (`title`,
|
|
44
|
+
* `body`/`message`, `icon`/`avatarUrl`, `badge`, `tag`/`messageId`,
|
|
45
|
+
* `renotify`, `requireInteraction`, `silent`, `data`), and finally to
|
|
46
|
+
* the SW's `defaultIcon` / `defaultBadge` options (boolean knobs
|
|
47
|
+
* default to `false` at the SW). Prefer setting overrides under
|
|
48
|
+
* `notification` for explicitness; top-level fallback exists so that
|
|
49
|
+
* legacy un-namespaced payloads keep working byte-for-byte.
|
|
50
|
+
*
|
|
51
|
+
* 选 `show` 的口径只有一条,跟机型无关:**要推就一定弹**(`"always"`,嫌打扰
|
|
52
|
+
* 配 `tag` 折叠 + `silent`),**不想弹就别推**(不配或配 `false`,内容落服务端
|
|
53
|
+
* 收件箱,等客户端上线补拉)。收到 push 却不弹通知是违约:Firefox 按配额退
|
|
54
|
+
* 订,iOS 过了订阅宽限期直接吊销。`"when-hidden"` 是给老部署留的兼容档,新代
|
|
55
|
+
* 码不选。`show: false` 在两端也不是同一件事——有收件箱的发送端见到它根本不发
|
|
56
|
+
* 这条 push,SW 压根收不到。
|
|
57
|
+
*
|
|
58
|
+
* 各档取舍见本包 README 的「选哪个 `show`」与 `@rei-standard/amsg-sw` README
|
|
59
|
+
* 的「不展示通知的代价」。
|
|
60
|
+
*
|
|
61
|
+
* @typedef {Object} NotificationDirective
|
|
62
|
+
* @property {"auto" | "always" | "when-hidden" | false} [show] - Rendering strategy. Defaults to "auto" (render only if messageKind is content).
|
|
63
|
+
* @property {string} [title] - Notification title override (falls back to top-level `title`, then `来自 {contactName}`).
|
|
64
|
+
* @property {string} [body] - Notification body override (falls back to top-level `body`, then `message`).
|
|
65
|
+
* @property {string} [icon] - Icon URL override (falls back to top-level `icon`/`avatarUrl`, then SW `defaultIcon`).
|
|
66
|
+
* @property {string} [badge] - Badge URL override (falls back to top-level `badge`, then SW `defaultBadge`).
|
|
67
|
+
* @property {string} [tag] - Notification grouping tag; matching tag replaces the prior notification (falls back to top-level `tag`, then `messageId`, then a generated unique tag).
|
|
68
|
+
* @property {boolean} [renotify] - When tag matches, still vibrate/sound (falls back to top-level `renotify`, default false at SW).
|
|
69
|
+
* @property {boolean} [requireInteraction] - Notification stays until user dismisses (falls back to top-level `requireInteraction`, default false at SW).
|
|
70
|
+
* @property {boolean} [silent] - Suppress sound and vibration (falls back to top-level `silent`, default false at SW).
|
|
71
|
+
* @property {Record<string, unknown>} [data] - Custom payload data to attach to the notification (falls back to top-level `data`).
|
|
72
|
+
*/
|
|
73
|
+
/**
|
|
74
|
+
* 这条 payload 到了客户端会不会弹系统通知——发送端与 SW 共用的这一份判定。
|
|
75
|
+
*
|
|
76
|
+
* 三个取值:
|
|
77
|
+
*
|
|
78
|
+
* | 返回值 | 意思 |
|
|
79
|
+
* |----------------|------|
|
|
80
|
+
* | `'always'` | 一定弹 |
|
|
81
|
+
* | `'when-hidden'`| 看有没有可见窗口,由 SW 当场定(发送端无从知道)。兼容档,新代码用 `'always'` |
|
|
82
|
+
* | `'never'` | 一定不弹 |
|
|
83
|
+
*
|
|
84
|
+
* 判定顺序跟 SW 里一模一样:`notification.show` 说了算,没说才按 `messageKind`
|
|
85
|
+
* 走默认(`content` / `result` 与缺 kind 的 2.0.x 老 payload 弹,`reasoning` /
|
|
86
|
+
* `tool_request` / `error` 不弹)。
|
|
87
|
+
*
|
|
88
|
+
* 两端各拿它做一件事:
|
|
89
|
+
*
|
|
90
|
+
* - **SW**:决定要不要 `showNotification`。
|
|
91
|
+
* - **发送端**:决定这条值不值得占用推送通道。`'never'` 的 payload 推过去就是
|
|
92
|
+
* 一次「收了 push 却不弹通知」,浏览器那边要记账;有收件箱兜底的发送端
|
|
93
|
+
* (`@rei-standard/amsg-server`)因此只落行、不推,等客户端上线补拉。
|
|
94
|
+
*
|
|
95
|
+
* @param {Record<string, unknown>|null|undefined} payload
|
|
96
|
+
* @returns {'always' | 'when-hidden' | 'never'}
|
|
97
|
+
*/
|
|
98
|
+
export function notificationIntent(payload: Record<string, unknown> | null | undefined): "always" | "when-hidden" | "never";
|
|
1
99
|
/**
|
|
2
100
|
* Build a {@link ContentPush}. Use this for legacy sentence-split
|
|
3
101
|
* bursts (set `messageIndex` 1-based + `totalMessages`) or for a
|
|
@@ -584,6 +682,16 @@ export type AmsgPushCommon = {
|
|
|
584
682
|
* default to `false` at the SW). Prefer setting overrides under
|
|
585
683
|
* `notification` for explicitness; top-level fallback exists so that
|
|
586
684
|
* legacy un-namespaced payloads keep working byte-for-byte.
|
|
685
|
+
*
|
|
686
|
+
* 选 `show` 的口径只有一条,跟机型无关:**要推就一定弹**(`"always"`,嫌打扰
|
|
687
|
+
* 配 `tag` 折叠 + `silent`),**不想弹就别推**(不配或配 `false`,内容落服务端
|
|
688
|
+
* 收件箱,等客户端上线补拉)。收到 push 却不弹通知是违约:Firefox 按配额退
|
|
689
|
+
* 订,iOS 过了订阅宽限期直接吊销。`"when-hidden"` 是给老部署留的兼容档,新代
|
|
690
|
+
* 码不选。`show: false` 在两端也不是同一件事——有收件箱的发送端见到它根本不发
|
|
691
|
+
* 这条 push,SW 压根收不到。
|
|
692
|
+
*
|
|
693
|
+
* 各档取舍见本包 README 的「选哪个 `show`」与 `@rei-standard/amsg-sw` README
|
|
694
|
+
* 的「不展示通知的代价」。
|
|
587
695
|
*/
|
|
588
696
|
export type NotificationDirective = {
|
|
589
697
|
/**
|
package/dist/index.mjs
CHANGED
|
@@ -761,6 +761,17 @@ var PUSH_SOURCE = Object.freeze({
|
|
|
761
761
|
INSTANT: "instant",
|
|
762
762
|
SCHEDULED: "scheduled"
|
|
763
763
|
});
|
|
764
|
+
function notificationIntent(payload) {
|
|
765
|
+
const notification = payload && typeof payload === "object" && payload.notification;
|
|
766
|
+
const show = notification && typeof notification === "object" ? notification.show : void 0;
|
|
767
|
+
if (show === "always") return "always";
|
|
768
|
+
if (show === "when-hidden") return "when-hidden";
|
|
769
|
+
if (show === false) return "never";
|
|
770
|
+
if (!payload || typeof payload !== "object") return "never";
|
|
771
|
+
const kind = payload.messageKind;
|
|
772
|
+
if (kind === void 0 || kind === null) return "always";
|
|
773
|
+
return kind === MESSAGE_KIND.CONTENT || kind === MESSAGE_KIND.RESULT ? "always" : "never";
|
|
774
|
+
}
|
|
764
775
|
function requireField(kind, field, value) {
|
|
765
776
|
if (value === void 0 || value === null || value === "") {
|
|
766
777
|
throw new Error(`[amsg-shared] ${kind}: '${field}' is required`);
|
|
@@ -1262,6 +1273,7 @@ export {
|
|
|
1262
1273
|
jsonToBase64Url,
|
|
1263
1274
|
normalizeAiApiUrl,
|
|
1264
1275
|
normalizeVapidSubject,
|
|
1276
|
+
notificationIntent,
|
|
1265
1277
|
randomBytes,
|
|
1266
1278
|
randomUUID,
|
|
1267
1279
|
readReasoningContent,
|
package/package.json
CHANGED