@rei-standard/amsg-shared 0.4.0-next.7 → 0.4.0-next.9
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 +13 -1
- package/dist/index.cjs +23 -5
- package/dist/index.d.cts +21 -3
- package/dist/index.d.ts +21 -3
- package/dist/index.mjs +23 -5
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -78,7 +78,7 @@ omit all four.
|
|
|
78
78
|
| `tag` | `string?` | Notification grouping tag. |
|
|
79
79
|
| `renotify` | `boolean?` | Re-alert when a matching `tag` replaces an existing notification. |
|
|
80
80
|
| `requireInteraction` | `boolean?` | Keep the notification visible until the user dismisses it. |
|
|
81
|
-
| `silent` | `boolean?`
|
|
81
|
+
| `silent` | `(boolean \| 'when-visible')?` | Suppress notification sound and vibration — see [选哪个 `silent`](#选哪个-silent). |
|
|
82
82
|
| `data` | `Record<string, unknown>?` | Custom data passed to the notification. |
|
|
83
83
|
|
|
84
84
|
Unknown fields are preserved for forward compatibility, but the known
|
|
@@ -101,6 +101,18 @@ fields above are validated by the builders when present.
|
|
|
101
101
|
|
|
102
102
|
完整取舍见 [`@rei-standard/amsg-sw` README 的「不展示通知的代价」](https://github.com/Tosd0/ReiStandard/blob/main/packages/rei-standard-amsg/sw/README.md#不展示通知的代价)。
|
|
103
103
|
|
|
104
|
+
### 选哪个 `silent`
|
|
105
|
+
|
|
106
|
+
`silent` 管的是「响不响铃、震不震」,跟弹不弹(`show`)是两件独立的事——通知照样进通知中心,只是安静地进。
|
|
107
|
+
|
|
108
|
+
| 值 | SW 那边 | 什么时候用 |
|
|
109
|
+
|---|---|---|
|
|
110
|
+
| 不配 / `false` | 正常响铃震动 | 默认 |
|
|
111
|
+
| `true` | 一律不响 | 一串连着来的通知配 `tag` 折叠,只想在通知中心留个痕迹 |
|
|
112
|
+
| `'when-visible'` | 有可见窗口就静音,没有就照常响 | 页面自己会把内容画出来的那类消息:用户正看着页面就别再响一声,人切后台了照样叫得动 |
|
|
113
|
+
|
|
114
|
+
`'when-visible'` 只能由 Service Worker 当场算:发送端发推那一刻并不知道用户此刻在不在前台,写死 `silent: true` 的话,用户切到后台收到的那条也不会响。判定用的是跟 `show: 'when-hidden'` 同一套窗口可见性口径,一条 payload 只算一次。
|
|
115
|
+
|
|
104
116
|
### `notificationIntent(payload)`
|
|
105
117
|
|
|
106
118
|
把上面这套规则算成一个值,`'always'` / `'when-hidden'` / `'never'`:`notification.show` 说了算,没说才按 `messageKind` 走默认。两端读同一份——SW 拿它决定要不要 `showNotification`,发送端拿它决定这条值不值得占用推送通道(`'never'` 的 payload 推过去不会有任何可见反馈,`@rei-standard/amsg-server` 只把它落进收件箱,等客户端上线 `GET /outbox?since=` 补拉)。
|
package/dist/index.cjs
CHANGED
|
@@ -424,7 +424,10 @@ function looksLikeJson(raw) {
|
|
|
424
424
|
function salvageJsonStringFields(raw) {
|
|
425
425
|
const found = {};
|
|
426
426
|
for (const match of raw.matchAll(JSON_STRING_FIELD)) {
|
|
427
|
-
if (found[match[1]]
|
|
427
|
+
if (found[match[1]] !== void 0) continue;
|
|
428
|
+
const value = unescapeJsonString(match[2]);
|
|
429
|
+
if (match[1] === "type" && value === "error") continue;
|
|
430
|
+
found[match[1]] = value;
|
|
428
431
|
}
|
|
429
432
|
return {
|
|
430
433
|
message: firstNonEmptyString(found.message, found.detail),
|
|
@@ -471,15 +474,27 @@ var UUID_SHAPE = /\b[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}
|
|
|
471
474
|
function alternationCount(segment) {
|
|
472
475
|
return (segment.match(/[a-z]+|[0-9]+/g) || []).length;
|
|
473
476
|
}
|
|
477
|
+
var MOE_SIZE_SEGMENT = /^\d+x\d+b$/;
|
|
478
|
+
var HEX_SEGMENT = /^[0-9a-f]+$/;
|
|
479
|
+
var HEX_RUN_MAX_CHARS = 15;
|
|
474
480
|
function looksLikeModelId(token) {
|
|
475
481
|
if (token.length > MODEL_ID_MAX_CHARS) return false;
|
|
476
482
|
if (!MODEL_ID_LIKE.test(token)) return false;
|
|
477
483
|
if (UUID_SHAPE.test(token)) return false;
|
|
478
484
|
const segments = token.split(/[.-]/);
|
|
479
485
|
if (CREDENTIAL_PREFIX_SEGMENTS.has(segments[0])) return false;
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
486
|
+
let hexRunChars = 0;
|
|
487
|
+
for (const segment of segments) {
|
|
488
|
+
if (HEX_SEGMENT.test(segment)) {
|
|
489
|
+
hexRunChars += segment.length;
|
|
490
|
+
if (hexRunChars > HEX_RUN_MAX_CHARS) return false;
|
|
491
|
+
} else {
|
|
492
|
+
hexRunChars = 0;
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
return segments.every(
|
|
496
|
+
(segment) => alternationCount(segment) < 3 || MOE_SIZE_SEGMENT.test(segment)
|
|
497
|
+
);
|
|
483
498
|
}
|
|
484
499
|
function redactCredentials(text) {
|
|
485
500
|
let s = text;
|
|
@@ -964,11 +979,14 @@ function validateNotificationArg(kind, value) {
|
|
|
964
979
|
throw new Error(`[amsg-shared] ${kind}: 'notification.${f}' must be a string when present`);
|
|
965
980
|
}
|
|
966
981
|
}
|
|
967
|
-
for (const f of ["renotify", "requireInteraction"
|
|
982
|
+
for (const f of ["renotify", "requireInteraction"]) {
|
|
968
983
|
if (n[f] !== void 0 && typeof n[f] !== "boolean") {
|
|
969
984
|
throw new Error(`[amsg-shared] ${kind}: 'notification.${f}' must be a boolean when present`);
|
|
970
985
|
}
|
|
971
986
|
}
|
|
987
|
+
if (n.silent !== void 0 && typeof n.silent !== "boolean" && n.silent !== "when-visible") {
|
|
988
|
+
throw new Error(`[amsg-shared] ${kind}: 'notification.silent' must be true, false, or "when-visible"`);
|
|
989
|
+
}
|
|
972
990
|
if (n.data !== void 0 && (n.data === null || typeof n.data !== "object" || Array.isArray(n.data))) {
|
|
973
991
|
throw new Error(`[amsg-shared] ${kind}: 'notification.data' must be a plain object when present`);
|
|
974
992
|
}
|
package/dist/index.d.cts
CHANGED
|
@@ -39,6 +39,10 @@
|
|
|
39
39
|
* un-kinded payloads) will display a system notification. `reasoning` / `tool_request` /
|
|
40
40
|
* `error` will dispatch silently.
|
|
41
41
|
* - `show: "always"`, `"when-hidden"`, or `false` overrides this default.
|
|
42
|
+
* - `silent: "when-visible"` is resolved by the SW at render time from the
|
|
43
|
+
* same window-visibility check `show: "when-hidden"` uses: silent while a
|
|
44
|
+
* `visibilityState === "visible"` window client exists, audible otherwise.
|
|
45
|
+
* `true` / `false` stay literal.
|
|
42
46
|
* - When rendering, `notification.*` is consulted first, with per-field
|
|
43
47
|
* fallback to the matching top-level payload fields (`title`,
|
|
44
48
|
* `body`/`message`, `icon`/`avatarUrl`, `badge`, `tag`/`messageId`,
|
|
@@ -55,6 +59,11 @@
|
|
|
55
59
|
* 码不选。`show: false` 在两端也不是同一件事——有收件箱的发送端见到它根本不发
|
|
56
60
|
* 这条 push,SW 压根收不到。
|
|
57
61
|
*
|
|
62
|
+
* 想要「前台安静、切后台照常响」就配 `silent: "when-visible"`:`show` 照旧
|
|
63
|
+
* `"always"`(通知一定弹出来,那笔账不欠),响不响铃由 SW 收到时看有没有可见
|
|
64
|
+
* 窗口现算。发送端定不了这件事——它发推那一刻并不知道用户在不在前台,写死
|
|
65
|
+
* `silent: true` 的结果是切后台也不响。
|
|
66
|
+
*
|
|
58
67
|
* 各档取舍见本包 README 的「选哪个 `show`」与 `@rei-standard/amsg-sw` README
|
|
59
68
|
* 的「不展示通知的代价」。
|
|
60
69
|
*
|
|
@@ -67,7 +76,7 @@
|
|
|
67
76
|
* @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
77
|
* @property {boolean} [renotify] - When tag matches, still vibrate/sound (falls back to top-level `renotify`, default false at SW).
|
|
69
78
|
* @property {boolean} [requireInteraction] - Notification stays until user dismisses (falls back to top-level `requireInteraction`, default false at SW).
|
|
70
|
-
* @property {boolean} [silent]
|
|
79
|
+
* @property {boolean | "when-visible"} [silent] - Suppress sound and vibration. `true` / `false` are literal; `"when-visible"` is decided by the SW at render time — silent while a window client is visible, audible otherwise (falls back to top-level `silent`, default false at SW).
|
|
71
80
|
* @property {Record<string, unknown>} [data] - Custom payload data to attach to the notification (falls back to top-level `data`).
|
|
72
81
|
*/
|
|
73
82
|
/**
|
|
@@ -674,6 +683,10 @@ export type AmsgPushCommon = {
|
|
|
674
683
|
* un-kinded payloads) will display a system notification. `reasoning` / `tool_request` /
|
|
675
684
|
* `error` will dispatch silently.
|
|
676
685
|
* - `show: "always"`, `"when-hidden"`, or `false` overrides this default.
|
|
686
|
+
* - `silent: "when-visible"` is resolved by the SW at render time from the
|
|
687
|
+
* same window-visibility check `show: "when-hidden"` uses: silent while a
|
|
688
|
+
* `visibilityState === "visible"` window client exists, audible otherwise.
|
|
689
|
+
* `true` / `false` stay literal.
|
|
677
690
|
* - When rendering, `notification.*` is consulted first, with per-field
|
|
678
691
|
* fallback to the matching top-level payload fields (`title`,
|
|
679
692
|
* `body`/`message`, `icon`/`avatarUrl`, `badge`, `tag`/`messageId`,
|
|
@@ -690,6 +703,11 @@ export type AmsgPushCommon = {
|
|
|
690
703
|
* 码不选。`show: false` 在两端也不是同一件事——有收件箱的发送端见到它根本不发
|
|
691
704
|
* 这条 push,SW 压根收不到。
|
|
692
705
|
*
|
|
706
|
+
* 想要「前台安静、切后台照常响」就配 `silent: "when-visible"`:`show` 照旧
|
|
707
|
+
* `"always"`(通知一定弹出来,那笔账不欠),响不响铃由 SW 收到时看有没有可见
|
|
708
|
+
* 窗口现算。发送端定不了这件事——它发推那一刻并不知道用户在不在前台,写死
|
|
709
|
+
* `silent: true` 的结果是切后台也不响。
|
|
710
|
+
*
|
|
693
711
|
* 各档取舍见本包 README 的「选哪个 `show`」与 `@rei-standard/amsg-sw` README
|
|
694
712
|
* 的「不展示通知的代价」。
|
|
695
713
|
*/
|
|
@@ -727,9 +745,9 @@ export type NotificationDirective = {
|
|
|
727
745
|
*/
|
|
728
746
|
requireInteraction?: boolean;
|
|
729
747
|
/**
|
|
730
|
-
* - Suppress sound and vibration (falls back to top-level `silent`, default false at SW).
|
|
748
|
+
* - Suppress sound and vibration. `true` / `false` are literal; `"when-visible"` is decided by the SW at render time — silent while a window client is visible, audible otherwise (falls back to top-level `silent`, default false at SW).
|
|
731
749
|
*/
|
|
732
|
-
silent?: boolean;
|
|
750
|
+
silent?: boolean | "when-visible";
|
|
733
751
|
/**
|
|
734
752
|
* - Custom payload data to attach to the notification (falls back to top-level `data`).
|
|
735
753
|
*/
|
package/dist/index.d.ts
CHANGED
|
@@ -39,6 +39,10 @@
|
|
|
39
39
|
* un-kinded payloads) will display a system notification. `reasoning` / `tool_request` /
|
|
40
40
|
* `error` will dispatch silently.
|
|
41
41
|
* - `show: "always"`, `"when-hidden"`, or `false` overrides this default.
|
|
42
|
+
* - `silent: "when-visible"` is resolved by the SW at render time from the
|
|
43
|
+
* same window-visibility check `show: "when-hidden"` uses: silent while a
|
|
44
|
+
* `visibilityState === "visible"` window client exists, audible otherwise.
|
|
45
|
+
* `true` / `false` stay literal.
|
|
42
46
|
* - When rendering, `notification.*` is consulted first, with per-field
|
|
43
47
|
* fallback to the matching top-level payload fields (`title`,
|
|
44
48
|
* `body`/`message`, `icon`/`avatarUrl`, `badge`, `tag`/`messageId`,
|
|
@@ -55,6 +59,11 @@
|
|
|
55
59
|
* 码不选。`show: false` 在两端也不是同一件事——有收件箱的发送端见到它根本不发
|
|
56
60
|
* 这条 push,SW 压根收不到。
|
|
57
61
|
*
|
|
62
|
+
* 想要「前台安静、切后台照常响」就配 `silent: "when-visible"`:`show` 照旧
|
|
63
|
+
* `"always"`(通知一定弹出来,那笔账不欠),响不响铃由 SW 收到时看有没有可见
|
|
64
|
+
* 窗口现算。发送端定不了这件事——它发推那一刻并不知道用户在不在前台,写死
|
|
65
|
+
* `silent: true` 的结果是切后台也不响。
|
|
66
|
+
*
|
|
58
67
|
* 各档取舍见本包 README 的「选哪个 `show`」与 `@rei-standard/amsg-sw` README
|
|
59
68
|
* 的「不展示通知的代价」。
|
|
60
69
|
*
|
|
@@ -67,7 +76,7 @@
|
|
|
67
76
|
* @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
77
|
* @property {boolean} [renotify] - When tag matches, still vibrate/sound (falls back to top-level `renotify`, default false at SW).
|
|
69
78
|
* @property {boolean} [requireInteraction] - Notification stays until user dismisses (falls back to top-level `requireInteraction`, default false at SW).
|
|
70
|
-
* @property {boolean} [silent]
|
|
79
|
+
* @property {boolean | "when-visible"} [silent] - Suppress sound and vibration. `true` / `false` are literal; `"when-visible"` is decided by the SW at render time — silent while a window client is visible, audible otherwise (falls back to top-level `silent`, default false at SW).
|
|
71
80
|
* @property {Record<string, unknown>} [data] - Custom payload data to attach to the notification (falls back to top-level `data`).
|
|
72
81
|
*/
|
|
73
82
|
/**
|
|
@@ -674,6 +683,10 @@ export type AmsgPushCommon = {
|
|
|
674
683
|
* un-kinded payloads) will display a system notification. `reasoning` / `tool_request` /
|
|
675
684
|
* `error` will dispatch silently.
|
|
676
685
|
* - `show: "always"`, `"when-hidden"`, or `false` overrides this default.
|
|
686
|
+
* - `silent: "when-visible"` is resolved by the SW at render time from the
|
|
687
|
+
* same window-visibility check `show: "when-hidden"` uses: silent while a
|
|
688
|
+
* `visibilityState === "visible"` window client exists, audible otherwise.
|
|
689
|
+
* `true` / `false` stay literal.
|
|
677
690
|
* - When rendering, `notification.*` is consulted first, with per-field
|
|
678
691
|
* fallback to the matching top-level payload fields (`title`,
|
|
679
692
|
* `body`/`message`, `icon`/`avatarUrl`, `badge`, `tag`/`messageId`,
|
|
@@ -690,6 +703,11 @@ export type AmsgPushCommon = {
|
|
|
690
703
|
* 码不选。`show: false` 在两端也不是同一件事——有收件箱的发送端见到它根本不发
|
|
691
704
|
* 这条 push,SW 压根收不到。
|
|
692
705
|
*
|
|
706
|
+
* 想要「前台安静、切后台照常响」就配 `silent: "when-visible"`:`show` 照旧
|
|
707
|
+
* `"always"`(通知一定弹出来,那笔账不欠),响不响铃由 SW 收到时看有没有可见
|
|
708
|
+
* 窗口现算。发送端定不了这件事——它发推那一刻并不知道用户在不在前台,写死
|
|
709
|
+
* `silent: true` 的结果是切后台也不响。
|
|
710
|
+
*
|
|
693
711
|
* 各档取舍见本包 README 的「选哪个 `show`」与 `@rei-standard/amsg-sw` README
|
|
694
712
|
* 的「不展示通知的代价」。
|
|
695
713
|
*/
|
|
@@ -727,9 +745,9 @@ export type NotificationDirective = {
|
|
|
727
745
|
*/
|
|
728
746
|
requireInteraction?: boolean;
|
|
729
747
|
/**
|
|
730
|
-
* - Suppress sound and vibration (falls back to top-level `silent`, default false at SW).
|
|
748
|
+
* - Suppress sound and vibration. `true` / `false` are literal; `"when-visible"` is decided by the SW at render time — silent while a window client is visible, audible otherwise (falls back to top-level `silent`, default false at SW).
|
|
731
749
|
*/
|
|
732
|
-
silent?: boolean;
|
|
750
|
+
silent?: boolean | "when-visible";
|
|
733
751
|
/**
|
|
734
752
|
* - Custom payload data to attach to the notification (falls back to top-level `data`).
|
|
735
753
|
*/
|
package/dist/index.mjs
CHANGED
|
@@ -339,7 +339,10 @@ function looksLikeJson(raw) {
|
|
|
339
339
|
function salvageJsonStringFields(raw) {
|
|
340
340
|
const found = {};
|
|
341
341
|
for (const match of raw.matchAll(JSON_STRING_FIELD)) {
|
|
342
|
-
if (found[match[1]]
|
|
342
|
+
if (found[match[1]] !== void 0) continue;
|
|
343
|
+
const value = unescapeJsonString(match[2]);
|
|
344
|
+
if (match[1] === "type" && value === "error") continue;
|
|
345
|
+
found[match[1]] = value;
|
|
343
346
|
}
|
|
344
347
|
return {
|
|
345
348
|
message: firstNonEmptyString(found.message, found.detail),
|
|
@@ -386,15 +389,27 @@ var UUID_SHAPE = /\b[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}
|
|
|
386
389
|
function alternationCount(segment) {
|
|
387
390
|
return (segment.match(/[a-z]+|[0-9]+/g) || []).length;
|
|
388
391
|
}
|
|
392
|
+
var MOE_SIZE_SEGMENT = /^\d+x\d+b$/;
|
|
393
|
+
var HEX_SEGMENT = /^[0-9a-f]+$/;
|
|
394
|
+
var HEX_RUN_MAX_CHARS = 15;
|
|
389
395
|
function looksLikeModelId(token) {
|
|
390
396
|
if (token.length > MODEL_ID_MAX_CHARS) return false;
|
|
391
397
|
if (!MODEL_ID_LIKE.test(token)) return false;
|
|
392
398
|
if (UUID_SHAPE.test(token)) return false;
|
|
393
399
|
const segments = token.split(/[.-]/);
|
|
394
400
|
if (CREDENTIAL_PREFIX_SEGMENTS.has(segments[0])) return false;
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
401
|
+
let hexRunChars = 0;
|
|
402
|
+
for (const segment of segments) {
|
|
403
|
+
if (HEX_SEGMENT.test(segment)) {
|
|
404
|
+
hexRunChars += segment.length;
|
|
405
|
+
if (hexRunChars > HEX_RUN_MAX_CHARS) return false;
|
|
406
|
+
} else {
|
|
407
|
+
hexRunChars = 0;
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
return segments.every(
|
|
411
|
+
(segment) => alternationCount(segment) < 3 || MOE_SIZE_SEGMENT.test(segment)
|
|
412
|
+
);
|
|
398
413
|
}
|
|
399
414
|
function redactCredentials(text) {
|
|
400
415
|
let s = text;
|
|
@@ -879,11 +894,14 @@ function validateNotificationArg(kind, value) {
|
|
|
879
894
|
throw new Error(`[amsg-shared] ${kind}: 'notification.${f}' must be a string when present`);
|
|
880
895
|
}
|
|
881
896
|
}
|
|
882
|
-
for (const f of ["renotify", "requireInteraction"
|
|
897
|
+
for (const f of ["renotify", "requireInteraction"]) {
|
|
883
898
|
if (n[f] !== void 0 && typeof n[f] !== "boolean") {
|
|
884
899
|
throw new Error(`[amsg-shared] ${kind}: 'notification.${f}' must be a boolean when present`);
|
|
885
900
|
}
|
|
886
901
|
}
|
|
902
|
+
if (n.silent !== void 0 && typeof n.silent !== "boolean" && n.silent !== "when-visible") {
|
|
903
|
+
throw new Error(`[amsg-shared] ${kind}: 'notification.silent' must be true, false, or "when-visible"`);
|
|
904
|
+
}
|
|
887
905
|
if (n.data !== void 0 && (n.data === null || typeof n.data !== "object" || Array.isArray(n.data))) {
|
|
888
906
|
throw new Error(`[amsg-shared] ${kind}: 'notification.data' must be a plain object when present`);
|
|
889
907
|
}
|
package/package.json
CHANGED