@ours.network/messenger-server 1.0.21 → 1.0.23

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.
Files changed (132) hide show
  1. package/README.md +21 -6
  2. package/dist/chunks/{chunk-7KB5SIFJ.js → chunk-G4IVQ6I4.js} +1 -1
  3. package/dist/chunks/{server-QLWLPQHE.js → server-TX7WLV64.js} +408 -161
  4. package/dist/cli.js +2 -2
  5. package/dist/web/assets/{abnfDiagram-N423BO3Z-B6ZizsiF.js → abnfDiagram-N423BO3Z-DluufZNF.js} +2 -2
  6. package/dist/web/assets/{abnfDiagram-N423BO3Z-B6ZizsiF.js.map → abnfDiagram-N423BO3Z-DluufZNF.js.map} +1 -1
  7. package/dist/web/assets/{arc-5r9LRWcz.js → arc-CQ3ltbkj.js} +2 -2
  8. package/dist/web/assets/{arc-5r9LRWcz.js.map → arc-CQ3ltbkj.js.map} +1 -1
  9. package/dist/web/assets/{architectureDiagram-T3A2C74G-Cj4KI9n0.js → architectureDiagram-T3A2C74G-D_OLaJJP.js} +2 -2
  10. package/dist/web/assets/{architectureDiagram-T3A2C74G-Cj4KI9n0.js.map → architectureDiagram-T3A2C74G-D_OLaJJP.js.map} +1 -1
  11. package/dist/web/assets/{blockDiagram-VBNYF7ZC-C-_bEmrE.js → blockDiagram-VBNYF7ZC-zWQJpXUI.js} +2 -2
  12. package/dist/web/assets/{blockDiagram-VBNYF7ZC-C-_bEmrE.js.map → blockDiagram-VBNYF7ZC-zWQJpXUI.js.map} +1 -1
  13. package/dist/web/assets/{browser-PrseyNpO.js → browser-BtETXeFB.js} +2 -2
  14. package/dist/web/assets/{browser-PrseyNpO.js.map → browser-BtETXeFB.js.map} +1 -1
  15. package/dist/web/assets/{c4Diagram-5PPSVZJV-DE8rEM7l.js → c4Diagram-5PPSVZJV-Ccpd3-UB.js} +2 -2
  16. package/dist/web/assets/{c4Diagram-5PPSVZJV-DE8rEM7l.js.map → c4Diagram-5PPSVZJV-Ccpd3-UB.js.map} +1 -1
  17. package/dist/web/assets/channel-p16PN_NW.js +2 -0
  18. package/dist/web/assets/{channel-CP3HqW0T.js.map → channel-p16PN_NW.js.map} +1 -1
  19. package/dist/web/assets/{chunk-2GRJ4B5K-DXDdnzqB.js → chunk-2GRJ4B5K-BCgfd6Td.js} +2 -2
  20. package/dist/web/assets/{chunk-2GRJ4B5K-DXDdnzqB.js.map → chunk-2GRJ4B5K-BCgfd6Td.js.map} +1 -1
  21. package/dist/web/assets/{chunk-2Q5K7J3B-B2scPBcd.js → chunk-2Q5K7J3B-CQ48PhVz.js} +2 -2
  22. package/dist/web/assets/{chunk-2Q5K7J3B-B2scPBcd.js.map → chunk-2Q5K7J3B-CQ48PhVz.js.map} +1 -1
  23. package/dist/web/assets/{chunk-5RXB4S5H-C8I-PBrV.js → chunk-5RXB4S5H-BTQ5wpp1.js} +2 -2
  24. package/dist/web/assets/{chunk-5RXB4S5H-C8I-PBrV.js.map → chunk-5RXB4S5H-BTQ5wpp1.js.map} +1 -1
  25. package/dist/web/assets/{chunk-5VM5RSS4-BXHXvnc8.js → chunk-5VM5RSS4-OmXNC4IB.js} +2 -2
  26. package/dist/web/assets/{chunk-5VM5RSS4-BXHXvnc8.js.map → chunk-5VM5RSS4-OmXNC4IB.js.map} +1 -1
  27. package/dist/web/assets/{chunk-6Q2QTUOP-08Ynset-.js → chunk-6Q2QTUOP-C8ooDwW4.js} +2 -2
  28. package/dist/web/assets/{chunk-6Q2QTUOP-08Ynset-.js.map → chunk-6Q2QTUOP-C8ooDwW4.js.map} +1 -1
  29. package/dist/web/assets/{chunk-GF5L2VYU-Bb7CYAyC.js → chunk-GF5L2VYU-BsX1H_fv.js} +2 -2
  30. package/dist/web/assets/{chunk-GF5L2VYU-Bb7CYAyC.js.map → chunk-GF5L2VYU-BsX1H_fv.js.map} +1 -1
  31. package/dist/web/assets/{chunk-JWPE2WC7-DZWvONVd.js → chunk-JWPE2WC7-Df86oX4z.js} +2 -2
  32. package/dist/web/assets/{chunk-JWPE2WC7-DZWvONVd.js.map → chunk-JWPE2WC7-Df86oX4z.js.map} +1 -1
  33. package/dist/web/assets/{chunk-KBJHAD2P-DLqf6yQb.js → chunk-KBJHAD2P-DHWP2W4_.js} +2 -2
  34. package/dist/web/assets/{chunk-KBJHAD2P-DLqf6yQb.js.map → chunk-KBJHAD2P-DHWP2W4_.js.map} +1 -1
  35. package/dist/web/assets/{chunk-RYQCIY6F-DTvnFCXh.js → chunk-RYQCIY6F-BxxxebdI.js} +2 -2
  36. package/dist/web/assets/{chunk-RYQCIY6F-DTvnFCXh.js.map → chunk-RYQCIY6F-BxxxebdI.js.map} +1 -1
  37. package/dist/web/assets/{chunk-XXDRQBXY-C_kaUCOs.js → chunk-XXDRQBXY-VkYGSsGl.js} +2 -2
  38. package/dist/web/assets/{chunk-XXDRQBXY-C_kaUCOs.js.map → chunk-XXDRQBXY-VkYGSsGl.js.map} +1 -1
  39. package/dist/web/assets/classDiagram-JCYQIIEL-DniFmDq7.js +2 -0
  40. package/dist/web/assets/{classDiagram-JCYQIIEL-DPmFBQ5b.js.map → classDiagram-JCYQIIEL-DniFmDq7.js.map} +1 -1
  41. package/dist/web/assets/classDiagram-v2-OCEON4UE-DniFmDq7.js +2 -0
  42. package/dist/web/assets/{classDiagram-v2-OCEON4UE-DPmFBQ5b.js.map → classDiagram-v2-OCEON4UE-DniFmDq7.js.map} +1 -1
  43. package/dist/web/assets/{cose-bilkent-JH36ORCC-GuA73T28.js → cose-bilkent-JH36ORCC-CwABkF0h.js} +2 -2
  44. package/dist/web/assets/{cose-bilkent-JH36ORCC-GuA73T28.js.map → cose-bilkent-JH36ORCC-CwABkF0h.js.map} +1 -1
  45. package/dist/web/assets/{cynefin-VYW2F7L2-ra9ViIw1.js → cynefin-VYW2F7L2-YRT0jK8r.js} +2 -2
  46. package/dist/web/assets/{cynefin-VYW2F7L2-ra9ViIw1.js.map → cynefin-VYW2F7L2-YRT0jK8r.js.map} +1 -1
  47. package/dist/web/assets/{cynefinDiagram-MW4NZA55-SATlKd94.js → cynefinDiagram-MW4NZA55-HpASrz5x.js} +2 -2
  48. package/dist/web/assets/{cynefinDiagram-MW4NZA55-SATlKd94.js.map → cynefinDiagram-MW4NZA55-HpASrz5x.js.map} +1 -1
  49. package/dist/web/assets/{dagre-VZM6K2ZE-VIOCx9FW.js → dagre-VZM6K2ZE-Bxqqu1Sj.js} +2 -2
  50. package/dist/web/assets/{dagre-VZM6K2ZE-VIOCx9FW.js.map → dagre-VZM6K2ZE-Bxqqu1Sj.js.map} +1 -1
  51. package/dist/web/assets/{diagram-7IWD3JNH-CC5X5bh9.js → diagram-7IWD3JNH-CdOULw_j.js} +2 -2
  52. package/dist/web/assets/{diagram-7IWD3JNH-CC5X5bh9.js.map → diagram-7IWD3JNH-CdOULw_j.js.map} +1 -1
  53. package/dist/web/assets/{diagram-B4RE2ZJO-DiG4yo_i.js → diagram-B4RE2ZJO-BmAH_WZj.js} +2 -2
  54. package/dist/web/assets/{diagram-B4RE2ZJO-DiG4yo_i.js.map → diagram-B4RE2ZJO-BmAH_WZj.js.map} +1 -1
  55. package/dist/web/assets/{diagram-LBJQPF4R-BlYoZBtY.js → diagram-LBJQPF4R-DhV2Gh-F.js} +2 -2
  56. package/dist/web/assets/{diagram-LBJQPF4R-BlYoZBtY.js.map → diagram-LBJQPF4R-DhV2Gh-F.js.map} +1 -1
  57. package/dist/web/assets/{diagram-Q27KOJAE-CRFPxIVY.js → diagram-Q27KOJAE-C84KsWMY.js} +2 -2
  58. package/dist/web/assets/{diagram-Q27KOJAE-CRFPxIVY.js.map → diagram-Q27KOJAE-C84KsWMY.js.map} +1 -1
  59. package/dist/web/assets/{diagram-UB23O5K3-CZhtWIy0.js → diagram-UB23O5K3-DdedAaau.js} +2 -2
  60. package/dist/web/assets/{diagram-UB23O5K3-CZhtWIy0.js.map → diagram-UB23O5K3-DdedAaau.js.map} +1 -1
  61. package/dist/web/assets/{ebnfDiagram-BXEA7PRR-pY730TDb.js → ebnfDiagram-BXEA7PRR-DfNXtSHl.js} +2 -2
  62. package/dist/web/assets/{ebnfDiagram-BXEA7PRR-pY730TDb.js.map → ebnfDiagram-BXEA7PRR-DfNXtSHl.js.map} +1 -1
  63. package/dist/web/assets/{erDiagram-JOGREHBK-Bn32rfJy.js → erDiagram-JOGREHBK-DYuWNOEG.js} +2 -2
  64. package/dist/web/assets/{erDiagram-JOGREHBK-Bn32rfJy.js.map → erDiagram-JOGREHBK-DYuWNOEG.js.map} +1 -1
  65. package/dist/web/assets/{flowDiagram-UKHOOZJN-BvRucSDE.js → flowDiagram-UKHOOZJN-0vb0_3vx.js} +2 -2
  66. package/dist/web/assets/{flowDiagram-UKHOOZJN-BvRucSDE.js.map → flowDiagram-UKHOOZJN-0vb0_3vx.js.map} +1 -1
  67. package/dist/web/assets/{ganttDiagram-PKOTCBZU-Nr1n-omI.js → ganttDiagram-PKOTCBZU-CK8tyfEC.js} +2 -2
  68. package/dist/web/assets/{ganttDiagram-PKOTCBZU-Nr1n-omI.js.map → ganttDiagram-PKOTCBZU-CK8tyfEC.js.map} +1 -1
  69. package/dist/web/assets/{gitGraphDiagram-DS77QQ5N-DynQOJUx.js → gitGraphDiagram-DS77QQ5N-DF6tegGR.js} +2 -2
  70. package/dist/web/assets/{gitGraphDiagram-DS77QQ5N-DynQOJUx.js.map → gitGraphDiagram-DS77QQ5N-DF6tegGR.js.map} +1 -1
  71. package/dist/web/assets/index-C13P33S8.css +1 -0
  72. package/dist/web/assets/{index-DcBXzDIU.js → index-CaFh5gEt.js} +14 -14
  73. package/dist/web/assets/index-CaFh5gEt.js.map +1 -0
  74. package/dist/web/assets/{infoDiagram-6WML65LV-BlEoz4uC.js → infoDiagram-6WML65LV-CGrFzjRa.js} +2 -2
  75. package/dist/web/assets/{infoDiagram-6WML65LV-BlEoz4uC.js.map → infoDiagram-6WML65LV-CGrFzjRa.js.map} +1 -1
  76. package/dist/web/assets/{ishikawaDiagram-WSZJBQD7-D7i55-jD.js → ishikawaDiagram-WSZJBQD7-CEo4pf_7.js} +2 -2
  77. package/dist/web/assets/{ishikawaDiagram-WSZJBQD7-D7i55-jD.js.map → ishikawaDiagram-WSZJBQD7-CEo4pf_7.js.map} +1 -1
  78. package/dist/web/assets/{journeyDiagram-NVQOT4AX-Ucz04wjf.js → journeyDiagram-NVQOT4AX-CspNWhHp.js} +2 -2
  79. package/dist/web/assets/{journeyDiagram-NVQOT4AX-Ucz04wjf.js.map → journeyDiagram-NVQOT4AX-CspNWhHp.js.map} +1 -1
  80. package/dist/web/assets/{jsQR-BwlCAqNO.js → jsQR-DcKyKt8x.js} +2 -2
  81. package/dist/web/assets/{jsQR-BwlCAqNO.js.map → jsQR-DcKyKt8x.js.map} +1 -1
  82. package/dist/web/assets/{kanban-definition-27J2QSJJ-DWsE9cHO.js → kanban-definition-27J2QSJJ-V17PR9V-.js} +2 -2
  83. package/dist/web/assets/{kanban-definition-27J2QSJJ-DWsE9cHO.js.map → kanban-definition-27J2QSJJ-V17PR9V-.js.map} +1 -1
  84. package/dist/web/assets/{linear-C5Sn5WRj.js → linear-DJQlR9NW.js} +2 -2
  85. package/dist/web/assets/{linear-C5Sn5WRj.js.map → linear-DJQlR9NW.js.map} +1 -1
  86. package/dist/web/assets/{mermaid.core-fsJdVgy7.js → mermaid.core-BJxvfP_g.js} +5 -5
  87. package/dist/web/assets/{mermaid.core-fsJdVgy7.js.map → mermaid.core-BJxvfP_g.js.map} +1 -1
  88. package/dist/web/assets/{mindmap-definition-FAOFIHXS-DaGgRc4d.js → mindmap-definition-FAOFIHXS-CRdQH63m.js} +2 -2
  89. package/dist/web/assets/{mindmap-definition-FAOFIHXS-DaGgRc4d.js.map → mindmap-definition-FAOFIHXS-CRdQH63m.js.map} +1 -1
  90. package/dist/web/assets/{pegDiagram-VL7TDLO6-D8yGmXPL.js → pegDiagram-VL7TDLO6-Ba0KPauI.js} +2 -2
  91. package/dist/web/assets/{pegDiagram-VL7TDLO6-D8yGmXPL.js.map → pegDiagram-VL7TDLO6-Ba0KPauI.js.map} +1 -1
  92. package/dist/web/assets/{pieDiagram-7S7Q4E2Y-DwH7tSjj.js → pieDiagram-7S7Q4E2Y-B923JQAs.js} +2 -2
  93. package/dist/web/assets/{pieDiagram-7S7Q4E2Y-DwH7tSjj.js.map → pieDiagram-7S7Q4E2Y-B923JQAs.js.map} +1 -1
  94. package/dist/web/assets/{quadrantDiagram-CIZ2JOQS-ChEMrl7C.js → quadrantDiagram-CIZ2JOQS-CRgKLy79.js} +2 -2
  95. package/dist/web/assets/{quadrantDiagram-CIZ2JOQS-ChEMrl7C.js.map → quadrantDiagram-CIZ2JOQS-CRgKLy79.js.map} +1 -1
  96. package/dist/web/assets/{railroadDiagram-AXF67PYL-BEovXgwU.js → railroadDiagram-AXF67PYL-ox905hPN.js} +2 -2
  97. package/dist/web/assets/{railroadDiagram-AXF67PYL-BEovXgwU.js.map → railroadDiagram-AXF67PYL-ox905hPN.js.map} +1 -1
  98. package/dist/web/assets/{requirementDiagram-LRYGKXZP-CmP1vUmt.js → requirementDiagram-LRYGKXZP-Duq__sqN.js} +2 -2
  99. package/dist/web/assets/{requirementDiagram-LRYGKXZP-CmP1vUmt.js.map → requirementDiagram-LRYGKXZP-Duq__sqN.js.map} +1 -1
  100. package/dist/web/assets/{sankeyDiagram-W5VNT64P-DxUz8yN6.js → sankeyDiagram-W5VNT64P-wHwQYnBB.js} +2 -2
  101. package/dist/web/assets/{sankeyDiagram-W5VNT64P-DxUz8yN6.js.map → sankeyDiagram-W5VNT64P-wHwQYnBB.js.map} +1 -1
  102. package/dist/web/assets/{sequenceDiagram-SI44F4Z6-d4jgy5oa.js → sequenceDiagram-SI44F4Z6-DxIJk57u.js} +2 -2
  103. package/dist/web/assets/{sequenceDiagram-SI44F4Z6-d4jgy5oa.js.map → sequenceDiagram-SI44F4Z6-DxIJk57u.js.map} +1 -1
  104. package/dist/web/assets/{sizeCapture-X5ZJPWSS-HXVh3XKR.js → sizeCapture-X5ZJPWSS-DTR7sbJ5.js} +2 -2
  105. package/dist/web/assets/{sizeCapture-X5ZJPWSS-HXVh3XKR.js.map → sizeCapture-X5ZJPWSS-DTR7sbJ5.js.map} +1 -1
  106. package/dist/web/assets/{stateDiagram-OKZ733FA-qUFT-p4T.js → stateDiagram-OKZ733FA-BVFtM2sL.js} +2 -2
  107. package/dist/web/assets/{stateDiagram-OKZ733FA-qUFT-p4T.js.map → stateDiagram-OKZ733FA-BVFtM2sL.js.map} +1 -1
  108. package/dist/web/assets/stateDiagram-v2-UEYNNEHI-cPQcuNzH.js +2 -0
  109. package/dist/web/assets/{stateDiagram-v2-UEYNNEHI-hxKmEhcU.js.map → stateDiagram-v2-UEYNNEHI-cPQcuNzH.js.map} +1 -1
  110. package/dist/web/assets/{swimlanes-SLNWSIFB-BqG_sSp3.js → swimlanes-SLNWSIFB-CD9G4YCZ.js} +3 -3
  111. package/dist/web/assets/{swimlanes-SLNWSIFB-BqG_sSp3.js.map → swimlanes-SLNWSIFB-CD9G4YCZ.js.map} +1 -1
  112. package/dist/web/assets/swimlanesDiagram-ULZ7WXOC-CyR-kDwU.js +9 -0
  113. package/dist/web/assets/{swimlanesDiagram-ULZ7WXOC-DXjdKK-f.js.map → swimlanesDiagram-ULZ7WXOC-CyR-kDwU.js.map} +1 -1
  114. package/dist/web/assets/{timeline-definition-Z64GVDOM-Cv2XcEyA.js → timeline-definition-Z64GVDOM-Dvedkrz5.js} +2 -2
  115. package/dist/web/assets/{timeline-definition-Z64GVDOM-Cv2XcEyA.js.map → timeline-definition-Z64GVDOM-Dvedkrz5.js.map} +1 -1
  116. package/dist/web/assets/{vennDiagram-T6HMQDX7-CfDqT27B.js → vennDiagram-T6HMQDX7-D5CnNWtW.js} +2 -2
  117. package/dist/web/assets/{vennDiagram-T6HMQDX7-CfDqT27B.js.map → vennDiagram-T6HMQDX7-D5CnNWtW.js.map} +1 -1
  118. package/dist/web/assets/{wardleyDiagram-T6FBY63Y-B1NVAn0o.js → wardleyDiagram-T6FBY63Y-n746vsfx.js} +2 -2
  119. package/dist/web/assets/{wardleyDiagram-T6FBY63Y-B1NVAn0o.js.map → wardleyDiagram-T6FBY63Y-n746vsfx.js.map} +1 -1
  120. package/dist/web/assets/{xychartDiagram-ELKLHX3M-B1-HStGS.js → xychartDiagram-ELKLHX3M-zEDnFO9e.js} +2 -2
  121. package/dist/web/assets/{xychartDiagram-ELKLHX3M-B1-HStGS.js.map → xychartDiagram-ELKLHX3M-zEDnFO9e.js.map} +1 -1
  122. package/dist/web/index.html +2 -2
  123. package/dist/web/sw.js +1 -1
  124. package/dist/web/version.json +1 -1
  125. package/package.json +2 -2
  126. package/dist/web/assets/channel-CP3HqW0T.js +0 -2
  127. package/dist/web/assets/classDiagram-JCYQIIEL-DPmFBQ5b.js +0 -2
  128. package/dist/web/assets/classDiagram-v2-OCEON4UE-DPmFBQ5b.js +0 -2
  129. package/dist/web/assets/index-DcBXzDIU.js.map +0 -1
  130. package/dist/web/assets/index-DcCHCB0s.css +0 -1
  131. package/dist/web/assets/stateDiagram-v2-UEYNNEHI-hxKmEhcU.js +0 -2
  132. package/dist/web/assets/swimlanesDiagram-ULZ7WXOC-DXjdKK-f.js +0 -9
