@rei-standard/amsg-sw 2.1.0-next.4 → 2.1.1
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 +4 -4
- package/dist/index.cjs +16 -3
- package/dist/index.d.cts +23 -5
- package/dist/index.d.ts +23 -5
- package/dist/index.mjs +16 -3
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -92,9 +92,9 @@ navigator.serviceWorker.addEventListener('message', (e) => {
|
|
|
92
92
|
|
|
93
93
|
当 `amsg-instant` 检测到 payload 超过 `maxInlineBytes` 时会改发 blob envelope `{ _blob: true, key, url, messageKind?, type? }`。SW **不会** 自动 fetch blob 内容(那是 client 的职责),但仍然会按 envelope 上的 `messageKind` 分发对应事件,让 client 知道有什么类型的内容即将到达,自己决定要不要拉取。Blob envelope 也只在 `messageKind === 'content'`(或缺失)时才渲染占位通知,与普通 push 行为一致。
|
|
94
94
|
|
|
95
|
-
### Generic multipart transport(
|
|
95
|
+
### Generic multipart transport(2.1.0+)
|
|
96
96
|
|
|
97
|
-
|
|
97
|
+
2.1.0 移除了旧 reasoning 专用 `chunkIndex` / `totalChunks` wire format。现在 `_multipart` 是统一 transport kind,任何原始 payload 都可以被包起来:
|
|
98
98
|
|
|
99
99
|
```json
|
|
100
100
|
{
|
|
@@ -128,7 +128,7 @@ installReiSW(self, {
|
|
|
128
128
|
maxChunks: 128,
|
|
129
129
|
cleanupIntervalMs: 15 * 60_000
|
|
130
130
|
},
|
|
131
|
-
// (新增于 2.1.0
|
|
131
|
+
// (新增于 2.1.0)离线持久化等业务拦截钩子:
|
|
132
132
|
onBusinessPayload: async (payload) => {
|
|
133
133
|
// 收到完整 payload 时触发,由于内置在 event.waitUntil 中,能够确保离线写库完毕再允许 SW 休眠
|
|
134
134
|
// await db.saveIncomingMessage(payload);
|
|
@@ -151,7 +151,7 @@ TTL 到期仍未收齐时,SW 会清理 pending 并广播:
|
|
|
151
151
|
### 升级注意事项
|
|
152
152
|
|
|
153
153
|
- 想给 `reasoning` / `tool_request` / `error` 弹通知的业务:SW 默认不再为它们弹通知,但可以通过设置 `payload.notification.show = "always"` 或 `"when-hidden"` 来让 SW 在包层直接弹通知。无需再强求在 app 内自绘。
|
|
154
|
-
- 应用级 SW 可以删除旧 reasoning `chunkIndex` / `totalChunks` 拼接逻辑;
|
|
154
|
+
- 应用级 SW 可以删除旧 reasoning `chunkIndex` / `totalChunks` 拼接逻辑;2.1.0+ 版本只会把完整还原后的 reasoning payload 发给 client。
|
|
155
155
|
- 客户端代码继续兼容只有 `installReiSW` + `REI_SW_MESSAGE_TYPE`(队列)的 2.0.x 写法——新增导出不破坏既有 API。
|
|
156
156
|
- 想拿到 push 类型相关的 TS 类型:从 `@rei-standard/amsg-shared` 引 `AmsgPush` 等类型(本包通过 JSDoc 引用同一份类型)。
|
|
157
157
|
|
package/dist/index.cjs
CHANGED
|
@@ -46,6 +46,7 @@ var DEFAULT_MULTIPART_OPTIONS = Object.freeze({
|
|
|
46
46
|
var memoryMultipartPending = /* @__PURE__ */ new Map();
|
|
47
47
|
var memoryMultipartDone = /* @__PURE__ */ new Map();
|
|
48
48
|
var memoryMultipartChunks = /* @__PURE__ */ new Map();
|
|
49
|
+
var multipartLocks = /* @__PURE__ */ new Map();
|
|
49
50
|
var REI_AMSG_POSTMESSAGE_TYPE = "REI_AMSG_PUSH";
|
|
50
51
|
var REI_SW_EVENT = Object.freeze({
|
|
51
52
|
CONTENT_RECEIVED: "rei-amsg-content-received",
|
|
@@ -146,8 +147,8 @@ async function dispatchBusinessPayload(sw, payload, defaults) {
|
|
|
146
147
|
if (typeof defaults.onBusinessPayload === "function") {
|
|
147
148
|
try {
|
|
148
149
|
const result = defaults.onBusinessPayload(payload);
|
|
149
|
-
if (result
|
|
150
|
-
work.push(result.catch((error) => {
|
|
150
|
+
if (result && typeof result.then === "function") {
|
|
151
|
+
work.push(Promise.resolve(result).catch((error) => {
|
|
151
152
|
console.error("[rei-standard-amsg-sw] onBusinessPayload promise rejected:", error);
|
|
152
153
|
}));
|
|
153
154
|
}
|
|
@@ -222,7 +223,7 @@ function createNotificationFromPayload(payload, defaults) {
|
|
|
222
223
|
};
|
|
223
224
|
}
|
|
224
225
|
const pushNotification = payload.notification && typeof payload.notification === "object" ? payload.notification : {};
|
|
225
|
-
const title = pushNotification.title || payload.title || payload.contactName || "New notification";
|
|
226
|
+
const title = pushNotification.title || payload.title || payload.contactName && `\u6765\u81EA ${payload.contactName}` || "New notification";
|
|
226
227
|
const body = pushNotification.body || payload.body || payload.message || "";
|
|
227
228
|
const data = pushNotification.data && typeof pushNotification.data === "object" ? { ...pushNotification.data } : payload.data && typeof payload.data === "object" ? { ...payload.data } : {};
|
|
228
229
|
if (data.payload == null) data.payload = payload;
|
|
@@ -266,6 +267,18 @@ function isMultipartPush(payload) {
|
|
|
266
267
|
async function acceptMultipartChunk(sw, payload, options) {
|
|
267
268
|
const normalized = normalizeMultipartChunk(payload, options);
|
|
268
269
|
if (!normalized) return null;
|
|
270
|
+
const previous = multipartLocks.get(normalized.id) || Promise.resolve();
|
|
271
|
+
const current = previous.catch(() => void 0).then(() => acceptMultipartChunkInternal(sw, normalized, options));
|
|
272
|
+
multipartLocks.set(normalized.id, current);
|
|
273
|
+
try {
|
|
274
|
+
return await current;
|
|
275
|
+
} finally {
|
|
276
|
+
if (multipartLocks.get(normalized.id) === current) {
|
|
277
|
+
multipartLocks.delete(normalized.id);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
async function acceptMultipartChunkInternal(sw, normalized, options) {
|
|
269
282
|
if (normalized.expiresAt <= Date.now()) {
|
|
270
283
|
await dispatchMultipartExpired(sw, {
|
|
271
284
|
id: normalized.id,
|
package/dist/index.d.cts
CHANGED
|
@@ -66,6 +66,7 @@ const DEFAULT_MULTIPART_OPTIONS = Object.freeze({
|
|
|
66
66
|
const memoryMultipartPending = new Map();
|
|
67
67
|
const memoryMultipartDone = new Map();
|
|
68
68
|
const memoryMultipartChunks = new Map();
|
|
69
|
+
const multipartLocks = new Map();
|
|
69
70
|
|
|
70
71
|
/**
|
|
71
72
|
* Wire-level message type for SW → client postMessage envelopes.
|
|
@@ -220,8 +221,8 @@ async function dispatchBusinessPayload(sw, payload, defaults) {
|
|
|
220
221
|
if (typeof defaults.onBusinessPayload === 'function') {
|
|
221
222
|
try {
|
|
222
223
|
const result = defaults.onBusinessPayload(payload);
|
|
223
|
-
if (result
|
|
224
|
-
work.push(result.catch(error => {
|
|
224
|
+
if (result && typeof result.then === 'function') {
|
|
225
|
+
work.push(Promise.resolve(result).catch(error => {
|
|
225
226
|
console.error('[rei-standard-amsg-sw] onBusinessPayload promise rejected:', error);
|
|
226
227
|
}));
|
|
227
228
|
}
|
|
@@ -345,7 +346,7 @@ function createNotificationFromPayload(payload, defaults) {
|
|
|
345
346
|
const title =
|
|
346
347
|
pushNotification.title ||
|
|
347
348
|
payload.title ||
|
|
348
|
-
payload.contactName ||
|
|
349
|
+
(payload.contactName && `来自 ${payload.contactName}`) ||
|
|
349
350
|
'New notification';
|
|
350
351
|
const body = pushNotification.body || payload.body || payload.message || '';
|
|
351
352
|
const data = pushNotification.data && typeof pushNotification.data === 'object'
|
|
@@ -404,14 +405,31 @@ function isMultipartPush(payload) {
|
|
|
404
405
|
}
|
|
405
406
|
|
|
406
407
|
async function acceptMultipartChunk(sw, payload, options) {
|
|
408
|
+
const normalized = normalizeMultipartChunk(payload, options);
|
|
409
|
+
if (!normalized) return null;
|
|
410
|
+
|
|
411
|
+
const previous = multipartLocks.get(normalized.id) || Promise.resolve();
|
|
412
|
+
const current = previous
|
|
413
|
+
.catch(() => undefined)
|
|
414
|
+
.then(() => acceptMultipartChunkInternal(sw, normalized, options));
|
|
415
|
+
|
|
416
|
+
multipartLocks.set(normalized.id, current);
|
|
417
|
+
try {
|
|
418
|
+
return await current;
|
|
419
|
+
} finally {
|
|
420
|
+
if (multipartLocks.get(normalized.id) === current) {
|
|
421
|
+
multipartLocks.delete(normalized.id);
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
async function acceptMultipartChunkInternal(sw, normalized, options) {
|
|
407
427
|
// State machine:
|
|
408
428
|
// 1. Validate the transport envelope and reject expired chunks before storage.
|
|
409
429
|
// 2. Drop already-completed multipart ids using the short-lived done marker.
|
|
410
430
|
// 3. Expire any stale pending record for this id before accepting a new one.
|
|
411
431
|
// 4. Store only new chunk indexes, track total received bytes, and wait.
|
|
412
432
|
// 5. Once all indexes are present, restore original JSON and mark done.
|
|
413
|
-
const normalized = normalizeMultipartChunk(payload, options);
|
|
414
|
-
if (!normalized) return null;
|
|
415
433
|
if (normalized.expiresAt <= Date.now()) {
|
|
416
434
|
await dispatchMultipartExpired(sw, {
|
|
417
435
|
id: normalized.id,
|
package/dist/index.d.ts
CHANGED
|
@@ -66,6 +66,7 @@ const DEFAULT_MULTIPART_OPTIONS = Object.freeze({
|
|
|
66
66
|
const memoryMultipartPending = new Map();
|
|
67
67
|
const memoryMultipartDone = new Map();
|
|
68
68
|
const memoryMultipartChunks = new Map();
|
|
69
|
+
const multipartLocks = new Map();
|
|
69
70
|
|
|
70
71
|
/**
|
|
71
72
|
* Wire-level message type for SW → client postMessage envelopes.
|
|
@@ -220,8 +221,8 @@ async function dispatchBusinessPayload(sw, payload, defaults) {
|
|
|
220
221
|
if (typeof defaults.onBusinessPayload === 'function') {
|
|
221
222
|
try {
|
|
222
223
|
const result = defaults.onBusinessPayload(payload);
|
|
223
|
-
if (result
|
|
224
|
-
work.push(result.catch(error => {
|
|
224
|
+
if (result && typeof result.then === 'function') {
|
|
225
|
+
work.push(Promise.resolve(result).catch(error => {
|
|
225
226
|
console.error('[rei-standard-amsg-sw] onBusinessPayload promise rejected:', error);
|
|
226
227
|
}));
|
|
227
228
|
}
|
|
@@ -345,7 +346,7 @@ function createNotificationFromPayload(payload, defaults) {
|
|
|
345
346
|
const title =
|
|
346
347
|
pushNotification.title ||
|
|
347
348
|
payload.title ||
|
|
348
|
-
payload.contactName ||
|
|
349
|
+
(payload.contactName && `来自 ${payload.contactName}`) ||
|
|
349
350
|
'New notification';
|
|
350
351
|
const body = pushNotification.body || payload.body || payload.message || '';
|
|
351
352
|
const data = pushNotification.data && typeof pushNotification.data === 'object'
|
|
@@ -404,14 +405,31 @@ function isMultipartPush(payload) {
|
|
|
404
405
|
}
|
|
405
406
|
|
|
406
407
|
async function acceptMultipartChunk(sw, payload, options) {
|
|
408
|
+
const normalized = normalizeMultipartChunk(payload, options);
|
|
409
|
+
if (!normalized) return null;
|
|
410
|
+
|
|
411
|
+
const previous = multipartLocks.get(normalized.id) || Promise.resolve();
|
|
412
|
+
const current = previous
|
|
413
|
+
.catch(() => undefined)
|
|
414
|
+
.then(() => acceptMultipartChunkInternal(sw, normalized, options));
|
|
415
|
+
|
|
416
|
+
multipartLocks.set(normalized.id, current);
|
|
417
|
+
try {
|
|
418
|
+
return await current;
|
|
419
|
+
} finally {
|
|
420
|
+
if (multipartLocks.get(normalized.id) === current) {
|
|
421
|
+
multipartLocks.delete(normalized.id);
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
async function acceptMultipartChunkInternal(sw, normalized, options) {
|
|
407
427
|
// State machine:
|
|
408
428
|
// 1. Validate the transport envelope and reject expired chunks before storage.
|
|
409
429
|
// 2. Drop already-completed multipart ids using the short-lived done marker.
|
|
410
430
|
// 3. Expire any stale pending record for this id before accepting a new one.
|
|
411
431
|
// 4. Store only new chunk indexes, track total received bytes, and wait.
|
|
412
432
|
// 5. Once all indexes are present, restore original JSON and mark done.
|
|
413
|
-
const normalized = normalizeMultipartChunk(payload, options);
|
|
414
|
-
if (!normalized) return null;
|
|
415
433
|
if (normalized.expiresAt <= Date.now()) {
|
|
416
434
|
await dispatchMultipartExpired(sw, {
|
|
417
435
|
id: normalized.id,
|
package/dist/index.mjs
CHANGED
|
@@ -20,6 +20,7 @@ var DEFAULT_MULTIPART_OPTIONS = Object.freeze({
|
|
|
20
20
|
var memoryMultipartPending = /* @__PURE__ */ new Map();
|
|
21
21
|
var memoryMultipartDone = /* @__PURE__ */ new Map();
|
|
22
22
|
var memoryMultipartChunks = /* @__PURE__ */ new Map();
|
|
23
|
+
var multipartLocks = /* @__PURE__ */ new Map();
|
|
23
24
|
var REI_AMSG_POSTMESSAGE_TYPE = "REI_AMSG_PUSH";
|
|
24
25
|
var REI_SW_EVENT = Object.freeze({
|
|
25
26
|
CONTENT_RECEIVED: "rei-amsg-content-received",
|
|
@@ -120,8 +121,8 @@ async function dispatchBusinessPayload(sw, payload, defaults) {
|
|
|
120
121
|
if (typeof defaults.onBusinessPayload === "function") {
|
|
121
122
|
try {
|
|
122
123
|
const result = defaults.onBusinessPayload(payload);
|
|
123
|
-
if (result
|
|
124
|
-
work.push(result.catch((error) => {
|
|
124
|
+
if (result && typeof result.then === "function") {
|
|
125
|
+
work.push(Promise.resolve(result).catch((error) => {
|
|
125
126
|
console.error("[rei-standard-amsg-sw] onBusinessPayload promise rejected:", error);
|
|
126
127
|
}));
|
|
127
128
|
}
|
|
@@ -196,7 +197,7 @@ function createNotificationFromPayload(payload, defaults) {
|
|
|
196
197
|
};
|
|
197
198
|
}
|
|
198
199
|
const pushNotification = payload.notification && typeof payload.notification === "object" ? payload.notification : {};
|
|
199
|
-
const title = pushNotification.title || payload.title || payload.contactName || "New notification";
|
|
200
|
+
const title = pushNotification.title || payload.title || payload.contactName && `\u6765\u81EA ${payload.contactName}` || "New notification";
|
|
200
201
|
const body = pushNotification.body || payload.body || payload.message || "";
|
|
201
202
|
const data = pushNotification.data && typeof pushNotification.data === "object" ? { ...pushNotification.data } : payload.data && typeof payload.data === "object" ? { ...payload.data } : {};
|
|
202
203
|
if (data.payload == null) data.payload = payload;
|
|
@@ -240,6 +241,18 @@ function isMultipartPush(payload) {
|
|
|
240
241
|
async function acceptMultipartChunk(sw, payload, options) {
|
|
241
242
|
const normalized = normalizeMultipartChunk(payload, options);
|
|
242
243
|
if (!normalized) return null;
|
|
244
|
+
const previous = multipartLocks.get(normalized.id) || Promise.resolve();
|
|
245
|
+
const current = previous.catch(() => void 0).then(() => acceptMultipartChunkInternal(sw, normalized, options));
|
|
246
|
+
multipartLocks.set(normalized.id, current);
|
|
247
|
+
try {
|
|
248
|
+
return await current;
|
|
249
|
+
} finally {
|
|
250
|
+
if (multipartLocks.get(normalized.id) === current) {
|
|
251
|
+
multipartLocks.delete(normalized.id);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
async function acceptMultipartChunkInternal(sw, normalized, options) {
|
|
243
256
|
if (normalized.expiresAt <= Date.now()) {
|
|
244
257
|
await dispatchMultipartExpired(sw, {
|
|
245
258
|
id: normalized.id,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rei-standard/amsg-sw",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.1",
|
|
4
4
|
"description": "ReiStandard Active Messaging service worker SDK — three-axis push schema (content / reasoning / tool_request / error) with per-kind client postMessage events",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"node": ">=20"
|
|
34
34
|
},
|
|
35
35
|
"dependencies": {
|
|
36
|
-
"@rei-standard/amsg-shared": "0.1.0
|
|
36
|
+
"@rei-standard/amsg-shared": "0.1.0"
|
|
37
37
|
},
|
|
38
38
|
"devDependencies": {
|
|
39
39
|
"tsup": "^8.0.0",
|