@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 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(next)
95
+ ### Generic multipart transport(2.1.0+)
96
96
 
97
- next 阶段移除了旧 reasoning 专用 `chunkIndex` / `totalChunks` wire format。现在 `_multipart` 是统一 transport kind,任何原始 payload 都可以被包起来:
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-next.3)离线持久化等业务拦截钩子:
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` 拼接逻辑;next 版本只会把完整还原后的 reasoning payload 发给 client。
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 instanceof Promise) {
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 instanceof Promise) {
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 instanceof Promise) {
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 instanceof Promise) {
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.0-next.4",
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-next.4"
36
+ "@rei-standard/amsg-shared": "0.1.0"
37
37
  },
38
38
  "devDependencies": {
39
39
  "tsup": "^8.0.0",