@@ -7,7 +7,7 @@ import {
7
7
  publicEngineError,
8
8
  publicInternalError,
9
9
  reportFailure
10
- } from "./chunk-7KB5SIFJ.js";
10
+ } from "./chunk-G4IVQ6I4.js";
11
11
 
12
12
  // node_modules/bn.js/lib/bn.js
13
13
  var require_bn = __commonJS({
@@ -9676,7 +9676,7 @@ var require_websocket = __commonJS({
9676
9676
  var http = __require("http");
9677
9677
  var net = __require("net");
9678
9678
  var tls = __require("tls");
9679
- var { randomBytes: randomBytes2, createHash: createHash4 } = __require("crypto");
9679
+ var { randomBytes: randomBytes2, createHash: createHash5 } = __require("crypto");
9680
9680
  var { Duplex, Readable } = __require("stream");
9681
9681
  var { URL: URL2 } = __require("url");
9682
9682
  var PerMessageDeflate2 = require_permessage_deflate();
@@ -10344,7 +10344,7 @@ var require_websocket = __commonJS({
10344
10344
  abortHandshake(websocket, socket, "Invalid Upgrade header");
10345
10345
  return;
10346
10346
  }
10347
- const digest = createHash4("sha1").update(key + GUID).digest("base64");
10347
+ const digest = createHash5("sha1").update(key + GUID).digest("base64");
10348
10348
  if (res.headers["sec-websocket-accept"] !== digest) {
10349
10349
  abortHandshake(websocket, socket, "Invalid Sec-WebSocket-Accept header");
10350
10350
  return;
@@ -10716,7 +10716,7 @@ var require_websocket_server = __commonJS({
10716
10716
  var EventEmitter = __require("events");
10717
10717
  var http = __require("http");
10718
10718
  var { Duplex } = __require("stream");
10719
- var { createHash: createHash4 } = __require("crypto");
10719
+ var { createHash: createHash5 } = __require("crypto");
10720
10720
  var extension2 = require_extension();
10721
10721
  var PerMessageDeflate2 = require_permessage_deflate();
10722
10722
  var subprotocol2 = require_subprotocol();
@@ -11023,7 +11023,7 @@ var require_websocket_server = __commonJS({
11023
11023
  );
11024
11024
  }
11025
11025
  if (this._state > RUNNING) return abortHandshake(socket, 503);
11026
- const digest = createHash4("sha1").update(key + GUID).digest("base64");
11026
+ const digest = createHash5("sha1").update(key + GUID).digest("base64");
11027
11027
  const headers = [
11028
11028
  "HTTP/1.1 101 Switching Protocols",
11029
11029
  "Upgrade: websocket",
@@ -20621,11 +20621,24 @@ var ROUTES = {
20621
20621
  const current = await within(client.currentIdentity(), deps.healthTimeoutMs ?? 1500);
20622
20622
  const identity = publicIdentity(current);
20623
20623
  if (identity.cid !== deps.identityCid) throw new Error("bound identity mismatch");
20624
+ const pushQueue = deps.push.queueStats();
20625
+ if (pushQueue.healthy === false) {
20626
+ sendJson(res, 503, {
20627
+ status: "degraded",
20628
+ message: "Push queue saturated",
20629
+ version: deps.buildInfo.version,
20630
+ sha: deps.buildInfo.sha,
20631
+ identityCid: deps.identityCid,
20632
+ pushQueue
20633
+ });
20634
+ return void 0;
20635
+ }
20624
20636
  sendJson(res, 200, {
20625
20637
  status: "ok",
20626
20638
  version: deps.buildInfo.version,
20627
20639
  sha: deps.buildInfo.sha,
20628
- identityCid: deps.identityCid
20640
+ identityCid: deps.identityCid,
20641
+ pushQueue
20629
20642
  });
20630
20643
  } catch {
20631
20644
  sendJson(res, 503, unavailable);
@@ -21195,6 +21208,34 @@ var require9 = createRequire8(import.meta.url);
21195
21208
  var require10 = createRequire9(import.meta.url);
21196
21209
 
21197
21210
  // src/daemon.ts
21211
+ function daemonNotificationReader(leaseToken) {
21212
+ let selected;
21213
+ return async (identity, since, signal) => {
21214
+ selected ??= _2();
21215
+ const url = `${selected.baseUrl.value}/identities/${encodeURIComponent(identity)}/notifications?since=${encodeURIComponent(String(since))}&kinds=inbound`;
21216
+ const response = await fetch(url, {
21217
+ headers: {
21218
+ "x-ours-lease-token": leaseToken,
21219
+ "x-ours-client-pid": String(process.pid),
21220
+ ...selected.token ? { "x-ours-api-token": selected.token.value } : {}
21221
+ },
21222
+ signal
21223
+ });
21224
+ let body;
21225
+ try {
21226
+ body = await response.json();
21227
+ } catch {
21228
+ throw new Error(`daemon notification page returned HTTP ${response.status} with invalid JSON`);
21229
+ }
21230
+ if (!response.ok) throw new Error(`daemon notification page returned HTTP ${response.status}`);
21231
+ if (!body || typeof body !== "object" || Array.isArray(body)) throw new Error("daemon notification page is malformed");
21232
+ const page = body;
21233
+ if (!Number.isSafeInteger(page.cursor) || page.cursor < 0 || !Array.isArray(page.events)) {
21234
+ throw new Error("daemon notification page is malformed");
21235
+ }
21236
+ return { cursor: page.cursor, events: page.events };
21237
+ };
21238
+ }
21198
21239
  function hasCode(error, code) {
21199
21240
  return error instanceof Error && "code" in error && error.code === code;
21200
21241
  }
@@ -21219,6 +21260,7 @@ async function startRuntime(_cfg, buildInfo, attach = j) {
21219
21260
  apiVisibility: "daemon-configured",
21220
21261
  mcp: false
21221
21262
  }),
21263
+ readNotificationPage: daemonNotificationReader(leaseToken),
21222
21264
  async close() {
21223
21265
  if (closed) return;
21224
21266
  closed = true;
@@ -21247,10 +21289,23 @@ import { dirname as dirname2, join as join2 } from "node:path";
21247
21289
  var MAX_BINDINGS = 32;
21248
21290
  var MAX_ENDPOINT_LENGTH = 2048;
21249
21291
  var MAX_LABEL_LENGTH = 160;
21250
- var MAX_JOBS_PER_IDENTITY = 4096;
21292
+ var MAX_RECORDS_PER_IDENTITY = 4096;
21293
+ var MAX_TOMBSTONES_PER_IDENTITY = 256;
21251
21294
  var JOB_EXPIRY_MS = 24 * 60 * 60 * 1e3;
21252
- var TERMINAL_RETENTION_MS = 7 * 24 * 60 * 60 * 1e3;
21295
+ var TOMBSTONE_RETENTION_MS = 15 * 60 * 1e3;
21253
21296
  var MAX_ATTEMPTS = 6;
21297
+ var emptyMetrics = () => ({
21298
+ admitted: 0,
21299
+ delivered: 0,
21300
+ suppressed: 0,
21301
+ retries: 0,
21302
+ dropped: 0,
21303
+ permanentFailures: 0,
21304
+ prunedBindings: 0,
21305
+ admissionFailures: 0,
21306
+ tombstonesPruned: 0,
21307
+ migratedTerminal: 0
21308
+ });
21254
21309
  function nonEmpty2(value, max = Number.MAX_SAFE_INTEGER) {
21255
21310
  return typeof value === "string" && value.length > 0 && value.length <= max;
21256
21311
  }
@@ -21298,10 +21353,44 @@ function validBinding(value) {
21298
21353
  }
21299
21354
  return nonEmpty2(row.bindingId, 128) && nonEmpty2(row.createdAt, 64) && nonEmpty2(row.updatedAt, 64) && nonEmpty2(row.vapidFingerprint, 128) && Number.isSafeInteger(row.configEpoch) && row.configEpoch > 0 && (row.label === void 0 || nonEmpty2(row.label, MAX_LABEL_LENGTH));
21300
21355
  }
21356
+ function validJobBase(row) {
21357
+ return nonEmpty2(row.id, 2048) && nonEmpty2(row.identityCid, 512) && nonEmpty2(row.wireId, 1024) && (row.kind === "message" || row.kind === "file" || row.kind === "photo" || row.kind === "voice") && nonEmpty2(row.contactId, 512) && (row.senderName === void 0 || nonEmpty2(row.senderName, 512)) && Number.isFinite(row.createdAt) && Number.isFinite(row.expiresAt) && Number.isSafeInteger(row.attempts) && row.attempts >= 0 && Number.isFinite(row.nextAttemptAt) && Array.isArray(row.targetBindingIds) && row.targetBindingIds.length <= MAX_BINDINGS && row.targetBindingIds.every((id) => nonEmpty2(id, 128)) && Array.isArray(row.deliveredBindingIds) && row.deliveredBindingIds.length <= MAX_BINDINGS && row.deliveredBindingIds.every((id) => nonEmpty2(id, 128)) && Number.isSafeInteger(row.sentCount) && row.sentCount >= 0;
21358
+ }
21301
21359
  function validJob(value) {
21302
21360
  if (!value || typeof value !== "object" || Array.isArray(value)) return false;
21303
21361
  const row = value;
21304
- return nonEmpty2(row.id, 2048) && nonEmpty2(row.identityCid, 512) && nonEmpty2(row.wireId, 1024) && (row.kind === "message" || row.kind === "file" || row.kind === "photo" || row.kind === "voice") && nonEmpty2(row.contactId, 512) && (row.senderName === void 0 || nonEmpty2(row.senderName, 512)) && Number.isFinite(row.createdAt) && Number.isFinite(row.expiresAt) && Number.isSafeInteger(row.attempts) && row.attempts >= 0 && Number.isFinite(row.nextAttemptAt) && Array.isArray(row.targetBindingIds) && row.targetBindingIds.length <= MAX_BINDINGS && row.targetBindingIds.every((id) => nonEmpty2(id, 128)) && Array.isArray(row.deliveredBindingIds) && row.deliveredBindingIds.length <= MAX_BINDINGS && row.deliveredBindingIds.every((id) => nonEmpty2(id, 128)) && (row.status === "pending" || row.status === "sent" || row.status === "dropped") && Number.isSafeInteger(row.sentCount) && row.sentCount >= 0 && (row.completedAt === void 0 || Number.isFinite(row.completedAt));
21362
+ return validJobBase(row) && (row.status === "pending" || row.status === "in_flight") && (row.inFlightAt === void 0 || Number.isFinite(row.inFlightAt));
21363
+ }
21364
+ function validLegacyJob(value) {
21365
+ if (!value || typeof value !== "object" || Array.isArray(value)) return false;
21366
+ const row = value;
21367
+ return validJobBase(row) && (row.status === "pending" || row.status === "sent" || row.status === "dropped") && (row.completedAt === void 0 || Number.isFinite(row.completedAt));
21368
+ }
21369
+ function validTombstone(value) {
21370
+ if (!value || typeof value !== "object" || Array.isArray(value)) return false;
21371
+ const row = value;
21372
+ return nonEmpty2(row.id, 2048) && Number.isFinite(row.completedAt) && Number.isFinite(row.expiresAt);
21373
+ }
21374
+ function validMetrics(value) {
21375
+ if (!value || typeof value !== "object" || Array.isArray(value)) return false;
21376
+ const row = value;
21377
+ return [
21378
+ "admitted",
21379
+ "delivered",
21380
+ "suppressed",
21381
+ "retries",
21382
+ "dropped",
21383
+ "permanentFailures",
21384
+ "prunedBindings",
21385
+ "admissionFailures",
21386
+ "tombstonesPruned",
21387
+ "migratedTerminal"
21388
+ ].every((key) => Number.isSafeInteger(row[key]) && Number(row[key]) >= 0) && (row.lastSaturationAt === void 0 || Number.isFinite(row.lastSaturationAt));
21389
+ }
21390
+ function validPageCheckpoint(value) {
21391
+ if (!value || typeof value !== "object" || Array.isArray(value)) return false;
21392
+ const row = value;
21393
+ return Number.isSafeInteger(row.fromCursor) && row.fromCursor >= 0 && Number.isSafeInteger(row.cursor) && row.cursor >= row.fromCursor && Number.isSafeInteger(row.eventCount) && row.eventCount >= 0 && Number.isSafeInteger(row.nextIndex) && row.nextIndex >= 0 && row.nextIndex <= row.eventCount && nonEmpty2(row.digest, 128) && /^[A-Za-z0-9_-]+$/.test(row.digest);
21305
21394
  }
21306
21395
  function validVapid(value) {
21307
21396
  if (!value || typeof value !== "object" || Array.isArray(value)) return false;
@@ -21316,11 +21405,17 @@ function validVapid(value) {
21316
21405
  return false;
21317
21406
  }
21318
21407
  }
21408
+ function validV3(value) {
21409
+ if (!value || typeof value !== "object" || Array.isArray(value)) return false;
21410
+ const state = value;
21411
+ if (state.version !== 3 || !validVapid(state.vapid) || !nonEmpty2(state.vapidFingerprint, 128) || !Number.isSafeInteger(state.configEpoch) || state.configEpoch < 1 || !state.identities || typeof state.identities !== "object" || Array.isArray(state.identities)) return false;
21412
+ return Object.entries(state.identities).every(([cid, identity]) => nonEmpty2(cid, 512) && !!identity && typeof identity === "object" && Array.isArray(identity.bindings) && identity.bindings.length <= MAX_BINDINGS && identity.bindings.every(validBinding) && Array.isArray(identity.jobs) && Array.isArray(identity.tombstones) && identity.jobs.length + identity.tombstones.length <= MAX_RECORDS_PER_IDENTITY && identity.tombstones.length <= MAX_TOMBSTONES_PER_IDENTITY && identity.jobs.every((job) => validJob(job) && job.identityCid === cid) && identity.tombstones.every(validTombstone) && (identity.notificationCursor === null || Number.isSafeInteger(identity.notificationCursor) && identity.notificationCursor >= 0) && (identity.notificationPage === void 0 || validPageCheckpoint(identity.notificationPage) && identity.notificationCursor !== null && identity.notificationPage.fromCursor === identity.notificationCursor) && validMetrics(identity.metrics) && typeof identity.saturated === "boolean");
21413
+ }
21319
21414
  function validV2(value) {
21320
21415
  if (!value || typeof value !== "object" || Array.isArray(value)) return false;
21321
21416
  const state = value;
21322
21417
  if (state.version !== 2 || !validVapid(state.vapid) || !nonEmpty2(state.vapidFingerprint, 128) || !Number.isSafeInteger(state.configEpoch) || state.configEpoch < 1 || !state.identities || typeof state.identities !== "object" || Array.isArray(state.identities)) return false;
21323
- return Object.entries(state.identities).every(([cid, identity]) => nonEmpty2(cid, 512) && !!identity && typeof identity === "object" && Array.isArray(identity.bindings) && identity.bindings.length <= MAX_BINDINGS && identity.bindings.every(validBinding) && Array.isArray(identity.jobs) && identity.jobs.length <= MAX_JOBS_PER_IDENTITY && identity.jobs.every((job) => validJob(job) && job.identityCid === cid));
21418
+ return Object.entries(state.identities).every(([cid, identity]) => nonEmpty2(cid, 512) && !!identity && typeof identity === "object" && Array.isArray(identity.bindings) && identity.bindings.length <= MAX_BINDINGS && identity.bindings.every(validBinding) && Array.isArray(identity.jobs) && identity.jobs.length <= MAX_RECORDS_PER_IDENTITY && identity.jobs.every((job) => validLegacyJob(job) && job.identityCid === cid));
21324
21419
  }
21325
21420
  function validLegacy(value) {
21326
21421
  if (!value || typeof value !== "object" || Array.isArray(value)) return false;
@@ -21341,13 +21436,22 @@ function preservedStateError(file, reason) {
21341
21436
  `Existing push state ${file} is ${reason}; it was left unchanged. Restore valid push.json contents, or move the file aside explicitly before restarting.`
21342
21437
  );
21343
21438
  }
21439
+ function newIdentityState() {
21440
+ return { bindings: [], jobs: [], tombstones: [], notificationCursor: null, metrics: emptyMetrics(), saturated: false };
21441
+ }
21344
21442
  function identityState(state, cid) {
21345
- return state.identities[cid] ??= { bindings: [], jobs: [] };
21443
+ return state.identities[cid] ??= newIdentityState();
21346
21444
  }
21347
21445
  function privateEvent(event) {
21348
21446
  const body = event.kind === "message" ? "New message" : event.kind === "voice" ? "New voice message" : event.kind === "photo" ? "New photo" : "New file";
21349
21447
  return { ...event, title: "ours messenger", body };
21350
21448
  }
21449
+ function invalidDeviceToken(error) {
21450
+ if (!error || typeof error !== "object") return false;
21451
+ const raw = error.reason ?? error.body;
21452
+ const body = typeof raw === "string" ? raw.slice(0, 2048) : Buffer.isBuffer(raw) ? raw.subarray(0, 2048).toString("utf8") : "";
21453
+ return /\b(?:BadDeviceToken|DeviceTokenNotForTopic|Unregistered)\b/.test(body);
21454
+ }
21351
21455
  var LEGACY_LOCAL_VAPID_SUBJECT = "mailto:admin@localhost";
21352
21456
  var DEFAULT_VAPID_SUBJECT = "https://ours.network";
21353
21457
  function defaultVapidSubject(env) {
@@ -21389,7 +21493,7 @@ var PushStore = class _PushStore {
21389
21493
  } catch (error) {
21390
21494
  throw preservedStateError(file, `corrupt or unreadable (${error instanceof Error ? error.message : String(error)})`);
21391
21495
  }
21392
- if (validV2(value) || validLegacy(value)) parsed = value;
21496
+ if (validV3(value) || validV2(value) || validLegacy(value)) parsed = value;
21393
21497
  else throw preservedStateError(file, "schema-invalid");
21394
21498
  } catch (error) {
21395
21499
  if (error.code !== "ENOENT") throw error;
@@ -21407,7 +21511,7 @@ var PushStore = class _PushStore {
21407
21511
  }
21408
21512
  const selectedFingerprint = fingerprint(selected.publicKey);
21409
21513
  let state;
21410
- if (parsed && validV2(parsed)) {
21514
+ if (parsed && validV3(parsed)) {
21411
21515
  const rotated = parsed.vapidFingerprint !== selectedFingerprint;
21412
21516
  state = {
21413
21517
  ...parsed,
@@ -21415,16 +21519,53 @@ var PushStore = class _PushStore {
21415
21519
  vapidFingerprint: selectedFingerprint,
21416
21520
  configEpoch: rotated ? parsed.configEpoch + 1 : parsed.configEpoch
21417
21521
  };
21522
+ } else if (parsed && validV2(parsed)) {
21523
+ const rotated = parsed.vapidFingerprint !== selectedFingerprint;
21524
+ const now = Date.now();
21525
+ state = {
21526
+ version: 3,
21527
+ vapid: selected,
21528
+ vapidFingerprint: selectedFingerprint,
21529
+ configEpoch: rotated ? parsed.configEpoch + 1 : parsed.configEpoch,
21530
+ identities: Object.fromEntries(Object.entries(parsed.identities).map(([cid, old]) => {
21531
+ const pending = old.jobs.filter((row) => row.status === "pending").map((row) => ({
21532
+ id: row.id,
21533
+ identityCid: row.identityCid,
21534
+ wireId: row.wireId,
21535
+ kind: row.kind,
21536
+ contactId: row.contactId,
21537
+ senderName: row.senderName,
21538
+ createdAt: row.createdAt,
21539
+ expiresAt: row.expiresAt,
21540
+ attempts: row.attempts,
21541
+ nextAttemptAt: row.nextAttemptAt,
21542
+ targetBindingIds: [...row.targetBindingIds],
21543
+ deliveredBindingIds: [...row.deliveredBindingIds],
21544
+ status: "pending",
21545
+ sentCount: row.sentCount
21546
+ }));
21547
+ const terminal = old.jobs.filter((row) => row.status !== "pending");
21548
+ const tombstones = terminal.filter((row) => row.completedAt !== void 0 && now - row.completedAt <= TOMBSTONE_RETENTION_MS).sort((a4, b3) => (b3.completedAt ?? 0) - (a4.completedAt ?? 0)).slice(0, MAX_TOMBSTONES_PER_IDENTITY).map((row) => ({ id: row.id, completedAt: row.completedAt, expiresAt: row.completedAt + TOMBSTONE_RETENTION_MS }));
21549
+ const metrics = emptyMetrics();
21550
+ metrics.admitted = old.jobs.length;
21551
+ metrics.delivered = terminal.reduce((sum, row) => sum + (row.status === "sent" ? row.sentCount : 0), 0);
21552
+ metrics.dropped = terminal.filter((row) => row.status === "dropped").length;
21553
+ metrics.migratedTerminal = terminal.length;
21554
+ metrics.tombstonesPruned = terminal.length - tombstones.length;
21555
+ return [cid, { bindings: old.bindings, jobs: pending, tombstones, notificationCursor: null, metrics, saturated: false }];
21556
+ }))
21557
+ };
21418
21558
  } else {
21419
21559
  const oldFingerprint = parsed ? fingerprint(parsed.vapid.publicKey) : selectedFingerprint;
21420
21560
  const now = (/* @__PURE__ */ new Date()).toISOString();
21421
21561
  state = {
21422
- version: 2,
21562
+ version: 3,
21423
21563
  vapid: selected,
21424
21564
  vapidFingerprint: selectedFingerprint,
21425
21565
  configEpoch: 1,
21426
21566
  identities: {
21427
21567
  [identityCid]: {
21568
+ ...newIdentityState(),
21428
21569
  bindings: (parsed?.subscriptions ?? []).map((row) => ({
21429
21570
  bindingId: randomUUID(),
21430
21571
  endpoint: row.endpoint,
@@ -21435,14 +21576,13 @@ var PushStore = class _PushStore {
21435
21576
  vapidFingerprint: oldFingerprint,
21436
21577
  configEpoch: 1,
21437
21578
  preview: "full"
21438
- })),
21439
- jobs: []
21579
+ }))
21440
21580
  }
21441
21581
  }
21442
21582
  };
21443
21583
  }
21444
21584
  const store = new _PushStore(file, identityCid, state, deps);
21445
- store.pruneTerminal(Date.now());
21585
+ store.recoverAndPrune(Date.now());
21446
21586
  store.persist();
21447
21587
  return store;
21448
21588
  }
@@ -21534,17 +21674,90 @@ var PushStore = class _PushStore {
21534
21674
  if (removed) this.persist();
21535
21675
  return removed;
21536
21676
  }
21537
- enqueueJob(input, now = Date.now()) {
21677
+ get notificationCursor() {
21678
+ return identityState(this.state, this.identityCid).notificationCursor;
21679
+ }
21680
+ commitNotificationCursor(cursor) {
21681
+ if (!Number.isSafeInteger(cursor) || cursor < 0) throw new Error("push notification cursor must be a non-negative safe integer");
21682
+ const owner = identityState(this.state, this.identityCid);
21683
+ if (owner.notificationCursor === cursor && owner.notificationPage === void 0) return;
21684
+ owner.notificationCursor = cursor;
21685
+ delete owner.notificationPage;
21686
+ this.persist();
21687
+ }
21688
+ get notificationPageProgress() {
21689
+ const page = identityState(this.state, this.identityCid).notificationPage;
21690
+ return page ? { ...page } : null;
21691
+ }
21692
+ beginNotificationPage(input) {
21693
+ if (!validPageCheckpoint({ ...input, nextIndex: 0 })) throw new Error("push notification page checkpoint is invalid");
21694
+ const owner = identityState(this.state, this.identityCid);
21695
+ if (owner.notificationCursor !== input.fromCursor) {
21696
+ throw new Error("push notification page does not start at the committed cursor");
21697
+ }
21698
+ if (owner.notificationPage) return { ...owner.notificationPage };
21699
+ owner.notificationPage = { ...input, nextIndex: 0 };
21700
+ this.persist();
21701
+ return { ...owner.notificationPage };
21702
+ }
21703
+ advanceNotificationPage(nextIndex, durable = false) {
21704
+ const owner = identityState(this.state, this.identityCid);
21705
+ const page = owner.notificationPage;
21706
+ if (!page || !Number.isSafeInteger(nextIndex) || nextIndex !== page.nextIndex + 1 || nextIndex > page.eventCount) {
21707
+ throw new Error("push notification page progress is invalid");
21708
+ }
21709
+ page.nextIndex = nextIndex;
21710
+ if (durable) this.persist();
21711
+ }
21712
+ checkpointNotificationPage() {
21713
+ if (!identityState(this.state, this.identityCid).notificationPage) {
21714
+ throw new Error("push notification page checkpoint is missing");
21715
+ }
21716
+ this.persist();
21717
+ }
21718
+ commitNotificationPage() {
21719
+ const owner = identityState(this.state, this.identityCid);
21720
+ const page = owner.notificationPage;
21721
+ if (!page || page.nextIndex !== page.eventCount) throw new Error("push notification page is incomplete");
21722
+ owner.notificationCursor = page.cursor;
21723
+ delete owner.notificationPage;
21724
+ owner.saturated = false;
21725
+ this.persist();
21726
+ }
21727
+ admitJob(input, now = Date.now(), notificationPageNextIndex) {
21538
21728
  if (!nonEmpty2(input.wireId, 1024) || !nonEmpty2(input.contactId, 512) || input.senderName !== void 0 && !nonEmpty2(input.senderName, 512)) {
21539
21729
  throw new Error("push job correlation metadata is invalid or too long");
21540
21730
  }
21541
21731
  const targets = this.list().map((row) => row.bindingId);
21542
- if (targets.length === 0) return false;
21732
+ if (targets.length === 0) return { status: "no_targets" };
21543
21733
  const id = `${this.identityCid}:${input.wireId}:${input.kind}`;
21544
21734
  const owner = identityState(this.state, this.identityCid);
21545
- this.pruneTerminal(now);
21546
- if (owner.jobs.some((job) => job.id === id)) return false;
21547
- if (owner.jobs.length >= MAX_JOBS_PER_IDENTITY) return false;
21735
+ const checkpointAdmission = () => {
21736
+ if (notificationPageNextIndex === void 0) return false;
21737
+ const page = owner.notificationPage;
21738
+ if (!page || notificationPageNextIndex !== page.nextIndex + 1 || notificationPageNextIndex > page.eventCount) {
21739
+ throw new Error("push notification page admission checkpoint is invalid");
21740
+ }
21741
+ page.nextIndex = notificationPageNextIndex;
21742
+ return true;
21743
+ };
21744
+ this.pruneTombstones(owner, now);
21745
+ if (owner.jobs.some((job) => job.id === id) || owner.tombstones.some((row) => row.id === id)) {
21746
+ const checkpointed2 = checkpointAdmission();
21747
+ if (checkpointed2) this.persist();
21748
+ return { status: "duplicate", jobId: id, ...checkpointed2 ? { checkpointed: true } : {} };
21749
+ }
21750
+ while (owner.jobs.length + owner.tombstones.length >= MAX_RECORDS_PER_IDENTITY && owner.tombstones.length > 0) {
21751
+ owner.tombstones.shift();
21752
+ owner.metrics.tombstonesPruned++;
21753
+ }
21754
+ if (owner.jobs.length + owner.tombstones.length >= MAX_RECORDS_PER_IDENTITY) {
21755
+ owner.saturated = true;
21756
+ owner.metrics.admissionFailures++;
21757
+ owner.metrics.lastSaturationAt = now;
21758
+ this.persist();
21759
+ return { status: "saturated", jobId: id };
21760
+ }
21548
21761
  owner.jobs.push({
21549
21762
  id,
21550
21763
  identityCid: this.identityCid,
@@ -21561,41 +21774,55 @@ var PushStore = class _PushStore {
21561
21774
  status: "pending",
21562
21775
  sentCount: 0
21563
21776
  });
21777
+ owner.metrics.admitted++;
21778
+ if (owner.notificationPage === void 0) owner.saturated = false;
21779
+ const checkpointed = checkpointAdmission();
21564
21780
  this.persist();
21565
- return true;
21781
+ return { status: "queued", jobId: id, ...checkpointed ? { checkpointed: true } : {} };
21782
+ }
21783
+ enqueueJob(input, now = Date.now()) {
21784
+ return this.admitJob(input, now).status === "queued";
21566
21785
  }
21567
21786
  dueJobs(now = Date.now()) {
21568
21787
  return identityState(this.state, this.identityCid).jobs.filter((job) => job.status === "pending" && job.nextAttemptAt <= now).map((job) => ({ ...job, targetBindingIds: [...job.targetBindingIds], deliveredBindingIds: [...job.deliveredBindingIds] }));
21569
21788
  }
21789
+ claimJob(jobId, now = Date.now()) {
21790
+ const job = identityState(this.state, this.identityCid).jobs.find((row) => row.id === jobId);
21791
+ if (!job || job.status !== "pending" || job.nextAttemptAt > now) return false;
21792
+ Object.assign(job, { status: "in_flight", inFlightAt: now });
21793
+ this.persist();
21794
+ return true;
21795
+ }
21570
21796
  nextJobDelay(now = Date.now()) {
21571
21797
  const pending = identityState(this.state, this.identityCid).jobs.filter((job) => job.status === "pending");
21572
21798
  if (pending.length === 0) return null;
21573
21799
  return Math.max(0, Math.min(...pending.map((job) => job.nextAttemptAt)) - now);
21574
21800
  }
21575
21801
  retryJob(jobId, now = Date.now(), random = Math.random) {
21576
- const job = identityState(this.state, this.identityCid).jobs.find((row) => row.id === jobId);
21577
- if (!job || job.status !== "pending") return false;
21802
+ const owner = identityState(this.state, this.identityCid);
21803
+ const job = owner.jobs.find((row) => row.id === jobId);
21804
+ if (!job || job.status !== "pending" && job.status !== "in_flight") return false;
21578
21805
  const attempts = job.attempts + 1;
21579
21806
  if (attempts >= MAX_ATTEMPTS || now >= job.expiresAt) {
21580
- Object.assign(job, { attempts, status: "dropped", completedAt: now });
21807
+ owner.metrics.dropped++;
21808
+ this.finishJob(owner, job, now);
21581
21809
  this.persist();
21582
21810
  return false;
21583
21811
  }
21584
21812
  const base = Math.min(6e4, 1e3 * 2 ** (attempts - 1));
21585
21813
  const jitter = Math.floor(base * 0.25 * Math.max(0, Math.min(1, random())));
21586
- Object.assign(job, { attempts, nextAttemptAt: Math.min(job.expiresAt, now + base + jitter) });
21814
+ Object.assign(job, { attempts, nextAttemptAt: Math.min(job.expiresAt, now + base + jitter), status: "pending" });
21815
+ delete job.inFlightAt;
21816
+ owner.metrics.retries++;
21587
21817
  this.persist();
21588
21818
  return true;
21589
21819
  }
21590
21820
  suppressJob(jobId, now = Date.now()) {
21591
- const job = identityState(this.state, this.identityCid).jobs.find((row) => row.id === jobId);
21821
+ const owner = identityState(this.state, this.identityCid);
21822
+ const job = owner.jobs.find((row) => row.id === jobId);
21592
21823
  if (!job || job.status !== "pending") return false;
21593
- Object.assign(job, {
21594
- status: "sent",
21595
- completedAt: now,
21596
- deliveredBindingIds: [...job.targetBindingIds],
21597
- sentCount: 0
21598
- });
21824
+ owner.metrics.suppressed++;
21825
+ this.finishJob(owner, job, now);
21599
21826
  this.persist();
21600
21827
  return true;
21601
21828
  }
@@ -21614,14 +21841,17 @@ var PushStore = class _PushStore {
21614
21841
  if (suppressed === 0) return 0;
21615
21842
  Object.assign(job, { deliveredBindingIds: [...completed] });
21616
21843
  const outstanding = job.targetBindingIds.some((id) => !completed.has(id) && owner.bindings.some((binding) => binding.bindingId === id));
21617
- if (!outstanding) Object.assign(job, { status: "sent", completedAt: now });
21844
+ if (!outstanding) {
21845
+ owner.metrics.suppressed++;
21846
+ this.finishJob(owner, job, now);
21847
+ }
21618
21848
  this.persist();
21619
21849
  return suppressed;
21620
21850
  }
21621
21851
  async dispatchJob(jobId, event, now = Date.now(), random = Math.random) {
21622
21852
  const owner = identityState(this.state, this.identityCid);
21623
21853
  const job = owner.jobs.find((row) => row.id === jobId);
21624
- if (!job || job.status !== "pending") return { sent: 0, pruned: 0, retried: 0, dropped: 0 };
21854
+ if (!job || job.status !== "in_flight") return { sent: 0, pruned: 0, retried: 0, dropped: 0 };
21625
21855
  const completed = new Set(job.deliveredBindingIds);
21626
21856
  const targets = job.targetBindingIds.map((id) => owner.bindings.find((binding) => binding.bindingId === id)).filter((binding) => !!binding && !completed.has(binding.bindingId) && binding.vapidFingerprint === this.state.vapidFingerprint && binding.configEpoch === this.state.configEpoch);
21627
21857
  let sent = 0;
@@ -21641,7 +21871,7 @@ var PushStore = class _PushStore {
21641
21871
  sent++;
21642
21872
  } catch (error) {
21643
21873
  const status = error.statusCode;
21644
- if (status === 404 || status === 410) {
21874
+ if (status === 404 || status === 410 || invalidDeviceToken(error)) {
21645
21875
  completed.add(binding.bindingId);
21646
21876
  dead.add(binding.bindingId);
21647
21877
  pruned++;
@@ -21655,28 +21885,66 @@ var PushStore = class _PushStore {
21655
21885
  }));
21656
21886
  if (dead.size) owner.bindings = owner.bindings.filter((binding) => !dead.has(binding.bindingId));
21657
21887
  Object.assign(job, { deliveredBindingIds: [...completed], sentCount: job.sentCount + sent });
21888
+ owner.metrics.delivered += sent;
21889
+ owner.metrics.permanentFailures += dropped;
21890
+ owner.metrics.prunedBindings += pruned;
21658
21891
  const outstanding = job.targetBindingIds.some((id) => !completed.has(id) && owner.bindings.some((binding) => binding.bindingId === id));
21659
21892
  if (outstanding && transient) {
21660
21893
  this.persist();
21661
21894
  const retried = this.retryJob(job.id, now, random) ? transient : 0;
21662
21895
  return { sent, pruned, retried, dropped: dropped + (retried ? 0 : transient) };
21663
21896
  }
21664
- Object.assign(job, { status: job.sentCount > 0 ? "sent" : "dropped", completedAt: now });
21897
+ if (dropped > 0 && job.sentCount === 0) owner.metrics.dropped++;
21898
+ this.finishJob(owner, job, now);
21665
21899
  this.persist();
21666
21900
  return { sent, pruned, retried: 0, dropped };
21667
21901
  }
21668
21902
  queueStats() {
21669
- const jobs = identityState(this.state, this.identityCid).jobs;
21903
+ const owner = identityState(this.state, this.identityCid);
21904
+ const jobs = owner.jobs;
21670
21905
  return {
21671
- pending: jobs.filter((job) => job.status === "pending").length,
21672
- sent: jobs.filter((job) => job.status === "sent").length,
21673
- dropped: jobs.filter((job) => job.status === "dropped").length,
21674
- depth: jobs.length
21906
+ pending: jobs.length,
21907
+ inFlight: jobs.filter((job) => job.status === "in_flight").length,
21908
+ sent: owner.metrics.delivered + owner.metrics.suppressed,
21909
+ dropped: owner.metrics.dropped,
21910
+ depth: jobs.length + owner.tombstones.length,
21911
+ tombstones: owner.tombstones.length,
21912
+ capacity: MAX_RECORDS_PER_IDENTITY,
21913
+ saturated: owner.saturated,
21914
+ healthy: !owner.saturated,
21915
+ admissionFailures: owner.metrics.admissionFailures,
21916
+ delivered: owner.metrics.delivered,
21917
+ retries: owner.metrics.retries,
21918
+ permanentFailures: owner.metrics.permanentFailures,
21919
+ prunedBindings: owner.metrics.prunedBindings,
21920
+ ...owner.metrics.lastSaturationAt === void 0 ? {} : { lastSaturationAt: owner.metrics.lastSaturationAt }
21675
21921
  };
21676
21922
  }
21677
- pruneTerminal(now) {
21678
- for (const identity of Object.values(this.state.identities)) {
21679
- identity.jobs = identity.jobs.filter((job) => job.status === "pending" || job.completedAt === void 0 || now - job.completedAt <= TERMINAL_RETENTION_MS);
21923
+ finishJob(owner, job, now) {
21924
+ owner.jobs = owner.jobs.filter((row) => row.id !== job.id);
21925
+ owner.tombstones.push({ id: job.id, completedAt: now, expiresAt: now + TOMBSTONE_RETENTION_MS });
21926
+ this.pruneTombstones(owner, now);
21927
+ }
21928
+ pruneTombstones(owner, now) {
21929
+ const before = owner.tombstones.length;
21930
+ owner.tombstones = owner.tombstones.filter((row) => row.expiresAt > now).sort((a4, b3) => a4.completedAt - b3.completedAt);
21931
+ if (owner.tombstones.length > MAX_TOMBSTONES_PER_IDENTITY) {
21932
+ owner.tombstones.splice(0, owner.tombstones.length - MAX_TOMBSTONES_PER_IDENTITY);
21933
+ }
21934
+ owner.metrics.tombstonesPruned += before - owner.tombstones.length;
21935
+ }
21936
+ recoverAndPrune(now) {
21937
+ for (const owner of Object.values(this.state.identities)) {
21938
+ for (const job of owner.jobs) {
21939
+ if (job.status === "in_flight") {
21940
+ Object.assign(job, { status: "pending", nextAttemptAt: Math.min(job.nextAttemptAt, now) });
21941
+ delete job.inFlightAt;
21942
+ }
21943
+ }
21944
+ this.pruneTombstones(owner, now);
21945
+ if (owner.jobs.length < MAX_RECORDS_PER_IDENTITY && owner.notificationPage === void 0) {
21946
+ owner.saturated = false;
21947
+ }
21680
21948
  }
21681
21949
  }
21682
21950
  /** Compatibility for tests/old callers; durable production delivery uses jobs. */
@@ -21684,6 +21952,7 @@ var PushStore = class _PushStore {
21684
21952
  const wire = `compat-${randomUUID()}`;
21685
21953
  this.enqueueJob({ wireId: wire, kind: event.kind, contactId: event.contact_id });
21686
21954
  const id = `${this.identityCid}:${wire}:${event.kind}`;
21955
+ this.claimJob(id);
21687
21956
  const result = await this.dispatchJob(id, event);
21688
21957
  return { sent: result.sent, pruned: result.pruned, failed: result.retried + result.dropped, errors: [] };
21689
21958
  }
@@ -21692,6 +21961,7 @@ var PushStore = class _PushStore {
21692
21961
  // src/push-delivery.ts
21693
21962
  init_define_MESSENGER_BUILD_INFO();
21694
21963
  var nonEmpty3 = (value) => typeof value === "string" && value.length > 0;
21964
+ var bounded = (value, max) => nonEmpty3(value) && value.length <= max;
21695
21965
  function notificationKind(record) {
21696
21966
  if (record.event === "message_received") return "message";
21697
21967
  if (record.event !== "file_received") return null;
@@ -21699,7 +21969,7 @@ function notificationKind(record) {
21699
21969
  if (record.kind === "photo") return "photo";
21700
21970
  return "file";
21701
21971
  }
21702
- async function projectFromSdk(client, job) {
21972
+ async function projectPushEvent(client, job) {
21703
21973
  const url = `/chats/${encodeURIComponent(job.contactId)}#chat-message-${encodeURIComponent(job.wireId)}`;
21704
21974
  if (job.kind === "message") {
21705
21975
  if (!client.getHistoryItem) throw new Error("message projection is unavailable");
@@ -21736,7 +22006,15 @@ async function projectFromSdk(client, job) {
21736
22006
  };
21737
22007
  }
21738
22008
  var PushDeliveryQueue = class {
21739
- stats = { queued: 0, sent: 0, pruned: 0, retried: 0, dropped: 0, suppressed: 0 };
22009
+ stats = {
22010
+ queued: 0,
22011
+ sent: 0,
22012
+ pruned: 0,
22013
+ retried: 0,
22014
+ dropped: 0,
22015
+ suppressed: 0,
22016
+ saturationEvents: 0
22017
+ };
21740
22018
  store;
21741
22019
  client;
21742
22020
  identityCid;
@@ -21757,39 +22035,50 @@ var PushDeliveryQueue = class {
21757
22035
  this.log = options.log;
21758
22036
  this.now = options.now ?? Date.now;
21759
22037
  this.random = options.random ?? Math.random;
21760
- this.project = options.project ?? ((job) => projectFromSdk(this.client, job));
22038
+ this.project = options.project ?? ((job) => projectPushEvent(this.client, job));
21761
22039
  this.isForeground = options.isForeground ?? (() => false);
21762
22040
  this.foregroundBindingIds = options.foregroundBindingIds ?? (() => /* @__PURE__ */ new Set());
21763
22041
  this.automatic = options.autoStart !== false;
21764
22042
  if (this.automatic) this.schedule(0);
21765
22043
  }
21766
22044
  enqueue(record) {
21767
- if (!nonEmpty3(record.sender_id) || !nonEmpty3(record.wire_id)) return false;
22045
+ return this.admit(record).status === "queued";
22046
+ }
22047
+ admit(record, notificationPageNextIndex) {
22048
+ if (!bounded(record.sender_id, 512) || !bounded(record.wire_id, 1024)) return { status: "no_targets" };
21768
22049
  const kind = notificationKind(record);
21769
- if (!kind || !this.store.hasBindings()) return false;
21770
- const queued = this.store.enqueueJob({
22050
+ if (!kind || !this.store.hasBindings()) return { status: "no_targets" };
22051
+ const admission = this.store.admitJob({
21771
22052
  wireId: record.wire_id,
21772
22053
  kind,
21773
22054
  contactId: record.sender_id,
21774
- senderName: nonEmpty3(record.sender_name) ? record.sender_name : void 0
21775
- }, this.now());
21776
- if (queued) {
22055
+ senderName: bounded(record.sender_name, 512) ? record.sender_name : void 0
22056
+ }, this.now(), notificationPageNextIndex);
22057
+ if (admission.status === "saturated") {
22058
+ this.stats.saturationEvents++;
22059
+ const state = this.store.queueStats();
22060
+ this.log.warn(
22061
+ `event=push_queue_saturated status=saturated wire=${record.wire_id} kind=${kind} pending=${state.pending} depth=${state.depth} capacity=${state.capacity} admission_failures=${state.admissionFailures}`
22062
+ );
22063
+ return admission;
22064
+ }
22065
+ if (admission.status === "queued") {
21777
22066
  this.stats.queued++;
21778
22067
  const jobId = `${this.identityCid}:${record.wire_id}:${kind}`;
21779
22068
  const suppressedBindings = this.store.suppressBindings(jobId, this.foregroundBindingIds(), this.now());
21780
22069
  if (suppressedBindings > 0) {
21781
22070
  this.stats.suppressed += suppressedBindings;
21782
22071
  this.log.info(`push queue: suppressed ${suppressedBindings} foreground binding(s) kind=${kind} wire=${record.wire_id}`);
21783
- if (this.store.queueStats().pending === 0) return true;
22072
+ if (this.store.queueStats().pending === 0) return admission;
21784
22073
  } else if (this.isForeground() && this.store.suppressJob(jobId, this.now())) {
21785
22074
  this.stats.suppressed++;
21786
22075
  this.log.info(`push queue: suppressed foreground kind=${kind} wire=${record.wire_id}`);
21787
- return true;
22076
+ return admission;
21788
22077
  }
21789
22078
  this.log.info(`push queue: queued kind=${kind} wire=${record.wire_id} depth=${this.store.queueStats().pending}`);
21790
22079
  if (this.automatic) this.schedule(0);
21791
22080
  }
21792
- return queued;
22081
+ return admission;
21793
22082
  }
21794
22083
  async drainDue() {
21795
22084
  if (this.draining) return this.draining;
@@ -21805,6 +22094,7 @@ var PushDeliveryQueue = class {
21805
22094
  async drainOnce() {
21806
22095
  for (const job of this.store.dueJobs(this.now())) {
21807
22096
  if (this.stopped) return;
22097
+ if (!this.store.claimJob(job.id, this.now())) continue;
21808
22098
  let event;
21809
22099
  try {
21810
22100
  event = await this.project(job);
@@ -21851,77 +22141,51 @@ var PushDeliveryQueue = class {
21851
22141
 
21852
22142
  // src/watch.ts
21853
22143
  init_define_MESSENGER_BUILD_INFO();
21854
- var nonEmpty4 = (value) => typeof value === "string" && value.length > 0;
21855
- async function pushEventFor(client, record) {
21856
- if (!nonEmpty4(record.sender_id) || !nonEmpty4(record.wire_id)) return null;
21857
- const contactId = record.sender_id;
21858
- const wireId = record.wire_id;
21859
- const url = `/chats/${encodeURIComponent(contactId)}`;
21860
- if (record.event === "message_received") {
21861
- let message;
21862
- for (const delay of [0, 50, 200, 500]) {
21863
- if (delay) await new Promise((resolve2) => setTimeout(resolve2, delay));
21864
- message = await client.getHistoryItem({ wire_id: wireId }) ?? void 0;
21865
- if (message?.direction !== "in" || message.peer.id !== contactId) message = void 0;
21866
- if (message) break;
21867
- }
21868
- if (!message) throw new Error("canonical message was not available for push projection");
21869
- const announcedSender = nonEmpty4(record.sender_name) ? record.sender_name : "New message";
21870
- const sender = contactDisplayName(announcedSender);
21871
- const body = contactMessagePreview(announcedSender, message.text);
21872
- return { v: 1, kind: "message", title: sender, body, contact_id: contactId, wire_id: wireId, url };
22144
+ import { createHash as createHash4 } from "node:crypto";
22145
+ function pageDigest(events) {
22146
+ return createHash4("sha256").update(JSON.stringify(events)).digest("base64url");
22147
+ }
22148
+ function applyNotificationPage(page, push, delivery, events, stats, log2) {
22149
+ if (!Number.isSafeInteger(page.cursor) || page.cursor < 0 || !Array.isArray(page.events)) {
22150
+ throw new Error("daemon notification page is malformed");
22151
+ }
22152
+ const fromCursor = push.notificationCursor;
22153
+ if (fromCursor === null) {
22154
+ if (page.events.length > 0) throw new Error("daemon notification tip page unexpectedly contained events");
22155
+ push.commitNotificationCursor(page.cursor);
22156
+ stats.cursorCommits++;
22157
+ return true;
21873
22158
  }
21874
- if (record.event === "file_received") {
21875
- let file;
21876
- for (const delay of [0, 50, 200, 500]) {
21877
- if (delay) await new Promise((resolve2) => setTimeout(resolve2, delay));
21878
- file = await client.getFileInfo({ wire_id: wireId }) ?? void 0;
21879
- if (file?.direction !== "in" || file.peer.id !== contactId) file = void 0;
21880
- if (file) break;
21881
- }
21882
- if (!file) throw new Error("canonical file metadata was not available for push projection");
21883
- const kind = file.kind === "voice_message" ? "voice" : file.mime.toLowerCase().startsWith("image/") ? "photo" : "file";
21884
- const label = kind === "voice" ? "Voice message" : kind === "photo" ? "Photo" : "File";
21885
- return {
21886
- v: 1,
21887
- kind,
21888
- title: contactDisplayName(file.from.name || label),
21889
- body: `${label}: ${file.filename}`,
21890
- contact_id: contactId,
21891
- wire_id: wireId,
21892
- url
21893
- };
22159
+ let progress = push.notificationPageProgress;
22160
+ if (progress) {
22161
+ const prefix = page.events.slice(0, progress.eventCount);
22162
+ if (progress.fromCursor !== fromCursor || page.cursor < progress.cursor || page.events.length < progress.eventCount || pageDigest(prefix) !== progress.digest) {
22163
+ throw new Error("daemon notification page changed while a checkpoint was in progress");
22164
+ }
22165
+ } else {
22166
+ progress = push.beginNotificationPage({
22167
+ fromCursor,
22168
+ cursor: page.cursor,
22169
+ eventCount: page.events.length,
22170
+ digest: pageDigest(page.events)
22171
+ });
21894
22172
  }
21895
- return null;
21896
- }
21897
- async function reconcileMissed(client, delivery, log2) {
21898
- let queued = 0;
21899
- const messages = await client.listIncomingMessages();
21900
- for (const m3 of messages) {
21901
- if (m3.status !== "unread") continue;
21902
- if (delivery.enqueue({
21903
- event: "message_received",
21904
- sender_id: m3.from.id,
21905
- sender_name: m3.from.name,
21906
- wire_id: m3.wire_id
21907
- })) queued++;
21908
- }
21909
- const files = await client.listIncomingFiles();
21910
- for (const f4 of files) {
21911
- if (f4.status !== "unread") continue;
21912
- if (delivery.enqueue({
21913
- event: "file_received",
21914
- sender_id: f4.from.id,
21915
- sender_name: f4.from.name,
21916
- wire_id: f4.wire_id,
21917
- // notificationKind() reads `kind` to split voice from file; passing the
21918
- // raw value keeps that decision in one place rather than duplicating it
21919
- // here, and matches what the live daemon record carries.
21920
- kind: f4.kind
21921
- })) queued++;
21922
- }
21923
- if (queued > 0) log2.info(`watch reconcile: queued ${queued} push(es) for notifications missed while disconnected`);
21924
- return queued;
22173
+ for (let index = progress.nextIndex; index < progress.eventCount; index++) {
22174
+ const record = page.events[index];
22175
+ const admission = delivery.admit(record, index + 1);
22176
+ if (admission.status === "saturated") {
22177
+ push.checkpointNotificationPage();
22178
+ stats.saturationEvents++;
22179
+ log2.warn(`event=push_cursor_blocked status=saturated source_cursor=${page.cursor}`);
22180
+ return false;
22181
+ }
22182
+ stats.events++;
22183
+ events.publish(normalizeNotification(record));
22184
+ if (!admission.checkpointed) push.advanceNotificationPage(index + 1);
22185
+ }
22186
+ push.commitNotificationPage();
22187
+ stats.cursorCommits++;
22188
+ return true;
21925
22189
  }
21926
22190
  function abortableWait(ms, signal) {
21927
22191
  if (signal.aborted) return Promise.resolve();
@@ -21935,12 +22199,11 @@ function abortableWait(ms, signal) {
21935
22199
  signal.addEventListener("abort", done, { once: true });
21936
22200
  });
21937
22201
  }
21938
- function startWatcher(client, identity, push, log2, events, options = {}) {
22202
+ function startWatcher(client, identity, push, log2, events, options) {
21939
22203
  const controller = new AbortController();
21940
- const stats = { pushes: 0, events: 0, reconnects: 0 };
22204
+ const stats = { pushes: 0, events: 0, reconnects: 0, cursorCommits: 0, saturationEvents: 0 };
21941
22205
  let backoffMs = 500;
21942
22206
  let reconnecting = false;
21943
- const watch = options.watch ?? ((name, signal) => client.watchNotifications(name, { signal }));
21944
22207
  const wait = options.wait ?? abortableWait;
21945
22208
  const probe = options.probe ?? (() => client.version());
21946
22209
  const loop = (async () => {
@@ -21952,35 +22215,16 @@ function startWatcher(client, identity, push, log2, events, options = {}) {
21952
22215
  events.publish({ type: "sync_required", reason: "daemon_reconnected" });
21953
22216
  reconnecting = false;
21954
22217
  }
21955
- const upstream = watch(identity, controller.signal);
21956
- if (options.delivery) {
21957
- try {
21958
- await reconcileMissed(client, options.delivery, log2);
21959
- } catch (e) {
21960
- reportFailure(log2.warn, "watch reconcile", e);
21961
- }
21962
- }
21963
- for await (const record of upstream) {
21964
- if (controller.signal.aborted) break;
21965
- stats.events++;
21966
- backoffMs = 500;
21967
- events.publish(normalizeNotification(record));
21968
- try {
21969
- if (options.delivery) options.delivery.enqueue(record);
21970
- if (options.delivery) continue;
21971
- const event = await pushEventFor(client, record);
21972
- if (!event) continue;
21973
- const result = await push.send(event);
21974
- stats.pushes += result.sent;
21975
- if (result.pruned) log2.info(`push: pruned ${result.pruned} dead subscription(s)`);
21976
- if (result.failed) {
21977
- log2.warn(`push: ${result.failed} subscription(s) failed (kept for retry)`);
21978
- }
21979
- } catch (e) {
21980
- reportFailure(log2.warn, "push send", e);
21981
- }
22218
+ const since = push.notificationCursor ?? "tip";
22219
+ const page = await options.readPage(identity, since, controller.signal);
22220
+ if (controller.signal.aborted) break;
22221
+ const committed = applyNotificationPage(page, push, options.delivery, events, stats, log2);
22222
+ if (!committed) {
22223
+ await wait(backoffMs, controller.signal);
22224
+ backoffMs = Math.min(backoffMs * 2, 3e4);
22225
+ continue;
21982
22226
  }
21983
- if (!controller.signal.aborted) throw new Error("notification stream ended");
22227
+ backoffMs = 500;
21984
22228
  } catch (e) {
21985
22229
  if (controller.signal.aborted) break;
21986
22230
  stats.reconnects++;
@@ -22480,15 +22724,18 @@ async function start(cfg, buildInfo, dependencies = DEFAULT_START_DEPENDENCIES)
22480
22724
  log,
22481
22725
  foregroundBindingIds: () => presence.onlineBindings(bound.cid)
22482
22726
  });
22483
- watcher = startWatcher(runtime.client, cfg.identity, push, log, events, { delivery });
22727
+ watcher = startWatcher(runtime.client, cfg.identity, push, log, events, {
22728
+ delivery,
22729
+ readPage: runtime.readNotificationPage
22730
+ });
22484
22731
  const readyDeps = {
22485
22732
  runtime,
22486
22733
  push,
22487
22734
  config: cfg,
22488
22735
  buildInfo,
22489
22736
  watcherStats: () => ({
22490
- ...watcher?.stats ?? { pushes: 0, events: 0, reconnects: 0 },
22491
- ...delivery?.stats ?? { queued: 0, sent: 0, pruned: 0, retried: 0, dropped: 0 },
22737
+ ...watcher?.stats ?? { pushes: 0, events: 0, reconnects: 0, cursorCommits: 0, saturationEvents: 0 },
22738
+ ...delivery?.stats ?? { queued: 0, sent: 0, pruned: 0, retried: 0, dropped: 0, suppressed: 0, saturationEvents: 0 },
22492
22739
  presenceIdentities: presence.onlineCount,
22493
22740
  presenceSockets: presence.socketCount
22494
22741
  }),